【设计模式】-03代理模式

1.什么是代理模式?

百度百科:所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网上连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

代理模式在Java中十分常见,有为扩展某些类的功能而使用静态代理,也有如Spring实现AOP而使用动态代理,更有RPC实现中使用的调用端调用的代理服务。

2.为什么使用代理模式?

中介隔离作用:代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。

开闭原则(扩展开放,修改关闭):代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则,使系统具有高扩展性。

3.如何实现代理模式?

代理模式分为静态代理和动态代理(jdk动态代理、cglib动态代理、Spring和AspectJ),静态代理是在程序运行前,代理类.class文件已经生成,而动态代理是程序运行中通过反射机制动态创建的.

3.1静态代理

静态代理一般有两种实现方式:继承和聚合.

为了分别用代码去实现这两种方式,我先写个Demo,接下来继承和聚合都分别代理这个Demo去实现,代码我尽量简单,重在理解和原理:

先定义一个接口:

public interface Move {
    void move();
}

实现这个接口:

public class Car implements Move {
    public void move() {
        Long begainTime = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        try {
            System.out.println("汽车行驶中...");
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Long endTime = System.currentTimeMillis();
        System.out.println("汽车行驶完毕,行驶了:"+(endTime-begainTime)+"毫秒");
    }
}

测试一下:

public class Test {
    public static void main(String[] args) {
        Move move = new Car();
        move.move();
    }
}

效果如图:

3.1.1继承

下面通过继承方式,实现不修改原来代码的基础上,实现对move接口提供的内容进行修改.

/**
 * 静态代理-继承实现
 */
public class Car2 extends Car{
    @Override
    public void move() {
        System.out.println("驾驶员打开了左转向灯...");
        super.move();//通过继承直接调用父类方法
        System.out.println("驾驶员下车了...");
    }
}

测试一下:

public class Test {
    public static void main(String[] args) {
       Move move = new Car2();
       move.move();
    }
}

效果如图:

3.1.2聚合

下面通过聚合方式,实现不修改原来代码的基础上,实现对move接口提供的内容进行修改.

/**
 * 静态代理-聚合
 */
public class Car3 implements Move {
    private Car car;
    public Car3(Car car){
        this.car = car;
    }
    public void move() {
        System.out.println("前面有山洞,驾驶员打开了近光灯...");
        car.move();
        System.out.println("车子故障了,驾驶员下了车...");
    }
}

测试一下:

public class Test {
    public static void main(String[] args) {
        Car car = new Car();
        Move move = new Car3(car);
        move.move();
    }
}

效果如图:

总结:无论是继承还是聚合,都实现了预期效果,但哪种更好用呢?不难发现,通过聚合这种方式实现会更加灵活,在多个代理的情况下,代理之间可以以构造参数的形式传递,从而实现相互调用,但无论如何静态代理还是不够灵活,如果我现在有辆自行车而不是car,我也需要用代理实现跟car一样的功能,那么我还需要为自行车也创建代理,以后要有火车,马车...都得去写代理,那也太麻烦了,有没有什么办法?下面要讲的动态代理就能灵活的解决这些弊端.

3.2动态代理

动态代理有两种实现方式:一种是基于jdk提供的方式去动态创建代理对象,另一种是通过cglib的实现,两者各有使用场景,前者只适用于实现接口的被代理对象,如果被代理对象并没有实现接口,就不能使用jdk动态代理,只能使用后者,但因为后者采用的是继承,所以不能对final修饰的类进行代理,两者各有适用场景,spring aop的aspectj采用的就是以上两种方式的结合.

3.2.1基于JDK实现

①创建一个类,实现InvocationHandler接口:

public class JdkproxyHandler implements InvocationHandler {
    private Object object;//这里的object其实就是被代理对象
    public JdkproxyHandler(Object object){
        this.object = object;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前面有交警,驾驶员绕道了...");
        method.invoke(object);//调用被代理对象的方法
        System.out.println("交警追上来了,驾驶员吓尿了...");
        return object;
    }
}

②调用Proxy.newProxyInstance方法创建一个代理类:

public class Test {
    public static void main(String[] args) {
        Move jdkProxy = (Move) Proxy.newProxyInstance(Car.class.getClassLoader(),new Class[]{Move.class},new JdkproxyHandler(new Car()));
        jdkProxy.move();//执行代理后的方法
    }
}

※ Proxy.newProxyInstance()方法接受三个参数:

▶ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的

▶Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型

▶InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

来看一下效果:

为什么基于Jdk创建代理对象时,被代理对象必须实现接口?

原因是因为java是单继承多实现的,所以jdk官方采用第二种方式写了动态代理的源码,要求传入的参数必须是接口.

public class $Proxy1 extends Proxy implements 传入的接口{

//

}

这里就简单抛转引玉一下,因为要从jdk源码开始讲清楚为什么,其实一篇博客都不一定真正讲得完,感兴趣的可以参考下面这篇博文:

https://www.cnblogs.com/frankliiu-java/articles/1896443.html

 

3.2.2基于cglib实现

①先创建一个类,提供一个需要被代理的方法,这个类不需要实现接口,但不能加final修饰:

public class Train {
    public void move(){
        System.out.println("开火车了,污...");
    }
}

②创建代理类,实现MethodInterceptor接口:

public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    /**
     * 获取代理对象的实例
     *
     * @param clazz 目标对象的字节码文件
     * @return 代理对象的实例
     */
    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     * @param o           目标类的实例
     * @param method      目标方法的反射对象
     * @param objects     目标方法的参数
     * @param methodProxy 代理类的实例
     * @return
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("检票进站了...");
        //代理类调用父类的方法
        methodProxy.invokeSuper(o, objects);
        System.out.println("火车开往幼儿园...");
        return null;
    }
}

