springboot利用AOP自定义注解的方式提供redis缓存

 

  1. 添加pom文件支持

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    ​
      <groupId>com.sg</groupId>
      <artifactId>cristina</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <name>cristina</name>
      <description>El psy congroo</description>
    ​
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
      </parent>
    ​
    ​
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
      </properties>
    ​
      <dependencies>
        <dependency>
          <groupId>commons-io</groupId>
          <artifactId>commons-io</artifactId>
          <version>2.4</version>
        </dependency>
    ​
    ​
        <dependency>
          <groupId>commons-lang</groupId>
          <artifactId>commons-lang</artifactId>
          <version>2.5</version>
        </dependency>
    ​
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.1.9</version>
        </dependency>
    ​
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.46</version>
        </dependency>
    ​
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    ​
        <dependency>
          <groupId>com.github.pagehelper</groupId>
          <artifactId>pagehelper</artifactId>
          <version>4.0.0</version>
        </dependency>
    ​
        <!-- dubbo相关 -->
        <!-- zookeeper -->
        <dependency>
          <groupId>org.apache.zookeeper</groupId>
          <artifactId>zookeeper</artifactId>
          <version>3.4.8</version>
        </dependency>
        <dependency>
          <groupId>com.github.sgroschupf</groupId>
          <artifactId>zkclient</artifactId>
          <version>0.1</version>
        </dependency>
        <!-- Dubbox -->
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>dubbo</artifactId>
          <version>2.8.4</version>
        </dependency>
        <!-- springboot的注解处理器 -->
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-configuration-processor</artifactId>
          <optional>true</optional>
        </dependency>
    ​
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
        </dependency>
    ​
    ​
        <dependency>
          <groupId>org.mybatis.spring.boot</groupId>
          <artifactId>mybatis-spring-boot-starter</artifactId>
          <version>1.3.2</version>
        </dependency>
    ​
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <!--<scope>runtime</scope>-->
        </dependency>
    ​
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.16.22</version>
        </dependency>
    ​
        <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger2</artifactId>
          <version>2.7.0</version>
        </dependency>
    ​
        <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger-ui</artifactId>
          <version>2.7.0</version>
        </dependency>
    ​
        <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.8.13</version>
        </dependency>
    ​
    ​
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    ​
        <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
          <version>2.8.1</version>
        </dependency>
    ​
    ​
    ​
        <dependency>
          <groupId>org.apache.httpcomponents</groupId>
          <artifactId>httpclient</artifactId>
          <version>4.5</version>
        </dependency>
    ​
      </dependencies>
    ​
    ​
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
          <!-- mybatis generator 自动生成代码插件 -->
          <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.2</version>
            <configuration>
              <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
              <overwrite>true</overwrite>
              <verbose>true</verbose>
            </configuration>
          </plugin>
        </plugins>
      </build>
    ​
    </project>
    ​
  2. springboot开启AOP

  3. @EnableAspectJAutoProxy
    @SpringBootApplication
    @MapperScan("com.sg.cristina.dao")
    public class CristinaApplication {
    ​
      public static void main(String[] args) {
        SpringApplication.run(CristinaApplication.class, args);
      }
    ​
    }
  4. 自定义用于添加缓存的注解和用户删除缓存的注解

    /**
     * Created by jw on 19/3/29.
     * @usage  缓存注解类
     */
    ​
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Documented
    public @interface Cacheable {
            String key();
            String fieldKey() ;
            int expireTime() default 3600;
    ​
    ​
    }
    ​
    ​
    /**
     * Created by jw on 19/3/29.
     *
     * @usage 清除过期缓存注解,放置于update delete insert 类型逻辑之上
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface CacheEvict {
        String key();
        String fieldKey() ;
        int expireTime() default 3600;
    ​
    }
    ​

     

  5. 切面逻辑

    import java.lang.reflect.Method;
    ​
    /**
     * Created by jw on 19/3/29.
     */
    @Aspect
    @EnableAspectJAutoProxy
    @Component
    public class RedisCacheAspect {
    ​
        private static final Logger logger = Logger.getLogger(RedisCacheAspect.class);
        /**
         * 分隔符
         **/
        private static final String DELIMITER = "|";
    ​
    ​
        @Autowired
        RedisUtil redisUtil;
    ​
        /**
         * Service层切点 使用到了我们定义的 Cacheable 作为切点表达式。
         * 而且我们可以看出此表达式基于 annotation。
         * 并且用于内建属性为查询的方法之上
         */
        @Pointcut("@annotation(com.sg.cristina.config.aspect.Cacheable)")
        public void queryCache() {
        }
    ​
        /**
         * Service层切点 使用到了我们定义的 RedisEvict 作为切点表达式。
         * 而且我们可以看出此表达式是基于 annotation 的。
         * 并且用于内建属性为非查询的方法之上,用于更新表
         */
        @Pointcut("@annotation(com.sg.cristina.config.aspect.CacheEvict)")
        public void ClearCache() {
        }
    ​
    ​
        @Around("queryCache()")
        public Object Interceptor(ProceedingJoinPoint pjp) throws Throwable {
            Object result = null;
            System.out.println("query cache -------------------------------");
            Method method = getMethod(pjp);
            //获取被切方法的注解
            Cacheable cacheable = method.getAnnotation(Cacheable.class);
            //key的value
            String fieldKey = parseKey(cacheable.fieldKey(), method, pjp.getArgs());
            //使用redis 的hash进行存取,类似于一张表
            result = redisUtil.hget(cacheable.key(), fieldKey);
            if (result == null) {
            //如果缓存没有则执行原本逻辑
                System.out.println("cache is empty ,query db--------");
                try {
                    result = pjp.proceed();
                    System.out.println("get result from db ----------");
                    //从db查询到了则再加入缓存
                    if (result != null) {
                        redisUtil.hset(cacheable.key(), fieldKey, result);
                        System.out.println("set result to cache ----------");
                    }
                } catch (Throwable e) {
                    e.printStackTrace();
                    logger.error(e.getMessage());
                }
            }
            return result;
        }
    ​
        /*** 定义清除缓存逻辑*/
        @Around(value = "ClearCache()")
        public Object evict(ProceedingJoinPoint pjp) throws Throwable {
            Object result = null;
            System.out.println("clear cache ---------------");
            Method method = getMethod(pjp);
            CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
            String fieldKey = parseKey(cacheEvict.fieldKey(), method, pjp.getArgs());
            //先删除缓存
            redisUtil.hdel(cacheEvict.key(), fieldKey);
            //然后操作db
            result = pjp.proceed();
    ​
            return result;
        }
    ​
        /**
         * 获取被拦截方法对象
         * <p>
         * MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
         * 而缓存的注解在实现类的方法上
         * 所以应该使用反射获取当前对象的方法对象
         */
        public Method getMethod(ProceedingJoinPoint pjp) {
            //获取参数的类型
            Object[] args = pjp.getArgs();
            Class[] argTypes = new Class[pjp.getArgs().length];
            for (int i = 0; i < args.length; i++) {
                argTypes[i] = args[i].getClass();
            }
            Method method = null;
            try {
    ​
                Object target = pjp.getTarget();
                Class<?> aClass = target.getClass();
                Signature signature = pjp.getSignature();
                method = aClass.getMethod(signature.getName(), argTypes);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
                logger.error(e.getMessage());
            } catch (SecurityException e) {
                e.printStackTrace();
                logger.error(e.getMessage());
            }
            return method;
    ​
        }
    ​
        /**
         * 获取缓存的key
         * key 定义在注解上,支持SPEL表达式
         *
         * @param
         * @return
         */
        private String parseKey(String key, Method method, Object[] args) {
    ​
            //获取被拦截方法参数名列表(使用Spring支持类库)
            LocalVariableTableParameterNameDiscoverer u =
                    new LocalVariableTableParameterNameDiscoverer();
            String[] paraNameArr = u.getParameterNames(method);
    ​
            //使用SPEL进行key的解析
            ExpressionParser parser = new SpelExpressionParser();
            //SPEL上下文
            StandardEvaluationContext context = new StandardEvaluationContext();
            //把方法参数放入SPEL上下文中
            for (int i = 0; i < paraNameArr.length; i++) {
                context.setVariable(paraNameArr[i], args[i]);
            }
            return parser.parseExpression(key).getValue(context, String.class);
        }
    ​
    ​
    }

     

  6. 注解使用方式及注意事项

    1. 这种注解方式适用于单表的增删改查,一般以id(唯一标示)作为缓存的要素,如果将连表查询的结果缓存到redis那么对缓存的对象有一定的要求,如:读的频率高,且改动少。

    2. 本次采用的缓存逻辑是在service层对select类方法进行先查缓存再查db处理,如果缓存没查到,那么执行db查询,然后将查到的结果缓存到redis,对update的方法和delete的方法统一采用先删除缓存然后修改db

    3. 使用方法参考:

      ​
      /**
       * @Author: jiangwei
       * @Date: 2019/4/20
       * @Desc:
       */
      ​
      @Service
      public class UserServiceImpl implements UserService {
      ​
      ​
          @Autowired
          SgUserMapper userMapper ;
          /**
           * @param sgId
           * @mbggenerated
           */
          @CacheEvict(key = "SgUser",fieldKey = "#sgId")
          @Override
          public int deleteByPrimaryKey(Integer sgId) {
              return userMapper.deleteByPrimaryKey(sgId);
          }
      ​
          /**
           * @param record
           * @mbggenerated
           */
          @Override
          public int insert(SgUser record) {
              return userMapper.insert(record);
          }
      ​
          /**
           * @param record
           * @mbggenerated
           */
          @CacheEvict(key = "SgUser",fieldKey = "#record.sgId")
          @Override
          public int insertSelective(SgUser record) {
              return userMapper.insertSelective(record);
          }
      ​
          /**
           * @param sgId
           * @mbggenerated
           */
          @Cacheable(key = "SgUser",fieldKey = "#sgId")
          @Override
          public SgUser selectByPrimaryKey(Integer sgId) {
              return userMapper.selectByPrimaryKey(sgId);
          }
      ​
          /**
           * @param record
           * @mbggenerated
           */
          @CacheEvict(key = "SgUser",fieldKey = "#record.sgId")
          @Override
          public int updateByPrimaryKeySelective(SgUser record) {
              return userMapper.updateByPrimaryKeySelective(record);
          }
      ​
          /**
           * @param record
           * @mbggenerated
           */
          @CacheEvict(key = "SgUser",fieldKey = "#record.sgId")
          @Override
          public int updateByPrimaryKey(SgUser record) {
              return userMapper.updateByPrimaryKey(record);
          }
      ​
          @Override
          public SgUser selectByGitId(String gitId) {
              return userMapper.selectByGitId(gitId);
          }
      }
      ​

       

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_40249994

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值