spring思維導圖,讓spring更加簡單易懂

爛豬皮發表於2018-03-03

一:《spring簡介》

關於Spring

Spring是一個開源框架,是為了解決企業應用程式開發複雜性而建立的。框架的主要優勢之一就是其分層架構,分層架構允許您選擇使用哪一個元件,同時為 J2EE 應用程式開發提供整合的框架。

它是一個全面的、企業應用開發一站式的解決方案,貫穿表現層、業務層、持久層。但是Spring仍然可以和其他的框架無縫整合。

spring思維導圖,讓spring更加簡單易懂

Sping架構

Spring框架是分模組存在,除了最核心的Spring Core Container(即Spring容器)是必要模組之外,其他模組都是可選,視需要而定。大約有20多個模組。

spring思維導圖,讓spring更加簡單易懂

spring思維導圖,讓spring更加簡單易懂

Spring3與Spring4是有區別的,4.0主要是對Java 8的新函式式語法進行支援,還有加強了對網路各種新技術比如http-streaming, websocket的更好的支援。

一般來說,Spring主要分為7個模組:

spring思維導圖,讓spring更加簡單易懂

spring思維導圖,讓spring更加簡單易懂

Spring的主要jar包

spring思維導圖,讓spring更加簡單易懂
spring思維導圖,讓spring更加簡單易懂

常用註解

bean注入與裝配的的方式有很多種,可以通過xml,getset方式,建構函式或者註解等。簡單易用的方式就是使用Spring的註解了,Spring提供了大量的註解方式,讓專案閱讀和開發起來更加方便。

spring思維導圖,讓spring更加簡單易懂

spring思維導圖,讓spring更加簡單易懂

第三方框架整合

Spring框架的開發不是為了替代現有的優秀第三方框架,而是通過整合的方式把它們都連線起來。下面總結了一些常整合的優秀框架。

spring思維導圖,讓spring更加簡單易懂

二:《spring之IOC》

寫過java的都知道:所有的物件都必須建立;或者說:使用物件之前必須先建立。而使用ioc之後,你就可以不再手動建立物件,而是從ioc容器中直接獲取物件。

就好像我們無需考慮物件的銷燬回收一樣,因為java垃圾回收機制幫助我們實現了這個過程;而ioc則是讓我們無需考慮物件的建立過程,由ioc容器幫我們實現物件的建立、注入等過程。
spring思維導圖,讓spring更加簡單易懂

控制反轉

spring思維導圖,讓spring更加簡單易懂
spring思維導圖,讓spring更加簡單易懂

spring ioc容器

在Spring框架中的核心元件只有三個:Core、Context和Bean。它們構建起了整個Spring的骨骼架構,沒有它們就不可能有AOP、Web等特性功能。

spring思維導圖,讓spring更加簡單易懂

如果說在三個核心中再選出一個核心,那就非Bean莫屬了。可以說,Spring就是面向Bean的程式設計,Bean在Spring中才是真正的主角。

Spring為何如此流行?你會發現Spring解決了一個非常關鍵的問題,它可以讓你對物件之間的關係轉而用配置檔案來管理,或者註解,也就是它的依賴注入機制。而這個注入關係在一個叫Ioc的容器中管理。Ioc容器就是被Bean包裹的物件。Spring正是通過把物件包裝在Bean中從而達到管理這些物件及做一些列額外操作的目的。

spring思維導圖,讓spring更加簡單易懂
spring思維導圖,讓spring更加簡單易懂

核心元件協同工作

spring思維導圖,讓spring更加簡單易懂

BeanFactory與ApplacationContext的區別

IOC中最核心的介面是Beanfactory提供IOC的高階服務,而ApplicationContext是建立在BeanFactory基礎之上提供抽象的面向應用的服務。

spring思維導圖,讓spring更加簡單易懂

3種注入方式
在Spring框架中,依賴注入(DI)的設計模式是用來定義物件彼此間的依賴。使用xml配置bean的情況下,它主要有兩種型別:

Setter方法注入
構造器注入
當然,有了註解之後,使用註解的方式更加方便快捷。即自動裝配功能實現屬性自動注入(@autowire)。

