【Spring】Aop使用詳解

概述

① AOP(Aspect Orient Programming), 面向切面編程,AOP 是一種編程思想,是面向?qū)ο缶幊蹋∣OP)的一種補充;
② 面向切面編程,不修改源代碼給程序動態(tài)統(tǒng)一添加額外功能的一種技術(shù),實現(xiàn)業(yè)務(wù)代碼解耦;
③ 應(yīng)用主要體現(xiàn)在事務(wù)處理、日志管理、權(quán)限控制、異常處理等方面,分離功能性需求和非功能性需求,增強代碼的可讀性和可維護(hù)性。

graph LR
日志記錄---事務(wù)管理---權(quán)限驗證---性能監(jiān)控

靜態(tài)代理

Spring Aop底層實現(xiàn)原理是動態(tài)代理,因此先介紹一下靜態(tài)代理。

特點

① 靜態(tài)代理要求目標(biāo)對象和代理對象實現(xiàn)同一個業(yè)務(wù)接口。代理對象中的核心功能是由目標(biāo)對象來完成,代理對象負(fù)責(zé)增強功能;
② 目標(biāo)對象(被代理對象)必須實現(xiàn)接口;
③ 代理對象在程序運行前就已經(jīng)存在——>擴展代理類Agent;
④ 支持目標(biāo)對象靈活的切換,無法對功能靈活的處理——>動態(tài)代理可解決此問題。
<font color= 'red'>注意:靜態(tài)代理只適合業(yè)務(wù)功能固定不變的情況。(業(yè)務(wù)接口方法不進(jìn)行增加和減少,實現(xiàn)類就不需要改動)</font>

使用示例

需求描述:一個司機具有開車的能力,在開車前需要加油,開車結(jié)束后需要洗車和保養(yǎng),開車前后的動作,在不改變開車內(nèi)部邏輯的情況下,就可以采用靜態(tài)代理實現(xiàn),如下:

// 定義一個駕駛員接口,具有開車功能
public interface Driver {
    /**
     * 駕駛功能
     */
    void drive();
}
// 小轎車駕駛員實現(xiàn)駕駛員接口
public class CarDriver implements Driver{
    @Override
    public void drive(){
        System.out.println("正在開小轎車。");
    }
}
// 小轎車代理類
public class CarAgent implements Driver{
    @Override
    public void drive(){
        System.out.println("開車前要記得加油。");
        CarDriver carDriver = new CarDriver();
        carDriver.drive();
        System.out.println("開車后記得洗車和保養(yǎng)");
    }
}
// 測試類
public class CarAgentTest {
    @Test
    void testDrive(){
        CarAgent carAgent = new CarAgent();
        carAgent.drive();
    }
}

[圖片上傳失敗...(image-44f9d6-1684200501221)]

動態(tài)代理

1、是使用反射和字節(jié)碼的技術(shù),在運行期創(chuàng)建指定接口或類的子類,以及其實例對象的技術(shù),通過這個技術(shù)可以無侵入的為代碼進(jìn)行增強,動態(tài)代理主要有JDK和CGLIB兩種方式;
2、如果代理方法很多,我們需要在每個代理方法都要寫一遍,很麻煩。而動態(tài)代理則不需要。

JDK
// 編寫動態(tài)代理類
public class CarJdkAgent<T> implements InvocationHandler {
    private T target;
    public CarJdkAgent(T target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable{
        System.out.println("調(diào)用方法前");
        Object ret = method.invoke(target,args);
        return ret;
    }
}
// 動態(tài)代理測試
public class DynAgentTest {

    @Test
    void testJdkAgent1(){
        Driver carDriver = new CarDriver();
        Driver proxyCarDriver = (Driver)Proxy.newProxyInstance(carDriver.getClass().getClassLoader(),
                carDriver.getClass().getInterfaces(),new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //args表示有幾個參數(shù),這里只有一個參數(shù)
                System.out.println("動態(tài)代理-開車前記得加油");
                //這里調(diào)用真實對象的方法
                return method.invoke(carDriver, args);
            }
        });
        proxyCarDriver.drive();
    }

    @Test
    void testJdkAgent2(){
        Driver carDriver = new CarDriver();
        InvocationHandler carJdkAgent = new CarJdkAgent<Driver>(carDriver);
        Driver proxyDriver= (Driver)Proxy.newProxyInstance(carDriver.getClass().getClassLoader(),
                carDriver.getClass().getInterfaces(), carJdkAgent);
        proxyDriver.drive();
    }
}

以上測試類兩種方式,方式一采用匿名方法調(diào)用,可以不編寫動態(tài)代理類,方式二通過編寫動態(tài)代理類實現(xiàn)。

CGLIB

