Analyse du processus d'exécution du code source de la transaction Spring

Le blog précédent a analysé le code sous-jacent de l'objet proxy dynamique généré par la transaction. Le résumé simple est en une phrase: si l'annotation de transaction @Transactional est ajoutée et que la méthode est publique, spring générera un objet proxy pour le bean. Comme pour que ce soit jdk ou cglib, cela dépend si la classe correspondant à votre méthode de transaction implémente l'interface.
Ensuite, examinons le processus d'exécution après la génération de l'objet proxy.

Exécution de l'intercepteur

org.springframework.transaction.interceptor.TransactionInterceptor

Pendant mes études, j'ai utilisé le proxy cglib. Comme je n'ai pas implémenté l'interface, j'ai utilisé le proxy CGLIB. Lorsque la méthode cible est appelée, elle sera appelée

org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept; 然后会调用到
	org.springframework.transaction.interceptor.TransactionInterceptor#invoke
	 org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

J'ai coupé une partie de cette méthode pour faire la distinction entre les transactions déclaratives et les transactions programmatiques. Ici, nous nous concentrons uniquement sur les transactions déclaratives

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
		final InvocationCallback invocation) throws Throwable {

	// If the transaction attribute is null, the method is non-transactional.
	TransactionAttributeSource tas = getTransactionAttributeSource();
	//获取事务属性
	final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
	//获取事务管理器
	final PlatformTransactionManager tm = determineTransactionManager(txAttr);
	final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

	//下面是声明式事务的处理逻辑
	if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
		// Standard transaction demarcation with getTransaction and commit/rollback calls.
		//看是否有必要创建一个事务,根据事务的传播行为做判断
		TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
		Object retVal = null;
		try {
			// This is an around advice: Invoke the next interceptor in the chain.
			// This will normally result in a target object being invoked.
			//执行目标方法
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			// target invocation exception
			//如果有异常就回滚事务,回滚时,根据配置的rollbackFor进行判断
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
		}
		finally {
			//清除事务信息
			cleanupTransactionInfo(txInfo);
		}
		//提交事务
		commitTransactionAfterReturning(txInfo);
		return retVal;
	}
}

Dans cette méthode,

  • Selon le mécanisme de propagation correspondant à l'annotation de transaction, déterminez s'il faut créer une nouvelle transaction, ou exécuter imbriqué dans la transaction en cours, etc.
  • Puis exécutez la méthode cible
  • Après avoir exécuté la méthode cible, si aucune exception ne se produit, validez la transaction
  • Si une exception se produit, déterminez s'il faut annuler en fonction des paramètres rollbackFor et noRollbackFor configurés. S'il n'est pas nécessaire de restaurer, soumettez la transaction, sinon, annulez la transaction.

Par conséquent, nous nous concentrons sur deux méthodes

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
和
completeTransactionAfterThrowing(txInfo, ex);

Code source du mécanisme de transmission

J'ajouterai le code source du mécanisme de transmission après avoir terminé

Code source de restauration d'exception

Si une exception se produit lors de l'exécution de la méthode cible, après l'avoir interceptée, elle entrera cette méthode pour le traitement

