java動態代理和spring動態代理對比
Java編譯器編譯好Java檔案之後,產生.class 檔案在磁碟中。這種class檔案是二進位制檔案,內容是隻有JVM虛擬機器能夠識別的機器碼。JVM虛擬機器讀取位元組碼檔案,取出二進位制資料,載入到記憶體中,解析.class 檔案內的資訊,生成對應的 Class物件:
.java檔案到jjvm的過程圖:
class位元組碼檔案是根據JVM虛擬機器規範中規定的位元組碼組織規則生成的、具體class檔案是怎樣組織類資訊的,可以參考 此博文:或者是。
在執行期的程式碼中生成二進位制位元組碼
由於JVM通過位元組碼的二進位制資訊載入類的,那麼,如果我們在執行期系統中,遵循Java編譯系統組織.class檔案的格式和結構,生成相應的二進位制資料,然後再把這個二進位制資料載入轉換成對應的類,這樣,就完成了在程式碼中,動態建立一個類的能力了。
由於本人知識有限就不胡給大家介紹如何利用位元組碼增強,實現動態生成.clas檔案了,大家可以參考http://www.blogjava.net/hello-yun/archive/2014/09/28/418365.html
今天就介紹如何利用jdk和cglib生成動態代理,:
首先我們編寫目標類,即要被增強的類:
package com.leige.proxy; public class UserServiceImpl implements UserService { @Override public void addUser() { // TODO Auto-generated method stub System.out.println("adddUser------"); } }
介面:切面類即增強程式碼類利用動態代理可以動態的將增強程式碼加入目標類中:package com.leige.proxy; public interface UserService { public void addUser(); }
package com.leige.proxy; public class MyAspect { public void before(){ System.out.println("前置通知"); } public void after(){ System.out.println("後置通知"); } }
工廠類:package com.leige.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import org.springframework.util.MethodInvoker; public class MyFactoryBean { /** * @return * 利用jdk實現動態代理, * jdk實現動態代理,必須要保證目標類有介面,否則無法實現動態代理 */ public Object getBean(){ //目標類,即被增強類 UserService service=new UserServiceImpl(); //切面類,即增強程式碼類 MyAspect aspect=new MyAspect(); //jdk動態代理實現 return Proxy.newProxyInstance(this.getClass().getClassLoader(), service.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub //前置通知 aspect.before(); //執行目標類的方法: Object obj=method.invoke(service, args); //後置通知 aspect.after(); return obj; } }); } /** * @return * 通過cglib實現動態代理,不需要目標類有介面,代理之後返回的是目標類的子類,所以目標類不是final的, * */ public Object getBeanByCglib(){ //目標類,即被增強類 UserService service=new UserServiceImpl(); //切面類,即增強程式碼類 MyAspect aspect=new MyAspect(); Enhancer enhancer=new Enhancer(); //設定父類,即被代理類,cglib的代理物件通過子類實現的 //因為我們的切入點是方法,所以使用MethodInterceptor enhancer.setSuperclass(service.getClass()); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // TODO Auto-generated method stub //前置通知 aspect.before(); //執行目標類的方法有兩種方式 //Object obj=method.invoke(service, args); /*執行的是代理物件的父類,在此代理物件是proxy,proxy的父類就是目標類即UserService * 這是cglib的工作機制,即生成目標的的子類,作為代理物件 */ Object obj=methodProxy.invokeSuper(proxy, args); //後置通知 aspect.after(); return obj; } }); //建立代理類: Object obj=enhancer.create(); return obj; } }
測試類:beans.xmlpackage com.leige.proxy; import static org.junit.Assert.*; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:com/leige/proxy/beans.xml") public class TestApp { @Autowired UserService userService; @Test public void test() { userService.addUser(); } }
<?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: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/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean id="myfactory" class="com.leige.proxy.MyFactoryBean"/> <bean id="userService" factory-bean="myfactory" factory-method="getBeanByCglib"/> </beans>
以上是未使用springaop的動態代理,下面將介紹spring實現動態代理的方法:第一種通知再切面類中配置,注意都是有介面的,這裡不再寫介面程式碼:
實現類:
package com.leige.aspect; public class StudentServiceImpl implements StudentService { public void add() { System.out.println("addd student"); } }
切面類,這種方法通知都是解除安裝程式碼中,xml中只需配置切面和切入點即可:package com.leige.aspect; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class Myaspect implements MethodInterceptor { public Object invoke(MethodInvocation mi) throws Throwable { // TODO Auto-generated method stub System.out.println("前置通知"); Object obj=mi.proceed(); System.out.println("後置通知"); return obj; } }
xml配置:第二種方法,通知型別在xml中指定,但是程式碼實現也是在切面類中:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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 "> <bean id="studentServiceId" class="com.leige.aspect.StudentServiceImpl"></bean> <!-- 配置切面bean --> <bean id="myAspect" class="com.leige.aspect.Myaspect"></bean> <aop:config> <!-- 切入點 expression定義切入點 advisor 定義通知 --> <aop:pointcut expression="execution(* com.leige.aspect.*ServiceImpl.*(..))" id="myPointCut"/> <aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/> </aop:config> </beans>
實現類:
切面類:package com.leige.aspect2; public class PersonServiceImpl implements PersonService { public void addPerson() { System.out.println("person --------addperson"); } }
package com.leige.aspect2; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; /** * @author * * * * <!-- 宣告通知型別 #1 前置通知 , 目標方法之前執行。 * 第一個引數為JoinPoint,可以獲得目標方法名等。 <aop:before method="myBefore" pointcut-ref="myPonitCut"/> #2 後置通知,目標方法之後執行,可以獲得返回值。 通過“returning”屬性配置第二個引數的名稱,獲得返回值的,型別必須Object * 第一個引數為:JoinPoint * 第二個引數為:Object xxx <aop:after-returning method="myAfterReturning" pointcut-ref="myPonitCut" returning="xxx"/> #3 環繞通知, 目標方法前後 方法要求:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ 執行目標方法:joinPoint.proceed(); <aop:around method="myAround" pointcut-ref="myPonitCut"/> #4 丟擲異常通知,目標方法出現異常時才執行。通過“throwing”屬性配置第二個引數的名稱,獲得具體的異常資訊,型別必須是Throwable * 第一個引數為:JoinPoint * 第二個引數為:Throwable e <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPonitCut" throwing="e"/> * */ public class MyAspect { public void myBefore(JoinPoint joinPoint){ System.out.println("前置通知, 方法名稱:" + joinPoint.getSignature().getName()); } public void myAfterReturning(JoinPoint joinPoint,Object xxx){ System.out.println("後置通知, 返回值:" + xxx); } public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("前"); //必須執行目標方法 Object obj = joinPoint.proceed(); System.out.println("後"); return obj; } public void myAfterThrowing(JoinPoint joinPoint, Throwable e){ System.out.println("丟擲異常通知, " + e.getMessage()); } public void myAfter(){ System.out.println("最終"); } }
xml配置方法·:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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 "> <!-- 配置增強bean --> <bean id="personServiceId" class="com.leige.aspect2.PersonServiceImpl"></bean> <!-- 配置切面bean ,包含多個通知--> <bean id="myAspect" class="com.leige.aspect2.MyAspect"></bean> <aop:config> <!-- 切面 通知配置--> <aop:aspect ref="myAspect"> <!-- 定義切入點 ,表示這個bean下的所有方法都是切入點--> <aop:pointcut expression="execution(* com.leige.aspect2.PersonServiceImpl.*(..))" id="myPointCut"/> <!-- 前置通知 <!-- 宣告通知型別 #1 前置通知 , 目標方法之前執行。 * 第一個引數為JoinPoint,可以獲得目標方法名等。 <aop:before method="myBefore" pointcut-ref="myPonitCut"/> #2 後置通知,目標方法之後執行,可以獲得返回值。 通過“returning”屬性配置第二個引數的名稱,獲得返回值的,型別必須Object * 第一個引數為:JoinPoint * 第二個引數為:Object xxx <aop:after-returning method="myAfterReturning" pointcut-ref="myPonitCut" returning="xxx"/> #3 環繞通知, 目標方法前後 方法要求:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ 執行目標方法:joinPoint.proceed(); <aop:around method="myAround" pointcut-ref="myPonitCut"/> #4 丟擲異常通知,目標方法出現異常時才執行。通過“throwing”屬性配置第二個引數的名稱,獲得具體的異常資訊,型別必須是Throwable * 第一個引數為:JoinPoint * 第二個引數為:Throwable e <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPonitCut" throwing="e"/> --> <aop:before method="myBefore" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config> </beans>
相比jdk的動態代理,spring遮蔽了底層的實現,使我們可以簡單實現aop程式設計,但是我們也需要對動態代理有一定的基礎瞭解,這是我學習經驗,我自己也是理解不是很透側,並沒有具體應用,所以先寫在這裡,以後在具體研究,歡迎大家矯正,spring中的動態代理採取了兩種實現方式:
當攔截物件實現了介面時,生成方式是用JDK的Proxy類。當沒有實現任何介面時用的是GCLIB開源專案生成的攔截類的子類.