对JDK动态代理的模拟

动态代理在JDK中的实现:

IProducer proxyProduec = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader()
, producer.getClass().getInterfaces(),new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
Float money = (Float)args[0];
if("saleProduct".equals(method.getName())){
rtValue = method.invoke(producer, money * 0.8f);
}
return rtValue;
}
});

来看看newProxyInstance()这个方法在JDK中的定义

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
...
}

它需要三个参数:

ClassLoader loader:类加载器,JDK代理中认为由同一个类加载器加载的类生成的对象相同所以要传入一个加载器,而且在代理对象生成过程中也可能用到类加载器。

Class<?>[] interfaces:要被代理的类的接口,因为类可以实现多个接口,使用此处给的是Class数组。

InvocationHandler h:代理逻辑就是通过重写该接口的invoke()方法实现

通过对newProxyInstance()方法的分析我们能可以做出以下分析:

第二个参数Class<?>[] interfaces是接口数组,那么为什么需要被代理类的接口?应该是为了找到要增强的方法,因为由JDK实现的动态代理只能代理有接口的类,

2.InvocationHandler h:参数通过重写其invoke()方法实现了对方法的增强。我们先来看一下invoke()方法是如何定义的

/**
* 作用:执行的被代理对象的方法都会经过此方法
* @param proxy 代理对象的引用
* @param method 当前执行的方法的对象
* @param args 被代理对象方法的参数
* @return 被代理对象方法的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;

而想要重新该方法并完成对目标对象的代理,就需要使用method对象的invoke()方法(注:这个方法与InvocationHandler 中的invoke方法不同不过InvocationHandler 中的invoke方法主要也是为了声明使用Method中的invoke()方法)。我们在来看看Method中的invoke()方法

public Object invoke(Object obj, Object... args)

这里终于看到我们要代理的对象要写入的位置。

对有以上内容,我们可以做出以下猜想:(说是猜想,但实际JDk的动态代理就是基于此实现,不过其处理的东西更多,也更加全面

Proxy.newProxyInstance(..)这个方法的并不参与具体的代理过程,而是通过生成代理对象proxy来调用InvocationHandler 中的invoke()方法,通过invoke()方法来实现代理的具体逻辑。

所以我以下模拟JDK动态代理的这个过程,就是基于以上猜想实现,需要写两个内容,一个是生成代理对象的类(我命名为ProxyUtil),一个实现对代理对象的增强(我命名为MyHandler接口)

Proxy类

package wf.util;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader; public class ProxyUtil { /**
* public UserDaoImpl(User)
* @param targetInf
* @return
*/
public static Object newInstance(Class targetInf,MyHandler h){
Object proxy = null;
String tab = "\t";
String line = "\n";
String infname = targetInf.getSimpleName();
String content = "";
//这里把代理类的包名写死了,但JDK实现的过程中会判断代理的方法是否是public,如果是则其包名是默认的com.sun.Proxy包下,而如果方法类型不是public则生产的Java文件包名会和要代理的对象包名相同,这是和修饰符的访问权限有关
String packageName = "package com.google;"+line;
String importContent = "import "+targetInf.getName()+";"+line
+"import wf.util.MyHandler;"+line
+"import java.lang.reflect.Method;"+line;
//声明类
String clazzFirstLineContent = "public class $Proxy implements "+infname+"{"+line;
//属性
String attributeCont = tab+"private MyHandler h;"+line;
//构造方法
String constructorCont = tab+"public $Proxy (MyHandler h){" +line
+tab+tab+"this.h = h;"+line
+tab+"}"+line;
//要代理的方法
String mtehodCont = "";
Method[] methods = targetInf.getMethods();
for (Method method : methods) {
//方法返回值类型
String returnType = method.getReturnType().getSimpleName();
// 方法名称
String methodName = method.getName();
//方法参数
Class<?>[] types = method.getParameterTypes();
//传入参数
String argesCont = "";
//调用目标对象的方法时的传参
String paramterCont = "";
int flag = 0;
for (Class<?> type : types) {
String argName = type.getSimpleName();
argesCont = argName+" p"+flag+",";
paramterCont = "p" + flag+",";
flag++;
}
if (argesCont.length()>0){
argesCont=argesCont.substring(0,argesCont.lastIndexOf(",")-1);
paramterCont=paramterCont.substring(0,paramterCont.lastIndexOf(",")-1);
}
mtehodCont+=tab+"public "+returnType+" "+methodName+"("+argesCont+")throws Exception {"+line
+tab+tab+"Method method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\");"+line; if (returnType == "void"){
mtehodCont+=tab+tab+"h.invoke(method);"+line;
}else {
mtehodCont+=tab+tab+"return ("+returnType+")h.invoke(method);"+line;
}
mtehodCont+=tab+"}"+line;
}
content=packageName+importContent+clazzFirstLineContent+attributeCont+constructorCont+mtehodCont+"}"; // System.out.println(content);
//把字符串写入java文件
File file = new File("D:\\com\\google\\$Proxy.java");
try {
if (!file.exists()){
file.createNewFile();
}
FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close(); //编译java文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file); JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
URL[] urls = new URL[]{new URL("file:D:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("com.google.$Proxy"); Constructor constructor = clazz.getConstructor(MyHandler.class);
proxy = constructor.newInstance(h); }catch (Exception e){
e.printStackTrace();
}
return proxy;
}
}

该类会通过String字符串生成一个java类文件进而生成代理对象

补充: 我在实现Java文件时把代理类的包名写死了,但JDK实现的过程中会判断代理的方法是否是public,如果是则其包名是默认的com.sun.Proxy包下,而如果方法类型不是public则生产的Java文件包名会和要代理的对象包名相同,这是和修饰符的访问权限有关

生成的java类为($Proxy)

package com.google;
import wf.dao.UserDao;
import wf.util.MyHandler;
import java.lang.reflect.Method;
public class $Proxy implements UserDao{
private MyHandler h;
public $Proxy (MyHandler h){
this.h = h;
}
public void query()throws Exception {
Method method = Class.forName("wf.dao.UserDao").getDeclaredMethod("query");
h.invoke(method);
}
public String query1(String p)throws Exception {
Method method = Class.forName("wf.dao.UserDao").getDeclaredMethod("query1");
return (String)h.invoke(method);
}
}

可以看到代理对象其实起一个中专作用,来调用实现代理逻辑的MyHandler接口

MyHandler实现如下:

package wf.util;

import java.lang.reflect.Method;

public interface MyHandler {
//这里做了简化处理只需要传入method对象即可
public Object invoke(Method method);
}

其实现类如下

package wf.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; public class MyHandlerImpl implements MyHandler {
Object target;
public MyHandlerImpl(Object target){
this.target=target;
} @Override
public Object invoke(Method method) {
try {
System.out.println("MyHandlerImpl");
return method.invoke(target);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}

这里对代理对象的传入是通过构造方法来传入。

至此模拟完成,看一下以下结果

package wf.test;

import wf.dao.UserDao;
import wf.dao.impl.UserDaoImpl;
import wf.proxy.UserDaoLog;
import wf.util.MyHandlerImpl;
import wf.util.MyInvocationHandler;
import wf.util.ProxyUtil; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) throws Exception {
final UserDaoImpl target = new UserDaoImpl();
System.out.println("自定义代理");
UserDao proxy = (UserDao) ProxyUtil.newInstance(UserDao.class, new MyHandlerImpl(new UserDaoImpl()));
proxy.query(); System.out.println("java提供的代理");
UserDao proxy1 = (UserDao) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{UserDao.class},
new MyInvocationHandler(new UserDaoImpl()));
proxy1.query();
}
}

