一、基礎工作
首先修改我們上一次做的 SpringMVC + spring3.1.1 + hibernate4.1.0 https://www.aiwalls.com/kf/201203/122443.html 集成及常見問題總結,如下所示:
將xml聲明式事務刪除
java代碼:
<aop:config expose-proxy="true">
<!– 隻對業務邏輯層實施事務 –>
<aop:pointcut id="txPointcut" expression="execution(* cn.javass..service..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
並添加註解式事務支持:
java代碼:
<tx:annotation-driven transaction-manager="txManager"/>
在我們的BaseService接口上添加 @Transactional 使該方法開啟事務
java代碼:
package cn.javass.common.service;
public interface IBaseService<M extends java.io.Serializable, PK extends java.io.Serializable> {
@Transactional //開啟默認事務
public int countAll();
}
在我們的log4j.properties中添加如下配置,表示輸出spring的所有debug信息
java代碼:
log4j.logger.org.springframework=INFO,CONSOLE
在我們的resources.properties裡將hibernate.show_sql=true 改為true,為瞭看到hibernate的sql。
單元測試類:
java代碼:
package cn.javass.ssonline.spider.service.impl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import cn.javass.demo.service.UserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class UserServiceTest2 {
@Autowired
private UserService userService;
@Test
public void testCreate() {
userService.countAll();
}
}
基礎工作做好,接下來我們詳細看看 Spring基於 JDK動態代理 和 CGLIB類級別代理到底有什麼區別。
二、基於JDK動態代理:
java代碼:
<tx:annotation-driven transaction-manager="txManager"/>
該配置方式默認就是JDK動態代理方式
運行單元測試,核心日志如下:
java代碼:
2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager – Creating new transaction with name [cn.javass.common.service.impl.BaseService.countAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' //開啟事務
2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager – Opened new Session
2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager – Bound value [org.springframework.orm.hibernate4.SessionHolder@1184a4f] for key [org.hibernate.internal.SessionFactoryImpl@107b56e] to thread [main] //綁定session到ThreadLocal
2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager – Initializing transaction synchronization
2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.interceptor.TransactionInterceptor – Getting transaction for [cn.javass.common.service.impl.BaseService.countAll]
2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager – Retrieved value [org.springframework.orm.hibernate4.SessionHolder@1184a4f] for key [org.hibernate.internal.SessionFactoryImpl@107b56e] bound to thread [main]
2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager – Found thread-bound Session
2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager – Retrieved value [org.springframework.orm.hibernate4.SessionHolder@1184a4f] for key [org.hibernate.internal.SessionFactoryImpl@107b56e] bound to thread [main]
Hibernate:
select
count(*) as col_0_0_
from
tbl_user usermodel0_
2012-03-07 09:58:44 [main] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager – Committing Hibernate transaction on Session //提交事務
2012-03-07 09:58:44 [main] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager – Removed value [org.springframework.orm.hibernate4.SessionHolder@1184a4f] for key [org.hibernate.internal.SessionFactoryImpl@107b56e] from thread [main] //解除綁定session到ThreadLocal
到此我們可以看到事務起作用瞭,也就是說即使把@Transactional放到接口上 基於JDK動態代理也是可以工作的。
三、基於CGLIB類代理:
java代碼:
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
該配置方式是基於CGLIB類代理
啟動測試會報錯,No Session found for current thread,說明事務沒有起作用
java代碼:
org.hibernate.HibernateException: No Session found for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1024)
at cn.javass.common.dao.hibernate4.BaseHibernateDao.getSession(BaseHibernateDao.java:63)
at cn.javass.common.dao.hibernate4.BaseHibernateDao.aggregate(BaseHibernateDao.java:238)
at cn.javass.common.dao.hibernate4.BaseHibernateDao.countAll(BaseHibernateDao.java:114)
at cn.javass.common.service.impl.BaseService.countAll(BaseService.java:60)
at cn.javass.common.service.impl.BaseService$$FastClassByCGLIB$$5b04dd69.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:618)
at cn.javass.demo.service.impl.UserServiceImpl$$EnhancerByCGLIB$$7d46c567.countAll(<generated>)
at cn.javass.ssonline.spider.service.impl.UserServiceTest2.testCreate(UserServiceTest2.java:20)
如果將註解放在具體類上或具體類的實現方法上才會起作用。
java代碼:
package cn.javass.common.service.impl;
public abstract class BaseService<M extends java.io.Serializable, PK extends java.io.Serializable> implements IBaseService<M, PK> {
@Transactional() //放在抽象類上
@Override
public int countAll() {
return baseDao.countAll();
}
}
運行測試類,將發現成功瞭,因為我們的UserService繼承該方法,但如果UserService覆蓋該方法,如下所示,也將無法織入事務(報錯):
java代碼:
package cn.javass.demo.service.impl;
public class UserServiceImpl extends BaseService<UserModel, Integer> implements UserService {
//沒有@Transactional
@Override
public int countAll() {
return baseDao.countAll();
}
}
四、基於aspectj的
java代碼:
<tx:annotation-driven transaction-manager="txManager" mode="aspectj" proxy-target-class="true"/>
在此就不演示瞭,我們主要分析基於JDK動態代理和CGLIB類代理兩種的區別。
五、結論:
基於JDK動態代理 ,可以將@Transactional放置在接口和具體類上。
基於CGLIB類代理,隻能將@Transactional放置在具體類上。
因此 在實際開發時全部將@Transactional放到具體類上,而不是接口上。
六、分析
1、 JDK動態代理
1.1、Spring使用JdkDynamicAopProxy實現代理:
java代碼:
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進行攔截:
java代碼:
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(表示開啟事務):
java代碼:
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註解:
java代碼:
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(),將可以發現父類/父接口中的註解。
這裡還一個問題,描述如下:
在接口中刪除@Transactional //開啟默認事務
java代碼:
package cn.javass.common.service;
public interface IBaseService<M extends java.io.Serializable, PK extends java.io.Serializable> {
public int countAll();
}
在具體類中添加@Transactional
java代碼:
package cn.javass.common.service.impl;
public abstract class BaseService<M extends java.io.Serializable, PK extends java.io.Serializable> implements IBaseService<M, PK> {
@Transactional() //開啟默認事務
@Override
public int countAll() {
return baseDao.countAll();
}
}
問題:
我們之前說過,基於JDK動態代理時, method 一定是接口上的method(因此放置在接口上的@Transactional是可以發現的),但現在我們放在具體類上,那麼Spring是如何發現的呢??
還記得發現TransactionAttribute是通過AnnotationTransactionAttributeSource嗎?具體看步驟1.3:
而AnnotationTransactionAttributeSource 繼承AbstractFallbackTransactionAttributeSource
java代碼:
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 {
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);
}
可以看出將找到當前類的那個方法。因此我們放置在BaseService countAll方法上的@Transactional起作用瞭。
//②如果子類覆蓋的方法沒有 再直接找當前傳過來的
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());
}
查找子類失敗時直接使用傳過來的方法。
因此,建議大傢使用基於Schema風格的事務(不用考慮這麼多問題,也不用考慮是類還是方法)。而@Transactional建議放置到具體類上,不要放置到接口。
摘自 hello spring