設計模式--->動態代理模式
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>
3.Spring動態代理實戰
- 目標類
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使用
- 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; //影響原始方法的返回值。 } }
- 配置檔案
<?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.切入點表示式
-
方法切入點表示式
execution(* *(…)) :所以方法進行增強
* login(…) :login方法進行增強
* login(String,String):login 方法且兩個引數為String的方法增強
* register(proxy.service.User) register方法且引數為User增強 -
類切入點
* proxy.service.impl.UserServiceImpl.*(…) 類中的所有方法加入了增強功能
*.UserServiceImpl.(…) 類只在一級包,對類所有方法增強
*…UserServiceImpl.(…) 類存在多級包,UserServiceImpl類下的所有方法增強 -
包切入點表示式
\ * proxy.service.impl..(…) 切入點包中的所有類,必須在impl中,不能在impl包的子包中
* proxy.service.impl….(…)
8.切入點函式
-
execution
可以滿足你的所有想象,可以做所有的事情:方法切入、類切入、包切入 -
args
execution(* *(String,String)) 等價於:args(String,String) -
within 和args互補
主要用於進行類、包切入點表示式的匹配
execution(*…UserServiceImpl.(…))等價於within(…UserServiceImpl)
execution(com.baizhiedu.proxy….(…))等價於ithin(com.baizhiedu.proxy…*) -
@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)"/>
- 切入點函式的邏輯運算
- 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.總結
面向切面程式設計的步驟:
- 目標類:UserServiceImpl
- 增強: MyArround implements MethodInterceptor
- 切入點配置 <aop:pointcut id=“pointcut” expression=“execution(* login(…))”/>
- 切入點和增強合併為切面進行增強。 <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中自動建立的,所以不會有靜態代理,類檔案數量過多,影響專案管理的問題。