1. 程式人生 > >基於JDK動態代理和CGLIB動態代理的區別

基於JDK動態代理和CGLIB動態代理的區別

Spring事務管理,有二種實現方式:xml宣告式事務和註解式事務支援,這裡介紹下,使用註解式事務,使用JDK和CGLIB二種方式的區別:

一、基礎工作

例項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();
        }
    }
}
  1. 開啟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 [[email protected]] for JDBC transaction
2017-11-08 16:36:52.235 耗時:1392020 日誌來自:o.s.jdbc.datasource.DataSourceTransactionManager 日誌型別: DEBUG 日誌內容:Switching JDBC Connection [[email protected]] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [[email protected]]
JDBC Connection [[email protected]] 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 [[email protected]]
Transaction synchronization deregistering SqlSession [[email protected]]
Transaction synchronization closing SqlSession [[email protected]]
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 [[email protected]]
2017-11-08 16:36:52.357 耗時:1392142 日誌來自:o.s.jdbc.datasource.DataSourceTransactionManager 日誌型別: DEBUG 日誌內容:Releasing JDBC Connection [[email protected]] 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 [[email protected]] 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 [[email protected]] 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 [[email protected]] 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 [[email protected]]
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 [[email protected]] for JDBC transaction
2017-11-08 16:49:54.997 耗時:3789 日誌來自:o.s.jdbc.datasource.DataSourceTransactionManager 日誌型別: DEBUG 日誌內容:Switching JDBC Connection [[email protected]] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [[email protected]]
JDBC Connection [[email protected]] 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 [[email protected]]
Transaction synchronization deregistering SqlSession [[email protected]]
Transaction synchronization closing SqlSession [[email protected]]
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 [[email protected]]
2017-11-08 16:49:55.124 耗時:3916 日誌來自:o.s.jdbc.datasource.DataSourceTransactionManager 日誌型別: DEBUG 日誌內容:Releasing JDBC Connection [[email protected]] 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建議放置到具體類上,不要放置到介面。

What a meaningless sense if losing myself,though owning all of the world.