Spring AOP的使用以及原理

這篇文章主要是對(duì)AOP實(shí)現(xiàn)原理以及Spring使用AOP進(jìn)行描述

還不了解AOP的請(qǐng)先看此文章Spring AOP就是這么簡(jiǎn)單啦

動(dòng)態(tài)代理


在這里我們的案例所用的思想為下圖


以此思想設(shè)計(jì)一個(gè)代理
創(chuàng)建IProducer.java作為接口,代表生產(chǎn)商需要有的特征

package com.ygg.proxy;

/**
 * 對(duì)生產(chǎn)廠家要求的接口
 */
public interface IProducer {

    /**
     * 銷售
     * @param money
     */
    public void saleProduct(float money);

    /**
     * 售后
     * @param money
     */
    public void afterService(float money);
}

接口實(shí)現(xiàn)類
package com.ygg.proxy;

/**
 * 一個(gè)生產(chǎn)者
 */
public class Producer implements IProducer{

    /**
     * 銷售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("銷售產(chǎn)品,并拿到錢:"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服務(wù),并拿到錢:"+money);
    }
}

動(dòng)態(tài)代理實(shí)現(xiàn)

package com.ygg.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 模擬一個(gè)消費(fèi)者
 */
public class Client {

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

        /**
         * 動(dòng)態(tài)代理:
         *  特點(diǎn):字節(jié)碼隨用隨創(chuàng)建,隨用隨加載
         *  作用:不修改源碼的基礎(chǔ)上對(duì)方法增強(qiáng)
         *  分類:
         *      基于接口的動(dòng)態(tài)代理
         *      基于子類的動(dòng)態(tài)代理
         *  基于接口的動(dòng)態(tài)代理:
         *      涉及的類:Proxy
         *      提供者:JDK官方
         *  如何創(chuàng)建代理對(duì)象:
         *      使用Proxy類中的newProxyInstance方法
         *  創(chuàng)建代理對(duì)象的要求:
         *      被代理類最少實(shí)現(xiàn)一個(gè)接口,如果沒有則不能使用
         *  newProxyInstance方法的參數(shù):
         *      ClassLoader:類加載器
         *          它是用于加載代理對(duì)象字節(jié)碼的。和被代理對(duì)象使用相同的類加載器。固定寫法。
         *      Class[]:字節(jié)碼數(shù)組
         *          它是用于讓代理對(duì)象和被代理對(duì)象有相同方法。固定寫法。
         *      InvocationHandler:用于提供增強(qiáng)的代碼
         *          它是讓我們寫如何代理。我們一般都是些一個(gè)該接口的實(shí)現(xiàn)類,通常情況下都是匿名內(nèi)部類,但不是必須的。
         *          此接口的實(shí)現(xiàn)類都是誰(shuí)用誰(shuí)寫。
         */
       IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:執(zhí)行被代理對(duì)象的任何接口方法都會(huì)經(jīng)過(guò)該方法
                     * 方法參數(shù)的含義
                     * @param proxy   代理對(duì)象的引用
                     * @param method  當(dāng)前執(zhí)行的方法
                     * @param args    當(dāng)前執(zhí)行方法所需的參數(shù)
                     * @return        和被代理對(duì)象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    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;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }
}

接口代理實(shí)現(xiàn)方法缺點(diǎn):沒有相應(yīng)接口將無(wú)法實(shí)現(xiàn)

基于子類動(dòng)態(tài)代理


實(shí)現(xiàn)此代理方式需導(dǎo)入cglib.jarasm.jar
實(shí)現(xiàn)方法與上雷同

package com.ygg.cglib;

/**
 * 一個(gè)生產(chǎn)者
 */
public class Producer {

    /**
     * 銷售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("銷售產(chǎn)品,并拿到錢:"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服務(wù),并拿到錢:"+money);
    }
}
package com.ygg.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 模擬一個(gè)消費(fèi)者
 */
public class Client {

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

