电竞比分网-中国电竞赛事及体育赛事平台

分享

Spring -- <tx:annotation-driven>注解基于JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理的實(shí)現(xiàn)Spring注解管理事務(wù)(@Trasactional)的區(qū)別。</tx:annotation-driven>

 沙門空海 2018-01-15

借鑒:http://jinnianshilongnian./blog/1508018

基于JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理的實(shí)現(xiàn)Spring注解管理事務(wù)(@Trasactional)到底有什么區(qū)別。

我還是喜歡基于Schema風(fēng)格的Spring事務(wù)管理,但也有很多人在用基于@Trasactional注解的事務(wù)管理,但在通過(guò)基于JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理的實(shí)現(xiàn)Spring注解管理事務(wù)是有區(qū)別的,我們接下來(lái)看看到底有哪些區(qū)別。

 

一、基礎(chǔ)工作

實(shí)例SpringMVC + Spring4.3.8 + Mybatis3.2.6 + Logback 的項(xiàng)目,如下所示:

  1. 將xml聲明式事務(wù)刪除
    復(fù)制代碼
    <!-- 切面 -->
    <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>
    復(fù)制代碼
  2. 并添加注解式事務(wù)支持
    <!-- 編程式即采用注解的方式,事務(wù)掃描開(kāi)始(開(kāi)啟注解@Tranctional) -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />
  3. 在我們的UserService接口上添加 @Transactional 使該方法開(kāi)啟事務(wù)
    復(fù)制代碼
    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); }
    復(fù)制代碼
    復(fù)制代碼
    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();
            }
        }
    }
    復(fù)制代碼

     

  4. 開(kāi)啟Logback日志的sql輸出

  準(zhǔn)備完畢

 

二、基于JDK動(dòng)態(tài)代理

<!-- 編程式即采用注解的方式,事務(wù)掃描開(kāi)始(開(kāi)啟注解@Tranctional) -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />

 

@Transactional注解放置在接口(抽象類或抽象方法)和具體類(實(shí)現(xiàn)類或?qū)崿F(xiàn)方法)上都可以,具體解析請(qǐng)看"七、問(wèn)題"。

 

運(yùn)行訪問(wèn),核心日志如下:                                    

復(fù)制代碼
2017-11-08 16:36:52.231 耗時(shí):1392016 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容:Acquired Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] for JDBC transaction
2017-11-08 16:36:52.235 耗時(shí):1392020 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容: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 耗時(shí):1392089 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容:Initiating transaction rollback
2017-11-08 16:36:52.304 耗時(shí):1392089 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容:Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1]
2017-11-08 16:36:52.357 耗時(shí):1392142 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容:Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f5672d1] after transaction
2017-11-08 16:36:52.357 耗時(shí):1392142 日志來(lái)自:o.springframework.jdbc.datasource.DataSourceUtils 日志類型: DEBUG 日志內(nèi)容:Returning JDBC Connection to DataSource
復(fù)制代碼

到此我們可以看到事務(wù)起作用了,事務(wù)有回滾,也就是說(shuō)即使把@Transactional放到接口上 基于JDK動(dòng)態(tài)代理也是可以工作的。

 

三、基于CGLIB類代理

<!-- 編程式即采用注解的方式,事務(wù)掃描開(kāi)始(開(kāi)啟注解@Tranctional) -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

該配置方式是基于CGLIB類代理

運(yùn)行訪問(wèn),核心日志如下:

復(fù)制代碼
2017-11-08 16:44:58.079 耗時(shí):8198 日志來(lái)自:org.springframework.web.servlet.DispatcherServlet 日志類型: DEBUG 日志內(nèi)容:DispatcherServlet with name 'springServlet' processing POST request for [/SpringMVC/user/save]
2017-11-08 16:44:58.088 耗時(shí):8207 日志來(lái)自:o.s.w.s.m.a.DefaultAnnotationHandlerMapping 日志類型: DEBUG 日志內(nèi)容:Mapping [/user/save] to HandlerExecutionChain with handler [com.only.mate.controller.UserController@4731f9a] and 1 interceptor
2017-11-08 16:44:58.111 耗時(shí):8230 日志來(lái)自:org.springframework.web.cors.DefaultCorsProcessor 日志類型: DEBUG 日志內(nèi)容:Skip CORS processing: request is from same origin
2017-11-08 16:44:58.119 耗時(shí):8238 日志來(lái)自:o.s.w.bind.annotation.support.HandlerMethodInvoker 日志類型: DEBUG 日志內(nèi)容: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 耗時(shí):8240 日志來(lái)自:com.only.mate.controller.UserController 日志類型: DEBUG 日志內(nèi)容:注冊(cè),用戶名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 耗時(shí):8283 日志來(lái)自:o.springframework.jdbc.datasource.DataSourceUtils 日志類型: DEBUG 日志內(nèi)容:Fetching JDBC Connection from DataSource
2017-11-08 16:44:58.453 耗時(shí):8572 日志來(lái)自:com.alibaba.druid.pool.DruidDataSource 日志類型: INFO  日志內(nèi)容:{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 耗時(shí):8675 日志來(lái)自:o.springframework.jdbc.datasource.DataSourceUtils 日志類型: DEBUG 日志內(nèi)容:Returning JDBC Connection to DataSource
復(fù)制代碼

到此我們可以看到事務(wù)沒(méi)有起作用,事務(wù)沒(méi)有回滾。

 

只有將注解放在具體類上或具體類的實(shí)現(xiàn)方法上才會(huì)起作用。

復(fù)制代碼
package com.only.mate.service;

import com.only.mate.entity.User;

public interface UserService {
    public User findOne(String username);
    public void save(User user);
}
復(fù)制代碼
復(fù)制代碼
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(); } } }
復(fù)制代碼

 

 運(yùn)行訪問(wèn),核心日志如下:

