本篇先通过带大家手动写一个简单版的仿JDK动态代理的实现,帮助大家理解jdk动态代理的原理
代理模式简单使用请参考本人的另一篇博客:代理模式简介及使用
静态代理
举个例子,我们需要实现一个数据库操作接口做数据的新增和修改,在执行新增和修改后打印方法执行的时间。
//数据库连接接口,定义新增和修改方法
public interface Connection {
void add(Object object);
void update(Object object);
}
下面模拟jdbc操作数据库的实现类JdbcTemplate,此处只简单打印日志
public class JdbcTemplate implements Connection {
@Override
public void add(Object object) {
System.out.println("add");
}
@Override
public void update(Object object) {
System.out.println("update");
}
}
静态代理想实现打印方法执行时间的需求,实现代码如下:
public class MyJdbcTemplate implements Connection {
private JdbcTemplate jdbcTemplate;
public MyJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void add(Object object) {
long start = System.currentTimeMillis();
this.jdbcTemplate.add(object);
long end = System.currentTimeMillis();
System.out.println(" addTime =" + (end - start));
}
@Override
public void update(Object object) {
long start = System.currentTimeMillis();
this.jdbcTemplate.update(object);
long end = System.currentTimeMillis();
System.out.println(" updateTime =" + (end - start));
}
}
上面的静态代理方式的弊端:如果这个类有100个方法需要计算执行时间,或者项目有100个类需要计算执行时间,那你需要写n多个类,和n多重复代码,会造成类爆发式增长、代码冗余重复等问题效率低下。(= =还有就是太low)
**
- 我们分析一下我们要做的事然后手写一个简单版的JDK动态代理:
**
jdk动态代理原理就是不用我们自己写增强的实现类(这里指MyJdbcTemplate),用代码自己生成这个MyJdbcTemplate.java文件,然后使用工具编译成class文件,使用类加载器加载到jvm,使用反射得到代理类的Class对象,通过Class对象获取代理类的Method对象,invok执行代理类的增强方法(不熟悉反射知识的小伙伴请自行学习)。
因为代理模式就是要在方法执行前后添加逻辑,增强被调用的方法,因此下面这段逻辑可以单独抽离出来一个接口,用来定义用户自定义的增强行为。同时要满足开闭原则,需要自定义或修改的代码都抽取出来
long start = System.currentTimeMillis();
add.invoke(object, param);
long end = System.currentTimeMillis();
System.out.println(" addTime =" + (end - start));
因此我们定义一个接口用来描述上面刚说的这种行为(参数就是Method对象和方法入参):
import java.lang.reflect.Method;
public interface InvocationHandler {
Object invoke(Method method, Object... args);
}
用户只需要定义一个类实现这个接口就可以在实现类的invoke方法写代理增强的逻辑了,被增强的对象也抽取invocationHandler的实现类中。修改后的 代理类如下MyJdbcTemplate
public class MyJdbcTemplate implements Connection {
private InvocationHandler invocationHandler ;
public MyJdbcTemplate(InvocationHandler invocationHandler ) {
this.invocationHandler = invocationHandler ;
}
@Override
public void add(Object param) {
try {
Method add = Connection.class.getMethod("add");
//重点体会
//long start = System.currentTimeMillis();
//add.invoke(object, param);
//long end = System.currentTimeMillis();
//System.out.println(" Time =" + (end - start));
invocationHandler.invoke(add,param);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void update(Object param) {
try {
Method update= Connection.class.getMethod("update");
invocationHandler.invoke(update,param);
} catch (Exception e) {
e.printStackTrace();
}
}
}
ok,接口的抽取完毕,接下来的事情就是怎样用代码生成这个java文件并编译加载到内存,反射获取到Class对象并获取到一个它的实例了。
根据我们之前的分析定义一个Proxy代理类,初步定义他应该有以下这个方法,入参接口Class对象和InvocationHandler 实现类,出参:反射得到的代理类的实例
public class Proxy {
public static Object newProxyInstance(Class interfaceClazz, InvocationHandler invocation) throws Exception {
......
}
}
下面的代码小伙伴们不必深究,看懂原理就行:用代码生成上面的MyJdbcTemplate 类,调用java自带的编译器生成class文件,然后使用类加载器加载到jvm,通过Class对象反射得到实例。方法入参需要InvocationHandler和接口,返回Object,方法如下
注:生成java源码可以使用
<!-- 生成java源码 -->
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.9.0</version>
</dependency>
其它的自带编译器javax.tools.JavaCompiler,URLClassLoader类加载器等知识可以自行百度了解。
import com.example.demo.proxy.JavaCompiler;
import com.squareup.javapoet.*;
import javax.lang.model.element.Modifier;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class Proxy {
public static Object newProxyInstance(Class interfaceClazz, InvocationHandler invocation) throws Exception {
TypeSpec.Builder typeSpec = TypeSpec.classBuilder("MyJdbcTemplate")
.addSuperinterface(interfaceClazz).addModifiers(Modifier.PUBLIC);
//添加属性
FieldSpec jdbcTemplate = FieldSpec.builder(InvocationHandler.class, "invocationHandler", Modifier.PRIVATE).build();
typeSpec.addField(jdbcTemplate);
//构造方法
MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(InvocationHandler.class, "invocationHandler")
.addStatement("this.invocationHandler = invocationHandler")
.build();
typeSpec.addMethod(constructorMethodSpec);
//增强接口的所有方法(添加执行时间)
Method[] methods = interfaceClazz.getDeclaredMethods();
for (Method method : methods) {
MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(method.getReturnType())
.addParameter(method.getParameterTypes()[0],"param")
.addCode("try {\n")
.addStatement("\t$T method = " + interfaceClazz.getName() + ".class.getMethod(\"" + method.getName() + "\",Object.class)", Method.class)
// 为了简单起见,这里参数直接写死为空
.addStatement("\tthis.invocationHandler.invoke(method, param)")
.addCode("} catch(Exception e) {\n")
.addCode("\te.printStackTrace();\n")
.addCode("}\n")
.build();
typeSpec.addMethod(methodSpec);
}
// 生成源码文件
JavaFile javaFile = JavaFile.builder("com.example.demo.proxy.instance", typeSpec.build()).build();
javaFile.writeTo(new File("D:\\myworkspase\\test\\src\\main\\java"));
// 编译
JavaCompiler.compile(new File("D:\\myworkspase\\test\\src\\main\\java\\com\\example\\demo\\proxy\\instance\\MyJdbcTemplate.java"));
URL[] urls = new URL[]{new URL("file:/D:\\myworkspase\\test\\src\\main\\java\\com\\example\\demo\\proxy\\instance\\MyJdbcTemplate.java")};
URLClassLoader classLoader = new URLClassLoader(urls);
Class clazz = classLoader.loadClass("com.example.demo.proxy.instance.MyJdbcTemplate");
Constructor constructor = clazz.getConstructor(InvocationHandler.class);
Object object = constructor.newInstance(invocation);
return object;
}
}
//编译类
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.IOException;
public class JavaCompiler {
public static void compile(File javaFile) throws IOException {
javax.tools.JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
Iterable iterable = fileManager.getJavaFileObjects(javaFile);
javax.tools.JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, iterable);
task.call();
fileManager.close();
}
}
接下来我们验证一下自己模仿jdk写的动态代理类的功能:
先写一个InvocationHandler实现类
public class JdbcInvocationHandler implements InvocationHandler {
private JdbcTemplate jdbcTemplate;
public JdbcInvocationHandler(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Object invoke(Method method, Object[] args) {
try {
long start = System.currentTimeMillis();
method.invoke(jdbcTemplate, args);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
接下来测试一下效果
public class MainTest {
public static void main(String[] args) throws Exception {
//自定义增强类
JdbcInvocationHandler handler = new JdbcInvocationHandler(new JdbcTemplate());
//获取动态代理实例
Object proxyInstance = Proxy.newProxyInstance(Connection.class, handler);
Connection connection = (Connection) proxyInstance;
Object param = new Object();
//调用方法
connection.add(param);
connection.update(param);
}
}
执行结果:打印形式或者线程休眠时间读者可以自行添加
add
耗时:0ms
update
耗时:0ms
到此为止我们实现了一个基本的基于接口的动态代理工具类,Jdk的动态代理和我们写的原理是一样的
我们来看一下jdk动态代理的IvocationHandler接口的方法就一个:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
多了一个Object proxy,这个是可以用于返回代理类等相关对象的。其它的定义和我们自己定义的一样。
jdk Proxy方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{}
和我们不一样的是接口Class传了一个数组,这个很好理解,一个类可以实现多个接口;还多了一个classLoder,用于用户自定义类加载器,上面我们的类加载器是写死的UrlClassLoder;其它基本和我们写的一样
jdk动态代理源码最重要的两句
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
跟我们之前分析的实现原理也是一样的。
好了本篇文章就到这里了,下一篇带大家一起看一下Jdk动态代理的源码