AOP——SpringAOP

1、AOP:java

是對OOP編程方式的一種補充。翻譯過來爲「面向切面編程」。程序員

能夠理解爲一個攔截器框架,可是這個攔截器會很是武斷,若是它攔截一個類,那麼它就會攔截這個類中的全部方法。如對一個目標列的代理,加強了目標類的全部方法。spring

兩個解決辦法:express

1.不優雅的作法:編程

在添加加強時,根據方法名去判斷,是否添加加強,可是這樣就得一直去維護這個加強類。後端

2.面向切面:框架

將加強類和攔截條件組合在一塊兒,而後將這個切面配置到 ProxyFactory 中,從而生成代理。ide

2、AOP 和 切面的關係工具

1.類比於 OOP 和 對象,AOP 和 切面就是這樣的一種關係。性能

2.也能夠將 切面 當作是 AOP 的一個工具。

3、幾個概念

切面(Advisor):是AOP中的一個術語,表示從業務邏輯中分離出來的橫切邏輯,好比性能監控,日誌記錄,權限控制等。

這些功能均可以從核心的業務邏輯中抽離出去。能夠解決代碼耦合問題,職責更加單一。封裝了加強和切點。

加強(Advice):加強代碼的功能的類,橫切到代碼中。

目標:目標方法(JDK代理)或目標類(CGLIB代理)

代理:JDK代理,CGLIB代理。或是經過 ProxyFactory 類生產。

切點:經過一個條件來匹配要攔截的類,這個條件稱爲切點。如攔截全部帶 Controller 註解的類。加強的條件。

鏈接點:做爲加強方法的入參,能夠獲取到目標方法的信息。

4、歸納爲一張圖

5、加強

1.Weaving(織入):對方法進行加強

(1)前置加強(BeforeAdvice):在目標方法前調用。

(2)後置加強(AfterAdvice):在目標方法後調用。

(3)環繞加強(AroundAdvice):將 Before 和 After ,甚至拋出加強和返回加強合到一塊兒。

(4)返回加強(AfterReturningAdvice):在方法返回結果後執行,該加強能夠接收到目標方法返回結果。

(5)拋出加強(AfterThrowingAdvice):在目標方法拋出對應的類型後執行,能夠接收到對應的異常信息。

2.Introduction(引入):對類進行加強

(1)引入加強(DeclareParentsAdvice):想讓程序在運行的時候動態去實現某個接口,須要引入加強。

6、SpringAOP

1.編程式

(1)前置加強,須要實現:MethodBeforeAdvice 接口

加強類:

/**
 * @author solverpeng
 * @create 2016-07-27-11:07
 */
public class CarBeforeAdvice implements MethodBeforeAdvice{
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("before");
    }
}

測試方法:

@Test
public void test03() {
  ProxyFactory proxyFactory = new ProxyFactory();
  proxyFactory.setTarget(new Car());
  proxyFactory.addAdvice(new CarBeforeAdvice());
  Wheel carProxy = (Wheel)proxyFactory.getProxy();
  carProxy.run();
}

(2)後置加強:實現 AfterReturningAdvice 接口

(3)環繞加強:實現 org.aopalliance.intercept.MethodInterceptor 接口,使用 Object result = methodInvocation.proceed(); 調用目標方法。在目標方法先後添加加強。

2.聲明式:Spring + AspectJ

開發步驟:

(1)定義切面類,將該切面類放入到 IOC 容器中

/**
 * @author solverpeng
 * @create 2016-07-27-13:10
 */
@Component
public class XMLLoggingAspect {

    public void beforeAdvice(JoinPoint point) {
        System.out.println("xml aspects logging before");
    }

    public void afterAdvice(JoinPoint point) {
        System.out.println("xml aspects logging after");
    }

    public void afterReturningAdvice(JoinPoint point, Object result) {
        System.out.println("xml aspects logging afterReturningAdvice");
    }

    public void afterThrowingAdvice(JoinPoint point, Exception e) {
        System.out.println("xml aspects logging afterThrowingAdvice");
    }

    public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable {
        Object result = point.proceed();
        System.out.println("xml aspects logging aroundAdvice");
        return result;
    }

}

(2)Spring AOP 配置都必須定義在 <aop:config>元素內部。

