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

来源:互联网 发布:大淘客cms有什么用 编辑:程序博客网 时间:2024/06/05 11:00

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

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

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

配置了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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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;//是否加锁,缓解缓存雪崩}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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

@Servicepublic 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;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

5.编写缓存服务类

/** * redis缓存服务类 * @author yuli * */@Servicepublic 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);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

6.编写一个aop缓存切面

@Aspect@Componentpublic 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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

因为使用了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);    }}
阅读全文
0 0
原创粉丝点击