高手如何處理快取:SpringBoot整合Redis實現快取處理(AOP技術)!

CSDN學院發表於2020-04-04

作者:smileNicky

 

第一章 需求分析

計劃在Team的開源專案里加入Redis實現快取處理,因為業務功能已經實現了一部分,通過寫Redis工具類,然後引用,改動量較大,而且不可以實現解耦合,所以想到了Spring框架的AOP(面向切面程式設計)。

開源專案:https://github.com/u014427391/jeeplatform

推薦教程:https://edu.csdn.net/courses/o280_s348_k?utm_source=blog11

歡迎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裡儲存到了快取

更多學習需求可以留言告訴CSDN學院的我們哦~~

推薦CSDN學院地址:https://edu.csdn.net?utm_source=blog11

-------------------------------------------------

希望本文對大家有用!

相關文章