(3)在 <aop:config> 中,每一個切面都須要建立一個 <aop:aspect> 元素

(4)爲具體的切面實現引用後端的  bean 實例。

下面展現 前置加強、後置加強、環繞加強、返回加強、拋出加強 的一個例子:

<aop:config>
  <aop:aspect ref="XMLLoggingAspect">
    <aop:pointcut id="carPointcut" expression="execution(void run())"/>
    <aop:before method="beforeAdvice" pointcut-ref="carPointcut"/>
    <aop:after method="afterAdvice" pointcut-ref="carPointcut"/>
    <aop:after-returning method="afterReturningAdvice" pointcut-ref="carPointcut" returning="result"/>
    <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="carPointcut" throwing="e"/>
    <aop:around method="aroundAdvice" pointcut-ref="carPointcut"/>
  </aop:aspect>
</aop:config>

控制檯輸出:

xml aspects logging before
i am a car, i can run.
xml aspects logging aroundAdvice
xml aspects logging afterReturningAdvice
xml aspects logging after

基於聲明式的 Spring AspectJ 織入加強配置說明:

支持配置兩個級別的公共切點表達式,一個是針對某個切面的全部方法(定義在 <aop:aspect> 節點內),另外一個是針對全部切面(定義在<aop:config>節點內)。使用 pointcut-ref 來引入切點。

下面展現 引入加強 的一個例子:

對於引入加強,只須要在配置在 <aop:aspect> 節點下就能夠,不須要去切面類中添加任何屬性。

<aop:config>
  <aop:aspect ref="XMLLoggingAspect">
    <aop:declare-parents
          types-matching="com.nucsoft.spring.target.impl.Student"
           implement-interface="com.nucsoft.spring.target.Fly"
           default-impl="com.nucsoft.spring.target.impl.SuperMan"/>
  </aop:aspect>
</aop:config>

測試:

@Test
public void test04() {
  Person student = context.getBean(Student.class);
  System.out.println(student.say("james"));
  Fly fly = (Fly) student;
  fly.fly();
}

控制檯輸出:

hello,james
i am super man, i can fly.

基於聲明式的 Spring AspectJ 引入加強配置說明:

(1)引入加強是類級別的,因此不存在切點表達式。

(1)利用 <aop:declare-parents> 節點在 <aop:aspect> 內部聲明

(2)types-matching 屬性,要加強的目標類,這裏須要全類名。

(3)implement-interface 屬性:動態的加強類接口。

(4)default-impl 屬性:動態加強類接口的實現類。

3.註解:Spring + AspectJ

對切面類添加 @Aspect 註解,將切面類和目標類放入到 IOC 容器中,能夠經過 <context:component-scan base-package=""/> 進行掃描。

添加加強方法(包括加強類型和切點表達式,以及鏈接點)。

在 Spring Config 文件中添加 <aop:aspectj-autoproxy proxy-target-class="true"/>, proxy-target-class屬性,false 只能代理接口(JDK動態代理),true 代理類(CGLIB動態代理)

3.1 經過切點表達式(AspectJ execution)進行攔截

spring-config.xml

<context:component-scan base-package="com.nucsoft.spring"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>

Person 接口:

/**
 * Created by solverpeng on 2016/7/26.
 */
public interface Person {
    String say(String name);
}

Person 實現類 Student:

/**
 * @author solverpeng
 * @create 2016-07-26-17:55
 */
@Component
public class Student implements Person{
    @Override
    public String say(String name) {
        return "hello," + name;
    }

}

測試:

/**
 * @author solverpeng
 * @create 2016-07-26-18:15
 */
public class SpringTest {
    @Test
    public void test01() {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        Student student = context.getBean(Student.class);
        String james = student.say("james");
        System.out.println(james);
    }
}

 

(1)前置加強:關鍵字:@Before,JoinPoint,execution 切點表達式,表達式內容支持通配符

/**
 * @author solverpeng
 * @create 2016-07-26-17:42
 */
@Aspect
@Component
public class LoggingAspect {
    @Before("execution(String say(String))")
    public void before(JoinPoint point) {
        System.out.println("before");
    }
}

控制檯輸出:

before
hello,james

(2)後置加強:關鍵字:@After

