JavaWeb-11-Spring基本特性-AOP

Table of Contents

三:AOP面向切面變成

1:AOP的介紹

2:AOP場景

2.1:Aop場景:在系統執行方法是加日誌來獲取方法的參數;

2.2:原生JDK動態代理實現上述場景

2.3:JDK動態代理的確定

3:Aop專業術語

4:AOP的使用

4.1:導包

4.2:寫配置

4.3:AOP的五種註解

4.4:切面類代碼

4.5:xml配置

4.6:結果

5:當被代理類沒有實現接口時,spring自動默認使用CGLIB代理、

6:AOP中的細節

6.1:當被代理類有實現接口時,默認使用JDK代理;在容器中保存的是代理對象,獲取bean只能傳接口類型,也可以用id來獲取這個接口對象

6.2:切入點表達式

6.3:AOP執行順序

6.4:切面類獲取目標方法參數和方法名

6.5:切面類接受目標方法返回值和異常信息

6.6:抽取可重用的切入點表達式

6.7:環繞通知---四合一接口

6.8:多切面的執行順序

7:基於註解的AOP(可以不改變源碼)


三:AOP面向切面變成

1:AOP的介紹

AOP:(Aspect Oriented Programming)面向切面編程;

OOP:(Object Oriented Programming )面向對象編程;

面向切面編程:基於OOP基礎之上新的編程思想;

指在程序運行期間,將某段代碼動態的切入到指定方法的指定位置進行運行的這種編程方式,面向切面編程;

2:AOP場景

2.1:Aop場景:在系統執行方法是加日誌來獲取方法的參數;

加日誌記錄:

1)、直接編寫在方法內部;不推薦,修改維護麻煩;          

          日誌記錄:系統的輔助功能;

          業務邏輯:(核心功能)

          耦合;

2)、我們希望的是;

          業務邏輯:(核心功能);日誌模塊;在覈心功能運行期間,自己動態的加上;

          運行的時候,日誌功能可以加上;

  可以使用動態代理來將日誌代碼動態的在目標方法執行前後先進行執行;

2.2:原生JDK動態代理實現上述場景

動態代理的內容我詳細寫了一篇博客:動態代理介紹

2.3:JDK動態代理的確定

3:Aop專業術語

將某段代碼動態的切入(不把日誌代碼寫死在業務邏輯方法中)到指定方法的指定位置)進行運行的這種編程方式(Spring簡化了面向切面編程)

●AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論,是對傳統 OOP(Object-Oriented Programming,面向對象編程)的補充。

●AOP編程操作的主要對象是切面(aspect),而切面模塊化橫切關注點

●在應用AOP編程時,仍然需要定義公共功能,但可以明確的定義這個功能應用在哪裏,以什麼方式應用,並且不必修改受影響的類。這樣一來橫切關注點就被模塊化到特殊的類裏——這樣的類我們通常稱之爲“切面”。

●AOP的好處:

○每個事物邏輯位於一個位置,代碼不分散,便於維護和升級

○業務模塊更簡潔,只包含核心業務代碼

  • 橫切關注點:從每個方法中抽取出來的同一類非核心業務。如方法執行前輸出日誌;
  • 切面(Aspect):封裝橫切關注點信息的類,每個關注點體現爲一個通知方法。就是日誌類
  • 通知(Advice):切面必須要完成的各個具體工作
  • 目標(Target):被通知的對象
  • 代理(Proxy):向目標對象應用通知之後創建的代理對象
  • 連接點(Joinpoint):橫切關注點在程序代碼中的具體體現,對應程序執行的某個特定位置。例如:類某個方法調用前、調用後、方法捕獲到異常後等。
  • 切入點(pointcut):定位連接點的方式。每個類的方法中都包含多個連接點,所以連接點是類中客觀存在的事物。如果把連接點看作數據庫中的記錄,那麼切入點就是查詢條件——AOP可以通過切入點定位到特定的連接點。切點通過org.springframework.aop.Pointcut 接口進行描述,它使用類和方法作爲連接點的查詢條件。

4:AOP的使用

4.1:導包

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.7.RELEASE</version>
        </dependency>

    </dependencies>

4.2:寫配置

1:將目標類和切面類(封裝了通知方法(在目標方法執行前後執行的方法))加入到ioc容器中

:2

2:還應該告訴Spring到底哪個是切面類@Aspect

4.3:AOP的五種註解

try {

    @Before():前置通知  在目標方法執行前執行
    method.invoke(obj,args);//執行目標方法
    @After() :後置通知  在目標方法執行後執行

} catch (Exception e) {
    @AfterThrowing() :後置異常通知
}finally {
    @AfterReturning() :後置成功通知
}

還有個環繞通知

@around 

4.4:切面類代碼

package com.wkl.utils;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * Description:
 * Date:       2020/6/27 - 上午 12:53
 * author:     wangkanglu
 * version:    V1.0
 */
@Component
@Aspect
public class LogUtil {