③测试一下:

public class Test {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        Train train = (Train) proxy.getProxy(Train.class);
        train.move();
    }
}

测试结果:

CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理.

两种代理方式各有适用场景,那有没有一种"万能"的代理实现方式?有,而且你绝对很熟悉,那就是spring的aop,其本质也是对这两种方式的代理实现进行了封装,使其能够适应各种场景,当然当你的方法既没有实现任何接口,还加了final修饰,那spring的aop也无能为力,在这种情况下会失效.

4.手写实现JDK动态代理(基础版)

4.1实现思路

①创建Proxy类,创建newProxyInstance静态方法.

②手写Proxy类中的源码,将其生成.java文件.

③编译.java文件并Load至内存中.

④返回代理对象,并测试.

4.2实现

创建InvocationHandler并实现它

public interface InvocationHandler {
    void invoke(Object proxy, Method method);
}
public class CarHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public void invoke(Object proxy, Method method) {
        try {
            System.out.println("测试手写代理...");
            method.invoke(target);
            System.out.println("jdk源码太高深了,吓得我车都停了...");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

创建Proxy类:

public class Proxy {
    public static Object newProxyInstance(Class inface, InvocationHandler h) throws Exception {
        String methodStr = "";
        for (Method method : inface.getMethods()) {
            methodStr += "     public void " + method.getName() + "() {\n" +
                    "           try{ \n" +
                    "               Method md = " + inface.getName() + ".class.getMethod(\""
                    + method.getName() + "\");\n" +
                    "               h.invoke(this,md);\n" +
                    "           }catch(Exception e){\n" +
                    "               e.printStackTrace();\n" +
                    "           }\n" +
                    "     }\n";
        }
        String str = "package com.xpc.myproxy;\n" +
                "\n" +
                "import com.xpc.proxy.Moveable;\n" +
                "import com.xpc.proxy.InvocationHandler;\n" +
                "import java.lang.reflect.Method;\n" +
                "public class $Proxy0 implements" + " " + inface.getSimpleName() + "{\n" +
                "\n" +
                "     private InvocationHandler h;\n" +
                "\n" +
                "     public $Proxy0(InvocationHandler h) {\n" +
                "        this.h = h;\n" +
                "    }\n" +
                "\n" + methodStr + "}";
        //产生java文件
        String fileName = System.getProperty("user.dir") + "\\src\\main\\java\\com\\xpc\\myproxy\\$Proxy0.java";
        System.out.println(fileName);
        File file = new File(fileName);
        FileUtils.writeStringToFile(file, str);
        //编译
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable units = fileManager.getJavaFileObjects(fileName);
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
        task.call();
        fileManager.close();
        //lodad到内存中
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Class clzz = classLoader.loadClass("com.xpc.myproxy.$Proxy0");
        Constructor constructor = clzz.getConstructor(InvocationHandler.class);
        return constructor.newInstance(h);
    }
}

其实上面这一堆代码主要是为了生成下面这两个文件:

其中$Proxy0的内容如下:

不难发现,其实跟我们手写的静态代理的聚合方式是一样的,动态代理也不是多么高大上的东西,本质还是个静态代理,只不过这个静态的代理类可以动态地生成.

然后将编译后的$Proxy0.class文件加载到内存中,然后调用生成我们需要的代理对象,最后调用代理对象中的方法.

public class Test {
    public static void main(String[] args) throws Exception {
        Car car = new Car();
        InvocationHandler h = new CarHandler(car);
       Moveable moveable = (Moveable) Proxy.newProxyInstance(Moveable.class,h);
       Thread.sleep(100);//模拟将创建的class文件加载至内存中的延迟
       moveable.move();
    }
}

测试结果:

没错,跟使用jdk提供的代理用起来几乎一模一样,当然手写版终究是手写版,不能跟jdk大神写的相提并论,jdk源码中对所传递的参数,接口大小,都作了验证,加载也作了缓存,几乎该优化的都优化完了,本篇仅是精简版的代理源码,留作帮助理解用.

项目中所涉及依赖:


        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.7</version>
        </dependency>
        <dependency>
            <groupId>org.zenframework.z8.dependencies.commons</groupId>
            <artifactId>commons-io-2.2</artifactId>
            <version>2.0</version>
        </dependency>

更多设计模式,请关注本博.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值