spring-redis缓存方案学习三:基于aop的自定义注解开发

springde@cache注解简单易用,但是应对复杂的业务场景仍然力有不逮。无法应对高并发下的缓存击穿,缓存雪崩,缓存备份等问题。所以自定义注解就是一个相对复杂,但是更好的解决方案。

1.在pom.xml中引入相关依赖

        <!-- aop依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.10</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.10</version>
        </dependency>
        <!-- redis客户端 -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <!-- spring对redis的支持 -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.8.4.RELEASE</version>
        </dependency>
        <!-- google开源的guava工具类 -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>22.0</version>
        </dependency>

2.编写spring的配置文件applicationContext.xml

在文件头部定义aop和cache的schema

xmlns:aop=”http://www.springframework.org/schema/aop”
xmlns:cache=”http://www.springframework.org/schema/cache”
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.3.xsd

    <!-- 使用cglib进行代理 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
    <!-- 配置reids连接池配置 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.maxIdle}" />
        <property name="maxTotal" value="${redis.maxTotal}" />
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
        <property name="testOnBorrow" value="${redis.testOnBorrow}" />
    </bean>
    <!-- 配置redis连接池工厂 -->
    <bean id="jedisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
        destroy-method="destroy">
        <property name="hostName" value="${redis.host}" />
        <property name="port" value="${redis.port}" />
        <property name="database" value="${redis.default.db}" />
        <property name="timeout" value="${redis.timeout}" />
        <property name="usePool" value="true" />
        <property name="poolConfig" ref="poolConfig" />
    </bean>
    <!-- key序列化策略 -->
        <bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    <!-- value序列化策略 -->
        <bean id="valueSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
    <!-- spring-redis模板 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
        <property name="keySerializer" ref="keySerializer" />
        <property name="valueSerializer" ref="valueSerializer" />
        <!-- 配置redis是否支持事务 -->
        <property name="enableTransactionSupport" value="false" />
    </bean>
    <!-- 开启缓存注解驱动 -->
    <cache:annotation-driven/>
    <!-- 声明redis缓存的管理器 -->
    <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg index="0" ref="redisTemplate"/>
        <!-- 配置数据默认过期时间 ,秒-->
        <property name="defaultExpiration" value="1000"/>
        <!-- 配置缓存域数据的存在时间,秒 -->
        <property name="expires">
            <map>
                <entry key="userCache" value="10"/>
                <entry key="user_backup" value="10"/>
            </map>
        </property>
    </bean>

RedisCacheManager类有两个关于过期时间的属性,默认过期时间是0,也就是永久有效

// 0 - never expire
    private long defaultExpiration = 0;
    private Map<String, Long> expires = null;

配置了expires之后,在创建相关缓存域的缓存时,过期时间就会覆盖默认过期时间。

    @SuppressWarnings("unchecked")
    protected RedisCache createCache(String cacheName) {
        //调用计算过期时间方法
        long expiration = computeExpiration(cacheName);
        return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
                cacheNullValues);
    }

    protected long computeExpiration(String name) {
        Long expiration = null;
        if (expires != null) {
            expiration = expires.get(name);
        }
        //覆盖原来的过期时间
        return (expiration != null ? expiration.longValue() : defaultExpiration);
    }

3.编写一个自定义缓存注解

/**
 * 自定义缓存注解
 * @author yuli
 *
 */
@Retention(RetentionPolicy.RUNTIME)//设置生命周期为运行时有效
@Target({ElementType.METHOD,ElementType.TYPE})//设置为可以是用在方法上和类上
public @interface CacheResult {
    String key();//键名
    String cacheName();//关联到哪个缓存域
    String backupKey() default "";//是否备份
    boolean needBloomFilter() default false;//布隆过滤器,防止缓存击穿
    boolean needLock() default false;//是否加锁,缓解缓存雪崩
}

4.在业务类上使用自定义缓存注解

@Service
public class UserServiceImpl3 implements UserService {
    public static Logger logger = Logger.getLogger(UserServiceImpl3.class);
    @Autowired
    private UserMapper userMpper;//usermapper接口

    private static final String CACHE_NAME = "userCache";
    private static final String CACHE_BACKUP_NAME ="user_backup";
    @Transactional
    //自定义缓存注解
    @CacheResult(key="#user.id",cacheName=CACHE_NAME,backupKey=CACHE_BACKUP_NAME)
    public User addUser(User user) {
        userMpper.insert(user);
        logger.info("向数据库添加用户");
        return user;
    }
    @Transactional
    public void deleteUser(String userId) {
        userMpper.deleteByPrimaryKey(userId);

    }
    @Transactional
    public User updateUser(User user) {
        userMpper.updateByPrimaryKeySelective(user);
        return user;
    }
    @Transactional(readOnly=true)
    //自定义缓存注解
    @CacheResult(key="#userId",cacheName=CACHE_NAME,backupKey=CACHE_BACKUP_NAME,needBloomFilter=true,needLock=true)
    public User queryUser(String userId) {
        logger.info("从数据库里取得用户");
        User user = userMpper.selectByPrimaryKey(userId);
        return user;
    }

}

