前言
代理模式是指为其他对象提供一种代理,以控制对这个对象的访问
特点:代理对象在客户端和目标对象之间起到中介的作用
代理模式(英语: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
。方法中三个参数分别表示:代理对象、要执行的方法、方法参数 - 由于动态代理所代理的类是不确定的,所以
target
为Object
类型 - 代理对象的产生是通过
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动态代理是靠反射来实现的,核心类就是InvocationHandler
和Proxy
。
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
。方法中四个参数已经分别在代码中注明 - 由于动态代理所代理的类是不确定的,所以
target
为Object
类型 - 代理对象的产生是通过
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动态代理是靠字节码增强技术来实现的,核心类就是MethodInterceptor
和Enhancer
。
JDK动态代理和Cglib动态代理
- 当
Bean
有实现接口时,Spring就会使用JDK实现动态代理 - 当
Bean
没有实现接口时,Spring使用Cglib实现动态代理 - 可以强制使用Cglib,在Spring配置中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>
代理模式优点
- 代理模式能将代理对象与真实被调用的目标对象分离
- 一定程度上降低了系统的耦合度,扩展性好
- 保护目标对象
- 增强目标对象
代理模式缺点
- 代理模式会造成系统设计中类数目的增加
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
- 增加系统复杂度
源代码
总结
代理模式是一种非常重要的设计模式,Spring核心组件AOP,就是通过动态代理来实现的。所以学好了动态代理,就学好了一半的Spring。