Ehcache学习ehcache与springAOP拦截器实例

Posted 李晓娜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ehcache学习ehcache与springAOP拦截器实例相关的知识,希望对你有一定的参考价值。

 本次我们使用springAOP+ehcache结合来实现数据的缓存,我们可以 Cache 系统中 Service 或则 DAO 层的 get/find 等方法返回结果,如果数据更新( 使用Create/update/delete 方法), 则刷新 cache 中相应的内容。

 Aop中最常见的就是拦截器,那么我们首先需要创建的就是一个拦截器:

 Ø  MethodCacheInterceptor

<span style="font-family:Microsoft YaHei;font-size:14px;">public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean 
    private static final Log logger = LogFactory.getLog(MethodCacheInterceptor.class);
    private Cache cache;
    public void setCache(Cache cache) 
        this.cache = cache;
    
    public MethodCacheInterceptor() 
        super();
    

    /**
     *  public Object invoke(MethodInvocation invocation) 中,
     完成了搜索 Cache/新建 cache 的功能。
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke( MethodInvocation invocation ) throws Throwable 
        String targetName = invocation.getThis().getClass().getName();
        String methodName = invocation.getMethod().getName();
        Object[] arguments = invocation.getArguments();
        Object result;
        logger.debug("Find object from cache is " + cache.getName());
        String cacheKey = getCacheKey(targetName, methodName, arguments);
//        搜索cache功能,如果存在直接返回,否则的话将创建之后进行存储
        Element element = cache.get(cacheKey);
        if (element == null) 
            logger.debug("Hold up method , Get method result and create cache........!");
            result = invocation.proceed();   //处理结果,这句代码的作用是获取所拦截方法的返回值
            element = new Element(cacheKey, (Serializable) result);
            cache.put(element);
        
        return element.getValue();
//        return null;
    

    /**
     * implement InitializingBean, 检查 cache 是否为空
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception 
        Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it.");

    


    /**
     * 获得 cache key 的方法, cache key 是 Cache 中一个 Element 的唯一标识
     * cache key 包括 包名+类名+方法名, 如 com.co.cache.service.UserServiceImpl.getAllUser
     */
    private String getCacheKey(String targetName, String methodName, Object[] arguments) 
        StringBuffer sb = new StringBuffer();
        sb.append(targetName).append(".").append(methodName);
        if ((arguments != null) && (arguments.length != 0)) 
            for (int i = 0; i < arguments.length; i++) 
                sb.append(".").append(arguments[i]);
            
        
        return sb.toString();
    
</span>

 Ø  MethodCacheAfterAdvice

<span style="font-family:Microsoft YaHei;font-size:14px;">public class MethodCacheAfterAdvice implements AfterReturningAdvice, InitializingBean 

    private static final Log logger = LogFactory.getLog(MethodCacheAfterAdvice.class);
    private Cache cache;
    public void setCache(Cache cache) 
        this.cache = cache;
    
    public MethodCacheAfterAdvice() 
        super();
    
    @Override
    public void afterReturning( Object arg0, Method arg1, Object[] arg2, Object arg3 ) throws Throwable 
        String className = arg3.getClass().getName();
        List list = cache.getKeys();
        for(int i = 0;i<list.size();i++)
            String cacheKey = String.valueOf(list.get(i));
            if(cacheKey.startsWith(className))
                cache.remove(cacheKey);
                logger.debug("remove cache " + cacheKey);
            
        
    

    @Override
    public void afterPropertiesSet() throws Exception 
        Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it.");
    
</span>

  随后,再建立一个拦截器MethodCacheAfterAdvice,作用是在用户进行create/update/delete操作时来刷新/remove 相关 cache 内容, 这个拦截器实现了AfterReturningAdvice 接口,将会在所拦截的方法执行后执行在

public void afterReturning(Object arg0, Method arg1,Object[] arg2, Object arg3)方法中所预定的操作

 Ø  配置文件:

  Application_spring_cache.xml

