对JDK动态代理的模拟实现

对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);
posted @ 2019-10-20 23:19  紫月冰凌  阅读(345)  评论(0编辑  收藏  举报