SpringBoot @Retryable 不重试。

35
以下代码没有重试。我错过了什么?
@EnableRetry
@SpringBootApplication
public class App implements CommandLineRunner
{
    .........
    .........


    @Retryable()
    ResponseEntity<String> authenticate(RestTemplate restTemplate, HttpEntity<MultiValueMap<String, String>> entity) throws Exception
    {
        System.out.println("try!");
        throw new Exception();
        //return restTemplate.exchange(auth_endpoint, HttpMethod.POST, entity, String.class);
    }
我已经将以下内容添加到pom.xml文件中。
    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
        <version>1.1.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

我还尝试了向@Retryable提供不同的参数组合。

@Retryable(maxAttempts=10,value=Exception.class,backoff=@Backoff(delay = 2000,multiplier=2))

谢谢。


1
我遇到了相同的问题。 - Ferdous Wahid
11个回答

59
在Spring Boot 2.0.2版本中,我发现如果retryable和被调用的方法在同一个类中,则@Retryable不起作用。经过调试发现,切入点没有正确构建。目前解决此问题的方法是需要将方法编写在不同的类中并进行调用。
可在这里找到工作示例。

我在使用Spring Boot 1.5.x时也有同样的经历。 - Cornelia Davis
2
观察到Spring Boot 2.0.0到2.0.5的情况相同。检查了mvn repo:2017年后没有spring-retry的版本,而Spring Boot 2是在2018年发布的。 是否有救世主成功地使spring-retry与任何版本的Spring Boot 2.x兼容? 同时回滚到Spring Boot 1.5.15,它可以正常工作。 - Jeremie
那是一个通用的Spring事情,你可以使用@Autowired自我引用吗? - Kalpesh Soni
10
这就是所有Spring注解的工作原理。你需要一个单独的代理对象,这样Spring才能应用额外的逻辑。 - iozee
4
这是因为Spring会创建一个代理对象来处理重试,但它无法对自身创建代理对象。 当使用其他注解,如@Transactional时,同样会出现这个问题。 - Federico Piazza
很好的发现,只要我把它放到另一个类中,它就开始工作了。虽然我将其注释为Configuration而不是Service,但它作为Service调用可重试方法。 - Yashpal

19

Spring的@Retryable、@Cacheable、@Transaction等功能都是通过面向切面编程(AOP)实现的。Spring通过基于代理的织入(weaving)来实现AOP,代理拦截一个bean对另一个bean的调用,但无法拦截一个对象方法对另一个对象方法的调用,这是代理织入的普遍限制。

以下解决方案可以解决这个限制:1)如上所述,使用@Autowired(或@Resource)注入具有自引用的bean,调用此引用会经过代理。2)使用AspectJ的ClassLoader而不是Spring默认的基于代理的织入。3)如上所述,将方法放在单独的bean中。我在不同的情况下尝试过每种方法,每种方法都有优点和缺点。


16

为了发现方法上的@Retryable注解,需要从初始化的上下文中正确地调用它。该方法是从Spring上下文中的bean调用还是通过其他方式调用的?

如果进行测试,您的运行器是否使用SpringJunit4ClassRunner


谢谢,我还没有机会查看这个,等我回头再看。 - engg
3
这应该是被接受的答案,当你说“我发现如果从你尝试重试的方法中返回一些东西,那么@Retryable()就不起作用了。”是错误的,实际上你可以在重试方法中返回一个对象,关键是该方法需要从一个bean中调用。 - fsimon

9
我解决了这个问题。我发现如果从你尝试重试的方法中返回了某些东西,那么 @Retryable() 就不起作用。
在 pom.xml 中添加以下 Maven 依赖:
    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
        <version>1.1.5.RELEASE</version>
    </dependency>

Spring Boot应用程序.java

@SpringBootApplication
@EnableTransactionManagement
@EnableRetry
public class Application {

     public static void main(String[] args) throws Exception {
       SpringApplication.run(Application.class, args);
     }

}

在controller.java中

