为哪些方法代理?

实现自己动态代理,首先需要关注的点就是,代理对象需要为哪些方法代理? 原生JDK的动态代理的实现是往上抽象出一层接口,让目标对象和代理对象都实现这个接口,怎么把接口的信息告诉jdk原生的动态代理呢? 如下代码所示,Proxy.newProxyInstance()方法的第二个参数将接口的信息传递了进去第一个参数的传递进去一个类加载器,在jdk的底层用它对比对象是否是同一个,标准就是相同对象的类加载器是同一个

ServiceInterface) Proxy.newProxyInstance(service.getClass().getClassLoader()
, new Class[]{ServiceInterface.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置通知");
method.invoke(finalService,args);
System.out.println("后置通知");
return proxy;
}
});

我们也效仿它的做法. 代码如下:

public class Test {
public static void main(String[] args) {
IndexDao indexDao = new IndexDao();
Dao dao =(Dao) ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao));
assert dao != null;
System.out.println(dao.say("changwu"));
}
}

拿到了接口的Class对象后,通过反射就得知了接口中有哪些方法描述对象Method,获取到的所有的方法,这些方法就是我们需要增强的方法

如何将增强的逻辑动态的传递进来呢?

JDK的做法是通过InvocationHandler的第三个参数完成,他是个接口,里面只有一个抽象方法如下: 可以看到它里面有三个入参,分别是 代理对象,被代理对象的方法,被代理对象的方法的参数

    public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

当我们使用jdk的动态代理时,就是通过这个重写这个钩子函数,将逻辑动态的传递进去,并且可以选择在适当的地方让目标方法执行

InvocationHandler接口必须存在必要性1:

为什么不传递进去Method,而是传递进去InvocationHandler对象呢? 很显然,我们的初衷是借助ProxyUtil工具类完成对代理对象的拼串封装,然后让这个代理对象去执行method.invoke(), 然而事与愿违,传递进来的Method对象的确可以被ProxyUtil使用,调用method.invoke(), 但是我们的代理对象不能使用它,因为代理对象在这个ProxyUtil还以一堆等待拼接字符串, ProxyUtil的作用只能是往代理对象上叠加字符串,却不能直接传递给它一个对象,所以只能传递一个对象进来,然后通过反射获取到这个对象的实例,继而有可能实现method.invoke()

InvocationHandler接口必须存在必要性2:

通过这个接口的规范,我们可以直接得知回调方法的名字就是invoke()所以说,在拼接字符串完成对代理对象的拼接时,可以直接写死它


思路

我们需要通过上面的ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao))方法完成如下几件事

  • 根据入参位置的信息,提取我们需要的信息,如包名,方法名,等等
  • 根据我们提取的信息通过字符串的拼接完成一个全新的java的拼接
    • 这个java类就是我们的代理对象
  • 拼接好的java类是一个String字符串,我们将它写入磁盘取名XXX.java
  • 通过ProxyUtil使用类加载器,将XXX.java读取JVM中,形成Class对象
  • 通过Class对象反射出我们需要的代理对象

ProxyUtil的实现如下:

public static Object newInstance(Class targetInf, MyInvocationHandler invocationHandler) {

    Method methods[] = targetInf.getDeclaredMethods();
String line = "\n";
String tab = "\t";
String infName = targetInf.getSimpleName();
String content = "";
String packageContent = "package com.myproxy;" + line;
// 导包,全部导入接口层面,换成具体的实现类就会报错
//
String importContent = "import " + targetInf.getName() + ";" + line
+ "import com.changwu.代理技术.模拟jdk实现动态代理.MyInvocationHandler;" + line
+ "import java.lang.reflect.Method;" + line
+ "import java.lang.Exception;" + line; String clazzFirstLineContent = "public class $Proxy implements " + infName +"{"+ line;
String filedContent = tab + "private MyInvocationHandler handler;"+ line;
String constructorContent = tab + "public $Proxy (MyInvocationHandler handler){" + line
+ tab + tab + "this.handler =handler;"
+ line + tab + "}" + line;
String methodContent = "";
// 遍历它的全部方法,接口出现的全部方法进行增强
for (Method method : methods) {
String returnTypeName = method.getReturnType().getSimpleName(); method.getReturnType().getSimpleName()); String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes(); // 参数的.class
String paramsClass = "";
for (Class<?> parameterType : parameterTypes) {
paramsClass+= parameterType.getName()+",";
} String[] split = paramsClass.split(","); //方法参数的类型数组 Sting.class String.class
String argsContent = "";
String paramsContent = "";
int flag = 0;
for (Class arg : parameterTypes) {
// 获取方法名
String temp = arg.getSimpleName();
argsContent += temp + " p" + flag + ",";
paramsContent += "p" + flag + ",";
flag++;
}
// 去掉方法参数中最后面多出来的,
if (argsContent.length() > 0) {
argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
}
methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line
+ tab + tab+"Method method = null;"+line
+ tab + tab+"String [] args0 = null;"+line
+ tab + tab+"Class<?> [] args1= null;"+line // invoke入参是Method对象,而不是上面的字符串,所以的得通过反射创建出Method对象
+ tab + tab+"try{"+line
// 反射得到参数的类型数组
+ tab + tab + tab + "args0 = \""+paramsClass+"\".split(\",\");"+line
+ tab + tab + tab + "args1 = new Class[args0.length];"+line
+ tab + tab + tab + "for (int i=0;i<args0.length;i++) {"+line
+ tab + tab + tab + " args1[i]=Class.forName(args0[i]);"+line
+ tab + tab + tab + "}"+line
// 反射目标方法
+ tab + tab + tab + "method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\",args1);"+line
+ tab + tab+"}catch (Exception e){"+line
+ tab + tab+ tab+"e.printStackTrace();"+line
+ tab + tab+"}"+line
+ tab + tab + "return ("+returnTypeName+") this.handler.invoke(method,\"暂时不知道的方法\");" + line; //
methodContent+= tab + "}"+line;
} content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}"; File file = new File("d:\\com\\myproxy\\$Proxy.java");
try {
if (!file.exists()) {
file.createNewFile();
} FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close(); // 将生成的.java的文件编译成 .class文件
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(); // 使用类加载器将.class文件加载进jvm
// 因为产生的.class不在我们的工程当中
URL[] urls = new URL[]{new URL("file:D:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("com.myproxy.$Proxy");
return clazz.getConstructor(MyInvocationHandler.class).newInstance(invocationHandler);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

运行的效果:

package com.myproxy;
import com.changwu.myproxy.pro.Dao;
import com.changwu.myproxy.pro.MyInvocationHandler;
import java.lang.reflect.Method;
import java.lang.Exception;
public class $Proxy implements Dao{
private MyInvocationHandler handler;
public $Proxy (MyInvocationHandler handler){
this.handler =handler;
}
public String say(String p) {
Method method = null;
String [] args0 = null;
Class<?> [] args1= null;
try{
args0 = "java.lang.String,".split(",");
args1 = new Class[args0.length];
for (int i=0;i<args0.length;i++) {
args1[i]=Class.forName(args0[i]);
}
method = Class.forName("com.changwu.myproxy.pro.Dao").getDeclaredMethod("say",args1);
}catch (Exception e){
e.printStackTrace();
}
return (String) this.handler.invoke(method,"暂时不知道的方法");
}
}

解读

通过newInstance()用户获取到的代理对象就像上面的代理一样,这个过程是在java代码运行时生成的,但是直接看他的结果和静态代理差不错,这时用户再去调用代理对象的say(), 实际上就是在执行用户传递进去的InvocationHandeler里面的invoke方法, 但是亮点是我们把目标方法的描述对象Method同时给他传递进去了,让用户可以执行目标方法+增强的逻辑

当通过反射区执行Method对象的invoke()方法时,指定的哪个对象的当前方法呢? 这个参数其实是我们手动传递进去的代理对象代码如下

public class MyInvocationHandlerImpl implements MyInvocationHandler {
private Object obj;
public MyInvocationHandlerImpl(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Method method, Object[] args) {
System.out.println("前置通知");
try {
method.invoke(obj,args);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("后置通知");
return null;
}
}

手动模拟JDK动态代理的更多相关文章

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

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

  2. 对JDK动态代理的模拟实现

    对JDK动态代理的模拟 动态代理在JDK中的实现: IProducer proxyProduec = (IProducer)Proxy.newProxyInstance(producer.getCla ...

  3. Spring中的JDK动态代理

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

  4. 模拟实现jdk动态代理

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

  5. 从静态代理,jdk动态代理到cglib动态代理-一文搞懂代理模式

    从代理模式到动态代理 代理模式是一种理论上非常简单,但是各种地方的实现往往却非常复杂.本文将从代理模式的基本概念出发,探讨代理模式在java领域的应用与实现.读完本文你将get到以下几点: 为什么需要 ...

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

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

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

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

  8. JDK动态代理与Cglib库

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

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

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

随机推荐

  1. POJ 1002 487-3279 map

    487-3279 Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 287874   Accepted: 51669 Descr ...

  2. 使用图灵机器人api搭建微信聊天机器人php实现

    之前通过hook技术实现了微信pc端发送消息功能,如果在结合图灵机器人就能实现微信聊天机器人. 代码下载:http://blog.yshizi.cn/131.html 逻辑如下: 下面我简单介绍一下步 ...

  3. java多线程之Executor 与 ExecutorService两个基本接口

    一.Executor 接口简介 Executor接口是Executor框架的一个最基本的接口,Executor框架的大部分类都直接或间接地实现了此接口. 只有一个方法 void execute(Run ...

  4. springboot以jar运行时参数传递

    springboot以jar运行时参数传递 spring boot项目我们都习惯以内嵌tomcat方式.直接打包成jar,运行时使用: java -jar XXX.jar  --spring.prof ...

  5. 如何在IDEA中导入一个普通的java工程

    1.如下: 2.如下,选中要导入的工程: 3.如下: 4.如下图 5.点击next,后如下图: 6.点击next后,如下图: 7.点击next后,如下图: 8.点击next后,如下图: 9.点击nex ...

  6. abp(net core)+easyui+efcore实现仓储管理系统——EasyUI前端页面框架 (十八)

    目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+easyui+efcore实现仓储管理系统——解决方案介绍(二) ab ...

  7. Docker下kafka学习三部曲之一:极速体验kafka

    Kafka是一种高吞吐量的分布式发布订阅消息系统,从本章开始我们先极速体验,再实战docker下搭建kafka环境,最后开发一个java web应用来体验kafka服务. 我们一起用最快的速度体验ka ...

  8. Docker下dubbo开发三部曲之三:java开发

    在前两章<Docker下dubbo开发,三部曲之一:极速体验>和<Docker下dubbo开发,三部曲之二:本地环境搭建>中,我们体验了dubbo环境搭建以及服务的发布和消费, ...

  9. 〈三〉ElasticSearch的认识:搜索、过滤、排序

    目录 上节回顾 本节前言 文档的搜索 URL参数条件搜索 请求体条件搜索 语法与示例: 补充: 小节总结: 文档的过滤filter 语法与举例: filter与bool constant_score ...

  10. 自定义JDBC工具类

    因为数据库的连接代码都是固定的,为了将减少重复的代码的书写,可以将这些代码封装为一个工具类,获取数据库的连接对象. import java.sql.Connection; import java.sq ...