输出:

自定义代理

MyHandlerImpl

查询数据库

------分界线----

java提供的代理

proxy

查询数据库

两种方式都实现了代理,而实际上JDK的动态代理的主要思想和以上相同,不过其底层不是通过String来实现代理类的Java文件的编写,而JDK则是通过byte[]实现,其生成Class对象时通过native方法实现,通过c++代码实现,不是java要讨论的内容。

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags); private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);

对JDK动态代理的模拟实现的更多相关文章

  1. 动态代理学习(一)自己动手模拟JDK动态代理

    最近一直在学习Spring的源码,Spring底层大量使用了动态代理.所以花一些时间对动态代理的知识做一下总结. 我们自己动手模拟一个动态代理 对JDK动态代理的源码进行分析 文章目录 场景: 思路: ...

  2. 模拟实现jdk动态代理

    实现步骤 1.生成代理类的源代码 2.将源代码保存到磁盘 3.使用JavaCompiler编译源代码生成.class字节码文件 4.使用JavaCompiler编译源代码生成.class字节码文件 5 ...

  3. Spring中的JDK动态代理

    Spring中的JDK动态代理 在JDK1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例.在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在动态代理是实现AOP的绝好底层 ...

  4. AOP学习心得&jdk动态代理与cglib比较

    什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...

  5. Spring AOP详解 、 JDK动态代理、CGLib动态代理

    AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码 ...

  6. JDK动态代理与Cglib库

    JDK动态代理 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在 ...

  7. 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理

    Spring AOP详解 . JDK动态代理.CGLib动态代理  原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...

  8. 设计模式之jdk动态代理模式、责任链模式-java实现

    设计模式之JDK动态代理模式.责任链模式 需求场景 当我们的代码中的类随着业务量的增大而不断增大仿佛没有尽头时,我们可以考虑使用动态代理设计模式,代理类的代码量被固定下来,不会随着业务量的增大而增大. ...

  9. JDK动态代理给Spring事务埋下的坑!

    一.场景分析 最近做项目遇到了一个很奇怪的问题,大致的业务场景是这样的:我们首先设定两个事务,事务parent和事务child,在Controller里边同时调用这两个方法,示例代码如下: 1.场景A ...

