转载自: https://www.jianshu.com/p/3616c70cb37b

JDK自带的动态代理主要是指,实现了InvocationHandler接口的类,会继承一个invoke方法,通过在这个方法中添加某些代码,从而完成在方法前后添加一些动态的东西。JDK自带的动态代理依赖于接口,如果有些类没有接口,则不能实现动态代理。

1. 原理源码剖析

*  首先我们先来讲一下JDK动态代理的实现原理

1. 拿到被代理对象的引用,然后获取他的接口
     2. JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口
     3. 把被代理对象的引用拿到了
     4. 重新动态生成一个class字节码
     5. 然后编译

*  然后先实现一个动态代理,代码很简单了,就是

实现java.lang.reflect.InvocationHandler接口,

并使用java.lang.reflect.Proxy.newProxyInstance()方法生成代理对象

/**
* @author mark
* @date 2018/3/30
*/
public class JdkInvocationHandler implements InvocationHandler { private ProductService target; public Object getInstance(ProductService target){
this.target = target;
Class clazz = this.target.getClass();
// 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3
return Proxy.newProxyInstance(clazz.getClassLoader(),
clazz.getInterfaces(),
this);
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String currentDate = simpleDateFormat.format(new Date());
System.out.println("日期【"+currentDate + "】添加了一款产品"); return method.invoke(this.target,args);
}
}

被代理接口和实现

/**
* 模仿产品Service
* @author mark
* @date 2018-03-30
*/
public interface ProductService {
/**
* 添加产品
* @param productName
*/
void addProduct(String productName);
} /**
* @author mark
* @date 2018/3/30
*/
public class ProductServiceImpl implements ProductService{
public void addProduct(String productName) {
System.out.println("正在添加"+productName);
}
}

测试类

public class Test {
public static void main(String[] args) throws Exception {
ProductService productService = new ProductServiceImpl();
ProductService proxy = (ProductService) new JdkInvocationHandler().getInstance(productService);
proxy.addProduct("iphone"); // 这里我们将jdk生成的代理类输出了出来,方便后面分析使用
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()}); FileOutputStream os = new FileOutputStream("Proxy0.class");
os.write(bytes);
os.close();
}
}

结果输出

日期【2018-03-30】添加了一款产品
正在添加iphone Process finished with exit code 0

上面我们实现动态动态代理的时候输出了代理类的字节码文件,现在来看一下字节码文件反编译过后的内容

import com.gwf.jdkproxy.ProductServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException; // 继承了Proxy类
public final class $Proxy0 extends Proxy implements ProductServiceImpl {
private static Method m1;
private static Method m8;
private static Method m2;
private static Method m3;
private static Method m5;
private static Method m4;
private static Method m7;
private static Method m9;
private static Method m0;
private static Method m6; public $Proxy0(InvocationHandler var1) throws {
super(var1);
} ....
.... /**
* 这里是代理类实现的被代理对象的接口的相同方法
*/
public final void addProduct(String var1) throws {
try {
// super.h 对应的是父类的h变量,他就是Proxy.nexInstance方法中的InvocationHandler参数
// 所以这里实际上就是使用了我们自己写的InvocationHandler实现类的invoke方法
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
} public final Class getClass() throws {
try {
return (Class)super.h.invoke(this, m7, (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"));
m8 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notify");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("addProduct", Class.forName("java.lang.String"));
m5 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE);
m4 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE, Integer.TYPE);
m7 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("getClass");
m9 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notifyAll");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m6 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

补充一下上面代码注释中的super.h

protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
} // 这个方法是Proxy的newProxyInstance方法,主要就是生成了上面的动态字节码文件
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h); 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 {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
} final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 重点看这里,将我们传来的InvocationHandler参数穿给了构造函数
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
以上就是jdk动态代理的内部实现过程,最后再次将上面的原理声明一遍,强化记忆
1.拿到被代理对象的引用,然后获取他的接口 (Proxy.getInstance方法)
2.JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口 (上面的反编译文件中实现了同样的接口)
3.把被代理对象的引用拿到了(上面被代理对象中在静态代码块中通过反射获取到的信息,以及我们实现的JdkInvocationHandler中的target)
4.重新动态生成一个class字节码
5.然后编译
 

2.  自己手写一个动态代理

(声明:本代码只用作实例,很多细节没有考虑进去,比如,多接口的代理类,Object类的其他默认方法的代理,为确保原汁原味,一些模板引擎和commons工具类也没有使用;觉得不足的老铁们可以随意完善,记得评论区留言完善方法哦)

我们使用jdk代理的类名和方法名定义,已经执行思路,但是所有的实现都自己来写;

首先先定义出类结构

/**
* 自定义类加载器
* @author gaowenfeng
* @date 2018/3/30
*/
public class MyClassLoader extends ClassLoader { /**
* 通过类名称加载类字节码文件到JVM中
* @param name 类名
* @return 类的Class独享
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
/**
* @desc 自己实现的代理类,用来生成字节码文件,并动态加载到JVM中
* @author gaowenfeng
* @date 2018/3/30
*/
public class MyProxy {
/**
* 生成代理对象
* @param loader 类加载器,用于加载被代理类的类文件
* @param interfaces 被代理类的接口
* @param h 自定义的InvocationHandler接口,用于具体代理方法的执行
* @return 返回被代理后的代理对象
* @throws IllegalArgumentException
*/
public static Object newProxyInstance(MyClassLoader loader,
Class<?>[] interfaces,
MyInvocationHandler h)
throws IllegalArgumentException{
/**
* 1.生成代理类的源代码
* 2.将生成的源代码输出到磁盘,保存为.java文件
* 3.编译源代码,并生成.java文件
* 4.将class文件中的内容,动态加载到JVM中
* 5.返回被代理后的代理对象
*/ return null; }
}
/**
* 自定义类加载器
* @author gaowenfeng
* @date 2018/3/30
*/
public class MyClassLoader extends ClassLoader { /**
* 通过类名称加载类字节码文件到JVM中
* @param name 类名
* @return 类的Class独享
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
/**
* @author gaowenfeng
* @date 2018/3/30
*/
public class CustomInvocationHandler implements MyInvocationHandler {
private ProductService target; public Object getInstance(ProductService target){
this.target = target;
Class clazz = this.target.getClass();
// 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3
// 这里的MyClassLoader先用new的方式保证编译不报错,后面会修改
return MyProxy.newProxyInstance(new MyClassLoader(),
clazz.getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String currentDate = simpleDateFormat.format(new Date());
System.out.println("日期【"+currentDate + "】添加了一款产品"); return method.invoke(this.target,args);
}
}

接下来我们来按照步骤一步一步的完善我们的类

  1. 生成代理类的源文件
/**
* 生成代理类的源代码
* @return
*/
private static String genSesource(Class<?> interfaces){
StringBuilder src = new StringBuilder();
src.append("package com.gwf.custom;").append(ln)
.append("import java.lang.reflect.Method;").append(ln)
.append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append(ln)
.append("private MyInvocationHandler h;").append(ln)
.append("public $Proxy0(MyInvocationHandler h){").append(ln)
.append("this.h=h;").append(ln)
.append("}").append(ln); for(Method method:interfaces.getMethods()){
src.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("() {").append(ln)
.append("try {").append(ln)
.append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\");").append(ln)
.append("this.h.invoke(this, m, new Object[]{});").append(ln)
.append("}catch (Throwable e){").append(ln)
.append("e.printStackTrace();").append(ln)
.append("}").append(ln)
.append("}").append(ln);
}
src.append("}"); return src.toString(); }

2.  将源文件保存到本地

            // 1.生成代理类的源代码
String src = genSesource(interfaces);
// 2.将生成的源代码输出到磁盘,保存为.java文件
String path = MyProxy.class.getResource("").getPath();
File file = new File(path+"$Proxy0.java"); FileWriter fw = new FileWriter(file);
fw.write(src);
fw.close();

3.  编译源代码,并生成.java文件

            // 3.编译源代码,并生成.java文件
// 获取java编译器
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
// 标注java文件管理器,用来获取java字节码文件
StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);
Iterable iterable = manager.getJavaFileObjects(file); // 创建task,通过java字节码文件将类信息加载到JVM中
JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);
// 开始执行task
task.call();
// 关闭管理器
manager.close();

4. 将class文件中的内容,动态加载到JVM中

public class MyClassLoader extends ClassLoader {

    private String baseDir;

    public MyClassLoader(){
this.baseDir = MyClassLoader.class.getResource("").getPath();
} /**
* 通过类名称加载类字节码文件到JVM中
* @param name 类名
* @return 类的Class独享
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 获取类名
String className = MyClassLoader.class.getPackage().getName()+"."+name;
if(null == baseDir) {
throw new ClassNotFoundException();
} // 获取类文件
File file = new File(baseDir,name+".class");
if(!file.exists()){
throw new ClassNotFoundException();
} // 将类文件转换为字节数组
try(
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
){
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer))!=-1){
out.write(buffer,0,len);
} // 调用父类方法生成class实例
return defineClass(className,out.toByteArray(),0,out.size());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

5.  返回被代理后的代理对象

Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
return c.newInstance(h);

最后看一下总体的MyProxy类 的 newProxyInstance方法

public static Object newProxyInstance(MyClassLoader loader,
Class<?> interfaces,
MyInvocationHandler h)
throws IllegalArgumentException{
/**
* 1.生成代理类的源代码
* 2.将生成的源代码输出到磁盘,保存为.java文件
* 3.编译源代码,并生成.java文件
* 4.将class文件中的内容,动态加载到JVM中
* 5.返回被代理后的代理对象
*/
try {
// 1.生成代理类的源代码
String src = genSesource(interfaces);
// 2.将生成的源代码输出到磁盘,保存为.java文件
String path = MyProxy.class.getResource("").getPath();
File file = new File(path+"$Proxy0.java"); FileWriter fw = new FileWriter(file);
fw.write(src);
fw.close(); // 3.编译源代码,并生成.java文件
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);
Iterable iterable = manager.getJavaFileObjects(file); JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);
task.call();
manager.close(); // 4.将class文件中的内容,动态加载到JVM中
Class proxyClass = loader.findClass("$Proxy0"); // 5.返回被代理后的代理对象
Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
} return null; }

