简单来说,代理模式就是通过访问代理对象替代对目标对象的访问,以实现在不修改目标对象的前提下,可以提供额外的功能或扩展目标对象的功能
举个简单例子,如果目标对象有两个方法:
public class Target {
// 获取 x 的值
public int getX() {
return 1;
}
// 获取 y 的值
public int getY() {
return 2;
}
}
如果此时客户端想要:获取 x 的值加 1、获取 y 的值加 2、获取 x + y 的值,那么在不修改目标对象的前提下可以提供一个代理对象,它包含三个方法:
xxxxxxxxxx
public class Proxy {
private Target target = new Target();
// 获取 x 的值加 1
public int getXPlus() {
return target.getX() + 1;
}
// 获取 x 的值加 2
public int getYPlus() {
return target.getY() + 2;
}
// 获取 x + y 的值
public int sum() {
return target.getX() + target.getY();
}
}
对于Proxy
类中的getXPlus()
和getYPlus()
方法,相当于扩展了目标对象的功能;对于Proxy
类中的sum()
方法,相当于提供了额外的功能
用大白话来说,客户端和目标对象通信,实际上是通过代理对象作为中间方来实现,至于中间方传话的过程中有无添油加醋就由代理类的实现来决定了,如下图所示:
从上面的介绍中可以总结出代理模式中有三个主要角色:
Real Subject:真实类,也被称为被代理类、委托类,用来真正完成业务服务功能
Proxy:代理类,通过调用 Real Subject 相关方法实现对应功能,并不真正的实现业务,但可以提供额外功能或扩展现有功能
Subject:定义 Real Subject 和 Proxy 角色都应该实现的接口
三个角色的关系如下图所示:
注意:对于代理对象,更多的是增强目标对象的功能,也就是在调用目标对象方法前后添加个性化处理,而并非改变目标对象的功能
本部分用大白话介绍一下代理模式在 Spring 中的应用,因为这玩意很重要,但又很抽象,如果一开始对「代理」的定位不明确,很有可能会影响后面对动态代理的理解
在 Spring 中,很多地方都用到了代理模式,更准确来说是动态代理,但这里先不区分到底用到了哪种代理,仅仅介绍代理模式的思想如何在 Spring 中体现,用 Spring 中的「拦截器」例子展开讨论
在 Spring 中,访问一个 URL 首先会进入 Controller 层对应的方法中,如果要对某个网页进行拦截,一般会在拦截 Controller 层的方法
举个简单的应用场景,如果用户在没有登陆的情况下,访问了某个需要登录后才能访问的页面,会跳转到登陆界面
假设登陆页面为/login
,对应的 Controller 层方法为login()
;需要登录后才能访问的页面为/test
,对应的 Controller 层方法为test()
当用户访问/test
页面时,实际并不是直接调用 Controller 对象的test()
方法,而是调用 Controller 对象的代理对象的对应方法
在代理对象中,会在调用test()
方法之前判断用户是否合法,如果未登录,直接跳转到/login
页面;如果已登录,才会调用test()
方法,具体如下图所示:
拦截器在代理层实现,它已内置到框架中,程序员只需要简单的配置即可实现拦截的功能
我们在使用 Spring 框架时,它已经被编译打包好,所以对于代理层来说,应用程序属于黑盒,Spring 打包时完全不知道它的存在
从 JVM 角度来说,类会先经过编译生成.class
字节码文件,然后通过类加载子系统加载到内存中,并生成对应 Class 对象
静态代理就是在编译时将接口、真实类、代理类生成为一个个实际的.class
字节码文件,而且需要为每一个真实类创建一个对应的代理类,一旦接口有修改,真实类和代理类都需要修改,麻烦且不灵活
代理类由 Spring 框架生成,而真实类由用户创建,在 Spring 打包时完全不知道用户程序的存在,所以静态代理实际应用场景非常少,日常开发过程中几乎看不到使用它的场景
静态代理实现步骤:
定义一个接口
创建一个真实类和代理类,并实现第一步中的接口
将目标对象注入代理类中,并在代理类相应的方法中调用目标对象的方法实现业务功能,这样可以通过代理类屏蔽对目标对象的访问,同时也可以目标方法执行前后做一些个性化处理
上述步骤如下图所示:
下面给出一个发送短信的 Demo!!
一:定义发送短信的接口
xxxxxxxxxx
public interface SmsService {
String send(String message);
}
二:实现发送短信的接口,充当真实类
xxxxxxxxxx
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message: " + message);
return message;
}
}
三:创建代理类并实现发送短信的接口
xpublic class SmsProxy implements SmsService {
// 注入目标对象
private final SmsService smsService;
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
public String send(String message) {
System.out.println("前置处理");
smsService.send(message);
System.out.println("后置处理");
return null;
}
}
四:测试
xxxxxxxxxx
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("Hello world");
}
}
五:输出
xxxxxxxxxx
前置处理
send message: Hello world
后置处理
动态代理不再需要为每一个目标类创建一个对应的代理类,而且也并没有强制要求代理类必须实现接口,更加的灵活。从 JVM 角度来说,动态代理是在运行时动态的生成所需的类字节码,并加载到 JVM 中
在 Spring 打包时并不知道应用程序的存在,只有在程序运行过程中,Spring 才能感知到应用程序,所以动态代理可以让 Spring 在运行时生成应用程序的代理类
就 Java 来说,动态代理实现的方式有很多种,如:JDK 动态代理、CGLIB 动态代理等,下面主要介绍这两种实现方式
JDK 动态代理和静态代理主要有两点不同:
静态代理中代理类需要实现接口;而 JDK 动态代理中代理类无须实现接口
静态代理中直接由代理对象调用目标对象的方法;而 JDK 动态代理中新增 InvocationHandler 角色,由它负责调用目标对象的方法,而代理类负责调用 InvocationHandler 的方法
JDK 动态代理结构如下图所示:
下面介绍 JDK 动态代理中用到的Proxy
类和InvocationHandler
接口
xxxxxxxxxx
// 创建一个接口的代理对象,其本质是一个披着接口外套的 InvocationHandler 代理对象
// loader 表示加载接口的类加载器;interfaces 表示接口;h 表示实现 InvocationHandler 的对象
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
public interface InvocationHandler {
// 动态代理对象调用方法时,实际会调用此处的 invoke 方法
// proxy 表示动态代理对象;method 表示动态代理对象调用的方法;args 表示动态代理对象调用方法的参数
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
同样的,给出一个小 Demo~
x
// SmsService.java 和 SmsServiceImpl.java 同上
// 代理类工厂,调用 getProxy() 可以获取动态代理对象
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new DebugInvocationHandler(target)
);
}
}
// InvocationHandler 的实现类
public class DebugInvocationHandler implements InvocationHandler {
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before method " + method.getName()); // 前置处理
method.invoke(target, args); // 调用目标对象方法
System.out.println("after method " + method.getName()); // 后置处理
return null;
}
}
// 测试
public class Main {
public static void main(String[] args) {
// smsService 是一个披着接口外套的 InvocationHandler 代理对象
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
// 实际调用 InvocationHandler 中的 invoke 方法
smsService.send("Hello World");
}
}
// 输出
before method send
send message: Hello World
after method send
JDK 动态代理最致命的问题是只能代理实现了接口的类,也就是必须要有接口。为了解决这个问题,可以使用 CGLIB 动态代理机制,它可以代理没有实现接口的类。除了该区别外,感觉 GCLIB 和 JDK 很像
下面直接给出一个 Demo。首先 GCLIB 是一个开源项目,需要导包:
x
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
然后给出其它代码:
x
// 真实类
public class AliSmsService {
public String send(String message) {
System.out.println("send message: " + message);
return message;
}
}
// 代理类工厂,调用 getProxy() 可以获取动态代理对象
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
Enhancer enhancer = new Enhancer(); // 创建动态代理增强类
enhancer.setClassLoader(clazz.getClassLoader()); // 设置类加载器
enhancer.setSuperclass(clazz); // 设置被代理类
enhancer.setCallback(new DebugMethodInterceptor()); // 设置方法拦截器
return enhancer.create(); // 创建代理类
}
}
// MethodInterceptor 的实现类
public class DebugMethodInterceptor implements MethodInterceptor {
// o 表示被代理对象;method 表示拦截方法;args 表示方法参数;methodProxy 用于调用原方法
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
System.out.println("after method " + method.getName());
return object;
}
}
// 测试
public class Main {
public static void main(String[] args) {
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("Hello World");
}
}
// 输出
before method send
send message: Hello World
after method send
JDK 动态代理只能代理实现接口的类;CGLIB 动态代理可以代理没有实现接口的类
大部分情况下,JDK 动态代理的效率更加优秀,随着 JDK 版本的升级,这个优势更加明显
在静态代理中,代理类必须和目标类一样,实现所有接口,而且每个目标类都需要对应有一个代理类;在动态代理中,代理类不需要实现接口,而且不需要为每个目标类都创建一个代理类
静态代理是在编译时将所有接口和类生成一个个对应的.class
文件;动态代理在运行时动态生成字节码,并记载到 JVM 中