《Spring in action 4》(一)初識Spring

初識Spring

莫道君行早,更有早行人

本篇主要是簡單的嘗試一下Spring的兩大功能,來感覺一下Spring的強大,後面將進行更加詳細的介紹。java

spring的兩大功能

​ 咱們都知道,Spring兩大核心功能就是控制反轉/依賴注入、面向切面編程。下面介紹一下兩大功能。git

IoC/DI

Don't call me , I will call you!

控制反轉(Inversion of Control)/依賴注入(Dependency Injection),簡稱IoC/DI.正則表達式

控制反轉不是一種技術,而是一種設計思想:將原來程序須要什麼對象本身建立 轉變爲 須要什麼對象向IoC容器獲取,建立對象的工做由原來程序自身控制,反轉到了由IoC容器進行建立。把相關的控制權進行了反轉,反轉給了Spring IoC容器。spring

DI:Dependency Injection。即依賴注入。對象(組件)之間的依賴關係由IoC容器來進行決定。express

好比:編程

在UserController中須要調用UserService(暫不考慮接口設計)。則在UserController中須要其自身經過new來建立UserService對象。網絡

以下:學習

UserService:測試

public class UserService{

    private PrintStream printStream;
    
    public UserService(PrintStream printStream){
        this.printStream = printStream;
    }
    public void sayHello(){
        printStream.println("sayHello!")
    }
}

UserController:this

public class UserController{
    private UserService userService;
    
    public UserController(){
        this.userService = new UserService(System.out);
    }
    
    public void sayHi(){
        userService.sayHello();
    }    
}

​ 在Spring中,程序的對象控制權由其自身反轉到了Spring容器,也就是不須要應用程序來new對象。既然不須要應用程序自身來建立Bean了,那麼程序在運行的過程當中,Bean從何而來呢?此時就是DI的show time了。

​ Spring中的DI正是來實現IoC的一種方式:Spring容器負責維護對象(Bean)之間的依賴關係,並經過DI來向對象中注入其所依賴的對象。

Xml方式

下面使用Spring的方式來設計:

public class UserService{
    
    private PrintStream printStream;
    
    public UserService(PrintStream printStream){
        this.printStream = printStream;
    }
    
    public void sayHello(){
        printStream.println("sayHello!")
    }
}

public class UserController{
    
    private UserService userService;
    
    public UserController(UserService userService){
        this.userService = userService;
    }
    public void sayHi(){
        userService.sayHello();
    }
}

spring.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="com.ooyhao.spring.bean.UserService">
        <constructor-arg value="#{T(System).out}"/>
    </bean>

    <bean id="UserController" class="com.ooyhao.spring.bean.UserController">
        <constructor-arg ref="userService"/>
    </bean>
</beans>

測試類:

public void testXml(){
      ClassPathXmlApplicationContext ctx = 
                          new ClassPathXmlApplicationContext("spring.xml");
      UserController userController = ctx.getBean(UserController.class);
      userController.sayHi();
}

​ 若是須要面向接口設計的話,接口因爲沒法實例化,因此在編碼的時候必須指定具體的實現類,如此一來,致使沒法自由動態的切換實現類,耦合度過高。而Spring xml方式的話,實現類鬆耦合,簡化了開發,後期若是須要修改的話,直接修改xml文件,不用修改代碼。

Java配置類方式

SpringConfig配置類:

public class SpringConfig{
  
      @Bean
      public UserService userService(){
          UserService userService = new UserService(System.out);
          return userService;
      }
  
      @Bean
      public UserController userController(){
          UserController userController = new UserController(userService());
          return userController;
      }
  
}

測試類:

public void testJavaConfig(){
      AnnotationConfigApplicationContext ctx 
                  = new AnnotationConfigApplicationContext(SpringConfig.class);
      UserController userController = ctx.getBean(UserController.class);
      userController.sayHi();
}

::: tip

​ 確實Spring xml文件實現了鬆耦合,可是實際項目中能夠發現,每每xml不多修改。因此,SpringBoot又主張Java配置類的方式,可是Java配置類的絕大部分都是由Spring xml轉化過來的。因此,不論是Java配置類方式仍是Spring xml文件方式都有必要了解,固然我以爲,因爲爲了後期更好的學習和使用SpringBoot,能夠以Java配置類方式爲主。

