在日常的开发中,需求的变动是很正常的,而且也是经常发生的,比如在我们的开发中有如下一个接口
public interface UserService {
User query(User user);
void save(User user);
}
有如下的实现类,最开始的实现是这样的。
public class UserServiceImpl implements UserService {
@Override
public User query(User user) {
System.out.println("查询用户成功:" + user.getName());
return user;
}
@Override
public void save(User user) {
System.out.println("保存用户成功:" + user.getName());
}
}
如果某一天,需求发生变化了,需要在每一个方法执行前都加上 验证权限 和在每一个方法 执行完成后 都添加上 操作日志 的业务逻辑,那你说很简单呀,直接像下面一样改动就好了。
public class UserServiceImpl implements UserService {
@Override
public User query(User user) {
System.out.println("验证权限!");
System.out.println("查询用户成功:" + user.getName());
System.out.println("记录操作日志!");
return user;
}
@Override
public void save(User user) {
System.out.println("验证权限!");
System.out.println("保存用户成功:" + user.getName());
System.out.println("记录操作日志!");
}
}
但是这样的改动其实存在一些问题的:
1、我们需要对每一个接口的每一个方法都添加相应的逻辑,而且代码都大同小异的,这就会导致代码重复严重
2、验权和记录操作日志这种操作其实是通用的问题,不应该和具体的方法,具体的接口耦合在一起,可以剥离出来形成单独的框架
3、比如后续需要调整验证权限相关的逻辑,涉及到的改动很大
4、如果UserService是其他第三方开发的,我们无权修改代码,这个时候直接改原始代码就不行了
其实上面的问题可以使用代理模式很好的解决
接下来我们看下静态代理
其实静态代理比较简单,为每一个类生成对应的代理类,逻辑相对来说比较简单,直接上代码
public class UserServiceImplProxy implements UserService {
private UserService userService;
public UserServiceImplProxy(UserService userService) {
this.userService = userService;
}
@Override
public User query(User user) {
System.out.println("验证权限!");
//委托给原始的UserService执行
User user1 = userService.query(user);
System.out.println("记录操作日志!");
return user1;
}
@Override
public void save(User user) {
System.out.println("验证权限!");
userService.save(user);
System.out.println("记录操作日志!");
}
}
其实静态代理也是很繁琐的,我们需要为每一个类,每一个方法添加相应的操作。
接下来分析下JDK动态代理
1、首先我们来看下类Proxy
可以看到其实有两个构造函数,一个私有的构造函数,一个protected的构造函数
/**
* Prohibits instantiation.
*/
private Proxy() {
}
/**
* Constructs a new {@code Proxy} instance from a subclass
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
* @param h the invocation handler for this proxy instance
*
* @throws NullPointerException if the given invocation handler, {@code h},
* is {@code null}.
*/
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
第二个构造函数接收一个InvocationHandler,我们再看看InvocationHandler是什么?
其实我们可以看到InvocationHandler是一个接口,就一个方法,invoke方法。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
下面我们基于动态代理实现上面的需求,首先定义一个InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
//代理对象
private Object targerObject;
public MyInvocationHandler(Object targetObject) {
this.targerObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法执行前逻辑
System.out.println(method.getName() + ":鉴权开始!");
//代理对象的方法
Object result = method.invoke(targerObject, args);
//执行后逻辑
System.out.println(method.getName() + ":执行完成");
System.out.println(method.getName() + ":已记录日志");
return result;
}
}
接下来我们看下如何生成代理类,代码如下
public class UserServiceDynamicProxy {
//被代理的类
private Object targetObject;
public UserServiceDynamicProxy(Object targetObject) {
this.targetObject = targetObject;
}
//获得代理类
public Object getProxyObject() {
//获得实现的接口
Class<?>[] interfaces = targetObject.getClass().getInterfaces();
//获得InvocationHandler
InvocationHandler invocationHandler = new MyInvocationHandler(targetObject);
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), interfaces, invocationHandler);
}
}
如何使用代理类
public class Test {
public static void main(String[] args) {
//设置saveGeneratedFiles值为true生成class字节码到文件,JDK8下,其他版本没试过
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
User user = new User();
user.setName("Ycb");
//原始类
UserService userService = new UserServiceImpl();
//调用动态代理生成代理类
UserService userServiceProxy = (UserService) new UserServiceDynamicProxy(userService).getProxyObject();
//调用相关方法
userServiceProxy.save(user);
}
}
输出结果,其实我们看到,经过上面的InvocationHandler的创建,代理类的生成,以及最终的调用,已经实现了相关的效果,而且我们没有和具体的接口,具体的方法绑定,更加灵活,那是怎么达到我们的需求的?
其实我们可以通过反编译生成的动态代理的字节码文件看一下究竟,使用的是JDK8,可以添加上 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 就可以拿到生成的代理类的字节码文件了,其他版本的JDK,估计不能这样玩,没试过。
我们可以看下生成的代理类,如下。
package com.sun.proxy;
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final User query(User var1) throws {
try {
return (User)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void save(User var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.Ycb.proxy.UserService").getMethod("query", new Class[]{Class.forName("com.Ycb.proxy.User")});
m3 = Class.forName("com.Ycb.proxy.UserService").getMethod("save", new Class[]{Class.forName("com.Ycb.proxy.User")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我们通过这个代理类可以看到,其实这个代理是实现了 我们 需要代理的类实现的接口,比如上面的UserService接口,而且继承了Proxy类。
1、最开始定义了几个静态的Method,static静态块中通过反射获取了类相关的方法。
2、接下来可以看到一个构造函数,调用了super(var1),其实就是Proxy中的protected接口,传递的InvocationHandler接口就是我们调用Proxy.newProxyInstance时设置的。
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
接下来就是一些方法,我们具体看save方法,其实代码很简单,就是一句super.h.invoke(this,m3,new Object[]{var1});
public final void save(User var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
super.h不就是我们自己定义的InvovationHandler吗?m3就是前面所讲的方法,参数就是我们传递的参数。其实到这里动态代理的过程已经很清楚了。
其实通过上面的调用,我们可以看到,第一个参数proxy,传进来的是this,其实也就是生成的代理类实例,method其实就是我们调用的method,第三个就是参数,
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法执行前逻辑
System.out.println(method.getName() + ":鉴权开始!");
//代理对象的方法
Object result = method.invoke(targerObject, args);
//执行后逻辑
System.out.println(method.getName() + ":执行完成");
System.out.println(method.getName() + ":已记录日志");
return result;
}
所以我们在invoke方法中执行如下语句时要特别小心,对象别弄错。
//代理对象的方法
Object result = method.invoke(targerObject, args);
我们也可以看到JDK动态代理的弊端,被代理的类需要实现接口,那不实现接口可以代理吗?后续单独开博文来讲解,CGLIB,后续再更新啦
个人学习记录有错误麻烦指出。