代理模式(Java代码举例)

1. 什么是代理模式

简单的来说,代理模式可以用这样一句话总结:

为其他对象(暂且称之为A)提供一种代理,通过代理对象,就可以控制对A的访问

那么,怎么理解这句话呢?代理的作用就好比是我们日常生活中的中介。举个栗子,假设现在有一个房东,他有一间房子想要出租,那么对于这个房东来说,他的“核心业务”就是出租房子,而其它的诸如看房、签合同等业务(暂且称之为关注点吧),他其实并不想管。在房屋中介出现之前,房客和房东之间是直接关联的

在这里插入图片描述
而当房屋中介出现之后,房东想要出租房子,只需要将其交由中介“代理”即可,这样他就可以只关注与出租房子这一“核心业务”,其它的关注点(看房、合同等)都归中介负责。现在,想要租房的房客,就可以通过中介,“访问”到房东的房子了:
在这里插入图片描述
至此,我们再来理解一下文章一开始对代理模式的简单定义:为其他对象(暂且称之为A)提供一种代理,通过代理对象,就可以控制对A的访问。

在上述的栗子中,房东就相当于被代理的对象(对象A),房屋中介就相当于代理,他控制着被代理对象(房东)的引用。原本房客租房是直接找的房东(没有代理的时候,直接引用原实现类)租房,而有了中介之后,房客就可以通过中介完成租房了(有了代理,原本直接引用实现类的地方,变为引用代理类)。

2. 代理模式的优点

在了解了什么是代理模式之后,一个新的问题又出现了:我为什么我们要引入代理模式呢?

在程序中引入代理模式之后,会有一系列的优点:

  • 使用代理模式,可以将安全、事务和日志等关注点与核心业务逻辑相分离,让业务代码可以专注于核心业务的实现;
  • 代理模式进一步降低了各模块之间的耦合;
  • 同时,代理模式可以把遍布应用各处的关注点(如日志)分离出来形成可重用的组件,并对其进行更好的管理。

在我们的程序中,系统一般由许多不同的组件组成,每一个组件各负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理和安全这样的系统服务经常融入到自身具有核心
业务逻辑的组件中去
,这些系统服务通常被称为横切关注点,因为它们会跨越系统的多个组件。

如果这些关注点被分散到多个组件中,就会引入额外的复杂性

  • 如果你要改变这些关注点的逻辑,必须修改各个模块中的相关实现。
  • 组件会因为那些与自身核心业务无关的代码而变得混乱。

通过代理模式,就可以很好的解决这些问题。代理模式将这些横切关注点从各个组件中分离出来,放到代理类中统一进行管理。 这样的话,各个组件就可以专注于自己的核心业务,其代码也会变得更简洁;如果需要改变这些关注点的逻辑,也只需要在代理类中稍作修改即可,无需修改各个模块的代码。

3. 代理模式的实现

了解了代理模式的优点之后,我们继续聚焦到其具体代码实现上。继续上面的房东与房客的栗子,其具体代码实现应该是怎么样的呢?

公共代码(租房接口):

/**
 * 接口Rent,定义了一个出租房子的方法:rent()
 */
public interface Rent {
    void rent();
}

3.1 不使用代理

/**
 * 房东类Host,出租房子的具体方法
 */
public class Host implements Rent {

    /*
     * 模拟没有中介的情况,租房的整体流程为:先看房、然后确认租房
     * 假设核心业务为出租房子,此处关注点(看房)被耦合到了业务代码中
     */
    @Override
    public void rent() {
        System.out.println("房客来看房。");
        System.out.println("房东出租房子。");
    }
}
/**
 * 测试类
 */
public class Test {

    public static void main(String[] args) {
        Rent host = new Host();
        host.rent();
    }
}

3.2 静态代理

静态代理通过一个静态代理类实现,它已经具备了前面提到的代理的优点。但美中不足的是,我们需要为每一个服务创建一个代理类,工作量比较大。同时,如果接口发展改变,代理类也得做出相应的修改。

public class Host implements Rent {

    // 静态代理中,房东已经可以专注于其核心业务(出租房子)
    @Override
    public void rent() {
        System.out.println("房东出租房子。");
    }
}
/**
 * 使用静态代理类,控制对原对象的引用(房东)
 */
public class StaticProxy {
    private Rent rent; // 原对象(房东)的引用