    @Before("execution(public void com.wkl.impl.UserImpl.add())")
    //方法開始執行前,寫入切入點表達式
    //execution(權限訪問控制符  返回值類型 方法簽名)
    public void Start(){
        System.out.println("start。。。。");
    }

    @After("execution(public void com.wkl.impl.UserImpl.add())")
    //想在目標方法正常執行完成之後執行
    public void logReturn(){
        System.out.println("logReturn。。。。");
    }

    @AfterThrowing("execution(public void com.wkl.impl.UserImpl.add())")
    //想在目標方法出現異常的時候執行
    public void logException(){
        System.out.println("logException。。。。");
    }

    @AfterReturning("execution(public void com.wkl.impl.UserImpl.add())")
    //想在目標方法結束的時候執行
    public void logEnd(){
        System.out.println("logEnd。。。。");
    }
}

 

4.5: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"
       xmlns:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">


    <context:component-scan base-package="com.wkl"></context:component-scan>

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

導入命名空間的參考:https://blog.csdn.net/weixin_42382121/article/details/82050091

4.6:結果

注:尋找這個bean時,尋找的是接口的class

5:當被代理類沒有實現接口時,spring自動默認使用CGLIB代理、

 @Test
    public void test01(){
//        UserInter bean = ioc.getBean(UserInter.class);
//        bean.add();
//        System.out.println(""+bean.getClass());

        UserImpl userImpl = (UserImpl) ioc.getBean("userImpl");
        userImpl.add();
        System.out.println(""+userImpl.getClass());

    }

6:AOP中的細節

6.1:當被代理類有實現接口時,默認使用JDK代理;在容器中保存的是代理對象,獲取bean只能傳接口類型,也可以用id來獲取這個接口對象

 @Test
    public void test01(){
        UserInter bean = ioc.getBean(UserInter.class);
        bean.add();
        System.out.println(""+bean.getClass());

        

    }

 

 @Test
    public void test01(){
        UserInter bean = ioc.getBean(UserInter.class);
        bean.add();
        System.out.println(""+bean.getClass());

        UserInter userImpl = (UserInter) ioc.getBean("userImpl");
        userImpl.add();
        System.out.println(""+userImpl.getClass());

    }

start。。。。
add.....
logReturn。。。。
logEnd。。。。
class com.sun.proxy.$Proxy18
start。。。。
add.....
logReturn。。。。
logEnd。。。。
class com.sun.proxy.$Proxy18

Process finished with exit code 0
 

6.2:切入點表達式

切入點表達式的語法細節

①切入點表達式的語法格式

execution([權限修飾符] [返回值類型] [簡單類名/全類名] [方法名]([參數列表]))

②舉例說明

表達式

execution(* com.atguigu.spring.ArithmeticCalculator.*(..))

含義

ArithmeticCalculator接口中聲明的所有方法。

第一個“*”代表任意修飾符及任意返回值。

第二個“*”代表任意方法。

“..”匹配任意數量、任意類型的參數。

若目標類、接口與該切面類在同一個包中可以省略包名。

表達式

execution(public * ArithmeticCalculator.*(..))

含義

ArithmeticCalculator接口的所有公有方法

表達式

execution(public double ArithmeticCalculator.*(..))

含義

ArithmeticCalculator接口中返回double類型數值的方法

表達式

execution(public double ArithmeticCalculator.*(double, ..))

含義

第一個參數爲double類型的方法。

“..” 匹配任意數量、任意類型的參數。

表達式

execution(public double ArithmeticCalculator.*(double, double))

含義

參數類型爲double,double類型的方法

③在AspectJ中,切入點表達式可以通過 “&&”、“||”、“!”等操作符結合起來。

表達式

execution (* *.add(int,..)) || execution(* *.sub(int,..))

含義

任意類中第一個參數爲int類型的add方法或sub方法

 

6.3:AOP執行順序

try {

         @Before():前置通知 在目標方法執行前執行

         method.invoke(obj,args);//執行目標方法

         @After() :後置通知 在目標方法執行後執行

} catch (Exception e) {

         @AfterThrowing() :後置異常通知

}finally {

         @AfterReturning() :後置成功通知

}

正常:@Before---@After---@AfterReturning

異常:@Before---@After---@AfterThrowing

6.4:切面類獲取目標方法參數和方法名

@Before("execution(public void com.wkl.impl.UserImpl.add())")
    //方法開始執行前,寫入切入點表達式
    //execution(權限訪問控制符  返回值類型 方法簽名)
    public void Start(JoinPoint joinPoint){
        //參數信息
        Object[] args = joinPoint.getArgs();
        //獲取方法名--獲取簽名,然後獲取方法名
        String name = joinPoint.getSignature().getName();
        System.out.println("["+name+"]方法執行了--start。。。。參數是:"+ Arrays.asList(args));
    }

[add]方法執行了--start。。。。參數是:[]
add.....
logReturn。。。。
logEnd。。。。

