设计模式:代理模式

前言

代理模式是指为其他对象提供一种代理,以控制对这个对象的访问
特点:代理对象在客户端和目标对象之间起到中介的作用

代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式。
所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。

了解代理模式的同学都知道,根据代理类生成的方式,可以分为静态代理动态代理静态代理是指开发者手动为目标类创建一个代理类,动态代理是指根据一系列规则,由JDK或者第三方类库动态生成代理类。

静态代理

静态代理相对动态代理稍微简单一些,因为代理类完完全全由开发者来编写。假设一个租房场景,用静态代理来实现
首先有一个目标类就是房东

/**
 * 房东
 * 目标类
 * @author sicimike
 * @create 2020-02-26 19:35
 */
public class Landlord {

    public void signContract() {
        System.out.println("房东签合同...");
    }
}

房东只需要签合同就行了(核心服务),而其余非核心服务都由代理来完成。
代理类就是中介或者二房东

/**
 * 房东代理(中介、二房东)
 * 代理类
 * @author sicimike
 * @create 2020-02-26 19:37
 */
public class LandlordProxy {

    private Landlord landlord = new Landlord();

    public void rentHouse() {
        System.out.println("草拟合同...");
        landlord.signContract();
        System.out.println("查水表...");
    }
}

代理类的作用就是把房子租出去,包括草拟合同,查水表等等,核心服务签合同还是由房东来完成。但是中介不会让你见到房东,也就是代理类实现了对目标类的访问控制。

测试代码

/**
 * @author sicimike
 * @create 2020-02-26 19:40
 */
public class StaticProxyDemo {

    public static void main(String[] args) {
        LandlordProxy landlordProxy = new LandlordProxy();
        landlordProxy.rentHouse();
    }
}

执行结果

草拟合同...
房东签合同...
查水表...

这样租户在不接触房东的情况下,通过代理实现了租房的需求。

静态代理就是开发者需要为每个目标类创建一个代理类。这样的话导致工作量剧增,并且类的数量过多,不易维护,于是便产生了动态代理

动态代理

动态代理是指程序在运行的过程中,根据一系列的规则,为目标类动态生成代理类。根据其实现方式不同,动态代理可以分成JDK动态代理Cglib动态代理

JDK动态代理

JDK是通过反射来创建代理类的,相关的类和接口主要有两个InvocationHandler接口和Proxy类,先看实例
首先创建目标接口及其实现类,因为JDK动态代理是针对接口的,所以目标类(被代理的类)必须实现接口

/**
 * 租赁服务
 * @author sicimike
 * @create 2020-02-26 20:08
 */
public interface RentService {

    public void signContract();
}
/**
 * @author sicimike
 * @create 2020-02-26 20:09
 */
public class RentServiceImpl implements RentService {
    @Override
    public void signContract() {
        System.out.println("签署合同....");
    }
}

再创建实现动态代理的关键类,用来采集日志

/**
 * 日志处理器
 * @author sicimike
 * @create 2020-02-26 20:04
 */
public class LogHandlerProxy implements InvocationHandler {

    // 目标对象
    private Object target;

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

    public Object bind() {
        Class aClass = target.getClass();
        return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        befor();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    public void befor() {
        System.out.println("日志采集...");
    }

    public void after() {
        System.out.println("执行成功...");
    }
}

需要关注的几点是:

  • 动态代理的实现需要实现InvocationHandler接口,接口中只有一个方法invoke。方法中三个参数分别表示:代理对象、要执行的方法、方法参数
  • 由于动态代理所代理的类是不确定的,所以targetObject类型
  • 代理对象的产生是通过Proxy#newProxyInstance方法,三个参数分别表示:加载目标类的类加载器、目标类中所有接口、实现了InvocationHandler接口的类的实例

测试代码

/**
 * @author sicimike
 * @create 2020-02-26 20:10
 */
public class JdkDynamicProxyDemo {

    public static void main(String[] args) {
        // RentService必须是接口
        RentService proxy = (RentService) new LogHandlerProxy(new RentServiceImpl()).bind();
        proxy.signContract();
    }
}

执行结果

日志采集...
签署合同....
执行成功...

所以JDK动态代理是靠反射来实现的,核心类就是InvocationHandlerProxy

Cglib动态代理

Cglib是利用ASM(字节码增强)技术来实现动态代理,也就是直接修改字节码。根据目标类生成一个子类作为代理类,所以目标类以及被代理的方法均不能被final修饰
使用Cglib需要引入相关的jar包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.4</version>
</dependency>

Cglib方式实现动态代理主要涉及到一个接口net.sf.cglib.proxy.MethodInterceptor和一个类net.sf.cglib.proxy.Enhancer。先来看个实例
首先创建目标类

/**
 * 被代理的类和方法均不能被final修饰
 * @author sicimike
 * @create 2020-02-26 21:13
 */
public class RentServiceImpl {

    public void signContract() {
        System.out.println("签署合同....");
    }
}

再创建实现动态代理的关键类,用来采集日志

/**
 * 日志处理器
 * @author sicimike
 * @create 2020-02-26 21:14
 */
public class LogHandlerProxy implements MethodInterceptor {

    // 目标对象
    private Object target;

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

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

    /**
     * @param object 表示要进行增强的对象
     * @param method 表示拦截的方法
     * @param args 数组表示参数列表,基本数据类型需要传入其包装类型
     * @param methodProxy 用于调用父类方法
     */
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    public void before() {
        System.out.println("日志采集...");
    }

    public void after() {
        System.out.println("执行成功...");
    }
}

需要关注的几点是:

  • 动态代理的实现需要实现MethodInterceptor接口,接口中只有一个方法intercept。方法中四个参数已经分别在代码中注明
  • 由于动态代理所代理的类是不确定的,所以targetObject类型
  • 代理对象的产生是通过Enhancer#create方法

测试代码

/**
 * @author sicimike
 * @create 2020-02-26 21:19
 */
public class CglibDynamicProxyDemo {

    public static void main(String[] args) {
        RentServiceImpl proxy = (RentServiceImpl) new LogHandlerProxy(new RentServiceImpl()).bind();
        proxy.signContract();
    }
}

执行结果

日志采集...
签署合同....
执行成功...

所以Cglib动态代理是靠字节码增强技术来实现的,核心类就是MethodInterceptorEnhancer

JDK动态代理和Cglib动态代理

  • Bean有实现接口时,Spring就会使用JDK实现动态代理
  • Bean没有实现接口时,Spring使用Cglib实现动态代理
  • 可以强制使用Cglib,在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

代理模式优点

  • 代理模式能将代理对象与真实被调用的目标对象分离
  • 一定程度上降低了系统的耦合度,扩展性好
  • 保护目标对象
  • 增强目标对象

代理模式缺点

  • 代理模式会造成系统设计中类数目的增加
  • 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
  • 增加系统复杂度

源代码

Proxy

总结

代理模式是一种非常重要的设计模式,Spring核心组件AOP,就是通过动态代理来实现的。所以学好了动态代理,就学好了一半的Spring。
张学友

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值