Spring之AOP

     上兩週寫了兩篇Spring IOC相關的文章:Spring使用之IOCSpring之IOC補充主要講Spring的核心功能IOC。Spring做爲一個輕量級容器,其核心功能就是IOC。但也不只僅是IOC,它提供的另外一個核心功能就是AOP(Aspect Oriented Programming)即面向切面編程。spring

1、Spring AOP概述

(一)、描述

    面向對象編程已經給咱們提供了對現實業務很好的抽象,也實現數據的封裝,既然咱們已經有OOP,那麼咱們爲何還須要AOP呢??你想想這樣一個業務場景,假如咱們銀行有500個業務,假若有一天有這麼一個需求,要統計天天全部業務被操做的總數??你怎麼實現??每一個業務邏輯方法加上一個統計??那若是咱們有成千上萬的業務呢??你每一個業務方法都去修改,這樣的工做量會有多大,何況統計好像不是業務,不該該寫在業務方法裏面,還有你修改了全部業務方法,那是否是全部的業務都得從新測試,有了AOP這個問題就迎刃而解。express

Spring AOP就如同在選定邏輯方法上統一織入一段邏輯,咱們可織入統計的邏輯,也可作安全驗證。編程


(二)、切面編程相關的概念

一、 Joinpoint 鏈接點(《Spring 實戰》中的翻譯),可被織入操做的系統點即爲Joinpoint,便可被加入邏輯代碼的點,常見Joinpoint類型:安全

(1)、方法調用(方法調用處,調用對象上的執行點)。bash

(2)、方法調用執行(方法內,被調用方法邏輯執行時點,Spring僅支持該類JoinPoint)。app

(3)、構造方法調用。post

(4)、類初始化(靜態類型,靜態塊初始化時點)測試

二、PointCut(切點) 用來描述一組Joinpoint,指定系統中符合條件的一組Joinpoint。spring一般經過AspectJ的切點表達式來描述。ui

在Spring中全部的方法方法均可以做爲鏈接點,可是咱們不須要統計全部方法的調用次數,咱們可能只須要統計用戶業務的總數,這是咱們就得經過表達式來指定咱們須要統計的業務方法,經過表達式指定的的鏈接點就是切點,咱們要織入的統計代碼就應用在這些切點上。this

三、Advice(通知) :織入PointCut的邏輯內容 。

顯然咱們上面的業務需求中須要作的是統計,Advice根據織入內容在PointCut執行位置又可分爲一下幾種

(1)、Before Advice 指定PointCut方法執行以前執行。

(2)、After Advice 指定位置以後執行,After Advice包括後面三種,執行位置以下方法展現(After Advice、After Returning Advice 、After throwing Advice)。

執行位置分別對應以下:

public void mockMethod(){
	//before Advice 織入邏輯執行點
	try{
            ...........
	    retrun;
	    //after Returning Advice 織入邏輯執行點
	}catch(Exception e){
	    //After throwing Advice 織入邏輯執行點
	}finally{
	    //After Advice 織入邏輯執行點
	}
}複製代碼

 (3)AroundAdvice ,方法執行先後,這是比較特殊的通知,以前的兩種通知都不能阻止調用業務方法,但這個通知可阻止對業務方法的調用,好比,咱們可以使用AroudAdvice在邏輯方法前加一道安全驗證,驗證不經過則不讓調相應的邏輯方法。(這裏不理解沒關係,後面會給例子)。

2、Spring切面編程初印象

接下來,咱們就上面說的銀行的例子來寫個demo,見識見識AOP。如今咱們須要統計業務辦理數,咱們可直接在業務方法執行前作一個統計,因此咱們通知使用Before Advice,Spring使用切面也又兩種方式:XML配置、註解。

(一)、使用XML配置方式

使用AOP除了以前第一篇文章裏說的須要引進Spring核心包外,還須要引進新的兩個包

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>${org.springframework.version}</version>
</dependency>

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.6.8</version>
</dependency>
複製代碼

項目結構以下


咱們經過一個靜態的int變量來統計全部業務的使用數,CountBusiness以下:

package cn.springstudy.inspectadvice;

import org.springframework.aop.BeforeAdvice;

public class CountBusiness{

    public static int count;

    public void beforeBusinessCount(){
        count++;
    }
}
複製代碼

咱們建全部銀行業務都放在cn.springstudy.bank下,咱們如今加上兩個銀行業務處理類:

package cn.springstudy.bank;

//帳號業務
public class AccountBusiness {

