Spring框架(二):AOP面向切面編程

SpringAOP


介紹

AOP:面向切面編程( Aspect Oriented Programming ),在不修改原來邏輯代碼的前提下,使用切面的方式添加功能(一般是通用功能,例如:日誌,事務,校驗等)

AOP實現原理:動態代理

好處:少寫代碼,降低通用功能與業務代碼的耦合

注:AOP在幾乎每個框架中都有,介紹的是spring的AOP

常見概念

1.通知advice

也就是你想加的功能

2.連接點 joinpoint

就是允許你加入功能的地方,一般是在一個方法的週期中,包括方法調用前,方法調用後,方法拋出異常等情況下,spring允許加功能的所有地方

3.切入點(Pointcut)

不是每個連接點都需要加強,程序員可以根據自己的情況,指定需要加強的方法以及方法運行的某個時間,這就是切入點.由程序員指定哪些方法的什麼時候被增強

4.切面(Aspect)

切面=通知+切入點。通知讓你知道需要增強的功能,切入點讓你知道切入到哪兒,一整合一張的切面就形成了。其實就是你自定義的需要增強的類.

指定哪些方法的什麼時候要被增強+增強的內容

就是invoke方法中去掉method.invoke這行代碼以外所有的叫切面

5.引介(introduction)

引介是一個過程的概念,就是把上面的切面真正的加在被代理的方法上

6.目標(target)

就是要增強的類,就是被代理類

7.代理(proxy)

動態代理(cglib,jdk)

8.織入(weaving)

就是通過Proxy.newInstance()方法生成代理類的過程,比如jdk代理就是調用類加載器生成的類過程

註解方式完成spring AOP

1.加入jar包
jar包 概述
aspectjweaver 是spring的切入點表達式需要用的包,指定切入點
aopalliance 是AOP聯盟的API包,裏面包含了針對面向切面的接口。 通常Spring等其它具備動態織入功能的框架依賴此包
aspectjrt 處理事務和AOP所需的包
cglib cglib動態代理需要的包
spring-aop spring對AOP支持的包
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.26</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.26</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.4</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.0</version>
            <configuration>
                <source>1.8</source> <!-- 源代碼使用jdk1.8支持的特性 -->
                <target>1.8</target> <!-- 使用jvm1.8編譯目標代碼 -->
                <compilerArgs> <!-- 傳遞參數 -->
                    <arg>-parameters</arg>
                    <arg>-Xlint:unchecked</arg>
                    <arg>-Xlint:deprecation </arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>
2.編寫配置文件與測試類
<?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.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--開啓AOP註解支持
        spring 的AOP默認支持JDK代理與cglib代理
        當被代理類實現了接口的情況下使用JDK代理,否則使用CGLIB代理
        可以在<aop:aspectj-autoproxy></aop:aspectj-autoproxy>這個配置中加入
        <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
        告訴spring強制使用cglib代理

    -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <context:component-scan base-package="cn.cdqf.aop"></context:component-scan>
</beans>
3.編寫被代理類
package cn.cdqf.aop;

import org.springframework.stereotype.Service;

@Service
public class StudentService {
    
    public void show(){
        System.out.println("show方法被調用");
    }
}
4.編寫切面(增強類)
package cn.cdqf.aop;

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

@Aspect //該類爲增強類:切面
@Component
public class StudentAspect {

    @Pointcut("execution(* cn.cdqf.aop..*.*(..))")//切入點
    public void aa(){}
    //Before:定義前置通知,在方法調用前被執行
    //execution表達式:
    // 第一個*表示任意返回值
    //第一個.. 表示當前包及其子包
    //第二個*表示任意類
    //第三個*表示任意方法
    //第二個..表示任意參數
    @Before("aa()")
    public void before(){
        System.out.println("前置通知執行了:在方法調用前被執行");
    }
    //AfterReturning:後置通知,在方法正常執行結束後執行
    @AfterReturning("execution(* cn.cdqf.aop..*.*(..))")
    public void afterReturning(){
        System.out.println("正常返回通知執行了:在方法正常執行結束後執行");
    }

