基於AOP配置的Spring快取
等從3.1開始,Spring引入對Cache的支援,Spring Cache可以通過@Cacheable註解方式,也可以通過AOP配置方式實現,註解方式在之前的文章中已經介紹過,這裡介紹通過AOP配置方式實現
相比於註解方式,AOP配置優點在於對業務程式碼的零侵入性,不需要在業務程式碼中新增任何與快取有關的程式碼,對於需要快取的方法集中管理,方便維護;而對註解式的實現方式,至少需要在方法或類上增加如@CacheEable等關鍵字,隨著快取方法的不段增加,這種註解分散在專案中的各個檔案的各個方法上,維護起來很困難,因此相比於註解方式,個人更傾向於AOP配置
Redis的服務端配置前面已經介紹過這裡也不再介紹,下面看在Spring中,快取的配置
1、增加redis.properties的配置檔案
redis.sentinel.host1=127.0.0.1
redis.sentinel.port1=26379
redis.sentinel.host2=127.0.0.1
redis.sentinel.port2=26479
redis.pool.maxTotal=1024
redis.pool.maxIdle=200
redis.pool.maxWaitMillis=1000
redis.pool.testOnBorrow=true
redis.pool.timeBetweenEvictionRunsMillis=30000
redis.pool .minEvictableIdleTimeMillis=30000
redis.pool.softMinEvictableIdleTimeMillis=10000
redis.pool.numTestsPerEvictionRun=1024
#1000*60*60*1
redis.pool.expire=3600000
redis.pool.unlock=false
2、applicationContext.xml檔案中增加Redis的支援
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
<property name="locations">
<list>
<value>classpath:config/redis.properties</value>
</list>
</property>
</bean>
<!-- redis屬性配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxTotal}"/>
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<property name="numTestsPerEvictionRun" value="${redis.pool.numTestsPerEvictionRun}" />
<property name="timeBetweenEvictionRunsMillis" value="${redis.pool.timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis" value="${redis.pool.minEvictableIdleTimeMillis}" />
<property name="softMinEvictableIdleTimeMillis" value="${redis.pool.softMinEvictableIdleTimeMillis}" />
<property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
</bean>
<!-- redis叢集配置 哨兵模式 -->
<bean id="sentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="mymaster"></property>
</bean>
</property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="${redis.sentinel.host1}"></constructor-arg>
<constructor-arg name="port" value="${redis.sentinel.port1}"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="${redis.sentinel.host2}"></constructor-arg>
<constructor-arg name="port" value="${redis.sentinel.port2}"></constructor-arg>
</bean>
</set>
</property>
</bean>
<!-- 連線工廠 -->
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg>
<constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg>
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="redisConnectionFactory"></property>
</bean>
在註解方式中,需要使用<cache:annotation-driven/>
啟動Spring註解功能,這裡就不需要了,在上面的applicationContext.xml檔案中增加如下AOP配置
<!-- 快取Key生成器 -->
<bean id="cacheKeyGenerator" class="com.bug.common.CacheKeyGenerator"></bean>
<!-- 快取管理器 -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg ref="redisTemplate" />
</bean>
<cache:advice id="cacheAdvice" cache-manager="cacheManager" key-generator="cacheKeyGenerator">
<cache:caching cache="user">
<cache:cacheable method="selectUserById"/>
<cache:cache-evict method="deleteUserById"/>
</cache:caching>
</cache:advice>
<aop:config>
<aop:pointcut id="cachePointcut" expression="execution(* com.bug.service.*.impl..*(..))" />
<aop:advisor advice-ref="cacheAdvice" pointcut-ref="cachePointcut"/>
</aop:config>
cache:advice下可以指定多個cache:caching,有點類似於基於註解的@caching,cache:caching元素下又可以指定cache:cacheable、cache:cache-put和cache:cache-evict元素,它們類似於使用註解時的@Cacheable、@CachePut和@CacheEvict,這裡的方法名還可以用萬用字元如select*,表示任何以select開頭的方法都可以使用快取; 有了cache:advice之後,還需要引入aop名稱空間,然後通過aop:config指定定義好的cacheAdvice要應用在哪些pointcut上
以上是AOP的主要配置
在同在的專案組中,需要引入Redis快取,但是有兩個要求:1、對現有業務程式碼不能有任何侵入性,這一點用AOP已經可以滿足,2、要有降級方案,如果不需要快取,需要把快取關掉,因此需要配置一個開關,當開關開啟時,快取生效,當開關關閉時,快取不生效,但是這個開關配置在哪裡呢,經查閱Spring原始碼時發現,每一次呼叫快取之前,需經過一個攔截器CacheInterceptor,原始碼如下
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Invoker aopAllianceInvoker = new Invoker() {
@Override
public Object invoke() {
try {
return invocation.proceed();
} catch (Throwable ex) {
throw new ThrowableWrapper(ex);
}
}
};
try {
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
} catch (ThrowableWrapper th) {
throw th.original;
}
}
private static class ThrowableWrapper extends RuntimeException {
private final Throwable original;
ThrowableWrapper(Throwable original) {
this.original = original;
}
}
}
因此我的想法是,重寫這個攔截器,在攔截器中首先查詢快取是否開啟的標識,判斷如果快取開啟,繼續執行下面的execute方法,如果未開啟,則跳過,這樣就可以滿足降級方案的要求了,但是遺憾的是,CacheInterceptor攔截器是寫死在原始碼中的,不能通過配置檔案的方式進行覆蓋,因此需要修改Spring原始碼才行,修改點如下,把原始碼中的CacheInterceptor替換成自己的Interceptor(未經驗證過,只是猜測)