springde@cache注解简单易用,但是应对复杂的业务场景仍然力有不逮。无法应对高并发下的缓存击穿,缓存雪崩,缓存备份等问题。所以自定义注解就是一个相对复杂,但是更好的解决方案。
1.在pom.xml中引入相关依赖
<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> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.8.4.RELEASE</version> </dependency> <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
<aop:aspectj-autoproxy proxy-target-class="true" /> <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> <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> <bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" /> <bean id="valueSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> <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" /> <property name="enableTransactionSupport" value="false" /> </bean> <cache:annotation-driven/> <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,也就是永久有效
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.在业务类上使用自定义缓存注解
@Servicepublic class UserServiceImpl3 implements UserService { public static Logger logger = Logger.getLogger(UserServiceImpl3.class); @Autowired private UserMapper userMpper; 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(){ 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); 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){ 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); }}