<span style="font-family:Microsoft YaHei;font-size:14px;"><?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:context="http://www.springframework.org/schema/context"
       xmlns:cache="http://www.springframework.org/schema/cache" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--支持缓存注解-->
    <cache:annotation-driven cache-manager="cacheManager"/>
    <context:component-scan base-package="ehcache.*" />
    <!-- 一些@RequestMapping 请求和一些转换 -->
    <mvc:annotation-driven />

    <!-- 前后缀 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--  静态资源访问 的两种方式  -->
    <!-- <mvc:default-servlet-handler/>   -->
    <!--<mvc:resources location="/*" mapping="/**" />-->
    <mvc:view-controller path="/" view-name="forward:/index.jsp"/>
    <!-- Spring自己的基于java.util.concurrent.ConcurrentHashMap实现的缓存管理器(该功能是从Spring3.1开始提供的) -->
    <!--
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean name="myCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"/>
            </set>
        </property>
    </bean>
     -->
    <!-- 若只想使用Spring自身提供的缓存器,则注释掉下面的两个关于Ehcache配置的bean,并启用上面的SimpleCacheManager即可 -->
    <!-- Spring提供的基于的Ehcache实现的缓存管理器 -->
    <!--  缓存  属性-->
    <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:/ehcache.xml" />
    </bean>

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager"  ref="cacheManagerFactory"/>
    </bean>



    <!--为了测试拦截器缓存-->
    <!-- 定义 ehCache 的工厂, 并设置所使用的 Cache name -->
    <bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager">
            <ref local="cacheManagerFactory"/>
        </property>
        <!--如果 cacheName 属性内设置的 name 在 ehCache.xml 中无法找到, 那么将使用默认的-->
        <!--cache(defaultCache 标签定义)-->
        <property name="cacheName">
            <value>DEFAULT_CACHE</value>
        </property>
    </bean>


    <!-- find/create cache 拦截器 -->
    <bean id="methodCacheInterceptor"
          class="ehcache.CacheInterceptor.MethodCacheInterceptor">
        <property name="cache">
            <ref local="ehCache" />
        </property>
    </bean>

    <!-- flush cache 拦截器 -->
    <bean id="methodCacheAfterAdvice"
          class="ehcache.CacheInterceptor.MethodCacheAfterAdvice">
        <property name="cache">
            <ref local="ehCache" />
        </property>
    </bean>
    <!--上面的代码最终创建了两个"切入点", methodCachePointCut 和-->
    <!--methodCachePointCutAdvice, 分别用于拦截不同方法名的方法, 可以根据需要任意增加-->
    <!--所需要拦截方法的名称。-->
    <bean id="methodCachePointCut"
          class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice">
            <ref local="methodCacheInterceptor"/>
        </property>
        <property name="patterns">
            <list>
                <value>.*find.*</value>
                <value>.*get.*</value>
            </list>
        </property>
    </bean>

    <bean id="methodCachePointCutAdvice"
          class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice">
            <ref local="methodCacheAfterAdvice"/>
        </property>
        <property name="patterns">
            <list>
                <value>.*create.*</value>
                <value>.*update.*</value>
                <value>.*delete.*</value>
            </list>
        </property>
    </bean>
    <!--方法测试-->
    <!--如果缓存的配置与spring拦截在一个方法中就可以直接使用不用引入,否则的话需要引入-->
    <!--<import resource="cacheContext.xml"/>-->
    <bean id="testServiceTarget" class="ehcache.Service.TestServiceImpl"/>
    <bean id="testService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target">
            <ref local="testServiceTarget"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>methodCachePointCut</value>
                <value>methodCachePointCutAdvice</value>
            </list>
        </property>
    </bean>
</beans></span>

  Ehcache.xml

<span style="font-family:Microsoft YaHei;font-size:14px;"><ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!--当内存缓存中对象数量超过maxElementsInMemory时,将缓存对象写到磁盘缓存中(需对象实现序列化接口)。-->
    <diskStore path="java.io.tmpdir"/>
    <!--:用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data和*.index。-->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
