当前位置: 移动技术网 > IT编程>开发语言>Java > 浅谈SpringBoot集成Redis实现缓存处理(Spring AOP实现)

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

2019年07月19日  | 移动技术网IT编程  | 我要评论

第一章 需求分析

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

开源项目:

第二章 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编译器):

3.2 redis简介

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

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

springboot框架入门的可以参考之前的文章:

第四章 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里保存到了缓存

 

项目代码:

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

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网