    //更改用戶帳號綁定電話
    public void chgAccountPhone(){
        System.out.println("Change Account Phone");
    }
    //開通新帳號
    public void createNewAccount(){
        System.out.println("Create New Account");
    }
    //更改用戶信息
    public void chgAccountInfo(){
        System.out.println("Change Account Info Success");
    }
    //銷戶
    public void delAccount(){
        System.out.println("Delete Account");
    }
}複製代碼

package cn.springstudy.bank;

//轉帳業務
public class TransferBusiness {

    //轉帳
    public void transfer(){
        System.out.println("Transfer A Account To B Account");
    }
}
複製代碼

接下來,須要在配置文件中配置

<?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:aop="http://www.springframework.org/schema/aop"
       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">
    <!-- 使用Spring AOP -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
    <bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
    <!--Advice類  -->
    <bean id="countBusiness" class="cn.springstudy.inspectadvice.CountBusiness"></bean>
    <!--AOP配置 -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect ref="countBusiness">
<!--切點,表示執行cn.springstudy.bank下的任意類的任意方法,expression中的表單
       是即爲AspectJ表達式,指定須要被織入邏輯的切點,這裏表達式可先不深究,後面會講解 -->
            <aop:pointcut id="bankbusinesspointcut" 
                 expression="execution(* cn.springstudy.bank.*.*(..))">
            </aop:pointcut>
<!--配置BeforeAdvice 執行織入邏輯爲bean 
      countBusiness的beforeBusinessCount方法,應用在bankbusinesspointcut切點上-->
            <aop:before method="beforeBusinessCount" 
                   pointcut-ref="bankbusinesspointcut" >
            </aop:before>
        </aop:aspect>
    </aop:config>
</beans>複製代碼

接下來就能夠作測試了

package cn.springstudy.spring;

import cn.springstudy.bank.AccountBusiness;
import cn.springstudy.bank.TransferBusiness;
import cn.springstudy.inspectadvice.CountBusiness;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringStudy {

    public static void main(String arg[]){
        //方式一
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");;
        AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
        //辦理修改帳號信息業務
        accountBusiness.chgAccountInfo();
        //辦理修改綁定電話業務
        accountBusiness.chgAccountPhone();
        TransferBusiness transferBusiness = (TransferBusiness) classPathXmlApplicationContext.getBean("transferBusiness");
         //辦理轉帳業務
        transferBusiness.transfer();
        System.out.println(CountBusiness.count);
    }
}複製代碼

OK,執行結構如咱們想的同樣,統計出來的業務辦理量是3:


能夠看到整個AOP功能的使用仍是很清爽的,咱們只是增長了一個統計的類CountBusiness,還有增長了點配置,能夠看出Spring的理念之一:非侵入式。有一天你不想用Spring了,那些業務類、CountBusiness依舊是Java類,仍是可使用。

(二)、使用註解。

咱們只需建CountBusiness類作下修改

package cn.springstudy.inspectadvice;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.BeforeAdvice;
//這是一個切面類
@Aspect
public class CountBusiness implements BeforeAdvice {
    public static int count;
    //定義切點
    @Pointcut("execution(* cn.springstudy.bank.*.*(..))")
    public void declarePointCut(){}
    //before Advice 執行邏輯
    @Before("declarePointCut()")
    public void beforeBusinessCount(){
        count++;
    }
}
複製代碼

此時咱們的XML配置就不在須要AOP配置了,只需將CountBusiness配置成一個普通Bean便可,配置文件改成一下,便可執行測試方法,執行結果同上。

<?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:c="http://www.springframework.org/schema/c"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/c http://www.springframework.org/schema/c/spring-c.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
    <bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
    <!--切面類-->
    <bean id="countBusiness" class="cn.springstudy.inspectadvice.CountBusiness"></bean>
</beans>
複製代碼

3、更多Spring AOP知識

(一)、Around Advice

Around Advice:前面咱們說過使用Around Advice能夠可阻止對業務方法的調用。接下來咱們就來體驗一下。咱們在操做用戶業務前面加個驗證。

咱們增長一個用戶類:

package cn.springstudy.vo;

public class User {

    private String name;
    private String passWord;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
}複製代碼

再更改下帳號業務的類,辦理業務,順便把要辦理的用戶傳過來

package cn.springstudy.bank;

import cn.springstudy.vo.User;
//帳號業務
public class AccountBusiness {

    //更改用戶帳號綁定電話
    public void chgAccountPhone(User user){
        System.out.println("Change Account Phone");
    }