1、動態(tài)代理需要被代理類實現(xiàn)接口,如果被代理類沒有實現(xiàn)接口,那么這么實現(xiàn)動態(tài)代理?這時候就需要用到CGLib了。這種代理方式就叫做CGlib代理;
2、Cglib代理也叫作子類代理,他是通過在內(nèi)存中構(gòu)建一個子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,然后加入自己需要的操作。因為使用繼承的方式,所以不能代理final 類;
3、需要導(dǎo)入cglib的jar包,另外Spring的核心包中已經(jīng)包括了Cglib功能,也可以導(dǎo)入spring-core-3.2.5.jar。

public class CarCglibAgent implements MethodInterceptor {
    public Object getProxy(Class cls) {
        // CGLIB <u>enhancer</u>增強類對象
        Enhancer enhancer = new Enhancer();
        // 設(shè)置增強類型
        enhancer.setSuperclass(cls);
        // 定義代理邏輯對象為當(dāng)前對象,要求當(dāng)前對象實現(xiàn)MethodInterceptor方法
        enhancer.setCallback(this);
        // 生成并返回代理對象
        return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        System.out.println("開車前先加油");
        // 執(zhí)行目標(biāo)對象的方法
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("開完車記得洗車");
        return result;
    }
    // 測試調(diào)用
    public static void main(String[] args) {
        CarCglibAgent carCglibAgent = new CarCglibAgent();
        CarDriver carDriver = (CarDriver)carCglibAgent.getProxy(CarDriver.class);
        carDriver.drive();
    }

Spring Aop名詞解釋

關(guān)于AOP的概念這里不做過多介紹,我們只需要知道其中有三個非常重要的概念:
aspect(切面)、pointcut(切入點)、advice(通知)。

pointcut(切入點)

也就是具體攔截的某個業(yè)務(wù)點。
分為 execution(路徑表達(dá)式)和 annotation 方式,被這兩種方式修飾的代碼將會被切面攔截處理;

advice(通知)

切面當(dāng)中的處理方式,聲明通知方法在業(yè)務(wù)層的執(zhí)行位置,類型如下:
@Before:前置通知,在方法執(zhí)行之前執(zhí)行
@After:后置通知,在方法執(zhí)行之后執(zhí)行
@AfterRunning:返回通知,在方法返回結(jié)果后執(zhí)行
@AfterThrowing:異常通知,在方法執(zhí)行異常后執(zhí)行
@Around:環(huán)繞通知,可以替代上述通知方法,并且可以控制方法是否執(zhí)行以及何時執(zhí)行。

aspect(切面)

即 pointcut+ advice ,和攔截器(HandlerInterceptorAdapter)的作用并沒有太大的區(qū)別,只是兩者的作用域不同,相對而言,切面的作用域更靈活一些。

使用介紹

基于自定義注解實現(xiàn)方式
// 定義自定義注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface CustomAnnotation {
    public String ceShiValue() default "";
}
// 定義切面類
@Component
@Aspect
public class CustomAnnotationAspect {
    @Pointcut(value = "@annotation(com.zhc.javabase.aop.CustomAnnotation)")
    public void pointCut(){}
    @Before("pointCut()")
    public void beforeHandle(JoinPoint joinPoint){
        System.out.println("基于注解方法執(zhí)行之前調(diào)用");
    }
    @After("pointCut()")
    public void afterHandle(JoinPoint joinPoint){
        System.out.println("方法執(zhí)行之后調(diào)用");
    }
}
// Controller類中添加注解
@RestController
@RequestMapping("com/zhc/javabase/aop")
public class AopUseController {
    @GetMapping("/firstAop")
    @CustomAnnotation
    public String firstAop(){
        System.out.println("哈哈,aop學(xué)習(xí)");
        return "hello aop";
    }
}
基于路徑表達(dá)式方式
// 定義切面類
@Component
@Aspect
public class CustomAnnotationAspect {
    @Pointcut(value = "execution(public * com.zhc.javabase.aop..*.*(..)))")
    public void executionPointCut(){}

    @Before("executionPointCut()")
    public void beforeHandle1(JoinPoint joinPoint){
        System.out.println("基于非注解方式,方法執(zhí)行之前調(diào)用");
    }
}

在對應(yīng)包路徑下的接口會被切面所攔截,執(zhí)行對應(yīng)切面邏輯。

execution 切點表達(dá)式

由于Spring切面粒度最小是達(dá)到方法級別,而execution表達(dá)式可以用于明確指定方法返回類型,類名,方法名和參數(shù)名等與方法相關(guān)的部件,并且在Spring中,大部分需要使用AOP的業(yè)務(wù)場景也只需要達(dá)到方法級別即可,因而execution表達(dá)式的使用是最為廣泛的。如下是execution表達(dá)式的語法:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

這里問號表示當(dāng)前項可以有也可以沒有,其中各項的語義如下:

modifiers-pattern:方法的可見性,如public,protected;
ret-type-pattern:方法的返回值類型,如int,void等;
declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect;
name-pattern:方法名類型,如buisinessService();
param-pattern:方法的參數(shù)類型,如java.lang.String;
throws-pattern:方法拋出的異常類型,如java.lang.Exception。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容