1.前言
代理模式属于行为型模式,代理一词很好理解,在计算机世界中的代理其实就是在服务端和客户端之间增加一个中间层,通过正向代理或反向代理向访问者提供了一种间接的访问控制,也可以说通过代理层向访问者屏蔽了被访问目标对象的具体细节,访问者只需要面向代理对象或代理服务就可以间接的访问到目标对象,如果反映到现实世界中,那各种中介、商品代理商就充当了这个中间层的角色。
而通过增加代理对象,那自然就可以在代理的过程中去做一些额外的事情,对目标对象进行增强操作,比如在反向代理服务器中,对访问者的鉴别,拦截过滤等。
谈到增强,那装饰者模式也是对目标对象进行增强,两者区别?
首先代理模式第一强调的是访问控制,即通过代理对象实现对目标对象访问的控制,这个过程对于客户端访问者是透明的,再以反向代理为例,客户端面向代理服务器,客户端并不知道真实的服务地址,即代理服务器完成了对目标服务的访问控制,或者说目标服务对于客户端透明,VPN亦是如此。
在完成了对象的访问控制基础上,由于代理具备访问控制的特性,于是乎,才有了在访问控制过程中对目标对象增强的机会,但最核心的是访问控制。
而装饰器模式,则是单纯的对目标对象进行功能增强操作,目标对象对于客户端来说,并不是透明的,装饰器模式追求的是在原始目标对象基础上进行复用增强,原始对象和装饰后的对象是相互独立发展的,客户端只是根据需要选择是否要增强,如果是透明的,这与装饰器模式初衷目标相驳。
尽管两者实现上有些相似,但最终目标和应用场景是不同的。
贴图:
图片来源:百度图库
2.实现
上文中谈到代理模式是一种设计上的思想概念,而对代理模式思想的实现则是可以有多种多样的,目前主要分为静态代理,以及动态代理,而动态代理目前也有两种,第一种是jdk提供的代理方式,另外一种是第三方库提供的CGLib代理。
静态代理和动态代理
- 静态代理:在源代码中进行代理模式的实现,在编译期就已经确定,对于jvm来说class文件内容是固定的。
- 动态代理:动态代理在在编译期是未知的,是jvm在字节码层面根据我们的需要动态的生成代理类,然后在目标对象代码执行前后植入我们设定的增强操作。
动态代理又分为jdk代理和cglib代理
- jdk动态代理:利用拦截器原理通过反射机制生成一个匿名代理类,该类的特征是实现了与目标类相同的接口,根据我们指定的增强逻辑生成class,然后使用目标类的ClassLoader将字节码加载进jvm运行,故这种方式要求目标类需要实现一个接口
- cglib代理:利用ASM框架(这是个用来操作字节码的框架),将目标类的字节码加载进内存,然后通过修改目标类的字节码,生成一个目标类的子类,在子类中调用父类,在这个过程中添加进我们指定的增强逻辑,然后jvm加载,运行。
在原始jdk版本中,jdk代理性能不如cglib,随着jdk升级,整体上看jdk已经略强于cglib,在spring AOP中也是优先使用jdk做进行代理,只有代理目标类没有实现任何接口的时候,才会切换为cglib进行代理,当然您也可以任何时候都强制使用cglib
由于这里不是讨论jdk&cglib的具体操作原理,太细节的知识在此不过多赘述。
简单的介绍就到这里,下面贴一些简单的示例来说明静态代理和动态代理的基本实现方式,这里就以为用户登录注销操作添加日志为例
首先定义一组登录/注销接口以及实现
/**
* @description: 用户操作接口
* @version: 1.0
*/
public interface UserCenterService {
void login(String uName);
void logout(String uName);
}
具体实现:
/**
* @description: 用户操作实现
* @version: 1.0
*/
public class UserCenterServiceImpl implements UserCenterService{
@Override
public void login(String uName) {
System.out.println(uName + "用户认证登录操作");
}
@Override
public void logout(String uName) {
System.out.println(uName + "用户注销操作");
}
}
静态代理
静态代理相对比较简单,只需要按照设计的思想去编写就可以了,静态代理在编译期就已经确定的具体的代理类
/**
* @description: 静态代理
* @version: 1.0
*/
public class StaticProxy implements UserCenterService{
private UserCenterService userCenterService;
public StaticProxy(){
userCenterService = new UserCenterServiceImpl();
}
@Override
public void login(String uName) {
System.out.println("user login operating before....." + uName);
userCenterService.login(uName);
System.out.println("user logout operating after ....." + uName);
}
@Override
public void logout(String uName) {
System.out.println("user login operating before....." + uName);
userCenterService.logout(uName);
System.out.println("user logout operating after ....." + uName);
}
}
测试
/**
* @description: test
* @version: 1.0
*/
public class StaticProxyTest {
public static void main(String[] args) {
StaticProxy proxy = new StaticProxy();
proxy.login("王二");
System.out.println();
proxy.logout("王二");
}
}
input:
user login operating before.....王二
王二用户认证登录操作
user logout operating after .....王二
user login operating before.....王二
王二用户注销操作
user logout operating after .....王二
动态代理
动态代理是在jvm层面动态生成代理类,我们只需要按照动态代理规范去编写代理逻辑即可
首先是jdk动态代理,要求被代理目标类必须实现任意一个接口(UserCenterService ),然后增强逻辑类需要实现InvocationHandler接口,实现invoke方法,完成具体逻辑的增强。
/**
* @description: jdk动态代理
* @version: 1.0
*/
public class UserCenterJDKProxy implements InvocationHandler {
private Object target;
public UserCenterJDKProxy(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("user login operating before....." + Arrays.asList(args));
Object invoke = method.invoke(target, args);
System.out.println("user logout operating after ....." + Arrays.asList(args));
return invoke;
}
}
测试
/**
* @description: test
* @create by twotiger2tigersofast
* @datetime 2023/3/26 21:33
* @version: 1.0
*/
public class TestJDKProxy {
public static void main(String[] args) {
//原始目标对象
UserCenterService userService = new UserCenterServiceImpl();
//通过Proxy.newProxyInstance创建代理对象,
//并指定类加载器,目标类接口信息,以及要代理的逻辑
UserCenterJDKProxy proxy = new UserCenterJDKProxy(userService);
UserCenterService userServiceWithLog = (UserCenterService) Proxy
.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),proxy);
//通过代理对象调用
userServiceWithLog.login("张三");
System.out.println();
userServiceWithLog.logout("张三");
}
}
input:
user login operating before.....[张三]
张三用户认证登录操作
user logout operating after .....[张三]
user login operating before.....[张三]
张三用户注销操作
user logout operating after .....[张三]
接下来是cglib动态代理,由于cglib是第三方库,故需要导入对应的依赖才可以使用
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
/**
* @description: cglib动态代理
* @version: 1.0
*/
public class UserCenterCGLibProxy implements MethodInterceptor {
private Object target;
public UserCenterCGLibProxy(Object target){
this.target = target;
}
public Object getProxy(){
//创建增强工具类
Enhancer enhancer = new Enhancer();
//设置目标类为父类
enhancer.setSuperclass(target.getClass());
//设置回调函数接口 也就是intercept方法入口
enhancer.setCallback(this);
//创建子类代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("user login operating before ....." + Arrays.asList(objects));
//调用父类
Object ret= methodProxy.invokeSuper(o, objects);
System.out.println("user logout operating after ....." + Arrays.asList(objects));
return ret;
}
}
测试
/**
* @description: test
* @version: 1.0
*/
public class TestCGLibProxy {
public static void main(String[] args) {
//原始目标对象
UserCenterServiceImpl userService = new UserCenterServiceImpl();
//构建代理的逻辑
UserCenterCGLibProxy userCGLibProxy = new UserCenterCGLibProxy(userService);
//创建代理对象
UserCenterServiceImpl userServiceWithLog = (UserCenterServiceImpl) userCGLibProxy.getProxy();
//通过代理对象调用
userServiceWithLog.login("李四");
System.out.println();
userServiceWithLog.logout("李四");
}
}
input:
user login operating before .....[李四]
李四用户认证登录操作
user logout operating after .....[李四]
user login operating before .....[李四]
李四用户注销操作
user logout operating after .....[李四]
动态代理看上去似乎有一层雾,为什么这么写?规定的,想要在字节码层面动态生成代理类,就得这么写,如果对其中实现细节有兴趣,可以去看各路大神的源码剖析,后续有机会再贴源码剖析的文章
3.总结
代理模式可以说是使用非常广泛的一个设计模式,比如防火墙代理,远程调用,spring AOP,mybatis中的动态代理实现等等,实际应用需要实现访问控制、目标增强的场景中,都离不开代理模式的设计思想。