/**
 * @author solverpeng
 * @create 2016-07-26-17:42
 */
@Aspect
@Component
public class LoggingAspect {
    @After("execution(String say(String))")
    public void afterAdvice(JoinPoint point) {
        System.out.println("after...");
    }
}

控制檯輸出:

after...
hello,james

(3)環繞加強:關鍵字:@Around,ProceedingJoinPoint

將 Student 類還原

切面類:

/**
 * @author solverpeng
 * @create 2016-07-26-17:42
 */
@Aspect
@Component
public class LoggingAspect {

    @Around("execution(String say(String))")
    public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable {
        before();
        Object result = point.proceed();
        after();
        return result;
    }
    private void before(){
        System.out.println("before");
    }

    private void after(){
        System.out.println("after");
    }

}

控制檯輸出:

before
after
hello,james

注意:

<1>.加強方法的返回值爲 Object 類型的,該返回值與目標方法返回值一致。

<2>.Object result = point.proceed(); 該 result 即爲目標方法執行後的返回值。

<3>.在環繞通知中須要明確調用 ProceedingJoinPoint 的 proceed() 方法來執行被代理的方法. 若是忘記這樣作就會致使通知被執行了, 但目標方法沒有被執行

<4>.環繞通知的方法須要返回目標方法執行以後的結果, 即調用 joinPoint.proceed(); 的返回值, 不然會出現空指針異常

(4)返回加強:關鍵字:@AfterReturning,returning,JoinPoint

/**
 * @author solverpeng
 * @create 2016-07-26-17:42
 */
@Aspect
@Component
public class LoggingAspect {
    @AfterReturning(value = "execution(String say(String))", returning = "str")
    public void aferRetruningAdvice(JoinPoint point, String str) {
        System.out.println("str:" + str);
        System.out.println("aferRetruningAdvice");
    }
}

控制檯輸出:

str:hello,james
aferRetruningAdvice
hello,james

(5)拋出加強:關鍵字:@AfterThrowing,throwing。注意:拋出的異常類型必須和切面拋出加強接收的異常類型相同或是其子類。

更改 Student 類,手動拋出一個異常:

/**
 * @author solverpeng
 * @create 2016-07-26-17:55
 */
@Component
public class Student implements Person{
    @Override
    public String say(String name) {
        throw new RuntimeException("exception");
    }
}

切面類:

/**
 * @author solverpeng
 * @create 2016-07-26-17:42
 */
@Aspect
@Component
public class LoggingAspect {

    @AfterThrowing(value = "execution(String say(String))", throwing = "e")
    public void AfterThrowingAdvice(JoinPoint point, Exception e) {
        String message = e.getMessage();
        System.out.println(message);
        System.out.println("AfterThrowingAdvice");
    }

}

控制檯輸出:

exception
AfterThrowingAdvice

3.2 經過切點註解表達式(AspectJ @annotation)進行攔截

開發步驟:

(1)定義註解類

/**
 * Created by solverpeng on 2016/7/27.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorityTag {}

(2)爲切面類中加強指定註解表達式

/**
 * Created by solverpeng on 2016/7/27.
 */
@Aspect
@Component
public class AuthorityAspect {
    @Before("@annotation(com.nucsoft.spring.annotation.AuthorityTag)")
    public void before(JoinPoint point) {
        System.out.println("authority before");
    }
}

(3)在目標類目標方法上標註註解

/**
 * @author xzsw
 * @create 2016-07-27-9:59
 */
@Component
public class Car implements Wheel{
    @AuthorityTag
    @Override
    public void run() {
        System.out.println("i am a car, i can run.");
    }
}

各類加強的使用:

(1)前置加強

上面介紹步驟的例子就是一個前置加強。

控制檯輸出:

authority before
i am a car, i can run.

(2)後置加強

能夠爲多個加強使用同一個註解,如:

/**
 * Created by solverpeng on 2016/7/27.
 */
@Aspect
@Component
public class AuthorityAspect {
    @Before("@annotation(com.nucsoft.spring.annotation.AuthorityTag)")
    public void before(JoinPoint point) {
        System.out.println("authority before");
    }

    @After("@annotation(com.nucsoft.spring.annotation.AuthorityTag)")
    public void afterAdvice(JoinPoint point) {
        System.out.println("authority after");
    }
}