寫到這裡,讓我想起了最近在牛客網上看的一道選擇題了:

下面有關spring的依賴注入,說法錯誤的是?

A、依賴注入通常有如下兩種:設定注入和構造注入:
B、構造注入可以在構造器中決定依賴關係的注入順序,優先依賴的優先注入
C、當設值注入與構造注入同時存在時,先執行構造注入,再執行設值注入
D、設值注入是指IoC容器使用屬性的setter方法來注入被依賴的例項。這種注入方式比較簡單、直觀
牛客網給出的答案是選C,不過網友們好像對答案有不同的意見哈。檢視網友評論及答案

原理解析

Spring的程式碼還真是不好讀,分得太細了,文字也是難以描述出來,看了別人有關的部落格,貼了好多程式碼,畫了好多ER圖來描述關鍵介面或類之間的關係。這麼一篇這麼長文章下來,大家也未必會認真讀程式碼,看ER圖,乾脆也不跟風了。就貼了一點在我看來特關鍵的程式碼,嘿嘿。

context的初始化過程

當執行ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 構造方法ClassPathXmlApplicationContext(String configLocation)呼叫了this(new String[] {configLocation}, true, null);, 該構造方法具體程式碼如下。

spring思維導圖,讓spring更加簡單易懂

spring思維導圖,讓spring更加簡單易懂
spring思維導圖,讓spring更加簡單易懂

從時序圖來看啟動上述初始化

spring思維導圖,讓spring更加簡單易懂

三:《spring之MVC》

spring mvc簡介與執行原理

Spring的模型-檢視-控制器(MVC)框架是圍繞一個DispatcherServlet來設計的,這個Servlet會把請求分發給各個處理器,並支援可配置的處理器對映、檢視渲染、本地化、時區與主題渲染等,甚至還能支援檔案上傳。

spring思維導圖,讓spring更加簡單易懂

(1) Http請求:客戶端請求提交到DispatcherServlet。
(2) 尋找處理器:由DispatcherServlet控制器查詢一個或多個HandlerMapping,找到處理請求的Controller。
(3) 呼叫處理器:DispatcherServlet將請求提交到Controller。
(4)(5)呼叫業務處理和返回結果:Controller呼叫業務邏輯處理後,返回ModelAndView。
(6)(7)處理檢視對映並返回模型: DispatcherServlet查詢一個或多個ViewResoler檢視解析器,找到ModelAndView指定的檢視。
(8) Http響應:檢視負責將結果顯示到客戶端。

主要註解

spring思維導圖,讓spring更加簡單易懂

ContextLoaderListener

在講ContextLoaderListener之前,首先來了解一下web.xml的作用。

一個web中可以沒有web.xml檔案,也就是說,web.xml檔案並不是web工程必須的。web.xml檔案是用來初始化配置資訊:比如Welcome頁面、servlet、servlet-mapping、filter、listener、啟動載入級別等。當你的web工程沒用到這些時,你可以不用web.xml檔案來配置你的Application。
當要啟動某個web專案時,伺服器軟體或容器如(tomcat)會第一步載入專案中的web.xml檔案,通過其中的各種配置來啟動專案,只有其中配置的各項均無誤時,專案才能正確啟動。web.xml有多項標籤,在其載入的過程中順序依次為:context-param >> listener >> fileter >> servlet。(同類多個節點以出現順序依次載入)

spring思維導圖,讓spring更加簡單易懂

而spring mvc啟動過程大致分為兩個過程:

ContextLoaderListener初始化,例項化IoC容器,並將此容器例項註冊到ServletContext中。
DispatcherServlet初始化。

spring思維導圖,讓spring更加簡單易懂

其中ContextLoaderListener監聽器它實現了ServletContextListener這個介面,在web.xml配置這個監聽器,啟動容器時,就會預設執行它實現的方法。在ContextLoaderListener中關聯了ContextLoader這個類,所以整個載入配置過程由ContextLoader來完成。

ContextLoaderListener在web.xml中的配置

<!-- 配置contextConfigLocation初始化引數 -->
 <param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- 配置ContextLoaderListerner -->
   <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
     </listener>
     複製程式碼

ServletContextListener 介面有兩個方法:contextInitialized,contextDestroyed

