1. 程式人生 > >基於AOP配置的Spring快取

基於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(未經驗證過,只是猜測)

這裡寫圖片描述