@TOC
什么是动态代{过}{滤}理
在学习大部分框架的时候,都会使用到动态代{过}{滤}理机制,下面让我们来了解一下什么是动态代{过}{滤}理
给大家讲个故事吧,主人公小明(XiaoMing),是一个单身程序员,他想找对象,但是没有时间,小明的父母(Parent)看到这个情况,非常着急,于是要替小明找对象,这个流程下,小明就是一个被代{过}{滤}理对象,他的父母就是代{过}{滤}理对象
动态代{过}{滤}理中,非常重要的几个东西
被代{过}{滤}理对象 小明(XiaoMing)
代{过}{滤}理对象 小明的父母(Parent):代{过}{滤}理对象的主要作用就是对被代{过}{滤}理对象的增强和通知
代码说明动态代{过}{滤}理
- 找对象接口(只定义一个方法,就是人要找对象)
public interface People {
void findMM();
}
- 小明(小明是一个人,又要找对象,因此实现了一下People接口)
public class XiaoMing implements People {
@Override
public void findMM() {
System.out.println("=================我是小明,我要找对象");
}
}
-
小明的父母(小明要替小明找对象)
public class Parent{
public void heplXm(){
//找对象前的准备工作(对找对象的增强处理)
after();
//帮助小明找对象(代{过}{滤}理小明找对象)
XXX.findMM();
//找到对象后的善后工作(对找对象的增强处理)
before();
}
private void before() {
System.out.println("===========找到对象后还要给他准备结婚");
}
private void after() {
System.out.println("===========小明父母准备帮小明找对象了,找他要了照片");
}
}
但是现在来看Parent这个方法,并没有真正的帮助到小明,因为XXX.findMM()这个动作,还是小明自己在调用的,他的父母并没有帮上他什么忙。
- 此时,我们就要引入Proxy动态代{过}{滤}理了,下面我们写一个测试类来进行动态代{过}{滤}理
public void test01(){
People proxy = (People) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{People.class},new Parent(new XiaoMing()));
proxy.findMM();
}
//针对Proxy.newProxyInstance的说明
//1. ClassLoader loader 当前处理类的类加载器
//2. Class<?>[] interfaces 被代{过}{滤}理的接口的类型(是一个数组)
//3. InvocationHandler h 代{过}{滤}理类
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
引入动态代{过}{滤}理后,Parent就升级了,就会变成这个样子,只有实现了InvocationHandler 接口,才可以使用JDK自带的动态代{过}{滤}理
-
修改后的Parent
public class Parent implements InvocationHandler {
private People people;
public Parent(People p) {
this.people = p;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
after();
//调用people下的方法,null就是被调用方法传入的参数
//findMM是一个无参方法,所以此处传null
method.invoke(people,null);
before();
return null;
}
private void before() {
System.out.println("===========找到对象后还要给他准备结婚");
}
private void after() {
System.out.println("===========小明父母准备帮小明找对象了,找他要了照片");
}
}
运行test01,执行修改后的代码得到以下结果
发现,代码被正常执行了,并且没有显示调用XiaoMing.findMM(),而且在没有修改XiaoMing.findMM()的前提下,还增加了一些功能。
动态代{过}{滤}理解析
那问题来了,到底是谁调用了findMM()这个方法呢
此时就会有人回答是Parent 中的 method.invoke(people,null);
【面试😒】
动态代{过}{滤}理中的 invoke方法,到底是谁在调用的?
根据Debug,我们可以看到是$Proxy4调用的invoke($Proxy4的命名格式为 $Proxy + 序号,所以你执行出来不一定是$Proxy几)
这个$Proxy4存在于jvm的缓存中,在运行中我们是看不到具体的代码,但是,为了方便使用,Proxy给我们提供了对应的代码,可以获取到对应的class文件,让我们更加直观的看到Proxy的代{过}{滤}理实现,使用以下代码,可以将$Proxy从内存中获取出来,变成文件,方便阅读
-获取$Proxy0.class
public void test02(){
byte[] $Proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{People.class});
try {
FileOutputStream fos = new FileOutputStream("$Proxy0.class");
fos.write($Proxy0s);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
在根目录下得到了$Proxy0.class,反编译打开
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import a.demo.People;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements People {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
//重写equals
public final boolean equals(Object var1) throws {...}
//重写toString
public final String toString() throws {...}
//重点看这里!!!
public final void findMM() throws {
try {
//调用了父类的h.invoke,这个h就是$Proxy0
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//重写hashCode
public final int hashCode() throws {...}
//加载一些初始化类
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("a.demo.People").getMethod("findMM");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
手写一个简单的动态代{过}{滤}理
源码方面,我们稍后在读,我们先手写一个简单的动态代{过}{滤}理,通过这个来理解Proxy.newProxyInstance
准备工作如下
- 手写一个新的newProxyInstance
起名就叫myNewProxyInstance吧,我们首先需要要明确的是。myNewProxyInstance,要具有生成一个类的功能,生成的类要类似于我们获取的$Proxy0,那如何生成一个类呢,通过拼凑字符串的方式生成一个$Proxy0.java文件
- 使用流的方式,将字符串写到$Proxy0.java文件中
- 将$Proxy0.java文件编译成.class文件
- 将这个.class文件加载到JVM中
- 在内存中执行,然后返回实例
开始写代码了
-
myInvocationHandler 用于方法的实例化(调用其invok)
public interface myInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
- myProxy 代{过}{滤}理啦 啦啦啦啦啦啦啦
```java
package org.process;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class myProxy {
protected myInvocationHandler h;
private static String rt = "\r\n";
public static Object newProxyInstance(myClassLoader loader,
Class<?>[] interfaces,
myInvocationHandler h)
throws IllegalArgumentException
{
//1. 以字符串的方式拼出一个代{过}{滤}理类
String sJava = getJavaStr(interfaces);
//2. 使用流的方式,将字符串写到.java文件中
File file = cratejavaFile(sJava,"$Proxy0");
//3. 将.java文件编译成.class文件
compiler(file);
//4. 将这个.class文件加载到JVM中
Class<?> $Proxy0 = loader.findClass("$Proxy0");
//这里先不删除生成的$Proxy0.java文件,实际上要删除的
//file.delete();
Object o = null;
try {
Constructor<?> c = $Proxy0.getConstructor(myInvocationHandler.class);
//5. 在内存中执行,返回实例
o = c.newInstance(h);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return o;
}
private static void compiler(File file) {
try {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manager.getJavaFileObjects(file);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
task.call();
manager.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static File cratejavaFile(String sJava,String sKey) {
String basePath = myClassLoader.class.getResource("").getPath();
File file = new File(basePath + sKey + ".java");
try {
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(sJava);
fileWriter.flush();
fileWriter.close();
}catch (Exception e ){
e.printStackTrace();
}
return file;
}
/** 生成代{过}{滤}理对象$Proxy0的源代码
* @Param interfaces 抽象对象
* @Return String
*/
private static String getJavaStr(Class<?>[] interfaces) {
String ClazzName = "$Proxy" + 0;
String src = CrateStr(interfaces,ClazzName);
return src;
}
private static String CrateStr(Class<?>[] interfaces, String clazzName) {
//获取包名
String name = myProxy.class.getPackage().getName();
StringBuilder src = new StringBuilder();
src.append("package " + rt);
src.append(name);
src.append(";" + rt);
//引入反射相关的包
src.append("import java.lang.reflect.Method;" + rt);
src.append("import org.process.myProxy;" + rt);
//动态代{过}{滤}理类实现被代{过}{滤}理接口
src.append("public class ");
src.append(clazzName);
//开始拼接接口信息
src.append(" extends myProxy implements ");
for(int i = 0 ; i < interfaces.length ;i ++ ){
Class clazz = interfaces[i];
if(interfaces.length == 1){
src.append(clazz.getName());
}else if(interfaces.length - 1 == i){
src.append(clazz.getName());
}else{
src.append(clazz.getName() + ",");
}
}
src.append("{" + rt);
src.append("public ");
src.append(clazzName);
src.append(" (myInvocationHandler h) {" + rt);
src.append("this.h = h;" + rt);
src.append("}" + rt);
//通过反射获取代{过}{滤}理接口的所有方法并激活
for(Class clazz : interfaces){
for (Method m : clazz.getMethods()) {
src.append("public " + m.getReturnType().getName() + " " + m.getName() + "(){" + rt);
src.append("try{" + rt);
if(m.getParameterTypes().length > 0){
src.append("Method m = " + clazz.getName() + ".class.getMethod(\"" + m.getName() + "\",null);" + rt);
}else{
src.append("Method m = " + clazz.getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + rt);
}
src.append("this.h.invoke(this,m,null);" + rt);
src.append("}catch(Throwable e){e.printStackTrace();}" + rt);
src.append("}" + rt);
}
}
src.append("}");
return src.toString();
}
}
这里需要用到类加载器,我们又在新建了一个类继承了一下ClassLoader,然后重写了一下他的findclass方法,enmmm。。。就是为了用于将生成的.class文件装载到内存中。
- myClassLoader
package org.process;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class myClassLoader extends ClassLoader{
private File baseDir;
public myClassLoader(){
String basePath = myClassLoader.class.getResource("").getPath();
this.baseDir = new File(basePath);
}
@Override
protected Class<?> findClass(String name) {
String className = myClassLoader.class.getPackage().getName() + "." + name;
if(baseDir != null){
File classFile = new File(baseDir,name.replaceAll("\\.", "/") + ".class");
if(classFile.exists()){
FileInputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte [] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1) {
out.write(buff, 0, len);
}
return defineClass(className, out.toByteArray(), 0,out.size());
}catch (Exception e) {
e.printStackTrace();
}finally{
if(null != in){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != out){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//先不删除,可以看到class文件内容
//classFile.delete();
}
}
}
return null;
}
}
好像也么啥其他的了,然后就复制了一下Partent类,改了一下他的实现关系
- Parent1
```java
package org.process;
import org.example.People;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class Parent1 implements myInvocationHandler {
private People people;
public Parent1(People p) {
this.people = p;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
after();
method.invoke(people,null);
before();
return null;
}
private void before() {
System.out.println("===========Parent1 找到对象后还要给他准备结婚");
}
private void after() {
System.out.println("===========Parent1 小明父母准备帮小明找对象了,找他要了照片");
}
}
到这里 代码算是写完了,然后写一个测试类看看情况
public void test04(){
People proxy = (People) myProxy.newProxyInstance(new myClassLoader(), new Class[]{People.class},new Parent1(new XiaoMing()));
proxy.findMM();
}
执行结果如下:
https://img-blog.csdnimg.cn/3fe9382d374d46189a786e1cf6f064d9.png
完成喽,运行成功~
动态代{过}{滤}理应用
老代码已经在生产上运营,不方便修改的情况下(或者压根就改不了),还需要对其功能进行扩容,就会有大量的应用,具体逻辑请参照Parent
动态代{过}{滤}理源码
看下载链接