DispatcherServlet

Spring MVC框架,與其他很多web的MVC框架一樣:請求驅動;所有設計都圍繞著一箇中央Servlet來展開,它負責把所有請求分發到控制器;同時提供其他web應用開發所需要的功能。不過Spring的中央處理器,DispatcherServlet,能做的比這更多。

下圖展示了Spring Web MVC的DispatcherServlet處理請求的工作流。熟悉設計模式的朋友會發現,DispatcherServlet應用的其實就是一個“前端控制器”的設計模式(其他很多優秀的web框架也都使用了這個設計模式)。

流程圖

spring思維導圖,讓spring更加簡單易懂

在web.xml中的配置

<!-- servlet定義 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
 </servlet-mapping>
 複製程式碼

其中

load-on-startup:表示啟動容器時初始化該Servlet;
url-pattern:表示哪些請求交給Spring Web MVC處理, “/” 是用來定義預設servlet對映的。也可以如“*.html”表示攔截所有以html為副檔名的請求。
在Spring MVC中,每個DispatcherServlet都持有一個自己的上下文物件WebApplicationContext,它又繼承了根(root)WebApplicationContext物件中已經定義的所有bean。這些繼承的bean可以在具體的Servlet例項中被過載,在每個Servlet例項中你也可以定義其scope下的新bean。

WebApplicationContext繼承自ApplicationContext,它提供了一些web應用經常需要用到的特性。它與普通的ApplicationContext不同的地方在於,它支援主題的解析,並且知道它關聯到的是哪個servlet(它持有一個該ServletContext的引用)

spring思維導圖,讓spring更加簡單易懂

spring mvc同時提供了很多特殊的註解,用於處理請求和渲染檢視等。DispatcherServlet初始化的過程中會預設使用這些特殊bean進行配置。如果你想指定使用哪個特定的bean,你可以在web應用上下文WebApplicationContext中簡單地配置它們。

spring思維導圖,讓spring更加簡單易懂
spring思維導圖,讓spring更加簡單易懂

其中,常用的ViewResolver的配置。以jsp作為檢視為例

<!-- 對模型檢視名稱的解析,即在模型檢視名稱新增前字尾 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
       <property name="prefix" value="/WEB-INF/jsp/" />
     <property name="suffix" value=".jsp" />
</bean>

配置上傳檔案限制MultipartResolver

   <!-- 上傳限制 -->
 <bean id="multipartResolver"                         class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
 <!-- 上傳檔案大小限制為31M,31*1024*1024 -->
 <property name="maxUploadSize" value="32505856"/>
</bean>
複製程式碼

applicationContext.xml中的標籤

spring思維導圖,讓spring更加簡單易懂

檔案上傳

前面說到DispatcherServlet中有個特殊的Bean叫MultipartResolver,可用於限制檔案的上傳大小等。當解析器MultipartResolver完成處理時,請求便會像其他請求一樣被正常流程處理。

表單

<form method="post" action="/form" enctype="multipart/form-data">
 <input type="text" name="name"/>
 <input type="file" name="file"/>
  <input type="submit"/>
    </form>
    複製程式碼

控制器

     @RequestMapping(path = "/form", method = RequestMethod.POST)
         @RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
      byte[] bytes = file.getBytes();
      // store the bytes somewhere
      return "redirect:uploadSuccess";
       }
   return "redirect:uploadFailure";
}   
複製程式碼

異常處理
先來說下常見的異常處理有幾種方式,如下圖:

spring思維導圖,讓spring更加簡單易懂

Spring的處理器異常解析器HandlerExceptionResolver介面的實現負責處理各類控制器執行過程中出現的異常。也是上面提到的,是DispatcherServlet中的特殊bean,可以自定義配置處理。

某種程度上講,HandlerExceptionResolver與你在web應用描述符web.xml檔案中能定義的異常對映(exception mapping)很相像,不過它比後者提供了更靈活的方式。比如它能提供異常被丟擲時正在執行的是哪個處理器這樣的資訊。

HandlerExceptionResolver 提供resolveException介面

 public interface HandlerExceptionResolver {  
ModelAndView resolveException(  
        HttpServletRequest request, HttpServletResponse response, Object handler,                            Exception ex);  
     }
     複製程式碼

