动态代理-你不必知道我的存在

一、举例

要计算某个类的某个方法运行了多长时间?比如Tank类的move方法,要计算坦克移动了多长时间。

坦克可以移动,抽象出接口Moveable,里面一个move() 方法。

实现类Tank,实现Moveable接口

public class Tank implements Moveable{

    @Override
    public void move() {
        //计算方法运行了多长时间
        long start = System.currentTimeMillis();
        System.out.println("Tank Moving...");

        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("time:"+(end-start));
    }

}

如果你能修改源代码,可以在move方法内部的前后,计算开始、结束时间,相减就是move方法执行的时间。

如果你不能修改源代码,怎么办?

1,用继承

新建Tank2 ,继承Tank,重写move()方法,在move方法的前后,加上计算时间的逻辑

public class Tank2 extends Tank{

    @Override
    public void move() {
        //计算方法运行了多长时间
        long start = System.currentTimeMillis();
        super.move();
        long end = System.currentTimeMillis();
        System.out.println("time:"+(end-start));
    }    
}

2,用聚合

新建Tank3 ,实现Moveable接口,重写move()方法。Tank3有一个成员变量Tankmove()方法里调用Tankmove方法,Tank3其实就是Tank的一个代理。

 

public class Tank3 implements Moveable{

    Tank t;
    public Tank3(Tank t) {
        super();
        this.t = t;
    }
    @Override
    public void move() {
        //计算方法运行了多长时间
        long start = System.currentTimeMillis();
        t.move();
        long end = System.currentTimeMillis();
        System.out.println("time:"+(end-start));
    }

}

 

继承和聚合,都能实现计算move方法运行时长的问题,但是聚合更灵活。

Tank3Tank2都是Tank的一个代理。这里就是静态的代理

假设现在想要实现一个功能,先记录运行时间,再记录日志,那么如果用继承,就得这样写:

新建一个类,继承Tank2(记录运行时间的代理)

 

public class Tank2_1 extends Tank2{

    @Override
    public void move() {
        //记录日志
        System.out.println("Tank start....");
        super.move();
        System.out.println("Tank end....");
    }    
}

这样Test测试打印:

Tank start....

Tank Moving...

time:9528

Tank end....

如果想先记录时间,再记录日志呢?就要再新建一个类,顺序是,用时间的代理类,去继承日志的代理类,如果还有其他的代理,如权限检查的代理,等等,调换记录顺序,会更麻烦。。。代理类会无限制的多下去。

如果用聚合实现代理之间的组合呢?

用聚合实现代理,代理对象被代理对象要实现同一个接口:

TankLogProxy

 

public class TankLogProxy implements Moveable{

    Moveable m;
    public TankLogProxy(Moveable m) {
        super();
        this.m = m;
    }

    @Override
    public void move() {
        System.out.println("Tank start....");
        m.move();
        System.out.println("Tank end....");
    }

}

 

TankTimeProxy

 

public class TankTimeProxy implements Moveable{

    Moveable m;
    
    public TankTimeProxy(Moveable m) {
        super();
        this.m = m;
    }

    @Override
    public void move() {
        //计算方法运行了多长时间
        long start = System.currentTimeMillis();
        System.out.println("start:"+start);
        m.move();
        long end = System.currentTimeMillis();
        System.out.println("time:"+(end-start));
    }

}

 

测试:

先时间,再日志:

Tank tank = new Tank();
TankLogProxy tlp = new TankLogProxy(tank);
TankTimeProxy ttp = new TankTimeProxy(tlp);
ttp.move();

打印:

start:1581495475807

Tank start....

Tank Moving...

Tank end....

time:6543

先日志,再时间,只要调换测试类的代理顺序即可:

 

Tank tank = new Tank();
TankTimeProxy ttp = new TankTimeProxy(tank);
TankLogProxy tlp = new TankLogProxy(ttp);
tlp.move();

 

打印结果:

Tank start....

start:1581495785543

Tank Moving...

time:2139

Tank end....

可以看到,用聚合实现代理,要比用继承灵活的多!

第二个问题,先只考虑TimeProxy

Moveable接口:新添加stop()方法

 

public interface Moveable {
    void move();
    void stop();
}

 

Tank也实现stop方法

 

@Override
    public void stop() {
        System.out.println("Tank Stoping...");
    }

 

TankTimeProxy也记录stop方法的运行时间:

 

public class TankTimeProxy implements Moveable{

    Moveable m;
    
    public TankTimeProxy(Moveable m) {
        super();
        this.m = m;
    }

    @Override
    public void move() {
        //计算方法运行了多长时间
        long start = System.currentTimeMillis();
        System.out.println("start:"+start);
        m.move();
        long end = System.currentTimeMillis();
        System.out.println("time:"+(end-start));
    }

    @Override
    public void stop() {
        //计算方法运行了多长时间
        long start = System.currentTimeMillis();
        System.out.println("start:"+start);
        m.stop();
        long end = System.currentTimeMillis();
        System.out.println("time:"+(end-start));
        
    }

}

 

如果一段代码重复出现了多次,就要考虑封装了,movestop方法,都有计算时间的逻辑,可以考虑将他们封装成为方法。

现在如果要有个Car类的move方法,要记录汽车移动的时间,就需要再写个CarProxy

如果再有个Animal类的eat方法,要记录动物吃的时间,就要有个AnimalProxy

...... 如果一个系统有100个类,就要有100个代理类出现,又出现了类爆炸。

 

所以现在有个需求就是:

能不能产生一个代理类,可以给所有的类做代理呢???

从上边的例子可以看出,用聚合产生代理,需要代理类和被代理类实现同一个接口。

现在假设,假设被代理的类都实现某一个接口,(Spring里面也是这么要求的,Spring也能用继承实现代理但是不推荐),就能给这个类生成代理。

二,下面模拟JDK的实现

站在使用者的角度,有一个专门产生代理的类,假设现在只是产生时间的代理

    //站在使用者的角度,动态代理,Proxy产生一个代理类的对象,你根本看不到这个代理类的名字
        Moveable m = (Moveable)Proxy.newProxyInstance();
        m.move();
/**
 * 产生代理的类
 * @author dev
 *
 */
public class Proxy {

    public static Object newProxyInstance(){
        //只要能动态的 编译这段代码,就能动态的产生代理类!类的名字无所谓
        //动态编译的技术:JDK6 Compiler API,CGLib(用到了ASM) ,ASM
        //(CGLib、ASM不用源码来编译,能直接生成二进制文件,因为java的二进制文件格式是公开的)
        //Spring内部,如果是实现接口就是用的JDK本身的API产生代理,否则就用CGLib
        //换行字符串
        String rt = "
";
        String src = 
        "package com.lhy.proxy;"+ rt +

        "public class TankTimeProxy implements Moveable{"+rt +

        "    Moveable m;"+rt +
            
        "    public TankTimeProxy(Moveable m) {"+rt +
        "        super();"+rt +
        "        this.m = m;"+rt +
        "    }"+rt +

        "    @Override" +rt +
        "    public void move() {" +rt +
                //计算方法运行了多长时间
        "        long start = System.currentTimeMillis();" +rt +
        "        System.out.println("start:"+start);" +rt +
        "        m.move();"+rt +
        "        long end = System.currentTimeMillis();"+rt +
        "        System.out.println("time:"+(end-start));"+rt +
        "    }"+rt +
        "}";

        return null;
    } 
}

新建测试类,测试用java代码产生代理类,然后进行编译,然后load到内存进行加载,用反射新建一个代理类的对象。

 