6.5:切面類接受目標方法返回值和異常信息

@AfterThrowing(value = "execution(public String com.wkl.impl.UserImpl.add())",throwing = "e")
    //想在目標方法出現異常的時候執行
    public void logException(Exceptione){
        System.out.println("logException。。。。");
    }

    //告訴切面類,標識用result接受返回值
    @AfterReturning(value = "execution(public String com.wkl.impl.UserImpl.add())",returning = "result")
    //想在目標方法結束的時候執行
    public void logEnd(Object result){
        System.out.println("logEnd。。。。返回值是:"+result);
    }

6.6:抽取可重用的切入點表達式

/*
* 1:隨便定義一個沒有實現的返回void的空方法
* 2:提取表達式,並註解
* 3:將方法名注在之前切入點表達式的地方
* */
package com.wkl.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * Description:
 * Date:       2020/6/27 - 上午 12:53
 * author:     wangkanglu
 * version:    V1.0
 */
@Component
@Aspect
public class LogUtil {

    /*
    * 1:隨便定義一個沒有實現的返回void的空方法
    * 2:提取表達式,並註解
    * 3:將方法名注在之前切入點表達式的地方
    * */
    @Pointcut("execution(public String com.wkl.impl.UserImpl.add())")
    public void MyPonintcat(){}

    @Before("MyPonintcat()")
    //方法開始執行前,寫入切入點表達式
    //execution(權限訪問控制符  返回值類型 方法簽名)
    public void Start(JoinPoint joinPoint){
        //參數信息
        Object[] args = joinPoint.getArgs();
        //獲取方法名--獲取簽名,然後獲取方法名
        String name = joinPoint.getSignature().getName();
        System.out.println("["+name+"]方法執行了--start。。。。參數是:"+ Arrays.asList(args));
    }

    @After("MyPonintcat()")
    //想在目標方法正常執行完成之後執行
    public void logReturn(){
        System.out.println("logReturn。。。。");
    }


    @AfterThrowing(value = "MyPonintcat()",throwing = "e")
    //想在目標方法出現異常的時候執行
    public void logException(Exception e){
        System.out.println("logException。。。。");
    }

    //告訴切面類,標識用result接受返回值
    @AfterReturning(value = "MyPonintcat()",returning = "result")
    //想在目標方法結束的時候執行
    public void logEnd(Object result){
        System.out.println("logEnd。。。。返回值是:"+result);
    }
}

 

6.7:環繞通知---四合一接口

//環繞通知
    @Around("MyPonintcat()")
    public Object myAround(ProceedingJoinPoint pjp){

        Object[] args = pjp.getArgs();
        Object proceed =null;
        try {
            System.out.println("環繞前置");
            proceed = pjp.proceed(args);
            System.out.println("環繞返回通知");
        } catch (Throwable throwable) {
            System.out.println("環繞異常");
            throwable.printStackTrace();
        }finally {
            System.out.println("環繞後置");
        }

        return proceed;

    }

環繞前置
add.....
環繞返回通知
環繞後置

  1. 環繞通知是所有通知類型中功能最爲強大的,能夠全面地控制連接點,甚至可以控制是否執行連接點。
  2. 對於環繞通知來說,連接點的參數類型必須是ProceedingJoinPoint。它是 JoinPoint的子接口,允許控制何時執行,是否執行連接點。
  3. 在環繞通知中需要明確調用ProceedingJoinPoint的proceed()方法來執行被代理的方法。如果忘記這樣做就會導致通知被執行了,但目標方法沒有被執行。

環繞和普通都在的話,執行順序:

環繞前置---普通前置---目標方法執行---環繞正常/出現異常---環繞後置---普通後置---普通返回/出現異常

6.8:多切面的執行順序

 

至於一切面或者而且二切面的順序:

1:默認是比較全類名的:

2:可以使用@Order()來指定切面的順序;在()中填入整數,數越小,優先級越高;

7:基於註解的AOP(可以不改變源碼)


    <!--基於註解的AOP的步驟
        1:將目標類和切面類加入容器中
        2:告訴spring那個是切面類
        3:在切面類中,那個是方法是合適何地運行
        4:開啓註解的AOP功能
    -->
    <!--目標類-->
    <bean id="userImpl" class="com.wkl.impl.UserImpl"></bean>
    <!--切面類-->
    <bean id="logUti" class="com.wkl.utils.LogUtil"></bean>

    <!--需要aop命名空間-->
    <aop:config>
        <!--指定切面類-->
        <aop:aspect ref="logUti" order="3">
            <!--指定切面表達式-->
            <aop:pointcut id="mypoint1" expression="execution(public String com.wkl.impl.UserImpl.add())"/>
            <aop:before method="Start" pointcut="execution(public String com.wkl.impl.UserImpl.add())"></aop:before>
            <aop:after-returning method="logReturn" pointcut-ref="mypoint1" returning="result"></aop:after-returning>
        </aop:aspect>

    </aop:config>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章