在BaseController中使用 @ExceptionHandler註解處理異常

   @ExceptionHandler(Exception.class)
   public Object exceptionHandler(Exception ex, HttpServletResponse response, 
          HttpServletRequest request) throws IOException {
    String url = "";
    String msg = ex.getMessage();
    Object resultModel = null;
    try {
        if (ex.getClass() == HttpRequestMethodNotSupportedException.class) {
            url = "admin/common/500";
            System.out.println("--------毛有找到對應方法---------");
        } else if (ex.getClass() == ParameterException.class) {//自定義的異常

        } else if (ex.getClass() == UnauthorizedException.class) {
            url = "admin/common/unauth";
            System.out.println("--------毛有許可權---------");
        }

        String header = req.getHeader("X-Requested-With");
        boolean isAjax = "XMLHttpRequest".equalsIgnoreCase(header);
        String method = req.getMethod();
        boolean isPost = "POST".equalsIgnoreCase(method);

        if (isAjax || isPost) {
            return Message.error(msg);
        } else {
            ModelAndView view = new ModelAndView(url);
            view.addObject("error", msg);
            view.addObject("class", ex.getClass());
            view.addObject("method", request.getRequestURI());
            return view;
        }
    } catch (Exception exception) {
        logger.error(exception.getMessage(), exception);
        return resultModel;
    } finally {
        logger.error(msg, ex);
        ex.printStackTrace();
    }
}

複製程式碼

在web.xml中處理異常

       
      <!-- 預設的錯誤處理頁面 -->
 <error-page>
<error-code>403</error-code>
<location>/403.html</location>
</error-page>
  <error-page>
<error-code>404</error-code>
<location>/404.html</location>
  </error-page>
      <!-- 僅僅在除錯的時候注視掉,在正式部署的時候不能註釋 -->
       <!-- 這樣配置也是可以的,表示發生500錯誤的時候,轉到500.jsp頁面處理。 -->
      <error-page> 
          <error-code>500</error-code> 
     <location>/500.html</location> 
      </error-page> 

<!-- 這樣的配置表示如果jsp頁面或者servlet發生java.lang.Exception型別(當然包含子類)的異常         就會轉到500.jsp頁面處理。 -->
   <error-page> 
<exception-type>java.lang.Exception</exception-type> 
<location>/500.jsp</location> 
     </error-page> 

     <error-page> 
<exception-type>java.lang.Throwable</exception-type> 
<location>/500.jsp</location> 
  </error-page>

    <!-- 當error-code和exception-type都配置時,exception-type配置的頁面優先順序高及出現500錯誤,發生異常Exception時會跳轉到500.jsp-->
    複製程式碼

來一個問題:HandlerExceptionResolver和web.xml中配置的error-page會有衝突嗎?

解答:如果resolveException返回了ModelAndView,會優先根據返回值中的頁面來顯示。不過,resolveException可以返回null,此時則展示web.xml中的error-page的500狀態碼配置的頁面。 當web.xml中有相應的error-page配置,則可以在實現resolveException方法時返回null。 API文件中對返回值的解釋: return a corresponding ModelAndView to forward to, or null for default processing.

四:《spring之aop》

什麼是aop

AOP(Aspect-OrientedProgramming,面向方面程式設計),可以說是OOP(Object-Oriented Programing,物件導向程式設計)的補充和完善。OOP允許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌程式碼往往水平地散佈在所有物件層次中,而與它所散佈到的物件的核心功能毫無關係。這種散佈在各處的無關的程式碼被稱為橫切(cross-cutting)程式碼,在OOP設計中,它導致了大量程式碼的重複,而不利於各個模組的重用。

spring思維導圖,讓spring更加簡單易懂

而AOP技術則恰恰相反,它利用一種稱為“橫切”的技術,剖解開封裝的物件內部,並將那些影響了多個類的公共行為封裝到一個可重用模組,並將其名為“Aspect”,即方面。所謂“方面”,簡單地說,就是將那些與業務無關,卻為業務模組所共同呼叫的邏輯或責任封裝起來,便於減少系統的重複程式碼,降低模組間的耦合度,並有利於未來的可操作性和可維護性。

