spring-AOP

0913)spring-使用IOC實現CRUD

1)配置包掃描

引入spring-Context的名稱空間,可以告知spring在創建時要掃描的包位置.

2)四類註解的使用

* 用於創建對象的
 *      他們的作用就和在XML配置文件中編寫一個<bean>標籤實現的功能是一樣的
 *      Component:
 *          作用:用於把當前類對象存入spring容器中
 *          屬性:
 *              value:用於指定bean的id。當我們不寫時,它的默認值是當前類名,且首字母改小寫。
 *      Controller:一般用在表現層
 *      Service:一般用在業務層
 *      Repository:一般用在持久層
 *      以上三個註解他們的作用和屬性與Component是一模一樣。
 *      他們三個是spring框架爲我們提供明確的三層使用的註解,使我們的三層對象更加清晰
 *
 *
 * 用於注入數據的
 *      他們的作用就和在xml配置文件中的bean標籤中寫一個<property>標籤的作用是一樣的
 *      Autowired:
 *          作用:自動按照類型注入。只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配,就可以注入成功
 *                如果ioc容器中沒有任何bean的類型和要注入的變量類型匹配,則報錯。
 *                如果Ioc容器中有多個類型匹配時:
 *          出現位置:
 *              可以是變量上,也可以是方法上
 *          細節:
 *              在使用註解注入時,set方法就不是必須的了。
 *      Qualifier:
 *          作用:在按照類中注入的基礎之上再按照名稱注入。它在給類成員注入時不能單獨使用。但是在給方法參數注入時可以(稍後我們講)
 *          屬性:
 *              value:用於指定注入bean的id。
 *      Resource
 *          作用:直接按照bean的id注入。它可以獨立使用
 *          屬性:
 *              name:用於指定bean的id。
 *      以上三個注入都只能注入其他bean類型的數據,而基本類型和String類型無法使用上述註解實現。
 *      另外,集合類型的注入只能通過XML來實現。
 *
 *      Value
 *          作用:用於注入基本類型和String類型的數據
 *          屬性:
 *              value:用於指定數據的值。它可以使用spring中SpEL(也就是spring的el表達式)
 *                      SpEL的寫法:${表達式}
 *
 * 用於改變作用範圍的
 *      他們的作用就和在bean標籤中使用scope屬性實現的功能是一樣的
 *      Scope
 *          作用:用於指定bean的作用範圍
 *          屬性:
 *              value:指定範圍的取值。常用取值:singleton prototype
 *
 * 和生命週期相關 瞭解
 *      他們的作用就和在bean標籤中使用init-method和destroy-methode的作用是一樣的
 *      PreDestroy
 *          作用:用於指定銷燬方法
 *      PostConstruct
 *          作用:用於指定初始化方法
 */

3)程序包 javax.annotation 不可見,@Resource註解無法使用 【解決方法】

原文鏈接:https://blog.csdn.net/qq_37476266/article/details/89459250

4)spring中新註解

 * spring中的新註解
 * Configuration
 *     作用:指定當前類是一個配置類
 *     細節:當配置類作爲AnnotationConfigApplicationContext對象創建的參數時,該註解可以不寫。
 * ComponentScan
 *      作用:用於通過註解指定spring在創建容器時要掃描的包
 *      屬性:
 *          value:它和basePackages的作用是一樣的,都是用於指定創建容器時要掃描的包。
 *                 我們使用此註解就等同於在xml中配置了:
 *                      <context:component-scan base-package="com.itheima"></context:component-scan>
 *  Bean
 *      作用:用於把當前方法的返回值作爲bean對象存入spring的ioc容器中
 *      屬性:
 *          name:用於指定bean的id。當不寫時,默認值是當前方法的名稱
 *      細節:
 *          當我們使用註解配置方法時,如果方法有參數,spring框架會去容器中查找有沒有可用的bean對象。
 *          查找的方式和Autowired註解的作用是一樣的
 *  Import
 *      作用:用於導入其他的配置類
 *      屬性:
 *          value:用於指定其他配置類的字節碼。
 *                  當我們使用Import的註解之後,有Import註解的類就父配置類,而導入的都是子配置類
 *  PropertySource
 *      作用:用於指定properties文件的位置
 *      屬性:
 *          value:指定文件的名稱和路徑。
 *                  關鍵字:classpath,表示類路徑下
 */

