这里写自定义目录标题
说明
最近在写一些可视化的数据接口,由于数据量过大导致请求数据异常慢,解决方案有很多种。由于数据是一些不变且量大的原因这里选择spring缓存注解来实现。这里来记一下日志。
Spring从3.1就已经引入了对Cache的支持,Spring Cache作用在方法上,在方法上添加了注解及代表此方法在使用的过程中返回的数据将存入缓存中。其思想是当我们调用带有缓存注解的方法时,会把他的参数和返回数据当成键值对来存入缓存中,等到下一次调用方法时不会请求接口会直接从缓存中拿到数据返回给前端(用于不经常改变的数据) 所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。
准备
使用缓存注解首先在springboot中使用redis来作为缓存
1.在pom.xml文件中引入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置文件中设置Redis的配置(这里设置一些基本设置)
spring.redis.host=192.xxx.xxx.xx
spring.redis.port=8080
spring.redis.password=123456
spring.redis.timeout=10s
spring.redis.database=1
配置好Redis后注解缓存默认缓存进Redis中,接下来就可以使用
使用
Spring Cache支持基于注解和基于XML配置的两张方式,这里只介绍基于注解的方式来实现缓存
对于缓存声明,spring的缓存提供了一组java注解:
@Cacheable:触发缓存写入。
@CacheEvict:触发缓存清除。
@CachePut:更新缓存(不会影响到方法的运行)。
@Caching:重新组合要应用于方法的多个缓存操作。
@CacheConfig:设置类级别上共享的一些常见缓存设置。
1.@Cacheable
@Cacheable可以标记在方法和类上面。当标记在方法上表示只对该方法是支持缓存的,当标记在类上面表示该类的所有方法都会支持缓存的,当调用支持注解的方法时会把该方法返回的数据缓存到Redis中,当下次以同样的参数来请求时可以直接从缓存中拿到结果,而不需要执行该方法来拿到数据。Spring Cache是已键值对进行缓存数据的,他的值就是方法的返回结果,他的键Spring又支持两种策略,默认策略和自定义策略,稍后会进行说明。需要注意的是当一个被缓存的方法在对象内部调用时是不会触发缓存的
@CacheConfig(cacheNames = "visualization:allWatch")
@Controller
@RequestMapping("allWatch")
public class AllWatchController {
@Autowired
private AllWatchService allWatchService;
@Cacheable(key = "'queryOrderCount_'+ #time+'_'+#dept")
@ResponseBody
@RequestMapping("queryOrderCount")
public Result queryOrderCount(String time, String dept) {
return MessageUtil.success(allWatchService.queryOrderCount(time, dept));
}
}
CacheConfig注解主要用于配置该类中会用到的一些共用的缓存配置。在这里@CacheConfig(cacheNames = “visualization:allWatch”):配置了该数据访问对象中返回的内容将存储于名为visualization:allWatch的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable自己配置缓存集的名字来定义。
@Cacheable可以指定三个属性,value、key和condition。
(1) value属性
value属性是必须指定的,如果有CacheConfig可以不用谢。其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
@Cacheable({"cache1", "cache2"})//Cache是发生在cache1和cache2上的
(2) key属性
key属性是用来指定Spring缓存方法的返回结果时对应的key的,该方法支持SpringEL表达式,当我们没有指定该属性时,Spring将使用默认策略生成key。我们这里先来看看自定义策略,至于默认策略会在后文单独介绍。
自定义策略可以使用它的参数和属性来通过SpringEL表达式来指定key,使用方法参数时我们可以直接使用“#参数名”或者“#p参数index"
@Cacheable(key = "'queryOrderCount_'+ #time+'_'+#dept")
@ResponseBody
@RequestMapping("queryOrderCount")
public Result queryOrderCount(String time, String dept)
@Cacheable(key = "'queryOrderCount_'+ #p0+'_'+#p1")
@ResponseBody
@RequestMapping("queryOrderCount")
public Result queryOrderCount(String time, String dept)
@Cacheable(key = "'queryOrderCount_'+ #order.time+'_'+#order.dept")
@ResponseBody
@RequestMapping("queryOrderCount")
public Result queryOrderCount(Order order)
@Cacheable(key = "'queryOrderCount_'+ #p0.time+'_'+#p1.dept")
@ResponseBody
@RequestMapping("queryOrderCount")
public Result queryOrderCount(Order order)
除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。
属性名称 | 描述 | 示例 |
---|---|---|
methodName | 当前方法名 | #root.methodName |
method | 当前方法 | #root.method.name |
target | 当前被调用的对象 | #root.target |
targetClass | 当前被调用的对象的class | #root.targetClass |
args | 当前方法参数组成的数组 | #root.args[0] |
caches | 当前被调用的方法使用的Cache | #root.caches[0].name |
当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。如:
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
return null;
}
(3) condition属性
有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存。
condition属性相当于sql中的where属性,当我们不希望缓存一个方法所有的返回结果。condition默认为空,表示将缓存所有的调用情形,也可以通过SpringEL表达式来指定,如下当dept等于管理员的时候才会进行缓存。
@Cacheable(key = "'queryOrderCount_'+ #time+'_'+#dept",condition='#dept==管理员')
@ResponseBody
@RequestMapping("queryOrderCount")
public Result queryOrderCount(String time, String dept)
2.@CachePut
@Cacheable是每次调用方法是都会去检查一下key是否存在,如果不存在则加入到缓存中,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回
@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。他的也可以写在类或者方法上,属性和@Cacheable是一样的
@CachePut("users")//每次都会执行方法,并将结果存入指定的缓存中
public User find(Integer id) {
return null;
}
3.@CacheEvict
有往缓存中存入数据当然也有清除缓存中的数据注解(有奥特曼一定有怪兽吗哈哈哈)@CacheEvict用于标志需要清除缓存的类或者方法上,当标志的类上标识所有的缓存方法都会执行清除操作,他有5个属性value,key,condition,allEntries和beforeInvocation其中前三个使用方法和上面一样这里讲一下另外两个属性的作用
(1) allEntries
allEntries是boolean类型,true表示清除所属value指定的缓存中所有的数据,false表示不需要。当我们有需求时可以不用一个一个来删除
(2) beforeInvocation
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
4.@Caching
@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),
@CacheEvict(value = "cache3", allEntries = true) })
public User find(Integer id) {
return null;
}
配置Spring对Cache的支持
基于注解
配置Spring对基于注解的Cache的支持,首先我们需要在Spring的配置文件中引入cache命名空间,其次通过<cache:annotation-driven />就可以启用Spring对基于注解的Cache的支持。
<?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: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/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven/>
</beans>
<cache:annotation-driven/>有一个cache-manager属性用来指定当前所使用的
CacheManager对应的bean的名称,默认是cacheManager,所以当我们的
CacheManager的id为cacheManager时我们可以不指定该参数,否则就需要我们指定了。
<cache:annotation-driven/>还可以指定一个mode属性,可选值有proxy和aspectj。
默认是使用proxy。当mode为proxy时,只有缓存方法在外部被调用的时候
Spring Cache才会发生作用,这也就意味着如果一个缓存方法在其声明对象
内部被调用时Spring Cache是不会发生作用的。而mode为aspectj时就不会有这种问题。
另外使用proxy时,只有public方法上的@Cacheable等标注才会起作用,如果需要非public
方法上的方法也可以使用Spring Cache时把mode设置为aspectj。
此外,<cache:annotation-driven/>还可以指定一个proxy-target-class属性,表示是否要代理class,
默认为false。我们前面提到的@Cacheable、@cacheEvict等也可以标注在接口上,
这对于基于接口的代理来说是没有什么问题的,但是需要注意的是当我们设置proxy-target-class为
true或者mode为aspectj时,是直接基于class进行操作的,定义在接口上的@Cacheable等
Cache注解不会被识别到,那对应的Spring Cache也不会起作用了。
需要注意的是<cache:annotation-driven/>只会去寻找定义在同一个ApplicationContext下的@Cacheable等缓存注解。
基于XML配置
除了使用注解来声明对Cache的支持外,Spring还支持使用XML来声明对Cache的支持。这主要是通过类似于aop:advice的cache:advice来进行的。在cache命名空间下定义了一个cache:advice元素用来定义一个对于Cache的advice。其需要指定一个cache-manager属性,默认为cacheManager。cache:advice下面可以指定多个cache:caching元素,其有点类似于使用注解时的@Caching注解。cache:caching元素下又可以指定cache:cacheable、cache:cache-put和cache:cache-evict元素,它们类似于使用注解时的@Cacheable、@CachePut和@CacheEvict。下面来看一个示例:
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="users">
<cache:cacheable method="findById" key="#p0"/>
<cache:cacheable method="find" key="#user.id"/>
<cache:cache-evict method="deleteAll" all-entries="true"/>
</cache:caching>
</cache:advice>
上面配置定义了一个名为cacheAdvice的cache:advice,其中指定了将缓存findById方法和find方法到名为users的缓存中。这里的方法还可以使用通配符“”,比如“find”表示任何以“find”开始的方法。
有了cache:advice之后,我们还需要引入aop命名空间,然后通过aop:config指定定义好的cacheAdvice要应用在哪些pointcut上。如:
<aop:config proxy-target-class="false">
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.xxx.UserService.*(..))"/>
</aop:config>
上面的配置表示在调用com.xxx.UserService中任意公共方法时将使用cacheAdvice对应的cache:advice来进行Spring Cache处理。更多关于Spring Aop的内容不在本文讨论范畴内。
配置CacheManager
CacheManager是Spring定义的一个用来管理Cache的接口。Spring自身已经为我们提供了两种CacheManager的实现,一种是基于Java API的ConcurrentMap,另一种是基于第三方Cache实现——Ehcache,如果我们需要使用其它类型的缓存时,我们可以自己来实现Spring的CacheManager接口或AbstractCacheManager抽象类。下面分别来看看Spring已经为我们实现好了的两种CacheManager的配置示例。
基于ConcurrentMap的配置
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="xxx"/>
</set>
</property>
</bean>
上面的配置使用的是一个SimpleCacheManager,其中包含一个名为“xxx”的ConcurrentMapCache。
基于Ehcache的配置
<!-- Ehcache实现 -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcacheManager"/>
<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache-spring.xml"/>
上面的配置使用了一个Spring提供的EhCacheCacheManager来生成一个Spring的CacheManager,其接收一个Ehcache的CacheManager,因为真正用来存入缓存数据的还是Ehcache。Ehcache的CacheManager是通过Spring提供的EhCacheManagerFactoryBean来生成的,其可以通过指定ehcache的配置文件位置来生成一个Ehcache的CacheManager。若未指定则将按照Ehcache的默认规则取classpath根路径下的ehcache.xml文件,若该文件也不存在,则获取Ehcache对应jar包中的ehcache-failsafe.xml文件作为配置文件。更多关于Ehcache的内容这里就不多说了,它不属于本文讨论的内容,欲了解更多关于Ehcache的内容可以参考我之前发布的Ehcache系列文章,也可以参考官方文档等。
键的生成策略
键的生成策略有两种,一种是默认策略,一种是自定义策略。
默认策略
默认的key生成策略是通过KeyGenerator生成的,其默认策略如下:
1, 如果方法没有参数,则使用0作为key。
2, 如果只有一个参数的话则使用该参数作为key。
3, 如果参数多余一个的话则使用所有参数的hashCode作为key。
如果我们需要指定自己的默认策略的话,那么我们可以实现自己的KeyGenerator,然后指定我们的Spring Cache使用的KeyGenerator为我们自己定义的KeyGenerator。
使用基于注解的配置时是通过cache:annotation-driven指定的.
<cache:annotation-driven key-generator="userKeyGenerator"/>
<bean id="userKeyGenerator" class="com.xxx.cache.UserKeyGenerator"/>
而使用基于XML配置时是通过cache:advice来指定的。
<cache:advice id="cacheAdvice" cache-manager="cacheManager" key-generator="userKeyGenerator">
</cache:advice>
需要注意的是此时我们所有的Cache使用的Key的默认生成策略都是同一个KeyGenerator。