aop使用場景

aop框架種類

  • AspectJ
  • JBoss AOP
  • Spring AOP

使用aop可以做的事情有很多。

  • 效能監控,在方法呼叫前後記錄呼叫時間,方法執行太長或超時報警。
  • 快取代理,快取某方法的返回值,下次執行該方法時,直接從快取裡獲取。
  • 軟體破解,使用AOP修改軟體的驗證類的判斷邏輯。
  • 記錄日誌,在方法執行前後記錄系統日誌。
  • 工作流系統,工作流系統需要將業務程式碼和流程引擎程式碼混合在一起執行,那麼我們可以使用AOP將其分離,並動態掛接業務。
  • 許可權驗證,方法執行前驗證是否有許可權執行當前方法,沒有則丟擲沒有許可權執行異常,由業務程式碼捕捉。

觀察一下傳統編碼方式與使用aop的區別

spring思維導圖,讓spring更加簡單易懂

核心概念

描述AOP常用的一些術語有通知(Adivce)、切點(Pointcut)、連線點(Join point)、切面(Aspect)、引入(Introduction)、織入(Weaving)、通知(Advice)等。

spring思維導圖,讓spring更加簡單易懂

簡單例子

相比xml配置,基於註解的方式更加簡潔方便。

@Aspect
public class TransactionDemo {
@Pointcut(value="execution(* com.yangxin.core.service.*.*.*(..))")
public void point(){
}
@Before(value="point()")
public void before(){
    System.out.println("transaction begin");
}

@AfterReturning(value = "point()")
public void after(){
    System.out.println("transaction commit");
}
@Around("point()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
    System.out.println("transaction begin");
    joinPoint.proceed();
    System.out.println("transaction commit");
         }
     }
     複製程式碼

在applicationContext.xml中配置。

<aop:aspectj-autoproxy />
  <bean id = "transactionDemo" class = "com.yangxin.core.transaction.TransactionDemo" /> 
  複製程式碼

spring aop原理

通過前面介紹可以知道:AOP 代理其實是由 AOP 框架動態生成的一個物件,該物件可作為目標物件使用。AOP 代理包含了目標物件的全部方法,但 AOP 代理中的方法與目標物件的方法存在差異:AOP 方法在特定切入點新增了增強處理,並回撥了目標物件的方法。

spring思維導圖,讓spring更加簡單易懂

Spring 的 AOP 代理由 Spring 的 IoC 容器負責生成、管理,其依賴關係也由 IoC 容器負責管理。因此,AOP 代理可以直接使用容器中的其他 Bean 例項作為目標,這種關係可由 IoC 容器的依賴注入提供。

aop開發時,其中需要程式設計師參與的只有 3 個部分:

  • 定義普通業務元件。
  • 定義切入點,一個切入點可能橫切多個業務元件。
  • 定義增強處理,增強處理就是在 AOP 框架為普通業務元件織入的處理動作。

為了理清關係,先來個類關係圖。

spring思維導圖,讓spring更加簡單易懂

兩種動態代理方式

Spring預設採取的動態代理機制實現AOP,當動態代理不可用時(代理類無介面)會使用CGlib機制。

Spring提供了兩種方式來生成代理物件: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據AdvisedSupport物件的配置來決定。預設的策略是如果目標類是介面,則使用JDK動態代理技術,否則使用Cglib來生成代理。

JDK動態代理

  • JDK動態代理主要涉及到java.lang.reflect包中的兩個類:Proxy和InvocationHandler。InvocationHandler是一個介面,通過實現該介面定義橫切邏輯,並通過反射機制呼叫目標類的程式碼,動態將橫切邏輯和業務邏輯編制在一起。
  • Proxy利用InvocationHandler動態建立一個符合某一介面的例項,生成目標類的代理物件。

CGLib動態代理

  • CGLib全稱為Code Generation
    Library,是一個強大的高效能,高質量的程式碼生成類庫,可以在執行期擴充套件Java類與實現Java介面,CGLib封裝了asm,可以再執行期動態生成新的class。和JDK動態代理相比較:JDK建立代理有一個限制,就是隻能為介面建立代理例項,而對於沒有通過介面定義業務方法的類,則可以通過CGLib建立動態代理。

