Java动态代理自己写代码简单实现(一)

上一篇说了JDK动态代理如何使用,这里就有个想法,不使用官方的,自己能否模拟一个,网上一搜还真有不少同学已经实现了,但那句话咋说的:纸上得来终觉浅,绝知此事要躬行。

  • 官方接口:
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
        
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
}
  • 自定义接口(尽量和官方一致):
public interface MyInvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
//自定义类加载器    
public static Object newProxyInstance(MyClassLoader myClassLoader, 
												Class<?>[] interfaces, 
												MyInvocationHandler h)    
//不用自定义类加载器
public static Object newProxyInstance(Object object, 
												Class<?>[] interfaces, 
												MyInvocationHandler h)
}
  • 实现代码:
public class MyProxyInvocationHandler<T> implements MyInvocationHandler {

    private T target;

    public MyProxyInvocationHandler(T target) {
        this.target = target;
    }

    /**
     *
     * @param proxy 代理后的实例对象
     * @param method 对象被调用的方法
     * @param args 调用参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();

        return result;
    }

    public void before() {
        System.out.println("我的名字之前...");
    }

    public void after() {
        System.out.println("我的名字之后...");
    }
}
  • 自定义类加载器:
public class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) {
        //class文件路径
        String classPath = MyClassLoader.class.getResource("").getPath() + "/" + name + ".class";
        System.out.println(classPath);
        //class的包名+文件名
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        System.out.println(className);

        //读取class,此处借助hutool工具
        byte[] bytes = FileUtil.readBytes(classPath);

        //加载
        return defineClass(className, bytes, 0, bytes.length);
    }

}
  • 自定义代理类:
public class MyProxy {
    public static final String ln = "\r\n";

    public static Object newProxyInstance(Object object, Class<?>[] interfaces, MyInvocationHandler h) {
        try {
            //1 java源码
            String src = src(interfaces);

            //2. 源码输出到java文件中,并保存到本地
            File file = disk(src);

            //3、将java文件编译成class文件
            compile(file);

            //4.1 使用自定义加载器将class加载进jvm里
            Class proxyClass = myClassLoader.findClass("$Proxy0");

            //5、获取构造方法
            Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);

            //构造代理对象
            return constructor.newInstance(h);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 结构固定
     * @param interfaces
     * @return
     */
    private static String src(Class<?>[] interfaces) {
        // 找包名
        String packageName = interfaces[0].getPackage().getName();

        StringBuffer sb = new StringBuffer();
        sb.append("package " + packageName + ";" + ln);
        sb.append("import java.lang.reflect.Method;" + ln);
        sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
        sb.append("private MyInvocationHandler h;"+ln);
        sb.append("public $Proxy0(MyInvocationHandler h) { " + ln);
        sb.append("this.h = h;"+ln);
        sb.append("}" + ln);
        for (Method m : interfaces[0].getMethods()) {
            sb.append("public " + m.getReturnType().getName() + " "
                    + m.getName() + "() {" + ln);
            sb.append("try{" + ln);
            sb.append("Method m = " + interfaces[0].getName()
                    + ".class.getMethod(\"" + m.getName()
                    + "\",new Class[]{});" + ln);
            sb.append("this.h.invoke(this,m,null);" + ln);
            sb.append("}catch(Throwable e){" + ln);
            sb.append("e.printStackTrace();" + ln);
            sb.append("}"+ln);
            sb.append("}"+ln);
        }
        sb.append("}" + ln);
        System.out.println(sb.toString());
        return sb.toString();
    }

    /**
     * 源码保存到磁盘
     * @param classSrc
     * @return
     */
    public static File disk(String classSrc) {
        String filePath = MyProxy.class.getResource("").getPath() + "$Proxy0.java";
        //此处借助hutool工具
        return FileUtil.writeBytes(classSrc.getBytes(), filePath);
    }

    /**
     * 编译java文件
     * @param file
     */
    public static void compile(File file) {
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manage.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
            task.call();
            manage.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  • 测试代码:
public interface User {

    void myName();
}

public class UserImpl implements User {
    @Override
    public void myName() {
        System.out.println("我的名字叫张三");
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        User user = new UserImpl();
        User userProxy = (User) MyProxy.newProxyInstance(new MyClassLoader(), user.getClass().getInterfaces(), new MyProxyInvocationHandler(user));
        userProxy.myName();
    }
}

输出:  
我的名字之前...
我的名字叫张三
我的名字之后...

我们也可以不使用自定义类加载器,代码如下改造:

public static Object newProxyInstance(Object object, Class<?>[] interfaces, MyInvocationHandler h) {
        try {
            //1 java源码
            String src = src(interfaces);

            //2. 源码输出到java文件中,并保存到本地
            File file = disk(src);

            //3、将java文件编译成class文件
            compile(file);

            //4、使用自定义加载器将class加载进jvm里
//            Class proxyClass = myClassLoader.findClass("$Proxy0");

            //使用URLClassLoader加载代理class
            URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class proxyClass = urlClassLoader.loadClass(object.getClass().getPackage().getName() + ".$Proxy0");

            //5、获取构造方法
            Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);

            //构造代理对象
            return constructor.newInstance(h);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
    
public class ProxyTest {
    public static void main(String[] args) {
        User user = new UserImpl();
        User userProxy = (User) MyProxy.newProxyInstance(user, user.getClass().getInterfaces(), new MyProxyInvocationHandler(user));
        userProxy.myName();
    }
}
输出: 
我的名字之前...
我的名字叫张三
我的名字之后...

流程还是比较清晰的:

  1. 代理源码手动创建,格式较为固定
  2. 源码保存到本地.java文件中
  3. 调用编译器对.java编译成.class文件
  4. 使用类加载器加载.class文件
  5. 获取构造方法
  6. 获取代理对象

问题:.java文件必须先写到本地,编译后再从本地读取,能否省去读写磁盘操作,直接内存中搞定?

参考:
自己动手实现java 动态代理
自己动手实现JDK动态代理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值