模拟JDK动态代理类的实现

问题:
要理解动态代理及其优点,我们先从这样一个问题入手,比如现在我们有UserDao这样一个接口,里面有addUser()方法,同时有一个UserDaoImpl类实现了该接口,具体实现了addUser()方法,现在我要实现在该方法前后记录日志的功能,有什么解决办法呢?

  1. 在源代码上直接修改。第一反应肯定是直接在源码上添加该功能,但是如果我的需求变成在所有的DaoImpl包里的类的所有的方法都添加记录日志的功能,那再去每一个都添加,工作量大,代码的重用率也不高,而且有的时候你可能没有源代码,所以该方法不考虑。
  2. 继承。写一个类继承UserDaoImpl类,再重写addUser()方法,在方法的前后实现记录日志。
public class ExtendsImpl1 extends UserDaoImpl {
    @Override
    public void addUser() {
        //模拟记录日志
        System.out.println("开始记录日志......");

        super.addUser();

        System.out.println("记录日志结束。");
    }
}

这样是可以完成功能。但是缺点有三:1.Java只支持单继承,这意味着该类在没法继承别的类,2.比如我现在功能拓展为先记录日志,在处理事务?如果按照继承的思路只能在一个ExtendsImpl2继承ExtendsImpl1在外面在包装上一层事务处理的代码。如果现在我要求先处理事务在记录日志,那我又要在重新写两个类……到这里大家就可以看出弊端,我有N多种功能,每种功能之间有不同的组合,那我们要写的类的数量就很多了,因此该方法也不推荐。

  1. 聚合。我们写一个专门记录日志的类,同时该类实现了UserDao这个接口,同时在构造方法给该类传递一个该接口变量,再覆盖的addUser方法中通过实例化不同的接口实例变量来调用addUser方法。结构如下:
public class LogProxy implements UserDao{
    UserDao u ;
    public LogProxy(UserDao u) {
        super();
        this.u = u;
    }
    @Override
    public void addUser() {
        //模拟记录日志
        System.out.println("开始记录日志......");
        u.addUser();
        System.out.println("记录日志结束。");
    }
}

public class TransacProxy  implements UserDao{
    UserDao u ;
    public TransacProxy(UserDao u) {
        super();
        this.u = u;
    }
    @Override
    public void addUser() {
        //模拟事务
        System.out.println("transaction begin......");
        u.addUser();
        System.out.println("transaction commit.");
    }
}

假如现在我要增加事务功能,同时实现和日志功能的组合,只需要创建实现事务和日志的两个类的对象,并互相包装即可,因为这两个类都实现了USerDao接口,并持有一个该接口类型的变量。LogProxy log = new LogProxy(userDao);TransacProxy trac = new TransacProxy(log);就是完成先记录日志,在处理事务。TransacProxy trac = new TransacProxy(userDao);LogProxy log = new LogProxy(trac);完成先处理事务,在记录日志。

public static void main(String[] args) {
        UserDao userDao = new UserDaoImpl();
        //先记录日志,在处理事务
        //LogProxy log =  new LogProxy(userDao);
        //TransacProxy trac = new TransacProxy(log);
        //完成先处理事务,在记录日志
        TransacProxy trac = new TransacProxy(userDao);
        LogProxy log =  new LogProxy(trac);
        log.addUser();
    }

多种功能的组合的时候,道理一样。这个时候LogProxy就可以看成是UserDaoImpl的一个代理。继承的思路基本接近动态代理的思路了,只是现在LogProxy只是UserDaoImpl类的addUser方法的日志代理类,我现在想把它写成任意接口任意方法的日志代理类,这时候就要用到动态代理了。
动态代理
自己写了个两个类模拟Java动态代理的实现,即可实现对任意接口的任意方法的AOP,只注重实现思路,并没实现细节,默认被切入点的被代理的方法是无参的(有参数反射依然可以实现,默认无参只是为了说明问题)。比如我现在要在任意接口的所有方法执行时统计方法执行的时间,即在每个方法执行前后获得系统时间取差值即为执行时间。首先做一个接口InvocationHandler,他的实现类TimeHandler中的invoke方法根据传递进来的被代理对象和方法,用反射执行该方法,并在方法执行前后统计时间;

public interface InvocationHandler {

    public void invoke(Method m)  throws Exception ;
}