:::

AOP

參考自:阿古拉斯啦啦 http://www.javashuo.com/article/p-hfyrskjs-mu.html

​ Aop:是Aspect oriented Programming的縮寫,即面向切面編程。經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP(面向切面編程)是OOP(面向對象編程)的一種補充,而不是一種替代品。利用AOP能夠對業務邏輯的各個部分進行隔離,從而下降各個模塊之間的耦合度,簡化維護。常見使用AOP的場景:事務控制,日誌管理,權限控制等等。

Aop簡單介紹

下面先用幾張圖熟悉一下AOP是怎麼回事。(圖片來源於網絡)

在這裏插入圖片描述

  1. 從圖中能夠看出:不論是 【獲取活動相關數據】仍是 【根據條件活動獎勵發放】都須要先【檢測活動有效性】和【檢測活動是否須要登陸】這兩步操做。

在這裏插入圖片描述

  1. 能夠將【檢測活動有效性】和【檢測活動是否須要登陸】兩部操做封裝到一個方法,而後在兩個不一樣的業務中進行調用,可是這樣雖然重用了代碼,可是仍是將兩步不是必須耦合在一塊兒的代碼耦合在了一塊兒。

在這裏插入圖片描述

  1. 而第三幅圖則使用了AOP的思想,將【檢測活動有效性】和【檢測活動是否須要登陸】兩個操做封裝到一個單獨的類(切面)。只須要在須要執行的地方,進行切入便可達到前面同樣的效果。這樣最大程度的下降了模塊之間的耦合度。

Aop中的術語

  • Aspect(切面): Aspect 聲明相似於 Java 中的類聲明,在 Aspect 中會包含着一些 Pointcut 以及相應的 Advice。
  • Joint point(鏈接點):表示在程序中明肯定義的點,典型的包括方法調用,對類成員的訪問以及異常處理程序塊的執行等等,它自身還能夠嵌套其它 joint point。
  • Pointcut(切點):表示一組 joint point,這些 joint point 或是經過邏輯關係組合起來,或是經過通配、正則表達式等方式集中起來,它定義了相應的 Advice 將要發生的地方。
  • Advice(加強):Advice 定義了在 Pointcut 裏面定義的程序點具體要作的操做,它經過 before、after 和 around 來區別是在每一個 joint point 以前、以後仍是代替執行的代碼。
  • Target(目標對象):織入 Advice 的目標對象.。
  • Weaving(織入):將 Aspect 和其餘對象鏈接起來, 並建立 Adviced object 的過程

舉一個容易理解的例子

​ 看完了上面的理論部分知識, 我相信仍是會有很多朋友感受到 AOP 的概念仍是很模糊, 對 AOP 中的各類概念理解的還不是很透徹. 其實這很正常, 由於 AOP 中的概念是在是太多了, 我當時也是花了老大勁才梳理清楚的.
下面我以一個簡單的例子來比喻一下 AOP 中 Aspect, Joint point, Pointcut 與 Advice之間的關係.

​ 讓咱們來假設一下, 從前有一個叫爪哇的小縣城, 在一個月黑風高的晚上, 這個縣城中發生了命案. 做案的兇手十分狡猾, 現場沒有留下什麼有價值的線索. 不過萬幸的是, 剛從隔壁回來的老王剛好在這時候無心中發現了兇手行兇的過程, 可是因爲天色已晚, 加上兇手蒙着面, 老王並無看清兇手的面目, 只知道兇手是個男性, 身高約七尺五寸. 爪哇縣的縣令根據老王的描述, 對守門的士兵下命令說: 凡是發現有身高七尺五寸的男性, 都要抓過來審問. 士兵固然不敢違背縣令的命令, 只好把進出城的全部符合條件的人都抓了起來.

​ 來讓咱們看一下上面的一個小故事和 AOP 到底有什麼對應關係.
首先咱們知道, 在 Spring AOP 中 Joint point 指代的是全部方法的執行點, 而 point cut 是一個描述信息, 它修飾的是 Joint point, 經過 point cut, 咱們就能夠肯定哪些 Joint point 能夠被織入 Advice. 對應到咱們在上面舉的例子, 咱們能夠作一個簡單的類比, Joint point 就至關於 爪哇的小縣城裏的百姓,pointcut 就至關於 老王所作的指控, 即兇手是個男性, 身高約七尺五寸, 而 Advice 則是施加在符合老王所描述的嫌疑人的動做: 抓過來審問.

