Spring 不使用原型 bean 字段重试原型 bean 实现 Runnable 的 @Recover 方法
Spring Retry @Recover method for prototype-bean implementing Runnable not using field of prototype-bean
我有一个实现 Runnable 的原型 bean,它必须重试其 运行 方法,并在达到最大重试上限时执行某些操作。
现在我遇到的问题是 recover 方法似乎总是从同一个 spring bean 中调用,而不是从相应的实例中调用。
到目前为止,这是我的代码:
RetryableRunnable
@Slf4j
@AllArgsConstructor
public class RetryableRunnable implements Runnable {
private final RetryDemoService retryDemoService;
private final Status someStatus;
@Override
@Retryable(
value = { RuntimeException.class },
maxAttempts = 2,
backoff = @Backoff(delay = 2000))
public void run() {
log.info( "+++ RetryableRunnable executed! +++" );
retryDemoService.demoRun();
}
@Recover
private void recover() {
retryDemoService.demoRecover();
log.info( String.valueOf( someStatus ) );
}
}
配置
@Configuration
@AllArgsConstructor
@EnableRetry(proxyTargetClass = true)
public class RetryDemoConfig {
private final RetryDemoService retryDemoService;
@Bean
@Scope( "prototype" )
public RetryableRunnable retryableRunnable(Status status) {
return new RetryableRunnable( retryDemoService, status );
}
}
服务
@Service
@Slf4j
public class RetryDemoService {
void demoRun() {
log.info( "+++ Run! +++" );
}
void demoRecover() {
log.info( "+++ Recover! +++" );
}
}
状态枚举
public enum Status {
STATUS1, STATUS2
}
测试显示问题
@RunWith( SpringRunner.class )
@SpringBootTest
public class RetryableRunnableTest {
@Autowired
private BeanFactory beanFactory;
@MockBean
RetryDemoService retryDemoService;
@Test
public void retrieableRunnableIsRetriedOnlyThreeTimesAndRecoverMethodIsRun() throws InterruptedException {
RetryableRunnable testInstance1 = beanFactory.getBean( RetryableRunnable.class, Status.STATUS1 );
RetryableRunnable testInstance2 = beanFactory.getBean( RetryableRunnable.class, Status.STATUS2 );
doThrow( new RuntimeException() )
.doThrow( new RuntimeException() )
.doThrow( new RuntimeException() )
.when( retryDemoService ).demoRun();
Thread thread1 = new Thread( testInstance1 );
thread1.start();
thread1.join();
Thread thread2 = new Thread( testInstance2 );
thread2.start();
thread2.join();
}
}
现在日志的输出是:
+++ RetryableRunnable executed! +++
+++ RetryableRunnable executed! +++
STATUS1
+++ RetryableRunnable executed! +++
+++ RetryableRunnable executed! +++
STATUS1
虽然应该是:
+++ RetryableRunnable executed! +++
+++ RetryableRunnable executed! +++
STATUS1
+++ RetryableRunnable executed! +++
+++ RetryableRunnable executed! +++
STATUS2
当我调试此测试方法时,RetryRunnable@3053 第一次和第二次调用了 recover 方法!
这是一个错误还是我没有理解一个概念?我该怎么做才能解决这个问题并调用相应的 prototype-bean "Status"-field?
当前不支持原型范围。
只有一个 AnnotationAwareRetryOperationsInterceptor
并且它根据 Method
缓存委托 RetryOperationsInterceptor
,而不是对象实例...
private MethodInterceptor getDelegate(Object target, Method method) {
if (!this.delegates.containsKey(method)) {
...
}
return this.delegates.get(method);
}
调用正确的 @Retryable
方法,但所有实例将调用第一个缓存的 @Recoverer
。
缓存必须更改为目标对象和 Method
组合的键。
您可以open an issue on github引用这个问题。
欢迎投稿。
这是我用来重现问题的应用...
@SpringBootApplication
@EnableRetry
public class So47513907Application {
private static final Log log = LogFactory.getLog(So47513907Application.class);
public static void main(String[] args) {
SpringApplication.run(So47513907Application.class, args);
}
@Bean
public ApplicationRunner runner(ApplicationContext ctx) {
return args -> {
Baz baz1 = ctx.getBean(Baz.class, "one");
Baz baz2 = ctx.getBean(Baz.class, "two");
baz1.foo();
baz2.foo();
};
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Baz baz(String arg) {
return new Baz(arg);
}
@Service
public static class Foo {
void demoRun(Baz baz) {
log.info(baz.instance + " +++ Run! +++");
throw new RuntimeException();
}
void demoRecover(Baz baz) {
log.info(baz.instance + " +++ Recover! +++");
}
}
public interface Bar {
void foo();
void bar();
}
public static class Baz implements Bar {
public String instance;
@Autowired
private Foo foo;
public Baz(String instance) {
this.instance = instance;
}
@Retryable
@Override
public void foo() {
log.info(this.instance);
foo.demoRun(this);
}
@Recover
@Override
public void bar() {
log.info("recover: " + this.instance);
foo.demoRecover(this);
}
}
}
编辑
最简单的解决方法是使用 RetryTemplate
而不是注释:
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(new SimpleRetryPolicy(2));
return template;
}
public static class Baz implements Bar {
public String instance;
@Autowired
private Foo foo;
@Autowired
private RetryTemplate retryTemplate;
public Baz(String instance) {
this.instance = instance;
}
// @Retryable
@Override
public void foo() {
this.retryTemplate.execute(context -> {
log.info(this.instance);
foo.demoRun(this);
return null;
}, context -> {
bar();
return null;
});
}
// @Recover
@Override
public void bar() {
log.info("recover: " + this.instance);
foo.demoRecover(this);
}
}
如果需要,您可以使用重试上下文将信息从失败的方法传递给恢复器。
我有一个实现 Runnable 的原型 bean,它必须重试其 运行 方法,并在达到最大重试上限时执行某些操作。 现在我遇到的问题是 recover 方法似乎总是从同一个 spring bean 中调用,而不是从相应的实例中调用。
到目前为止,这是我的代码:
RetryableRunnable
@Slf4j
@AllArgsConstructor
public class RetryableRunnable implements Runnable {
private final RetryDemoService retryDemoService;
private final Status someStatus;
@Override
@Retryable(
value = { RuntimeException.class },
maxAttempts = 2,
backoff = @Backoff(delay = 2000))
public void run() {
log.info( "+++ RetryableRunnable executed! +++" );
retryDemoService.demoRun();
}
@Recover
private void recover() {
retryDemoService.demoRecover();
log.info( String.valueOf( someStatus ) );
}
}
配置
@Configuration
@AllArgsConstructor
@EnableRetry(proxyTargetClass = true)
public class RetryDemoConfig {
private final RetryDemoService retryDemoService;
@Bean
@Scope( "prototype" )
public RetryableRunnable retryableRunnable(Status status) {
return new RetryableRunnable( retryDemoService, status );
}
}
服务
@Service
@Slf4j
public class RetryDemoService {
void demoRun() {
log.info( "+++ Run! +++" );
}
void demoRecover() {
log.info( "+++ Recover! +++" );
}
}
状态枚举
public enum Status {
STATUS1, STATUS2
}
测试显示问题
@RunWith( SpringRunner.class )
@SpringBootTest
public class RetryableRunnableTest {
@Autowired
private BeanFactory beanFactory;
@MockBean
RetryDemoService retryDemoService;
@Test
public void retrieableRunnableIsRetriedOnlyThreeTimesAndRecoverMethodIsRun() throws InterruptedException {
RetryableRunnable testInstance1 = beanFactory.getBean( RetryableRunnable.class, Status.STATUS1 );
RetryableRunnable testInstance2 = beanFactory.getBean( RetryableRunnable.class, Status.STATUS2 );
doThrow( new RuntimeException() )
.doThrow( new RuntimeException() )
.doThrow( new RuntimeException() )
.when( retryDemoService ).demoRun();
Thread thread1 = new Thread( testInstance1 );
thread1.start();
thread1.join();
Thread thread2 = new Thread( testInstance2 );
thread2.start();
thread2.join();
}
}
现在日志的输出是:
+++ RetryableRunnable executed! +++
+++ RetryableRunnable executed! +++
STATUS1
+++ RetryableRunnable executed! +++
+++ RetryableRunnable executed! +++
STATUS1
虽然应该是:
+++ RetryableRunnable executed! +++
+++ RetryableRunnable executed! +++
STATUS1
+++ RetryableRunnable executed! +++
+++ RetryableRunnable executed! +++
STATUS2
当我调试此测试方法时,RetryRunnable@3053 第一次和第二次调用了 recover 方法!
这是一个错误还是我没有理解一个概念?我该怎么做才能解决这个问题并调用相应的 prototype-bean "Status"-field?
当前不支持原型范围。
只有一个 AnnotationAwareRetryOperationsInterceptor
并且它根据 Method
缓存委托 RetryOperationsInterceptor
,而不是对象实例...
private MethodInterceptor getDelegate(Object target, Method method) {
if (!this.delegates.containsKey(method)) {
...
}
return this.delegates.get(method);
}
调用正确的 @Retryable
方法,但所有实例将调用第一个缓存的 @Recoverer
。
缓存必须更改为目标对象和 Method
组合的键。
您可以open an issue on github引用这个问题。
欢迎投稿。
这是我用来重现问题的应用...
@SpringBootApplication
@EnableRetry
public class So47513907Application {
private static final Log log = LogFactory.getLog(So47513907Application.class);
public static void main(String[] args) {
SpringApplication.run(So47513907Application.class, args);
}
@Bean
public ApplicationRunner runner(ApplicationContext ctx) {
return args -> {
Baz baz1 = ctx.getBean(Baz.class, "one");
Baz baz2 = ctx.getBean(Baz.class, "two");
baz1.foo();
baz2.foo();
};
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Baz baz(String arg) {
return new Baz(arg);
}
@Service
public static class Foo {
void demoRun(Baz baz) {
log.info(baz.instance + " +++ Run! +++");
throw new RuntimeException();
}
void demoRecover(Baz baz) {
log.info(baz.instance + " +++ Recover! +++");
}
}
public interface Bar {
void foo();
void bar();
}
public static class Baz implements Bar {
public String instance;
@Autowired
private Foo foo;
public Baz(String instance) {
this.instance = instance;
}
@Retryable
@Override
public void foo() {
log.info(this.instance);
foo.demoRun(this);
}
@Recover
@Override
public void bar() {
log.info("recover: " + this.instance);
foo.demoRecover(this);
}
}
}
编辑
最简单的解决方法是使用 RetryTemplate
而不是注释:
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(new SimpleRetryPolicy(2));
return template;
}
public static class Baz implements Bar {
public String instance;
@Autowired
private Foo foo;
@Autowired
private RetryTemplate retryTemplate;
public Baz(String instance) {
this.instance = instance;
}
// @Retryable
@Override
public void foo() {
this.retryTemplate.execute(context -> {
log.info(this.instance);
foo.demoRun(this);
return null;
}, context -> {
bar();
return null;
});
}
// @Recover
@Override
public void bar() {
log.info("recover: " + this.instance);
foo.demoRecover(this);
}
}
如果需要,您可以使用重试上下文将信息从失败的方法传递给恢复器。