說明: 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>
@拈花爲何不一笑, 謝謝大家閱讀。還有一年就要畢業了,工作好找嘛?嘟嘟……