动态代理由来
Java程序员应该都知道,静态代理就是使用一个代理类(Proxy)来完成想要完成的事情,Proxy类通过编译器编译成class文件,当系统运行时,此class已经存在。这种静态的代理模式在增强现有的接口业务功能方面有很大的优点,但是大量使用静态代理,会使系统内的类大规模爆发,不易维护。
为了解决这个问题,就有了动态创建Proxy的想法,在运行状态中,动态的创建一个Proxy代理类,使用完之后就会销毁,这样就不用维护大量的代理类。
静态代理案例
创建一个车票接口,定义一个卖票的方法。
package com.doaredo.test.proxy;
public interface Ticket {
public void sell();
}
创建一个车站类,实现卖票的接口,让其拥有卖票的功能。
package com.doaredo.test.proxy;
public class Station implements Ticket {
@Override
public void sell() {
System.out.println("车站售票");
}
}
创建一个售票代理类,实现车票接口,通过构造函数注入车站类,让其可以代理车站售票。
package com.doaredo.test.proxy;
public class StationProxy implements Ticket {
private Station station;
// 构造函数注入车站类,使其拥有售票功能
public StationProxy(Station station){
this.station = station;
}
@Override
public void sell() {
System.out.println("代理点开始售票");
station.buy();
System.out.println("代理点售票完成");
}
}
创建测试类,使用售票代理类来完成售票。
package com.doaredo.test.proxy;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception {
// 通过构造函数传入车站对象,使代理对象拥有售票功能
StationProxy stationProxy = new StationProxy(new Station());
stationProxy.sell();
}
}
静态代理比较简单,代理类只需要实现目标类的接口,然后通过构造函数注入目标类,这样便可以完成目标类方法的调用,代理类可以在目标方法执行的前后做一些额外的功能,从而实现代理功能。
InvocationHandler的由来
代理就是在调用目标方法之前或者之后做一些额外的功能,但是我们在写完代理类后,还需要再去调用指定的目标方法,上面静态代理的目标方法为sell方法,这个目标方法是由我们自己定义的,不能统一。
为了构造出具有通用性和简单性的代理类,于是将调用目标方法统一交给一个管理器,让这个管理器统一的调用目标方法。这个管理器就是InvocationHandler。由于目标方法名称不同,怎么才能统一调用呢?在Java中使用反射就可以来实现统一的调用目标方法,将目标方法统一为反射中的Method。
JDK动态代理
使用JDK动态代理来实现本站售票的代理类。
创建一个车票接口,定义一个卖票的方法。
package com.doaredo.test.proxy;
public interface Ticket {
public void sell();
}
创建一个车站类,实现卖票的接口,让其拥有卖票的功能。
package com.doaredo.test.proxy;
public class Station implements Ticket {
@Override
public void sell() {
System.out.println("车站售票");
}
}
创建InvocationHandler的实现类,通过构造函数注入目标类(即被代理的类,这里Station车站类将被代理)。
实现invoke方法,Method参数就是要执行的目标方法,通过method.invoke从而完成目标方法的调用,在目标方法调用的前后来完成代理功能。
package com.doaredo.test.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class InvocationHandlerImpl implements InvocationHandler {
private Station station;
public InvocationHandlerImpl(Station station){
this.station = station;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk动态代理开始");
method.invoke(station, args);
System.out.println("jdk动态代理结束");
return null;
}
}
创建测试类,完成JDK动态代理的调用,同时将JDK动态生成的代理类的class文件输出。
- 既然JDK要动态的帮我们生成代理类,动态的生成代理类后需要进行加载,所以需要一个类加载器ClassLoader。
- 需要目标类实现的接口。(为什么要接口?)
- 执行目标方法,完成代理功能的管理类,InvocationHandler的实现。
package com.doaredo.test.proxy;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception {
Station station = new Station();
// 类加载器
ClassLoader classLoader = station.getClass().getClassLoader();
// 接口信息
Class[] interfaces = station.getClass().getInterfaces();
// InvocationHandler的实现,完成代理功能,执行目标方法
InvocationHandler handler = new InvocationHandlerImpl(station);
// Jdk动态生成代理对象
Object o = Proxy.newProxyInstance(classLoader,interfaces,handler);
Ticket ticket = (Ticket)o;
ticket.sell();
generateClassFile(station.getClass(), "JdkProxyStation");
}
// 生成class文件
public static void generateClassFile(Class clazz, String proxyName) throws Exception {
byte[] bytes = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String path = clazz.getResource(".").getPath();
FileOutputStream fos = new FileOutputStream(path + proxyName + ".class");
fos.write(bytes);
fos.flush();
fos.close();
}
}
使用Jdk动态代理,并生成动态产生的代理类的class文件,通过反编译查看生成的代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.doaredo.test.proxy.Ticket;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class JdkProxyStation extends Proxy implements Ticket {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public JdkProxyStation(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} 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 void sell() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (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"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.doaredo.test.proxy.Ticket").getMethod("sell");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我们可以看到,Jdk生成的代理类都继承了Proxy,同时实现了我们目标类的接口,其中m3就是要执行的目标方法。其它的都是Object类中的方法。我们看一下是怎么执行目标方法的:
super.h其实就是指的Proxy中的InvocationHandler,也就是在这里将m3传递给了InvocationHandler的invoke方法,从而可以通过m3来完成目标方法的调用。
平时我们看的资料都说Jdk动态代理只能是接口,这下大家都知道原因了吧,因为Java是单继承的,而Jdk动态代理中,Jdk自己已经继承了Proxy,所以我们不能再使用继承了,而只能使用接口的形式。
代理类特点:
- 继承自Proxy,实现了定义的接口
- 类中的所有方法都是final的
- 所有的方法实现都统一调用了InvocationHandler的invoke()方法
Cglib动态代理
Jdk提供的动态代理有个特点:某个类必须有实现的接口,而生成的代理类也只能代理接口定义的方法。也就是说,如果某个类没有实现接口,那么这个类就不能使用Jdk产生动态代理了。
Cglib可以在没有接口的情况下完成动态代理,原理差不多,后面我们自己来生成代理类实现代理功能。
下面是Cglib的使用:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>idea-demo</groupId>
<artifactId>idea-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--sm包 -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>8.0.1</version>
</dependency>
<!-- cglib包 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
package com.doaredo.test.proxy;
public class CglibStation {
public void sell() {
System.out.println("车站售票");
}
}
package com.doaredo.test.proxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理开始");
methodProxy.invokeSuper(o, objects);
System.out.println("cglib代理结束");
return null;
}
}
package com.doaredo.test.proxy;
import net.sf.cglib.proxy.Enhancer;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
public class Test {
public static void main(String[] args) throws Exception {
CglibStation cglibStation = new CglibStation();
CglibProxy cglibProxy = new CglibProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cglibStation.getClass());
enhancer.setCallback(cglibProxy);
CglibStation proxy = (CglibStation) enhancer.create();
proxy.sell();
}
}
更深层次理解动态代理
要明白动态代理,不能只是表面的怎么使用动态代理,记住一点,动态代理最重要的就是:动态的创建一个类,加入需要添加的逻辑代码,并且执行被代理对象的逻辑。
既然是动态的创建类,据我所知,操作Java常用的有Javassist和ASM,Javassist相对ASM使用上比较简单,下面就使用Javassist来学习动态代理模式是怎么实现的。
加入javassist依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
定义接口
// 先定义一个接口
public interface Service {
public void hello(String msg);
public void bye(String name);
}
使用javassist动态创建类,注意:并非是代理类,是用javassist动态创建Service接口的实现类:
- 创建一个ServiceImpl类,实现Service接口
- 在实现类的hello方法中,输出hello+msg参数,在javassist中,$1表示第一个参数
package com.doaredo.test.proxy;
import javassist.*;
import org.junit.Test;
public class JavassistProxy {
@Test
public void test() throws Exception {
ClassPool classPool = ClassPool.getDefault();
// 创建一个类
CtClass clazz = classPool.makeClass("ServiceImpl");
// 获取定义的接口,转换成CtClass
CtClass interfaceCtClass = classPool.get(Service.class.getName());
// 给类添加接口
clazz.addInterface(interfaceCtClass);
// 逻辑代码
String code = "{System.out.println(\"hello:\"+$1);}";
// String类型的CtClass
CtClass stringCtClass = classPool.get(String.class.getName());
// 实现接口中的hello方法
CtMethod hello = CtNewMethod.make(CtClass.voidType, "hello",
new CtClass[]{stringCtClass},// 参数
new CtClass[0],// 异常
code,// 逻辑代码
clazz);// 实现方法的类
clazz.addMethod(hello);
// 将CtClass转换成Java的Class
Class cla = classPool.toClass(clazz);
// 实例化Class执行对应的方法
Service service = (Service) cla.newInstance();
service.hello("doaredo");
}
}
上面利用Javassist动态的创建了一个类,并且实现了Service接口的hello方法,但是有几个问题:
- 获取定义接口的时候,Service.class.getName()是写死的,如果接口不叫Service那么就不好使了
- 逻辑代码是通过手写的,编译器无法识别,容易出错
改进后代码如下:
- 将接口和逻辑代码都通过参数形式传递进来
package com.doaredo.test.proxy;
import javassist.*;
import org.junit.Test;
public class JavassistProxy {
public static <T> T createClass(Class<T> interfaceClass, String code) throws Exception {
ClassPool classPool = ClassPool.getDefault();
// 创建一个类
CtClass clazz = classPool.makeClass("ServiceImpl");
// 获取定义的接口,转换成CtClass
CtClass interfaceCtClass = classPool.get(interfaceClass.getName());
// 给类添加接口
clazz.addInterface(interfaceCtClass);
// String类型的CtClass
CtClass stringCtClass = classPool.get(String.class.getName());
CtMethod hello = CtNewMethod.make(CtClass.voidType, "hello",
new CtClass[]{stringCtClass},// 参数
new CtClass[0],// 异常
code,// 逻辑代码
clazz);// 实现方法的类
clazz.addMethod(hello);
// 实例化Class
Class cla = classPool.toClass(clazz);
return (T) cla.newInstance();
}
@Test
public void test2() throws Exception {
Service service = createClass(Service.class,"{System.out.println(\"hello:\"+$1);}");
service.hello("doaredo");
// 缺点:换方法后不能用了
//service.bye("doaredo");
}
}
经过改进后发现这里hello方法与是写死的,我们的目的是要能代理接口的所有方法,以及自定义逻辑代码。既然要自定义逻辑代码,那么就定义一个执行逻辑代码的接口,将逻辑代码以参数形式传递进来。
public interface InvocationHandler {
Object invoke(String method, Object args[]);
}
接下来动态的实现接口中的所有的方法:
- 创建接口的实现类
- 给类添加InvocationHandler属性,通过InvocationHandler可以调用实现的逻辑代码
- 遍历接口中所有的方法,并实现所有的方法,所有方法都调用InvocationHandler的invoke方法来执行代理逻辑以及目标方法
- 将InvocationHandler实现类注入到handler属性中
- $args是javassist中的语法,相当于Object args[]
package com.doaredo.test.proxy;
import javassist.*;
import org.junit.Test;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.stream.Collectors;
public class JavassistProxy {
private static int count = 0;
public static<T> T proxy(Class<T> interfaceClass, InvocationHandler h) throws Exception {
ClassPool classPool = ClassPool.getDefault();
// 创建一个类
CtClass clazz = classPool.makeClass("&proxy&"+(count++)+"."+interfaceClass.getName());
clazz.addInterface(classPool.get(interfaceClass.getName()));
// 添加handler属性
CtField ctField = CtField.make("public com.doaredo.test.proxy.JavassistProxy.InvocationHandler handler=null;", clazz);
clazz.addField(ctField);
// 遍历接口中的所有方法,并实现接口中的方法
for (Method m : interfaceClass.getMethods()){
// 获取方法的返回类型
CtClass returnType = classPool.get(m.getReturnType().getName());
// 获取方法名称
String name = m.getName();
// 获取方法参数
CtClass[] parameters = ctClass(classPool, m.getParameterTypes());
// 获取异常
CtClass[] errors = ctClass(classPool, m.getExceptionTypes());
String code = "";
if(Void.class.equals(returnType)){
// 没有返回值
code = "this.handler.invoke(\"%s\", $args);";
}else{
code = "return ($r)this.handler.invoke(\"%s\", $args);";
}
// 实现接口中的方法
CtMethod hello = CtNewMethod.make(returnType, name,
parameters,
errors,
String.format(code, m.getName()),
clazz);
clazz.addMethod(hello);
}
// 将CtClass转换成Java的Class
Class cla = classPool.toClass(clazz);
// 实例化Class
Object o = cla.newInstance();
// 获取实例化类的handler属性,并且赋值为h。h为传入进来的逻辑代码实现类
cla.getField("handler").set(o, h);
// 将生成的类的代码写入到项目的target目录下
byte[] bytes = clazz.toBytecode();
Files.write(Paths.get(System.getProperty("user.dir") + "/target/" + clazz.getName() + ".class"), bytes);
return (T) o;
}
private static CtClass[] ctClass(ClassPool cp, Class[] classes) {
CtClass[] result = Arrays.stream(classes).map(c -> {
try {
return cp.get(c.getName());
} catch (NotFoundException e) {
e.printStackTrace();
}
return null;
}).collect(Collectors.toList()).toArray(new CtClass[0]);
return result;
}
@Test
public void test3() throws Exception {
Service service = proxy(Service.class, new InvocationHandler() {
@Override
public Object invoke(String method, Object[] args) {
if(method.equals("hello")){
System.out.println("hello " + args[0]);
}else if(method.equals("bye")){
System.out.println("bye " + args[0]);
}
return null;
}
});
service.hello("doaredo");
service.bye("doaredo");
}
}
注意上面invoke方法中的method参数,是一个String类型,需要通过判断具体方法名称来决定具体的代理逻辑,显然不太合适,注意:上面只是利用Javassist动态的给Service接口创建了一个实现类,并没有给哪个类做代理。
下面我们稍微改造一下代码,来对具体的实现类进行动态代理:
package com.doaredo.test.proxy;
import javassist.*;
import org.junit.Test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Collectors;
public class JavassistProxy {
/**
* 支持所有接口代理
* 不直接传代码,容易出错
*/
private static int count = 0;
public static<T> T proxy(Class<T> interfaceClass, InvocationHandler h) throws Exception {
ClassPool classPool = ClassPool.getDefault();
// 创建一个类
CtClass clazz = classPool.makeClass("&proxy&"+(count++)+"."+interfaceClass.getName());
clazz.addInterface(classPool.get(interfaceClass.getName()));
// 添加handler属性
CtField ctField = CtField.make("public com.doaredo.test.proxy.JavassistProxy.InvocationHandler handler=null;", clazz);
clazz.addField(ctField);
// 遍历接口中的所有方法,并实现接口中的方法
Method[] methods = interfaceClass.getMethods();
for (int i = 0; i < methods.length; i++){
Method m = methods[i];
// 获取方法的返回类型
CtClass returnType = classPool.get(m.getReturnType().getName());
// 获取方法名称
String name = m.getName();
// 获取方法参数
CtClass[] parameters = ctClass(classPool, m.getParameterTypes());
// 获取异常
CtClass[] errors = ctClass(classPool, m.getExceptionTypes());
String code = "";
String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
String classParams = "new Class[0]";
if (m.getParameterTypes().length>0) {
for (Class param : m.getParameterTypes()){
classParams = classParams.equals("new Class[0]") ? param.getName() + ".class" : classParams + "," + param.getName() + ".class";
}
classParams = "new Class[]{"+classParams+"}";
}
// 方法字段
String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), m.getName(), classParams);
// 为代理类添加反射方法字段
CtField methodField=CtField.make(methodFieldBody,clazz);
clazz.addField(methodField);
if(Void.class.equals(returnType)){
// 没有返回值
// $0=this / $1,$2,$3... 代表方法参数
code = "&0.handler.invoke("+methodField.getName()+", $args);";
}else{
code = "return ($r)this.handler.invoke("+methodField.getName()+", $args);";
}
// 实现接口中的方法
CtMethod hello = CtNewMethod.make(returnType, name,
parameters,
errors,
String.format(code, m),
clazz);
clazz.addMethod(hello);
}
// 将生成的类的代码写入到项目的target目录下
byte[] bytes = clazz.toBytecode();
Files.write(Paths.get(System.getProperty("user.dir") + "/target/" + clazz.getName() + ".class"), bytes);
// 将CtClass转换成Java的Class
Class cla = classPool.toClass(clazz);
// 实例化Class
Object o = cla.newInstance();
// 获取实例化类的handler属性,并且赋值为h。h为传入进来的逻辑代码实现类
cla.getField("handler").set(o, h);
return (T) o;
}
private static CtClass[] ctClass(ClassPool cp, Class[] classes) {
CtClass[] result = Arrays.stream(classes).map(c -> {
try {
return cp.get(c.getName());
} catch (NotFoundException e) {
e.printStackTrace();
}
return null;
}).collect(Collectors.toList()).toArray(new CtClass[0]);
return result;
}
@Test
public void test3() throws Exception {
Service service = proxy(Service.class, new InvocationHandlerImpl(new ServiceImpl()));
service.hello("doaredo");
service.bye("doaredo");
}
// 实现代理接口,完成代理逻辑
public class InvocationHandlerImpl implements InvocationHandler {
private Service service;
public InvocationHandlerImpl(Service service) {
this.service = service;
}
@Override
public Object invoke(Method method, Object[] args) {
try {
System.out.println("代理前");
// 通过反射执行被代理对象的方法
method.invoke(service, args);
System.out.println("代理后");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
// 代理类接口
public interface InvocationHandler {
Object invoke(Method method, Object args[]);
}
// 服务接口
public interface Service {
public void hello(String msg);
public void bye(String name);
}
// 服务实现
public class ServiceImpl implements Service {
@Override
public void hello(String msg) {
System.out.println("hello " + msg);
}
@Override
public void bye(String name) {
System.out.println("bye " + name);
}
}
}
下面就是使用Javassist来实现JDK的动态代理,反编译生成的代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package &proxy&0.com.doaredo.test.proxy;
import com.doaredo.test.proxy.JavassistProxy.InvocationHandler;
import com.doaredo.test.proxy.JavassistProxy.Service;
import java.lang.reflect.Method;
public class JavassistProxy$Service implements Service {
public InvocationHandler handler = null;
private static Method m0 = Class.forName("com.doaredo.test.proxy.JavassistProxy$Service").getDeclaredMethod("hello", String.class);
private static Method m1 = Class.forName("com.doaredo.test.proxy.JavassistProxy$Service").getDeclaredMethod("bye", String.class);
public void hello(String var1) {
this.handler.invoke(m0, new Object[]{var1});
}
public String bye(String var1) {
return (String)this.handler.invoke(m1, new Object[]{var1});
}
public JavassistProxy$Service() {
}
}
初次看会觉得比较绕,前面Jdk生成的代理类跟我们自己生成的代理类原理都是一样的,但是Jdk中还生成了Object类中的所有方法,同时继承了Proxy类。