package com.lhy.proxy;

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class TestCompiler {

    public static void main(String[] args) throws Exception{
        String rt = "
";
        String src = 
        "package com.lhy.proxy;"+ rt +

        "public class TankTimeProxy implements Moveable{"+rt +

        "    Moveable m;"+rt +
            
        "    public TankTimeProxy(Moveable m) {"+rt +
        "        super();"+rt +
        "        this.m = m;"+rt +
        "    }"+rt +

        "    @Override" +rt +
        "    public void move() {" +rt +
                //计算方法运行了多长时间
        "        long start = System.currentTimeMillis();" +rt +
        "        System.out.println("start:"+start);" +rt +
        "        m.move();"+rt +
        "        long end = System.currentTimeMillis();"+rt +
        "        System.out.println("time:"+(end-start));"+rt +
        "    }"+rt +
        "}";
        
        //1,生成代理类
        String fileName = System.getProperty("user.dir")
                            +"/src/com/lhy/proxy/TankTimeProxy.java";//获取项目根路径
        File file = new File(fileName);
        FileWriter fw = new FileWriter(file);
        fw.write(src);
        fw.flush();
        fw.close();
        
        //2,将生成的类进行编译成class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//拿到系统默认的编译器(其实就是javac)
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);//诊断监听器;语言;编码
        Iterable units =  fileMgr.getJavaFileObjects(fileName);
        CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units);
        task.call();
        fileMgr.close();
        
        //3,将class load到内存 
        URL[] urls = new URL[]{new URL("file:/"+ System.getProperty("user.dir")+"/src")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class clazz = urlClassLoader.loadClass("com.lhy.proxy.TankTimeProxy");
        //System.out.println(clazz);
        //4,,创建一个对象
        //不能用 clazz.newInstance();创建对象因为它会调用空构造方法
        Constructor<Moveable> constructor = clazz.getConstructor(Moveable.class);//获取某个类型参数的构造器
        Moveable m = constructor.newInstance(new Tank());//
        m.move();
    
    }
}

 

打印结果

start:1581515256858

Tank Moving...

time:6611

生成的代理类和编译后的class

 

 测试结果可以看出,可以动态产生代理类,你看不到代理类的名字,你只要调用Proxy.newProxyInstance()方法就能返回一个代理类,这就是动态代理,用完你就可以吧代理类的代码删了

但是现在产生的代理 是实现了Moveable接口的代理,要想产生实现任意接口的代理怎么办呢? 只要把接口传给产生代理的方法就可以了。而且 ,接口的方法,也要动态生成,这就需要用到反射了:

反射拿到接口的方法代码:

Method[] methods = Moveable.class.getMethods();
        for(Method m : methods){
            System.err.println(m.getName());//move
        }

修改后的产生代理的类:

用反射拿到接口的所有方法,动态的构建代理类的方法

package com.lhy.proxy;

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 javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * 产生代理的类
 * @author dev
 *
 */
public class Proxy {

    public static Object newProxyInstance(Class interfaces) throws Exception{//动态传入接口,其实jdk可以传多个接口
        //换行字符串
        String rt = "
";
        String methodStr = "";
        //反射拿到接口的所有的方法
        Method[] methods = interfaces.getMethods();
        for(Method m : methods){
            methodStr += "@Override"+rt +
                    "public void "+ m.getName()+ "() {"+
                    //计算方法运行了多长时间
                    "        long start = System.currentTimeMillis();" +rt +
                    "        System.out.println("start:"+start);" +rt +
                    "        m."+m.getName() +"();" +rt +
                    "        long end = System.currentTimeMillis();"+rt +
                    "        System.out.println("time:"+(end-start));"+rt +
                    "}";
        }
        
        //只要能动态的 编译这段代码,就能动态的产生代理类!类的名字无所谓
        //动态编译的技术:JDK6 Compiler API,CGLib(用到了ASM) ,ASM
        //(CGLib、ASM不用源码来编译,能直接生成二进制文件,因为java的二进制文件格式是公开的)
        //Spring内部,如果是实现接口就是用的JDK本身的API产生代理,否则就用CGLib
        
        String src = 
        "package com.lhy.proxy;"+ rt +

        "public class TankTimeProxy implements "+ interfaces.getName() +"{"+rt +

        "    Moveable m;"+rt +
            
        "    public TankTimeProxy(Moveable m) {"+rt +
        "        super();"+rt +
        "        this.m = m;"+rt +
        "    }"+rt +

         methodStr +
        "}";
        
        //1,生成代理类
        String fileName = System.getProperty("user.dir")
                            +"/src/com/lhy/proxy/TankTimeProxy.java";//获取项目根路径
        File file = new File(fileName);
        FileWriter fw = new FileWriter(file);
        fw.write(src);
        fw.flush();
        fw.close();
        
        //2,将生成的类进行编译成class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//拿到系统默认的编译器(其实就是javac)
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);//诊断监听器;语言;编码
        Iterable units =  fileMgr.getJavaFileObjects(fileName);
        CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units);
        task.call();
        fileMgr.close();
        
        //3,将class load到内存 
        URL[] urls = new URL[]{new URL("file:/"+ System.getProperty("user.dir")+"/src")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class clazz = urlClassLoader.loadClass("com.lhy.proxy.TankTimeProxy");
        System.out.println(clazz);
        //4,,创建一个对象
        //不能用 clazz.newInstance();创建对象因为它会调用空构造方法
        Constructor<Moveable> constructor = clazz.getConstructor(Moveable.class);//获取某个类型参数的构造器
        Object obj = constructor.newInstance(new Tank());//
        
        return obj;
    } 
}

测试

 

 产生的代理类TankTimeProxy

 

package com.lhy.proxy;

public class TankTimeProxy implements com.lhy.proxy.Moveable {
    Moveable m;

    public TankTimeProxy(Moveable m) {
        super();
        this.m = m;
    }

    @Override
    public void move() {
        long start = System.currentTimeMillis();
        System.out.println("start:" + start);
        m.move();
        long end = System.currentTimeMillis();
        System.out.println("time:" + (end - start));
    }
}

结论:

到目前为止,已经可以动态创建某个接口的代理类,并调用代理类的方法,但是目前的代理只是实现了时间的代理,代理的逻辑是写死的,肯定不能写死,那怎么写活呢?

思路:代理的逻辑,可以自己指定

写一个处理代理逻辑的接口

 

import java.lang.reflect.Method;
public interface InvocationHandler {

    /**
     * 代理执行的逻辑
     * @param o 方法所属的对象
     * @param m 要执行的方法
     */
    public void invoke(Object o,Method m);
}

 

时间的代理类的处理逻辑,实现InvocationHandler 接口

import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler{

    //被代理类
    private Object target;
    

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

    @Override
    public void invoke(Object o,Method m) {
        long start = System.currentTimeMillis();
        System.out.println("start:" + start);
        try {
            m.invoke(target, new Object[]{});
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("time:" + (end - start));
        
    }
}

产生代理类的Proxy

 

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 javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * 产生代理的类
 * @author dev
 *
 */
public class Proxy {
    /**
     * 
     * @param interfaces 代理实现的接口
     * @param h 代理处理逻辑
     * @return
     * @throws Exception
     */
    public static Object newProxyInstance(Class interfaces,InvocationHandler h) throws Exception{//动态传入接口,其实jdk可以传多个接口
        //换行字符串
        String rt = "
";
        String methodStr = "";
        //反射拿到接口的所有的方法
        Method[] methods = interfaces.getMethods();
        for(Method m : methods){
            methodStr += "@Override"+rt +
                    "public void "+ m.getName()+ "() {"+
                    "    try{"+rt+
                    "    Method md = "+ interfaces.getName()+".class.getMethod(""+m.getName()+"");"+rt+    
                    "    h.invoke(this,md);"+rt+ //this->代理对象
                        "    }catch(Exception e){e.printStackTrace();}"+
                    "}";
        }
        
        //只要能动态的 编译这段代码,就能动态的产生代理类!类的名字无所谓
        //动态编译的技术:JDK6 Compiler API,CGLib(用到了ASM) ,ASM
        //(CGLib、ASM不用源码来编译,能直接生成二进制文件,因为java的二进制文件格式是公开的)
        //Spring内部,如果是实现接口就是用的JDK本身的API产生代理,否则就用CGLib
        
        String src = 
        "package com.lhy.proxy;"+ rt +
        "import java.lang.reflect.Method;"+rt+
        "public class $Proxy1 implements "+ interfaces.getName() +"{"+rt +

        "    com.lhy.proxy.InvocationHandler h;"+rt+
        "    public $Proxy1(InvocationHandler h) {"+rt +
        "        this.h = h;"+rt +
        "    }"+rt +

         methodStr +
        "}";
        
        //1,生成代理类
        String fileName = System.getProperty("user.dir")
                            +"/src/com/lhy/proxy/$Proxy1.java";//获取项目根路径
        File file = new File(fileName);
        FileWriter fw = new FileWriter(file);
        fw.write(src);
        fw.flush();
        fw.close();
        
        //2,将生成的类进行编译成class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//拿到系统默认的编译器(其实就是javac)
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);//诊断监听器;语言;编码
        Iterable units =  fileMgr.getJavaFileObjects(fileName);
        CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units);
        task.call();
        fileMgr.close();
        
        //3,将class load到内存 
        URL[] urls = new URL[]{new URL("file:/"+ System.getProperty("user.dir")+"/src")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class clazz = urlClassLoader.loadClass("com.lhy.proxy.$Proxy1");
    
        //4,,创建一个对象
        //不能用 clazz.newInstance();创建对象因为它会调用空构造方法
        Constructor constructor = clazz.getConstructor(InvocationHandler.class);//获取某个类型参数的构造器
        Object obj = constructor.newInstance(h);//
        
        return obj;
    } 
}

 

