Spring AOP使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议优先使用JDK的动态代理)
如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。 若该目标对象没有实现任何接口,则创建一个CGLIB代理。
如果你希望强制使用CGLIB代理,(例如:希望代理目标对象的所有方法,而不只是实现自接口的方法) 那也可以。但是需要考虑以下问题:
-
无法通知(advise)final方法,因为他们不能被覆写。
- 代理对象的构造器会被调用两次 。因为在CGLIB代理模式下每一个代理对象都会 产生一个子类。每一个代理实例会生成两个对象:实际代理对象和它的一个实现了通知的子类实例 而是用JDK代理时不会出现这样的行为。通常情况下,调用代理类型的构造器两次并不是问题, 因为除了会发生指派外没有任何真正的逻辑被实现。
- 且CGLib的效率没有使用JDK代理机制高,速度平均要慢8倍左右。
强制使用CGLIB代理需要将<aop:config>的proxy-target-class属性设为true:
1
|
<
aop:config
proxy-target-class
=
"true"
>
|
2
|
...
|
3
|
</
aop:config
>
|
当使用@AspectJ自动代理时要强制使用CGLIB,请将<aop:aspectj-autoproxy>的proxy-target-class属性设置为true:
<aop:aspectj-autoproxy
proxy-target-class="true"
/>
Spring AOP是 基于代理机制 的。深刻领会这一句的意思是非常重要的。
考虑如下场景,当你拿到一个无代理的、无任何特殊之处的POJO对象引用时,如以下代码段所示
01
|
public
class
SimplePojo
implements
Pojo {
|
02
|
public
void
foo() {
|
03
|
// this next method invocation is a direct call on the 'this' reference
|
04
|
this
.bar();
|
05
|
}
|
06
|
|
07
|
public
void
bar() {
|
08
|
// some logic...
|
09
|
}
|
10
|
}
|
当你调用一个对象引用的方法时,此对象引用上的方法 直接 被调用,如下所示
1
|
public
static
void
main(String[] args) {
|
2
|
Pojo pojo =
new
SimplePojo();
|
3
|
// this is a direct method call on the 'pojo' reference
|
4
|
pojo.foo();
|
5
|
}
|
当客户代码所持有的引用是一个代理的时候则略有不同了。请考虑如下图示和代码段片断
1
|
ProxyFactory proxyFactory =
new
ProxyFactory(
new
SimplePojo());
|
2
|
proxyFactory.addInterface(Pojo.
class
);
|
3
|
// 添加前置通知
|
4
|
proxyFactory.addAdvice(
new
BeforeAdviceImpl());
|
5
|
6
|
Pojo pojo = (Pojo) proxyFactory.getProxy();
|
7
|
pojo.foo();
|
理解此处的关键方法中的客户代码 拥有一个代理的引用 。这意味着对这个对象引用中方法的调用就是对代理的调用, 而这个代理能够代理所有跟特定方法调用相关的拦截器。不过, 一旦调用最终抵达了目标对象 (此处为SimplePojo类的引用), 任何对自身的调用例如this.bar()或者this.foo()将对 this 引用进行调用 而非代理。这一点意义重大, 它意味着自我调用将 不 会导致和方法调用关联的通知得到执行的机会。
那好,为此要怎么办呢?最好的办法就是重构你的代码使自我调用不会出现。 当然,这的确需要你做一些工作,但却是最好的,最少侵入性的方法。另一个方法则很可怕, 也正因为如此我几乎不愿指出这种方法。你可以象如下这样完全把业务逻辑写在你的Spring AOP类中:
01
|
public
class
SimplePojo
implements
Pojo {
|
02
|
public
void
foo() {
|
03
|
// this works, but... gah!
|
04
|
((Pojo) AopContext.currentProxy()).bar();
|
05
|
}
|
06
|
|
07
|
public
void
bar() {
|
08
|
// some logic...
|
09
|
}
|
10
|
}
|
这样完全将你的代码交给了Spring AOP, 并且 让类本身知道它正被用于一个AOP的上下文中, 而它其中的文件直接面对AOP。当代理在被创建时也需要一些额外的配置:
01
|
public
static
void
main(String[] args) {
|
02
|
ProxyFactory factory =
new
ProxyFactory(
new
SimplePojo());
|
03
|
factory.adddInterface(Pojo.
class
);
|
04
|
factory.addAdvice(
new
RetryAdvice());
|
05
|
factory.setExposeProxy(
true
);
|
06
|
|
07
|
Pojo pojo = (Pojo) factory.getProxy();
|
08
|
|
09
|
// this is a method call on the proxy!
|
10
|
pojo.foo();
|
11
|
}
|
上面的例子中用到了Spring AOP中ProxyFactory这些特定的API。在使用Spring容器配置的环境下也同样有此问题,同样以之前的支付为例:
01
|
public
interface
IPayService {
|
02
|
String pay(
long
userId,
long
money);
|
03
|
String inner();
|
04
|
}
|
05
|
// 实现类,在pay方法中调用了inner()方法
|
06
|
@Service
|
07
|
public
class
RMBPayService
implements
IPayService {
|
08
|
private
static
final
Logger LOGGER = LoggerFactory.getLogger(RMBPayService.
class
);
|
09
|
10
|
@Override
|
11
|
public
String pay(
long
userId,
long
money) {
|
12
|
LOGGER.info(
"用户:"
+ userId +
"使用人民币支付金额:"
+ money);
|
13
|
inner();
|
14
|
return
"ok"
;
|
15
|
}
|
16
|
17
|
@Override
|
18
|
public
String inner() {
|
19
|
LOGGER.info(
"inner method,can you see the aop advice...."
);
|
20
|
return
"go"
;
|
21
|
}
|
22
|
}
|
这样从容器中获取RMBPayService实例对象时,调用pay()方法,则只能在pay()方法环境下看到aop的特性,而在inner()中则看不到,可以使用如下方法来解决(虽然不建议):
01
|
@Service
|
02
|
public
class
RMBPayService
implements
IPayService {
|
03
|
private
static
final
Logger LOGGER = LoggerFactory.getLogger(RMBPayService.
class
);
|
04
|
05
|
@Override
|
06
|
public
String pay(
long
userId,
long
money) {
|
07
|
LOGGER.info(
"用户:"
+ userId +
"使用人民币支付金额:"
+ money);
|
08
|
09
|
ApplicationContext ctx =
new
ClassPathXmlApplicationContext(
"applicationContext.xml"
);
|
10
|
11
|
IPayService service = ctx.getBean(RMBPayService.
class
);
|
12
|
service.inner();
|
13
|
return
"ok"
;
|
14
|
}
|
15
|
16
|
@Override
|
17
|
public
String inner() {
|
18
|
19
|
LOGGER.info(
"inner method,can you see the aop advice...."
);
|
20
|
return
"go"
;
|
21
|
}
|
22
|
}
|