        /**
         * 動(dòng)態(tài)代理:
         *  特點(diǎn):字節(jié)碼隨用隨創(chuàng)建,隨用隨加載
         *  作用:不修改源碼的基礎(chǔ)上對(duì)方法增強(qiáng)
         *  分類:
         *      基于接口的動(dòng)態(tài)代理
         *      基于子類的動(dòng)態(tài)代理
         *  基于子類的動(dòng)態(tài)代理:
         *      涉及的類:Enhancer
         *      提供者:第三方cglib庫(kù)
         *  如何創(chuàng)建代理對(duì)象:
         *      使用Enhancer類中的create方法
         *  創(chuàng)建代理對(duì)象的要求:
         *      被代理類不能是最終類
         *  create方法的參數(shù):
         *      Class:字節(jié)碼
         *          它是用于指定被代理對(duì)象的字節(jié)碼。
         *
         *      Callback:用于提供增強(qiáng)的代碼
         *          它是讓我們寫如何代理。我們一般都是些一個(gè)該接口的實(shí)現(xiàn)類,通常情況下都是匿名內(nèi)部類,但不是必須的。
         *          此接口的實(shí)現(xiàn)類都是誰(shuí)用誰(shuí)寫。
         *          我們一般寫的都是該接口的子接口實(shí)現(xiàn)類:MethodInterceptor
         */
        Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 執(zhí)行北地阿里對(duì)象的任何方法都會(huì)經(jīng)過(guò)該方法
             * @param proxy
             * @param method
             * @param args
             *    以上三個(gè)參數(shù)和基于接口的動(dòng)態(tài)代理中invoke方法的參數(shù)是一樣的
             * @param methodProxy :當(dāng)前執(zhí)行方法的代理對(duì)象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) 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;
            }
        });
        cglibProducer.saleProduct(12000f);
    }
}

Spring中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: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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置srping的Ioc,把service對(duì)象配置進(jìn)來(lái)-->
    <bean id="accountService" class="com.ygg.service.impl.AccountServiceImpl"></bean>

    <!--spring中基于XML的AOP配置步驟
        1、把通知Bean也交給spring來(lái)管理
        2、使用aop:config標(biāo)簽表明開始AOP的配置
        3、使用aop:aspect標(biāo)簽表明配置切面
                id屬性:是給切面提供一個(gè)唯一標(biāo)識(shí)
                ref屬性:是指定通知類bean的Id。
        4、在aop:aspect標(biāo)簽的內(nèi)部使用對(duì)應(yīng)標(biāo)簽來(lái)配置通知的類型
               我們現(xiàn)在示例是讓printLog方法在切入點(diǎn)方法執(zhí)行之前之前:所以是前置通知
               aop:before:表示配置前置通知
                    method屬性:用于指定Logger類中哪個(gè)方法是前置通知
                    pointcut屬性:用于指定切入點(diǎn)表達(dá)式,該表達(dá)式的含義指的是對(duì)業(yè)務(wù)層中哪些方法增強(qiáng)

            切入點(diǎn)表達(dá)式的寫法:
                關(guān)鍵字:execution(表達(dá)式)
                表達(dá)式:
                    訪問(wèn)修飾符  返回值  包名.包名.包名...類名.方法名(參數(shù)列表)
                標(biāo)準(zhǔn)的表達(dá)式寫法:
                    public void com.ygg.service.impl.AccountServiceImpl.saveAccount()
                訪問(wèn)修飾符可以省略
                    void com.ygg.service.impl.AccountServiceImpl.saveAccount()
                返回值可以使用通配符,表示任意返回值
                    * com.ygg.service.impl.AccountServiceImpl.saveAccount()
                包名可以使用通配符,表示任意包。但是有幾級(jí)包,就需要寫幾個(gè)*.
                    * *.*.*.*.AccountServiceImpl.saveAccount())
                包名可以使用..表示當(dāng)前包及其子包
                    * *..AccountServiceImpl.saveAccount()
                類名和方法名都可以使用*來(lái)實(shí)現(xiàn)通配
                    * *..*.*()
                參數(shù)列表:
                    可以直接寫數(shù)據(jù)類型:
                        基本類型直接寫名稱           int
                        引用類型寫包名.類名的方式   java.lang.String
                    可以使用通配符表示任意類型,但是必須有參數(shù)
                    可以使用..表示有無(wú)參數(shù)均可,有參數(shù)可以是任意類型
                全通配寫法:
                    * *..*.*(..)

                實(shí)際開發(fā)中切入點(diǎn)表達(dá)式的通常寫法:
                    切到業(yè)務(wù)層實(shí)現(xiàn)類下的所有方法
                        * com.ygg.service.impl.*.*(..)
    -->

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

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