/**
 * 这里是判断是否需要回滚的逻辑
 * 如果在事务注解上指定了回滚的异常类型、或者指定了不回滚的异常类型,就会在这里进行判断
 * 1.如果判断满足回滚的条件,就事务回滚
 * 		先判断开发人员指定的类型,如果业务代码抛出的异常符合指定的类型,就回滚
 * 		如果没有指定,就判断是否是错误(error)或者运行时异常(runTimeException),如果是,就回滚
 *    所以:如果程序员没有指定回滚的异常,默认情况下,如果是运行时异常或者是error(错误),也是会进行事务的回滚
 * 2.否则,就提交事务
 * @param txInfo
 * @param ex
 */
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
	if (txInfo != null && txInfo.getTransactionStatus() != null) {
		if (logger.isTraceEnabled()) {
			logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
					"] after exception: " + ex);
		}
		/**
		 * 如果程序员有指定回滚或者不回滚的异常,就会进入
		 * 	org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn(java.lang.Throwable)进行判断
		 *
		 * 如果没有指定,就默认调用org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(java.lang.Throwable)
		 * 进行判断  
		 */
		if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
			try {
				txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
			}
			catch (TransactionSystemException ex2) {
				logger.error("Application exception overridden by rollback exception", ex);
				ex2.initApplicationException(ex);
				throw ex2;
			}
			catch (RuntimeException | Error ex2) {
				logger.error("Application exception overridden by rollback exception", ex);
				throw ex2;
			}
		}
		else {
      // 这里else就是不符合回滚的条件,会进行事务的提交(即使发生了异常场景)
			// We don't roll back on this exception.
			// Will still roll back if TransactionStatus.isRollbackOnly() is true.
			try {
				txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
			}
			catch (TransactionSystemException ex2) {
				logger.error("Application exception overridden by commit exception", ex);
				ex2.initApplicationException(ex);
				throw ex2;
			}
			catch (RuntimeException | Error ex2) {
				logger.error("Application exception overridden by commit exception", ex);
				throw ex2;
			}
		}
	}
}

L'une des méthodes les plus importantes ici consiste à juger les exceptions et à déterminer si les exceptions levées par le code métier actuel satisfont aux exigences de restauration;

Insérez la description de l'image ici

Cette méthode consiste ici à déterminer si l'exception actuellement levée est conforme aux informations de restauration d'exception configurées sur l'annotation @Transactional

org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn
 
  @Override
	public boolean rollbackOn(Throwable ex) {
		if (logger.isTraceEnabled()) {
			logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
		}
		RollbackRuleAttribute winner = null;
		int deepest = Integer.MAX_VALUE;
  	// 这里的rollbackRules是在解析@Transactional注解的时候,保存的;parseTransactionAnnotation在这个方法中解析的注解;这里的rollbackRules既有rollbackException中配置的,也有noRollbackException中配置的
		if (this.rollbackRules != null) {
			for (RollbackRuleAttribute rule : this.rollbackRules) {
				/**
				 * depth:当前rule和ex的相似度
				 *
				 * deepest:和ex最相近的depth
				 * winner:相似度最近的RollbackRuleAttribute
				 * 如果当前rule,返回的depth比上一次返回的depth小,且大于0,就用当前这次异常
				 *
				 * 这里会保存和业务代码抛出异常最相似的rule
				 * 比如:
				 * 	我在代码中,抛出了一个java.lang.ArithmeticException: / by zero
				 *
				 * 	如果我在@Transactional(rollbackFor = {Exception.class,ArithmeticException.class})
				 * 	那肯定会返回ArithmeticException这个rule,因为ArithmeticException返回的depth是0
				 * 	Exception返回的的depth是2  下面解释为什么一个是0,一个是2
				 */
				int depth = rule.getDepth(ex);
				if (depth >= 0 && depth < deepest) {
					deepest = depth;
					winner = rule;
				}
			}
		}

		if (logger.isTraceEnabled()) {
			logger.trace("Winning rollback rule is: " + winner);
		}

		// User superclass behavior (rollback on unchecked) if no rule matches.
    // 如果没有匹配到最相似的异常、或者没有配置回滚异常类,就会执行这里,这里是调用父类的方法进行判断
		if (winner == null) {
			logger.trace("No relevant rollback rule found: applying default rules");
			return super.rollbackOn(ex);
		}
		/**
		 * 如果相似度最近的rule不是无需回滚的类型,就可以进行事务回滚
		 */
		return !(winner instanceof NoRollbackRuleAttribute);
	}
	// 这里就是父类对应的判断逻辑,也就是说:如果程序员单单加了一个@Transactional注解,那么在业务代码抛出运行时异常后者error的时候,还是会回滚的
	@Override
	public boolean rollbackOn(Throwable ex) {
		return (ex instanceof RuntimeException || ex instanceof Error);
	}