5)spring與junit的整合

/**
 * 使用Junit單元測試:測試我們的配置
 * Spring整合junit的配置
 *      1、導入spring整合junit的jar(座標)
 *      2、使用Junit提供的一個註解把原有的main方法替換了,替換成spring提供的
 *             @Runwith
 *      3、告知spring的運行器,spring和ioc創建是基於xml還是註解的,並且說明位置
 *          @ContextConfiguration
 *                  locations:指定xml文件的位置,加上classpath關鍵字,表示在類路徑下
 *                  classes:指定註解類所在地位置
 *
 *   當我們使用spring 5.x版本的時候,要求junit的jar必須是4.12及以上
 */

6)代理對象proxy-JDK官方提供的

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

        /**
         * 動態代理:
         *  特點:字節碼隨用隨創建,隨用隨加載
         *  作用:不修改源碼的基礎上對方法增強
         *  分類:
         *      基於接口的動態代理
         *      基於子類的動態代理
         *  基於接口的動態代理:
         *      涉及的類:Proxy
         *      提供者:JDK官方
         *  如何創建代理對象:
         *      使用Proxy類中的newProxyInstance方法
         *  創建代理對象的要求:
         *      被代理類最少實現一個接口,如果沒有則不能使用
         *  newProxyInstance方法的參數:
         *      ClassLoader:類加載器
         *          它是用於加載代理對象字節碼的。和被代理對象使用相同的類加載器。固定寫法。
         *      Class[]:字節碼數組
         *          它是用於讓代理對象和被代理對象有相同方法。固定寫法。
         *      InvocationHandler:用於提供增強的代碼
         *          它是讓我們寫如何代理。我們一般都是些一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。
         *          此接口的實現類都是誰用誰寫。
         */
       IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:執行被代理對象的任何接口方法都會經過該方法
                     * 方法參數的含義
                     * @param proxy   代理對象的引用
                     * @param method  當前執行的方法
                     * @param args    當前執行方法所需的參數
                     * @return        和被代理對象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增強的代碼
                        Object returnValue = null;

                        //1.獲取方法執行的參數
                        float money = (float)args[0];
                        //2.判斷當前方法是不是銷售
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }

注意:匿名內部類使用外部類的變量,該成員變量必須是final修飾的

7)代理對象proxy-cglib

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

        /**
         * 動態代理:
         *  特點:字節碼隨用隨創建,隨用隨加載
         *  作用:不修改源碼的基礎上對方法增強
         *  分類:
         *      基於接口的動態代理
         *      基於子類的動態代理
         *  基於子類的動態代理:
         *      涉及的類:Enhancer
         *      提供者:第三方cglib庫
         *  如何創建代理對象:
         *      使用Enhancer類中的create方法
         *  創建代理對象的要求:
         *      被代理類不能是最終類
         *  create方法的參數:
         *      Class:字節碼
         *          它是用於指定被代理對象的字節碼。
         *
         *      Callback:用於提供增強的代碼
         *          它是讓我們寫如何代理。我們一般都是些一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。
         *          此接口的實現類都是誰用誰寫。
         *          我們一般寫的都是該接口的子接口實現類:MethodInterceptor
         */
        Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 執行北地阿里對象的任何方法都會經過該方法
             * @param proxy
             * @param method
             * @param args
             *    以上三個參數和基於接口的動態代理中invoke方法的參數是一樣的
             * @param methodProxy :當前執行方法的代理對象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增強的代碼
                Object returnValue = null;

                //1.獲取方法執行的參數
                Float money = (Float)args[0];
                //2.判斷當前方法是不是銷售
                if("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(12000f);
    }

8)AOP相關術語

Joinpoint(連接點):

所謂連接點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因爲 spring 只支持方法類型的

Pointcut(切入點):

所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義。

Advice(通知/增強):

所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。

通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知。

Introduction(引介):

引介是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運行期爲類動態地添加一些方 法或 Field。

Target(目標對象):

代理的目標對象。

Weaving(織入):

是指把增強應用到目標對象來創建新的代理對象的過程。

spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝載期織入。

Proxy(代理):

一個類被 AOP 織入增強後,就產生一個結果代理類。

Aspect(切面):

是切入點和通知(引介)的結合。

9)基於XML的AOP配置及切入點表達式

 <!-- 配置srping的Ioc,把service對象配置進來-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>

    <!--spring中基於XML的AOP配置步驟
        1、把通知Bean也交給spring來管理
        2、使用aop:config標籤表明開始AOP的配置
        3、使用aop:aspect標籤表明配置切面
                id屬性:是給切面提供一個唯一標識
                ref屬性:是指定通知類bean的Id。
        4、在aop:aspect標籤的內部使用對應標籤來配置通知的類型
               我們現在示例是讓printLog方法在切入點方法執行之前之前:所以是前置通知
               aop:before:表示配置前置通知
                    method屬性:用於指定Logger類中哪個方法是前置通知
                    pointcut屬性:用於指定切入點表達式,該表達式的含義指的是對業務層中哪些方法增強

            切入點表達式的寫法:
                關鍵字:execution(表達式)
                表達式:
                    訪問修飾符  返回值  包名.包名.包名...類名.方法名(參數列表)
                標準的表達式寫法:
                    public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                訪問修飾符可以省略
                    void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                返回值可以使用通配符,表示任意返回值
                    * com.itheima.service.impl.AccountServiceImpl.saveAccount()
                包名可以使用通配符,表示任意包。但是有幾級包,就需要寫幾個*.
                    * *.*.*.*.AccountServiceImpl.saveAccount())
                包名可以使用..表示當前包及其子包
                    * *..AccountServiceImpl.saveAccount()
                類名和方法名都可以使用*來實現通配
                    * *..*.*()
                參數列表:
                    可以直接寫數據類型:
                        基本類型直接寫名稱           int
                        引用類型寫包名.類名的方式   java.lang.String
                    可以使用通配符表示任意類型,但是必須有參數
                    可以使用..表示有無參數均可,有參數可以是任意類型
                全通配寫法:
                    * *..*.*(..)

                實際開發中切入點表達式的通常寫法:
                    切到業務層實現類下的所有方法
                        * com.itheima.service.impl.*.*(..)
    -->

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

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

10)通知類型

<!-- 配置srping的Ioc,把service對象配置進來-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>


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

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

            <!-- 配置後置通知:在切入點方法正常執行之後值。它和異常通知永遠只能執行一個
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->

            <!-- 配置異常通知:在切入點方法執行產生異常之後執行。它和後置通知永遠只能執行一個
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->

            <!-- 配置最終通知:無論切入點方法是否正常執行它都會在其後面執行
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->

            <!-- 配置環繞通知 詳細的註釋請看Logger類中-->
            <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

注環繞通知:

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

    /**
     * 環繞通知
     * 問題:
     *      當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了。
     * 分析:
     *      通過對比動態代理中的環繞通知代碼,發現動態代理的環繞通知有明確的切入點方法調用,而我們的代碼中沒有。
     * 解決:
     *      Spring框架爲我們提供了一個接口:ProceedingJoinPoint。該接口有一個方法proceed(),此方法就相當於明確調用切入點方法。
     *      該接口可以作爲環繞通知的方法參數,在程序執行時,spring框架會爲我們提供該接口的實現類供我們使用。
     *
     * spring中的環繞通知:
     *      它是spring框架爲我們提供的一種可以在代碼中手動控制增強方法何時執行的方式。
     */
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法執行所需的參數

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

            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方法開始記錄日誌了。。。最終");
        }
    }

11)基於XML的註解配置

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

    <!-- 配置spring開啓註解AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

12)動態代理的特點

字節碼隨用隨創建,隨用隨加載。

它與靜態代理的區別也在於此。因爲靜態代理是字節碼一上來就創建好,並完成加載。

裝飾者模式就是靜態代理的一種體現。

13)動態代理的兩種實現方式

基於接口的動態代理

提供者:JDK 官方的 Proxy 類。

要求:被代理類最少實現一個接口。

基於子類的動態代理

提供者:第三方的 CGLib,如果報 asmxxxx 異常,需要導入 asm.jar。

要求:被代理類不能用 final 修飾的類(最終類)。

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