请先阅读上一篇!!!
aop的知识请参考我的博文【http://blog.csdn.net/wx5040257/article/details/78878645】
上一篇提到,spring cache要精确控制缓存,比方说删除一个用户数据不至于清空所有的用户缓存,可以办到,但是代码不优雅,需要项目组程序员要有较高的素养,
那么切面编程(aop)就可以解决这一困境!!!
废话不多少了,上代码!
实体类
====================UserEntity===========================
package com.wx.entitys;
import java.io.Serializable;
public class UserEntity implements Serializable {
private static final long serialVersionUID = 1034088741557780953L;
private Integer userId;
private String userName;
private String passWord;
private String email;
public UserEntity() {
}
public UserEntity(String userName, String passWord) {
super();
this.userName = userName;
this.passWord = passWord;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
//给对象打个标志,方便比对删除等操作
public String toString() {
return this.userId+"@"+this.userName;
}
}
工具类
package com.wx.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
//存取对象工具类
public class CacheSeriUtil {
public static byte[] toByteArray(Object obj) {
byte[] bytes = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray();
oos.close();
bos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return bytes;
}
public static Object toObject(byte[] bytes) {
Object obj = null;
try {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
obj = ois.readObject();
ois.close();
bis.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return obj;
}
}
spring cache实现类
========================SpringRedisCache=========================
package com.wx.utils;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
public class SpringRedisCache implements Cache {
private JedisConnectionFactory jedisConnectionFactory;
private String name;
public void setJedisConnectionFactory(
JedisConnectionFactory jedisConnectionFactory) {
this.jedisConnectionFactory = jedisConnectionFactory;
}
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return null;
}
@Override
public ValueWrapper get(Object key) {
System.out.println("get key from cache************:"+key.toString());
final String keyf = key.toString();
RedisConnection conn=null;
Object object = null;
try {
conn=jedisConnectionFactory.getConnection();
byte[] bkey = keyf.getBytes();
byte[] value = conn.get(bkey);
if (value == null) {
System.out.println("缓存中木有,要查数据库了");
object= null;
}else{
object=CacheSeriUtil.toObject(value);
System.out.println("恭喜,从缓存中找到了:"+object.toString());
}
} catch (Exception e) {
object=null;
e.printStackTrace();
}finally{
if(conn!=null){
conn.close();
}
}
return (object!=null) ? new SimpleValueWrapper(object) : null;
}
/**
* 添加缓存的操作
*/
public void put(Object key, Object value) {
String srcKey = key.toString();
final long liveTime = 86400; //默认缓存一天
RedisConnection conn=null;
try {
conn=jedisConnectionFactory.getConnection();
byte[] keyb = srcKey.getBytes();
byte[] valueb = CacheSeriUtil.toByteArray(value);
conn.set(keyb, valueb);
if (liveTime > 0) {
conn.expire(keyb, liveTime);
}
System.out.println("***键:"+srcKey+"*********添加进了缓存");
} catch (Exception e1) {
e1.printStackTrace();
}finally{
if(conn!=null){
conn.close();
}
}
}
/**
*由切面编程完成
*/
public void evict(Object key) {
}
@Override
public void clear() {
System.out.println("clear key");
RedisConnection conn=null;
try {
conn=jedisConnectionFactory.getConnection();
conn.flushDb();
} catch (Exception e) {
e.printStackTrace();
}finally{
if(conn!=null){
conn.close();
}
}
}
}
redis配置文件
===================redis.properties==============================#*****************jedis连接参数设置*********************
#redis服务器ip
redis.ip=169.254.130.122
#redis服务器端口号
redis.port=6379
#redis访问密码
redis.passWord=test123
#与服务器建立连接的超时时间
redis.timeout=3000
#************************jedis池参数设置*******************
#jedis的最大活跃连接数
jedis.pool.maxActive=50
#jedis最大空闲连接数
jedis.pool.maxIdle=10
#jedis最小空闲连接数
jedis.pool.minIdle=5
#jedis池没有连接对象返回时,等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。
#如果超过等待时间,则直接抛出JedisConnectionException
jedis.pool.maxWait=1500
#从池中获取连接的时候,是否进行有效检查
jedis.pool.testOnBorrow=true
#归还连接的时候,是否进行有效检查
jedis.pool.testOnReturn=false
spring核心配置文件
=================================applicationContext.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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- 扫描包含注解类所在的包 -->
<context:component-scan base-package="com.wx.dao.*"></context:component-scan>
<context:component-scan base-package="com.wx.biz"></context:component-scan>
<bean id="config" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:configs/druidConfig.properties</value>
<value>classpath:configs/redis.properties</value>
</list>
</property>
</bean>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="filters" value="${filters}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
<property name="minIdle" value="${minIdle}" />
<property name="maxWait" value="${maxWait}" />
<property name="validationQuery" value="${validationQuery}" />
<property name="testWhileIdle" value="${testWhileIdle}" />
<property name="testOnBorrow" value="${testOnBorrow}" />
<property name="testOnReturn" value="${testOnReturn}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${maxPoolPreparedStatementPerConnectionSize}" />
<property name="removeAbandoned" value="${removeAbandoned}" />
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
</bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="druidDataSource"></property>
<property name="configLocation">
<value>classpath:configs/mybatis-config.xml</value>
</property>
<property name="mapperLocations">
<list>
<value>classpath:configs/mappers/*.xml</value>
</list>
</property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sessionFactory"></constructor-arg>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<tx:advice id="txAdvise" transaction-manager="txManager">
<tx:attributes>
<tx:method name="find*" read-only="true"/>
<tx:method name="search*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="do*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.wx.biz.*.*(..))" id="myCut"/>
<aop:advisor advice-ref="txAdvise" pointcut-ref="myCut"/>
</aop:config>
<!-- 配置redis 单机版 -->
<!-- redis数据源 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最小空闲数 -->
<property name="minIdle" value="${jedis.pool.minIdle}"></property>
<!-- 最大空闲数 -->
<property name="maxIdle" value="${jedis.pool.maxIdle}" />
<!-- 最大活跃数 -->
<property name="maxTotal" value="${jedis.pool.maxActive}" />
<!-- 最大等待时间 -->
<property name="maxWaitMillis" value="${jedis.pool.maxWait}" />
<!-- 返回连接时,检测连接是否成功 -->
<property name="testOnBorrow" value="${jedis.pool.testOnBorrow}" />
</bean>
<!-- Spring-redis连接池管理工厂 -->
<bean id="jedisConnectionFactory" scope="singleton" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<!-- IP地址 -->
<property name="hostName" value="${redis.ip}" />
<!-- 端口号 -->
<property name="port" value="${redis.port}" />
<!-- 密码 -->
<property name="password" value="${redis.passWord}"/>
<!-- 超时时间 -->
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="poolConfig" />
</bean>
<!-- redis缓存配置结束 -->
<!-- spring自己的缓存管理器,这里定义了一个缓存位置名称 ,既注解中的value -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="com.wx.utils.SpringRedisCache">
<property name="jedisConnectionFactory" ref="jedisConnectionFactory" />
<property name="name" value="redis_cache"/>
</bean>
</set>
</property>
</bean>
<!-- 开启Spring缓存 -->
<cache:annotation-driven cache-manager="cacheManager"/>
<!-- 采用切面编程的方式对缓存精确控制 -->
<!-- 启用对于@AspectJ注解的支持,表明是增强处理的代码 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="cacheAdvise" class="com.wx.cache.aops.FlushCacheAdvise">
<property name="jedisConnectionFactory" ref="jedisConnectionFactory"></property>
</bean>
</beans>
统一处理缓存的aop类
====================FlushCacheAdvise.java=================package com.wx.cache.aops;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import com.wx.utils.CacheSeriUtil;
@Aspect
public class FlushCacheAdvise {
private JedisConnectionFactory jedisConnectionFactory;
public void setJedisConnectionFactory(
JedisConnectionFactory jedisConnectionFactory) {
this.jedisConnectionFactory = jedisConnectionFactory;
}
/**
* 删除操作,如果只是根据id删除一个对象,则把其它缓存里面的该对象也删除
* 如果是删除很多对象(例如是根据性别删),则把该类下所有缓存全部清除
* @param key 形式"[className].[id]"
*/
@AfterReturning(pointcut = "execution(* com.wx.biz.*.del*(..))",
returning = "returnValue")
public void afterDelete(JoinPoint jp, Object returnValue) throws Throwable {
String keyPrefix = jp.getTarget().getClass().getName();
String methName = jp.getSignature().getName();
final long liveTime = 86400; // 默认缓存一天
RedisConnection conn = null;
String toDel = "";
try {
conn = jedisConnectionFactory.getConnection();
// 如果只是删除了一个对象或者更新了一个对象,则删除所有list缓存中的该对象
if (methName.equals("deleteById")) {
toDel = keyPrefix + "." + jp.getArgs()[0];
conn.del(toDel.getBytes());
System.out.println("*****aop****del_key:" + toDel+ "==============");
// 其它list缓存中删除该对象
toDel = keyPrefix + ".list.*";
Set<byte[]> keys = conn.keys(toDel.getBytes());
Iterator<byte[]> it = keys.iterator();
while (it.hasNext()) {
byte[] onelistKey = it.next();
byte[] oneListValue = conn.get(onelistKey);
List<Object> theList = (List<Object>) CacheSeriUtil.toObject(oneListValue);
for (Object theObj : theList) {
if (theObj.toString().startsWith(jp.getArgs()[0] + "@")) {
// 找到了,list里面的对象被去掉了
theList.remove(theObj);
break;
}
}
// 重新放回缓存中
oneListValue = CacheSeriUtil.toByteArray(theList);
conn.set(onelistKey, oneListValue);
if (liveTime > 0) {
conn.expire(onelistKey, liveTime);
}
System.out.println("**aop**del**" + new String(onelistKey)+ "****list缓存中的数据更新了*********");
}
} else { // 如果删除了很多对象,则目标类下所有的缓存全部删除
toDel = keyPrefix + "*";
Set<byte[]> keys = conn.keys(toDel.getBytes());
Iterator<byte[]> it = keys.iterator();
while (it.hasNext()) {
byte[] onekey = it.next();
conn.del(onekey);
System.out.println("**aop**del key:" + new String(onekey));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
}
/**
* 添加操作,如果是save方法,则添加该对象的缓存的同时,把它添加到其它list缓存中去
* @param key 形式"[className].[id]"
*/
@AfterReturning(pointcut = "execution(* com.wx.biz.*.save*(..))||execution(* com.wx.biz.*.add*(..))",
returning = "returnValue")
public void afterAdd(JoinPoint jp, Object returnValue) throws Throwable {
String targetName = jp.getTarget().getClass().getName();
final long liveTime = 86400; // 默认缓存一天
RedisConnection conn = null;
try {
conn = jedisConnectionFactory.getConnection();
String objStr = returnValue.toString();
String theId = objStr.substring(0, objStr.indexOf('@'));// table.primarykey的形式
String keyf = targetName + "." + theId;
System.out.println("****aop add key:" + keyf + "===value:" + objStr);
byte[] keyb = keyf.getBytes();
byte[] valueb = CacheSeriUtil.toByteArray(returnValue);
conn.set(keyb, valueb);
if (liveTime > 0) {
conn.expire(keyb, liveTime);
}
// 往其它list缓存里添加数据
String toAdd = targetName + ".list.*";
Set<byte[]> keys = conn.keys(toAdd.getBytes());
Iterator<byte[]> it = keys.iterator();
while (it.hasNext()) {
byte[] onelistKey = it.next();
byte[] oneListValue = conn.get(onelistKey);
List<Object> theList = (List<Object>)CacheSeriUtil.toObject(oneListValue);
theList.add(returnValue);
// 重新放回缓存中
oneListValue = CacheSeriUtil.toByteArray(theList);
conn.set(onelistKey, oneListValue);
if (liveTime > 0) {
conn.expire(onelistKey, liveTime);
}
System.out.println("***aop add后***" + new String(onelistKey)
+ "****list缓存中的数据更新了*********");
}
} catch (Exception e1) {
e1.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
}
/**
* 更新操作,先更新一个对象的缓存,再更新其它list中的缓存
* @param key 形式"[className].[id]"
*/
@AfterReturning(pointcut = "execution(* com.wx.biz.*.modify*(..))||execution(* com.wx.biz.*.update*(..))",
returning = "returnValue")
public void afterModify(JoinPoint jp, Object returnValue) throws Throwable {
String targetName = jp.getTarget().getClass().getName();
String paramStr=jp.getArgs()[0].toString();
final long liveTime = 86400; // 默认缓存一天
RedisConnection conn = null;
try {
conn = jedisConnectionFactory.getConnection();
//先更新缓存中的对应的key值
String optKey = targetName + "." + paramStr.substring(0, paramStr.indexOf('@'));
byte[] keyb = optKey.getBytes();
byte[] valueb = CacheSeriUtil.toByteArray(returnValue);
conn.set(keyb, valueb);
if (liveTime > 0) {
conn.expire(keyb, liveTime);
}
System.out.println("*****aop_update****update_key:" + optKey+ "==============");
// 再更新其它list缓存中的数据
String toUpdate = targetName + ".list.*";
Set<byte[]> keys = conn.keys(toUpdate.getBytes());
Iterator<byte[]> it = keys.iterator();
while (it.hasNext()) {
byte[] onelistKey = it.next();
byte[] oneListValue = conn.get(onelistKey);
List<Object> theList = (List<Object>) CacheSeriUtil.toObject(oneListValue);
for (Object theObj : theList) {
if (theObj.toString().startsWith(paramStr.substring(0, paramStr.indexOf('@')+1))) {
// 找到了,list里面先清除旧数据,再添加新的对象
theList.remove(theObj);
theList.add(returnValue);
break;
}
}
// 重新放回缓存中
oneListValue = CacheSeriUtil.toByteArray(theList);
conn.set(onelistKey, oneListValue);
if (liveTime > 0) {
conn.expire(onelistKey, liveTime);
}
System.out.println("**aop**update**" + new String(onelistKey)
+ "****list缓存中的数据更新了*********");
}
} catch (Exception e1) {
e1.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
}
}
应用缓存的类
===================UserBiz========================package com.wx.biz;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Service;
import com.wx.dao.user.IUserDao;
import com.wx.entitys.UserEntity;
@Service("userBiz")
@EnableCaching
public class UserBiz {
@Autowired
private IUserDao userDao;
public boolean isLogin(UserEntity user){
return userDao.isLogin(user);
}
public UserEntity save(UserEntity user){
return userDao.add(user);
}
@Cacheable(value="redis_cache",key="#root.targetClass.getName()+'.'+#userId")
public UserEntity getById(String userId){
return userDao.getById(userId);
}
@Cacheable(value="redis_cache",key="#root.targetClass.getName()+'.'+#userName")
public boolean isUserExist(String userName){
return userDao.isUserExist(userName);
}
@Cacheable(value="redis_cache", key="#root.targetClass.getName()+'.list.'+#root.methodName")
public List<UserEntity> queryMany(UserEntity user){
return userDao.queryMany(user);
}
public Integer deleteById(String id) {
return userDao.deleteById(id);
}
public UserEntity modify(UserEntity user) {
return userDao.modify(user);
}
}
现在来看运行效果
第一进管理界面
先从缓存里找,没找到,从数据库查,然后放入缓存
然后多次刷新当前页码,都是从缓存取数据
再来看添加
添加进数据库的同时更新键【com.wx.biz.UserBiz.list.queryMany】,而不是清空缓存
接下来看修改
修改数据库里面的数据的同时,删除键【com.wx.biz.UserBiz.list.queryMany】中对应的旧数据,再放入修改后的新数据
最后看删除
删除数据库数据的同时,删除键【com.wx.biz.UserBiz.list.queryMany】中的数据
读者们,aop方式是不是很强大,其实你也可以完全不用spring cache,全部改为aop方式!!!!