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中自動創建的,所以不會有靜態代理,類文件數量過多,影響項目管理的問題。