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

第一章 需求分析

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

开源项目:https://github.com/u014427391/jeeplatform

第二章 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://www.jb51.net/article/79096.htm

3.2 Redis简介

Redis如今已经成为Web开发社区最火热的内存数据库之一,随着Web2.0的快速发展,再加上半结构数据比重加大,网站对高效性能的需求也越来越多。

而且大型网站一般都有几百台或者更多Redis服务器。Redis作为一款功能强大的系统,无论是存储、队列还是缓存系统,都有其用武之地。

SpringBoot框架入门的可以参考之前的文章:http://www.jb51.net/article/111197.htm

第四章 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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • springboot+mybatis+redis 二级缓存问题实例详解

    前言 什么是mybatis二级缓存? 二级缓存是多个sqlsession共享的,其作用域是mapper的同一个namespace. 即,在不同的sqlsession中,相同的namespace下,相同的sql语句,并且sql模板中参数也相同的,会命中缓存. 第一次执行完毕会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率. Mybatis默认没有开启二级缓存,需要在全局配置(mybatis-config.xml)中开启二级缓存. 本文讲述的是使用Redi

  • SpringBoot+Mybatis项目使用Redis做Mybatis的二级缓存的方法

    介绍 使用mybatis时可以使用二级缓存提高查询速度,进而改善用户体验. 使用redis做mybatis的二级缓存可是内存可控<如将单独的服务器部署出来用于二级缓存>,管理方便. 1.在pom.xml文件中引入redis依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifac

  • 详解SpringBoot集成Redis来实现缓存技术方案

    概述 在我们的日常项目开发过程中缓存是无处不在的,因为它可以极大的提高系统的访问速度,关于缓存的框架也种类繁多,今天主要介绍的是使用现在非常流行的NoSQL数据库(Redis)来实现我们的缓存需求. Redis简介 Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件,Redis 的优势包括它的速度.支持丰富的数据类型.操作原子性,以及它的通用性. 案例整合 本案例是在之前一篇SpringBoot + Mybatis + RESTful的基础上来集

  • SpringBoot项目中使用redis缓存的方法步骤

    本文介绍了SpringBoot项目中使用redis缓存的方法步骤,分享给大家,具体如下: Spring Data Redis为我们封装了Redis客户端的各种操作,简化使用. - 当Redis当做数据库或者消息队列来操作时,我们一般使用RedisTemplate来操作 - 当Redis作为缓存使用时,我们可以将它作为Spring Cache的实现,直接通过注解使用 1.概述 在应用中有效的利用redis缓存可以很好的提升系统性能,特别是对于查询操作,可以有效的减少数据库压力. 具体的代码参照该

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

    第一章 需求分析 计划在Team的开源项目里加入Redis实现缓存处理,因为业务功能已经实现了一部分,通过写Redis工具类,然后引用,改动量较大,而且不可以实现解耦合,所以想到了Spring框架的AOP(面向切面编程). 开源项目:https://github.com/u014427391/jeeplatform 第二章 SpringBoot简介 Spring框架作为JavaEE框架领域的一款重要的开源框架,在企业应用开发中有着很重要的作用,同时Spring框架及其子框架很多,所以知识量很广.

  • 浅谈SpringBoot集成Quartz动态定时任务

    SpringBoot自带schedule 沿用的springboot少xml配置的优良传统,本身支持表达式等多种定时任务 注意在程序启动的时候加上@EnableScheduling @Scheduled(cron="0/5 * * * * ?") public void job(){ System.out.println("每五秒执行一次"); } 为什么要使用Quartz 多任务情况下,quartz更容易管理,可以实现动态配置 执行时间表达式: 表达式示例: 集成

  • 浅谈MySQL与redis缓存的同步方案

    本文介绍MySQL与Redis缓存的同步的两种方案 方案1:通过MySQL自动同步刷新Redis,MySQL触发器+UDF函数实现 方案2:解析MySQL的binlog实现,将数据库中的数据同步到Redis 一.方案1(UDF) 场景分析:当我们对MySQL数据库进行数据操作时,同时将相应的数据同步到Redis中,同步到Redis之后,查询的操作就从Redis中查找 过程大致如下: 在MySQL中对要操作的数据设置触发器Trigger,监听操作 客户端(NodeServer)向MySQL中写入数

  • SpringBoot集成Redis数据库,实现缓存管理

    目录 一.Redis简介 二.Spring2.0集成Redis 1.核心依赖 2.配置文件 3.简单测试案例 4.自定义序列化配置 5.序列化测试 三.源代码地址 一.Redis简介 Spring Boot中除了对常用的关系型数据库提供了优秀的自动化支持之外,对于很多NoSQL数据库一样提供了自动化配置的支持,包括:Redis, MongoDB, Elasticsearch.这些案例整理好后,陆续都会上传Git. SpringBoot2 版本,支持的组件越来越丰富,对Redis的支持不仅仅是扩展

  • springBoot集成redis的key,value序列化的相关问题

    使用的是maven工程 springBoot集成redis默认使用的是注解,在官方文档中只需要2步; 1.在pom文件中引入即可 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency> 2.编写一个CacheService接口,使用redisCach

  • 浅谈springboot中tk.mapper代码生成器的用法说明

    问:什么是tk.mapper? 答:这是一个通用的mapper框架,相当于把mybatis的常用数据库操作方法封装了一下,它实现了jpa的规范,简单的查询更新和插入操作都可以直接使用其自带的方法,无需写额外的代码. 而且它还有根据实体的不为空的字段插入和更新的方法,这个是非常好用的哈. 而且它的集成非常简单和方便,下面我来演示下使用它怎么自动生成代码. pom中引入依赖,这里引入tk.mybatis.mapper的版本依赖是因为在mapper-spring-boot-starter的新版本中没有

  • 使用SpringBoot集成redis的方法

    今天,日月在这里教大家如何使用springBoot集成redis,说实话比较简单,网上也有大把的教程.先套用一下网上的简介. 定义 REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统. Redis是一个开源的使用ANSI C语言编写.遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. 它通常被称为数据结构服务器,因为值(value)可以是 字符串(S

  • 浅谈springboot自动装配原理

    一.SpringBootApplication @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFi

  • SpringBoot 集成Redis 过程

    Redis 介绍: Redis 服务 Redis (REmote Dictionary Server) 是一个由Salvatore Sanfilippo 完成的key-value存储系统,是跨平台的非关系型数据库. Redis 是一个开源的使用ANSI C语言编写.遵循BSD 协议的.支持网络.可基于内存.分布式.可选择持久性的键值对存储数据库,并提供多语言的API. Redis 通常被认为是数据结构服务器,其值可以是字符串.哈希.列表.集合以及有序集合. Redis 优点 异常快,每秒可以执行

  • 浅谈SpringBoot资源初始化加载的几种方式

    目录 一.问题 二.资源初始化 一.问题 在平时的业务模块开发过程中,难免会需要做一些全局的任务.缓存.线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢? 二.资源初始化 1.既然要做资源的初始化,那么就需要了解一下springboot启动过程(这里大体说下启动过程,详细:https://www.jb51.net/article/133648.htm) 按照前面的分析,Spring-boot容器启动流程总体可划分为2部分: 执行注解:扫描指定范围下的bean.载入自

随机推荐