Spring事务与JDK/CGLIB动态代理

  • 首先Spring事务在开发过程中是通过@Transactional注解来控制。

1. Transactional注解的原理

  • 对于使用了Transactional注解的方法的类,Spring AOP代理会在运行时生成这个类的代理对象。
  • 当这个对象运行这个注解方法时,会读取@Transactional注解里面配置的信息,决定该方法是否要使用TransactionInterceptor拦截器来拦截。
  • 当拦截器拦截该方法时,会在调用该方法之前,创建事务,并在这个事务中执行该方法,最后根据执行是否有异常使用抽象事务管理器(AbstractPlatformTransactionManager)操作数据源提交或者回滚这个事务。
  • 它本质使用一个事务拦截器,在方法调用的前后/周围进行事务性增强(advice),来驱动事务完成。也就是在业务方法外边通过Spring AOP包上一层事务管理器的代码(即插入切面),这是Java设计模式中常见的通过代理增强被代理类的做法。

2. JDK 和 CGLib动态代理区别

Spring AOP的底层有2种实现:JDK动态代理、CGLIB。

  • 前者的原理是JDK反射,并且只支持Java接口的代理;
  • 后者的原理是继承(extend)与覆写(override),因此能支持普通的Java类的代理。两种方式都是动态代理,即运行时实时生成代理。

由于JVM的限制,CGLIB无法替换被代理类已经被载入的字节码,只能生成并载入一个新的子类作为代理类,被代理类的字节码依然存在于JVM中。

  • 区别于前两者,AspectJ是一种静态代理的实现,即在编译时或者载入类时直接修改被代理类文件的字节码,而非运行时实时生成代理。因此这种方式需要额外的编译器或者JVM Agent支持,通过一些配置Spring和AspectJ也可以配合使用。

@Aspect一开始是AspectJ推出的Java注解形式,后来Spring AOP也支持使用这种形式表示切面,但实际上底层实现和AspectJ毫无关系,毕竟Spring AOP是动态代理,和静态代理是不兼容的。

2.1 JDK动态代理具体实现原理:
  • 通过实现InvocationHandlet接口创建自己的调用处理器;
  • 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
  • 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
  • JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
2.2 CGLib动态代理:

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。

2.3 两者对比:

JDK动态代理是面向接口的。
CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,会失败)。

  • 如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
  • 如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

3. 两个问题

针对面试官的提问和回来后自己的测试和了解,有两个问题:

  1. 在一个实例方法中调用被@Transactional注解标记的另一个方法,且两个方法都属于同一个类时,事务不会生效。
  2. 调用被@Transactional注解标记的非public方法,事务不会生效。
4. 关于问题
4.1 自调用失效

一个例子,有如下调用逻辑:

controller部分代码

@RequestMapping("/transaction")
public Response test() {
    testService.create();
    return new Response();
}

TestService部分代码

@Override
public void create() {
    ....
    insert();
    ....
}

@Transactional(rollbackFor = Exception.class)
public void insert() {
    insertBid();
}

private User insertTest() {
    User user = new User();
    user.setName("xx");
    user.setId(100);
    userMapper.insert(user);
    return user;
}
  • TestService的对外接口是create(),但不想整个方法做事务,只对于insert()里面的操作做事务。
    但这种写法是无效的。虽然执行时候不会有任何报错异常,但一旦insert()方法执行过程中抛出异常,事务是不会生效的,即使方法是public也没用(Transaction注解要求作用于public的方法上)
@Transactional 的AOP切点
  • 上述问题涉及到@Transactional 的AOP切点。
  • Spring AOP是与IOC配合使用的,而Spring AOP是用动态代理的技术。 也就是说一个类被IOC所注入生成的对象被Spring动态代理成一个新的代理对象。
  • 无效的原因其实就容易理解了,在外部调用这个动态代理对象,会在代理的时候增强对象,但在对象的内部调用的时候,调用的还是原来的对象的方法,该方法明细不会被AOP增强。
  • 如上面的例子,在Spring里,TestService注入到controller中,生成一个代理的testService对象,在controller调用testService的方法时,被代理的方法拦截。该方法中会找出这个AOP连接点的Advice,然后切入执行(也就是执行@Transactional)。而若是调用对象内容方法的时候,就不会被代理发放拦截的了。
  • 下图即为拦截切入点,框内为切入后执行该切点的Advice,事务就在其中执行
    在这里插入图片描述
4.2 不支持非public

第二个问题则是Spring AOP不支持非public方法增强,与自调用类似,也是动态代理无法解决的盲区。

  • 从代码分析原因是TransactionInterceptor拦截器会间接调用到一个AbstractFallbackTransactionAttributeSource里面的computeTransactionAttribute方法,通过这个方法来获取注解上的各个属性,同时判断该方法是否为public,若不是public则不会读取注解属性,返回null。

protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {// Don't allow no-public methods as required.if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}...}

虽然CGLIB通过继承的方式是可以支持public、protected、package级别的方法增强的,但是由于JDK动态代理必须通过Java接口,只能支持public级别的方法,因此Spring AOP不得不取消非public方法的支持。

5.总结

面试造航母就完事了。

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring AOP使用CGLIB作为默认的代理方式来实现动态代理。代理过程中,首先需要定义一个被代理的目标对象或者叫做目标类。该目标对象可以是任何一个Java对象,它会在运行时被代理创建。然后,通过Spring的配置文件或者注解,将该目标对象注入到Spring容器中。 接下来,需要定义一个切面类,该类中包含了代理的具体操作和条件。这些操作和条件可以是在目标对象的方法执行前后增加日志、事务控制、权限验证等。切面类通常会实现Advice接口或者继承AbstractAspectJAdvice类。 在Spring容器启动时,会根据配置文件或者注解自动扫描到切面类,并且生成代理对象。生成代理对象的方式有两种:JDK动态代理CGLIB动态代理。在Spring AOP中,默认使用CGLIB动态代理方式,因为CGLIB可以代理没有实现接口的目标类。 当目标对象的方法被调用时,代理对象会先调用切面类的相应方法,然后再调用目标对象的方法。这样,代理对象就可以在目标对象方法执行前后,添加自己的逻辑。 CGLIB是一个强大的第三方类库,它通过继承的方式创建代理对象。通过修改字节码,可以在目标类中创建子类,并将代理逻辑插入到子类的方法中,从而实现代理的功能。 总之,Spring AOP使用CGLIB作为默认的代理方式来实现动态代理。代理过程中,首先需要定义目标对象和切面类,然后通过配置文件或者注解将它们注入到Spring容器中。在运行时,Spring容器会自动生成代理对象,并在方法执行前后执行切面逻辑,实现代理的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值