SpringBoot集成Redis實現緩存處理(Spring AOP實現)


第一章 需求分析

計划在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(收藏)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM