@[toc]
结构型模式
结构型模式主要是解决如何将对象和类组装成较大的结构, 并同时保持结构的灵活和⾼效。
结构型模式包括:适配器、桥接、组合、装饰器、外观、享元、代理,这7类
概述
代理模式有点像⽼⼤和⼩弟,也有点像分销商。主要解决的是问题是为某些资源的访问、对象的类的易⽤操作上提供⽅便使⽤的代理服务。
⽽这种设计思想的模式经常会出现在我们的系统中,或者你⽤到过的组件中,它们都提供给你⼀种⾮常简单易⽤的⽅式控制原本你需要编写很多代码的进⾏使⽤的服务类。
类似这样的场景还有“”
- 你的数据库访问层⾯经常会提供⼀个较为基础的应⽤,以此来减少应⽤服务扩容时不⾄于数据库连接数暴增。
- 使⽤过的⼀些中间件例如RPC框架,在拿到jar包对接⼝的描述后,中间件会在服务启动的时候⽣成对应的代理类,当调⽤接⼝的时候,实际是通过代理类发出的socket信息进⾏通过。
- 另外像我们常⽤的 MyBatis ,基本是定义接⼝但是不需要写实现类,就可以对 xml 或者⾃定义注解⾥的 sql 语句进⾏增删改查操作。
Case
模拟实现mybatis-spring中代理类⽣成部分.
对于Mybatis的使⽤中只需要定义接⼝不需要写实现类就可以完成增删改查操作,解析下来我们会通过实现⼀个这样的代理类交给spring管理的核⼼过程,来讲述代理类模式。
接下来会使⽤代理类模式来模拟实现⼀个Mybatis中对类的代理过程,也就是只需要定义接⼝,就可以关联到⽅法注解中的 sql 语句完成对数据库的操作。
前置知识:
BeanDefinitionRegistryPostProcessor
spring的接⼝类⽤于处理对bean的定义注册。
GenericBeanDefinition
定义bean的信息,在mybatis-spring中使⽤到的是ScannedGenericBeanDefinition
略有不同。FactoryBean
⽤于处理bean⼯⼚的类
【⼯程结构】
【代理模式中间件模型结构】
- 此模型中涉及的类并不多,但都是抽离出来的核⼼处理类。主要的事情就是对类的代理和注册到spring中。
- 上图中最上⾯是关于中间件的实现部分,下⾯对应的是功能的使⽤
Code
【⾃定义注解】
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {
String value() default "";
}
定义了⼀个模拟mybatis-spring中的⾃定义注解,⽤于使⽤在⽅法层⾯
【Dao层接⼝】
public interface IUserDao {
@Select("select userName from user where id = #{uId}")
String queryUserInfo(String uId);
}
定义⼀个Dao层接⼝,并把⾃定义注解添加上。这与使⽤的mybatis组件是⼀样的。
接下来开始实现中间件功能部分
【代理类定义】
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class MapperFactoryBean<T> implements FactoryBean<T> {
private Logger logger = LoggerFactory.getLogger(MapperFactoryBean.class);
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
InvocationHandler handler = (proxy, method, args) -> {
Select select = method.getAnnotation(Select.class);
logger.info("SQL:{}", select.value().replace("#{uId}", args[0].toString()));
return args[0] + " xxxxxxxxxxxx!";
};
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容器】
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 {
// left intentionally blank
}
}
- 将代理的bean交给spring容器管理,也就可以⾮常⽅便让我们可以获取到代理的bean。
GenericBeanDefinition
,⽤于定义⼀个bean的基本信息setBeanClass(MapperFactoryBean.class)
; ,也包括可以透传给构造函数信息addGenericArgumentValue(IUserDao.class);
- 最后使⽤
BeanDefinitionReaderUtils.registerBeanDefinition
,进⾏bean的注册,也就是注册到DefaultListableBeanFactory
中
接下来, 配置⽂件spring-config,配置RegisterBeanFactory
bean
【单元测试 】
@Test
public void test_IUserDao() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
String res = userDao.queryUserInfo("100001");
logger.info("测试结果:{}", res);
}
- 通过加载Bean⼯⼚获取我们的代理类的实例对象,之后调⽤⽅法返回结果
- 那么这个过程可以 看到我们是没有对接⼝先⼀个实现类的,⽽是使⽤代理的⽅式给接⼝⽣成⼀个实现类,并交给spring管理。