Manually simulate JDK dynamic agent

For which methods?

To implement your own dynamic proxy, you should first pay attention to which methods the proxy object needs to proxy for? The implementation of the dynamic agent of the native JDK is to abstract a layer of interface upward and let both the target object and the proxy object implement this interface. How can the information of the interface be told to the native dynamic agent of the JDK? As shown in the following code, proxy The second parameter of the newproxyinstance () method passes in the interface information. The first parameter is passed into a classloader. It is used at the bottom of the JDK to compare whether the object is the same. The standard is that the classloader of the same object is the same

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;
            }
        });

We also follow its example The code is as follows:

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"));
    }
}

After getting the class object of the interface, we can know which methods in the interface describe the object method and all the methods obtained through reflection. These methods are the methods we need to enhance

How to dynamically transfer enhanced logic?

JDK is implemented through the third parameter of invocationhandler. It is an interface with only one abstract method as follows: you can see that it has three input parameters, namely, the proxy object, the method of the proxy object and the parameter of the method of the proxy object

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

When we use the dynamic proxy of JDK, we rewrite the hook function to dynamically pass in the logic, and we can choose to let the target method execute in an appropriate place

Invocationhandler interface must exist. Necessity 1:

Why not pass in the method, but the invocationhandler object? Obviously, our original intention is to use the proxyutil tool class to complete the string encapsulation of the proxy object, and then let the proxy object execute the method Invoke(), but it backfires. The method object passed in can indeed be used by proxyutil to call method Invoke(), but our proxy object can't use it because the proxy object still has a pile of strings waiting to be spliced in this proxyutil. Proxyutil can only superimpose strings on the proxy object, but can't directly pass it to an object. Therefore, only one object can be passed in, and then the instance of this object can be obtained through reflection, and then it is possible to implement method invoke()

Invocationhandler interface must exist. Necessity 2:

Through the specification of this interface, we can directly know that the name of the callback method is invoke (). Therefore, when the splicing string completes the splicing of the proxy object, we can write it directly

thinking

We need to go through proxyutil The newinstance (Dao. Class, new myinvocationhandlerimpl (indexdao)) method does the following

Proxyutil is implemented as follows:

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,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;
}
}

Effect of operation:

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,"暂时不知道的方法");
	}
}

unscramble

The proxy object obtained by the user through newinstance () is like the above proxy. This process is generated when the Java code is running, but its result is not good compared with the static proxy. At this time, the user calls say () of the proxy object, which is actually executing the invoke method in the invocationhandler passed in by the user, But the highlight is that we pass in the description object method of the target method at the same time, so that the user can execute the target method + enhanced logic

When the invoke () method of the method object is executed through the reflection area, which object's current method is specified? This parameter is actually a proxy object that we manually pass in. The code is as follows

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;
    }
}
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>