    //開通新帳號
    public void createNewAccount(User user){
        System.out.println("Create New Account");
    }

    //更改用戶信息
    public void chgAccountInfo(User user){
        System.out.println("Change Account Info Success");
    }

    //銷戶
    public void delAccount(User user){
        System.out.println("Delete Account");
    }
}複製代碼

接下來是Advice 類:

package cn.springstudy.inspectadvice;

import cn.springstudy.vo.User;
import org.aspectj.lang.ProceedingJoinPoint;

public class Authenticate {

    public void aroundAdvice(ProceedingJoinPoint joinPoint){
        //拿到調用切點方法的參數
        Object[] arg = joinPoint.getArgs();
        if (arg[0] instanceof User){
            //若是用戶名是zhangsan,不讓調切點方法,直接return
            if ("zhangsan".equals(((User)arg[0]).getName())){
                System.out.println("zhangsan 異經常使用戶,不讓辦理業務");
                return;
            }
            try {
                joinPoint.proceed();//執行切點方法,即邏輯方法
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
    }
}複製代碼

配置文件

<?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:c="http://www.springframework.org/schema/c"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/c http://www.springframework.org/schema/c/spring-c.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
    <bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
    <bean id="authenticate" class="cn.springstudy.inspectadvice.Authenticate"></bean>

    <aop:config>
        <aop:aspect ref="authenticate">
            <aop:around method="aroundAdvice" pointcut-ref="authenticatepointcut"></aop:around>
        </aop:aspect>
    </aop:config>

</beans>
複製代碼

接下來就能夠測試了

public class SpringStudy {
    public static void main(String arg[]){
        AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
        User user = new User();
        user.setName("zhangsan");
        user.setPassWord("123456");
        //zhangsan 過來辦理更改用戶信息的業務
        accountBusiness.chgAccountInfo(user);  
    }
}
複製代碼

執行結果,能夠看到chgAccountInfo方法裏東西並無執行。


若是是李四辦理業務呢,沒錯李四能夠正常調用業務。

public class SpringStudy {
    public static void main(String arg[]){
        AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
        User user = new User();
        user.setName("lisi");
        user.setPassWord("123456");
        //zhangsan 過來辦理更改用戶信息的業務
        accountBusiness.chgAccountInfo(user);  
    }
}複製代碼


(二)、ASpectJ表達式

前面說到Apring AOP支持部分SpectJ切點語言來指定要應用切面的切點,那麼ASpectJ切點語言到底是怎樣的語法。

支持的部分指示符包括:execution、within、this、target、args

一、execution是最經常使用的了,用來指定切點方法,接下來就看看以前咱們的配置:

execution(* cn.springstudy.bank.*.*(..))複製代碼

(1)、其中第一個*表示返回類型能夠是任意類型,固然咱們也能夠直接指定,好比:

execution(int cn.springstudy.bank.*.*(..))        表示匹配的返回類型爲int的方法。execution(cn.springstudy.vo.User cn.springstudy.bank.AccountBusiness.*(..))      匹配的是返回類型爲User的方法。

(2)、第二個*號表示任意類,第三個*號表示任意方法,括號裏面兩個點表示參數能夠是任意個數、任意類型。

因此execution(* cn.springstudy.bank.*.*(..))這個表達式表示的就是cn.springstudy.bank包下任意類,類中任意方法,返回類型,和參數都不作限定,因此指定的切點就是AccountBusiness、TransferBusiness這兩個類中的全部方法。



二、within匹配因此持有指定註解類型內的方法

within(cn.springstudy.bank.*)    效果同   execution(* cn.springstudy.bank.*.*(..))

within(cn.springstudy.bank..*) 其中..表示包括子包。因此這個表示cn.springstudy.bank下,包括子包中的全部類下的方法。

三、args:使用「args(參數類型列表)」匹配當前執行的方法傳入的參數爲指定類型的執行方法,例:args(cn.springstudy.vo.User) 表示參數爲User的方法。

四、target,用於匹配類型,例:target(cn.springstudy.bank.VIPBusiness)實現了VIPBusiness接口的類都會被匹配上

以上的全部指示器還能夠經過 and 和 or這些邏輯運算符結合在一塊兒,例:表示匹配cn.springstudy.bank包下類的方法,且方法參數類型爲User

within(cn.springstudy.bank.*) and args(cn.springstudy.vo.User)複製代碼

五、 this(),這個我測試,沒看出跟target有什麼區別,暫時不清楚跟target的區別。


未完,待續...............

相關文章
相關標籤/搜索