Original link:www.ciphermagic.cn/spring-clou…

The problem

Recently, in the project development, I used Feign to call the service. When triggering the circuit breaker, I encountered the following problems:

  • The exception information is as follows:TestService#addRecord(ParamVO) failed and no fallback available.;
  • Cannot get the original exception information thrown by the service provider;
  • Implement some business methods without entering the circuit breaker, directly throw exceptions out;

The next steps will address each of these issues.

For failed and no fallback available. This abnormal message is because the project is on a circuit breaker:

feign.hystrix.enabled: true
Copy the code

The above exception is thrown when the service is called and the fallback method is not defined. This leads to the first solution.

@FeignClientaddfallbackMethod and get exception information

There are two ways to add the fallback method to the @FeignClient-modified interface. To get exception information, use the fallbackFactory method:

@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class)
public interface TestService {

    @RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
    Result get(@PathVariable("id") Integer id);
    
}
Copy the code

Specify fallbackFactory in the @FeignClient annotation, TestServiceFallback in the example above:

import feign.hystrix.FallbackFactory;
import org.apache.commons.lang3.StringUtils;

@Component
public class TestServiceFallback implements FallbackFactory<TestService> {

    private static final Logger LOG = LoggerFactory.getLogger(TestServiceFallback.class);

    public static final String ERR_MSG = "Test interface temporarily unavailable:";

    @Override
    public TestService create(Throwable throwable) {
        String msg = throwable == null ? "" : throwable.getMessage();
        if(! StringUtils.isEmpty(msg)) { LOG.error(msg); }return new TestService() {
            @Override
            public String get(Integer id) {
                returnResultBuilder.unsuccess(ERR_MSG + msg); }}; }}Copy the code

By implementing FallbackFactory, you can get the exception thrown by the service in the create method. Note, however, that the exceptions here are Feign wrapped, and you can’t see the exceptions thrown by the original method directly in the exception message. At this time, the abnormal information is as follows:

status 500 reading TestService#addRecord(ParamVO); content:
{"success":false,"resultCode":null,"message":"/ by zero","model":null,"models":[],"pageInfo":null,"timelineInfo":null,"extra":null,"validationMessages":null,"valid":false}
Copy the code

To clarify, in this example, the service provider’s interface return information will be encapsulated in a custom class Result, the content is the above content:

{"success":false."resultCode":null."message":"/ by zero"."model":null."models": []."pageInfo":null."timelineInfo":null."extra":null."validationMessages":null."valid":false}
Copy the code

Therefore, I want the exception message to be the content of message: / by zero, so that the exception can be easily identified when logging.

Retain the original exception information

When calling the service, if the service returns a status code other than 200, it goes into Feign’s ErrorDecoder, so if we want to parse the exception message, we need to override ErrorDecoder:

import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;

/ * * *@Author: CipherCui
 * @Description: Reserved feign service exception information *@Date: Created in 1:29 2018/6/2
 */
public class KeepErrMsgConfiguration {

    @Bean
    public ErrorDecoder errorDecoder(a) {
        return new UserErrorDecoder();
    }

    /** * Custom error decoder */
    public class UserErrorDecoder implements ErrorDecoder {

        private Logger logger = LoggerFactory.getLogger(getClass());

        @Override
        public Exception decode(String methodKey, Response response) {
            Exception exception = null;
            try {
                // Get the original returned content
                String json = Util.toString(response.body().asReader());
                exception = new RuntimeException(json);
                // Deserialize the returned content to Result, which should be modified according to your project
                Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
                // A business exception throws a simple RuntimeException, preserving the original error message
                if(! result.isSuccess()) { exception =newRuntimeException(result.getMessage()); }}catch (IOException ex) {
                logger.error(ex.getMessage(), ex);
            }
            returnexception; }}}Copy the code

Here is an example that deserializes response.body() into a custom Result class, extracts the message from it, and throws a RuntimeException so that when it enters the fuse method, The exception we get is the RuntimeException we handled.

Note that the above examples are not generic, but the principles are common and should be adapted to suit your own projects.

To make the above code work, you also need to specify configuration in the @feignClient annotation:

@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class, configuration = {KeepErrMsgConfiguration.class})
public interface TestService {

    @RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
    String get(@PathVariable("id") Integer id);
    
}
Copy the code

Throw an exception without entering a circuit breaker

Sometimes we don’t want methods to go into circuit breaker logic, but just throw out the exception as it is. In this case, we only need to catch two points: do not enter the fuse, original.

As it is to get the original exception, has been introduced above, and not into the fuse, need to abnormal encapsulated into HystrixBadRequestException, for HystrixBadRequestException, Feign thrown directly, not into the fusing method.

So we just need to make a few changes to the KeepErrMsgConfiguration above:

/ * * *@Author: CipherCui
 * @Description: Feign service is abnormal and does not enter fuse *@Date: Created in 1:29 2018/6/2
 */
public class NotBreakerConfiguration {

    @Bean
    public ErrorDecoder errorDecoder(a) {
        return new UserErrorDecoder();
    }

    /** * Custom error decoder */
    public class UserErrorDecoder implements ErrorDecoder {

        private Logger logger = LoggerFactory.getLogger(getClass());

        @Override
        public Exception decode(String methodKey, Response response) {
            Exception exception = null;
            try {
                String json = Util.toString(response.body().asReader());
                exception = new RuntimeException(json);
                Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
                / / a business exception packaged into HystrixBadRequestException, don't enter the fusing logic
                if(! result.isSuccess()) { exception =newHystrixBadRequestException(result.getMessage()); }}catch (IOException ex) {
                logger.error(ex.getMessage(), ex);
            }
            returnexception; }}}Copy the code

conclusion

To achieve a better fusing effect, we should specify a fallback method for each interface. You can flexibly configure the KeepErrMsgConfiguration and NotBreakerConfiguration according to your service characteristics, or write your own Configuration.

Above example particularity is strong, inadequacy place please don’t hesitate to give advice. I hope you can get something useful from it and apply it to your own projects. Thank you for reading.

Original link:www.ciphermagic.cn/spring-clou…