java jdk动态代理学习记录

时间:2022-12-29 11:38:30

转载自: 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&comma;JDK动态代理的原理分析

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

  6. JDK动态代理学习心得

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

  7. java jdk动态代理

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

  8. java JDK动态代理的机制

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

  9. jdk动态代理学习

    在jdk的好多底层代码中很多都使用jdk的动态代理,下面就写写简单的代码来look look. 老规矩先上代码: public interface SayDao { public String say ...

随机推荐

  1. ASP&period;NET MVC 6 一些不晓得的写法

    今天在看 Scott Guthrie 的一篇博文<Introducing ASP.NET 5>,在 MVC 6 中,发现有些之前不晓得的写法,这边简单记录下,算是对自己知识的补充,有些我并 ...

  2. 阿里云CentOS6&period;5搭建服务器JDK&plus;tomcat&plus;MySQL

    阿里云ECS,计划安装jdk:MySQL:tomcat: 一.yum Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的S ...

  3. C&num;函数参数

    当函数接受参数时,必须指定下属内容 函数在其定义中指定参数列表,以及这些参数的类型 在每个函数调用中匹配参数列表 参数匹配:当调用函数时,必须使参数与函数定义中指定的参数完全匹配,这意味着要匹配参数的 ...

  4. 怎么在php里面利用str&lowbar;replace防注入

    <php    /**    * 安全过滤函数    *    * @param $string    * @return string    */    function safe_repla ...

  5. &lbrack;Unity 5&period;2&rsqb; The imported type &grave;UnityEngine&period;Advertisements&period;ShowResult&&num;39&semi; is defined multiple times

    unityAds报这个错: The imported type `UnityEngine.Advertisements.ShowResult' is defined multiple times go ...

  6. hdu 4762 &amp&semi;&amp&semi; 2013 ACM&sol;ICPC 长春网络赛解题报告

    这次的答案是猜出来的,如果做得话应该是应该是一个几何概型的数学题: 答案就是:n/(m^(n-1)); 具体的证明过程: 1.首先枚举这M个点中的的两个端点,概率是:n*(n-1); 2.假设这个蛋糕 ...

  7. iosOC&sol;C不可变数组排序

    //1.回顾C数组排序 int a[6] = {1,4,3,5,6,2}; //选择 for (int i =0; i<6-1; i++) { for (int j = i+1; j<6; ...

  8. 剖析Asp&period;Net Web API路由系统---WebHost部署方式

    上一篇我们剖析了Asp.Net路由系统,今天我们再来简单剖析一下Asp.Net Web API以WebHost方式部署时,Asp.Net Web API的路由系统内部是怎样实现的.还是以一个简单实例开 ...

  9. TFS应用经验-大型项目数据仓库抽取导致的TFS应用无法访问

    在超过千人使用的TFS生产环境中,每天周期性出现无法正常查看工作项白板.无法签入代码.无法进行自动化构建.无法进行报表数据的查看等情况,真是一个让人焦灼的问题.作为TFS平台支持和运维的团队,也想进了 ...

  10. 基于RC4加密算法的图像加密

    基于RC4加密算法的图像加密 某课程的一个大作业内容,对图像加密.项目地址:https://gitee.com/jerry323/RC4_picture 这里使用的是RC4(流.对称)加密算法,算法流 ...