1. 程式人生 > >Redis 第四集Spring宣告式快取(Redis)

Redis 第四集Spring宣告式快取(Redis)

說明: 1. 此專案為Spring-data-redis採用宣告式快取的方式進行演示 2. 此專案的精要之處:一是從零開始搭建環境並解決各路異常, 二是配置檔案中的解析(具體解析未貼出於博文中)。 3. 環境中的各種jar版本和jdk版本一定要看清,這個專案演示要求版本之間要匹配

=環境================

在這裡插入圖片描述 =程式碼==============

===>> User.java

package com.it.po;

import java.io.Serializable;
/**
 * 
 * @author 拈花為何不一笑
 *
 */
public class User implements Serializable{

	/**
	 *  serialVersionUID
	 */
	private static final long serialVersionUID = 2115183521540767080L;

	private int id;
	
	private String name;
	
	private String password;
	
	private String sex;//male, femal
	
	public User(){}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	@Override
	public String toString() {
		return "user={"
				+ "id:" + id +","
				+ "name:" + name +","
				+ "sex:" + sex +","
				+ "password:" + password
			+ "}";
	}
	
	
}

===>> SpringDataRedis.java

package com.it.service;

import org.springframework.cache.annotation.Cacheable;

import com.it.po.User;
import com.it.util.PassUtil;

/**
 * Service層。
 * 
 * Spring宣告式快取,避開採用程式設計式操作Redis
 *  達到簡化開發的目的即不需要顯示的在程式碼中寫java程式碼來操作Redis的存取,序列化等操作。
 *  
 *  這也應證了Spring誕生之初的設計理念:簡化。
 * 
 * @author 拈花為何不一笑
 *  
 *  Spring中幾個常用的快取註解(搜尋引擎中檢索具體用法,這裡不詳述)
 * 	 	@Cacheable  
 *  	@CacheEvict
 *  	@CachePut
 *  	@Caching
 *  比如:@CacheEvict(value={"k1","k2"},allEntries=true) 
 *  表示執行註解上的方法後,清除redis中key名稱為k1,k2的資料。
 *  
 *  注意:使用快取的前提條件,並不是什麼資料都可以放到快取中的。
 *  	可以設定快取時間,長時間快取某個資料,當資料被修改了就會變成"髒資料"。
 *  	
 */
public class SpringDataRedis {

	/**
	 * 這個方法相當於Service層方法
	 * @param id
	 * @return
	 */
	//"redis_2.com.it.service.findUserById"為key,存到Redis快取中,下次再查詢此資料就從Redis快取中查詢
	@Cacheable(value = "redis_2.com.it.service.findUserById")
	public User findUserById(int id){
		//模擬從關係型資料庫(比如:mysql,oracle等)中獲取資料
		User user = getUserById(id);
		return user;
	}

	/**
	 * 這個方法相當於Dao層方法,操作關係型資料庫
	 * @param id
	 * @return
	 */
	private User getUserById(int id) {
		System.out.println("===>>getUserById查詢資料庫...");
		User user = new User();
		if(id==6){
			user.setId(id);
			user.setName("拈花為何不一笑");
			user.setPassword(PassUtil.md5Hex("admin123"));
			user.setSex("male");
		}
		
		return user;
	}
}

===>> SpringDataRedisTest.java

 package com.it.test;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.it.po.User;
import com.it.service.SpringDataRedis;



/**
 * JUnit測試:Spring宣告式快取Redis
 * 
 * @author 拈花為何不一笑
 *
 */
public class SpringDataRedisTest {
	
	private SpringDataRedis springDataRedis;
	
	//使用完後,要進行關閉,否則記憶體洩漏
	private AbstractApplicationContext ctx;
	
	@Before//注意:註解@BeforeClass修飾的方法為靜態方法
	public void init(){
		//初始化Spring容器
		ctx = new ClassPathXmlApplicationContext("classpath:applicationContext-redisCache.xml");
		System.out.println("=====>>>初始Spring容器,並連線Redis伺服器成功...");
		springDataRedis = (SpringDataRedis) ctx.getBean("springDataRedis");
	}
	
	//Spring容器關閉
	@After
	public void destroy(){
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("destroy...");
		ctx.close();
	}
	
