Java動態代理之通俗理解
代理模式是一種常用的設計模式,其作用就是為目標對象提供額外的訪問方式,在不修改目標對象的前提下,擴展目標對象的額外功能,比如統計執行時間,打印日誌等。
代理模式分為兩種:靜態代理和動態代理。
需求:假如不想改動原有代碼情況下,並記錄用戶保存方法的執行時間。示例代碼如下:
接口
public interface UserService {
public void saveUser(User user);
}
實現類
public class UserServiceImpl implements UserService { private UserDao userDaoImpl; @Override public void saveUser(User user) { userDaoImpl.save(user); } ... }
靜態代理實現
靜態代理是在程序運行前產生的,一般來說,代理類和目標類實現同一接口或者有相同的父類
代理類
public class UserServiceProxyImpl implements UserService { private UserService UserServiceImpl;//代理類持有一個委托類的對象引用 public UserServiceProxyImpl(UserService UserServiceImpl) { this.UserServiceImpl = UserServiceImpl; } @Override public void saveUser(User user) { long startTime = System.currentTimeMillis(); System.out.println("開始記錄時間"); delegate.dealTask(taskName); // 將請求分派給委托類處理 long endTime = System.currentTimeMillis(); System.out.println("保存用戶信息方法耗時" + (endTime-startTime) + "毫秒"); } }
產生代理對象的靜態工廠類
public class UserServiceProxyFactory {
public static UserService getInstance() {
return new UserServiceProxyImpl(new UserServiceImpl());
}
}
客戶端
public class ClientTest { public static void main(String[] args) { UserService UserServiceProxy = UserServiceProxyFactory.getInstance(); UserServiceProxy.saveUser(new User("1","張三")); } }
運行結果
開始記錄時間
保存用戶信息方法耗時0.01毫秒
靜態代理優缺點:
優點:
1、業務類UserServiceImpl只需要關註業務邏輯本身,保證了業務類的重用性。
2、客戶端Client和業務類UserServiceImpl之間沒有直接依賴關系,對客戶的而言屏蔽了具體實現。
缺點:
1、代理對象的一個接口只服務於一種接口類型的對象,靜態代理在程序規模稍大時就無法使用。
2、如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度
動態代理實現
動態代理是程序在運行過程中在JVM內部動態產生的代理對象,代理類可以實現對多種類的代理。
動態代理又分為兩種:JDK動態代理和CGLIB動態代理。
JDK動態代理
JDK動態代理需先聲明一個代理類和目標類之間的中間類,此中間類需要實現jdk中的一個接口InvocationHandler。源碼如下:
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
產生代理對象的中間類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxy implements InvocationHandler {
private Object target; //持有目標對象的引用
public JDKProxy(Object target){
this.target = target;
}
//創建代理對象
public Object createProxy(){
//1.得到目標對象的classloader
ClassLoader classLoader = target.getClass().getClassLoader();
//2.得到目標對象的實現接口的class[]
Class<?>[] interfaces = target.getClass().getInterfaces();
//3.第三個參數需要一個實現InvocationHandler接口的對象
//3-1.第一種寫法,讓當前類實現InvocationHandler,第三個參數寫this
return Proxy.newProxyInstance(classLoader, interfaces, this);
//3-2.第二種寫法,第三個參數用匿名內部類的形式,先註釋掉
/*return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("開始記錄時間");
Object ret = method.invoke(target, args); //執行目標對象方法,此處寫target如果寫proxy會死循環直到內存溢出
long endTime = System.currentTimeMillis();
System.out.println("保存用戶信息方法耗時" + (endTime-startTime) + "毫秒");
return ret;
}
});*/
}
/*
在代理實例上執行目標對象的方法
參數1 就是代理對象,一般不使用
參數2 它調用的方法的Method對象
參數3 調用的方法的參數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("開始記錄時間");
Object ret = method.invoke(target, args);//執行目標對象方法,此處寫target如果寫proxy會死循環直到內存溢出
long endTime = System.currentTimeMillis();
System.out.println("保存用戶信息方法耗時" + (endTime-startTime) + "毫秒");
return ret;
}
}
客戶端
public class ProxyTest {
@Test
public void test1() {
UserService userService = new UserServiceImpl(); //1.創建目標對象
JDKProxy factory = new JDKProxy(userService); // 2.通過JKDProxy完成代理對象創建
UserService userServiceProxy = (UserService)factory.createProxy();
userServiceProxy.saveUser(new User("1","張三"));
}
}
JDK動態代理中,需要關註的兩點:
1、Proxy.newProxyInstance(classLoader, interfaces, this); 底層是怎麽創建的代理對象
2、invoke方法是什麽時候執行的,誰來調用的此方法
<font color=red>解析1>></font>怎麽產生代理對象:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
// implement non-public interfaces that requires a special permission
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
//繼續看newInstance方法
private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
try {
return cons.newInstance(new Object[] {h} );
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString());
}
}
}
由此可以看出創建代理對象,是利用反射,先獲取目標對象的構造器,然後在通過構造反射生成代理對象
<font color=red>解析2>></font>invoke方法什麽時候被調用:
我們通過一個工具類把生成的代理對象的字節碼輸出到磁盤,然後通過反編譯來查看代理對象有哪些內容
工具類如下:
public class ProxyGeneratorUtils {
/**
* 把代理類的字節碼寫到硬盤上
* @param fileName 文件名
* @param path 路徑信息
* @param clazz 目標類的接口數組
*/
public static void writeProxyClassToHardDisk(String fileName, String path, Class<?>[] clazz) {
byte[] classFile = ProxyGenerator.generateProxyClass(fileName, clazz);
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//主方法
public static void main(String[] args) {
ProxyGeneratorUtils.writeProxyClassToHardDisk("$JDKProxy1","F:/$JDKProxy1.class",UserServiceImpl.class.getInterfaces());
}
}
運行main方法生成代理對象字節碼文件,用JD.exe反編譯打開如下
public final class $JDKProxy1 extends Proxy implements UserService {
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
public $JDKProxy1(InvocationHandler arg0) throws {
super(arg0);
}
public final void saveUser(User arg0) throws {
try {
super.h.invoke(this, m3, new Object[]{arg0});
} catch (RuntimeException | Error arg2) {
throw arg2;
} catch (Throwable arg3) {
throw new UndeclaredThrowableException(arg3);
}
}
...
用一張圖來說明調用userServiceProxy代理對象的saveUser()方法內部發生了什麽
CGLIB動態代理
cglib動態代理也需要一個產生代理對象的中間類,此類需實現MethodInterceptor接口,此接口在cglib包中,目前已經被spring整合,在spring-core核心包中<br>
產生代理對象的中間類
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Object target; //持有目標對象的引用
public CglibProxy(Object target){
this.target = target;
}
//創建代理對象
public Object createProxy(){
Enhancer enhancer = new Enhancer(); //1.創建Enhancer
enhancer.setSuperclass(target.getClass()); //2.傳遞目標對象的class
enhancer.setCallback(this); //3.設置回調操作(相當於InvocationHanlder)
return enhancer.create();
}
//相當於InvocationHanlder中的invoke
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("開始記錄時間");
Object ret = method.invoke(target, args);//執行目標對象方法,此處寫target如果寫proxy會死循環直到內存溢出
long endTime = System.currentTimeMillis();
System.out.println("保存用戶信息方法耗時" + (endTime-startTime) + "毫秒");
return ret;
}
}
客戶端
public class ProxyTest {
@Test
public void test1() {
UserService userService = new UserServiceImpl(); //1.創建目標對象
CglibProxy factory = new CglibProxy(customerService); // 2.通過CglibProxy完成代理對象創建
UserService userServiceProxy = (UserService)factory.createProxy();
userServiceProxy.saveUser(new User("1","張三"));
}
}
產生代理對象字節碼,用JD.exe反編譯如下
import java.lang.reflect.Method;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class UserServiceImpl$$EnhancerByCGLIB$$1772a9ea extends UserServiceImpl implements Factory {
...
public final void saveUser(User paramUser)
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null){
tmp4_1;
CGLIB$BIND_CALLBACKS(this);
}
if (this.CGLIB$CALLBACK_0 != null) return;
super.saveUser(paramUser);
}
...
}
JDK動態代理和CGLIB動態代理區別:
1、JDK動態代理是針對於接口代理,目標類必須實現了接口,產生的代理對象也會實現該接口。
2、CGLIB代理是采用繼承,產生的代理對象繼承於目標類,所以目標類和目標方法不能用final修飾。
Java動態代理之通俗理解