Redis 第四集Spring宣告式快取(Redis)
阿新 • • 發佈:2018-12-20
說明: 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>
@拈花為何不一笑, 謝謝大家閱讀。還有一年就要畢業了,工作好找嘛?嘟嘟……