测试代码:

 

public static void main(String[] args) throws Exception{
        InvocationHandler h = new TimeHandler(new Tank());
        //站在使用者的角度,动态代理,Proxy产生一个代理类的对象,你根本看不到这个代理类的名字
        Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class,h);
        m.move();
    }

 

打印结果:

start:1581596505206

Tank Moving...

time:5193

产生的代理类$Proxy1:

import java.lang.reflect.Method;

public class $Proxy1 implements com.lhy.proxy.Moveable {
    com.lhy.proxy.InvocationHandler h;

    public $Proxy1(InvocationHandler h) {
        this.h = h;
    }
    @Override
    public void move() {
        try {
            Method md = com.lhy.proxy.Moveable.class.getMethod("move");
            h.invoke(this, md);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

现在,想实现什么代理,只要实现InvocationHandler接口,自定义代理的处理逻辑,即可实现代理,这就是动态代理。

 

二,实际举例说明

UserMgr接口:

 

public interface UserMgr {
    void addUser();
}

 

UserMgr实现类

 

public class UserMgrImpl implements UserMgr {
    @Override
    public void addUser() {
        System.err.println("插入到数据库user表");
        System.err.println("记录到日志表");
    }
}

 

事务代理处理逻辑TransitionHandler

 

import java.lang.reflect.Method;
import com.lhy.proxy.InvocationHandler;

public class TransitionHandler implements InvocationHandler{
    private Object target;
    public TransitionHandler(Object target) {
        this.target = target;
    }
    @Override
    public void invoke(Object o, Method m) {
        System.err.println("事务开始....");
        try {
            m.invoke(target, new Object[]{});
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("事务回滚....");
        }
        System.err.println("事务提交....");
    }
}

 

测试类:

 

import com.lhy.proxy.InvocationHandler;
import com.lhy.proxy.Proxy;

public class Client {
    public static void main(String[] args) throws Exception{
        UserMgr userMgr = new UserMgrImpl();
        InvocationHandler h = new TransitionHandler(userMgr);
        UserMgr proxy = (UserMgr)Proxy.newProxyInstance(UserMgr.class, h);
        proxy.addUser();
    }
}

 

运行:

事务开始....

插入到数据库user

记录到日志表

事务提交....

产生的事务代理类:

 

import java.lang.reflect.Method;

public class $Proxy1 implements com.lhy.proxy.test.UserMgr {
    com.lhy.proxy.InvocationHandler h;

    public $Proxy1(InvocationHandler h) {
        this.h = h;
    }
    @Override
    public void addUser() {
        try {
            Method md = com.lhy.proxy.test.UserMgr.class.getMethod("addUser");
            h.invoke(this, md);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

从运行结果可以看出,已经控制了事务!

 

动态代理:不用修改原来的实现的代码,就能在原来基础上前后插入一些内容

AOP:可插拔的,可以将代理配置在配置文件,想实现什么样的代理就实现什么样的代理。代理之间是可以叠加的

AOP的运用:日志、事务、权限。。。。

 

完整代码在github,地址:    https://github.com/lhy1234/DesignPattern_Proxy.git

 

 

原文地址:https://www.cnblogs.com/lihaoyang/p/13296253.html