通过注解方式实现Spring AOP整合Redis缓存

 

1、实现自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TsingRedisCache {

}

2、创建切面配置类

/**
 * @ClassName : AopConfig
 * @Description: 横切面配置
 * 		@Configuration 声明配置类
 * 		@ComponentScan("com.tsingsoft.common.*") 扫描包
 * 		@EnableAspectJAutoProxy //开启 Spring 对 AspectJ 的支持
 * @author : 
 * @Company :
 * @date 2019年7月11日 下午1:41:22
 */
@Configuration
@ComponentScan("com.tsingsoft.common.*")
@EnableAspectJAutoProxy
public class AspectConfig {

}

3、创建Redis缓存配置及执行器类文件

  • 创建spring-data-redis 和 jedis整合xml的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context" 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:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
	http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
	
    <!-- 配置JedisPoolConfig实例 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大连接数 -->
		<property name="maxTotal" value="30" />
		<!-- 最大空闲连接数 -->
		<property name="maxIdle" value="10" />
		<!-- 每次释放连接的最大数目 -->
		<property name="numTestsPerEvictionRun" value="1024" />
		<!-- 释放连接的扫描间隔(毫秒) -->
		<property name="timeBetweenEvictionRunsMillis" value="30000" />
		<!-- 连接最小空闲时间 -->
		<property name="minEvictableIdleTimeMillis" value="1800000" />
		<!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
		<property name="softMinEvictableIdleTimeMillis" value="10000" />
		<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
		<property name="maxWaitMillis" value="1500" />
		<!-- 在获取连接的时候检查有效性, 默认false -->
		<property name="testOnBorrow" value="true" />
		<!-- 在空闲时检查有效性, 默认false -->
		<property name="testWhileIdle" value="true" />
		<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
		<property name="blockWhenExhausted" value="false" />
    </bean>
    
    <!-- 配置JedisConnectionFactory -->
    <bean id="jedisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" 
          p:usePool="true" 
          p:poolConfig-ref="poolConfig" 
          p:hostName="192.168.0.46" 
          p:port="6379" 
          p:timeout="200" 
          p:password="123456" />
	
    <!-- 配置RedisTemplate -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
          p:connection-factory-ref="jedisConnectionFactory" >
        <!--以下针对各种数据进行序列化方式的选择-->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
    </bean>
    <!-- cache配置 -->	
    <bean id="redisCacheExecutor" class="com.tsingsoft.common.redis.RedisCacheExecutor">
        <property name="redisTemplate" ref="redisTemplate"/>
    </bean>
    
</beans>
  • 说明一下,这个基于spring-data-redis中RedisTemplate实现的常用方法。
@Component
public class RedisCacheExecutor {
    private Logger logger = LoggerFactory.getLogger(RedisCacheExecutor.class);
    
    /**
     * sping-data-redis提供的Redis模板
     */
    @Resource
    private RedisTemplate<Serializable, Object> redisTemplate;
    
