SpringBoot 集成 Redis 实现缓存处理(Spring AOP 实现) - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
javahih
V2EX    GitHub

SpringBoot 集成 Redis 实现缓存处理(Spring AOP 实现)

  •  
  •   javahih 2017-12-15 10:11:40 +08:00 3903 次点击
    这是一个创建于 2931 天前的主题,其中的信息可能已经有所发展或是发生改变。

    第一章 需求分析

    计划在 Team 的开源项目里加入 Redis 实现缓存处理,因为业务功能已经实现了一部分,通过写 Redis 工具类,然后引用,改动量较大,而且不可以实现解耦合,所以想到了 Spring 框架的 AOP(面向切面编程)。 开源项目: https://github.com/u014427391/jeeplatform 欢迎 star(收藏)

    第二章 SpringBoot 简介

    Spring 框架作为 JavaEE 框架领域的一款重要的开源框架,在企业应用开发中有着很重要的作用,同时 Spring 框架及其子框架很多,所以知识量很广。 SpringBoot:一款 Spring 框架的子框架,也可以叫微框架,是 2014 年推出的一款使 Spring 框架开发变得容易的框架。学过 Spring 框架的都知识,Spring 框架难以避免地需要配置不少 XMl,而使用 SpringBoot 框架的话,就可以使用注解开发,极大地简化基于 Spring 框架的开发。SpringBoot 充分利用了 JavaConfig 的配置模式以及“约定优于配置”的理念,能够极大的简化基于 SpringMVC 的 Web 应用和 REST 服务开发。

    第三章 Redis 简介

    3.1 Redis 安装部署(Linux)

    Redis 安装部署的可以参考我的博客(Redis 是基于 C 编写的,所以安装前先安装 gcc 编译器): http://blog.csdn.net/u014427391/article/details/71210989

    3.2 Redis 简介

    Redis 如今已经成为 Web 开发社区最火热的内存数据库之一,随着 Web2.0 的快速发展,再加上半结构数据比重加大,网站对高效性能的需求也越来越多。 而且大型网站一般都有几百台或者更多 Redis 服务器。Redis 作为一款功能强大的系统,无论是存储、队列还是缓存系统,都有其用武之地。

    SpringBoot 框架入门的可以参考我之前的博客: http://blog.csdn.net/u014427391/article/details/70655332

    第四章 Redis 缓存实现

    4.1 下面结构图

    项目结构图: 这里写图片描述

    4.2 SpringBoot 的 yml 文件配置

    添加 resource 下面的 application.yml 配置,这里主要配置 mysql,druid,redis

    spring: datasource: # 主数据源 shop: url: jdbc:mysql://127.0.0.1:3306/jeeplatform?autoRecOnnect=true&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 连接池设置 druid: initial-size: 5 min-idle: 5 max-active: 20 # 配置获取连接等待超时的时间 max-wait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 time-between-eviction-runs-millis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 min-evictable-idle-time-millis: 300000 # Oracle 请使用 select 1 from dual validation-query: SELECT 'x' test-while-idle: true test-on-borrow: false test-on-return: false # 打开 PSCache,并且指定每个连接上 PSCache 的大小 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 # 配置监控统计拦截的 filters,去掉后监控界面 sql 无法统计,'wall'用于防火墙 filters: stat,wall,slf4j # 通过 connectProperties 属性来打开 mergeSql 功能;慢 SQL 记录 connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 合并多个 DruidDataSource 的监控数据 use-global-data-source-stat: true jpa: database: mysql hibernate: show_sql: true format_sql: true ddl-auto: none naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl mvc: view: prefix: /WEB-INF/jsp/ suffix: .jsp #Jedis 配置 jedis : pool : host : 127.0.0.1 port : 6379 password : password timeout : 0 config : maxTotal : 100 maxIdle : 10 maxWaitMillis : 100000 

    编写一个配置类启动配置 JedisConfig.java:

    package org.muses.jeeplatform.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @Configuration //@ConfigurationProperties(prefix = JedisConfig.JEDIS_PREFIX ) public class JedisConfig { //public static final String JEDIS_PREFIX = "jedis"; @Bean(name= "jedisPool") @Autowired public JedisPool jedisPool(@Qualifier("jedisPoolConfig") JedisPoolConfig config, @Value("${spring.jedis.pool.host}")String host, @Value("${spring.jedis.pool.port}")int port, @Value("${spring.jedis.pool.timeout}")int timeout, @Value("${spring.jedis.pool.password}")String password) { return new JedisPool(config, host, port,timeout,password); } @Bean(name= "jedisPoolConfig") public JedisPoolConfig jedisPoolConfig (@Value("${spring.jedis.pool.config.maxTotal}")int maxTotal, @Value("${spring.jedis.pool.config.maxIdle}")int maxIdle, @Value("${spring.jedis.pool.config.maxWaitMillis}")int maxWaitMillis) { JedisPoolConfig cOnfig= new JedisPoolConfig(); config.setMaxTotal(maxTotal); config.setMaxIdle(maxIdle); config.setMaxWaitMillis(maxWaitMillis); return config; } } 

    4.3 元注解类编写

    编写一个元注解类 RedisCache.java,被改注解定义的类都自动实现 AOP 缓存处理

    package org.muses.jeeplatform.annotation; import org.muses.jeeplatform.common.RedisCacheNamespace; import java.lang.annotation.*; /** * 元注解 用来标识查询数据库的方法 */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RedisCache { // RedisCacheNamespace nameSpace(); } 

    JDK 5 提供的注解,除了 Retention 以外,还有另外三个,即 Target、Inherited 和 Documented。基于这个,我们可以实现自定义的元注解 我们设置 RedisCache 基于 Method 方法级别引用。

    1.RetentionPolicy.SOURCE 这种类型的 Annotations 只在源代码级别保留,编译时就会被忽略 2.RetentionPolicy.CLASS 这种类型的 Annotations 编译时被保留,在 class 文件中存在,但 JVM 将会忽略 3.RetentionPolicy.RUNTIME 这种类型的 Annotations 将被 JVM 保留,所以他们能在运行时被 JVM 或其他使用反射机制的代码所读取和使用.

    4.4 调用 JedisPool 实现 Redis 缓存处理

    package org.muses.jeeplatform.cache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import javax.annotation.Resource; @Component("redisCache") public class RedisCache { @Autowired private JedisPool jedisPool; private JedisPool getJedisPool(){ return jedisPool; } public void setJedisPool(JedisPool jedisPool){ this.jedisPool = jedisPool; } /** * 从 Redis 缓存获取数据 * @param redisKey * @return */ public Object getDataFromRedis(String redisKey){ Jedis jedis = jedisPool.getResource(); byte[] byteArray = jedis.get(redisKey.getBytes()); if(byteArray != null){ return SerializeUtil.unSerialize(byteArray); } return null; } /** * 保存数据到 Redis * @param redisKey */ public String saveDataToRedis(String redisKey,Object obj){ byte[] bytes = SerializeUtil.serialize(obj); Jedis jedis = jedisPool.getResource(); String code = jedis.set(redisKey.getBytes(), bytes); return code; } } 

    对象序列化的工具类:

    package org.muses.jeeplatform.cache; import java.io.*; public class SerializeUtil { /** * 序列化对象 * @param obj * @return */ public static byte[] serialize(Object obj){ ObjectOutputStream oos = null; ByteArrayOutputStream baos = null; try{ baos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(baos); oos.writeObject(obj); byte[] byteArray = baos.toByteArray(); return byteArray; }catch(IOException e){ e.printStackTrace(); } return null; } /** * 反序列化对象 * @param byteArray * @return */ public static Object unSerialize(byte[] byteArray){ ByteArrayInputStream bais = null; try { //反序列化为对象 bais = new ByteArrayInputStream(byteArray); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); } catch (Exception e) { e.printStackTrace(); } return null; } } 

    这里记得 Vo 类都要实现 Serializable 例如菜单信息 VO 类,这是一个 JPA 映射的实体类

    package org.muses.jeeplatform.core.entity.admin; import javax.persistence.*; import java.io.Serializable; import java.util.List; /** * @description 菜单信息实体 * @author Nicky * @date 2017 年 3 月 17 日 */ @Table(name="sys_menu") @Entity public class Menu implements Serializable { /** 菜单 Id**/ private int menuId; /** 上级 Id**/ private int parentId; /** 菜单名称**/ private String menuName; /** 菜单图标**/ private String menuIcon; /** 菜单 URL**/ private String menuUrl; /** 菜单类型**/ private String menuType; /** 菜单排序**/ private String menuOrder; /**菜单状态**/ private String menuStatus; private List<Menu> subMenu; private String target; private boolean hasSubMenu = false; public Menu() { super(); } @Id @GeneratedValue(strategy=GenerationType.IDENTITY) public int getMenuId() { return this.menuId; } public void setMenuId(int menuId) { this.menuId = menuId; } @Column(length=100) public int getParentId() { return parentId; } public void setParentId(int parentId) { this.parentId = parentId; } @Column(length=100) public String getMenuName() { return this.menuName; } public void setMenuName(String menuName) { this.menuName = menuName; } @Column(length=30) public String getMenuIcon() { return this.menuIcon; } public void setMenuIcon(String menuIcon) { this.menuIcon = menuIcon; } @Column(length=100) public String getMenuUrl() { return this.menuUrl; } public void setMenuUrl(String menuUrl) { this.menuUrl = menuUrl; } @Column(length=100) public String getMenuType() { return this.menuType; } public void setMenuType(String menuType) { this.menuType = menuType; } @Column(length=10) public String getMenuOrder() { return menuOrder; } public void setMenuOrder(String menuOrder) { this.menuOrder = menuOrder; } @Column(length=10) public String getMenuStatus(){ return menuStatus; } public void setMenuStatus(String menuStatus){ this.menuStatus = menuStatus; } @Transient public List<Menu> getSubMenu() { return subMenu; } public void setSubMenu(List<Menu> subMenu) { this.subMenu = subMenu; } public void setTarget(String target){ this.target = target; } @Transient public String getTarget(){ return target; } public void setHasSubMenu(boolean hasSubMenu){ this.hasSubMenu = hasSubMenu; } @Transient public boolean getHasSubMenu(){ return hasSubMenu; } } 

    4.5 Spring AOP 实现监控所有被 @RedisCache 注解的方法缓存

    先从 Redis 里获取缓存,查询不到,就查询 MySQL 数据库,然后再保存到 Redis 缓存里,下次查询时直接调用 Redis 缓存

    package org.muses.jeeplatform.cache; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; /** * AOP 实现 Redis 缓存处理 */ @Component @Aspect public class RedisAspect { private static final Logger LOGGER = LoggerFactory.getLogger(RedisAspect.class); @Autowired @Qualifier("redisCache") private RedisCache redisCache; /** * 拦截所有元注解 RedisCache 注解的方法 */ @Pointcut("@annotation(org.muses.jeeplatform.annotation.RedisCache)") public void pointcutMethod(){ } /** * 环绕处理,先从 Redis 里获取缓存,查询不到,就查询 MySQL 数据库, * 然后再保存到 Redis 缓存里 * @param joinPoint * @return */ @Around("pointcutMethod()") public Object around(ProceedingJoinPoint joinPoint){ //前置:从 Redis 里获取缓存 //先获取目标方法参数 long startTime = System.currentTimeMillis(); String applId = null; Object[] args = joinPoint.getArgs(); if (args != null && args.length > 0) { applId = String.valueOf(args[0]); } //获取目标方法所在类 String target = joinPoint.getTarget().toString(); String className = target.split("@")[0]; //获取目标方法的方法名称 String methodName = joinPoint.getSignature().getName(); //redis 中 key 格式: applId:方法名称 String redisKey = applId + ":" + className + "." + methodName; Object obj = redisCache.getDataFromRedis(redisKey); if(obj!=null){ LOGGER.info("**********从 Redis 中查到了数据**********"); LOGGER.info("Redis 的 KEY 值:"+redisKey); LOGGER.info("REDIS 的 VALUE 值:"+obj.toString()); return obj; } long endTime = System.currentTimeMillis(); LOGGER.info("Redis 缓存 AOP 处理所用时间:"+(endTime-startTime)); LOGGER.info("**********没有从 Redis 查到数据**********"); try{ obj = joinPoint.proceed(); }catch(Throwable e){ e.printStackTrace(); } LOGGER.info("**********开始从 MySQL 查询数据**********"); //后置:将数据库查到的数据保存到 Redis String code = redisCache.saveDataToRedis(redisKey,obj); if(code.equals("OK")){ LOGGER.info("**********数据成功保存到 Redis 缓存!!!**********"); LOGGER.info("Redis 的 KEY 值:"+redisKey); LOGGER.info("REDIS 的 VALUE 值:"+obj.toString()); } return obj; } } 

    然后调用 @RedisCache 实现缓存

    /** * 通过菜单 Id 获取菜单信息 * @param id * @return */ @Transactional @RedisCache public Menu findMenuById(@RedisCacheKey int id){ return menuRepository.findMenuByMenuId(id); } 

    登录系统,然后加入 @RedisCache 注解的方法都会实现 Redis 缓存处理 这里写图片描述

    这里写图片描述

    可以看到 Redis 里保存到了缓存

    这里写图片描述

    项目代码: https://github.com/u014427391/jeeplatform, 欢迎去 github 上 star(收藏)

    2 条回复    2017-12-16 22:15:11 +08:00
    letitbesqzr
        1
    letitbesqzr  
       2017-12-15 10:53:40 +08:00
    不是有 spring cache 可以做这事,而且可以用 spring data redis 更方便的操作啊
    jack80342
        2
    jack80342  
       2017-12-16 22:15:11 +08:00
    翻译了 Spring Boot 最新的官方文档,https://www.gitbook.com/book/jack80342/spring-boot/details
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     934 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 20:56 PVG 04:56 LAX 12:56 JFK 15:56
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86