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

第一章 需求分析

計劃在Team的開源項目里加入Redis實現(xiàn)緩存處理,因為業(yè)務(wù)功能已經(jīng)實現(xiàn)了一部分增热,通過寫Redis工具類,然后引用胧辽,改動量較大峻仇,而且不可以實現(xiàn)解耦合,所以想到了Spring框架的AOP(面向切面編程)邑商。
開源項目:https://github.com/u014427391/jeeplatform
歡迎star(收藏)

第二章 SpringBoot簡介

Spring框架作為JavaEE框架領(lǐng)域的一款重要的開源框架摄咆,在企業(yè)應(yīng)用開發(fā)中有著很重要的作用,同時Spring框架及其子框架很多人断,所以知識量很廣吭从。
SpringBoot:一款Spring框架的子框架,也可以叫微框架恶迈,是2014年推出的一款使Spring框架開發(fā)變得容易的框架涩金。學(xué)過Spring框架的都知識,Spring框架難以避免地需要配置不少XMl暇仲,而使用SpringBoot框架的話步做,就可以使用注解開發(fā),極大地簡化基于Spring框架的開發(fā)奈附。SpringBoot充分利用了JavaConfig的配置模式以及“約定優(yōu)于配置”的理念全度,能夠極大的簡化基于SpringMVC的Web應(yīng)用和REST服務(wù)開發(fā)。

第三章 Redis簡介

3.1 Redis安裝部署(Linux)

Redis安裝部署的可以參考我的博客(Redis是基于C編寫的斥滤,所以安裝前先安裝gcc編譯器):http://blog.csdn.net/u014427391/article/details/71210989

3.2 Redis簡介

Redis如今已經(jīng)成為Web開發(fā)社區(qū)最火熱的內(nèi)存數(shù)據(jù)庫之一将鸵,隨著Web2.0的快速發(fā)展勉盅,再加上半結(jié)構(gòu)數(shù)據(jù)比重加大,網(wǎng)站對高效性能的需求也越來越多顶掉。
而且大型網(wǎng)站一般都有幾百臺或者更多Redis服務(wù)器草娜。Redis作為一款功能強(qiáng)大的系統(tǒng),無論是存儲痒筒、隊列還是緩存系統(tǒng)驱还,都有其用武之地。

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

第四章 Redis緩存實現(xiàn)

4.1下面結(jié)構(gòu)圖

項目結(jié)構(gòu)圖:


這里寫圖片描述

4.2 SpringBoot的yml文件配置

添加resource下面的application.yml配置凸克,這里主要配置mysql,druid闷沥,redis

spring:
  datasource:

    # 主數(shù)據(jù)源
    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

    # 連接池設(shè)置
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 配置獲取連接等待超時的時間
      max-wait: 60000
      # 配置間隔多久才進(jìn)行一次檢測萎战,檢測需要關(guān)閉的空閑連接,單位是毫秒
      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
      # 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計路狮,'wall'用于防火墻
      filters: stat,wall,slf4j
      # 通過connectProperties屬性來打開mergeSql功能虫啥;慢SQL記錄
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      # 合并多個DruidDataSource的監(jiān)控數(shù)據(jù)
      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,被改注解定義的類都自動實現(xiàn)AOP緩存處理

package org.muses.jeeplatform.annotation;

import org.muses.jeeplatform.common.RedisCacheNamespace;

import java.lang.annotation.*;

/**
 * 元注解 用來標(biāo)識查詢數(shù)據(jù)庫的方法
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCache {
//    RedisCacheNamespace nameSpace();
}

JDK 5提供的注解奄妨,除了Retention以外涂籽,還有另外三個,即Target 砸抛、Inherited 和 Documented评雌。基于這個直焙,我們可以實現(xiàn)自定義的元注解
我們設(shè)置RedisCache基于Method方法級別引用景东。

1.RetentionPolicy.SOURCE 這種類型的Annotations只在源代碼級別保留,編譯時就會被忽略
2.RetentionPolicy.CLASS 這種類型的Annotations編譯時被保留,在class文件中存在,但JVM將會忽略
3.RetentionPolicy.RUNTIME 這種類型的Annotations將被JVM保留,所以他們能在運(yùn)行時被JVM或其他使用反射機(jī)制的代碼所讀取和使用.

4.4 調(diào)用JedisPool實現(xiàn)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緩存獲取數(shù)據(jù)
     * @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;
    }
    
    /**
     * 保存數(shù)據(jù)到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類都要實現(xiàn)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;
    
    /** 菜單圖標(biāo)**/
    private String menuIcon;
    
    /** 菜單URL**/
    private String menuUrl;
    
    /** 菜單類型**/
    private String menuType;
    
    /** 菜單排序**/
    private String menuOrder;

    /**菜單狀態(tài)**/
    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實現(xiàn)監(jiān)控所有被@RedisCache注解的方法緩存

