代理模式介绍
代理模式就是为了方便访问某些资源,使对象类更加易用,从而在操作上使用的代理服务。
代理模式经常会出现在系统或组件中,他们提供一种非常简单易用的方式,控制原本需要编写很多代码才能实现的服务类,类似一下场景:
- 在数据库访问层面会提供一个比较基础的应用,避免在对应服务扩容时造成数据库链接数暴增
- 使用过的一些中间件,例如RPC框架,在拿到jar包对接口的描述后,中间件会在服务启动时生成对应的代理类。当调用接口时,实际是通过代理类发出的Socker信息。
- 常用的Mybatis基本功能是定义接口,不需要写实现类就可以对XML或自定义注解里的SQL语句增删查改。
MyBatis-Spring中代理类场景
这次的样例是模拟实现MyBatis-Spring中代理类生成部分。我们在使用MyBatis时,只需要定义接口,而不需要写实现类就可以完成增删查改操作,本章会通过代理类交给Spring管理的过程介绍代理类模式。
代理类模式实现过程
先介绍一些用到的,但是可能不太了解的知识点:
BeanDefinitionRegistryPostProcessor
: spring的接口类用于处理对bean的定义注册。
GenericBeanDefinition
,定义bean的信息,在mybatis-spring中使用到的是;ScannedGenericBeanDefinition
略有不同。
FactoryBean
,用于处理bean工厂的类,这个类非常见。
自定义注解
1 2 3 4 5 6 7 8 9 10 11 12
| package com.bestrookie.design.agent; import java.lang.annotation.*;
@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的注册等知识点在平常的业务员开发中很少用到,但在中间件开发中却很常见。代理模式除了用于开发中间件,还可用于对服务进行包装、物联网组件等,让复杂的各项服务变为轻量级调用和缓存使用。
代理模式的设计方式可以让代码更加整洁、干净,易于维护,虽然在这部分开发过程中增加了很多类,但是这种中间件的复用性极高,也更加智能,也可以非常方便地扩展到各种服务应用中。