    public StaticProxy(Rent rent) {
        this.rent = rent;
    }

    // 由代理控制对原对象的引用,实现租房
    public void rent() {
        seeHouse();
        rent.rent();
    }

    // 关注点(看房)交由代理类管理
    public void seeHouse() {
        System.out.println("中介带房客看房子。");
    }
}
public class Test {

    public static void main(String[] args) {
        Rent host = new Host();
        StaticProxy proxy = new StaticProxy(host);
        proxy.rent();
    }
}

3.3 动态代理

虽然使用静态代理已经可以实现代理的功能,但正如前面所提到的,静态代理还有许多的局限性。使用动态代理,可以较好的解决这些问题。

Java的动态代理,需要借助反射实现。

public class Host implements Rent {

    // 与静态代理类似,房东只需要专注于出租房子这一核心业务
    @Override
    public void rent() {
        System.out.println("房东出租房子。");
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 代理实例的调用处理程序,需要实现java.lang.reflect.InvocationHandler接口
 */
public class ProxyInvocationHandler implements InvocationHandler {

    private Object target; // 被代理的对象

    public void setTarget(Object target) {
        this.target = target;
    }

    /**
     * getProxy方法返回一个动态代理实例,每个代理实例都有一个关联的调用处理程序。
     * 在这里就是指ProxyInvocationHandler的invoke方法。
     * @return 返回动态代理实例,与前面的静态代理类StaticProxy的实例类似,只不过这里是动态生成的。
     */
    public Object getProxy() {
        /*
        * 通过Proxy.newProxyInstance方法可以得到动态代理实例,此方法需要三个参数:
        * 第一个参数:ClassLoader 类型,需要传入一个类加载器
        * 第二个参数:Class<?>[] 类型,被代理的接口类型,Java的动态代理是基于接口的
        * 第三个参数:InvocationHandler 类型,与代理类关联的调用处理程序
        */
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    /**
     * 与代理实例相关联的调用处理程序,即调用了代理实例需要代理的业务。
     * 官方文档解释为:
     * Processes a method invocation on a proxy instance and returns the result.
     * @param proxy method的代理实例
     * @param method 在代理实例上调用被代理的接口方法对应的实例
     * @param args 参数
     * @return 代理实例上方法调用的返回值
     * @throws Throwable 代理实例上的方法调用引发的异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse(); // 关注点(看房)交由代理类管理
        // 调用代理的业务
        Object result = method.invoke(target, args);
        return result;
    }

    public void seeHouse() {
        System.out.println("中介带房客看房子。");
    }
}

public class Test {

    public static void main(String[] args) {
        Rent host = new Host();
        // 代理实例的调用处理程序的实例
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(host); // 传入被代理的对象
        // Java的动态代理是基于接口实现的,因此需要强转为接口(如果强转为Host类型会报错)
        Rent proxy = (Rent) pih.getProxy(); // 获得代理实例
        proxy.rent(); // 通过代理实例租房
    }
}

动态代理的实现显然已经比较完美,但它其实也还存在着一些问题:

  • Java的继承机制注定了其无法实现对class的动态代理,即它仅支持interface代理。

3.4 CGLIB代理

由于JDK提供的动态代理仅支持对接口的代理,那对于没有接口的类,改如何实现动态代理呢?这时候我们可以使用CGLib。

CGLib的原理是通过字节码技术为一个类创建子类,并在子类中利用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。 但因为采用的是继承,所以不能对final修饰的类进行代理。

JDK动态代理与CGLib动态代理均是实现Spring AOP(面向切面编程)的基础。

// 此时,Host类已经可以不实现Rent接口了(即不是必须实现接口)
public class Host {

    @Override
    public void rent() {
        System.out.println("房东出租房子。");
    }
}
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CGLIB代理
 */
public class ProxyMethodInterceptor implements MethodInterceptor {
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object object, Method method, Object[] args, 
        MethodProxy methodProxy) throws Throwable {
        
        seeHouse();
        Object result = methodProxy.invokeSuper(object, args);
        return result;
    }

    public void seeHouse() {
        System.out.println("中介带房客看房子。");
    }
}
public class Test {

    public static void main(String[] args) {
        Host host = new Host();
        ProxyMethodInterceptor pmi = new ProxyMethodInterceptor();
        pmi.setTarget(host);
        Host proxy = (Host) pmi.getProxy();
        proxy.rent();
    }
}
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值