Spring强制使用CGLIB代理事务

本文详细解析Spring AOP中的代理策略,包括JDK动态代理与CGLIB代理的区别,以及如何在Spring框架下选择合适的代理策略。通过案例演示静态与动态代理的实现方式,并对比两者在性能与应用场景上的差异。此外,文章还介绍了如何通过配置文件灵活调整代理策略,以适应不同的业务需求。
代理的核心就是拦截方法调用,并在需要的时候执行匹配某方法的通知链。

和CGLIB不同的是,JDK代理只能代理接口,不能代理类。

使用JDK代理时,如何处理一个特定的方法调用的决定是在程序运行时做出的,也就是在每次方法被调用时。使用CGLIB代理可以边开这种处理方法,CGLIB会在运行中随时为代理创建新类的字节码,并尽可能的重用已经生成的类的字节码。


Spring1.2:
将事务代理工厂[TransactionProxyFactoryBean] 或 自动代理拦截器[BeanNameAutoProxyCreator]
proxyTargetClass属性,设置为true,则使用CGLIB代理,此属性默认为false,使用JDK动态代理.
以下引用 Spring Framework reference 2.0.5:
Spring2.0:

Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议尽量使用JDK的动态代理)

如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。

如果你希望强制使用CGLIB代理,(例如:希望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以。但是需要考虑以下问题:

无法通知(advise)Final 方法,因为他们不能被覆写。
你需要将CGLIB 2二进制发行包放在classpath下面,与之相较JDK本身就提供了动态代理
强制使用CGLIB代理需要将 |aop:config| 的 proxy-target-class 属性设为true:

|aop:config proxy-target-class="true"|
...
|/aop:config|

当需要使用CGLIB代理和@AspectJ自动代理支持,请按照如下的方式设置 |aop:aspectj-autoproxy| 的 proxy-target-class 属性:

|aop:aspectj-autoproxy proxy-target-class="true"/|

而实际使用的过程中才会发现细节问题的差别,The devil is in the detail.JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。
Spring是依靠什么来判断采用哪种代理策略来生成AOP代理呢?以下代码就是Spring的判断逻辑

//org.springframework.aop.framework.DefaultAopProxyFactory
//参数AdvisedSupport 是Spring AOP配置相关类
publicAopProxy createAopProxy(AdvisedSupport advisedSupport)
throwsAopConfigException {
//在此判断使用JDK动态代理还是CGLIB代理
if(advisedSupport.isOptimize()||advisedSupport.isProxyTargetClass()
||hasNoUserSuppliedProxyInterfaces(advisedSupport)) {
if(!cglibAvailable) {
thrownewAopConfigException(
"Cannot proxy target class because CGLIB2 is not available."
+"Add CGLIB to the class path or specify proxy interfaces.");
}
returnCglibProxyFactory.createCglibProxy(advisedSupport);
}
else{
returnnewJdkDynamicAopProxy(advisedSupport);
}
}

advisedSupport.isOptimize()与advisedSupport.isProxyTargetClass()默认返回都是false,所以在默认情况下目标对象有没有实现接口决定着Spring采取的策略,当然可以设置advisedSupport.isOptimize()或者advisedSupport.isProxyTargetClass()返回为true,这样无论目标对象有没有实现接口Spring都会选择使用CGLIB代理。

所以在默认情况下,如果一个目标对象如果实现了接口Spring则会选择JDK动态代理策略动态的创建一个接口实现类(动态代理类)来代理目标对象,可以通俗的理解这个动态代理类是目标对象的另外一个版本,所以这两者之间在强制转换的时候会抛出java.lang.ClassCastException。而所以在默认情况下,如果目标对象没有实现任何接口,Spring会选择CGLIB代理, 其生成的动态代理对象是目标类的子类。

上说的是默认情况下,也可以手动配置一些选项使Spring采用CGLIB代理。
org.springframework.transaction.interceptor.TransactionProxyFactoryBean是org.springframework.aop.framework. ProxyConfig的子类,所以可以参照ProxyConfig里的一些设置如下所示,将optimize和proxyTargetClass任意一个设置为true都可以强制Spring采用CGLIB代理。

如果当需要使用CGLIB代理和@AspectJ自动代理支持,请按照如下的方式设置 |aop:aspectj-autoproxy| 的 proxy-target-class 属性
|aop:aspectj-autoproxy proxy-target-class="true"/|

这样使用CGLIB代理也就不会出现前面提到的ClassCastException问题了,也可以在性能上有所提高,关键是对于代理对象是否继承接口可以统一使用。





两种类型AOP:静态AOP和动态AOP。

静态代理:
代理对象与被代理对象必须实现同一个接口。

demo:

  1. packagecn.partner4java.proxy.staticproxy;
  2. /**
  3. * 静态代理,统一接口
  4. * @author partner4java
  5. *
  6. */
  7. publicinterfaceIHello {
  8. /**
  9. * 可以带来的统一方法
  10. * @param name
  11. */
  12. publicvoidhello(String name);
  13. }
  1. packagecn.partner4java.proxy.staticproxy;
  2. /**
  3. * 被代理的对象,需要借助代理对象加入日志
  4. * @author partner4java
  5. *
  6. */
  7. publicclassHelloSpeakerimplementsIHello {
  8. publicvoidhello(String name) {
  9. System.out.println("Hello "+ name);
  10. }
  11. }
  1. packagecn.partner4java.proxy.staticproxy;
  2. /**
  3. * 代理对象,给被代理对象添加日志
  4. */
  5. publicclassHelloProxyimplementsIHello {
  6. privateIHello iHello;
  7. publicHelloProxy(IHello iHello) {
  8. super();
  9. this.iHello = iHello;
  10. }
  11. publicvoidhello(String name) {
  12. System.out.println("记录日志");
  13. iHello.hello(name);
  14. }
  15. }
  1. packagecn.partner4java.proxy.staticproxy;
  2. /**
  3. * 调用
  4. * @author partner4java
  5. *
  6. */
  7. publicclassProxyDemo {
  8. publicstaticvoidmain(String[] args) {
  9. IHello iHello =newHelloProxy(newHelloSpeaker());
  10. iHello.hello("long");
  11. }
  12. }





动态代理:
动态代理区别于静态带来实现的地方在于织入过程是在运行时动态进行的。自己实现一般实现java.lang.reflect.InvocationHandler接口。
例子:
  1. packagecn.partner4java.proxy.dynamicproxy;
  2. publicinterfaceIHello {
  3. publicvoidhello(String name);
  4. }
  1. packagecn.partner4java.proxy.dynamicproxy;
  2. /**
  3. * 被代理的对象,需要借助代理对象加入日志
  4. * @author partner4java
  5. *
  6. */
  7. publicclassHelloSpeakerimplementsIHello {
  8. publicvoidhello(String name) {
  9. System.out.println("Hello "+ name);
  10. }
  11. }
  1. packagecn.partner4java.proxy.dynamicproxy;
  2. importjava.lang.reflect.InvocationHandler;
  3. importjava.lang.reflect.Method;
  4. importjava.lang.reflect.Proxy;
  5. /**
  6. * 动态代理对象
  7. * @author partner4java
  8. *
  9. */
  10. publicclassLogHandlerimplementsInvocationHandler {
  11. privateObject delegate;
  12. publicObject bind(Object delegate){
  13. this.delegate = delegate;
  14. returnProxy.newProxyInstance(delegate.getClass().getClassLoader(),
  15. delegate.getClass().getInterfaces(),this);
  16. }
  17. /**
  18. * 代理对象,这里面还可以改变原有的方法
  19. */
  20. publicObject invoke(Object proxy, Method method, Object[] args)
  21. throwsThrowable {
  22. Object result =null;
  23. try{
  24. System.out.println("添加日志");
  25. result = method.invoke(delegate, args);
  26. }catch(Exception e) {
  27. e.printStackTrace();
  28. }
  29. returnnull;
  30. }
  31. }
  1. packagecn.partner4java.proxy.dynamicproxy;
  2. /**
  3. * 测试
  4. * @author partner4java
  5. *
  6. */
  7. publicclassProxyDemo {
  8. publicstaticvoidmain(String[] args) {
  9. LogHandler logHandler =newLogHandler();
  10. IHello iHello = (IHello) logHandler.bind(newHelloSpeaker());
  11. iHello.hello("long");
  12. }
  13. }


------------------------------------------------------------------


利用ProxyFactory连接CGLIB简单实现AOP:
加入包aopalliance.jar\cglib-nodep-2.1_3.jar
demo:
  1. packagecn.partner4java.proxy.proxyfactory;
  2. /**
  3. * 被代理的对象
  4. * @author partner4java
  5. *
  6. */
  7. publicclassMessageWriter {
  8. publicvoidwriteMessage(){
  9. System.out.println("world!");
  10. }
  11. }
  1. packagecn.partner4java.proxy.proxyfactory;
  2. importorg.aopalliance.intercept.MethodInterceptor;
  3. importorg.aopalliance.intercept.MethodInvocation;
  4. /**
  5. * 装饰者<br/>
  6. * MethodInterceptor接口是对方法调用连接点实现包围通知的AOP联盟标准接口
  7. * @author partner4java
  8. *
  9. */
  10. publicclassMessageDecoratorimplementsMethodInterceptor{
  11. publicObject invoke(MethodInvocation invocation)throwsThrowable {
  12. System.out.print("Hello ");
  13. Object retVal = invocation.proceed();
  14. returnretVal;
  15. }
  16. }
  1. packagecn.partner4java.proxy.proxyfactory;
  2. importorg.springframework.aop.framework.ProxyFactory;
  3. /**
  4. * 调用组装
  5. * 这里最重要的部分是我们使用ProxyFactory来创建一个目标对象代理,同时织入通知
  6. * @author partner4java
  7. *
  8. */
  9. publicclassHelloWorldWeaver {
  10. publicstaticvoidmain(String[] args) {
  11. //目标
  12. MessageWriter target =newMessageWriter();
  13. //create the proxy
  14. ProxyFactory proxyFactory =newProxyFactory();
  15. proxyFactory.addAdvice(newMessageDecorator());
  16. proxyFactory.setTarget(target);
  17. //获取返回被代理的目标
  18. MessageWriter proxy = (MessageWriter) proxyFactory.getProxy();
  19. target.writeMessage();
  20. System.out.println("---");
  21. proxy.writeMessage();
  22. //后台打印:
  23. //world!
  24. //---
  25. //World world!
  26. }
  27. }




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值