Spring重试功能@Retryable

        今天在某个公众号收到一篇关于Spring重试功能(@Retryable注解)的推文,内容大概是“不要重复开发重试功能啦,靠这个注解一键搞定!”,咦,还有这么个注解?功能这么强?让我来看看它是不是能满足我这多变的需求!源码开扒......

        要使用@Retryable注解,必须先开启重试功能(即在启动类上添加@EnableRetry注解)。

 1、@EnableRetry

        那先看看@EnableRetry注解的内容,除了声明作用域、生命周期等外,多了下面两个注解:

@EnableAspectJAutoProxy(proxyTargetClass = false):哦?!果然用的是切面编程。

@Import(RetryConfiguration.class):需要先引入此配置类,那盲猜一下这个配置类里面应该会包含一些AOP相关的配置。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Import(RetryConfiguration.class)
@Documented
public @interface EnableRetry {

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies. The default is {@code false}.
	 *
	 * @return whether to proxy or not to proxy the class
	 */
	boolean proxyTargetClass() default false;

}

2、RetryConfiguration配置类

         果然如此,继承了AbstractPointcutAdvisor,实现IntroductionAdvisor接口。

AbstractPointcutAdvisor:用来自定义切面通知(Advice)和切入点(Pointcut);

IntroductionAdvisor:用来自定义哪些类作为切面。

2.1 buildAdvice

此方法用来定义切面通知的实现方法,比如设置重试上下文缓存、监听器、参数唯一标识、参数识别策略、重试等待策略等等。

protected Advice buildAdvice() {
	AnnotationAwareRetryOperationsInterceptor interceptor = new AnnotationAwareRetryOperationsInterceptor();
	if (retryContextCache != null) {
		interceptor.setRetryContextCache(retryContextCache);
	}
	if (retryListeners != null) {
		interceptor.setListeners(retryListeners);
	}
	if (methodArgumentsKeyGenerator != null) {
		interceptor.setKeyGenerator(methodArgumentsKeyGenerator);
	}
	if (newMethodArgumentsIdentifier != null) {
		interceptor.setNewItemIdentifier(newMethodArgumentsIdentifier);
	}
	if (sleeper != null) {
		interceptor.setSleeper(sleeper);
	}
	return interceptor;
}

2.2 buildPointcut

此方法用来获取切点。

protected Pointcut buildPointcut(Set<Class<? extends Annotation>> retryAnnotationTypes) {
	ComposablePointcut result = null;
	for (Class<? extends Annotation> retryAnnotationType : retryAnnotationTypes) {
		Pointcut filter = new AnnotationClassOrMethodPointcut(retryAnnotationType);
		if (result == null) {
			result = new ComposablePointcut(filter);
		}
		else {
			result.union(filter);
		}
	}
	return result;
}

3、AnnotationAwareRetryOperationsInterceptor

        细心的同学可能在上面的buildAdvice方法中,已经发现了“AnnotationAwareRetryOperationsInterceptor”这个类,那这个类是用来干嘛的呢?顾名思义,它是一个拦截器。

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
	MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
	if (delegate != null) {
		return delegate.invoke(invocation);
	}
	else {
		return invocation.proceed();
	}
}
private MethodInterceptor getDelegate(Object target, Method method) {
	if (!this.delegates.containsKey(target) || !this.delegates.get(target).containsKey(method)) {
		synchronized (this.delegates) {
			if (!this.delegates.containsKey(target)) {
				this.delegates.put(target, new HashMap<Method, MethodInterceptor>());
			}
			Map<Method, MethodInterceptor> delegatesForTarget = this.delegates.get(target);
			if (!delegatesForTarget.containsKey(method)) {
				Retryable retryable = AnnotationUtils.findAnnotation(method, Retryable.class);
				if (retryable == null) {
					retryable = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Retryable.class);
				}
				if (retryable == null) {
					retryable = findAnnotationOnTarget(target, method);
				}
				if (retryable == null) {
					return delegatesForTarget.put(method, null);
				}
				MethodInterceptor delegate;
				if (StringUtils.hasText(retryable.interceptor())) {
					delegate = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
				}
				else if (retryable.stateful()) {
					delegate = getStatefulInterceptor(target, method, retryable);
				}
				else {
					delegate = getStatelessInterceptor(target, method, retryable);
				}
				delegatesForTarget.put(method, delegate);
			}
		}
	}
	return this.delegates.get(target).get(method);
}
private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
	RetryTemplate template = createTemplate(retryable.listeners());
	template.setRetryPolicy(getRetryPolicy(retryable));
	template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
	return RetryInterceptorBuilder.stateless()
			.retryOperations(template)
			.label(retryable.label())
			.recoverer(getRecoverer(target, method))
			.build();
}