知識擴充

通過上面的分析,大家是否有種熟悉的感覺,似乎和攔截器、過濾器的功能相似。那麼問題來了,aop與攔截器、過濾器是什麼關係。

先來回顧一下攔截器與過濾器。如下圖一網友的測試,在web.xml中註冊了TestFilter1和TestFilter2。然後在spring的配置檔案中配置了BaseInterceptor和TestInterceptor。得到的結果如下圖所示。從圖中可以看出,攔截器和過濾器都橫切了業務方法,看似符合aop的思想。

spring思維導圖,讓spring更加簡單易懂

Filter過濾器:攔截web訪問url地址。 Interceptor攔截器:攔截以 .action結尾的url,攔截Action的訪問。 Spring AOP攔截器:只能攔截Spring管理Bean的訪問(業務層Service)

spring思維導圖,讓spring更加簡單易懂

五:《spring之cache》

關於快取

快取是實際工作中非常常用的一種提高效能的方法。而在java中,所謂快取,就是將程式或系統經常要呼叫的物件存在記憶體中,再次呼叫時可以快速從記憶體中獲取物件,不必再去建立新的重複的例項。這樣做可以減少系統開銷,提高系統效率。

在增刪改查中,資料庫查詢佔據了資料庫操作的80%以上,而非常頻繁的磁碟I/O讀取操作,會導致資料庫效能極度低下。而資料庫的重要性就不言而喻了:

  • 資料庫通常是企業應用系統最核心的部分
  • 資料庫儲存的資料量通常非常龐大
  • 資料庫查詢操作通常很頻繁,有時還很複雜

在系統架構的不同層級之間,為了加快訪問速度,都可以存在快取

spring思維導圖,讓spring更加簡單易懂

spring cache特性與缺憾

現在市場上主流的快取框架有ehcache、redis、memcached。spring cache可以通過簡單的配置就可以搭配使用起來。其中使用註解方式是最簡單的。

spring思維導圖,讓spring更加簡單易懂

Cache註解

spring思維導圖,讓spring更加簡單易懂

從以上的註解中可以看出,雖然使用註解的確方便,但是缺少靈活的快取策略,

快取策略:

  • TTL(Time To Live ) 存活期,即從快取中建立時間點開始直到它到期的一個時間段(不管在這個時間段內有沒有訪問都將過期)
  • TTI(Time To Idle) 空閒期,即一個資料多久沒被訪問將從快取中移除的時間
    專案中可能有很多快取的TTL不相同,這時候就需要編碼式使用編寫快取。

條件快取