爲何能夠這樣類比呢?

  • Join point : 爪哇的小縣城裏的百姓: 由於根據定義, Join point 是全部可能被織入 Advice 的候選的點, 在 Spring AOP中, 則能夠認爲全部方法執行點都是 Joinpoint. 而在咱們上面的例子中, 命案發生在小縣城中, 按理說在此縣城中的全部人都有多是嫌疑人.
  • Pointcut :男性, 身高約七尺五寸: 咱們知道, 全部的方法(joinpoint) 均可以織入 Advice, 可是咱們並不但願在全部方法上都織入 Advice, 而 Pointcut 的做用就是提供一組規則來匹配joinpoint, 給知足規則的 joinpoint 添加 Advice. 同理, 對於縣令來講, 他再昏庸, 也知道不能把縣城中的全部百姓都抓起來審問, 而是根據兇手是個男性, 身高約七尺五寸, 把符合條件的人抓起來. 在這裏 兇手是個男性, 身高約七尺五寸 就是一個修飾謂語, 它限定了兇手的範圍, 知足此修飾規則的百姓都是嫌疑人, 都須要抓起來審問.
  • Advice :抓過來審問, Advice 是一個動做, 即一段 Java 代碼, 這段 Java 代碼是做用於 point cut 所限定的那些 Joint point 上的. 同理, 對比到咱們的例子中, 抓過來審問 這個動做就是對做用於那些知足 男性, 身高約七尺五寸 的爪哇的小縣城裏的百姓.
  • Aspect::Aspect 是 pointcut 與 Advice 的組合, 所以在這裏咱們就能夠類比: 「根據老王的線索, 凡是發現有身高七尺五寸的男性, 都要抓過來審問」 這一整個動做能夠被認爲是一個 Aspect.

在這裏插入圖片描述

其餘的一些內容

AOP中的Joinpoint能夠有多種類型:構造方法調用,字段的設置和獲取,方法的調用,方法的執行,異常的處理執行,類的初始化。也就是說在AOP的概念中咱們能夠在上面的這些Joinpoint上織入咱們自定義的Advice,可是在Spring中卻沒有實現上面全部的joinpoint,確切的說,Spring只支持方法執行類型的Joinpoint

Advice 的類型

  • before advice, 在 join point 前被執行的 advice. 雖然 before advice 是在 join point 前被執行, 可是它並不可以阻止 join point 的執行, 除非發生了異常(即咱們在 before advice 代碼中, 不能人爲地決定是否繼續執行 join point 中的代碼)
  • after return advice, 在一個 join point 正常返回後執行的 advice
  • after throwing advice, 當一個 join point 拋出異常後執行的 advice
  • after(final) advice, 不管一個 join point 是正常退出仍是發生了異常, 都會被執行的 advice.
  • around advice, 在 join point 前和 joint point 退出後都執行的 advice. 這個是最經常使用的 advice.
  • introductionintroduction能夠爲原有的對象增長新的屬性和方法。

在Spring中,經過動態代理和動態字節碼技術實現了AOP,這些內容,咱們將在之後進行講解。

下面分別使用XML和Java配置類實現AOP

XmlAOP方式

目標類:

public class Person {

    public String sayHello(){
        System.out.println("Person say Hello!");
        return "sayHelloMethod";
    }

    public String sayBye(){
        System.out.println("Person say ByeBye!");
        return "sayByeMethod";
    }
}

切面類:

public class XmlLoggerAspect {


    public void before(){
        System.out.println("--->before");
    }


    public void after(){
        System.out.println("--->after");
    }

    public void afterReturning(Object returnVal){
        System.out.println("--->afterReturning : " + returnVal);
    }

    public void afterThrowing(Exception exception){

        System.out.println("--->afterTrowing:"+exception.getMessage());
    }

    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("--->around before");

        Object proceed = joinPoint.proceed();
        System.out.println("around result : "+proceed);
        System.out.println("--->around after");
        return proceed;
    }
}

