本帖最后由 我是楠楠 于 2018-4-14 15:23 编辑
【郑州校区】Java动态代理之通俗理解
代理模式介绍代理模式是一种常用的设计模式,其作用就是为目标对象提供额外的访问方式,在不修改目标对象的前提下,扩展目标对象的额外功能,比如统计执行时间,打印日志等。代理模式分为两种:静态代理和动态代理。需求:假如不想改动原有代码情况下,并记录用户保存方法的执行时间。示例代码如下: 接口 [AppleScript] 纯文本查看 复制代码 public interface UserService {
public void saveUser(User user);
} 实现类 [AppleScript] 纯文本查看 复制代码 public class UserServiceImpl implements UserService {
private UserDao userDaoImpl;
@Override
public void saveUser(User user) {
userDaoImpl.save(user);
}
...
}
静态代理实现静态代理是在程序运行前产生的,一般来说,代理类和目标类实现同一接口或者有相同的父类 代理类
[AppleScript] 纯文本查看 复制代码 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) + "毫秒");
}
} 产生代理对象的静态工厂类
[AppleScript] 纯文本查看 复制代码 public class UserServiceProxyFactory {
public static UserService getInstance() {
return new UserServiceProxyImpl(new UserServiceImpl());
}
} 客户端
[AppleScript] 纯文本查看 复制代码 public class ClientTest {
public static void main(String[] args) {
UserService UserServiceProxy = UserServiceProxyFactory.getInstance();
UserServiceProxy.saveUser(new User("1","张三"));
}
} 运行结果
[AppleScript] 纯文本查看 复制代码 开始记录时间
保存用户信息方法耗时0.01毫秒 静态代理优缺点:优点:1、业务类UserServiceImpl只需要关注业务逻辑本身,保证了业务类的重用性。2、客户端Client和业务类UserServiceImpl之间没有直接依赖关系,对客户的而言屏蔽了具体实现。缺点: 1、代理对象的一个接口只服务于一种接口类型的对象,静态代理在程序规模稍大时就无法使用。2、如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度 动态代理实现动态代理是程序在运行过程中在JVM内部动态产生的代理对象,代理类可以实现对多种类的代理。动态代理又分为两种:JDK动态代理和CGLIB动态代理。 JDK动态代理JDK动态代理需先声明一个代理类和目标类之间的中间类,此中间类需要实现jdk中的一个接口InvocationHandler。源码如下:
[AppleScript] 纯文本查看 复制代码 package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
} 产生代理对象的中间类
[AppleScript] 纯文本查看 复制代码 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;
}
} 客户端
[AppleScript] 纯文本查看 复制代码 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>怎么产生代理对象:
[AppleScript] 纯文本查看 复制代码 @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方法什么时候被调用:我们通过一个工具类把生成的代理对象的字节码输出到磁盘,然后通过反编译来查看代理对象有哪些内容工具类如下:
[AppleScript] 纯文本查看 复制代码 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反编译打开如下
[AppleScript] 纯文本查看 复制代码 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()方法内部发生了什么file://D:/2017%E5%B9%B4%E5%BA%A6/2017%E5%B9%B4%E5%BA%A6%E5%B7%A5%E4%BD%9C%E8%B5%84%E6%96%99/%E6%95%99%E7%A0%94%E9%83%A8%E6%8F%90%E4%BE%9B%E7%9A%84%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0%E6%B1%87%E6%80%BB/%E9%83%91%E5%B7%9E-3%E6%9C%88%E4%BB%BD-%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/Java%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E4%B9%8B%E9%80%9A%E4%BF%97%E7%90%86%E8%A7%A3/img/001.bmp?lastModify=1523690291 CGLIB动态代理cglib动态代理也需要一个产生代理对象的中间类,此类需实现MethodInterceptor接口,此接口在cglib包中,目前已经被spring整合,在spring-core核心包中<br>产生代理对象的中间类
[AppleScript] 纯文本查看 复制代码 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;
}
} 客户端
[AppleScript] 纯文本查看 复制代码 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反编译如下
[AppleScript] 纯文本查看 复制代码 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修饰。 河南省郑州市 高新区长椿路11号大学科技园(西区)东门8号楼三层 联系电话 0371-56061160/61/62 来校路线 地铁一号线梧桐街站A口出
|