Examinons ensuite la méthode ci-dessus pour juger des similitudes anormales: int depth = rule.getDepth (ex);

Ici, je l'appelle similitude, je ne sais pas si c'est approprié; voici la comparaison entre le nom correspondant à l'exception A lancée dans le code métier et le nom correspondant à l'exception B spécifiée par le programmeur. S'il correspond, il renvoie la profondeur, s'il correspond Sinon, appelez récursivement, comparez la classe parent correspondant à l'exception A lancée dans le code métier avec le nom de l'exception B, et appelez récursivement à son tour

/**
	 * 这里其实就是用业务代码抛出的异常和程序员在@Transactional注解中执行的异常信息进行对比
	 *
	 * 实际底层使用的是String.contains()方法
	 * exceptionName:就是程序员指定的异常对应的类名;比如:我指定的是rollbackFor = Exception.class,那这里的exceptionName就是:java.lang.Exception
	 *
	 * 1.如果当前抛出的异常和程序员指定的异常匹配不上,就依次递归调用抛出异常的父类和程序员指定的异常进行比较,
	 * 		1.1 直到匹配上,就返回当前的depth,depth每递归调用一次,就+1
	 * 		1.2	或者是到Throwable依旧没有比对上,这时,就表示我指定的异常和代码抛出的异常不匹配
	 *
	 * 	这两种场景也好验证:
	 * 		1.首先,我在业务代码中,加上这么一行代码:int i = 10/0;
	 * 		2.然后在@Transactional注解中加上rollbackException = Exception.class	或者是rollbackException = IoException.class
	 * 	    这两种异常,最后事务都会回滚,但是效果却是不一样的
	 * 	    如果我加的是rollbackException = Exception.class,这里会匹配上,返回的是depth是2
	 * 	    但是如果加的是rollbackException = IoException.class,这里返回的是-1
	 *
	 * 	    因为:如果是rollbackException = Exception.class;那这里在匹配的时候,会递归调用两次,
	 * 	    int i = 10/0;会抛出java.lang.ArithmeticException: / by zero
	 * 	    ArithmeticException的父类是RuntimeException;RuntimeException的父类是Exception;所以只有递归调用两次,才能匹配到我指定的Exception.class
	 *
	 * 	    但是,如果我指定的是IoException.class,那永远也匹配不上,因为IOException和ArithmeticException都继承了RuntimeException,是并行的关系,在最后
	 * 	    递归调用到父类Throwable的时候,就会返回-1(即使这里返回了-1,最后事务还是会回滚,为什么?因为在方法之后会判断,如果程序员指定的异常和当前业务代码抛出的异常不相似,那就会判断业务代码抛出的异常是否是运行时异常或者error)
	 *
	 * @param exceptionClass:当前业务代码抛出的异常/或者是抛出异常对应的父类
	 * @param depth:相似度/或者说是深度
	 * @return
	 */
	private int getDepth(Class<?> exceptionClass, int depth) {
		if (exceptionClass.getName().contains(this.exceptionName)) {
			// Found it!
			return depth;
		}
		// If we've gone as far as we can go and haven't found it...
		if (exceptionClass == Throwable.class) {
			return -1;
		}
		return getDepth(exceptionClass.getSuperclass(), depth + 1);
	}

Par conséquent, lorsque le code métier lève une exception,

  1. Il jugera le type d'exception et le type d'exception spécifiés par le programmeur, trouvera l'exception avec le degré de correspondance le plus similaire et déterminera finalement si l'exception la plus similaire est la classe d'exception configurée par noRollbackException. Si c'est le cas, il n'est pas nécessaire de revenir en arrière et soumettre la transaction.
  2. S'il n'y a pas de correspondance, déterminez si l'exception levée est Error ou RuntimeException, si c'est le cas, elle sera également annulée.

Je suppose que tu aimes

Origine blog.csdn.net/CPLASF_/article/details/108695188
conseillé
Classement