	/**
	 * 遇到異常:
	 * 	1.Caused by: java.lang.NoClassDefFoundError: org/springframework/aop/config/AopNamespaceUtils
	 * 解決方案:原因是缺少spring-aop.jar,匯入此jar包到類路徑下即可,若使用的是maven工程則在相應的pom.xml中配置即可。
	 *  2.Caused by: java.lang.ClassNotFoundException: org.aopalliance.intercept.MethodInterceptor
	 *  同上解決方案
	 *  3.Caused by: java.lang.UnsupportedClassVersionError: 
	 *  org/springframework/data/redis/cache/RedisCacheManager : Unsupported major.minor version 52.0
	 *  解決方案:jdk版本過低,把jdk改1.8
	 *  
	 *  4.Caused by: java.lang.ClassNotFoundException: org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager
	 *  解決方案:這個異常是由於缺少sping-context-support.jar包,匯入此jar包即可。
	 *  
	 *  5.Caused by: java.lang.ClassNotFoundException: org.springframework.cache.support.AbstractValueAdaptingCache
	 *  解決方案:此類位於spring-context.jar中,
	 *  由於3.2版本沒有這個類,高版本spring-context.jar才有,所以版本升級不能用spring3.2,筆者這裡換成Spring4.39。
	 *  換成spring-context-4.39.jar後其它的spring jar包也要全換成這個版本的。因為它們是一整套。
	 *  
	 *  6.Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisCacheManager' defined in class path resource [applicationContext-redisCache.xml]: Cannot resolve reference to bean 'jedisConnectionFactory' while setting constructor argument; nested exception is
	 *	 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jedisConnectionFactory' defined in class path resource [applicationContext-redisCache.xml]: Cannot create inner bean 'org.springframework.data.redis.connection.RedisStandaloneConfiguration#12325ad' of type 
	 *	 [org.springframework.data.redis.connection.RedisStandaloneConfiguration] while setting constructor argument; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.data.redis.connection.RedisStandaloneConfiguration#12325ad' 
	 *	 defined in class path resource [applicationContext-redisCache.xml]: 
	 *	 Unsatisfied dependency expressed through constructor parameter 1: Could not convert argument value of type [java.lang.String] to required type [int]: Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "${redis.port}"
	 *   解決方案:資料型別轉換異常,哈哈!大家看出來哪裡有問題了嘛?使用字串"${redis.port}",${}語法是從哪裡取值?明白了吧,由於redis.proerties沒有引入,取不到值把這個表示式"${redis.port}"當作了字串,而實際注入的型別要求是int型別
	 *   
	 *  7.Caused by: org.xml.sax.SAXParseException; lineNumber: 14; columnNumber: 85; 元素 
	 *  	"context:property-placeholder" 的字首 "context" 未繫結。
	 *   解決方案:schema約束,名稱空間沒有指定,xsd也沒有指定。簡單加上就可以了,如下:
	 *   xmlns:context="http://www.springframework.org/schema/context"
	 *   http://www.springframework.org/schema/context
     * 	 http://www.springframework.org/schema/context/spring-context.xsd"
	 *  
	 *  8.Caused by: org.xml.sax.SAXParseException; lineNumber: 13; columnNumber: 11; 
	 *  	與元素型別 "beans" 相關聯的屬性名 "http:" 必須後跟 ' = ' 字元。
	 *  解決方案:粗心導致,由於多個雙引號,導致xsi:schemaLocation="..."..." 語法異常。去掉多出來的就OK了
	 *  
	 *  9.(a)異常Failed to instantiate [org.springframework.data.redis.connection.RedisStandaloneConfiguration]: 
	 *  	Constructor threw exception; nested exception is java.lang.NoSuchMethodError: 
	 *  	org.springframework.util.Assert.isTrue(ZLjava/util/function/Supplier;)V
	 *  
	 *     解決方法:Spring-data-redis-2.0.6.jar包中的類 RedisStandaloneConfiguration引用不到Spring3.9 jar中的Assert類的isTrue方法(檢視jar包發現有這個類和方法,就是引用不到)
	 *     (Assert斷言,這個概念首次接觸是在玩C語言的時候...)。
	 *     	第一種方案:想到的是jar順序問題,可能導致訪問不到Assert.java類,通過調整順序後問題依舊存在,說明不是jar順序問題。
	 *      第二種方案:Spring-data-redis-2.0.6.jar與Spring4.3.9版本不兼空。
	 *      	大意了!前面的異常5,解決方案中隨便使用了一個Spring版本(Spring4.3.9),熟悉Maven的同學估計都能夠處理各種框架和jar包版本的匹配。
	 *      	說一下筆者的思路:Spring-data-redis-2.0.6.jar找到pom.xml-->找到它的父專案pom.xml-->找到依賴Srping的版本號,一看為Spring5.0.5
	 *      換上Spring5.0.5(要求jdk1.8) 再解決下面的(b)異常,對於這裡的異常就解決了。
	 *     
	 *     
	 *    (b)異常:
	 *    	The project was not built since its build path is incomplete. 
	 *		Cannot find the class file for org.springframework.context.support.AbstractApplicationContext. 
	 *		Fix the build path then try building this project
	 *		原因:Spring5.x + jkd1.8在MyEclipse6.5空間中不能建立完整的空間,因為該IDE:MyEclipse6.5空間不支援jdk1.8環境
	 *    	(同時:JedisConnectionFactory.java和RedisStandaloneConfiguration.java都載入不了到MyEclipse6.5的空間中
	 *    	切換空間後,Ctrl+左鍵都能顯示"下劃線連線")
	 *    解決方案:換個能夠使用jdk1.8的IDE空間或環境,換成Eclipse4.5成功解決。(筆者機器上多種IDE切換方便)
	 *  
	 *  10.序列化異常:快取的物件被要求序列化,那麼User物件就需要實現Serializable介面。
	 *  
	 *  11.異常時:Caused by: java.net.SocketTimeoutException: connect timed out
	 *  	解決方案:這個原因有多種,要根據自己的環境來定。
	 *  筆者這裡就遇到二種:一是密碼未設定,二是主機地址填寫錯了。但是不會報密碼為空或地址有誤,這個要你自己來除錯了。
	 *  
	 *  
	 *  
	 *  這麼多異常能夠快速解決也需要一定的功力的,你說是不是?哈哈!
	 */
	@Test
	public void testFindUserById(){
		//第二次再執行此方法testFindUserById(),
		//通過 debug跟蹤一下看看是不是從快取中取的資料或者看看有沒有再呼叫dao層訪問資料庫的日誌資訊
		//此演示設定的日誌資訊為"===>>getUserById查詢資料庫...", 如果輸出這條日誌,說明是查詢資料庫而不是查詢Redis快取
		//通過日誌檢視發現,是從Redis快取中獲取資料的
		User user = springDataRedis.findUserById(6);
		System.out.println("user:" + user);
	}
}