测试运行

public class CustomClient {
public static void main(String[] args){
ProductService productService = new ProductServiceImpl();
ProductService proxy = (ProductService) new CustomInvocationHandler().getInstance(productService);
proxy.addProduct();
}
}

运行结果

日期【2018-03-30】添加了一款产品
正在添加iphone Process finished with exit code 0

总结:以上通过理解jdk动态代理的原理,自己手写了一个动态代理,里面涉及到的重点主要是代理类字节码的生成(这里采用通过反射强行生成源文件并编译的方法,其实应该可以直接生成字节码文件的,有兴趣的同学可以尝试)和将生成的类动态加载到JVM中(本次试验由于测试,比较简单,直接将类名硬编码到了系统里,正常应该是自动加载),虽然还不完善,但是对于理解原理应该是有狠多帮助了,欢迎同学们评论区留言评论给出更好的建议

最后附上源码地址:https://github.com/MarkGao11520/my-proxy

作者:Meet相识_bfa5
链接:https://www.jianshu.com/p/3616c70cb37b
來源:简书

java jdk动态代理学习记录的更多相关文章

  1. JAVA 动态代理学习记录

    打算用JAVA实现一个简单的RPC框架,看完RPC参考代码之后,感觉RPC的实现主要用到了两个方面的JAVA知识:网络通信和动态代理.因此,先补补动态代理的知识.---多看看代码中写的注释 参考:Ja ...

  2. Java JDK 动态代理使用及实现原理分析

    转载:http://blog.csdn.net/jiankunking   一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理 ...

  3. java jdk动态代理模式举例浅析

    代理模式概述 代理模式是为了提供额外或不同的操作,而插入的用来替代”实际”对象的对象,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色. java中常用的动态代理模式为jdk动态代理和c ...

  4. Java JDK动态代理解析

    动态代理虽不常自己实现,但在Spring或MyBatis中都有重要应用.动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问.Spring常JDK和CGLIB动态代理 ...

  5. Java,JDK动态代理的原理分析

    1. 代理基本概念: 以下是代理概念的百度解释:代理(百度百科) 总之一句话:三个元素,数据--->代理对象--->真实对象:复杂一点的可以理解为五个元素:输入数据--->代理对象- ...

  6. JDK动态代理学习心得

    JDK动态代理是代理模式的一种实现方式,其只能代理接口.应用甚为广泛,比如我们的Spring的AOP底层就有涉及到JDK动态代理(此处后面可能会分享) 1.首先来说一下原生的JDK动态代理如何实现: ...

  7. Java JDK 动态代理实现和代码分析

    JDK 动态代理 内容 一.动态代理解析 1. 代理模式 2. 为什么要使用动态代理 3. JDK 动态代理简单结构图 4. JDK 动态代理实现步骤 5. JDK 动态代理 API 5.1 java ...

  8. java jdk动态代理

    在面试的时候面试题里有一道jdk的动态代理是原理,并给一个事例直接写代码出来,现在再整理一下 jdk动态代理主要是想动态在代码中增加一些功能,不影响现有代码,实现动态代理需要做如下几个操作 1.首先必 ...

  9. java JDK动态代理的机制

    一:前言 自己在稳固spring的一些特性的时候在网上看到了遮掩的一句话“利用接口的方式,spring aop将默认通过JDK的动态代理来实现代理类,不适用接口时spring aop将使用通过cgli ...

