代理(pxoxy)無處不在,哪怕在現實生活中
在看源碼的時候,看到了aop,
就以此引出代理的知識擴展。
爲什麼要有代理呢,主要是不改變原來代碼的功能的前提下,增加一些特殊功能,比如記錄日誌,權限認證,事務相關。
代理有靜態代理和動態代理,而動態代理有兩種實現方式:jdk代理和cglib代理,下面分別介紹下其實現方式
0 靜態代理
這是很古老的方式了,一開始入行時用的代理就是這樣方式實現的。核心僞代碼如下
/**
*
* @author dgm
* @describe "產品接口"
* @date 2020年4月20日
*/
public interface ProductDao {
Product createProduct(String name, String size);
}
public class ProductDaoImpl implements ProductDao {
public ProductDaoImpl() {
}
public Product createProduct(String name, String size) {
return new Product(name,size);
}
}
/**
*
* @author dgm
* @describe "靜態代理"
* @date 2020年4月20日
*/
public class ProductStaticProxy implements ProductDao {
private ProductDao target;
public ProductStaticProxy(ProductDao target) {
this.target = target;
}
@Override
public Product createProduct(String name, String size) {
System.out.println("代理執行,開始工地幹活生產房子辦公樓產品了...");
return target.createProduct(name,size);
}
}
public class ProductStaticProxyTest {
public static void main(String[] args) {
// 目標對象
ProductDao dao = new ProductDaoImpl();
System.out.println("目標對象: " + dao.getClass());
// 獲取代理對象
ProductDao proxy = new ProductStaticProxy(dao);
System.out.println("代理對象: "+proxy.getClass());
// 代理生產產品
System.out.println(proxy.createProduct("手機屏幕", "4寸"));
}
}
執行結果圖示
此種方式簡單,但隨着項目量越來越大,維護的代碼也就越多,且接口改動也頻繁,迫切需要一種新的方式,這時動態代理登場了
1. jdk動態代理(自帶功能,不需要引入第三方包)
先介紹下java.lang.reflect.Proxy類,它提供的一個newProxyInstance方法用來創建一個對象的代理對象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
newProxyInstance方法用來返回一個代理對象,這個方法總共有3個參數,ClassLoader loader用來指明生成代理對象使用哪個類裝載器,Class<?>[] interfaces用來指明生成哪個對象的代理對象,通過接口指定,InvocationHandler h用來指明產生的這個代理對象要做什麼事情。所以我們只需要調用newProxyInstance方法就可以得到某一個對象的代理對象了。
示例代碼:
/**
*
* @author dgm
* @describe "jdk動態代理"
* @date 2020年4月20日
*/
public class ProductJdkDynamicProxy {
// 接收一個目標對象
private Object target;
public ProductJdkDynamicProxy(Object target) {
this.target = target;
}
// 返回對目標對象(target)代理後的對象(proxy)
public Object getProxyInstance() {
Object proxy = Proxy.newProxyInstance(target.getClass()
.getClassLoader(), // 目標對象使用的類加載器
target.getClass().getInterfaces(), // 目標對象實現的所有接口
new InvocationHandler() { // 執行代理對象方法時候觸發
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
System.out.println("原生jdk開啓生產產品線,拼命加班中");
// 執行目標對象方法
Object result = method.invoke(target, args);
System.out.println("原生jdk乾的不錯,結束生產產品");
return result;
}
});
return proxy;
}
}
public class ProductJdkDynamicProxyTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//目標對象
ProductDao target = new ProductDaoImpl();
System.out.println("目標對象: "+target.getClass());
//代理生產產品
ProductDao proxy = (ProductDao) new ProductJdkDynamicProxy(target).getProxyInstance();
System.out.println("代理對象: "+proxy.getClass());
//執行代理業務具體業務操作
System.out.println(proxy.createProduct("手機屏幕", "4寸"));
}
}
測試效果:
spring aop的實現方式之一就是它實現的
2. cglib代理
慢慢地工作到了一定階段,時代在發展爲了快速開發,項目化運作,開始大量引進框架和第三方庫,真的很好用(特別是項目開發),比如cglib,由於它是針對類實現的代理,故類不能是final類型。
/**
*
* @author dgm
* @describe "cglib動態代理"
* @date 2020年4月20日
*/
public class ProductCglibDynamicProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
try {
before(method);// 前置通知
Object ret = methodProxy.invokeSuper(obj, args);// 目標方法執行
after(method, ret);// 後置通知
return ret;
} catch (Exception e) {
exception();// 異常通知
} finally {
afterReturning();// 方法返回通知
}
return null;
}
// 前置增強
private void before(Method method) {
System.out.printf("cglib 動態代理:%s", method.getName());
}
// 後置增強
private void after(Method method, Object ret) {
System.out.printf(",cglib動態代理:%s, ret:%s", method.getName(), ret);
}
// 異常增強
private void exception() {
System.out.println(",程序出錯了!");
}
// after返回增強
private void afterReturning() {
System.out.println(",產品成型");
}
}
/**
*
* @author dgm
* @describe "測試cglib動態代理"
* @date 2020年4月20日
*/
public class ProductCglibDynamicProxyTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//代理對象
ProductDao proxy = CglibProxyFactory.create();
System.out.println("代理對象: "+proxy.getClass());
//執行代理業務具體業務操作
System.out.println(proxy.createProduct("手機屏幕", "4寸"));
}
}
運行結果:
spring-aop的另一實現也是基於此
簡單總結:
- 靜態代理實現較簡單,只要代理對象對目標對象進行包裝,即可實現增強功能,但靜態代理只能爲一個目標對象服務,如果目標對象過多,則會產生很多代理類。
- JDK動態代理需要目標對象實現業務接口,代理類只需實現InvocationHandler接口。
- 靜態代理在編譯時產生class字節碼文件,可以直接使用,效率高。
- 動態代理必須實現InvocationHandler接口,通過反射代理方法,比較消耗系統性能,但可以減少代理類的數量,使用更靈活。
- cglib代理無需實現接口,通過生成類字節碼實現代理,比反射稍快,不存在性能問題,但cglib會繼承目標對象,需要重寫方法,所以目標對象不能爲final類。
延伸閱讀:
0. java動態代理原理及解析 https://www.sohu.com/a/246547051_132276
1. Spring AOP 實現原理與 CGLIB 應用 https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/
2. 從動態代理到SpringAop以及AspectJ風格 https://segmentfault.com/a/1190000015262333
3. Java 動態代理機制分析及擴展,第 1 部分 https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html
4. spring AOP是什麼?你都拿它做什麼? https://zhuanlan.zhihu.com/p/28097563