代理模式介绍

代理模式就是为了方便访问某些资源,使对象类更加易用,从而在操作上使用的代理服务。

代理模式经常会出现在系统或组件中,他们提供一种非常简单易用的方式,控制原本需要编写很多代码才能实现的服务类,类似一下场景:

  • 在数据库访问层面会提供一个比较基础的应用,避免在对应服务扩容时造成数据库链接数暴增
  • 使用过的一些中间件,例如RPC框架,在拿到jar包对接口的描述后,中间件会在服务启动时生成对应的代理类。当调用接口时,实际是通过代理类发出的Socker信息。
  • 常用的Mybatis基本功能是定义接口,不需要写实现类就可以对XML或自定义注解里的SQL语句增删查改。

MyBatis-Spring中代理类场景

这次的样例是模拟实现MyBatis-Spring中代理类生成部分。我们在使用MyBatis时,只需要定义接口,而不需要写实现类就可以完成增删查改操作,本章会通过代理类交给Spring管理的过程介绍代理类模式。

代理类模式实现过程

先介绍一些用到的,但是可能不太了解的知识点:

  • BeanDefinitionRegistryPostProcessor: spring的接口类用于处理对bean的定义注册。
  • GenericBeanDefinition,定义bean的信息,在mybatis-spring中使用到的是;ScannedGenericBeanDefinition 略有不同。
  • FactoryBean,用于处理bean工厂的类,这个类非常见。

image-20211117215315722

自定义注解
1
2
3
4
5
6
7
8
9
10
11
12
package com.bestrookie.design.agent;
import java.lang.annotation.*;
/**
* @author bestrookie
* @date 2021/11/17 11:42 上午
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {
String value() default " ";
}

这里定义了一个模拟MyBatis中的自定义注解,用在方法层方面

Dao层接口
1
2
3
4
public interface IUserDao {
@Select("select userName from user where id = #{userId}")
String queryUserInfo(String userId);
}
代理类定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MapperFactoryBean <T> implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface){
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
InvocationHandler handler = (poxy,method,args)->{
Select select = method.getAnnotation(Select.class);
System.out.println("SQL:"+select.value().replace("#userId", args[0].toString()));
return args[0] + ",我真的好菜啊";
};
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface},handler);
}

@Override
public Class<?> getObjectType() {
return mapperInterface;
}

@Override
public boolean isSingleton() {
return true;
}
}
  • 如果你有阅读过mybatis源码,是可以看到这样的一个类;MapperFactoryBean,这里我们也模拟一个这样的类,在里面实现我们对代理类的定义。
  • 通过继承FactoryBean,提供bean对象,也就是方法;T getObject()
  • 在方法getObject()中提供类的代理以及模拟对sql语句的处理,这里包含了用户调用dao层方法时候的处理逻辑。
  • 还有最上面我们提供构造函数来透传需要被代理类,Class<T> mapperInterface,在mybatis中也是使用这样的方式进行透传。
  • 另外getObjectType()提供对象类型反馈,以及isSingleton()返回类是单例的。
将Bean定义注册到Spring容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MapperFactoryBean.class);
beanDefinition.setScope("singleton");
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(IUserDao.class);

BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

}
}
  • 这里我们将代理的bean交给spring容器管理,也就可以非常方便让我们可以获取到代理的bean。这部分是spring中关于一个bean注册过程的源码。
  • GenericBeanDefinition,用于定义一个bean的基本信息setBeanClass(MapperFactoryBean.class);,也包括可以透传给构造函数信息addGenericArgumentValue(IUserDao.class);
  • 最后使用 BeanDefinitionReaderUtils.registerBeanDefinition,进行bean的注册,也就是注册到DefaultListableBeanFactory中。
配置文件spring-config
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-autowire="byName">
<bean id="userDao" class="com.bestrookie.design.agent.RegisterBeanFactory"/>
</beans>

总结

通过开发一个关于Mybatis-Spring中间件中的部分核心功能,体现代理模式的强大之处。虽然涉及的一些关于代理类的创建以及Spring中对象Bean的注册等知识点在平常的业务员开发中很少用到,但在中间件开发中却很常见。代理模式除了用于开发中间件,还可用于对服务进行包装、物联网组件等,让复杂的各项服务变为轻量级调用和缓存使用。

代理模式的设计方式可以让代码更加整洁、干净,易于维护,虽然在这部分开发过程中增加了很多类,但是这种中间件的复用性极高,也更加智能,也可以非常方便地扩展到各种服务应用中。

评论