Java custom annotations implement the Redis automatic caching method

  • 2020-07-21 07:57:26
  • OfStack

In real development, you will often need to query a piece of data (such as user information) from MySQL and save the user information to Redis.

At the beginning, we may write another section of Redis-related operation code after the query's business logic, but after a long time, we find that this part of the code actually only does the write action of Redis, which has no substantial connection with the business logic. So is there any way for us to omit this duplication of effort?

The first thought of using AOP is to finish our faceted correlation processing (that is, writing to Redis) before querading for some data in the 1 pointcut (Pointcut). So how do you know where to cache, where to use AOP? The implementation of the reference database transaction USES @Transactional, so we can also customize an annotation @RedisCache for the method we want, the result of the method is the information we need to save, and the query parameters of the method (such as the user's id) can be used as key.

The above analysis seems to work, so go ahead and do it!

The detailed steps

1. Create a custom annotation @RedisCache


package redis;

import java.lang.annotation.*;

/**
 *  Custom annotations, combined AOP implementation Redis Automatic cache 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface RedisCache {
}

2. Create a helper class for caching writes: ES27en.java, which contains a generic method for receiving different class of instance objects to ensure that our method can be generic. In this case, you simply convert the object to json and save it with string in Redis. And no matter what the case, you can actually improve the specific logic, such as whether the cache exists, whether the cache information is up to date, and so on.


package redis;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class RedisHelper {

  @Autowired
  private StringRedisTemplate stringRedisTemplate;

  public <T> void saveCache(String key,T t){
    String json = JSONObject.toJSONString(t);
    stringRedisTemplate.opsForValue().set(key,json);
  }

}

3. Create ES35en.java, using the AOP framework AspectJ to complete the faceted processing (surround notifications with panning oil, and use specific types of notifications selectively and selectively as needed). Here we use return notifications, which are performed after a method call successfully returns a result


package redis;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RedisCacheAspect {

  @Autowired
  private RedisHelper redisHelper;

  @Pointcut("@annotation(redis.RedisCache)")
  public void setJoinPoint(){}

  // Surrounding the notification : You can get the return value 
  @Around(value = "setJoinPoint()")
  public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
    Object result = null;
    try {
      // Pre notice 

      result = proceedingJoinPoint.proceed();

      // Return to inform 

      // The cache to Redis
      Object[] args = proceedingJoinPoint.getArgs();
      //key Policy: Full class name of the object to be cached -id , such as: entity.User-1
      redisHelper.saveCache(result.getClass().getName()+"-"+args[0],result);

    } catch (Throwable e) {
      // Abnormal notice 

    }
    // The rear notice 

    return result;
  }

}

4. Next comes the business-specific code

UserController.java


package controller;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import service.UserService;

@SuppressWarnings("unused")
@Controller
public class UserController {

  @Autowired
  private UserService userService;

  @RequestMapping(value = "/user/{id}", method = RequestMethod.GET,produces = "application/json;charset=utf-8")
  @ResponseBody
  public String test(@PathVariable Long id){
    return JSONObject.toJSONString(userService.get(id));
  }
}

UserService.java, where the get method USES the custom annotation @RedisCache


package service;

import dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.RedisCache;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class UserService<User> implements BaseService<User> {

  @Autowired
  private UserDao userDao;

  public Map add(User user) {
    return null;
  }

  public Map update(User user) {
    return null;
  }

  @RedisCache
  public User get(Long id) {
    return (User) userDao.get(id);
  }

  public List<User> query(User user) {
    List<User> list = new ArrayList<User>();
    list = userDao.query(user);
    return list;
  }

  public Map delete(User user) {
    return null;
  }
}

5. Test

Direct access to the browser http: / / localhost: 8080 / user / 1, to get to return the result

http://localhost:8080/user/1

Connect to Redis to see the results


127.0.0.1:6381> keys entity*
1) "entity.User-1"
127.0.0.1:6381> get entity.User-1
"{\"id\":1,\"mobile\":\"110\",\"name\":\"\xe7\x94\xa8\xe6\x88\xb71\",\"password\":\"123456\",\"username\":\"0001\"}"
127.0.0.1:6381>

Ok, so far we have seen that the initial idea has been validated. Just annotate @RedisCache on the query method and write Redis automatically and silently, isn't that convenient?


Related articles: