設計模式--->動態代理模式

1.Spring動態代理的概念

概念:通過代理類爲原始類(目標類)增加額外功能

好處:利於原始類(目標類)的維護
從這點看和靜態代理一樣一樣的

2.Spring動態代理相關依賴的引入

<!--Spring aop支持-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.14.RELEASE</version>
        </dependency>

        <!--aspectj框架包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.8</version>
        </dependency>

        <!--編制切面包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.3</version>
        </dependency>
View Code

 

3.Spring動態代理實戰

  1. 目標類
    package proxy.service.impl;
    
    import proxy.service.UserService;
    
    public class UserServiceImpl implements UserService {
    
        @Override
        public void login(String username, String password) {
            System.out.println("UserServiceImpl.login 我是【service核心】");
        }
    }

    2. 方法前置增強代碼,需要實現MethodBeforeAdvice接口

package proxy.service.aop;import org.springframework.aop.BeforeAdvice;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;/**
 * @Classname MyAdvice
 * @Description 實現spring aop 包下MethodBeforeAdvice接口添加前置通知 這樣只要在切面上的方法在執行前
 * 均要增強: MyBefore.before 【service外圍】
 */

publicclass MyBefore implements MethodBeforeAdvice{/** * * @param method 目標方法 login() * @param args login()的參數username\password * @param target 目標對象userServiceImpl * @throws Throwable */@Override

public void before(Method method, Object[] args, Object target)throws Throwable { System.out.println("MyBefore.before 【service外圍】");
}
}

  3.配置文件

<?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:p="http://www.springframework.org/schema/p"
       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 https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--目標類 此時和代理類無關-->
    <bean id = "userService" class="proxy.service.impl.UserServiceImpl"/>
    <bean id = "orderService" class="proxy.service.impl.OrderServiceImpl"/>

    <!--通知類-->
    <bean id= "myBefore" class="proxy.service.aop.MyBefore"/>

    <!--aop配置標籤,會自動添加工作空間-->
    <aop:config>
        <!--定義接入點,即那些方法需要被加強-->
        <aop:pointcut id="pointcut" expression="execution(* *(..))"/>
        <!--切入點和通知的結合-->
        <aop:advisor advice-ref="myBefore" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

4. 測試

 /**
     * spring動態代理
     */
    @Test
    public void test3() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext6.xml");
        OrderService userService = (OrderService)ctx.getBean("orderService");
        userService.order();
    }

 

5.debug查看獲得的確實是代理類

1.Spring工廠通過原始對象的id值獲取的是代理對象

2.獲取代理對象後,可以通過聲明接口類型,進行對象的存儲。

在這裏插入圖片描述

 

 

 

 

 

 

 

 

 

 

 

 

4.Spring動態代理類在哪裏?

動態代理和之前的靜態代理不同,他不需要java文件然後通過類加載子系統,加載進運行時數據區,這裏是直接使用字節碼相關技術,在JVM內存中直接生成當前類的代理類。也就沒有那麼多的java類讓我們去管理,也就解決了這個痛點。另外他實用配置的方式對所有需要增強的類進行切入點的統一配置,這樣就沒有了代碼冗餘。

5.Spring MethodBeforeAdvice 小總結及不使用他的原因

那麼肯定會有人提出問題,這個只能對方法的前置進行增強太雞肋了。有沒有更好的辦法,可以在之前和之後均能增強呢?

您能想到的問題spring都想到了。接着往下看。使用MethodInterceptor這個就可以實現。 特別注意是這個報下的:import org.aopalliance.intercept.MethodInterceptor;spring使用了aop聯盟的相關接口來處理這個問題,並不是原生的spring的解決方案。

6.MethodInterceptor使用

  1. MyArround.java
    package proxy.service.aop;
    
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    
    public class MyArround implements MethodInterceptor {
        /**
         *
         * @param invocation 和MethodBeforeAdvice.before()方法中的Method參數一樣,只是更爲強大的封裝
         * @return Object 原始方法的返回值
         * @throws Throwable
         */
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("MyArround.invoke 【service外圍】前面的");
            Object res = null;
            try {//統一對異常進行拋出
                res = invocation.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            System.out.println("MyArround.invoke 【service外圍】後面的");
            return res;
            //return false; //影響原始方法的返回值。
        }
    }

     

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

    <!--目標類 此時和代理類無關-->
    <bean id = "userService" class="proxy.service.impl.UserServiceImpl"/>
    <bean id = "orderService" class="proxy.service.impl.OrderServiceImpl"/>

    <!--通知類-->
    <bean id= "myBefore" class="proxy.service.aop.MyBefore"/>

    <!--aop配置標籤,會自動添加工作空間-->
    <aop:config>
        <!--定義接入點,即那些方法需要被加強-->
        <aop:pointcut id="pointcut" expression="execution(* *(..))"/>
        <!--切入點和通知的結合-->
        <aop:advisor advice-ref="myBefore" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

  <aop:pointcut id=“pointcut” expression=“execution(* *(…))”/>表示對所以方法進行增強。

7.切入點表達式

  1. 方法切入點表達式
    execution(* *(…)) :所以方法進行增強
    * login(…) :login方法進行增強
    * login(String,String):login 方法且兩個參數爲String的方法增強
    * register(proxy.service.User) register方法且參數爲User增強

  2. 類切入點
    * proxy.service.impl.UserServiceImpl.*(…) 類中的所有方法加入了增強功能
    .UserServiceImpl.(…) 類只在一級包,對類所有方法增強
    …UserServiceImpl.(…) 類存在多級包,UserServiceImpl類下的所有方法增強

  3. 包切入點表達式
    \ * proxy.service.impl..(…) 切入點包中的所有類,必須在impl中,不能在impl包的子包中
    * proxy.service.impl….(…)

8.切入點函數

  1. execution
    可以滿足你的所有想象,可以做所有的事情:方法切入、類切入、包切入

  2. args
    execution(* *(String,String)) 等價於:args(String,String)

  3. within 和args互補
    主要用於進行類、包切入點表達式的匹配
    execution(* …UserServiceImpl.(…))等價於within(…UserServiceImpl)
    execution(
     com.baizhiedu.proxy….(…))等價於ithin(com.baizhiedu.proxy…*)

  4. @annotation

    package proxy;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @Classname Log
     * @Description 用於切面
     */
    @Target(ElementType.METHOD) //使用在方法上
    @Retention(RetentionPolicy.RUNTIME) //使用在運行時環境中
    public @interface Log {
    }

     

       使用時:<aop:pointcut id="" expression="@annotation(proxy.Log)"/>

  1. 切入點函數的邏輯運算
  • and與操作
    指的是 整合多個切入點函數一起配合工作,進而完成更爲複雜的需求
    login 同時 參數 2個字符串
    execution(* login(String,String))等價於: execution(* login(…)) and args(String,String)
    不能使用execution and execution 的語法形式。
  • or或操作
    register方法 和 login方法作爲切入點:
    execution(* login(…)) or execution(* register(…))

9.總結

面向切面編程的步驟:

  1. 目標類:UserServiceImpl
  2. 增強: MyArround implements MethodInterceptor
  3. 切入點配置 <aop:pointcut id=“pointcut” expression=“execution(* login(…))”/>
  4. 切入點和增強合併爲切面進行增強。 <aop:advisor advice-ref=“myBefore” pointcut-ref=“pointcut”/>
<?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:p="http://www.springframework.org/schema/p"
       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 https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--目標類 此時和代理類無關-->
    <bean id = "userService" class="proxy.service.impl.UserServiceImpl"/>
    <bean id = "orderService" class="proxy.service.impl.OrderServiceImpl"/>

    <!--通知類-->
  <!--  <bean id= "myBefore" class="proxy.service.aop.MyBefore"/>-->

    <bean id="myArround" class="proxy.service.aop.MyArround"/>
    <!--aop配置標籤,會自動添加工作空間-->
    <aop:config>
        <!--定義接入點,即那些方法需要被加強-->
        <aop:pointcut id="pointcut" expression="execution(* *(..))"/>
        <!--切入點和通知的結合-->
        <aop:advisor advice-ref="myArround" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

 

測試:

@Test
    public void test4() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext6.xml");
        UserService userService = (UserService)ctx.getBean("userService");
        userService.login("zhangsan","111111");
        userService.regester(new User(2, "222222"));
    }

 

測試結果

       MyArround.invoke 【service外圍】前面的
    UserServiceImpl.login 我是【service核心】
    MyArround.invoke 【service外圍】後面的
    MyArround.invoke 【service外圍】前面的
    UserServiceImpl.regester 我是【service核心】
    MyArround.invoke 【service外圍】後面的

 

動態字節碼技術原理:

動態代理細節分析:

1.spring創建的動態代理類在哪裏呢?

spring框架在運行的時,通過動態字節碼技術,在jvm中創建的,運行在jvm內部, 等程序結束後會和jvm類一起消失。

2.什麼叫動態字節碼技術?

Java運行一個類, 其實就是運行這個類的編譯後的字節碼--->object

java在類加載的時候會把字節碼加載到jvm的內存中。

3.那麼問題來了,動態字節碼的字節碼從哪裏來的呢?

(動態字節碼其實就是不需要(.Java文件生成.class文件的過程)字節碼的一個過程),它是由一些第三方動態字節碼框架(如ASM,Javassist,cglib)直接在jvm中生成字節碼(動態字節碼),當jvm結束,動態字節碼也跟着消失了。

結論:動態代理不需要定義類文件,都是在jvm中自動創建的,所以不會有靜態代理,類文件數量過多,影響項目管理的問題。

 

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