xml配置文件:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--業務類Target-->
    <bean id="person" class="com.ooyhao.spring.aop.Person"/>

    <!--切面類-->
    <bean id="loggerAspect" class="com.ooyhao.spring.aspect.XmlLoggerAspect"/>

    <aop:config>
        <aop:aspect ref="loggerAspect">
            <!--public String com.ooyhao.spring.aop.Person.sayHello()
                * String com.ooyhao.spring.aop.Person.sayHello()
                * com.ooyhao.spring.aop.Person.sayHello()
                * *.sayHello()
                * *.say*()
                * *.say*(..)
            -->
            <aop:pointcut id="pointCut" expression="execution(* *.say*(..))"/>
            <aop:before method="before" pointcut-ref="pointCut"/>
            <aop:after method="after" pointcut-ref="pointCut"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="returnVal"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="exception"/>
            <aop:around method="around" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>
</beans>

測試:

@Test
public void testXmlAop(){

  ClassPathXmlApplicationContext context
    = new ClassPathXmlApplicationContext("spring-aop.xml");
  Person bean = context.getBean(Person.class);
  bean.sayHello();
  System.out.println("=================");
  bean.sayBye();
  /**
  *
  *              --->before
  *             --->around before
  *             Person say Hello!
  *             around result : sayHelloMethod
  *             --->around after
  *             --->afterReturning : sayHelloMethod
  *             --->after
  *             =================
  *             --->before
  *             --->around before
  *             Person say ByeBye!
  *             around result : sayByeMethod
  *             --->around after
  *             --->afterReturning : sayByeMethod
  *             --->after
  */

  //sayHello出現 int i = 1/0;時
  /**
    *
    *          --->before
    *          --->around before
    *          Person say Hello!
    *          --->afterTrowing:/ by zero
    *          --->after
    *
    *          java.lang.ArithmeticException: / by zero
    *          at com.ooyhao.spring.aop.Person.sayHello(Person.java:7)
    *
    * */
}

由上測試結果能夠看出:

  • 正常執行的執行過程

    before -- > around before --> target method --> around after --> afterReturning --> after

  • 出現異常的執行過程

    before --> around before --> target method --> afterTrowing --> after

Java配置類方式

java配置方式的切面:

@Aspect
@Component
@EnableAspectJAutoProxy
public class ConfigLoggerAspect {

    @Pointcut("execution(**.say*()))")
    public void pointCut(){}

    @Before("pointCut()")
    public void before(){
        System.out.println("--->before");
    }

    @After("pointCut()")
    public void after(){
        System.out.println("--->after");
    }

    @AfterReturning(value = "pointCut()", returning = "returnVal")
    public void afterReturning(Object returnVal){
        System.out.println("--->afterReturning : " + returnVal);
    }

    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void afterThrowing(Exception exception){
        System.out.println("--->afterTrowing:"+exception.getMessage());
    }

    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("--->around before");
        Object proceed = joinPoint.proceed();
        System.out.println("around result : "+proceed);
        System.out.println("--->around after");
        return proceed;
    }
}
  • @Aspect: 定義爲一個切面
  • @Component:定義爲一個Spring組件
  • @EnableAspectJAutoProxy:開啓Aop自動代理模式

Java配置類:

@ComponentScan(basePackages = "com.ooyhao.spring")
public class AopConfig {

    @Bean
    public Person person(){
        return new Person();
    }
}
  • @ComponentScan(basePackages = "com.ooyhao.spring") : 將前面的切面進行掃描成組件。

測試類:

@Test
public void testJavaConfigAop(){

  AnnotationConfigApplicationContext context
    = new AnnotationConfigApplicationContext(AopConfig.class);
  Person bean = context.getBean(Person.class);
  bean.sayHello();
  bean.sayBye();

}

:::tip

提示:

​ 前面均使用的是AspectJ表達式,這樣能夠定位到有必定規律的目標方法,下降程序耦合,可是操做不是特別靈活,我的比較使用註解方式,能夠指定到某一個目標方法。

@pointcut("@annotation(com.sample.security.AdminOnly)") // 匹配註解有AdminOnly註解的方法

:::

源碼地址: https://gitee.com/ooyhao/JavaRepo_Public/tree/master/Spring-in-Action

最後

若是以爲不錯的話,那就關注一下小編哦!一塊兒交流,一塊兒學習

相關文章
相關標籤/搜索