===>> PassUtil

 package com.it.util;

import org.apache.commons.codec.digest.DigestUtils;


public class PassUtil {

	public static String md5Hex(String msg){
		return DigestUtils.md5Hex(msg);
	}
	
	public static void main(String[] args) {
		System.out.println(md5Hex("123"));
	}
	
}

=配置檔案============== ===>>redis.properties配置檔案

 #最大分配的物件數
redis.maxActive=600
#最大能夠保持idel狀態的物件數
redis.maxIdle=300
redis.minIdle=10
#當池內沒有返回物件時,最大等待時間
redis.maxWait=1000
redis.testOnBorrow=true

redis.host=127.0.0.1
redis.port=6379
redis.pass=foo666k
#連線並使用Redis的資料庫0,Redis資料庫名稱用數字表示0,1,2...
redis.dbIndex=0
#redis 快取資料過期時間單位秒
redis.expiration=3000
 

===>>applicationContext-redisCache.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      	xmlns:c="http://www.springframework.org/schema/c"
      	xmlns:p="http://www.springframework.org/schema/p"
      	xmlns:context="http://www.springframework.org/schema/context"
       	xmlns:cache="http://www.springframework.org/schema/cache"
       	xsi:schemaLocation=
       	"http://www.springframework.org/schema/beans
    	http://www.springframework.org/schema/beans/spring-beans.xsd
    	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context.xsd
    	http://www.springframework.org/schema/cache
    	http://www.springframework.org/schema/cache/spring-cache.xsd">
 
 	<!-- 引入redis.properties檔案 -->
 	<context:property-placeholder location="classpath:properties/redis.properties"/>
 	
    <!-- 開啟快取註解功能 -->
    <cache:annotation-driven cache-manager="redisCacheManager"/>
 
    <!-- spring-data-redis相關配置如下 ,這裡使用的Spring配置是不是與大家平常的玩法有點不同,熟悉下這些方式。
    	下面配置的bean(比如:RedisPassword)同時還涉及到了jdk1.8的新特性"lambda表示式"。
     -->
    
    <!--1.redis快取管理器 -->
     <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
          factory-method="create" c:connection-factory-ref="jedisConnectionFactory"/>
        
    <!--2.Redis伺服器配置的密碼  -->
     <bean id="redisPassword" class="org.springframework.data.redis.connection.RedisPassword"
     	c:thePassword="${redis.pass}" />
          
    <!--3.jedis連線工廠 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <constructor-arg>
            <bean class="org.springframework.data.redis.connection.RedisStandaloneConfiguration"
                  c:host-name="${redis.host}" c:port="${redis.port}" p:password-ref="redisPassword" />
        </constructor-arg>
    </bean>
 
    
 	<!-- 4.序列化 -->
    <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    
    <!-- 5.模板,類比JdbcTemplate或HibernateTemplate,可以得出這個模板相當於"操作Redis的dao層模板" -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
        <property name="keySerializer" ref="stringRedisSerializer"/>
        <property name="hashKeySerializer" ref="stringRedisSerializer"/>
    </bean>
	 
    <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"
          p:connection-factory-ref="jedisConnectionFactory"/>
 
 	<!-- service層bean -->
 	<bean id="springDataRedis" class="com.it.service.SpringDataRedis" />
 
</beans>

@拈花為何不一笑, 謝謝大家閱讀。還有一年就要畢業了,工作好找嘛?嘟嘟……