控制檯輸出:

authority before
i am a car, i can run.
authority after

也能夠爲每一個加強使用不一樣的註解,如:

註解:

/**
 * Created by solverpeng on 2016/7/27.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BeforeAuthorityTag {}

 

/**
 * Created by solverpeng on 2016/7/27.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AfterAuthorityTag {}

切面:

/**
 * Created by solverpeng on 2016/7/27.
 */
@Aspect
@Component
public class AuthorityAspect {
    @Before("@annotation(com.nucsoft.spring.annotation.BeforeAuthorityTag)")
    public void before(JoinPoint point) {
        System.out.println("authority before");
    }

    @After("@annotation(com.nucsoft.spring.annotation.AfterAuthorityTag)")
    public void afterAdvice(JoinPoint point) {
        System.out.println("authority after");
    }
}

使用:

/**
 * @author solverpeng
 * @create 2016-07-27-9:59
 */
@Component
public class Car implements Wheel{
    @BeforeAuthorityTag @AfterAuthorityTag
    @Override
    public void run() {
        System.out.println("i am a car, i can run.");
    }
}

控制檯輸出:

authority before
i am a car, i can run.
authority after

(3)環繞加強

註解類:

/**
 * Created by solverpeng on 2016/7/27.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AroundAuthorityTag {}

切面加強:

@Around(value = "@annotation(com.nucsoft.spring.annotation.AroundAuthorityTag)")
public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable {
  Object result = point.proceed();
  after();
  System.out.println("authority aroundAdvice");
  return result;
}

private void after() {
  System.out.println("after");
}

目標類:

/**
 * @author solverpeng
 * @create 2016-07-27-9:59
 */
@Component
public class Car implements Wheel{
    @AroundAuthorityTag
    @Override
    public void run() {
        System.out.println("i am a car, i can run.");
    }
}

 

控制檯輸出:

i am a car, i can run.
after
authority aroundAdvice

(4)返回加強

註解類:

/**
 * Created by solverpeng on 2016/7/27.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AfterReturningAuthorityTag {}

切面加強:

@AfterReturning(value = "@annotation(com.nucsoft.spring.annotation.AfterReturningAuthorityTag)", returning = "result")
public void afterReturningAdvice(JoinPoint point, Object result) {
  System.out.println("authority afterReturning");
}

目標類:

/**
 * @author solverpeng
 * @create 2016-07-27-9:59
 */
@Component
public class Car implements Wheel{
    @AfterReturningAuthorityTag
    @Override
    public void run() {
        System.out.println("i am a car, i can run.");
    }
}

控制檯輸出:

i am a car, i can run.
authority afterReturning

(5)拋出加強

註解類:

/**
 * Created by solverpeng on 2016/7/27.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AfterThrowingAuthorityTag {}

切面加強:

@AfterThrowing(value = "@annotation(com.nucsoft.spring.annotation.AfterThrowingAuthorityTag)", throwing = "e")
public void afterThrowAdvice(JoinPoint point, Exception e) {
  System.out.println(e.getMessage());
  System.out.println("authority afterThrowing");
}

目標類:

@Component
public class Car implements Wheel{
    @AfterThrowingAuthorityTag
    @Override
    public void run() {
        System.out.println("i am a car, i can run.");
        throw new RuntimeException("throw a new runtimeException");
    }
}

控制檯輸出:

i am a car, i can run.
throw a new runtimeException
authority afterThrowing

java.lang.RuntimeException: throw a new runtimeException

(6)引入加強:關鍵字:@DeclareParents

將要引入的接口:

/**
 * Created by solverpeng on 2016/7/26.
 */
public interface Fly {
    void fly(); }

將要引入的接口的實現:

/**
 * @author solverpeng
 * @create 2016-07-26-20:55
 */
public class SuperMan implements Fly{
    @Override
    public void fly() { System.out.println("i am super man, i can fly."); } }

切面類:

/**
 * @author solverpeng
 * @create 2016-07-26-17:42
 */
@Aspect
@Component
public class LoggingAspect {   @DeclareParents(value = "com.nucsoft.spring.target.impl.Student", defaultImpl = SuperMan.class) private Fly fly; }