根據執行流程,如下@Cacheable將在執行方法之前( #result還拿不到返回值)判斷condition,如果返回true,則查快取;

 @Cacheable(value = "user", key = "#id", condition = "#id lt 10")  
 public User conditionFindById(final Long id) 
複製程式碼

如下@CachePut將在執行完方法後(#result就能拿到返回值了)判斷condition,如果返回true,則放入快取

   @CachePut(value = "user", key = "#id", condition = "#result.username ne 'zhang'")  
   public User conditionSave(final User user)
複製程式碼

如下@CachePut將在執行完方法後(#result就能拿到返回值了)判斷unless,如果返回false,則放入快取;(即跟condition相反)

    @CachePut(value = "user", key = "#user.id", unless = "#result.username eq 'zhang'")  
     public User conditionSave2(final User user)
複製程式碼

如下@CacheEvict, beforeInvocation=false表示在方法執行之後呼叫(#result能拿到返回值了);且判斷condition,如果返回true,則移除快取;

    @CacheEvict(value = "user", key = "#user.id", beforeInvocation = false, condition =                "#result.username ne 'zhang'") 
    public User conditionDelete(final User user)
複製程式碼
  • 小試牛刀,綜合運用:

            @CachePut(value = "user", key = "#user.id")
            public User save(User user) {
       users.add(user);
       return user;
       }
        @CachePut(value = "user", key = "#user.id")
         public User update(User user) {
       users.remove(user);
       users.add(user);
       return user;
      }
     @CacheEvict(value = "user", key = "#user.id")
     public User delete(User user) {
       users.remove(user);
       return user;
     }
     @CacheEvict(value = "user", allEntries = true)
     public void deleteAll() {
       users.clear();
      }
     @Cacheable(value = "user", key = "#id")
     public User findById(final Long id) {
       System.out.println("cache miss, invoke find by id, id:" + id);
       for (User user : users) {
           if (user.getId().equals(id)) {
               return user;
           }
       }
       return null;
       }
       複製程式碼

配置ehcache與redis

  • spring cache整合ehcache,spring-ehcache.xml主要內容:

     <dependency>
     <groupId>net.sf.ehcache</groupId>
     <artifactId>ehcache-core</artifactId>
     <version>${ehcache.version}</version>
    </dependency>
    
    
         <!-- Spring提供的基於的Ehcache實現的快取管理器 -->
          <!-- 如果有多個ehcacheManager要在bean加上p:shared="true" -->
     <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="classpath:xml/ehcache.xml"/>
         </bean>
        <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="ehcacheManager"/>
    <property name="transactionAware" value="true"/>
         </bean>
    
        <!-- cache註解,和spring-redis.xml中的只能使用一個 -->
            <cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true"/>
    複製程式碼
  • spring cache整合redis,spring-redis.xml主要內容:

     <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
     <version>1.8.1.RELEASE</version>
       </dependency>
     <dependency>
     <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
    </dependency>
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    </dependency>
    
    
    
    <!-- 注意需要新增Spring Data Redis等jar包 -->
    <description>redis配置</description>
    
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
     <property name="maxIdle" value="${redis.pool.maxIdle}"/>
    <property name="maxTotal" value="${redis.pool.maxActive}"/>
    <property name="maxWaitMillis" value="${redis.pool.maxWait}"/>
    <property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/>
    <property name="testOnReturn" value="${redis.pool.testOnReturn}"/>
    </bean>
    
       <!-- JedisConnectionFactory -->
        <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="hostName" value="${redis.master.ip}"/>
    <property name="port" value="${redis.master.port}"/>
    <property name="poolConfig" ref="jedisPoolConfig"/>
    </bean>
    
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
     p:connectionFactory-ref="jedisConnectionFactory">
     <property name="keySerializer">
       <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>
     </property>
     <property name="valueSerializer">
       <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
     </property>
     <property name="hashKeySerializer">
       <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
     </property>
     <property name="hashValueSerializer">
       <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
    </property>
    </bean>
    
     <!--spring cache-->
     <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
     c:redisOperations-ref="redisTemplate">
    <!-- 預設快取10分鐘 -->
     <property name="defaultExpiration" value="600"/>
     <property name="usePrefix" value="true"/>
     <!-- cacheName 快取超時配置,半小時,一小時,一天 -->
    <property name="expires">
       <map key-type="java.lang.String" value-type="java.lang.Long">
           <entry key="halfHour" value="1800"/>
           <entry key="hour" value="3600"/>
           <entry key="oneDay" value="86400"/>
           <!-- shiro cache keys -->
           <entry key="authorizationCache" value="1800"/>
           <entry key="authenticationCache" value="1800"/>
           <entry key="activeSessionCache" value="1800"/>
       </map>
    </property>
    </bean>
    <!-- cache註解,和spring-ehcache.xml中的只能使用一個 -->
       <cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true"/>
       複製程式碼

專案中註解快取只能配置一個,所以可以通過以下引入哪個配置檔案來決定使用哪個快取。

<import resource="classpath:spring/spring-ehcache.xml"/>
<!-- <import resource="classpath:spring/spring-redis.xml"/>--> 
複製程式碼

當然,可以通過其他配置搭配使用兩個快取機制。比如ecache做一級快取,redis做二級快取。

spring思維導圖,讓spring更加簡單易懂

更加詳細的使用與配置,可以參考專案中spring-shiro-training中有關spring cache的配置。

推薦一個交流學習群:478030634 裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化這些成為架構師必備的知識體系。還能領取免費的學習資源,目前受益良多:

spring思維導圖,讓spring更加簡單易懂


相關文章