Spring 事务失效那点事

工作满三年了,总觉得不看点源码啥的,肚子里没啥东西。前一阵打算实际的看点spring源码啥的看了,看了几天也没记住多少,还是带着问题分析源码记忆比较深
刻,好闲话少说,上问题。
前几天在处理事务的时候遇到了,事务不生效的情况。

public interface VideoTestService {

    void test1();

    void test2();

    void test3();

    void test4();

    void test5();
}

@Service
public class VideoTestServiceImpl implements VideoTestService{
    @Override
    public void test1() {
        System.out.println("test1()");
    }

    @Override
    public void test2() {
        System.out.println("test2()");
        test3();
    }

    @Override
    public void test3() {
        System.out.println("test3()");
    }

    @Override
    public void test4() {
        System.out.println("test4()");
        VideoTestService service = (VideoTestService)AopContext.currentProxy();
        service.test5();
    }

    @Override
    public void test5() {
        System.out.println("test3()");
    }
}

@Service
public class VideoTestService2 {

    private static final Logger logger = LoggerFactory.getLogger(VideoTestService2.class);

    public void test1() {
        System.out.println("test1()");
    }

    public void test2() {
        System.out.println("test2()");
        test3();
    }

    public void test3() {
        System.out.println("test3()");
    }
    public void test4() {
        System.out.println("test4()");
        VideoTestService2 service = (VideoTestService2)AopContext.currentProxy();
        service.test5();
    }

    public void test5() {
        System.out.println("test5()");
    }
    public void add() {
        System.out.println("add()");
        test1();

    }
}

事务切点配置 execution(* com.test.java.service..impl..test*(..))

@Aspect
public class ServiceConfigAspect {

    /**
     * 定义切入点函数
     */
    @Pointcut("execution(* com.test.java.service.impl.*.test*(..))")
    void pointcut() {
    }

    /**
     * 使用@Before声明前置通知
     * @param thisJoinPoint
     */
    @Before("pointcut()")
    public void beforeExecute(JoinPoint thisJoinPoint){
        System.out.println("before ...");
    }

    /**
     * 使用@After 注解声明后置通知
     * @param thisJoinPoint
     */
    @After("pointcut()")
    public void afterExecute(JoinPoint thisJoinPoint){
        System.out.println("end......");
    }
}

public class TestServiceAop extends BaseTest {

    @Autowired
    private VideoTestService videoTestService;

    @Autowired
    private VideoTestService2 videoTestService2;

    @Test
    public void test(){
         System.out.println(""+ AopUtils.isCglibProxy(videoTestService));
        System.out.println(""+AopUtils.isJdkDynamicProxy(videoTestService));
        System.out.println(""+ AopUtils.isCglibProxy(videoTestService2));
        System.out.println(""+AopUtils.isJdkDynamicProxy(videoTestService2));

        System.out.println("\n--------------------------");
        videoTestService.test1();

        videoTestService.test2();

        videoTestService.test4();

        System.out.println("\n--------------------------");

        videoTestService2.test1();

        videoTestService2.test2();

        videoTestService2.test4();

        videoTestService2.add();

        System.out.println("\n--------------------------");
     } 
   }

为了直观的显示过程,用ServiceConfigAspect模拟了 事务处理过程,把开启事务和事务提交用beforeExecute和afterExecute分别代替。
执行结果:
false
true
true
false


before …
test1()
end……
before …
test2()
test3()
end……
before …
test4()
before …
test3()
end……
end……


before …
test1()
end……
before …
test2()
test3()
end……
before …
test4()
before …
test5()
end……
end……
add()
test1()

为什么会出现这样的情况呢?我们自认为的事务videoTestService.test2(); 和videoTestService2.test2(); videoTestService2.add();都没生效,我们考虑下日常的写代码的过程中是不是也写过这样随意加事务(@Transactional)的代码,或是在一个事务里面嵌套this.methodB(事务方法)和一个普通类里面this.methodB (事务方法),其实这样都是不生效的。下面我们来详细的分析下原因。
首先对于VideoTestService和VideoTestService2的区别是一个是接口的实现类bean另一个是普通的bean,这两个在spring中使用的是不同的方式生成。
Aop 技术其实可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;(AspectJ);而动态代理则在运行时借助于 默写类库在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。其中java是使用的动态代理模式 (JDK+CGlib)。
Spring的动态代理包括两个部分:
· JDK动态代理
JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

· CGLib动态代理
CGLib全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。
和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。
这是为什么jdk 会有两种方式生成代理对象,而这两者的切换是对开发者透明的,我们去翻看源码;

@Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

一目了然,可以看到其中的代理对象可以由JDK或者Cglib来生成的,JdkDynamicAopProxy类和Cglib2AopProxy都实现的是AopProxy的接口,上面的这些逻辑就是要判断采用两种动态代理中的那一种。我们又会问了
既然 CGLib 可以代理任何的类了,那为什么还要用 JDK 的动态代理呢?其实聪明的老罗也想到了,参考网上的观点:CGLib 创建代理的速度比较慢,但创建代理后运行的速度却非常快,而 JDK 动态代理正好相反。如果在运行的时候不断地用 CGLib 去创建代理,系统的性能会大打折扣。
目前我们解析了前面的 false、true、true、false。
这里写图片描述
借用网上的图片,图片展示了过程,我们到达代理对象前test2(),spring为我们开启事务,然后执行模板对象的test2()方法,此时我们内部有个test3()方法都认为是本地test2()的一部分。执行完之后关闭事务。所以总结来说如果持有代理对象已经到达目标对象,然后执行所有的事务方法都是本地方法,不会有事务起作用。
解决之法:
像test4() 这样,使用AopContext.currentProxy();相当于我们又回到了代理对象,使得事务重新起作用。
这里写图片描述
下面列举JdkDynamicAopProxy jdk方式生成代理对象时的源码:
在执行被调用方法的时候 invoke()

 public Object getProxy(ClassLoader classLoader) {
        if(logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }

        Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
        this.findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); //this 就是JdkDynamicAopProxy实现了InvocationHandler,所以invoke 会被调用。
    }
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 //代码片段    
       ......
            // 得到拦截器链
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

            // 检查是否定义了拦截器方法,如果没有的话直接调用目标方法

            if (chain.isEmpty()) {

                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
            }
            else {
                //我们需要创建一个调用方法
                invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                // proceed内部实现了递归调用遍历拦截器链
                retVal = invocation.proceed();
            }
......

invocation.proceed(); //源码
@Override
    public Object proceed() throws Throwable {
        //  We start with an index of -1 and increment early.
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint(); //执行目标方法
        }

        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            // Evaluate dynamic method matcher here: static part will already have
            // been evaluated and found to match.
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
                return dm.interceptor.invoke(this);
            }
            else {
                // Dynamic matching failed.
                // Skip this interceptor and invoke the next in the chain.
                return proceed();
            }
        }
        else {
            // It's an interceptor, so we just invoke it: The pointcut will have
            // been evaluated statically before this object was constructed.
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

//展示了执行完所有拦截器后执行目标方法。因此此时的test2()内部的test()都相当于内部方法了,不会在调用拦截器了
protected Object invokeJoinpoint() throws Throwable {
        return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
    }

目前我们已经解释了有些情况下事务失效的原因。事情spring AOP 对于我们来说封装的太好了,所以有些东西对我们很透明,但其实只有我们深入内部一定会发现出现问题的原因。

对于spring中的AspectJ 和 MethodInterceptor 和RegexpMethodPointcutAdvisor 为啥共存http://www.importnew.com/21811.html 这篇文章已经写得很清楚了,有兴趣的同学可以看看。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值