測試類:

/**
 * @author solverpeng
 * @create 2016-07-26-18:15
 */
public class SpringTest {
    @Test
    public void test01() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); Person student = context.getBean(Student.class); String james = student.say("james"); System.out.println(james); Fly fly = (Fly) student; fly.fly(); } }

控制檯輸出:

hello,james
i am super man, i can fly.

說明:

<1>.在 Aspect 類中定義一個須要引入加強的接口,它也就是運行時須要動態實現的接口。

<2>.@DeclareParents  註解:

value 屬性:目標類,能夠是一個 AspectJ 類型的表達式,能夠引入到多個類中。

defaultImpl 屬性:引入接口的默認實現類。

<3>.雖然切面類中標註有@DeclareParents  註解 的屬性能夠是任意的,可是通常仍是將其設置爲 引入加強類型。

<4>.從 ApplicationContext 中獲取到的 student 對象實際上是一個代理對象,能夠轉型爲本身靜態實現的接口 Person,也能夠轉型爲動態實現的接口 Fly,切換起來很是方便。

3.3 對比基於註解的切點表達式和註解表達式

註解表達式:更加靈活,可是相應的在開發的過程當中,須要程序員手動的去爲每一個須要添加加強的方法添加對應註解。更加容易擴展。

切點表達式:能夠寫出通用的加強,也不須要程序員手動的去爲每一個方法添加加強,可是須要切點表達式適配。

 

7、小的知識點

1.利用方法簽名編寫 AspectJ 切點表達式

execution * com.nucsoft.spring.Calculator.*(..): 匹配 Calculator 中聲明的全部方法,

第一個 * 表明任意修飾符及任意返回值. 第二個 * 表明任意方法. .. 匹配任意數量的參數. 若目標類與接口與該切面在同一個包中, 能夠省略包名.

execution public * Calculator.*(..): 匹配 ArithmeticCalculator 接口的全部公有方法.

execution public double Calculator.*(..): 匹配 Calculator 中返回 double 類型數值的方法

execution public double Calculator.*(double, ..): 匹配第一個參數爲 double 類型的方法, .. 匹配任意數量任意類型的參數

execution public double Calculator.*(double, double): 匹配參數類型爲 double, double 類型的方法.

2.能夠合併切點表達式 使用 &&, ||, !  來合併。如:

execution(void run()) || execution(void say())

3.切面優先級:

能夠經過實現 Ordered 接口或利用 @Order 註解指定。

(1)實現 Ordered 接口,getOrder() 方法返回的值越小,優先級越高。

(2)使用 @Order 註解,須要出如今註解中,一樣是值越小優先級越高。

4.重用切點定義

在 AspectJ 切面中, 能夠經過 @Pointcut 註解將一個切入點聲明成簡單的方法. 切入點的方法體一般是空的, 由於將切入點定義與應用程序邏輯混在一塊兒是不合理的。

切入點方法的訪問控制符同時也控制着這個切入點的可見性。若是切入點要在多個切面中共用, 最好將它們集中在一個公共的類中。

在這種狀況下, 它們必須被聲明爲 public。在引入這個切入點時, 必須將類名也包括在內。若是類沒有與這個切面放在同一個包中, 還必須包含包名。

其餘通知能夠經過方法名稱引入該切入點。

如:

@Pointcut("execution(void run())")
public void LoggingPointcut(){};

@Before("LoggingPointcut()")
public void before() {
  System.out.println("before");
}

8、總結

1.在學習 Spring AOP 以前,最好先理解了 靜態代理,JDK動態代理,CGLIB動態代理。

2.明白切面和加強以及切點之間的關係

3.幾個加強之間的區別

除環繞加強外,全部的鏈接點使用的都是 JoinPoint 類型的入參,而環繞加強使用的是 ProceedingJoinPoint。

返回加強能接受到返回值

拋出加強能接收到拋出的異常

環繞加強的返回值類型爲目標方法返回值類型

4.Spring AOP 支持聲明式和基於註解的方式,註解優先。

5.步驟:

1.定義切面,以及加強,編寫切點表達式

2.若是切點表達式是基於註解的,還須要對目標方法添加對應的註解。

相關文章
相關標籤/搜索