public class TimeHandler implements InvocationHandler {
    //被代理对象
    Object target;

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

    @Override
    public void invoke(Method m) throws Exception {
        long start = System.currentTimeMillis();
        System.out.println("starttime:" + start);
        //接收代理类传递过来的方法
        m.invoke(target);
        long end = System.currentTimeMillis();
        System.out.println("time:" + (end-start));
    }

}

第2个类Proxy,是一个动态产生代理的类,在newProxyInstance()方法中,只需要你传递接口的Class和你需要的InvocationHandler实现类,通过拼接字符串,该字符串通过FileWriter写入到一个Java文件中去,该Java类就是我们的时间代理类,该类会调用InvocationHandler对象的invoke(Method method)方法,就会实现每个方法的时间统计功能。我们会将该代理类手动编译,使用classLoader加载,通过反射返回被代理接口类型的对象。

public class Proxy {

    public static Object newProxyInstance(Class inter , InvocationHandler handler) throws Exception{
        String methodStr="";
        String rt = "\r\n";
        Method[] methods = inter.getMethods();
        for(Method m : methods){
        /*  methodStr +="@Override"+rt+
                        "public void "+m.getName()+"() {"+rt+
                            "    long start = System.currentTimeMillis();" + rt +
                            "    System.out.println(\"starttime:\" + start);" + rt +
                            "    t.move();" + rt +
                            "    long end = System.currentTimeMillis();" + rt +
                            "    System.out.println(\"time:\" + (end-start));" + rt +
                        "}";*/
            methodStr +="@Override"+rt+
                    "public void "+m.getName()+"() {"+rt+
                        "java.lang.reflect.Method method;"+rt+
                        "try {"+rt+
                        "method = "+inter.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+
                        "h.invoke(this , method);"+rt+
                        "} catch (Exception e) {"+rt+
                        "e.printStackTrace();"+rt+
                        "}"+rt+
                    "}";
        }
        String src = 
            "package com.proxy;" +  rt +
            "public class $proxy1 implements "+inter.getName()+ "{" + rt +
            "    public $proxy1(InvocationHandler h) {" + rt +
            "        super();" + rt +
            "        this.h = h;" + rt +
            "    }" + rt +

            "    InvocationHandler h;" + rt +
                methodStr+rt+
            "}";
        String filePath = System.getProperty("user.dir")+"\\src\\com\\proxy\\$proxy1.java";
        File file = new File(filePath);
        FileWriter fw = new FileWriter(file);
        fw.write(src);
        fw.flush();
        fw.close();

        //compile
        JavaCompiler javaCompile = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr = javaCompile.getStandardFileManager(null, null, null);
        Iterable units = fileMgr.getJavaFileObjects(filePath);
        CompilationTask t = javaCompile.getTask(null, fileMgr, null, null, null, units);
        t.call();
        fileMgr.close();

        //load into memory and create an instance
        URL[] urls = new URL[] {new URL("file:/"+System.getProperty("user.dir")+"/src/")};
        URLClassLoader ul = new URLClassLoader(urls);
        Class c = ul.loadClass("com.proxy.$proxy1");

        //反射
        Constructor constructor = c.getConstructor(InvocationHandler.class);
        Object object = constructor.newInstance(handler);
        return object;
    }
}

自动生成的Java代理对象如下:

package com.proxy;
public class $proxy1 implements com.test.UserManager{
    public $proxy1(InvocationHandler h) {
        super();
        this.h = h;
    }
    InvocationHandler h;
    @Override
    public void addUsers() {
        java.lang.reflect.Method method;
        try {
            method = com.test.UserManager.class.getMethod("addUsers");
            h.invoke(method);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

该类就是自动生成的代理类,跟聚合时使用的代理有点像,只是那个被代理类对代理类是透明的,被代理对象在TimeHandler中声明,并通过反射,将被代理对象的方法交给TimeHandler来做。如此,我们便可代理任何借口的任意方法,比如下面我要在实现了Moveable接口的Tank类里面的方法统计时间,只需如下操作:

public class Client {
    public static void main(String[] args) throws Exception {
        Moveable t = new Tank();
        InvocationHandler h = new TimeHandler(t);
        Moveable moveable = (Moveable)Proxy.newProxyInstance(Moveable.class, h);
        moveable.move();
    }

}

Java动态代理技术的强大由此可见一斑!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值