Steve B
Steve B

Reputation: 577

Spring @Cacheable does not appear to be caching anything

I have a microservice that is calling another microservice to see if that microservice is up via Spring Boot Actuator. I'd like to cache the results to avoid having to perform this check every time my microservice is called. So first step would be to cache the the method that performs this check and then have this cache expire after x seconds. So far the first step is not working. I'm expecting to not enter the method that performs the check, but presently I'm entering isAccountConnectorUp() every time. Is there something that I'm missing?

@Service
public class DepositService {

  public void getCredit(String message) {
    isAccountConnectorUp();

    sendRequestToStream(message);
  }

  @Cacheable(value = "result", key = "#root.method.name", unless = "#result == true")
  private static boolean isAccountConnectorUp() {
    final String uri = "http://localhost:9996/health";
    boolean isUp = false;

    RestTemplate restTemplate = new RestTemplate();

    try {
      String result = restTemplate.getForObject(uri, String.class);
      isUp = true;
    } catch (ResourceAccessException exception) {
      throw new DatabaseException(ACCOUNT_CONNECTOR_IS_DOWN);
    }

    return isUp;
  }

}

I have added @EnableCaching to my application class.

Upvotes: 2

Views: 7922

Answers (2)

M. Deinum
M. Deinum

Reputation: 124471

There are actually 3 things wrong with this code.

  1. Trying to apply proxy-based AOP on a private method
  2. Trying to apply proxy-based AOP on a static method
  3. Doing an internal method call while using proxy-based AOP.

The @Cacheable will be applied through the use of AOP. Which when using Spring is by default using proxies. Which means AOP advice will only be applied if a method call passes through the proxy, as you are already inside the proxy, this won't work as it bypasses the proxy.

Now for a proxy-based AOP solution to work the method has to be non-private and not static. As currently those cannot be proxied.

So basically you are running into all of those 3. The last one is, somewhat easy, to circumvent, by injecting a reference to yourself and call the method on that.

@Service
public class DepositService {

  @Autowired
  private DepositService self;
  
  public void getCredit(String message) {
    self.isAccountConnectorUp();

    sendRequestToStream(message);
  }

  @Cacheable(value = "result", key = "#root.method.name", unless = "#result == true")
  public boolean isAccountConnectorUp() {
    final String uri = "http://localhost:9996/health";
    boolean isUp = false;

    RestTemplate restTemplate = new RestTemplate();

    try {
      String result = restTemplate.getForObject(uri, String.class);
      isUp = true;
    } catch (ResourceAccessException exception) {
      throw new DatabaseException(ACCOUNT_CONNECTOR_IS_DOWN);
    }

    return isUp;
  }

}

If you really don't want to change the visibility and static nature of the method. Stop using proxy-based AOP and use compile-time weaving or load-time weaving to modify the bytecode of the class. However, this introduces complexity in either your build or how you need to start your application.

NOTE: Your caching doesn't work as there is unless clause. Which skips caching if the value is true, and exception won't be cached. So even with caching this won't work as nothing will be effectively cached.

Upvotes: 3

Max Peng
Max Peng

Reputation: 3197

The @Cacheable will generate a proxy, but it will only work on the public method. Also, as mentioned in the comments, the proxy will not work on the self-call method.

Upvotes: 0

Related Questions