spring框架入門之怎么使用切面編程AOP

本篇內(nèi)容介紹了“spring框架入門之怎么使用切面編程AOP”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

專注于為中小企業(yè)提供成都網(wǎng)站制作、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)交口免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上千企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

1. 動態(tài)代理

特點:字節(jié)碼隨用隨創(chuàng)建,隨用隨加載

作用:在不修改源碼的基礎(chǔ)上對方法進(jìn)行增強(qiáng)

分類:


基于接口的動態(tài)代理基于子類的動態(tài)代理
涉及的類ProxyEnhancer
提供者JDK官方第三方庫cglib
如何創(chuàng)建代理對象使用Proxy中的 newProxyInstance 方法使用Enhancer類中的create方法
創(chuàng)建代理對象的要求被代理類至少實現(xiàn)一個接口,沒有則不能使用被代理對象不能是最終類
1.1 基于接口的動態(tài)代理

newProxyInstance方法的參數(shù):

  • ClassLoader : 類加載器。用于加載代理對象字節(jié)碼,和被代理類使用相同的類加載器。

    寫法: 代理對象.getClass().getClassLoader()

  • Class[] : 字節(jié)碼數(shù)組。用于讓代理對象和被代理對象有相同方法。

    寫法: 代理對象.getClass().getInterfaces()

  • InvocationHandler:用于增強(qiáng)的代碼。書寫對被代理方法增強(qiáng)的代碼,一般書寫此接口的實現(xiàn)類,通常情況下是匿名內(nèi)部類,但不是必須的,此接口的實現(xiàn)類一般誰用到誰寫。

  • InvocationHandler參數(shù)中的invoke方法,執(zhí)行被代理對象的任何接口方法都會經(jīng)過該方法。方法參數(shù)及其含義:

    • proxy :代理對象的引用

    • method :當(dāng)前執(zhí)行的方法

    • args:當(dāng)前執(zhí)行方法所需的參數(shù)

    • 返回值:與被代理類有相同的返回值

代碼示例:

public class Client {
    public static void main(String[] args) {
        final ProducerImpl producer = new ProducerImpl();

        producer.saleProduct(1000f);// 銷售產(chǎn)品,拿到錢1000.0
        System.out.println("對方法進(jìn)行增強(qiáng)后。。。。。");
        Producer proxyProduct = (Producer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {

                    /**
                     * 執(zhí)行被代理對象的任何接口方法都會經(jīng)過該方法
                     * 方法的參數(shù)含義
                     * @param proxy  代理對象的引用
                     * @param method 當(dāng)前執(zhí)行方法
                     * @param args   當(dāng)前執(zhí)行方法所需的參數(shù)
                     * @return       和被代理對象有相同的返回值
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 提供增強(qiáng)的代碼
                        Object returnValue = null;
                        // 1.獲取方法的執(zhí)行參數(shù)
                        Float money = (Float) args[0];

                        // 2.判斷當(dāng)前方法是不是銷售方法
                        if ("saleProduct".equals(method.getName())){
                            returnValue = method.invoke(producer, money * 0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProduct.saleProduct(1000f);// 銷售產(chǎn)品,拿到錢800.0
    }
}
1.2 基于子類的動態(tài)代理

create方法的參數(shù):

  • Class:字節(jié)碼。用于指定被代理對象的字節(jié)碼。

  • Callback:用于提供增強(qiáng)的代碼,類似于基于接口的動態(tài)代理的invoke方法。一般寫的是該接口的子接口實現(xiàn)類 MethodInterceptor

  • create參數(shù)中 MethodInterceptor 的方法參數(shù):

    • o :代理對象的引用

    • method :當(dāng)前執(zhí)行的方法

    • objects:當(dāng)前執(zhí)行方法所需的參數(shù)

    • methodProxy:當(dāng)前執(zhí)行方法的代理對象

代碼示例:

public class Client {

    final Producer producer = new Producer();

    public static void main(String[] args) {

        final Producer producer = new Producer();

        producer.saleProduct(1000f);// 售賣商品,得到錢1000.0
        System.out.println("對方法進(jìn)行增強(qiáng)后。。。。。");
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 執(zhí)行任何被處理對象的任何方法都會經(jīng)過該方法
             * @param o           代理對象的引用
             * @param method      當(dāng)前的執(zhí)行方法
             * @param objects     當(dāng)前執(zhí)行方法所需的參數(shù)
             * @param methodProxy 當(dāng)前執(zhí)行方法的代理對象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                // 提供增強(qiáng)的方法
                Object returnValue = null;

                // 1.獲取當(dāng)前方法的執(zhí)行參數(shù)
                Float money = (Float) objects[0];
                // 2.判斷當(dāng)前的方法是不是銷售動作
                if ("saleProduct".equals(method.getName())){
                    returnValue = method.invoke(producer, money * 0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(1000f);// 售賣商品,得到錢800.0
    }
}
1.3 動態(tài)代理總結(jié)

動態(tài)代理的一般使用方式:

  • 獲取被代理類對象(被代理對象的字節(jié)碼、被代理類對象的類加載器等信息)

  • 在代理類提供的方法中對被代理類中的方法進(jìn)行增強(qiáng)

2. spring中的AOP

spring中的AOP是通過配置的方式實現(xiàn)動態(tài)代理

2.1 spring中的相關(guān)術(shù)語:

**Joinpoint(連接點):**指被攔截到的點。在spring中這些點指的是方法,因為spring只支持方法類型的連接點。可以理解為業(yè)務(wù)層中所有的方法。

**Pointcut(切入點):**指需要對那些Joinpoint進(jìn)行攔截的定義??梢岳斫鉃楸辉鰪?qiáng)的方法。

**Advice(通知/增強(qiáng)):**指攔截到Joinpoint后需要做的事情。通知類型:前置通知,后置通知,異常通知,最終通知,環(huán)繞通知。

  • 前置通知:在執(zhí)行業(yè)務(wù)層方法前的通知;

  • 后置通知:在執(zhí)行業(yè)務(wù)層方法后的通知;

  • 異常通知:catch中的通知;

  • 最終通知:在finally中的通知;

  • 環(huán)繞通知:整個invoke方法執(zhí)行就是環(huán)繞通知;

spring框架入門之怎么使用切面編程AOP

**Introduction(引介):**一種特殊的通知在不修改類代碼的前提下,Introduction可以在運(yùn)行期為類動態(tài)的添加一些方法或Field。

Target(目標(biāo)對象):代理的目標(biāo)對象。

**Weaving(織入):**指把增強(qiáng)應(yīng)用到目標(biāo)對象來創(chuàng)建代理對象的過程。spring是動態(tài)代理織入的,而AspectJ采用編譯期織入和類裝載期織入。

**Proxy(代理):**一個類被AOP織入增強(qiáng)后,就產(chǎn)生一個結(jié)果代理類。

**Aspect(切面):**是切入點和通知(引介)的結(jié)合。

2.2 spring中AOP

開發(fā)階段:

編寫核心業(yè)務(wù)代碼(主線開發(fā),熟悉業(yè)務(wù)代碼即可進(jìn)行開發(fā))

把公共代碼提取出來,制作成通知。(開發(fā)最后階段)

在配置文件中聲明切入點與通知之間的關(guān)系,即切面。

運(yùn)行階段:

spring框架監(jiān)控切入點的方法執(zhí)行。一旦監(jiān)控到切入點方法被運(yùn)行,使用代理機(jī)制,動態(tài)創(chuàng)建目標(biāo)對象的代理對象,通知類別,在代理對象的對應(yīng)位置,將通知對應(yīng)的功能織入,完成完整的代碼邏輯運(yùn)行。

spring中的AOP會根據(jù)目標(biāo)是否實現(xiàn)了接口來決定采用哪種動態(tài)代理的方式

3.基于XML的AOP配置

3.1 將通知類交由IoC容器管理

將通知類注冊到spring的IoC容器中

<bean id="" class="">
	<property name="" ref=""></property>
</bean>

3.2 使用 <aop:config> 標(biāo)簽進(jìn)行AOP配置

用于聲明aop配置

<aop:config>
	<!-- 配置的代碼寫在此處 -->
</aop:config>

3.3 使用 <aop:aspect> 配置切面

用于配置切面

屬性:

① id屬性:是給切面提供一個唯一標(biāo)識

② ref屬性:是指定通知類bean的Id。

<aop:aspect id="" ref="">
    <!-- 在這里配置通知類型 -->
</aop:aspect>
  • <aop:before>:用于配置前置通知

  • <aop:after-return>:用于配置后置通知

  • <aop:after-throwing>:用于配置異常通知

  • <aop:after>:用于配置最終通知

  • <aop: around>:用于配置環(huán)繞通知

    ① method屬性:用于指定Logger類中哪個方法是前置通知

    ② pointcut屬性:用于指定切入點表達(dá)式,該表達(dá)式的含義指的是對業(yè)務(wù)層中哪些方法增強(qiáng)

    ③ pointcut-ref屬性:用于指定切入點表達(dá)式的id

3.4 使用 <aop:pointcut> 配置切入點表達(dá)式

用于配置切入點表達(dá)式,就是指定對那些類進(jìn)行的那些方法進(jìn)行增強(qiáng)

屬性:

① id屬性:用于指定切入點的唯一標(biāo)識

② expression屬性:用于配置切入點表達(dá)式

<aop:pointcut id="" expression="execution()"/>

代碼示例:

<!-- 配置service對象 -->
<bean id="accountService" class="cn.bruce.service.impl.AccountServiceImpl"></bean>
<bean id="testService" class="cn.bruce.service.impl.TestServiceImpl"></bean>


<!-- 配置Logger類 -->
<bean id="logger" class="cn.bruce.utils.Logger"></bean>

<!-- 配置AOP -->
<aop:config>
    <!-- 配置切面 -->
    <aop:aspect id="logAdvice" ref="logger">
        <!-- 配置通知類型,建立通知方法和切入點方法的關(guān)聯(lián) -->
        <aop:before method="printLog" pointcut="execution(* cn.bruce.service.impl.*.*(..))"></aop:before>
        <aop:after method="printLog" pointcut="execution(* cn..impl.Test*.*(cn.bruce.domain.Account))"></aop:after>
    </aop:aspect>
</aop:config>

4. 切入點表達(dá)式

關(guān)鍵字:execution("表達(dá)式")

表達(dá)式寫法:訪問修飾符 返回值 包名.***.包名.類名.方法名(參數(shù)列表)

標(biāo)準(zhǔn)寫法:public void cn.bruce.service.impl.AccountServiceImpl.saveAccount()

  • 訪問修飾符可以省略(訪問權(quán)限不能寫 *),表示匹配任意類型的訪問權(quán)限,但Spring現(xiàn)在只支持public權(quán)限;

    void cn.bruce.service.impl.AccountServiceImpl.saveAccount()

  • 返回值可以使用通配符,表示任意返回值;

    * cn.bruce.service.impl.AccountServiceImpl.saveAccount()

  • 包名可以使用通配符,表示任意包,有幾級包就要寫幾個 *;

    * *.*.*.*.AccountServiceImpl.saveAccount()

  • 包名可以使用 .. 表示當(dāng)前包及其子包

    * cn..AccountServiceImpl.saveAccount()

  • 類名和方法名都可以使用通配符代替

    * *..*.*()

**參數(shù)列表:**直接寫數(shù)據(jù)類型

  • 基本數(shù)據(jù)類型直接寫名稱,如:int long double boolean

  • 引用數(shù)據(jù)類型要寫全類名,如:cn.bruce.domain.Accout

  • 可以使用通配符 * 表示任意類型,但是必須有參數(shù)

  • 可以使用通配符 * 進(jìn)行占位,如:* *..*.*(*, int)

  • 可以使用 .. 表示有無參數(shù)均可,有參數(shù)可以是任意類型 * *..*.*(..)

全通配寫法:* *..*.*(..)

開發(fā)中切入點表達(dá)式的通常寫法:如:切到業(yè)務(wù)層實現(xiàn)類下的所有方法 * cn.bruce.service.impl.*.*(..)

5. 常用通知類型

前置通知 <aop:before>:在切入點方法執(zhí)行之前執(zhí)行

后置通知 <aop:after-returning>:在切入點方法執(zhí)行之后執(zhí)行。后置通知和異常通知永遠(yuǎn)只能執(zhí)行一個

異常通知 <aop:after-throwing>:在切入點方法執(zhí)行產(chǎn)生異常后執(zhí)行。異常通知和后置通知永遠(yuǎn)只能執(zhí)行一個

最終通知 <aop:after>:無論切入點方法是否正常執(zhí)行,它都會在其后面執(zhí)行

環(huán)繞通知 <aop:around>:是spring框架為我們提供的一種可以在代碼中手動控制增強(qiáng)方法何時執(zhí)行的方式。

代碼示例:

<!-- 配置AOP -->
<aop:config>
    <!-- 配置切入點表達(dá)式,id屬性是表達(dá)式的唯一標(biāo)識,expression屬性用于指定表達(dá)式內(nèi)容
             此標(biāo)簽可以寫在aop:aspect標(biāo)簽內(nèi)部只能當(dāng)前切面使用
             寫在aop:aspect外面,此時表示所有的切面可用
         -->
    <aop:pointcut id="loggerPointCut" expression="execution(* cn..impl.Account*.*(..))"/>

    <!-- 配置切面 -->
    <aop:aspect id="logAdvice" ref="logger">
        <!-- 前置通知:在切入點方法執(zhí)行之前執(zhí)行 -->
        <aop:before method="beforePrintLog" pointcut-ref="loggerPointCut"></aop:before>

        <!-- 后置通知:在切入點方法執(zhí)行之后執(zhí)行。后置通知和異常通知永遠(yuǎn)只能執(zhí)行一個 -->
        <aop:after-returning method="afterReturningPrintLog" pointcut-ref="loggerPointCut"></aop:after-returning>

        <!-- 異常通知:在切入點方法執(zhí)行產(chǎn)生異常后執(zhí)行。異常通知和后置通知永遠(yuǎn)只能執(zhí)行一個 -->
        <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="loggerPointCut"></aop:after-throwing>

        <!-- 最終通知:無論切入點方法是否正常執(zhí)行,它都會在其后面執(zhí)行 -->
        <aop:after method="afterPrintLog" pointcut-ref="loggerPointCut"></aop:after>

        <!-- 環(huán)繞通知:是spring框架為我們提供的一種可以在代碼中手動控制增強(qiáng)方法何時執(zhí)行的方式。 -->
        <aop:around method="aroundPrintLog" pointcut-ref="loggerPointCut"></aop:around>
    </aop:aspect>
</aop:config>

6. 基于注解的AOP配置

配置步驟:

①導(dǎo)入maven坐標(biāo)

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

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
    </dependency>
</dependencies>

② 書寫spring配置類,開啟包掃描和注解支持

@configuration
@ComponentScan("cn.bruce") // 開啟包掃描,配置需要掃描的包
@EnableAspectJAutoProxy(proxyTargetClass = true) // 開啟注解驅(qū)動
public class SpringConfiguration {
}

③ 將業(yè)務(wù)層實體類交由IoC容器管理

@Service("testService")
public class TestServiceImpl implements TestService {

    @Override
    public void testOfVoid() {
        System.out.println("testOfVoid is running......");
    }

    @Override
    public void testOfInt(int i) {
        System.out.println("testOfInt is running......number is" + i);
    }

    @Override
    public void testOfInteger(Integer i) {
//        i = 1/0;
        System.out.println("testOfInteger is running......number is" + i);

    }

    @Override
    public void testOfAccount(Account account) {
        int i = 1/0;
        System.out.println("testOfInt is running......number is" + account);

    }
}

④ 書寫切面類,聲明為切面類并設(shè)置切入點和通知類型

@Component("logger")
@Aspect // 表示此類為切面類
public class Logger {

    @Pointcut("execution(* cn..impl.*.*(..))") // 指定切入點表達(dá)式
    private void pointcut(){}

    /**
     * 前置通知
     */
    @Before("execution(* cn..impl.*.*(int))")
    public  void beforePrintLog(){
        System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日志了。。。");
    }

    /**
     * 后置通知
     */
    @AfterReturning("execution(* cn..impl.*.*(Integer))")
    public  void afterReturningPrintLog(){
        System.out.println("后置通知Logger類中的afterReturningPrintLog方法開始記錄日志了。。。");
    }
    /**
     * 異常通知
     */
    @AfterThrowing("pointcut()")
    public  void afterThrowingPrintLog(){
        System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日志了。。。");
    }

    /**
     * 最終通知
     */
    @After("execution(* cn..impl.*.*())")
    public  void afterPrintLog(){
        System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日志了。。。");
    }

    /**
     * 環(huán)繞通知
     */
    @Around("execution(* cn..impl.*.*(cn.bruce.domain.Account))")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            //得到方法執(zhí)行所需的參數(shù)
            Object[] args = pjp.getArgs();

            System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。前置");

            //明確調(diào)用業(yè)務(wù)層方法(切入點方法)
            rtValue = pjp.proceed(args);

            System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。異常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。最終");
        }
    }

}

⑤ 書寫測試類進(jìn)行測試

public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        TestService testService = (TestService) ac.getBean("testService");
        testService.testOfInt(133);
        System.out.println("-----------");
        testService.testOfInteger(112);
        System.out.println("-----------");
        testService.testOfVoid();
        System.out.println("-----------");

        Account account = (Account) ac.getBean("account");
        account.setName("Bruce");
        account.setAge(112);
        testService.testOfAccount(account);
    }
}

“spring框架入門之怎么使用切面編程AOP”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

分享文章:spring框架入門之怎么使用切面編程AOP
網(wǎng)站地址:http://bm7419.com/article44/jdciee.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供、品牌網(wǎng)站建設(shè)、企業(yè)網(wǎng)站制作、電子商務(wù)做網(wǎng)站、ChatGPT

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管