先從Redis里獲取緩存,查詢不到奔誓,就查詢MySQL數(shù)據(jù)庫斤吐,然后再保存到Redis緩存里,下次查詢時直接調(diào)用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實現(xiàn)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(){

    }

    /**
     * 環(huán)繞處理厨喂,先從Redis里獲取緩存,查詢不到和措,就查詢MySQL數(shù)據(jù)庫,
     * 然后再保存到Redis緩存里
     * @param joinPoint
     * @return
     */
    @Around("pointcutMethod()")
    public Object around(ProceedingJoinPoint joinPoint){
        //前置:從Redis里獲取緩存
        //先獲取目標(biāo)方法參數(shù)
        long startTime = System.currentTimeMillis();
        String applId = null;
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            applId = String.valueOf(args[0]);
        }

        //獲取目標(biāo)方法所在類
        String target = joinPoint.getTarget().toString();
        String className = target.split("@")[0];

        //獲取目標(biāo)方法的方法名稱
        String methodName = joinPoint.getSignature().getName();

        //redis中key格式:    applId:方法名稱
        String redisKey = applId + ":" + className + "." + methodName;

        Object obj = redisCache.getDataFromRedis(redisKey);

        if(obj!=null){
            LOGGER.info("**********從Redis中查到了數(shù)據(jù)**********");
            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查到數(shù)據(jù)**********");
        try{
            obj = joinPoint.proceed();
        }catch(Throwable e){
            e.printStackTrace();
        }
        LOGGER.info("**********開始從MySQL查詢數(shù)據(jù)**********");
        //后置:將數(shù)據(jù)庫查到的數(shù)據(jù)保存到Redis
        String code = redisCache.saveDataToRedis(redisKey,obj);
        if(code.equals("OK")){
            LOGGER.info("**********數(shù)據(jù)成功保存到Redis緩存!!!**********");
            LOGGER.info("Redis的KEY值:"+redisKey);
            LOGGER.info("REDIS的VALUE值:"+obj.toString());
        }
        return obj;
    }


}

然后調(diào)用@RedisCache實現(xiàn)緩存

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

登錄系統(tǒng)杯聚,然后加入@RedisCache注解的方法都會實現(xiàn)Redis緩存處理


這里寫圖片描述
這里寫圖片描述

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

這里寫圖片描述

項目代碼:https://github.com/u014427391/jeeplatform,歡迎去github上star(收藏)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末臼婆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子幌绍,更是在濱河造成了極大的恐慌颁褂,老刑警劉巖故响,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異颁独,居然都是意外死亡彩届,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門誓酒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來樟蠕,“玉大人,你說我怎么就攤上這事靠柑≌纾” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵歼冰,是天一觀的道長靡狞。 經(jīng)常有香客問我,道長隔嫡,這世上最難降的妖魔是什么甸怕? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮腮恩,結(jié)果婚禮上梢杭,老公的妹妹穿的比我還像新娘。我一直安慰自己秸滴,他們只是感情好武契,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荡含,像睡著了一般吝羞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上内颗,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天钧排,我揣著相機(jī)與錄音,去河邊找鬼均澳。 笑死恨溜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的找前。 我是一名探鬼主播糟袁,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躺盛!你這毒婦竟也來了项戴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤槽惫,失蹤者是張志新(化名)和其女友劉穎周叮,沒想到半個月后辩撑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仿耽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年合冀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片项贺。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡君躺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出开缎,到底是詐尸還是另有隱情棕叫,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布奕删,位于F島的核電站谍珊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏急侥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一侮邀、第九天 我趴在偏房一處隱蔽的房頂上張望坏怪。 院中可真熱鬧,春花似錦绊茧、人聲如沸铝宵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹏秋。三九已至,卻和暖如春亡笑,著一層夾襖步出監(jiān)牢的瞬間侣夷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工仑乌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留百拓,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓晰甚,卻偏偏與公主長得像衙传,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子厕九,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354