</defaultCache>
<cache name="DEFAULT_CACHE"
           maxElementsInMemory="10000"
           eternal="false"
           timeToIdleSeconds="300000"
           timeToLiveSeconds="600000"
           overflowToDisk="true"
    />
</ehcache></span>

 Ø  测试用例

  接口TestService

<span style="font-family:Microsoft YaHei;font-size:14px;">package ehcache.Iservice;

import java.util.List;

/**
 * Created by xiaona on 2016/6/14.
 */
public interface TestService 
    public List getAllObject();
    public void updateObject(Object Object);

</span>

  实现TestServiceImpl

<span style="font-family:Microsoft YaHei;font-size:14px;">package ehcache.Service;

import ehcache.Iservice.TestService;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * Created by xiaona on 2016/6/14.
 */
@Service
public class TestServiceImpl implements TestService 
    @Override
    public List getAllObject() 
        System.out.println("---TestService: Cache 内不存在该 element, 查找并放入 Cache! ");
        return null;
    

    @Override
    public void updateObject( Object Object ) 
        System.out.println("---TestService:更新了对象,这个 Class 产生的 cache 都将被 remove");
    

</span>

  方法调用

  两种方式:getBean方式和使用注解读取

   第一种方式:

<span style="font-family:Microsoft YaHei;font-size:14px;">package ehcache.controller;

import ehcache.Iservice.TestService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by xiaona on 2016/6/14.
 */
public class TestMain 



    public static void main( String[] args ) 
        ApplicationContext context = new ClassPathXmlApplicationContext("application_spring_cache.xml");
        TestService testService=(TestService)context.getBean("testService");
        System.out.println("1--第一次查找并创建 cache");
        testService.getAllObject();
        System.out.println("2--在 cache 中查找");
        testService.getAllObject();
        System.out.println("3--remove cache");
        testService.updateObject(null);
        System.out.println("4--需要重新查找并创建 cache");
        testService.getAllObject();
    


</span>

   第二种方式:

<span style="font-family:Microsoft YaHei;font-size:14px;">@Resource
private TestService testService;
    @RequestMapping(value = "/testInterceptor", method = RequestMethod.GET)
    public String testInterceptor() 
        System.out.println("1--第一次查找并创建 cache");
        testService.getAllObject();
        System.out.println("2--在 cache 中查找");
        testService.getAllObject();
        System.out.println("3--remove cache");
        testService.updateObject(null);
        System.out.println("4--需要重新查找并创建 cache");
        testService.getAllObject();

        return root + "/" + "testInterceptor";
    
</span>

  前提是TestService的实现TestServiceImpl必须加上@Service注解,这样可以把其交给容器进行管理。什么样的结果表示我们的是成功的从缓存中获取到的呢?可以看到,

  第一步执行getAllObject(),执行 TestServiceImpl 内的方法,并创建了 cache, 在第二次执行getAllObject()方法时,由于 cache 有该方法的缓存,直接从cache 中 get 出方法的结果, 所以没有打印出 TestServiceImpl中的内容, 而第三步, 调用了 updateObject 方法,和TestServiceImpl 相关的 cache 被 remove,所以在第四步执行时,又执行TestServiceImpl 中的方法, 创建 Cache。

 Ø  小结

  我们看出结合SpringAop的实例,可以实现我们对于请求方法的拦截,其实很多时候我们用到的页面都是一定时间内不会发生变化的,此时我们需要的是拦截经常被调用的方法从而进行缓存,这样的话可以提高用户对于页面请求的响应速度。

以上是关于Ehcache学习ehcache与springAOP拦截器实例的主要内容,如果未能解决你的问题,请参考以下文章

Ehcache学习ehcache与springAOP拦截器实例

Ehcache基础知识学习

Ehcache学习简介与实例

Ehcache学习总结二: Ehcache+Spring+Mybaits整合

mybatis学习笔记(14)-mybatis整合ehcache

Ehcache学习ehcache缓存共享