转载自: 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. Prime Flip AtCoder - 2689

    发现我们每次区间取反,相邻位置的正反关系只有两个位置发生改变 我们定义bi为ai和ai-1的正反关系,即ai=ai-1时bi=0,否则bi=1,每次取反l~r,b[l]和b[r+1]会发生改变 容易发 ...

  2. python->解析xml文件

    '''"D:\three_test\gpn_InternetGatewayDevice_v2.xml" <SOAP-ENV:Envelope> <SOAP-ENV ...

  3. 最全的MonkeyRunner自动化测试从入门到精通(5)

    夜神模拟器的安装与配置步骤一:我们为什么会选择使用夜神模拟器呢? 众所周知,Android studio的模拟器运行速度也很快,可以媲美真机.虽然其运行速度很快,可以满足我们测试的需求.但仍存在以下问 ...

  4. 从Windows到linux小记

    从Windows到linux小记 年后疯狂加班,趁着喘息的时间,更新一下安装linux的艰辛路程. 周四晚上,公司举办活动,好不容易从加班的节奏暂时脱离出来,我这人就是不能闲,只要一闲下来就会做die ...

  5. Spring Boot事务管理(下)

    在上两篇 Spring Boot事务管理(上)和Spring Boot事务管理(中)的基础上介绍注解@Transactional. 5 @Transactional属性 属性 类型 描述 value ...

  6. 【托业】【新东方托业全真模拟】TEST07~08-----P5~6

    unless ---conj:barring(除非,不包括)perp+名词短语 be capable of doing 有能力做某事 qualified commensurate with 与……相应 ...

  7. 1、vue 笔记之 组件

    1.组件个人理解:  <组件>是页面的一部分,将界面切分成部分,每部分称为 <组件>   2.组件化思想:          //2.1.定义一个全局的组件,组件支持‘驼峰命名 ...

  8. vue--vuex详解

    安装vuex npm install vuex --save Vuex 什么是Vuex?  官方说法:Vuex 是一个专为 Vue.js应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的 ...

  9. WIN7无法访问共享打印机及文件的解决办法

    故障: 在网上邻居上能看到对方的电脑名字,双击进入的时候弹出问题.出现“用户账户限制”或“未授予用户在计算机上的请求登陆类型”的问题,这说明我们的局域网和网络是畅通的,问题的根源就在电脑的安全设置上. ...

  10. 01-python3.5-模块导入-while-for-range-break-continue

    一.输入用户名和密码----导入getpass模块 #!/usr/bin/env python # -*- coding:utf-8 -*- #Author:XZ """ ...