结构型模式之代理模式的案例示范

1. 代理模式介绍

在这里插入图片描述

代理模式有点像老大和小弟,也有点像分销商。主要解决的问题是为某些资源的访问、对象的类的易用操作上提供方便使用的代理服务。而这种设计思想的模式经常会出现在我们的系统或用到过的组件中,它们都提供给你一种非常简单易用的方式控制原本你需要编写很多代码而进行使用的服务类。

类似的场景:

  1. 你的数据库访问层面经常会提供一个较为基础的应用,以此来减少应用服务扩容时不至于数据库连接数暴增。
  2. 使用过的一些中间件:PRC框架。在拿到jar包对接口的描述后,中间件会在服务启动的时候生成对应的代理类,当调用接口时,实际是通过代理类发出的socket信息进行调用。
  3. 另外像常用的Mybatis,基本是定义接口但是不需要写实现类,就可以对xml或者自定义注解里的sql语句进行增删改查。

2. 案例场景模拟

在这里插入图片描述

在这里我们模拟实现mybatis-spring中代理类生成部分

对于Mybatis的使用中只需要定义接口不需要写实现类就可以完成增删改查操作有疑问的小伙伴,在这里可以学习到这部分知识。解析下来我们会通过实现一个这样的代理类交给spring管理的核心过程,来讲述代理类模式。

4. 代理模式实现过程

接下来会使用代理模式来模拟实现一个Mybatis中对类的代理过程,也就是只需要定义接口,就可以关联到方法注解中的sql语句完成对数据库的操作。

需要提前了解的一些知识点:

  1. BeanDefinitionRegistryPostProcessor,spring的接口类用于处理的bean的定义注册。
  2. GenericBeanDefinition,定义bean的信息,在mybatis-spring中使用到的是ScannedGenericBeanDefinition略有不同。
  3. FactoryBean,用于处理bean工厂的类。

工程结构

org.itstack.demo.design
----agent
	----MapperFactoryBean.java
	----RegisterBeanFactory.java
	----Select.java
----IUserDao.java

resources
----spring-config.xml

代理模式中间件模型结构

在这里插入图片描述

  • 此模型中涉及的类并不多,但都是抽离出来的核心处理类,主要的事情就是对类的代理和注册到spring中。
  • 图中上面是关于中间件的实现部分,下面对应的是功能的使用。

代码实现

自定义注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {
	String value() default ""; // sql语句
}
  • 这里我们定义了一个模拟mybatis-spring中的自定义注解,用于使用在方法层面。

Dao层接口

public interface IUserDao {
	@Select("select userName from user where id = #{uId}")
	String queryUserInfo(String uId);
}
  • 这里定义了一个Dao层接口,并把自定义注解添加上,这与我们使用mybatis组件是一样的。
  • 自定义注解和Dao层接口的准备就做好了,后面开始实现中间件功能部分。

代理类定义

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] + ",沉淀、分享、成长!";
        };
        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()。
  • 在方法getObecjt()中提供了类的代理以及模拟对sql语句的处理,这里包含了用户调用dao层方法的处理逻辑。
  • 还有最上面我们提供构造函数来透传需要被代理类,Class 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,这部分是spring中关于一个bean注册过程的源码。
  • GenericBeanDefinition,用于定义一个bean的基本信息setBeanClass(MapperFactoryBean.class),也可以透传给构造函数信息addGenericArgumentValue(IUserDao.class)
  • 最后使用BeanDefinitionReaderUtils.registerBeanDefinition进行bean的注册,也就是注册到DefaultListableBeanFactory中。

配置文件 spring-config

<?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="org.itstack.demo.design.agent.RegisterBeanFactory"/>
</beans>
  • 在配置文件中添加我们的bean配置,在mybatis的使用中一般会配置扫描的dao层包,这样就可以减少这部分的配置。

测试验证

编写测试类

@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("springconfig.xml");
    IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
    String res = userDao.queryUserInfo("100001");
    logger.info("测试结果:{}", res);
}
  • 测试过程比较简单,通过加载Bean工厂获取我们的代理类的实例对象,之后调用方法返回结果。
  • 这个过程可以看到我们没有对接口先一个实现类,而是使用代理的方式给接口生成一个实现类,并交给spring管理。

测试结果

23:21:57.551 [main] DEBUG o.s.core.env.StandardEnvironment - Adding
PropertySource 'systemProperties' with lowest search precedence
...
23:21:57.858 [main] DEBUG o.s.c.s.ClassPathXmlApplicationContext - Unable
to locate LifecycleProcessor with name 'lifecycleProcessor': using default
[org.springframework.context.support.DefaultLifecycleProcessor@7bc1a03d]
23:21:57.859 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning
cached instance of singleton bean 'lifecycleProcessor'
23:21:57.860 [main] DEBUG o.s.c.e.PropertySourcesPropertyResolver - Could
not find key 'spring.liveBeansView.mbeanDomain' in any property source
23:21:57.861 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning
cached instance of singleton bean 'userDao'
23:21:57.915 [main] INFO o.i.d.design.agent.MapperFactoryBean - SQL􀒓
select userName from user where id = 100001
23:21:57.915 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:
100001,沉淀、分享、成长!

Process finished with exit code 0
  • 从测试结果可以看到,我们打印了SQL语句,这部分语句是从自定义注解中获取的:select userName from user where id = 100001,我们做了简单的适配。在mybatis框架中会交给SqlSession的实现类进行逻辑处理返回操作数据库数据。

4. 总结

  • 这里我们模拟开发了一个关于mybatis-spring中间件的核心功能,体现了代理模式的强大之处。
  • 代理模式除了开发中间件外还可以是对服务的包装、物联网组件等,让复杂的各项服务变为轻量级调用、缓存使用。
  • 代理模式的设计方式可以让代码更加干净、易于维护,虽然在这部分开发中额外增加了很多类也包括了自己处理bean的注册等,但是这样的中间件复用性很高,可以非常方便的扩展到各个服务应用中。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值