    @AfterThrowing("execution(* cn.cdqf.aop..*.*(..))")
    public void afterThrowing(){
        System.out.println("異常通知執行了:在方法拋出異常後執行");
    }
    @After("execution(* cn.cdqf.aop..*.*(..))")
    public void after(){
        System.out.println("後置通知執行了:不管拋沒拋出異常都會在方法運行後執行,相當於finally");
    }

}
5.測試
package cn.cdqf.aop;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:applicationAop.xml"})
public class StudentServiceTest {
    @Autowired
    private StudentService studentService;
    @Test
    public void show() {
        studentService.show();
    }
}
6.環繞通知

切面代碼:invoke的所有代碼都可以在這裏寫

package cn.cdqf.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect //該類爲增強類:切面
@Component
public class StudentAspect {
    //Around:就相當於我們實現動態代理的invoke方法
    //比其它通知厲害的地方:
    //1.可以選擇是否執行目標方法
    //2.可以改變返回值與參數等等
    @Around("execution(* cn.cdqf.aop..*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint){
        //獲得方法參數 既然能獲得就能改變
        Object[] args = joinPoint.getArgs();
        //獲得方法簽名
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        //獲取所有參數的名稱
        String[] parameterNames = signature.getParameterNames();
        //獲取方法參數類型數組,配合前面的參數 可以秀幾波
        Class[] paramTypeArray = signature.getParameterTypes();

        try {
            //1.如果在方法前面加內容 就相當於前置通知
            //調用方法
            Object proceed = joinPoint.proceed(args);
            //2.如果在方法後面加內容就相當於正常返回通知
            //正常返回方法的返回值
            return proceed;
        } catch (Throwable throwable) {
            //3.如果在異常出現後加內容 就相當於異常通知
            throwable.printStackTrace();
        }finally {
            //4.如果在這裏加內容就相當於後置通知
        }
        return null;
    }
}

配置方式實現實現AOP

<!-- 定義目標對象 -->
    <bean name="productDao" class="com.zejian.spring.springAop.dao.daoimp.ProductDaoImpl" />

    <!-- 定義切面 -->
    <bean name="myAspectXML" class="com.zejian.spring.springAop.AspectJ.MyAspectXML" />
    <!-- 配置AOP 切面 -->
    <aop:config>
        <!-- 定義切點函數 -->
        <aop:pointcut id="pointcut" expression="execution(* com.zejian.spring.springAop.dao.ProductDao.add(..))" />

        <!-- 定義其他切點函數 -->
        <aop:pointcut id="delPointcut" expression="execution(* com.zejian.spring.springAop.dao.ProductDao.delete(..))" />

        <!-- 定義通知 order 定義優先級,值越小優先級越大-->
        <aop:aspect ref="myAspectXML" order="0">
            <!-- 定義通知
            method 指定通知方法名,必須與MyAspectXML中的相同
            pointcut 指定切點函數
            -->
            <aop:before method="before" pointcut-ref="pointcut" />

            <!-- 後置通知  returning="returnVal" 定義返回值 必須與類中聲明的名稱一樣-->
            <aop:after-returning method="afterReturn" pointcut-ref="pointcut"  returning="returnVal" />

            <!-- 環繞通知 -->
            <aop:around method="around" pointcut-ref="pointcut"  />

            <!--異常通知 throwing="throwable" 指定異常通知錯誤信息變量,必須與類中聲明的名稱一樣-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"/>

            <!--
                 method : 通知的方法(最終通知)
                 pointcut-ref : 通知應用到的切點方法
                -->
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

AOP總結

AOP是面向切面編程思想,強調不影響正常代碼的情況下切入通用邏輯,核心實現原理是動態代理,最大好處是減少代碼量與解耦合

可以想象AOP的功能巨大

在各大框架中也常常使用AOP,例如mybatis的插件,spring的事務控制,springmvc的攔截器等

在實際開發中常常使用到AOP

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