zoukankan      html  css  js  c++  java
  • oscache-4

    [转:http://www.blogjava.net/zddava/archive/2010/12/20/341124.html]

    oscache对于jsp/servlet的缓存是使用Filter来实现的,对应的类是com.opensymphony.oscache.web.filter.CacheFilter,既然是Filter那么要看的自然主要有三个方法:init、doFilter和destroy,这里#destroy()并没有具体实现,只关注前两个即可,首先看一下#init()方法,

        public void init(FilterConfig filterConfig) {
            config 
    = filterConfig;

            log.info(
    "OSCache: Initializing CacheFilter with filter name " + config.getFilterName());

            
    // 此变量用于防治请求被重复的缓存
            requestFiltered = REQUEST_FILTERED + config.getFilterName();
            log.info(
    "Request filter attribute is " + requestFiltered);

            
    // 读取配置文件
            Properties props = null;
            
    try {
                
    // 首先按照Filter参数指定的地方读取配置文件,否则读取默认位置的
                String propertiesfile = config.getInitParameter("oscache-properties-file");

                
    if (propertiesfile != null && propertiesfile.length() > 0{
                    props 
    = Config.loadProperties(propertiesfile,
                            
    "CacheFilter with filter name '" + config.getFilterName() + "'");
                }

            }
     catch (Exception e) {
                log.info(
    "OSCache: Init parameter 'oscache-properties-file' not set, using default.");
            }


            
    // 实例化ServletCacheAdministrator,ServletCacheAdministrator只有一个实例,这里需要关注一下
            admin = ServletCacheAdministrator.getInstance(config.getServletContext(), props);

            
    // 缓存超时时间
            String timeParam = config.getInitParameter("time");
            
    if (timeParam != null{
                
    try {
                    setTime(Integer.parseInt(timeParam));
                }
     catch (NumberFormatException nfe) {
                    log.error(
    "OSCache: Unexpected value for the init parameter 'time', defaulting to one hour. Message="
                            
    + nfe.getMessage());
                }

            }


            
    // 缓存范围
            String scopeParam = config.getInitParameter("scope");
            
    if (scopeParam != null{
                
    if ("session".equalsIgnoreCase(scopeParam)) {
                    setCacheScope(PageContext.SESSION_SCOPE);
                }
     else if ("application".equalsIgnoreCase(scopeParam)) {
                    setCacheScope(PageContext.APPLICATION_SCOPE);
                }
     else {
                    log.error(
    "OSCache: Wrong value '" + scopeParam
                            
    + "' for init parameter 'scope', defaulting to 'application'.");
                }


            }


            
    // 利用"计划任务"表达式来处理超时时间
            setCron(config.getInitParameter("cron"));

            
    // 是否处理include
            String fragmentParam = config.getInitParameter("fragment");
            
    if (fragmentParam != null{
                
    if ("no".equalsIgnoreCase(fragmentParam)) {
                    setFragment(FRAGMENT_NO);
                }
     else if ("yes".equalsIgnoreCase(fragmentParam)) {
                    setFragment(FRAGMENT_YES);
                }
     else if ("auto".equalsIgnoreCase(fragmentParam)) {
                    setFragment(FRAGMENT_AUTODETECT);
                }
     else {
                    log.error(
    "OSCache: Wrong value '" + fragmentParam
                            
    + "' for init parameter 'fragment', defaulting to 'auto detect'.");
                }

            }


            
    // 是否处理URL里包括session id的请求
            String nocacheParam = config.getInitParameter("nocache");
            
    if (nocacheParam != null{
                
    if ("off".equalsIgnoreCase(nocacheParam)) {
                    nocache 
    = NOCACHE_OFF;
                }
     else if ("sessionIdInURL".equalsIgnoreCase(nocacheParam)) {
                    nocache 
    = NOCACHE_SESSION_ID_IN_URL;
                }
     else {
                    log.error(
    "OSCache: Wrong value '" + nocacheParam
                            
    + "' for init parameter 'nocache', defaulting to 'off'.");
                }

            }


            
    // 是否处理写入到response中的header属性Last-Modified
            String lastModifiedParam = config.getInitParameter("lastModified");
            
    if (lastModifiedParam != null{
                
    if ("off".equalsIgnoreCase(lastModifiedParam)) {
                    lastModified 
    = LAST_MODIFIED_OFF;
                }
     else if ("on".equalsIgnoreCase(lastModifiedParam)) {
                    lastModified 
    = LAST_MODIFIED_ON;
                }
     else if ("initial".equalsIgnoreCase(lastModifiedParam)) {
                    lastModified 
    = LAST_MODIFIED_INITIAL;
                }
     else {
                    log.error(
    "OSCache: Wrong value '" + lastModifiedParam
                            
    + "' for init parameter 'lastModified', defaulting to 'initial'.");
                }

            }


            
    // 是否处理写入到response中的header属性Expires
            String expiresParam = config.getInitParameter("expires");
            
    if (expiresParam != null{
                
    if ("off".equalsIgnoreCase(expiresParam)) {
                    setExpires(EXPIRES_OFF);
                }
     else if ("on".equalsIgnoreCase(expiresParam)) {
                    setExpires(EXPIRES_ON);
                }
     else if ("time".equalsIgnoreCase(expiresParam)) {
                    setExpires(EXPIRES_TIME);
                }
     else {
                    log.error(
    "OSCache: Wrong value '" + expiresParam
                            
    + "' for init parameter 'expires', defaulting to 'on'.");
                }

            }


            
    // 是否处理写入到response中的header属性Cache-Control
            String cacheControlMaxAgeParam = config.getInitParameter("max-age");
            
    if (cacheControlMaxAgeParam != null{
                
    if (cacheControlMaxAgeParam.equalsIgnoreCase("no init")) {
                    setCacheControlMaxAge(MAX_AGE_NO_INIT);
                }
     else if (cacheControlMaxAgeParam.equalsIgnoreCase("time")) {
                    setCacheControlMaxAge(MAX_AGE_TIME);
                }
     else {
                    
    try {
                        setCacheControlMaxAge(Long.parseLong(cacheControlMaxAgeParam));
                    }
     catch (NumberFormatException nfe) {
                        log.error(
    "OSCache: Unexpected value for the init parameter 'max-age', defaulting to '60'. Message="
                                
    + nfe.getMessage());
                    }

                }

            }


            
    // ICacheKeyProvider的实例,用于创建缓存的key
            ICacheKeyProvider cacheKeyProviderParam = (ICacheKeyProvider) instantiateFromInitParam(
                    
    "ICacheKeyProvider", ICacheKeyProvider.classthis.getClass().getName());
            
    if (cacheKeyProviderParam != null{
                setCacheKeyProvider(cacheKeyProviderParam);
            }


            
    // ICacheGroupsProvider的实例,用于创建缓存的group名字
            ICacheGroupsProvider cacheGroupsProviderParam = (ICacheGroupsProvider) instantiateFromInitParam(
                    
    "ICacheGroupsProvider", ICacheGroupsProvider.classthis.getClass().getName());
            
    if (cacheGroupsProviderParam != null{
                setCacheGroupsProvider(cacheGroupsProviderParam);
            }


            
    // EntryRefreshPolicy的实例,用于指定缓存过期策略
            EntryRefreshPolicy expiresRefreshPolicyParam = (EntryRefreshPolicy) instantiateFromInitParam(
                    
    "EntryRefreshPolicy", EntryRefreshPolicy.class, ExpiresRefreshPolicy.class.getName());
            
    if (expiresRefreshPolicyParam != null{
                setExpiresRefreshPolicy(expiresRefreshPolicyParam);
            }
     else {
                setExpiresRefreshPolicy(
    new ExpiresRefreshPolicy(time));
            }


            
    // 指定哪些请求方式不去缓存,如GET,POST等
            String disableCacheOnMethodsParam = config.getInitParameter("disableCacheOnMethods");
            
    if (StringUtil.hasLength(disableCacheOnMethodsParam)) {
                disableCacheOnMethods 
    = StringUtil.split(disableCacheOnMethodsParam, ',');
            }


        }


    这个方法主要是对Filter的init-param的载入还有缓存管理器类实例的创建(里面会包括一个Application Scope的Cache的创建还有oscache配置文件的读取)。其中的这句调用时需要关注一下的,admin = ServletCacheAdministrator.getInstance(config.getServletContext(), props),也就是ServletCacheAdministrator类实例的创建。

        public synchronized static ServletCacheAdministrator getInstance(ServletContext context, Properties p) {
            
    // Cache在ServletContext中的属性名
            String adminKey = null;
            
    if (p != null{
                
    // 这里是oscache配置文件中的cache.key属性
                adminKey = p.getProperty(CACHE_KEY_KEY);
            }

            
    if (adminKey == null{
                adminKey 
    = DEFAULT_CACHE_KEY;
            }

            
    // ServletCacheAdministrator在ServletContext中的键值要加上"_admin"这个后缀
            adminKey += CACHE_ADMINISTRATOR_KEY_SUFFIX;

            
    // 先尝试在ServletContext中找Cache,当然,第一次初始化时是不会找到的
            ServletCacheAdministrator admin = (ServletCacheAdministrator) context.getAttribute(adminKey);

            
    if (admin == null{
                
    // 实例化一个,并在ServletContext中设定好相关属性
                admin = new ServletCacheAdministrator(context, p);
                Map admins 
    = (Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);
                
    if (admins == null{
                    admins 
    = new HashMap();
                }

                admins.put(adminKey, admin);
                context.setAttribute(CACHE_ADMINISTRATORS_KEY, admins);
                context.setAttribute(adminKey, admin);

                
    if (log.isInfoEnabled()) {
                    log.info(
    "Created new instance of ServletCacheAdministrator with key " + adminKey);
                }


                
    // 创建Application级别的Cache
                admin.getAppScopeCache(context);
            }


            
    if (admin.context == null{
                admin.context 
    = context;
            }


            
    return admin;
        }


        
    public Cache getAppScopeCache(ServletContext context) {
            Cache cache;
            
    // 首先尝试在ServletContext中查询App级的缓存
            Object obj = context.getAttribute(getCacheKey());

            
    if ((obj == null|| !(obj instanceof Cache)) {
                
    if (log.isInfoEnabled()) {
                    log.info(
    "Created new application-scoped cache at key: " + getCacheKey());
                }


                
    // 创建一个缓存实例并放入ServletContext中
                cache = createCache(PageContext.APPLICATION_SCOPE, null);
                context.setAttribute(getCacheKey(), cache);
            }
     else {
                cache 
    = (Cache) obj;
            }


            
    return cache;
        }


        
    private ServletCache createCache(int scope, String sessionId) {
            
    // 创建ServletCache
            ServletCache newCache = new ServletCache(this, algorithmClass, cacheCapacity, scope);

            
    // 这里的2个参数是用于给持久化缓存构建缓存目录用的,这里要注意,Session级别的缓存可能会有问题
            
    // 因为config是全局唯一的,在并发访问的情况下如果多个session同时创建缓存会出现同步错误的
            
    // 所以session级别缓存是不是应该慎用磁盘缓存呢?
            config.set(HASH_KEY_SCOPE, "" + scope);
            config.set(HASH_KEY_SESSION_ID, sessionId);

            
    // 初始化Cache监听器,包括磁盘缓存的处理类
            newCache = (ServletCache) configureStandardListeners(newCache);

            
    if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY) != null{
                
    // Add any event listeners that have been specified in the
                
    // configuration
                CacheEventListener[] listeners = getCacheEventListeners();

                
    for (int i = 0; i < listeners.length; i++{
                    
    if (listeners[i] instanceof ScopeEventListener) {
                        newCache.addCacheEventListener(listeners[i]);
                    }

                }

            }


            
    return newCache;
        }


    看过#init()方法后就轮到#doFilter()方法了,这是真正每次对请求进行缓存的地方:

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                
    throws ServletException, IOException {
            
    if (log.isInfoEnabled()) {
                log.info(
    "OSCache: filter in scope " + cacheScope);
            }


            
    // 判断是否请求已经缓存或者是否能缓存
            
    // #isFilteredBefore()判断request中是否包括了requestFiltered这个变量
            
    // #isCacheableInternal()根据Filter的disableCacheOnMethods和nocache参数进行判断
            if (isFilteredBefore(request) || !isCacheableInternal(request)) {
                chain.doFilter(request, response);
                
    return;
            }


            
    // 设置当前的请求已经缓存了
            request.setAttribute(requestFiltered, Boolean.TRUE);

            HttpServletRequest httpRequest 
    = (HttpServletRequest) request;

            
    // checks if the response will be a fragment of a page
            
    // 是否处理"include",如果是自动判断那么要判断请求中是否有javax.servlet.include.request_uri参数
            boolean fragmentRequest = isFragment(httpRequest);

            
    // 根据不同的缓存范围来返回缓存实例
            Cache cache;
            
    if (cacheScope == PageContext.SESSION_SCOPE) {
                
    // #getSessionScopeCache()中返回的Cache是保存在Session中的
                cache = admin.getSessionScopeCache(httpRequest.getSession(true));
            }
     else {
                
    // #getAppScopeCache()中返回的Cache是保存在ServletContext中的
                cache = admin.getAppScopeCache(config.getServletContext());
            }


            
    // 生成缓存的key,默认的cacheKeyProvider就是CacheFilter自己
            
    // 成生的key默认是请求路径+方法名(GET,POST)
            String key = cacheKeyProvider.createCacheKey(httpRequest, admin, cache);

            
    try {
                
    // 查找缓存,如果还没有加入缓存,会抛出NeedsRefreshException,进入异常处理路径
                ResponseContent respContent = (ResponseContent) cache.getFromCache(key, time, cron);

                
    if (log.isInfoEnabled()) {
                    log.info(
    "OSCache: Using cached entry for " + key);
                }


                
    boolean acceptsGZip = false;
                
    // 这里是对客户端缓存的处理,判断下请求中是否包含If-Modified-Since头信息
                if ((!fragmentRequest) && (lastModified != LAST_MODIFIED_OFF)) {
                    
    long clientLastModified = httpRequest.getDateHeader(HEADER_IF_MODIFIED_SINCE);

                    
    // 如果请求中的最后修改时间大于缓存的最后修改时间,那么就返回状态码304
                    if ((clientLastModified != -1&& (clientLastModified >= respContent.getLastModified())) {
                        ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                        
    return;
                    }


                    
    // 判断是否接受gzip压缩的响应,通过判断请求Header Accept-Encoding是否包含gzip
                    acceptsGZip = respContent.isContentGZiped() && acceptsGZipEncoding(httpRequest);
                }


                
    // 将缓存的内容写入响应
                respContent.writeTo(response, fragmentRequest, acceptsGZip);
            }
     catch (NeedsRefreshException nre) {
                
    // 如果缓存中还没有想要的数据或者缓存需要刷新
                boolean updateSucceeded = false;

                
    try {
                    
    if (log.isInfoEnabled()) {
                        log.info(
    "OSCache: New cache entry, cache stale or cache scope flushed for " + key);
                    }


                    
    // 这里用CacheHttpServletResponseWrapper来代替原来的response对象继续请求处理
                    CacheHttpServletResponseWrapper cacheResponse = new CacheHttpServletResponseWrapper(
                            (HttpServletResponse) response, fragmentRequest, time 
    * 1000L, lastModified, expires,
                            cacheControlMaxAge);
                    
    // 继续调用后边的Filter和Servlet
                    chain.doFilter(request, cacheResponse);
                    cacheResponse.flushBuffer();

                    
    // 这里判断下响应码是否是200,只有"OK"才会缓存
                    if (isCacheableInternal(cacheResponse)) {
                        
    // 创建缓存的group,默认的cacheGroupsProvider也是自己
                        String[] groups = cacheGroupsProvider.createCacheGroups(httpRequest, admin, cache);
                        
    // Store as the cache content the result of the response
                        
    // 将响应内容写入缓存
                        cache.putInCache(key, cacheResponse.getContent(), groups, expiresRefreshPolicy, null);
                        updateSucceeded 
    = true;
                        
    if (log.isInfoEnabled()) {
                            log.info(
    "OSCache: New entry added to the cache with key " + key);
                        }

                    }

                }
     finally {
                    
    if (!updateSucceeded) {
                        
    // 如果写入缓存失败,要取消更新,防止缓存出现错误的状态
                        cache.cancelUpdate(key);
                    }

                }

            }

        }

    这个方法的整个流程通过代码的注释其实是很好理解的,要注意的地方有两点:

    首先,关注下异常路径里更新缓存的地方,这里用CacheHttpServletResponseWrapper来代替原来的Response对象来继续流程,通过对Response进行包装(Decorator)的方式来记录其要返回客户端的Header和页面内容(记录到ResponseContent类的属性中),其中页面内容的捕获是通过用SplitServletOutputStream类来代替原有的OutputStream来实现的,SplitServletOutputStream中包括了ResponseContent对象的一个ByteArrayOutputStream,每次写入页面响应的数据也都要记录在ByteArrayOutputStream中一份,而在调用Cache的putInCache()方法时有一个cacheResponse.getContent()方法,会返回ResponseContent类的属性,也就是真正要缓存的对象,并且将其ByteArrayOutputStream流中的数据"提交"到一个byte数组中保存下来,从而实现了响应数据的缓存。
    其次,是将缓存的ResponseContent中的数据输出的过程,也就是这一句:respContent.writeTo(response, fragmentRequest, acceptsGZip);基本可以理解为上边缓存过程的逆过程,这里就不多说了,有兴趣的可以了解下CacheHttpServletResponseWrapper,ResponseContent,SplitServletOutputStream的相关源代码。

  • 相关阅读:
    iOS 最新版 CocoaPods 的安装流程
    AFNetworking 3.0.4 的使用
    NSPredicate谓词
    PHP基本类型操作
    MJExtension使用指导(转)
    字典转模型KVC和runtime二者实现与区别
    iOS之KVC字典转模型的底层实现
    runtime 总结(原创)
    Objective-C Runtime能做什么?
    Runtime那些事儿(消息机制)
  • 原文地址:https://www.cnblogs.com/wangjianbg/p/3445461.html
Copyright © 2011-2022 走看看