</beans>

SpringAOP常用的通知


<!--配置AOP-->
    <aop:config>
        <!-- 配置切入點(diǎn)表達(dá)式 id屬性用于指定表達(dá)式的唯一標(biāo)識(shí)。expression屬性用于指定表達(dá)式內(nèi)容
              此標(biāo)簽寫在aop:aspect標(biāo)簽內(nèi)部只能當(dāng)前切面使用。
              它還可以寫在aop:aspect外面,此時(shí)就變成了所有切面可用
          -->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置前置通知:在切入點(diǎn)方法執(zhí)行之前執(zhí)行
            <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->

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

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

            <!-- 配置最終通知:無(wú)論切入點(diǎn)方法是否正常執(zhí)行它都會(huì)在其后面執(zhí)行
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->

            <!-- 配置環(huán)繞通知 詳細(xì)的注釋請(qǐng)看Logger類中-->
            <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

SpirngAOP注解開發(fā)


bean.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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置spring創(chuàng)建容器時(shí)要掃描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!-- 配置spring開啟注解AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
//一些注解的作用
@Component("logger")
@Aspect//表示當(dāng)前類是一個(gè)切面類
/**
     * 前置通知
     */
//    @Before("pt1()")
    public  void beforePrintLog(){
        System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日志了。。。");
    }

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

    /**
     * 最終通知
     */
//    @After("pt1()")
    public  void afterPrintLog(){
        System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日志了。。。");
    }
/**
     * 環(huán)繞通知
     * 問(wèn)題:
     *      當(dāng)我們配置了環(huán)繞通知之后,切入點(diǎn)方法沒有執(zhí)行,而通知方法執(zhí)行了。
     * 分析:
     *      通過(guò)對(duì)比動(dòng)態(tài)代理中的環(huán)繞通知代碼,發(fā)現(xiàn)動(dòng)態(tài)代理的環(huán)繞通知有明確的切入點(diǎn)方法調(diào)用,而我們的代碼中沒有。
     * 解決:
     *      Spring框架為我們提供了一個(gè)接口:ProceedingJoinPoint。該接口有一個(gè)方法proceed(),此方法就相當(dāng)于明確調(diào)用切入點(diǎn)方法。
     *      該接口可以作為環(huán)繞通知的方法參數(shù),在程序執(zhí)行時(shí),spring框架會(huì)為我們提供該接口的實(shí)現(xiàn)類供我們使用。
     *
     * spring中的環(huán)繞通知:
     *      它是spring框架為我們提供的一種可以在代碼中手動(dòng)控制增強(qiáng)方法何時(shí)執(zhí)行的方式。
     */
    @Around("pt1()")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法執(zhí)行所需的參數(shù)

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

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

            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方法開始記錄日志了。。。最終");
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 本文主要講實(shí)現(xiàn)AOP的 代理模式原理,以及靜態(tài)代理,動(dòng)態(tài)代理的區(qū)別和具體實(shí)現(xiàn)。 對(duì)SpringAOP的概念和使用,...
    _Zy閱讀 804評(píng)論 0 1
  • 轉(zhuǎn)自:https://javadoop.com/post/spring-aop-intro Spring AOP ...
    劍書藏于西閱讀 1,383評(píng)論 0 8
  • 周三, 早上你因?yàn)樾枰樵冏鞅椎氖虑楹軣恢廊绾螏湍?,只能盡我所能的為你做一些有的沒的。 今日有羽毛球比賽,很...
    Ermao閱讀 281評(píng)論 0 1
  • 一般來(lái)說(shuō),父母或身邊的人意愿讓你做的工作或事情都是安穩(wěn)的,因?yàn)橐运麄冞^(guò)來(lái)人的經(jīng)驗(yàn)告訴你:這樣做才是最保險(xiǎn)的。如果不...
    徐徐Junly閱讀 1,576評(píng)論 0 2
  • 今天報(bào)門頭門匾擦了擦,擦完效果挺好,許多事需要去嘗試,不嘗試不會(huì)成長(zhǎng),把握好每一分時(shí)間,做有意義的事,加油
    88e94d537f85閱讀 73評(píng)論 0 0

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