随机推荐

  1. SpringCloud Alibaba微服务实战 - 基础环境准备

    Springcloud Aibaba现在这么火,我一直想写个基于Springcloud Alibaba一步一步构建微服务架构的系列博客,终于下定决心从今天开始本系列文章的第一篇 - 基础环境准备. 该 ...

  2. day 14 内置函数

    复习了解: \t   输出一个制表符,协助在输出文本时,垂直方向保持对齐 \n       换行符 print(r"\n ")  #  在字符串前面加r 不会改变字符串的内容 a ...

  3. SpringMVC配置了拦截器(interceptors)却显示不出css、js样式的解决办法

    首先因为在web.xml里面配置了 <filter-mapping> <filter-name>characterEncodingFilter</filter-name& ...

  4. django-migrate一败再败

    python3 manage.py makemigrations # 生成数据库迁移文件 python3 manage.py migrate # 迁移数据库 简简单单两条命令就完成了django的数据 ...

  5. 堆模板(STL版)

    题目描述 如题,初始小根堆为空,我们需要支持以下3种操作: 操作1: 1 x 表示将x插入到堆中 操作2: 2 输出该小根堆内的最小数 操作3: 3 删除该小根堆内的最小数 输入输出格式 输入格式: ...

  6. 【JZOJ】3490. 旅游题解报告

    题目 思路 这道题看上去就像一个动态规划!但是还是要把矩阵压成一行. 然后按 \(A\)数组 将结构体从小到大排个序. 随后我们开始了动规标准步骤: 确定状态 很显然, \(f_i\) 表示游览完第\ ...

  7. maven中的setting文件

      localRepository默认jar包下载到本地哪个目录下 pluginGroups 把自己的插件放在这里进行管理 这样不用写groupId和artifactId     一个生命周期包含很多 ...

  8. phpStudy搭建PHP服务器

    目录 1 下载 2 安装 3 新建站点 4 配置host phpStudy是一个PHP调试环境的程序集成包. 该程序包集成最新的 Apache+Nginx+LightTPD PHP MySQL+php ...

  9. JavaEE基础(01):Servlet实现方式,生命周期执行过程

    本文源码:GitHub·点这里 || GitEE·点这里 一.Servlet简介 Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容.使用S ...

  10. ThinkPHP5——模型关联(一对多关联)

    关联定义 一对多关联的情况也比较常见,使用hasMany方法定义,参数包括: hasMany('关联模型名','外键名','主键名',['模型别名定义']); 例如租客表和宿舍表,一个宿舍有多个租客, ...