private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
	RetryTemplate template = createTemplate(retryable.listeners());
	template.setRetryContextCache(this.retryContextCache);

	CircuitBreaker circuit = AnnotationUtils.findAnnotation(method, CircuitBreaker.class);
	if (circuit!=null) {
		RetryPolicy policy = getRetryPolicy(circuit);
		CircuitBreakerRetryPolicy breaker = new CircuitBreakerRetryPolicy(policy);
		breaker.setOpenTimeout(getOpenTimeout(circuit));
		breaker.setResetTimeout(getResetTimeout(circuit));
		template.setRetryPolicy(breaker);
		template.setBackOffPolicy(new NoBackOffPolicy());
		String label = circuit.label();
		if (!StringUtils.hasText(label))  {
			label = method.toGenericString();
		}
		return RetryInterceptorBuilder.circuitBreaker()
				.keyGenerator(new FixedKeyGenerator("circuit"))
				.retryOperations(template)
				.recoverer(getRecoverer(target, method))
				.label(label)
				.build();
	}
	RetryPolicy policy = getRetryPolicy(retryable);
	template.setRetryPolicy(policy);
	template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
	String label = retryable.label();
	return RetryInterceptorBuilder.stateful()
			.keyGenerator(this.methodArgumentsKeyGenerator)
			.newMethodArgumentsIdentifier(this.newMethodArgumentsIdentifier)
			.retryOperations(template)
			.label(label)
			.recoverer(getRecoverer(target, method))
			.build();
}

        看源码,此拦截器实现了IntroductionInterceptor接口中的invoke方法,在getDelegate中通过获取方法或其所在类的@Retryable注解配置,来确定重试机制和策略,同时设置重试失败后操作。最后通过build方法构建拦截器后执行拦截器。

3.1 StatelessRetryInterceptorBuilder和StatefulRetryInterceptorBuilder

        在上述构建拦截器过程中出现了两种不同的构建方法:StatelessRetryInterceptorBuilder、StatefulRetryInterceptorBuilder,这两者的区别在于是否有状态。

        StatelessRetryInterceptorBuilder适用于无状态方法的拦截器。可以使用StatelessRetryOperationsInterceptor来处理与外部系统的通信,因为它们通常是无状态的操作。

        StatefulRetryInterceptorBuilder用于创建StatefulRetryOperationsInterceptor拦截器,这是一种适用于有状态方法的拦截器。例如,可以使用StatefulRetryOperationsInterceptor来处理需要在重试之间保留状态的方法,例如事务性方法。

3.2 StatefulRetryOperationsInterceptor和RetryOperationsInterceptor

        上述两个构建方法就是基于此两种拦截器来构建,具体区别可以看两者的源码,此处就不贴源码了。总的来说就是,StatefulRetryOperationsInterceptor此拦截器会在执行的过程中设置一个RetryState状态,可以用来获取当前重试状态;而RetryOperationsInterceptor没有此状态。

3.3 RetryTemplate

        在以上所有的配置设置完成后,RetryTemplate就上线了,这是真正执行重试操作的地方。一下是类中的执行方法doExecute:

protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
		RecoveryCallback<T> recoveryCallback, RetryState state)
		throws E, ExhaustedRetryException {

	RetryPolicy retryPolicy = this.retryPolicy;
	BackOffPolicy backOffPolicy = this.backOffPolicy;

	// Allow the retry policy to initialise itself...
	RetryContext context = open(retryPolicy, state);
	if (this.logger.isTraceEnabled()) {
		this.logger.trace("RetryContext retrieved: " + context);
	}

	// Make sure the context is available globally for clients who need
	// it...
	RetrySynchronizationManager.register(context);

	Throwable lastException = null;

	boolean exhausted = false;
	try {

		// Give clients a chance to enhance the context...
		boolean running = doOpenInterceptors(retryCallback, context);

		if (!running) {
			throw new TerminatedRetryException(
					"Retry terminated abnormally by interceptor before first attempt");
		}

		// Get or Start the backoff context...
		BackOffContext backOffContext = null;
		Object resource = context.getAttribute("backOffContext");

		if (resource instanceof BackOffContext) {
			backOffContext = (BackOffContext) resource;
		}

		if (backOffContext == null) {
			backOffContext = backOffPolicy.start(context);
			if (backOffContext != null) {
				context.setAttribute("backOffContext", backOffContext);
			}
		}

		/*
		 * We allow the whole loop to be skipped if the policy or context already
		 * forbid the first try. This is used in the case of external retry to allow a
		 * recovery in handleRetryExhausted without the callback processing (which
		 * would throw an exception).
		 */
		while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

			try {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Retry: count=" + context.getRetryCount());
				}
				// Reset the last exception, so if we are successful
				// the close interceptors will not think we failed...
				lastException = null;
				return retryCallback.doWithRetry(context);
			}
			catch (Throwable e) {

				lastException = e;

				try {
					registerThrowable(retryPolicy, state, context, e);
				}
				catch (Exception ex) {
					throw new TerminatedRetryException("Could not register throwable",
							ex);
				}
				finally {
					doOnErrorInterceptors(retryCallback, context, e);
				}

				if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
					try {
						backOffPolicy.backOff(backOffContext);
					}
					catch (BackOffInterruptedException ex) {
						lastException = e;
						// back off was prevented by another thread - fail the retry
						if (this.logger.isDebugEnabled()) {
							this.logger
									.debug("Abort retry because interrupted: count="
											+ context.getRetryCount());
						}
						throw ex;
					}
				}

				if (this.logger.isDebugEnabled()) {
					this.logger.debug(
							"Checking for rethrow: count=" + context.getRetryCount());
				}

				if (shouldRethrow(retryPolicy, context, state)) {
					if (this.logger.isDebugEnabled()) {
						this.logger.debug("Rethrow in retry for policy: count="
								+ context.getRetryCount());
					}
					throw RetryTemplate.<E>wrapIfNecessary(e);
				}

			}

			/*
			 * A stateful attempt that can retry may rethrow the exception before now,
			 * but if we get this far in a stateful retry there's a reason for it,
			 * like a circuit breaker or a rollback classifier.
			 */
			if (state != null && context.hasAttribute(GLOBAL_STATE)) {
				break;
			}
		}

		if (state == null && this.logger.isDebugEnabled()) {
			this.logger.debug(
					"Retry failed last attempt: count=" + context.getRetryCount());
		}

		exhausted = true;
		return handleRetryExhausted(recoveryCallback, context, state);

	}
	catch (Throwable e) {
		throw RetryTemplate.<E>wrapIfNecessary(e);
	}
	finally {
		close(retryPolicy, context, state, lastException == null || exhausted);
		doCloseInterceptors(retryCallback, context, lastException);
		RetrySynchronizationManager.clear();
	}

}

        可以看出,其中也是通过while循环来进行重试操作。每次循环之前都会通过canRetry和isExhaustedOnly来判断是否可以重试或者重试次数是否已耗尽。

        以上就是@Retryable注解涉及到的源码解析。当然除此之外还有一些重试延迟策略(固定延迟重试、随机延迟重试、倍数延迟重试等等)和重试触发策略(异常重试策略、断路器重试策略等等),这些都是根据注解的配置选择的策略,有兴趣的同学可以继续深入研究,在此我就不过多介绍了。

        总体来看,重试机制也是AOP的一种形式,将@Retryable注解所在的方法当作切点,所在的类当作切面,以拦截器的形式对方法进行重试。那么使用过程中就得注意以下事项:

1、类内部调用不会触发,因为AOP是基于代理类调用的,内部方法的调用不会触发机制;

2、方法内使用try catch后不抛出异常,则不会触发重试;

3、@Recover注解所在的方法参数一定要是@Retryable抛出的异常,否则无法识别。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值