随机推荐

  1. 主席树 || 可持久化线段树 || BZOJ 3653: 谈笑风生 || Luogu P3899 [湖南集训]谈笑风生

    题面:P3899 [湖南集训]谈笑风生 题解: 我很喜欢这道题. 因为A是给定的,所以实质是求二元组的个数.我们以A(即给定的P)作为基点寻找答案,那么情况分两类.一种是B为A的父亲,另一种是A为B的 ...

  2. Java随机字符串:随机数字字符串,工具类

    Java中生成随机数,字符串的工具类 1. 调用方法需要传入生成字符串的长度和需要的类型 生成随机数字 生成随机字母字符串 生成随机字符串+数字等 ......... 2. 总共8种类型,具体看工具类 ...

  3. 20175303 2018-2019-2 《Java程序设计》第2周学习总结

    •总体的说,第2周主要学习了课本第二章第三章的内容,这周的学习简直比上一周的简单而且容易理解得多.上周的各种安装,注册,眼花缭乱,很早就开始弄,但各种出错,到最后都想放弃了,好在有同学的帮助,帮我正确 ...

  4. 关于STM32时钟系统

    初学STM32,感觉最蛋疼的是它的时钟系统,每次看到它的那个时钟树就有点晕,虽然看了很多这方面的资料,甚至也已经写过很多STM32的模块代码,做过一些小项目,但一直还是对这一块模模糊糊,似懂非懂,所以 ...

  5. jsp (二) 练习

    package cn.sasa.serv; import java.io.IOException; import java.sql.SQLException; import java.util.Lis ...

  6. vue中生成二维码

    <template> <div id="qrcode" ></div> </template> <script> imp ...

  7. Cartographer源码阅读(5):PoseGraph位姿图

    PoseGraph位姿图 mapping2D::PoseGraph类的注释: // Implements the loop closure method called Sparse Pose Adju ...

  8. hibernate重要知识点总结

    一.使用注解方式-----实体和表之间的映射 配置spring的applicationContext.xml文件: <bean id="sessionFactory" cla ...

  9. javascript DOM 常用方法

    前端HTML+CSS+JS流程导图:https://www.processon.com/view/link/5ad1c2d0e4b0b74a6dd64f3c HTML+CSS+Javascript+j ...

  10. Spring Boot 全局异常捕获

    import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.Control ...