復(fù)制代碼
2017-11-08 16:49:54.992 耗時(shí):3784 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容:Acquired Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] for JDBC transaction
2017-11-08 16:49:54.997 耗時(shí):3789 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容: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 耗時(shí):3862 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容:Initiating transaction rollback
2017-11-08 16:49:55.070 耗時(shí):3862 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容:Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6]
2017-11-08 16:49:55.124 耗時(shí):3916 日志來(lái)自:o.s.jdbc.datasource.DataSourceTransactionManager 日志類型: DEBUG 日志內(nèi)容:Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@51e88ff6] after transaction
2017-11-08 16:49:55.124 耗時(shí):3916 日志來(lái)自:o.springframework.jdbc.datasource.DataSourceUtils 日志類型: DEBUG 日志內(nèi)容:Returning JDBC Connection to DataSource
復(fù)制代碼

運(yùn)行測(cè)試類,將發(fā)現(xiàn)成功了。

 

 還有一種情況如下:

復(fù)制代碼
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);
    }
//沒(méi)有@Transactional  @Override public void save(User user) { userMapper.insert(user); if("zhangsan".equals(user.getUserName())){ throw new RuntimeException(); } } }
復(fù)制代碼

這種情況也將無(wú)法織入事務(wù)。

 

四、基于aspectj的

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

在此就不演示了,我們主要分析基于JDK動(dòng)態(tài)代理和CGLIB類代理兩種的區(qū)別。

 

五、結(jié)論

基于JDK動(dòng)態(tài)代理 ,可以將@Transactional放置在接口(抽象類或抽象方法)和具體類實(shí)現(xiàn)類或?qū)崿F(xiàn)方法)上。

基于CGLIB類代理,只能將@Transactional放置在具體類實(shí)現(xiàn)類或?qū)崿F(xiàn)方法)上。

因此 在實(shí)際開(kāi)發(fā)時(shí)全部將@Transactional放到具體類上,而不是接口上。

 

六、分析

1、  JDK動(dòng)態(tài)代理

1.1、Spring使用JdkDynamicAopProxy實(shí)現(xiàn)代理:

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

注意此處的method 一定是接口上的method(因此放置在接口上的@Transactional是可以發(fā)現(xiàn)的)

 

1.2、如果<tx:annotation-driven 中 proxy-target-class="true" ,Spring將使用CGLIB動(dòng)態(tài)代理,而內(nèi)部通過(guò)Cglib2AopProxy實(shí)現(xiàn)代理,而內(nèi)部通過(guò)DynamicAdvisedInterceptor進(jìn)行攔截:

復(fù)制代碼
package org.springframework.aop.framework;  
final class Cglib2AopProxy implements AopProxy, Serializable {  
    private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {  
            //注意此處的method 一定是具體類上的method(因此只用放置在具體類上的@Transactional是可以發(fā)現(xiàn)的)  
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {  
            }  
       }  
}  
復(fù)制代碼

 

1.3、Spring使用AnnotationTransactionAttributeSource通過(guò)查找一個(gè)類或方法是否有@Transactional注解事務(wù)來(lái)返回TransactionAttribute(表示開(kāi)啟事務(wù)):

復(fù)制代碼
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;  
    }  
}  
復(fù)制代碼

 

而AnnotationTransactionAttributeSource又使用SpringTransactionAnnotationParser來(lái)解析是否有@Transactional注解:

復(fù)制代碼
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) {  
          
    }  
  
}  
復(fù)制代碼

  此處使用AnnotationUtils.getAnnotation(ae, Transactional.class); 這個(gè)方法只能發(fā)現(xiàn)當(dāng)前方法/類上的注解,不能發(fā)現(xiàn)父類的注解。 Spring還提供了一個(gè) AnnotationUtils.findAnnotation()方法 可以發(fā)現(xiàn)父類/父接口中的注解(但spring沒(méi)有使用該接口)。

  如果Spring此處換成AnnotationUtils.findAnnotation(),將可以發(fā)現(xiàn)父類/父接口中的注解。

 

七、問(wèn)題

  我們之前說(shuō)過(guò),基于JDK動(dòng)態(tài)代理時(shí), method 一定是接口上的method(因此放置在接口上的@Transactional是可以發(fā)現(xiàn)的),但現(xiàn)在我們放在具體類上,那么Spring是如何發(fā)現(xiàn)的呢??

  TransactionAttribute是通過(guò)AnnotationTransactionAttributeSource返回的(具體看步驟1.3),而AnnotationTransactionAttributeSource 繼承AbstractFallbackTransactionAttributeSource

復(fù)制代碼
package org.springframework.transaction.interceptor;  
public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {  
  
    public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {  
        //第一次 會(huì)委托給computeTransactionAttribute  
}  
  
    //計(jì)算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.  
        //①此處將查找當(dāng)前類覆蓋的方法  
        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;  
        }  
        //②如果子類覆蓋的方法沒(méi)有 再直接找當(dāng)前傳過(guò)來(lái)的  
        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;  
    }  
}  
復(fù)制代碼

//①此處將查找子類覆蓋的方法

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

復(fù)制代碼
// 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 {
      //可以看出將找到當(dāng)前類的那個(gè)方法。因此我們放置在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); }
復(fù)制代碼

因此,建議大家使用基于Schema風(fēng)格的事務(wù)(不用考慮這么多問(wèn)題,也不用考慮是類還是方法)。而@Transactional建議放置到具體類上,不要放置到接口。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多