隨筆-9  評論-3  文章-0  trackbacks-0
          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的相關源代碼。

          posted on 2010-12-20 08:42 臭美 閱讀(3416) 評論(2)  編輯  收藏 所屬分類: Cache

          評論:
          # re: oscache源代碼閱讀(四) -- JSP/Servlet緩存CacheFilter 2010-12-20 17:21 | 極歪歪
          只可惜搜索引擎蜘蛛不喜歡java  回復  更多評論
            
          # re: oscache源代碼閱讀(四) -- JSP/Servlet緩存CacheFilter 2010-12-20 19:30 | 臭美
          @極歪歪
          沒太懂誒,這個和爬蟲什么關系咧  回復  更多評論
            
          主站蜘蛛池模板: 宣化县| 岳西县| 深州市| 攀枝花市| 嘉义市| 布拖县| 筠连县| 南涧| 讷河市| 镇雄县| 汪清县| 焦作市| 洛隆县| 鹤庆县| 临洮县| 黔南| 武强县| 平定县| 武乡县| 新平| 平利县| 崇州市| 平顶山市| 余江县| 乾安县| 道真| 贺州市| 四平市| 大城县| 南阳市| 台中市| 高要市| 宝清县| 东兴市| 永吉县| 东阿县| 余庆县| 安国市| 厦门市| 安陆市| 望谟县|