前言
关于事务失效, 两年前在《Spring 事务学习笔记(一) 初遇篇》已经基本讨论过了,我们简单回忆一下里面的内容,这里我们讲了声明式事务也就是@Transaction的基本使用,讲了编程式事务的基本使用:
@Service
public class TransactionServiceTest {
@Autowired
private TransactionTemplate transactionTemplate;
public void transaction() {
transactionTemplate.execute(status -> "这是一个编程式事务");
}
}
简单的看了源码,在源码中我们也只是看到了AbstractFallbackTransactionAttributeSource中的computeTransactionAttribute这个方法:
接着就开始枚举场景看@Transaction是否失效,然后进行了总结。以现在的眼光来看这篇文章相对粗糙,还是列出了Spring 中事务会在哪几种场景失效的场景,结论相对有点多,后面写完不久后,我就给忘记了。这次我打算换种思路去解读在Spring中事务失效的几种场景,给出更简单的判定准则。
概述
首先我们知道注解不是直接对代码产生作用的,这一点我们在《注解入门》(见参考资料[1] ),已经详细讨论过了,注解是一种元数据,提供一些信息,但注解并不对它修饰的代码直接起作用。也就是说注解是通过反射来获取代码上的注解进而发挥作用的:
public class AnnotationTest {
public static void main(String[] args) {
getAnnotaion02();
}
private static void getAnnotaion02() {
Class<AnnotationTest> annotationTestClass = AnnotationTest.class;
Method method = null;
try {
method = annotationTestClass.getMethod("testAnnotation");
SimpleAnnotation simpleAnnotation = method.getAnnotation(SimpleAnnotation.class);
System.out.println(simpleAnnotation.value());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@SimpleAnnotation(value = "test")
public void testAnnotation(){
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface SimpleAnnotation {
String value();
}
}
那回到@Transaction注解,一般我们会像下面一样使用@Transaction:
@Transactional(rollbackFor = Exception.class)
public void transactionTest(){
updateDataBase();
}
这代表我们希望这个方法希望是事务方法,在遇到异常的时候执行回滚。也就是我们希望在方法执行的时候变成下面这样:
public void transactionTest() throws SQLException {
Connection conn = DriverManager.getConnection("", "", "");
conn.setAutoCommit(false);
try{
updateDataBase();
conn.commit();
}catch (Exception e){
// 日志输出
conn.rollback();
}
}
我们写代码的时候没有写这些关闭自动提交回滚这些,那么一定是我们的方法在运行时获得了增强,也就是说我们的方法在不修改源码的基础上获得了增强,有没有想起什么,动态代理? 对,就是动态代理。我们在《代理模式-AOP绪论》[2] 已经讨论过代理模式的初衷了,我们引入动态代理模式的初衷就是:
我们就希望在不修改目标对象的功能前提下,对目标的功能进行扩展。
在Spring里面对方法做动态代理就更简单了一些,这也就是AOP(不熟悉AOP的参看参看资料[3]), 那在Spring里面要为方法做代理,需要我们声明什么呢? 我们需要指明我们想要增强哪些方法,这在AOP中我们称之为切点(pointcut), 有了切点之后还不够,我们帮助目标方法的增强代码在哪个时机执行呢,在Java中有多种选择:
- 前置通知 在方法执行之前执行。实现org.springframework.aop.MethodBeforeAdvice接口。
- 后置通知 在方法执行之后执行。org.springframework.aop.AfterReturningAdvice。
- 环绕通知 拦截目标方法的调用。即调用目标方法的整个过程,即可以做到方法执行之前执行、方法执行之后执行、方法发生了异常之后执行。org.aopalliance.intercept.MethodInterceptor
- 异常通知 在方法发生了异常之后执行。org.springframework.aop.ThrowsAdvice
这也就是连接点,那我们帮助目标方法编写的代码也需要一个概念描述,这也就是切面。也就是说在实际开发中有些代码是重复代码,这些代码有些是在代码执行之前,有些是执行之后,我们就可以将这些代码抽出来,用切面描述。同时我们需要知道切面是哪些方法的切面,这也就是切点,有了切点之后,我们还需要知道切面在切点执行的什么时机执行,这也就是连接点。
现在让我们先自定义一个注解:@CustomTransactional,然后切点定义为有这个注解的方法,切面就随便写点代码,来体会@Transactional是如何生效的:
public class CustomTransactionTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
CustomTransactionUse customTransactionUse = applicationContext.getBean(CustomTransactionUse.class);
customTransactionUse.test();
}
}
@interface CustomTransaction{
String value();
}
class CustomTransactionUse{
@CustomTransaction(value = "这是一个测试注解")
public void test(){
System.out.println("hello world");
}
}
@Configuration
@EnableAspectJAutoProxy // 注意这个一定要加,这个是请求Spring 开启代理
class BeanConfig{
@Bean
public CustomTransactionUse customTransactionUse(){
return new CustomTransactionUse();
}
@Bean
public LogAround logAround(){
return new LogAround();
}
}
@Aspect
class LogAround{
@Pointcut("@annotation(com.example.demo.CustomTransaction)")
public void pointCut() {
}
@Around("pointCut()")
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("模拟开启事务");
try {
Object methodReturnResult = joinPoint.proceed();
return methodReturnResult;
}catch (Exception e){
System.out.println("模拟回滚");
return null;
}
}
}
现在我们已经对@Transactional是如何起作用的,有了一个大致的了解,还是通过AOP来做的。有了这些基础上,我想我们可以看看Spring 是怎么为被@Transactional修饰的方法做增强的,这里我们顺带讨论如何看源码这个问题。
现在来看源码
该如何看源码呢,其实在《浅谈知识的结构与认知(一)》中我们已经讨论了如何学习,这个思路其实可以被应用到看源码上:
黄娃插问道: 老师,什么是物理模型?
老师解释道: "实际问题往往是复杂的,其中包含一些非本质的枝节,物理模型就是把实际问题理想化,先略去一些次要因素,突出其主要因素,不这样做我们就得不到简洁的物理规律"
也就是说我们在看代码的时候要分清楚主次,带着问题去研究,明确自己要研究什么问题,略去次要因素,突出其主要因素,这样才方便我们得出结论。那我们这次看源码的目的就在于明确事务是在哪些场景会失效,首先我们已经基本知道了@Transactional是如何生效的,现在让我们来验证我们的想法,首先我们打开@Transactional的源码, 在注释我看到@Transactional是这样介绍自己的:
Describes a transaction attribute on an individual method or on a class. When this annotation is declared at the class level, it applies as a default to all methods of the declaring class and its subclasses.
描述一个类或者方法的事务属性。当注解被声明在类上,相当于在整个类或者方法上和子类上加上该注解。
Note that it does not apply to ancestor classes up the class hierarchy; inherited methods need to be locally redeclared in order to participate in a subclass-level annotation. For details on method visibility constraints, consult the https://docs.spring.io/spring-framework/docs/current/referenc...
需要注意的是,它不会应用于类层次结构中的父类;为了让继承的方法参与到子类级别的注解中,需要在子类重新声明这些方法。
我的理解是如果子类的方法需要让事务支持,需要自己重新加上@Transactional注解。有了大致了解之后,我们接着看@Transactional是如何起作用的,我们目前已知的是我们猜测打上@Transactional注解的方法被aop命中,被代理,那么我们现在的目标就是要寻找切面、切点。对此我主要有三个思路。 一是find usage、二是推测,三是打断点。
在@Transactional的属性 rollbackFor上可以看到一些注释:
Defines zero (0) or more exception classes, which must be subclasses of Throwable, indicating which exception types must cause a transaction rollback.By default, a transaction will be rolling back on RuntimeException and Error but not on checked exceptions (business exceptions).
该属性指定在发生哪些异常的情况进行回滚,默认的情况下事务将只会为RuntimeException 和Error回滚,但不会为受检异常回滚。
那这里也就引出来事务失效的第一个场景,使用是默认情况的@Transactional,而没有指定回滚的异常类型是Exception的子类:
@Override
@Transactional
public void noTransactionalTest() {
hasTransactionTest();
}
这里我也简单的回想一下Java的异常体系:
我们可以做出论断所有不是Error和RuntimeException的子类的都是CheckedException。
find usage
找该注解在哪里被使用, 也就是借助IDEA的find usage快捷键:
然后我们点过去,发现这行代码位于SpringTransactionAnnotationParser这个类里面,大致的看一下这个类:
我推测是策略模式,我还推测是通过isCandidateClass这个方法来判断是否交由哪个策略来执行的, 我们看下isCandidateClass在哪里被调用:
@Override
public boolean isCandidateClass(Class<?> targetClass) {
for (TransactionAnnotationParser parser : this.annotationParsers) {
if (parser.isCandidateClass(targetClass)) {
return true;
}
}
return false;
}
调用方在AnnotationTransactionAttributeSource的isCandidateClass里面,这里面获取了所有的事务注解解析来,然后来判断是否是哪个类负责来解析。面试题里面可以自己补充了,Spring使用了策略模式来解析事务注解,那事务解析器还有哪些, 我们看下TransactionAnnotationParser的实现类有哪几个:
然后在这个类的注释上看到了:
我推测这就是我们要找到切面, 我们看下这个类:
到现在我们已经找到了切面,现在让我们找切点在哪里被定义,我们还是用IDEA的快捷键find usage,这次找的是TransactionInterceptor:
那么可能会有人就会问了,为什么你看看带有Advisor的这个类呢,而不是看别的? 我的这个推断来自于我的基础知识,在Spring中使用Advisor来描述连接点这个概念,连接点连接的是切面和切点,不熟悉的话可以仔细阅读参考资料[3],在xml中我们配置连接点,如下代码所示:
<aop:advisor advice-ref="logBefore" pointcut-ref="pointCut"></aop:advisor>
现在我们将目光转移到TransactionAttributeSourceAdvisor上面:
我们如愿以偿的看到了我们切点,只不过这个切点通过类来定义,我们之前定义切点都是通过注解或者xml方式,通过类来定义那么问题来了,该如何描述要切哪些类呢? 带着这个问题我们去看TransactionAttributeSourcePointcut:
我们看到了这幅图里面带的有matcher,那么这里我推测就是由Matcher来判定要切哪些方法的,在TransactionAttributeSourcePointcut里面我们可以看到matches方法的实现:
@Override
public boolean matches(Method method, Class<?> targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
protected abstract TransactionAttributeSource getTransactionAttributeSource();
TransactionAttributeSourcePointcut 是一个抽象类,在TransactionAttributeSourceAdvisor中有一个实现:
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
@Override
@Nullable
protected TransactionAttributeSource getTransactionAttributeSource() {
return (transactionInterceptor != null ? transactionInterceptor.getTransactionAttributeSource() : null);
}
};
也就是说TransactionAttributeSource来自于TransactionInterceptor,那TransactionInterceptor的getTransactionAttributeSource来自于TransactionAspectSupport,我推测在初始化TransactionInterceptor的时候填入,我们看看是否是这样:
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
也就是说TransactionInterceptor来自于TransactionAttributeSource,这里面我们也如愿以偿的看到了TransactionInterceptor在这里被加入到Spring 的IOC容器里面,那可能有同学就会问了,new TransactionInterceptor的地方不是两处嘛,为什么你就确定是这处呢,那是因为我打了断点,Spring初始化的时候执行到这个类了:
那我们接着看AnnotationTransactionAttributeSource,这个类的实现:
public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource
implements Serializable {
private static final boolean jta12Present;
private static final boolean ejb3Present;
static {
ClassLoader classLoader = AnnotationTransactionAttributeSource.class.getClassLoader();
jta12Present = ClassUtils.isPresent("javax.transaction.Transactional", classLoader);
ejb3Present = ClassUtils.isPresent("javax.ejb.TransactionAttribute", classLoader);
}
// 默认只代理public级别的方法
private final boolean publicMethodsOnly;
private final Set<TransactionAnnotationParser> annotationParsers;
public AnnotationTransactionAttributeSource() {
this(true);
}
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
if (jta12Present || ejb3Present) {
this.annotationParsers = new LinkedHashSet<>(4);
this.annotationParsers.add(new SpringTransactionAnnotationParser());
if (jta12Present) {
this.annotationParsers.add(new JtaTransactionAnnotationParser());
}
if (ejb3Present) {
this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
}
}
else {
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
}
}
看了这个类的实现,我们就可以知道事务失效的一个场景是不是public,那写到这里有同学可能就会问了,那你是如何得出这个判断的呢? 当然是从AnnotationTransactionAttributeSource的注释上:
publicMethodsOnly – whether to support public methods that carry the Transactional annotation only (typically for use with proxy-based AOP), or protected/private methods as well (typically used with AspectJ class weaving)。
是支持只携带Transactional注释的public方法(通常用于基于代理的AOP),还是支持受保护/私有方法(通常与AspectJ类编织一起使用)
所以我们可以替换AnnotationTransactionAttributeSource的实现,让事务在private/protected也生效,这很简单。好让我们接着回到getTransactionAttribute,这个方法来自AbstractFallbackTransactionAttributeSource, 实现如下图所示:
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return null;
}
// First, see if we have a cached value.
// 缓存模板方法和属性
Object cacheKey = getCacheKey(method, targetClass);
TransactionAttribute cached = this.attributeCache.get(cacheKey);
if (cached != null) {
// Value will either be canonical value indicating there is no transaction attribute,
// or an actual transaction attribute.
if (cached == NULL_TRANSACTION_ATTRIBUTE) {
return null;
}
else {
return cached;
}
}
else {
// 为空计算出来该方法的事务属性
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
// Put it in the cache.
if (txAttr == null) {
this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
}
else {
String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
if (txAttr instanceof DefaultTransactionAttribute) {
DefaultTransactionAttribute dta = (DefaultTransactionAttribute) txAttr;
dta.setDescriptor(methodIdentification);
dta.resolveAttributeStrings(this.embeddedValueResolver);
}
if (logger.isTraceEnabled()) {
logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
}
this.attributeCache.put(cacheKey, txAttr);
}
return txAttr;
}
}
整段逻辑也比较简单,首先判断该方法和类是否被缓存过, 没有缓存计算事务属性, 也就是computeTransactionAttribute方法:
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
// 判断是否是public或者是否只允许为public方法开启代理,也就是AnnotationTransactionAttributeSource做的的事情
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// 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 = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
computeTransactionAttribute整段逻辑也比较简单,如果发现只允许给public方法开事务管理,而且方法不是public返回空,然后在缓存里面放NULL_TRANSACTION_ATTRIBUTE,下次再计算的时候发现已经计算过了不用再计算。
到现在我们已经直到切点,切面,连接点在哪里了,但我还是不太理解切点是如何发挥作用的, 我们回过头来接着看ProxyTransactionManagementConfiguration这个类,这个类里面定义了切点:
public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
@Nullable
private TransactionAttributeSource transactionAttributeSource;、
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
@Override
@Nullable
protected TransactionAttributeSource getTransactionAttributeSource() {
return transactionAttributeSource;
}
};
}
但这个类里面有价值的内容不多,我只好将目光看向TransactionAttributeSourcePointcut的继承图:
于是我打算去看PointCut这个接口, 上面有我想要的注释:
Core Spring pointcut abstraction. A pointcut is composed of a ClassFilter and a MethodMatcher. Both these basic terms and a Pointcut itself can be combined to build up combinations (e.g. through org.springframework.aop.support.ComposablePointcut).
Spring切点的抽象,切点由ClassFilter和MethodMatcher组成,这两个基本术语以及切入点本身可以组合起来构建组合(例如通过org.springframework.aop.support.ComposablePointcut)。
那其实我还是不太明白PointCut的原理,注解和配置类的我知道,那PointCut是如何发挥作用的呢? 他里面有两个方法:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
我想应当是ClassFilter返回哪些类需要切,而MethodMatcher则判断哪些方法需要被命中,我们看下TransactionAttributeSourcePointcut是如何实现这两个方法的:
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
protected TransactionAttributeSourcePointcut() {
setClassFilter(new TransactionAttributeSourceClassFilter());
}
// 然后看方法是否被命中
@Override
public boolean matches(Method method, Class<?> targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
private class TransactionAttributeSourceClassFilter implements ClassFilter {
@Override
public boolean matches(Class<?> clazz) {
// 判断传入的类是否是TransactionalProxy、TransactionManager、PersistenceExceptionTranslator
// PersistenceExceptionTranslator或者是这三个的子类。如果是代表当前类不被该切点命中。
if (TransactionalProxy.class.isAssignableFrom(clazz) || TransactionManager.class.isAssignableFrom(clazz) || PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {
return false;
}
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.isCandidateClass(clazz));
}
}
}
现在再来看看切点是如何命中被@Transactional修饰的方法的,而我们直到getTransactionAttributeSource的 来自于TransactionInterceptor:
public TransactionInterceptor(TransactionManager ptm, TransactionAttributeSource tas) {
setTransactionManager(ptm);
setTransactionAttributeSource(tas);
}
而TransactionInterceptor在ProxyTransactionManagementConfiguration这个配置类里面被注入:
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
}
而TransactionAttributeSource是一个抽象类,容器里面使用的是AnnotationTransactionAttributeSource, 也就是说getTransactionAttributeSource最终来自于AnnotationTransactionAttributeSource,在AnnotationTransactionAttributeSource里面又没有直接看到getTransactionAttributeSource,所以这个方法来自于AbstractFallbackTransactionAttributeSource, 在这个类里面计算被事务修饰的属性。到此我们已经知道了PointCut是如何起作用的:
- ClassFilter: 用于判断当前类是否需要被代理。
- MethodMatcher: 判断当前类的方法是否需要被代理。
那我这里还有一个问题,Spring在启动的时候将Bean放入IOC容器里面,那Bean在执行方法的时候是怎么跟AOP交互的,我想应该是有一个AOP链条,每个方法执行的时候都会进入这个链条判断是不是需要这个AOP处理,不是放到下一个。 这就引出了一个问题AOP和IOC是如何交互的。
AOP和IOC是如何交互的简介
在《记一次排查循环依赖的经历》、《Spring Bean的生命周期》我们已经大致分析了一个Bean在创建过程中会经过哪些流程? 一个很关键的接口就是BeanPostprocessor:
public interface BeanPostProcessor {
// Bean的初始化方法(如init-method或@PostConstruct注解标注的方法)被调用之前执行。
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// 在Bean的初始化方法被调用之后执行。
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
而在Spring中AspectJAwareAdvisorAutoProxyCreator是为目标Bean生成代理对象的关键:
AspectJAwareAdvisorAutoProxyCreator的父类间接的实现了BeanPostprocessor,也就是这个代理类可以在Bean创建之后,为该Bean筛选目标类:
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
// 如果有切面然后生成代理类
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
到这里我们已经对@Transactional已经大致有一个了解了,我们探索的路程就是用我们的基础知识在配合IDEA的快捷键。
猜测,根据包名去推测
现在我们介绍第二种看源码的方法,就是猜测,有人可能会说你全靠猜啊,当然不是全靠猜,我还有验证,科学探索的一个方法就是大胆猜想,小心求证。想起来之前在B站看的课程,当时有个老师再讲某个拦截器,in是方法执行之前,out是执行之后拦截,并且作出论断,有in就有out,我将这个结论记录了下来,在之后工作中,有一次同事说需要对webservice做拦截,只找到了in的拦截器,那返回出去的该怎么办,我说有in就会有out,后面果然找到了out。这也是工程经验,也是推测。我们总是在观察然后思考找出规律,那么如果你没有我上面的思考过程,那么另一种方式也可以找到MethodInterptor,这是推测不见得准,只是一种方法,基于我们对AOP的理解,我们推测@Transactional本质上是使用AOP的环绕通知来做的,这是我们已知的,环绕通知的接口是MethodInterceptor,记单词Inerceptor, 在@Transactional的注解里面我们看到:
所以我们可以大胆推测,对应的事务拦截器也会在这个包里面。
打断点调试大法
那前面说的都是顺着思路找,我们也可以逆着思路找,启用调试大法一步一步看:
public interface StuService {
void transactionalTest();
}
@Service
public class StuServiceImpl implements StuService {
@Transactional(rollbackFor = Exception.class)
@Override
public void transactionalTest() {
}
}
@RestController
public class TestController {
@Autowired
private StuService stuService;
@GetMapping("helloTx")
public String test(){
stuService.transactionalTest(); // 语句一
return "hello world";
}
}
然后再语句一打断点,然后step into, 然后就会发现进入到了CglibAopProxy:
所以这里也对我们上面的思想进行修正,不是每一个方法执行的时候都会进入AOP链,还是从进入这个类的第一个方法开始去获取AOP链条。其实到这里我们就可以引出事务失效的第一个场景了,一个非事务方法调用事务方法:
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StudentInfoDao studentInfoDao;
@Override
public void noTransactionalTest() {
hasTransactionTest();
}
@Transactional(rollbackFor = Exception.class)
public void hasTransactionTest(){
// 这里面执行的sql语句是:update student set name = 'zs' where id = 1597942948484419589;
studentInfoDao.updateStudentById();
int i = 1 / 0;
}
}
@RestController
public class TestController {
@Autowired
private StuService stuService;
@GetMapping("helloTx")
public String test(){
stuService.noTransactionalTest();
return "hello world";
}
}
最后还是没有执行回滚,在《Spring 事务学习笔记(一) 初遇篇》我们也聊到了这个场景,但其实当初的理解比较简单,认为没有非事务方法调用事务方法,非事务方法上没有@Transactional注解,不满足条件,没能开启代理。但是后面我又思考,那有@Transactional注解的方法在执行的时候不是能被代理吗? 相当于执行noTransactionalTest没代理,在执行hasTransactionTest代理这个动作,这里相错的一点是代理类在生成的时候,是在调用这个类的方法的时候,生成代理类,以这一次调用链为单位决定代理不代理,而不是每次方法执行的时候都过一遍代理类。这里有一个思考是能否做成调用noTransactionalTest不开启代理,在hasTransactionTest开启代理,但是这样设计的时候,调用的就不是一个对象了,假设一个调用链断断续续的过了一个方法,A => B=> C => D => E = > F,B、D、E是满足代理条件的,但是不是连续的,这样要生成几个代理类,这会让问题变得复杂一些,而且性能也感觉不是很好。索性不如在进入这个类的时候就判断要不要代理。
那怎么解决呢? 一种方式自己注入自己,本质上还是容器中的bean之间互相调用方法的时候会被送入AOP链条里面去找对应的AOP, 看看哪个会被命中:
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StudentInfoDao studentInfoDao;
@Autowired
private StuServiceImpl stuServiceImpl;
@Override
@Transactional(rollbackFor = Exception.class)
public void noTransactionalTest() {
hasTransactionTest();
}
@Transactional(rollbackFor = Exception.class)
public void hasTransactionTest(){
studentInfoDao.updateStudentById();
int i = 1 / 0;
}
public void noTransactionalTestProtected() {
stuServiceImpl.hasTransactionTest();
}
}
但是我觉得这个代码写起来很难看,虽然也有别的写法,AopContext.currentProxy()来获取代理类帮忙执行:
@Service
public class StuServiceImpl implements StuService {
@Autowired
private StudentInfoDao studentInfoDao;
@Override
@Transactional(rollbackFor = Exception.class)
public void noTransactionalTest() {
hasTransactionTest();
}
@Transactional(rollbackFor = Exception.class)
public void hasTransactionTest(){
studentInfoDao.updateStudentById();
int i = 1 / 0;
}
public void noTransactionalTestProtected() {
((StuServiceImpl)AopContext.currentProxy()).hasTransactionTest();
}
}
或者做事件监听,将这个事件广播出去:
public void noTransactionalTestProtected() {
applicationEventPublisher.publishEvent(new TransactionEvent());
}
public class TransactionEventListener {
@Autowired
private StuService stuService;
@EventListener(TransactionEvent.class)
public void handlerEvent(){
stuService.noTransactionalTest();
}
}
回过头看事务失效的场景
到现在为止我们已经对@Transactional是如何生效的有一个大致的了解了,
- 使用的数据库引擎不支持事务,在MySQL中的MyIsam
方法是private,这个我们可以替换掉AnnotationTransactionAttributeSource的实现,开启为protected、public代理。那怎么替换呢,还是用到了BeanDefinitionRegistryPostProcessor,我们可以这么写:
@Component public class AnnotationTransactionAttributeSourceReplacer implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { // class org.springframework.transaction.annotation.AnnotationTransactionAttributeSource registry.removeBeanDefinition("transactionAttributeSource"); BeanDefinitionBuilder beanDefinitionBuilderBeanDefinitionBuilder.rootBeanDefinition(CustomAnnotationTransactionAttributeSource.class); registry.registerBeanDefinition("transactionAttributeSource",beanDefinitionBuilder.getBeanDefinition()); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } } public class CustomAnnotationTransactionAttributeSource extends AnnotationTransactionAttributeSource { public CustomAnnotationTransactionAttributeSource() { super(false); } }
实际测试protected和默认级别都能有效被事务管理。
- 没被Spring管理,这个场景其实感觉是在硬答,我们平时大多数使用的类其实都是在Spring管理下面的。因为鱼会习惯水,就像大多数Java程序员习惯Spring,这种场景想象不出来也是合理的。
- 使用了@Transactional·注解的默认配置,抛出了受检异常,但是平时业务代码里面很少有这种受检异常。
- 业务方法本身捕获了异常,但这个我觉得这个并不算事务失效的场景,只是你吞掉了异常,代理没有感应到。
- 方法使用了final关键字,但其实这样的方法,在业务层也很少见,通常情况在业务开发中,很少会加final.
- 事务中使用了多线程,这个其实也很好理解,线程里面出现了异常,没有被外层线程感知到,所以没办法回滚。
@Override
@Transactional(rollbackFor = Exception.class)
public void noTransactionalTest() {
studentInfoDao.insert(); // 语句一
new Thread(()->{
hasTransactionTest();
}).start();
}
@Transactional(rollbackFor = Exception.class)
public void hasTransactionTest(){
studentInfoDao.updateById(); // 语句二
}
这种失效是因为语句一和语句二执行的时候获取的数据库链接不是一个,所以只能回滚自己的。
总结一下,我们可以再简化一下,大致分成三种情况:
- 数据库是否支持事务
- 目标方法能否被代理: 默认只能代理public。
- 执行的数据操作是否在一个连接上
- 业务方法是否吃掉了异常导致没办法回滚。
写在最后
今天又重新改了一下自己的认知,对事务注解,尝试以更本质的思路去思考事务注解是如何发挥作用的来倒退事务会在哪几种场景生效。在写的时候也许查了其他类型的文章,发现都是罗列场景,然后给结论,但是场景又多,感觉不能跟已有的知识结合起来,今天我尝试将这些场景统一都简化。
参考资料
[1] 《注解入门》 https://segmentfault.com/a/1190000020954182
[2] 《代理模式-AOP绪论》 https://juejin.cn/post/6933947703923572743
[3] 《欢迎光临Spring时代(二) 上柱国AOP列传》 https://juejin.cn/post/6934194437748850702
[4] Spring AOP源码分析(三)——拦截器链的执行过程分析 https://xuanjian1992.top/2019/07/27/Spring-AOP%E6%BA%90%E7%A0...(%E4%B8%89)-%E6%8B%A6%E6%88%AA%E5%99%A8%E9%93%BE%E7%9A%84%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90/
[5] 聊一下 SpringBoot 中使用的 cglib 作为动态代理中的一个注意点
[6] Using Custom AnnotationTransactionAttributeSource with tx:annotation-driven https://stackoverflow.com/questions/8316747/using-custom-annotationtransactionattributesource-with-txannotation-driven
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。