• Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。


    借鉴:http://jinnianshilongnian.iteye.com/blog/1508018

    基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。

    我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Trasactional注解的事务管理,但在通过基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务是有区别的,我们接下来看看到底有哪些区别。

    一、基础工作

    实例SpringMVC + Spring4.3.8 + Mybatis3.2.6 + Logback 的项目,如下所示:

    1. 将xml声明式事务删除
      <!-- 切面 -->
      <tx:advice id="txAdvice" transaction-manager="transactionManager">
          <tx:attributes >
              <tx:method name="delete*" propagation="REQUIRED" />
              <tx:method name="insert*" propagation="REQUIRED" />
              <tx:method name="update*" propagation="REQUIRED" />
              <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
          </tx:attributes>
      </tx:advice>
      <aop:config >
          <aop:pointcut expression="execution(* com.only.mate.service.*.*(..))" id="serviceCutPoint"/>
          <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceCutPoint"/>
      </aop:config>
    2. 并添加注解式事务支持
      <!-- 编程式即采用注解的方式,事务扫描开始(开启注解@Tranctional) -->
      <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />
    3. 在我们的UserService接口上添加 @Transactional 使该方法开启事务
      package com.only.mate.service;
      
      import org.springframework.transaction.annotation.Transactional;
      
      import com.only.mate.entity.User;
      
      public interface UserService {
          public User findOne(String username);
         @Transactional public void save(User user); }
      package com.only.mate.service.impl;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      
      import com.only.mate.entity.User;
      import com.only.mate.repository.UserMapper;
      import com.only.mate.service.UserService;
      
      @Service
      public class UserServiceImpl implements UserService {
          @Autowired
          private UserMapper userMapper;
          
          @Override
          public User findOne(String username) {
              return userMapper.getUserByUserName(username);
          }
      
          @Override
          public void save(User user) {
              userMapper.insert(user);
              if("zhangsan".equals(user.getUserName())){
                  throw new RuntimeException();
              }
          }
      }
    4. 开启Logback日志的sql输出

      准备完毕

    二、基于JDK动态代理

    <!-- 编程式即采用注解的方式,事务扫描开始(开启注解@Tranctional) -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />

    @Transactional注解放置在接口(抽象类或抽象方法)和具体类(实现类或实现方法)上都可以,具体解析请看"七、问题"。

    运行访问,核心日志如下:                                    

    2017-11-08 16:36:52.231 耗时:1392016 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Acquired Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] for JDBC transaction
    2017-11-08 16:36:52.235 耗时:1392020 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] to manual commit
    Creating a new SqlSession
    Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ac24ea4]
    JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] will be managed by Spring
    ==>  Preparing: insert into user (id, username, password, sex, age, telphone, address) values (?, ?, ?, ?, ?, ?, ?) 
    ==> Parameters: null, zhangsan(String), 123(String), 男(String), 13(Integer), 14444444444(String), 12313213123123(String)
    <==    Updates: 1
    Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ac24ea4]
    Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ac24ea4]
    Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ac24ea4]
    2017-11-08 16:36:52.304 耗时:1392089 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Initiating transaction rollback
    2017-11-08 16:36:52.304 耗时:1392089 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1]
    2017-11-08 16:36:52.357 耗时:1392142 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] after transaction
    2017-11-08 16:36:52.357 耗时:1392142 日志来自:o.springframework.jdbc.datasource.DataSourceUtils 日志类型: DEBUG 日志内容:Returning JDBC Connection to DataSource

    到此我们可以看到事务起作用了,事务有回滚,也就是说即使把@Transactional放到接口上 基于JDK动态代理也是可以工作的。

    三、基于CGLIB类代理

    <!-- 编程式即采用注解的方式,事务扫描开始(开启注解@Tranctional) -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

    该配置方式是基于CGLIB类代理

    运行访问,核心日志如下:

    2017-11-08 16:44:58.079 耗时:8198 日志来自:org.springframework.web.servlet.DispatcherServlet 日志类型: DEBUG 日志内容:DispatcherServlet with name 'springServlet' processing POST request for [/SpringMVC/user/save]
    2017-11-08 16:44:58.088 耗时:8207 日志来自:o.s.w.s.m.a.DefaultAnnotationHandlerMapping 日志类型: DEBUG 日志内容:Mapping [/user/save] to HandlerExecutionChain with handler [com.only.mate.controller.UserController@4731f9a] and 1 interceptor
    2017-11-08 16:44:58.111 耗时:8230 日志来自:org.springframework.web.cors.DefaultCorsProcessor 日志类型: DEBUG 日志内容:Skip CORS processing: request is from same origin
    2017-11-08 16:44:58.119 耗时:8238 日志来自:o.s.w.bind.annotation.support.HandlerMethodInvoker 日志类型: DEBUG 日志内容:Invoking request handler method: public java.util.Map com.only.mate.controller.UserController.save(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
    2017-11-08 16:44:58.121 耗时:8240 日志来自:com.only.mate.controller.UserController 日志类型: DEBUG 日志内容:注册,用户名zhangsan,密码123
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@158e6b7f] was not registered for synchronization because synchronization is not active
    2017-11-08 16:44:58.164 耗时:8283 日志来自:o.springframework.jdbc.datasource.DataSourceUtils 日志类型: DEBUG 日志内容:Fetching JDBC Connection from DataSource
    2017-11-08 16:44:58.453 耗时:8572 日志来自:com.alibaba.druid.pool.DruidDataSource 日志类型: INFO  日志内容:{dataSource-1} inited
    JDBC Connection [com.mysql.jdbc.JDBC4Connection@39d16e44] will not be managed by Spring
    ==>  Preparing: insert into user (id, username, password, sex, age, telphone, address) values (?, ?, ?, ?, ?, ?, ?) 
    ==> Parameters: null, zhangsan(String), 123(String), 男(String), 13(Integer), 14444444444(String), 12313213123123(String)
    <==    Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@158e6b7f]
    2017-11-08 16:44:58.556 耗时:8675 日志来自:o.springframework.jdbc.datasource.DataSourceUtils 日志类型: DEBUG 日志内容:Returning JDBC Connection to DataSource

    到此我们可以看到事务没有起作用,事务没有回滚。

    只有将注解放在具体类上或具体类的实现方法上才会起作用

    package com.only.mate.service;
    
    import com.only.mate.entity.User;
    
    public interface UserService {
        public User findOne(String username);
        public void save(User user);
    }
    package com.only.mate.service.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.only.mate.entity.User;
    import com.only.mate.repository.UserMapper;
    import com.only.mate.service.UserService;
    
    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserMapper userMapper;
        
        @Override
        public User findOne(String username) {
            return userMapper.getUserByUserName(username);
        }
    
        @Override
    @Transactional public void save(User user) { userMapper.insert(user); if("zhangsan".equals(user.getUserName())){ throw new RuntimeException(); } } }

     运行访问,核心日志如下:

    2017-11-08 16:49:54.992 耗时:3784 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Acquired Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] for JDBC transaction
    2017-11-08 16:49:54.997 耗时:3789 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] to manual commit
    Creating a new SqlSession
    Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ff32a24]
    JDBC Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] will be managed by Spring
    ==>  Preparing: insert into user (id, username, password, sex, age, telphone, address) values (?, ?, ?, ?, ?, ?, ?) 
    ==> Parameters: null, zhangsan(String), 123(String), 男(String), 13(Integer), 14444444444(String), 12313213123123(String)
    <==    Updates: 1
    Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ff32a24]
    Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ff32a24]
    Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2ff32a24]
    2017-11-08 16:49:55.070 耗时:3862 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Initiating transaction rollback
    2017-11-08 16:49:55.070 耗时:3862 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6]
    2017-11-08 16:49:55.124 耗时:3916 日志来自:o.s.jdbc.datasource.DataSourceTransactionManager 日志类型: DEBUG 日志内容:Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] after transaction
    2017-11-08 16:49:55.124 耗时:3916 日志来自:o.springframework.jdbc.datasource.DataSourceUtils 日志类型: DEBUG 日志内容:Returning JDBC Connection to DataSource

    运行测试类,将发现成功了。

     还有一种情况如下:

    package com.only.mate.service.impl;
    
    
    @Service
    public class StudentServiceImpl extends UserServiceImpl implements StudentService {
        @Autowired
        private UserMapper userMapper;
        
        @Override
        public User findOne(String username) {
            return userMapper.getUserByUserName(username);
        }
    //没有@Transactional  @Override
    public void save(User user) { userMapper.insert(user); if("zhangsan".equals(user.getUserName())){ throw new RuntimeException(); } } }

    这种情况也将无法织入事务。

    四、基于aspectj的

    <tx:annotation-driven transaction-manager="transactionManager" mode="aspectj" proxy-target-class="true"/>

    在此就不演示了,我们主要分析基于JDK动态代理和CGLIB类代理两种的区别。

    五、结论

    基于JDK动态代理 ,可以将@Transactional放置在接口(抽象类或抽象方法)和具体类实现类或实现方法)上。

    基于CGLIB类代理,只能将@Transactional放置在具体类实现类或实现方法)上。

    因此 在实际开发时全部将@Transactional放到具体类上,而不是接口上。

    六、分析

    1、  JDK动态代理

    1.1、Spring使用JdkDynamicAopProxy实现代理:

    package org.springframework.aop.framework;  
    final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {  
        //注意此处的method 一定是接口上的method(因此放置在接口上的@Transactional是可以发现的)  
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        }  
    } 

    注意此处的method 一定是接口上的method(因此放置在接口上的@Transactional是可以发现的)

    1.2、如果<tx:annotation-driven 中 proxy-target-class="true" ,Spring将使用CGLIB动态代理,而内部通过Cglib2AopProxy实现代理,而内部通过DynamicAdvisedInterceptor进行拦截:

    package org.springframework.aop.framework;  
    final class Cglib2AopProxy implements AopProxy, Serializable {  
        private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {  
                //注意此处的method 一定是具体类上的method(因此只用放置在具体类上的@Transactional是可以发现的)  
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {  
                }  
           }  
    }  

    1.3、Spring使用AnnotationTransactionAttributeSource通过查找一个类或方法是否有@Transactional注解事务来返回TransactionAttribute(表示开启事务):

    package org.springframework.transaction.annotation;  
    public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource implements Serializable {  
             protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {  
            for (TransactionAnnotationParser annotationParser : this.annotationParsers) {  
                TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);  
                if (attr != null) {  
                    return attr;  
                }  
            }  
            return null;  
        }  
    }  

    而AnnotationTransactionAttributeSource又使用SpringTransactionAnnotationParser来解析是否有@Transactional注解:

    package org.springframework.transaction.annotation;  
      
    public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {  
      
        public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {  
            Transactional ann = AnnotationUtils.getAnnotation(ae, Transactional.class);  
            if (ann != null) {  
                return parseTransactionAnnotation(ann);  
            }  
            else {  
                return null;  
            }  
        }  
      
        public TransactionAttribute parseTransactionAnnotation(Transactional ann) {  
              
        }  
      
    }  

      此处使用AnnotationUtils.getAnnotation(ae, Transactional.class); 这个方法只能发现当前方法/类上的注解,不能发现父类的注解。 Spring还提供了一个 AnnotationUtils.findAnnotation()方法 可以发现父类/父接口中的注解(但spring没有使用该接口)。

      如果Spring此处换成AnnotationUtils.findAnnotation(),将可以发现父类/父接口中的注解。

    七、问题

      我们之前说过,基于JDK动态代理时, method 一定是接口上的method(因此放置在接口上的@Transactional是可以发现的),但现在我们放在具体类上,那么Spring是如何发现的呢??

      TransactionAttribute是通过AnnotationTransactionAttributeSource返回的(具体看步骤1.3),而AnnotationTransactionAttributeSource 继承AbstractFallbackTransactionAttributeSource

    package org.springframework.transaction.interceptor;  
    public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {  
      
        public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {  
            //第一次 会委托给computeTransactionAttribute  
    }  
      
        //计算TransactionAttribute的  
        private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {  
              
            //省略  
      
            // Ignore CGLIB subclasses - introspect the actual user class.  
            Class<?> userClass = ClassUtils.getUserClass(targetClass);  
            // The method may be on an interface, but we need attributes from the target class.  
            // If the target class is null, the method will be unchanged.  
            //①此处将查找当前类覆盖的方法  
            Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);  
            // If we are dealing with method with generic parameters, find the original method.  
            specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);  
      
            // First try is the method in the target class.  
            TransactionAttribute txAtt = findTransactionAttribute(specificMethod);  
            if (txAtt != null) {  
                return txAtt;  
            }  
      
            //找类上边的注解  
            // Second try is the transaction attribute on the target class.  
            txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());  
            if (txAtt != null) {  
                return txAtt;  
            }  
            //②如果子类覆盖的方法没有 再直接找当前传过来的  
            if (specificMethod != method) {  
                // Fallback is to look at the original method.  
                txAtt = findTransactionAttribute(method);  
                if (txAtt != null) {  
                    return txAtt;  
                }  
                // Last fallback is the class of the original method.  
                return findTransactionAttribute(method.getDeclaringClass());  
            }  
            return null;  
        }  
    }  

    //①此处将查找子类覆盖的方法

    Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);

    // ClassUtils.getMostSpecificMethod
    public static Method getMostSpecificMethod(Method method, Class<?> targetClass) {
       Method specificMethod = null;
       if (method != null && isOverridable(method, targetClass) &&
              targetClass != null && !targetClass.equals(method.getDeclaringClass())) {
           try {
          //可以看出将找到当前类的那个方法。因此我们放置在UserService save方法上的@Transactional起作用了。 specificMethod
    = ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes()); } catch (AccessControlException ex) { // security settings are disallowing reflective access; leave // 'specificMethod' null and fall back to 'method' below } } return (specificMethod != null ? specificMethod : method); }

    因此,建议大家使用基于Schema风格的事务(不用考虑这么多问题,也不用考虑是类还是方法)。而@Transactional建议放置到具体类上,不要放置到接口。

  • 相关阅读:
    SVN版本库修改URL路径或者IP地址
    ES-PHP向ES批量添加文档报No alive nodes found in your cluster
    ansible IP
    ansible ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6 转为数字比大小
    Centos下Yum安装PHP5.5,5.6,7.0
    centos6.8上yum安装zabbix3.2
    线性筛的理解及应用
    5分钟使用docker搭建一个WordPress
    使用 Docker-Compose 编排容器
    Bootstrap基础
  • 原文地址:https://www.cnblogs.com/onlymate/p/7805187.html
Copyright © 2020-2023  润新知