三大代理模式
代理模式
代理(Proxy)是设计模式中的其中一种。即通过代理对象访问目标对象。
优势
可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
框架中涉及到代理模式的地方
我们常用的 mybatis ,都是定义接口但是不需要书写实现类,就可以对 XML 或者 自定义的 sql 进行 curd 操作等。
一定要看完文章末尾会有关于 这部分 源码 显示。 如果没看懂前文或者不熟悉代理模式。直接看后文 源码 头一定会很疼。
一定要 仔细看,仔细看,仔细看。 重要的事情要说 3 遍。
列子
假设我们现在想邀请一位明星,众所周知我们并不是直接联系到明星,而是联系到明星的经纪人,来达到同样的目的。 切换到代理中也就是:明星就是一个 目标 对象,他只需要负责活动中的演出。其余的繁琐的事情就交由 代理(经纪人)来解决。
用图表示
java 三种实现方式
- 静态代理
- 动态代理
- CGLIB代理
静态代理
- 创建演出对象
@Data
@Builder
public class Sign {
private String name;
private String program;
private LocalDateTime signTime;
}
- 定义开幕式演出接口
public interface OpeningCeremony {
// 定义开幕式演出接口
void commercialShow(Sign sign);
}
public class OpeningCeremonyImpl implements OpeningCeremony{
@Override
public void commercialShow(Sign sign) {
System.out.println("由" + sign.getName() +"于北京时间"+ sign.getSignTime() +"演出"+ sign.getProgram());
}
}
- 创建代理
@Slf4j
public class StaticProxy implements OpeningCeremony{
// 目标类
private OpeningCeremony openingCeremony;
public StaticProxy(OpeningCeremony openingCeremony) {
this.openingCeremony = openingCeremony;
}
@Override
public void commercialShow(Sign sign) {
log.info("经纪人告知" + sign.getName() + "本次活动");
log.info("预付款给经纪人");
// 执行目标方法
openingCeremony.commercialShow(sign);
log.info("付尾款给经纪人");
}
}
- Test
public class StaticProxyTest {
public static void main(String[] args) {
Sign sign = Sign.builder()
.name("薛之谦")
.signTime(LocalDateTime.now())
.program("你过得好吗")
.build();
// 创建代理对象
StaticProxy staticProxy = new StaticProxy(new OpeningCeremonyImpl());
staticProxy.commercialShow(sign);
}
}
- 输出
经纪人告知薛之谦本次活动
预付款给经纪人
由薛之谦于北京时间2021-07-15T15:26:28.784演出你过得好吗
付尾款给经纪人
静态代理总结
-
如代码所示在不改动 目标对象 的前提下,对目标对象进行了扩展。
-
缺点:
假设现在需求上需要改变一下明星,需要新增一个接口。那么现有的 StaticProxy 代理对象就无法为 改变明星 的目标对象进行代理。
-
静态代理需要和目标对象实现同一个接口。接口的变动会导致维护 StaticProxy 和 目标对象实现 。
2. 会因为接口的增加导致代理对象持续增长。
-
解决方案
由下文的 动态代理 来解决这个缺点。
动态代理
介绍 动态代理 前,先介绍两个重要的 接口 和 **类 ** InvocationHandler Proxy
Proxy 类
Proxy 类就是用来创建一个代理对象的类, 里面提供了很多方法(有兴趣的同学可以打开这个类去了解) 这里只介绍我们要用到的一个方法,也是经常用的一个。newProxyInstance 静态方法
/** * @param loader : 定义代理类的类加载器 * @param interfaces : 代理类实现的接口列表 即目标对象的接口列表 * @param h : 将方法调用分派到代理实例的调用处理程序 即调用该接口中的 invoke 方法(如果未理解,没关系下文还会提到) **/ public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
InvocationHandler 接口
proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用会被分派到调用处理程序的invoke方法。
该接口中只定义了一个方法 public Object invoke(Object proxy, Method method, Object[] args)
下面就用代码介绍如何使用 InvocationHandler 和 Proxy 。
- 创建动态代理对象
@Slf4j
public class DynamicProxy {
// 目标对象
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
public Object getProxyInstance() {
// 相当于实现 InvocationHandler 中的 invoke 方法
InvocationHandler handler = (proxy, method, args) -> {
Sign sign = (Sign)args[0];
log.info("经纪人告知" + sign.getName() + "本次活动");
log.info("预付款给经纪人");
// 执行目标方法
method.invoke(target, args);
log.info("付尾款给经纪人");
return null;
};
// 返回代理对象
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), handler);
}
}
- Test
public class DynamicProxyTest {
public static void main(String[] args) {
Sign sign = Sign.builder()
.name("薛之谦")
.signTime(LocalDateTime.now())
.program("你过得好吗")
.build();
// 创建代理对象
OpeningCeremony openingCeremony = (OpeningCeremony)new DynamicProxy(new OpeningCeremonyImpl()).getProxyInstance();
openingCeremony.commercialShow(sign);
}
- 输出
经纪人告知薛之谦本次活动
预付款给经纪人
由薛之谦于北京时间2021-07-15T15:26:28.784演出你过得好吗
付尾款给经纪人
添加扩展
在 静态代理 中我们提到过如果因为需要的变动需要 改变明星 静态代理会再次建立一个 代理类 实现 目标接口。 下面就用代码演示下 动态代理 如何解决掉这个缺点。
- 添加改变明星接口
public interface ChangeService {
void changeSign(Sign sign);
}
public class ChangeServiceImpl implements ChangeService{
@Override
public void changeSign(Sign sign) {
System.out.println("临时变更由" + sign.getName() +"于北京时间"+ sign.getSignTime() +"演出"+ sign.getProgram());
}
}
- Test
public class StaticProxyTest {
public static void main(String[] args) {
Sign sign = Sign.builder()
.name("毛不易")
.signTime(LocalDateTime.now())
.program("像我这样的人")
.build();
// 创建代理对象
ChangeService openingCeremony = (ChangeService)new DynamicProxy(new ChangeServiceImpl()).getProxyInstance();
openingCeremony.changeSign(sign);
}
}
- 输出
经纪人告知毛不易本次活动
预付款给经纪人
临时变更由毛不易于北京时间2021-07-15T16:03:07.803演出像我这样的人
付尾款给经纪人
如上所示: 我们无需再为 改变明星 这个接口再次创建一个代理类。 只需要通过传参更换 DynamicProxy 中 target 目标对象即可实现扩展效果。
动态代理总结
代理对象 不需要实现接口,但是目标对象一定要实现接口。
CGLIB 代理
上面的 静态代理 和 动态代理 模式都要求 目标对象 是实现一个接口或者多个接口的 目标对象 。但有时候 目标对象 仅仅是一个类。这个时候可以用目标对象的子类 实现代理
Enhancer : 对象把代理对象设置为被代理类的子类来实现动态代理的。因为是采用继承方式,所以代理类不能加final修饰,否则会报错。
final类 :类不能被继承,内部的方法和变量都变成final类型
- 创建Cglib代理对象
@Slf4j
public class CglibProxyChange implements MethodInterceptor {
private Change change;
public Change getInstance(Change change) {
this.change = change;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.change.getClass());
enhancer.setCallback(this);
return (Change) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Sign sign = (Sign) objects[0];
log.info("经纪人告知" + sign.getName() + "本次活动");
log.info("预付款给经纪人");
// 执行目标方法
Object object = methodProxy.invokeSuper(o, objects);
log.info("付尾款给经纪人");
return object;
}
}
- Test
public static void main(String[] args) {
Sign sign = Sign.builder()
.name("毛不易")
.signTime(LocalDateTime.now())
.program("像我这样的人")
.build();
CglibProxyChange prayingMantis = new CglibProxyChange();
Change instance = prayingMantis.getInstance(new Change());
instance.changeSign(sign);
}
- 输出
经纪人告知毛不易本次活动
预付款给经纪人
临时变更由毛不易于北京时间2021-07-15T16:41:28.275演出像我这样的人
付尾款给经纪人
总结
看到了这里。那么我相信你一定是一个很上进。对代码有 敬畏感 的人。小编为给你感到开心,也为你 加油!!!
先上两个关于 mybatis-spring 集成中的部分源码图,只需要 关注我框住的部分 即可。其余的先 不必关心。
看到这个是否感觉到一股 熟悉 的味道了, 是的,没错正是我们前文所提到的 动态代理 模式。
这里我们就只是简单的聊一下,考虑到写的太多,消化不掉(毕竟一口吃不成大胖子)
关于
Spring-mybatis的实现我们得从MapperScannerConfigurer说起, 首先MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。而
BeanDefinitionRegistryPostProcessor依赖于Spring框架,通俗的讲BeanDefinitionRegistryPostProcessor使得我们可以将BeanDefinition添加到BeanDefinitionRegistry中,而BeanDefinition描述了一个Bean实例所拥有的实例、结构参数和参数值,简单点说拥有它就可以实例化Bean了。BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法在Bean被定义但还没被创建的时候执行,所以Spring-mybatis也是借助了这一点。将扫描包路径的Bean注册到了registry
mapperFactoryBeanClass引用 便是MapperFactoryBean的子类 在checkDaoConfig方法中会调用addMapper而addMapper中便会创建一个MapperProxyFactory也是最开始的第一张图。是不是又回到了熟悉的味道。
这里就先大致介绍这些吧。 这里没对
spring-mybatis做详情介绍。只是从宏观上面介绍了一下大致流程。 有兴趣得小伙伴可以点开这几个类去追溯一下。当然大家觉得小编写的不错,希望小编继续输出这部分集成的内容,可以下方留言。
代理模式在业务代码上是比较少见的,但是代理模式也是我们必须要理解的一种模式,因为学习好代理模式有助于我们去读一些源码。 本期文章就到这里结束了。欢迎各位伙伴将自己的看法评论在下面。