    /**
     * @Description: set方式注入
     * @author 李继伟
     * @param redisTemplate
     * @date 2019年7月11日 上午11:12:29
     */
    public void setRedisTemplate(RedisTemplate<Serializable, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

	/**
	 * @Description: 批量删除对应的value
	 * @author 李继伟
	 * @param keys
	 * @date 2019年7月11日 下午3:17:27
	 */
    public void del(final String... keys) {
        for (String key : keys) {
        	del(key);
        }
    }
    /**
     * @Description: 批量删除对应的value
     * @author 李继伟
     * @param keys
     * @date 2019年7月11日 下午3:17:27
     */
    public void del(final List<String> keys) {
    	for (String key : keys) {
    		del(key);
    	}
    }

	/**
	 * @Description: 通过模糊查询的方式查找所有key,然后批量删除key。
	 *               Redis模糊查询的通配符是"*"
	 *               可以是:"*","key*","*key","*key*"
	 *               192.168.0.46:6379> keys *
	 *               1) "aba"
	 *               2) "abc"
	 *               192.168.0.46:6379> keys a*
	 *               1) "aba"
	 *               2) "abc"
	 *               192.168.0.46:6379> keys *c
	 *               1) "abc"
	 *               192.168.0.46:6379> keys *b*
	 *               1) "aba"
	 *               2) "abc"
	 * @author 李继伟
	 * @param pattern
	 * @date 2019年7月11日 下午3:15:43
	 */
    public void delByPattern(final String pattern) {
        Set<Serializable> keys = redisTemplate.keys(pattern);
        if (keys.size() > 0) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * 删除对应的value
     *
     * @param key
     */
    public void del(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 判断缓存中是否有对应的value
     *
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 读取缓存
     *
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * @Description: 写入缓存
     * @author 李继伟
     * @param key
     * @param value
     * @return
     * @date 2019年7月11日 下午3:11:52
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            logger.error("Redis缓存异常,set方法出错:", e);
        }
        return result;
    }

    /**
     * @Description: 写入缓存,带失效时间
     * @author 李继伟
     * @param key 
     * @param value
     * @param expireTime 失效时间
     * @return
     * @date 2019年7月11日 下午3:11:25
     */
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            logger.error("Redis缓存异常,set方法出错:", e);
        }
        return result;
    }

}

4、创建Redis缓存横切面

@Aspect
@Component
public class RedisCacheAspect {

	private Logger log = Logger.getLogger(RedisCacheAspect.class);
	
	@Autowired
	private RedisCacheExecutor redisCacheExecutor;
	
	@Autowired
	private ConfigProperties configProperties;
	
	private Long defaultCacheExpireTime; // 缓存默认的过期时间
	
	/**
	 * @Description: 创建《查询操作》切点。
	 *               对所有以select/find/get/query/search/init/append/load开头的方法,
	 *               	并且同时使用@TsingRedisCache注解的方法进行横切
	 * @author 
	 * @date 2019年7月11日 下午1:18:39
	 */
	@Pointcut(value="((execution(* com.tsingsoft.**.service.*.select*(..))) "
			+ "|| (execution(* com.tsingsoft.**.service.*.find*(..))) "
			+ "|| (execution(* com.tsingsoft.**.service.*.query*(..))) "
			+ "|| (execution(* com.tsingsoft.**.service.*.search*(..))) "
			+ "|| (execution(* com.tsingsoft.**.service.*.init*(..))) "
			+ "|| (execution(* com.tsingsoft.**.service.*.append*(..))) "
			+ "|| (execution(* com.tsingsoft.**.service.*.load*(..))) "
			+ "|| (execution(* com.tsingsoft.**.service.*.get*(..)))) "
			+ "&& @annotation(com.tsingsoft.common.annotation.TsingRedisCache)")
    public void selectPointCut(){};
    
    
    /**
     * @Description: 创建《修改操作》切点。
     *               对所有以insert/update/delete/modify/save/change/edit/remove/repair/del开头的方法,
     *               	并且同时使用@TsingRedisCache注解的方法进行横切
     * @author 
     * @date 2019年7月11日 下午1:18:39
     */
    @Pointcut(value="((execution(* com.tsingsoft.**.service.*.insert*(..))) "
    		+ "|| (execution(* com.tsingsoft.**.service.*.update*(..))) "
    		+ "|| (execution(* com.tsingsoft.**.service.*.delete*(..))) "
    		+ "|| (execution(* com.tsingsoft.**.service.*.modify*(..))) "
    		+ "|| (execution(* com.tsingsoft.**.service.*.save*(..))) "
    		+ "|| (execution(* com.tsingsoft.**.service.*.edit*(..))) "
    		+ "|| (execution(* com.tsingsoft.**.service.*.remove*(..))) "
    		+ "|| (execution(* com.tsingsoft.**.service.*.repair*(..))) "
    		+ "|| (execution(* com.tsingsoft.**.service.*.del*(..)))) "
    		+ "&& @annotation(com.tsingsoft.common.annotation.TsingRedisCache)")
    public void modifyPointCut(){};
	