@RestController
public class JavaAllDataTypeController {

@Autowired
JavaAllDataTypeService JavaAllDataTypeService;


@RequestMapping(
        value = "/springReTryTest",
        method = RequestMethod.GET
)
public ResponseEntity<String> springReTryTest() {

    System.out.println("springReTryTest controller");

    try {
         JavaAllDataTypeService.springReTryTest();
    } catch (Exception e) {
        e.printStackTrace();
    }

    return new  ResponseEntity<String>("abcd", HttpStatus.OK);
  }

}

在service.java文件中

@Service
@Transactional
public class JavaAllDataTypeService {

 // try the method 9 times with 2 seconds delay.
 @Retryable(maxAttempts=9,value=Exception.class,backoff=@Backoff(delay = 2000))
 public void springReTryTest() throws Exception {

    System.out.println("try!");
    throw new Exception();
  }

}

输出:尝试9次后抛出异常。

enter image description here


1
不,"从可重试方法返回值"与此问题无关。我有许多返回值和重试的方法。 - Taner
是的,你说得对,我也遇到了同样的问题。当我将这个方法移动到另一个类中时,Retryable 就起作用了。 - Mohamed Ismail M
这个答案是误导性的,请看其他的。 - Yorgos Lamprakis

6

对于想要在同一类中调用@Retryable块的人,可以按照以下方式进行。

关键在于不直接调用该方法,而是通过自注入的bean进行调用。

@Slf4j
@Service
public class RetryService {

    @Resource(name = "retryService")
    private RetryService self;

    public String getValue(String appender) {
        return self.getData(appender);
    }

    @Retryable(value = NumberFormatException.class, maxAttempts = 4, backoff = @Backoff(500))
    public String getData(String appender) {
        log.info("Calling getData");
        Integer value = Integer.parseInt(appender);
        value++;
        return value.toString();
    }

    @Recover
    public String recoverData(String appender) {
        log.info("Calling recoverData");
        return "DEFAULT";
    }

}

您可以在这里详细了解如何使用重试机制。


尝试执行以下操作后,我遇到了这个错误:“应用程序上下文中某些bean的依赖关系形成循环”。 - ArtOfWarfare

5

我遇到了与原问题描述一模一样的问题。

在我的情况下,原来是spring-boot-starter-aop依赖库被意外地省略了。在将其加入到我的pom.xml中后,我的@Retryable方法按预期工作了。

对于我而言,从@Retryable方法返回值也可以正常工作。


4

它同样适用于返回类型

@Service
public class RetryService {

private int count = 0;

// try the method 9 times with 2 seconds delay.
@Retryable(maxAttempts = 9, value = Exception.class, backoff = @Backoff(delay = 2000))
public String springReTryTest() throws Exception {
    count++;
    System.out.println("try!");

    if (count < 4)
        throw new Exception();
    else
        return "bla";
  }

}

3

虽然这个帖子有点老,但我想分享一下我的经验。将我的方法可见性从private更改为public后,Retryable成功地进行了重试。

除了使用上面提到的self资源外,这也是一个解决方法。


3
就算我也遇到了同样的问题,后来经过一些调查和研究才发现,除了在方法上面加上@Retryable注解之外,我们还需要在类上面加上@EnableRetry注解。这个@EnableRetry注解可以放在同一个类中,即你提供要重试的方法所在的类上面,也可以放在你的主要Spring Boot应用程序类上面。例如像这样:
@RequiredArgsConstructor
**@EnableRetry**
@Service
public class SomeService {

  **@Retryable(value = { HttpServerErrorException.class, BadRequestException.class},
      maxAttempts = maxRetry, backoff = @Backoff(random = true, delay = 1000,
                         maxDelay = 8000, multiplier = 2))**
  public <T> T get( ) throws  HttpServerErrorException, BadRequestException {
    
     //write code here which you want to retry
  }

}

希望这能帮助并解决您的问题。


2
一个替代方案可以是RetryTemplate。最初的回答。
@Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();

        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(2000l);
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(2);
        retryTemplate.setRetryPolicy(retryPolicy);

        return retryTemplate;
    }

并且

retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
    @Override
    public Void doWithRetry(RetryContext arg0) {
        myService.templateRetryService();
        ...
    }
});

worked out for me

source


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接