预备知识:反射、静态代理模式
由于静态代理的缺陷,我们要手动为每一个目标类编写对应的代理类。如果当前系统已经有成百上千个类,这会使得工作量成倍增加。
一、动态代理使用
1. 基本了解
- 动态代理和静态代理角色一样。
- 动态代理的代理类是动态生成的,不是我们直接写好的。
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理。以下使用的是基于接口的JDK动态代理。
使用前我们需要大概了解两个类,
Proxy
类和 InvocationHandler
类。
Proxy
类中有一个 newProxyInstance()
方法,来生成一个代理对象。
参数1 loader
: 用哪个类加载器去加载代理对象
参数2 interfaces
: 动态代理类需要实现的接口
参数3 h
: InvocationHandler
类,动态代理方法在执行时,会调用h里面的invoke方法去执行
Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
InvocationHandler
类是处理调用程序用的,我们需要实现 invoke() 方法。
public class ProxyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
2. 使用流程
与静态代理一样,需要定义一个接口。
public interface UserService {
void add();
void delete();
}
真实对象实现接口方法。
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("add");
}
@Override
public void delete() {
System.out.println("delete");
}
}
创建 ProxyInvocationHandler 类并且实现接口 InvocationHandler。
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的对象
private Object target;
//设置被代理的对象
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如何调用这个方法的?
Object result = method.invoke(target, args);
log(method.getName());
return result;
}
//代理对象增加的方法
private void log(String msg) {
System.out.println("执行了" + msg + "方法");
}
}
通过代理调用方法
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//动态代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//设置代理对象
pih.setTarget(userService);
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();
proxy.delete();
proxy.add();
}
}
运行结果:
动态代理和静态代理调用的过程:
二、源码思路
以上有个问题,就是生成的动态代理类,它是怎么调用 ProxyInvocationHandler
类中的 invoke()
方法的呢?
下面简单分析一下,最简单的方法就是使用断点看程序是怎么调用了什么方法。
直接来到源码重点的地方,发现这里生成了$Proxy0的字节码文件。
使用 createProxyClassFile()
方法调用 ProxyGenerator.generateProxyClass()
来生成的字节码文件。
private static void createProxyClassFile() {
String name = "ProxyTest";
byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{UserService.class});
FileOutputStream out = null;
try {
out = new FileOutputStream(name + ".class");
out.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != out) try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
打开生成的 ProxyTest.class 文件,我们只需要关注这里的 add 方法。
public final class ProxyTest extends Proxy implements UserService {
...
public final void add() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
...
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m6 = Class.forName("com.test.proxy.demo1.UserService").getMethod("query");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.test.proxy.demo1.UserService").getMethod("add");
m5 = Class.forName("com.test.proxy.demo1.UserService").getMethod("delete");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m4 = Class.forName("com.test.proxy.demo1.UserService").getMethod("update");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
注意看重点的方法
super.h.invoke(this, m3, (Object[])null);
这里的属性 h 是父类 Proxy 类中的 protected InvocationHandler h;
并且这个属性是我们在调用 Proxy.newProxyInstance()
方法时传入的参数,也就是ProxyInvocationHandler
类的对象。
所以这里调用的 invoke 方法就正是我们 ProxyInvocationHandler
类内部的方法。
也正是因为这样,所以程序才可以在调用实体的方法时,调用ProxyInvocationHandler
类内部的 invoke()
方法。
动态代理创建对象的过程:
三、总结
动态代理的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共也就就交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
参考文章:
Java 动态代理作用是什么?——bravo1988
通俗易懂的23种设计模式教学 ——遇见狂神说
Java JDK 动态代理(AOP)使用及实现原理分析 ——衣舞晨风