Proxy代理模式

当别人给你了源包,当你是用该源包方法的时候,无法修改源代码(比如只给你一个编译过的class文件)来修改实现你所需要的功能,或者需要把业务代码和日志打印完全分离开,不想要业务代码里面混着日志代码,或者需要提供的java类什么地方是瓶颈,肯定需要在方法的前后加上一些代码,就需要用到代理模式

Tank

package Proxy;

import java.util.Random;

public class Tank implements Moveable{

    @Override
    public void move() {
       System.out.println("Tanking moving....");
       try {
        Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       }
    }

Tank的代理者们

//日志代理
package Proxy;
//和被代理对象实现一样的接口
public class TankLogProxy implements Moveable{
    Tank tank;
    public TankLogProxy(Tank tank) {
        super();
        this.tank = tank;
    }
    @Override
    public void move() {
        //方法开始执行之前,可以进行检查、记录等操作
        System.out.print("Tank..start");
        tank.move();//--------------代理调用
        //方法结束后可以写日志等。
        System.out.print("Tank..stop");
    }
}
//时间代理
package Proxy;
import java.sql.Time;
//和被代理对象实现一样的接口
public class TankTimeProxy implements Moveable{
    Tank tank;
    public TankTimeProxy(Tank tank) {
        super();
        this.tank = tank;
    }
    @Override
    public void move() {
        //方法开始执行之前,可以进行检查、记录等操作
        long start=System.currentTimeMillis();
        tank.move();//--------------代理调用
        //方法结束后可以写日志等。
        long end=System.currentTimeMillis();
        System.out.println(start-end);
    }
}

调用

public class Client {
    public static void main(String[] args) {
        Tank tank=new Tank();
        TankLogProxy tlp=new TankLogProxy(tank);//把tank对象交给其代理对象
        tlp.move();
    }
}

两个代理一起使用(时间代理包着日志代理)

package Proxy;

public class Client {
    public static void main(String[] args) {
        Tank tank=new Tank();
        TankLogProxy tlp=new TankLogProxy(tank);//把tank对象交给其代理对象
        TankTimeProxy ttp=new TankTimeProxy(tlp);//以为都实现Moveable接口,把tlp日志代理对象交给时间代理对象
        ttp.move(); //先调用时间日志的move,会执行获取时间,因为把tlp传给时间,所以move方法执行的是日志代理的move 
    }
}

聚合的方式实现代理:
1、会和原来被代理对象实现一样的接口,代理者内部会聚合一个被代理对象。
2、所以,代理者里面可以装任何实现实现了某接口的对象,甚至是另外一个代理者。
这里写图片描述



问题:
1、当既需要时间代码,也需要日志代理,可以把它们组合在一起用,弄成一个新的代理者(各种不同的功能叠加,代理一个类就会包一层,再把该代理类代理,又会继续包一层,会使得继承或者聚合无限制的往下走,继承会产生类爆炸,聚合会产生好多层包下去),那如果还需要权限验证或者先执行日志后执行时间代理呢?
2、若Moveable接口里面增加一个方法,该方法也需要日志代理和时间代理,是不是还要在该方法内重写一遍打印时间和记录日志的操作呢?重写一个代理者?
3、若需要对任意一个对象进行时间代理和日志代理,而不仅仅只是Tank对象,比如,我想对Car实现,对Animal实现,难道还要全部写一遍各自的Proxy吗?比如如果需要对系统中任意对象进行运行时间的观察,那么又该怎样呢?

产生一个代理,可以对任何一个对象进行代理
以上代码中,代理者代理的对象都是实现了Moveable接口的对象。
所以,代理模式代理的对象都实现了某一个接口,所以真正代理的时候,是根据那个接口来实现代理的,而不是类
Spring中也是如此要求的,但是也可以不用接口实现代理,也可以用继承,但是Spring是不推荐这样做的。
AOP是动态代理的一种应用。

动态代理

模拟JDK实现动态代理
Moveable作为需要实现动态代理的接口

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

编写Proxy类,用来作为产生代理对象的类(代理的总代理)

package Proxy;

public class Proxy {
    /**
     *所有被代理对象都由该方法产生代理对象
     * @return
     */
    public static Object newProxyInstance(){
        String src="package Proxy;" +
                "import java.sql.Time;" +
                "public class TankTimeProxy implements Moveable{" +
                "Moveable tank;" +
                "   public TankTimeProxy(Moveable tlp) {" +
                "   super();" +
                "this.tank = tlp;" +
                "}" +
                "   @Override" +
                "public void move() {" +
                "//方法开始执行之前,可以进行检查、记录等操作" +
                "   long start=System.currentTimeMillis();" +
                "   tank.move();" +
                "//方法结束后可以写日志等。" +
                "long end=System.currentTimeMillis();" +
                "   System.out.println(start-end);" +
                "}" +
                "   @Override" +
                "public void stop() {" +
                "   //方法开始执行之前,可以进行检查、记录等操作" +
                " long start=System.currentTimeMillis();" +
                "       tank.move();" +
                "       //方法结束后可以写日志等。" +
                "       long end=System.currentTimeMillis();" +
                "       System.out.println(start-end);" +
                "   }" +
                "}";
        return null;
    }
}

以上代码中,如果src代表的一段源代码可以被编译装入内存中,直接产生一个新的代理对象,那么项目中甚至可以不写TankTimeProxy 类,直接可以由Proxy类的newProxyInstance方法根据src动态的生成代理对象。

动态代理的思想:不需要再写什么TankTimeProxy,只需要调用Proxy的newProxyInstance方法就可以直接返回代理对象,也不需要什么代理对象的名字。
如何生成动态代理对象呢?要能够动态编译src的这段源代码,这样就可以在运行的时候想得到什么类就得到什么类。

动态代理就是为了解决业务层分级、解耦、类太多的问题。

动态编译:

1、JDK6 Complier API,用jdk本身就是用接口实现动态代理
2、网上现有的开源的东西,如CGLib、ASM(甚至不用源码编译,会直接生成二进制文件,因为java的二进制文件的格式也是公开的。如果能理解二进制文件的意思,甚至连编译器都不需要了。)
【Spring中就是用的CGLib、ASM】

注意:编译器API只有在JDK6中有,以前的没有

JDK6调用Complier API动态编译

package main;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

public class main {
    public static void main(String[] args) throws IOException {
        String src="package main;" +"\r\n"+
                "import java.sql.Time;" +"\r\n"+
                "import Proxy.Moveable;"+"\r\n"+
                "public class TankTimeProxy implements Moveable{" +"\r\n"+
                "Moveable tank;" +"\r\n"+
                "   public TankTimeProxy(Moveable tlp) {" +"\r\n"+
                "   super();" +"\r\n"+
                "   this.tank = tlp;" +"\r\n"+
                "}" +"\r\n"+
                "   @Override" +"\r\n"+
                "   public void move() {" +"\r\n"+
                "   //方法开始执行之前,可以进行检查、记录等操作" +"\r\n"+
                "   long start=System.currentTimeMillis();" +"\r\n"+
                "   tank.move();" +"\r\n"+
                "   //方法结束后可以写日志等。" +"\r\n"+
                "   long end=System.currentTimeMillis();" +"\r\n"+
                "   System.out.println(start-end);" +"\r\n"+
                "}" +"\r\n"+
                "   @Override" +"\r\n"+
                "   public void stop() {" +"\r\n"+
                "   //方法开始执行之前,可以进行检查、记录等操作" +"\r\n"+
                "   long start=System.currentTimeMillis();" +"\r\n"+
                "   tank.move();" +"\r\n"+
                "   //方法结束后可以写日志等。" +"\r\n"+
                "   long end=System.currentTimeMillis();" +"\r\n"+
                "   System.out.println(start-end);" +"\r\n"+
                "   }" +"\r\n"+
                "}"+"\r\n";
                //先把源代码写入一个临时文件中
        String fileName=System.getProperty("user.dir");//获取当前文件的系统目录,就是项目的根路径
        String temp=fileName+"/src/main/TankTimeProxy.java";//临时文件系统路径
        File f=new File(temp);
        FileWriter fw=new FileWriter(f);
        fw.write(src);
        fw.flush();
        fw.close();

        //动态编译
        JavaCompiler complier=ToolProvider.getSystemJavaCompiler();//拿到编译器对象
        StandardJavaFileManager fileMgr=complier.getStandardFileManager(null,null,null);//文件管理对象
        Iterable units=fileMgr.getJavaFileObjects(temp);//得到需要编译的文件
        CompilationTask t=complier.getTask(null,fileMgr,null,null,null,units);//一次编译任务
        t.call();//调用编译任务
        fileMgr.close();//关闭

        //把class类文件load到内存中(用ClassLoader把class文件加载到内存,该class文件必须是在classPath下的,而自己写的动态 编译的class文件是在源码src下而不在bin里,
        //所以load class不能用ClassLoader,要用URLClassLoader。如果把自己动态编译的class放到bin里,就会和编译器自动编译的混淆在一起)
        URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")};//在找class的时候去该目录下找
        URLClassLoader ul=new URLClassLoader(urls);
        Class c=ul.loadClass("main.TankTimeProxy");//网上传来的class文件也可以这样load进来

        //生成新对象,用到反射
        Constructor ctr=c.getConstructor(Moveable.class);//拿到构造方法,表示找到一个参数是Moveable.class的构造方法
        Moveable m=(Moveable) ctr.newInstance(new Tank());//Tank为被代理的对象
        m.move();
    }
}


Moveable m=(Moveable) ctr.newInstance(new Tank());//Tank为被代理的对象
我们根本不知道具体的是什么类的代理对象,只知道是实现了Moveable接口的类,这就是动态生成代理类对象。



可能遇到的问题:
JavaCompiler:JDK6中新提供的一个类
JavaCompiler complier=ToolProvider.getSystemJavaCompiler();会拿到系统中默认的java编译器,其实就是javac。(如获取的时候报null,空指针异常,往往是由配置引起的,打开eclipse,在installed JRES中, jre是只包含纯运行环境,不包含编译的,要添加我们自己的jdk环境,而不是纯jre。
在非常多Java应用中需要在程序中调用Java编译器来编译和运行。但在早期的版本中(Java SE5及以前版本)中只能通过tools.jar中的com.sun.tools.javac包来调用Java编译器,但由于tools.jar不是标准的Java库,在使用时必须要设置这个jar的路径。而在Java SE6中为我们提供了标准的包来操作Java编译器,这就是javax.tools包。使用这个包,我们能不用将jar文件路径添加到classpath中了。

JDK6公开了Complier API,不用JDK6也可以进行动态编译,如使用编译命令(需要环境支持)或者使用Sun未公开的编译类。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值