	/**
	 * @Description: 《查询操作》切点操作。
	 *               存在缓存,则返回缓存值。
	 *               不存在,则从DB中获取,并放入缓存,然后返回
	 *               必须采用环绕通知方式,注意必须要有ProceedingJoinPoint参数传入。
	 * @author 
	 * @param joinPoint
	 * @date 2019年7月11日 下午2:27:31
	 */
    @Around(value="selectPointCut()")
    public Object aroundSelectPointCut(ProceedingJoinPoint joinPoint){
    	if(!configProperties.isOpenRedisCache()){
    		try {
				return joinPoint.proceed();
			}
			catch (Throwable e) {
				log.debug(e);
			}
    	}
    	//全限类名(指向父类,如果使用自动生成包的mapper方法,会指向tk包下的Mapper类)
		String clazzName = joinPoint.getSignature().getDeclaringTypeName();
		clazzName = clazzName.substring(clazzName.lastIndexOf(".")+1);
		//方法名
		String methodName = joinPoint.getSignature().getName();
		//参数
		Object[] args = joinPoint.getArgs();
		//生成KEY
		String key = generateCacheKey(clazzName, methodName, args);
		//检查是否有缓存,存在,则取出返回
        if (redisCacheExecutor.exists(key)) {
            return redisCacheExecutor.get(key);
        }
        //不存在,则正常执行相关方法,从数据库中查询数据
        Object obj = null;
        try {
			obj = joinPoint.proceed();
		}
		catch (Throwable e) {
			log.debug(e);
		}
        defaultCacheExpireTime = configProperties.defaultCacheExpireTime();
        if (obj != null) {
            final String tempKey = key;
            final Object tempValue = obj;
            new Thread(new Runnable() {
                @Override
                public void run() {
                	redisCacheExecutor.set(tempKey, tempValue, defaultCacheExpireTime);
                }
            }).start();
        }
        return obj;
    }
    
    /**
     * @Description: 在《修改操作》切点方法执行结束后,清空所有缓存
     * @author 
     * @date 2019年7月11日 下午2:58:16
     */
    @After(value="modifyPointCut()")
    public void afterModifyPointCut() {
    	if(configProperties.isOpenRedisCache()){
    		try {
        		redisCacheExecutor.delByPattern("*");
    		}
    		catch (Throwable e) {
    			log.debug(e);
    		}
    	}
	}
    
    /**
     * @Description: 创建缓存key,通过传入参数转大写,用"_"分隔
     * @author 
     * @param targetName
	 *            service实现类名称
	 * @param methodName
	 *            service实现类中方法名称
	 * @param arguments
	 *            service实现类中方法参数集合
     * @return
     * @date 2019年7月11日 下午2:33:10
     */
    private String generateCacheKey(String targetName, String methodName, Object[] arguments) {
        StringBuffer keySb = new StringBuffer();
        keySb.append(targetName.toUpperCase());
        keySb.append("_");
        keySb.append(methodName.toUpperCase());
        if ((arguments != null) && (arguments.length != 0)) {
            for (int i = 0; i < arguments.length; i++) {
            	Object arg = arguments[i];
            	if(arg instanceof String){
            		keySb.append("_").append(arg.toString().toUpperCase());
            	}else if(arg instanceof Integer){
            		keySb.append("_").append(arg.toString().toUpperCase());
            	}else if(arg instanceof Double){
            		keySb.append("_").append(arg.toString().toUpperCase());
            	}else if(arg instanceof Float){
            		keySb.append("_").append(arg.toString().toUpperCase());
            	}else if(arg instanceof Boolean){
            		keySb.append("_").append(arg.toString().toUpperCase());
            	}else if(arg instanceof Long){
            		keySb.append("_").append(arg.toString().toUpperCase());
            	}else if(arg instanceof Byte){
            		keySb.append("_").append(arg.toString().toUpperCase());
            	}else if(arg instanceof Short){
            		keySb.append("_").append(arg.toString().toUpperCase());
            	}
            }
        }
        return keySb.toString();
    }
}

ConfigProperties 是我对properties中,常量配置进行同一管理的类。

到此,整个配置完成。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值