5.编写缓存服务类

/**
 * redis缓存服务类
 * @author yuli
 *
 */
@Service
public class RedisCacheServiceImpl implements CacheService2 {
    @Autowired
    private CacheManager cm;//导入缓存管理器
    /**
     * 获取缓存结果
     */
    @SuppressWarnings("unchecked")
    public <T> T cacheResult(String key, String cacheName) {
        ValueWrapper valueWrapper = cm.getCache(cacheName).get(key);
        return (T) (valueWrapper == null ? null:valueWrapper.get());
    }
    /**
     * 移除缓存
     */
    public void cacheRemove(String key, String cacheName) {
        cm.getCache(cacheName).evict(key);
    }
    /**
     * 添加缓存
     */
    public <T> void cachePut(String key, T value, String cacheName) {
        cm.getCache(cacheName).put(key, value);
    }

}

6.编写一个aop缓存切面

@Aspect
@Component
public class CacheAspect {
    public static Logger logger = Logger.getLogger(CacheAspect.class);
    //互斥锁,用来缓解缓存雪崩
    private Lock lock = new ReentrantLock();
    //缓存服务类
    @Autowired
    private CacheService2 cs;
    //布隆过滤器,用来防止缓存击穿
    private BloomFilter<String> bf;
    @Autowired
    private UserMapper mapper;
    @PostConstruct
    public void init(){
        //初始化布隆过滤器,使用id来作为过滤名单
        List<String> userIds = mapper.selectAllUserId();
        bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),userIds.size());
        for (String userId : userIds) {
            bf.put(userId);
        }
    }

    @Around("@annotation(cr)")
    public Object doAround(ProceedingJoinPoint point,CacheResult cr) throws Throwable{
        String key = getKey(cr.key(), point);//键名
        String buckupkey = cr.backupKey();//备份的键名
        String cacheName = cr.cacheName();//缓存域的名称
        boolean needBloomFilter = cr.needBloomFilter();//是否使用布隆过滤器
        boolean needLock = cr.needLock();//是否加锁
        //使用布隆过滤器来校验数据
        if(needBloomFilter && !bf.mightContain(key)){
            logger.info("id名不存在于名单中");
            return null;
        }
        User rUser = cs.cacheResult(key, cacheName);
        if(rUser != null){
            logger.info("从缓存中取得数据");
            return rUser;
        }
        //缓存中没有数据,仅允许一个任务去数据库中加载数据,并写入缓存
        if(needLock){
            //尝试获取锁,没获取到就从备份缓存中中获取数据
            if(lock.tryLock()){
                Object object = point.proceed();
                cs.cachePut(key, object, cacheName);
                //放一份到备份缓存中
                cs.cachePut(buckupkey+key, object, buckupkey);
                lock.unlock();
                return object;
            }else{
                logger.info("从备份中获取数据");
                return cs.cacheResult(buckupkey+key, buckupkey);
            }

        }else{
            Object object = point.proceed();
            cs.cachePut(key, object, cacheName);
            //放一份到备份缓存中
            cs.cachePut(buckupkey+key, object, buckupkey);
            return object;
        }

    }
    /**
     * 解析spring的el表达式方法
     * @param key
     * @param point
     * @return
     */
    private String getKey(String key,ProceedingJoinPoint point){
        Object[] args = point.getArgs();//获取参数的值
        Signature signature = point.getSignature();//从连接点获取数字签名
        MethodSignature methodSignature = (MethodSignature)signature;
        Method method = methodSignature.getMethod();//获取形参的名字
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(method);
        //调用el表达式解析工具类
        return SpelParser.getKey(key, parameterNames, args);
    }

因为使用了spring的el表达式,所以要编写一个springEl表达式的解析工具类

7.编写spring的El表达式解析工具类

/**
 * spring表达式解析器
 * @author yuli
 *
 */
public class SpelParser {
    private static ExpressionParser parser = new SpelExpressionParser();

    public static String getKey(String key,String[] paramName,Object[] args){
        //将字符串转换为spring的el表达式
        Expression exp = parser.parseExpression(key);
        //定义赋值上下文
        EvaluationContext context = new StandardEvaluationContext();
        if(args.length < 1){
            return null;
        }
        for(int i = 0;i<args.length;i++){
            //向赋值上下文传入参数名相对应的值
            context.setVariable(paramName[i], args[i]);
        }
        //表达式取值,通过赋值上下文
        return exp.getValue(context,String.class);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值