jdk动态代理

Spring的动态代理有两种:一是JDK的动态代理;另一个是cglib动态代理(通过修改字节码来实现代理)。今天咱们主要讨论JDK动态代理的方式。JDK的代理方式主要就是通过反射跟动态编译来实现的,下面咱们就通过代码来看看它具体是怎么实现的。

假设我们要对下面这个用户管理进行代理:

接口:

package cn.hackcoder.service;

/**
 * Created by linzhichao on 2017/2/21.
 */
public interface UserMgr {
    void addUser();

    void delUser();

    void sayHi(String name);
}


实现:

package cn.hackcoder.service.impl;

import cn.hackcoder.service.UserMgr;

import java.util.Random;

/**
 * Created by linzhichao on 2017/2/21.
 */
public class UserMgrImpl implements UserMgr {
    @Override
    public void addUser() {
        System.out.println("添加用户.....");
    }

    @Override
    public void delUser() {
        System.out.println("删除用户.....");
    }

    @Override
    public void sayHi(String name) {
        try {
            Thread.sleep(new Random().nextInt(3000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hi:" + name);
    }
}


按照代理模式的实现方式,肯定是用一个代理类,让它也实现UserMgr接口,然后在其内部声明一个UserMgrImpl,然后分别调用addUser和delUser和sayHi方法,并在调用前后加上我们需要的其他操作。但是这样很显然都是写死的,我们怎么做到动态呢?别急,接着看。 我们知道,要实现代理,那么我们的代理类跟被代理类都要实现同一接口,但是动态代理的话我们根本不知道我们将要代理谁,也就不知道我们要实现哪个接口,那么要怎么办呢?我们只有知道要代理谁以后,才能给出相应的代理类,那么我们何不等知道要代理谁以后再去生成一个代理类呢?想到这里,我们好像找到了解决的办法,就是动态生成代理类!

这时候我们亲爱的反射又有了用武之地,我们可以写一个方法来接收被代理类,这样我们就可以通过反射知道它的一切信息——包括它的类型、它的方法等等。

JDK动态代理的两个核心分别是InvocationHandler和Proxy,下面我们就用简单的代码来模拟一下它们是怎么实现的:

InvocationHandler接口:

package cn.hackcoder.proxy;

import java.lang.reflect.Method;

/**
 * Created by linzhichao on 2017/2/21.
 */
public interface InvocationHandler {
    void invoke(Object o, Method m, Object... params);
}

实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类:

package cn.hackcoder.proxy;

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.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;

/**
 * Created by linzhichao on 2017/2/21.
 */
public class Proxy {

    private static final String srcDir = System.getProperty("user.dir") + "/";

    /**
     * @param infer 被代理类的接口
     * @param h     代理类
     * @return
     * @throws Exception
     */
    public static Object newProxyInstance(Class infer, InvocationHandler h) throws Exception {
        String proxyedName = infer.getSimpleName();
        String packageName = h.getClass().getPackage().getName();
        String methodStr = "";
        String rt = "\r\n";

        //利用反射得到infce的所有方法,并重新组装
        Method[] methods = infer.getMethods();
        for (Method m : methods) {
            Class[] classes = m.getParameterTypes();
            StringBuilder paramList = new StringBuilder();
            StringBuilder paramValueList = new StringBuilder();
            StringBuilder paramNameList = new StringBuilder();
            int i = 0;
            for (Class c : classes) {
                paramList.append(c.getName()).append(" arg").append(i).append(",");
                paramValueList.append(" arg").append(i).append(",");
                paramNameList.append(c.getName()).append(".class,");
                i++;
            }

            if (classes.length > 0) {
                paramList.deleteCharAt(paramList.length() - 1);

                paramNameList.insert(0, ",");
                paramNameList.deleteCharAt(paramNameList.length() - 1);

                paramValueList.insert(0, ",");
                paramValueList.deleteCharAt(paramValueList.length() - 1);
            }
            methodStr += "    @Override" + rt +
                    "    public  " + m.getReturnType() + " " + m.getName() + "(" + paramList.toString() + ") {" + rt +
                    "        try {" + rt +
                    "        Method md = " + infer.getName() + ".class.getMethod(\"" + m.getName() + "\"" + paramNameList + ");" + rt +
                    "        h.invoke(this, md" + paramValueList + ");" + rt +
                    "        }catch(Exception e) {e.printStackTrace();}" + rt +
                    "    }" + rt;
        }

        //生成Java源文件
        String srcCode =
                "package " + packageName + ";" + rt +
                        "import java.lang.reflect.Method;" + rt +
                        "public class $Proxy" + proxyedName + " implements " + infer.getName() + "{" + rt +
                        "    public $Proxy" + proxyedName + "(InvocationHandler h) {" + rt +
                        "        this.h = h;" + rt +
                        "    }" + rt +
                        "    " + packageName + ".InvocationHandler h;" + rt +
                        methodStr + rt +
                        "}";

        String[] paths = packageName.split("\\.");
        StringBuilder sb = new StringBuilder();
        Arrays.asList(paths).forEach(path -> {
            sb.append(path);
            sb.append("/");
        });
        String fileName = srcDir + sb.toString() + "$Proxy" + proxyedName + ".java";
        File f = new File(fileName);
        if (!f.exists()) {
            f.getParentFile().mkdirs();
            f.createNewFile();
        }
        FileWriter fw = new FileWriter(f);
        fw.write(srcCode);
        fw.flush();
        fw.close();

        //将Java文件编译成class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
        Iterable units = fileMgr.getJavaFileObjects(fileName);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        t.call();
        fileMgr.close();

        //加载到内存,并实例化
        URL[] urls = new URL[]{new URL("file://" + srcDir)};
        URLClassLoader ul = new URLClassLoader(urls);
        Class c = ul.loadClass(packageName + ".$Proxy" + proxyedName);

        Constructor ctr = c.getConstructor(InvocationHandler.class);
        Object m = ctr.newInstance(h);

        return m;
    }
}

这个类的主要功能就是,根据被代理对象的信息,动态组装一个代理类,生成$ProxyUserMgr.java文件,然后将其编译成$ProxyUserMgr.class。这样我们就可以在运行的时候,根据我们具体的被代理对象生成我们想要的代理类了。这样一来,我们就不需要提前知道我们要代理谁。也就是说,你想代理谁,想要什么样的代理,我们就给你生成一个什么样的代理类。

然后,在客户端我们就可以随意的进行代理了。

package cn.hackcoder.proxy;

import cn.hackcoder.service.UserMgr;
import cn.hackcoder.service.impl.UserMgrImpl;

/**
 * Created by linzhichao on 2017/2/21.
 */
public class Client {
    public static void main(String[] args) throws Exception {
        UserMgr mgr = new UserMgrImpl();

        //为用户管理添加事务处理
        InvocationHandler h = new TransactionHandler(mgr);
        UserMgr u = (UserMgr) Proxy.newProxyInstance(UserMgr.class, h);

        //为用户管理添加显示方法执行时间的功能
        TimeHandler h2 = new TimeHandler(u);
        u = (UserMgr) Proxy.newProxyInstance(UserMgr.class, h2);

        u.addUser();
        System.out.println("\r\n==========华丽的分割线==========\r\n");
        u.delUser();
        System.out.println("\r\n==========华丽的分割线==========\r\n");
        u.sayHi( "hackcoder");
    }
}

这里我写了两个代理的功能,一个是事务处理,一个是显示方法执行时间的代理,当然都是非常简单的写法,只是为了说明这个原理。当然,我们可以想Spring那样将这些AOP写到配置文件,因为之前那篇已经写了怎么通过配置文件注入了,这里就不重复贴了。 到这里,你可能会有一个疑问:你上面说,只要放到容器里的对象,都会有容器的公共服务,我怎么没看出来呢?好,那我们就继续看一下我们的代理功能:

事务处理:

package cn.hackcoder.proxy;

import java.lang.reflect.Method;

/**
 * Created by linzhichao on 2017/2/21.
 */
public class TransactionHandler implements InvocationHandler {
    private Object target;

    public TransactionHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public void invoke(Object o, Method m, Object... params) {
        System.out.println("开启事务.....");
        try {
            m.invoke(target, params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("提交事务.....");
    }
}

执行时间:

package cn.hackcoder.proxy;

import java.lang.reflect.Method;

/**
 * Created by linzhichao on 2017/2/21.
 */
public class TimeHandler implements InvocationHandler {
    private Object target;

    public TimeHandler(Object target) {
        this.target = target;
    }

    @Override
    public void invoke(Object o, Method m, Object... params) {
        long time = -System.currentTimeMillis();
        try {
            m.invoke(target, params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        time += System.currentTimeMillis();
        System.out.println(m.getName() + "耗时:" + time);
    }
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值