【设计模式】一文学透代理模式

文章目录


1、前言

Spring 的AOP 面向切面编程,是通过动态代理实现的, 由两部分组成:(a) 如果有接口的话 通过 JDK 接口级别的代理 (b) 如果没有接口的话,通过方法级别的代理 CGLib代理实现。

1.1、定义

什么是代理模式?

      代理模式就是多一个代理类出来,代替原对象进行一些操作。

      代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。举例说明,租房的中介、打官司的律师、旅行社,他们可以代替我们做一些事情,这就是代理 一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用。

      我们想对外开放某些功能,就可以将这些功能在代理类中被引用,如此一来,屏蔽了我们不想外露的功能,只将我们想开放的功能开放出来。亦即委托类中其实是可以有很多方法的,很多功能的,我们可以酌情对外开放,代理类犹如一道大门,将委托类与外部调用者隔绝开来,只将部分功能赋予这个大门,来代替委托类行使这个功能,哪怕最终还是要牵扯到自身(因为最终还是要调用委托类的对应方法实现)。

1.2、代理模式的实现

代理模式很简单,只要记住以下关键点,简单易实现:

(1)代理类与委托类实现同一接口

(2)在委托类中实现功能,在代理类的方法中中引用委托类的同名方法

(3)外部类调用委托类某个方法时,直接以接口指向代理类的实例,这正是代理的意义所在:屏蔽。

1.3、代理模式的应场景

如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:

  1. 修改原有的方法来做到改进。但这样违反了“对扩展开放,对修改关闭”的原则。
  2. 采用一个代理类调用原有的方法,且对产生的结果进行控制。这就是代理模式。

      1)当我们想要隐藏某个类时,可以为其提供代理类

      2)当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中金进行权限判断来进行不同权限的功能调用)

      3)当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行)

      代理模式虽然实现了调用者与委托类之间的强耦合,但是却增加了代理类与委托类之间的强耦合(在代理类中显式调用委托类的方法),而且增加代理类之后明显会增加处理时间,拖慢处理时间。

1.4、代理模式的分类:

      静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。再程序运行前代理类的.class文件就已经存在了。

      动态代理:在程序运行时用反射机制,动态创建代理类。动态代理有两种-JDK代理——接口级别代理CGLib代理——方法级别代理

2、静态代理

      所谓静态就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

      通过上面的代理模式描述我们可以知道,其目的就是为了控制对象引用,生活场景中我们以买车为例,如果我们要买一辆轿车必须通过汽车4S店,汽车4s店就是充当代理角色,其目的就是控制买车客户的买车行为,必须通过汽车4S店才能从汽车厂商买一辆车。
【设计模式】一文学透代理模式

1.)首先新建一个买车的接口

public interface IBuyCar {
    //买车
    void buyCar();
}

2.)声明一个要买车的客户,实现买车接口

public class Customer implements IBuyCar {

    private int cash;//购车款

    public int getCash() {
        return cash;
    }

    public void setCash(int cash) {
        this.cash = cash;
    }

    @Override
    public void buyCar() {
        Log.e("buyCar", "买一辆车花费了-->" + cash + "元");
    }
}

3.)声明一个买车代理汽车4S店,同样也实现买车接口,必须接受客户下单

public class BuyCarProxy implements IBuyCar{
    private Customer customer;//接收买车客户

    public BuyCarProxy(Customer customer){
        this.customer=customer;//接收买车客户
    }

    @Override
    public void buyCar() {//实现为客户买车
        customer.buyCar();
    }
}

4.) 创建一个客户端,模拟一次买车

 Customer customer=new Customer();
  customer.setCash(120000);
  BuyCarProxy buyCarProxy=new BuyCarProxy(customer);
  buyCarProxy.buyCar();

【设计模式】一文学透代理模式
代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

5.)通过代理模式实现权限控制

      通过上面的例子,我们可能有个疑问,难道就不能直接去厂家买车吗?当然可以,如果在使用场景中实现类能满足要求时,我们当然可以直接实现类,但当实现类不能满足要求,要扩展需求,根据开闭原则你又不能修改实现类代码,这时你就用代理类。比如购买一辆车我们要对客户进行一个购车款审核,如果符合条件就买车,不符合要求我们就告知客户购车款不足。

@Override
    public void buyCar() {//实现为客户买车
        int cash=customer.getCash();
        if(cash<100000){
            Log.e("buyCar","你的钱不够买一辆车");
            return;
        }
        customer.buyCar();
    }

实现场景

Customer customer=new Customer();
customer.setCash(120000);
BuyCarProxy buyCarProxy=new BuyCarProxy(customer);
buyCarProxy.buyCar();

Customer customer1 =new Customer();
customer1.setCash(90000);
BuyCarProxy buyCarProxy1 =new BuyCarProxy(customer1);
buyCarProxy1.buyCar();

【设计模式】一文学透代理模式
静态代理优点:
客户端不必知道实现类(委托类)如何如何,只需要调用代理类即可。
缺点:

  • 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。但这样出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也要实现这个方法。这显然增加了代码的复杂度。
  • 代理对象只服务于一种类型的对象,如果要服务多类型的对象,那就要对每种对象都进行代理。静态代理子啊程序规模稍大是就无法胜任了。

3、动态代理机制:

      以上讲的都是代理模式的静态实现,所谓静态代理就是自己要为要代理的类写一个代理类,或者用工具为其生成的代理类,总之,就是程序运行前就已经存在的编译好的代理类,这样有时候会觉得非常麻烦,也导致非常的不灵活,相比静态代理,动态代理具有更强的灵活性,因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延迟到程序运行时由JVM来实现。

3.1、 JDK代理——接口级别代理

package Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//Created by zhengbinMac on 2017/2/19.
public class DynamicProxy implements InvocationHandler{
    private Object target;
    public DynamicProxy(Object target) { this.target = target; }
    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this
        );
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        sayBefore();
        Object result = method.invoke(target, args);
        sayAfter();
        return result;
    }
    private void sayBefore() { System.out.println("before..."); }
    private void sayAfter() { System.out.println("after..."); }
}

JDK 提供的 Proxy 类的工厂方法 newProxyInstance 去动态地创建一个 Hello 接口的代理类。
Proxy.newProxyInstance:
参数:

  • loader - 定义代理类的类加载器
  • interfaces - 代理类要实现的接口列表
  • h - 指派方法调用的调用处理程序(每个代理实例都具有一个关联的调用处理程序,调用代理实例的方法时,将对方法的调用指派到它的调用处理程序的 invoke 方法)

返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定类加载器定义,并实现指定的接口。

package Proxy;
//Created by zhengbinMac on 2017/2/19.
public class Test {
    public static void main(String[] args) {
        DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl());
        Hello hello = dynamicProxy.getProxy();
        hello.sayHello("JDK");
    }
}

再举例:还是接着上面的例子
1.)首先我们要声明一个动态代理类,实现InvocationHandler接口

public class DynamicProxy implements InvocationHandler {

    // 被代理类的实例
    Object obj;

    // 将被代理者的实例传进动态代理类的构造函数中
    public DynamicProxy(Object obj) {
        this.obj = obj;
    }

    /**
     * 覆盖InvocationHandler接口中的invoke()方法
     * 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构
     * 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
     * 控制被代理对象的行为,下面的before、after就是我们可以进行特殊
     * 代码切入的扩展点了。
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        /*
         * before :doSomething();
         */
        Object result = method.invoke(this.obj, args);

        /*
         * after : doSomething();
         */
        return result;
    }
}

2.)具体实现

//我们要代理的真实对象
        Customer customer = new Customer();
        //我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(customer);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数customer.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        IBuyCar buyCar = (IBuyCar) Proxy.newProxyInstance(handler.getClass().getClassLoader(), customer.getClass().getInterfaces(), handler);
        buyCar.buyCar();

动态态代理相比静态代理,接口变了,动态代理类不需要改变,而静态代理类不仅需要改变实现类,代理类也需要修改。

但如果要代理一个没有接口的类,JDK 动态代理就用不上了,这就引出了 CGLib 代理。

3.2、 CGLib代理——方法级别代理

Spring、Hibernate 框架都是用了它,当然,spring两个都用到了 (a) 用到了JDK的 接口动态代理 (b) 也用到了 CGLib代理,它是一个在运行期间动态生成字节码的工具,也就是动态生成代理类。

package Proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//Created by zhengbinMac on 2017/2/19.
public class CGLibProxy implements MethodInterceptor {
    // 单例模式
    private static CGLibProxy instance = new CGLibProxy();
    private CGLibProxy() {}
    public static CGLibProxy getInstance () { return instance; }
    public <T> T getProxy(Class<T> cls) {
    	//设置要代理的类
        return (T) Enhancer.create(cls, this);
    }
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        sayBefore();
        Object result = methodProxy.invokeSuper(obj, objects);
        sayAfter();
        return result;
    }
    private void sayBefore() { System.out.println("before..."); }
    private void sayAfter() { System.out.println("after..."); }
}

测试类

package Proxy;
//Created by zhengbinMac on 2017/2/19.
public class Test {
    public static void main(String[] args) {
    	//CGlib要代理HelloImpl类
        Hello helloCGLib = CGLibProxy.getInstance().getProxy(HelloImpl.class);
        helloCGLib.sayHello("CGLib");
    }
}

3.)动态代理好处
使用Java动态代理机制的好处:

1、减少编程的工作量:假如需要实现多种代理处理逻辑,只要写多个代理处理器就可以了,无需每种方式都写一个代理类。

2、系统扩展性和维护性增强,程序修改起来也方便多了(一般只要改代理处理器类就行了)。

代理模式的好处:

  • 职责清晰真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
  • 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
  • 高扩展性
    【设计模式】一文学透代理模式

总结

Spring提供的实现动态代理有两种方式,一个是被代理对象需要实现JDK提供的动态代理接口。通过cglib的jar包实现动态代理,该方法只需要对目标对象继承即可。
spring支持两种方法,那么我们在使用spring进行动态代理时究竟使用的哪一种方法呢?spring优先支持实现接口的方式,如果没有接口则使用cglib方式。

原文地址:https://www.cnblogs.com/aspirant/p/7081738.html

上一篇:Java基础之Object 的方法重写与toString的方法重写


下一篇:java序列化和反序列化