什么網(wǎng)站可以做自考試題seo教育
重學 Java 設計模式:實戰(zhàn)代理模式「模擬mybatis-spring中定義DAO接口,使用代理類方式操作數(shù)據(jù)庫原理實現(xiàn)場景」
一、前言
難以跨越的瓶頸期,把你拿捏滴死死的!
編程開發(fā)學習過程中遇到的瓶頸期,往往是由于看不到前進的方向。這個時候你特別希望能有人告訴你,你還欠缺些什么朝著哪個方向努力。而導致這一問題的主要原因是由于日常的業(yè)務開發(fā)太過于復制過去,日復一日的重復。沒有太多的挑戰(zhàn),也沒參與過較大體量的業(yè)務場景,除了這些開發(fā)場景因素外,還有缺少組內(nèi)的技術氛圍和技術分享,沒有人做傳播和布道者,也缺少自己對各項技術學習的熱情,從而導致一直游蕩在瓶頸之下,難以提升。
小公司與大公司,選擇哪個?
刨除掉薪資以外你會選擇什么,是不有人建議小公司,因為可以接觸到各個環(huán)境,也有人建議大公司,因為正規(guī)體量大可以學習到更多。有些時候你的技術成長緩慢也是因為你的不同選擇而導致的,小公司確實要接觸各個環(huán)境,但往往如果你所做的業(yè)務體量不高,那么你會用到的技術棧就會相對較少,同時也會技術棧研究的深度也會較淺。大公司中確實有時候你不需要去關心一個集群的部署和維護、一個中間件的開發(fā)、全套服務監(jiān)控等等,但如果你愿意了解這些技術在內(nèi)部都是公開的,你會擁有無限的技術營養(yǎng)可以補充。而這最主要的是提升視野和事業(yè)。
除了業(yè)務中的CRUD開發(fā),有些技術你真的很難接觸到!
可能很多小伙伴認為技術開發(fā)就是承接下產(chǎn)品需求,寫寫CRUD,不會的百度一下,就完事了,總覺得別人問的東西像再造火箭一樣。但在高體量、高并發(fā)的業(yè)務場景下,每一次的壓測優(yōu)化,性能提升,都像在研究一道數(shù)學題一樣,反復的錘煉,壓榨性能。不斷的深究,找到最合適的設計。除了這些優(yōu)化提升外,還有那么廣闊的技術體系棧,都可能因為你只是注重CRUD而被忽略;字節(jié)碼編程、領域驅動設計架構、代理模式中間件開發(fā)、JVM虛擬機實現(xiàn)原理等等。
二、開發(fā)環(huán)境
- JDK 1.8
- Idea + Maven
- Spring 4.3.24.RELEASE
- 涉及工程三個,可以通過關注公眾號:
bugstack蟲洞棧
,回復源碼下載
獲取(打開獲取的鏈接,找到序號18)
工程 | 描述 |
---|---|
itstack-demo-design-12-00 | 模擬MyBatis開發(fā)中間件代理類部分 |
三、代理模式介紹
- 圖片來自:https://refactoringguru.cn/design-patterns/proxy
代理模式有點像老大和小弟,也有點像分銷商。主要解決的是問題是為某些資源的訪問、對象的類的易用操作上提供方便使用的代理服務。而這種設計思想的模式經(jīng)常會出現(xiàn)在我們的系統(tǒng)中,或者你用到過的組件中,它們都提供給你一種非常簡單易用的方式控制原本你需要編寫很多代碼的進行使用的服務類。
類似這樣的場景可以想到;
- 你的數(shù)據(jù)庫訪問層面經(jīng)常會提供一個較為基礎的應用,以此來減少應用服務擴容時不至于數(shù)據(jù)庫連接數(shù)暴增。
- 使用過的一些中間件例如;RPC框架,在拿到jar包對接口的描述后,中間件會在服務啟動的時候生成對應的代理類,當調(diào)用接口的時候,實際是通過代理類發(fā)出的socket信息進行通過。
- 另外像我們常用的
MyBatis
,基本是定義接口但是不需要寫實現(xiàn)類,就可以對xml
或者自定義注解里的sql
語句進行增刪改查操作。
四、案例場景模擬
在本案例中我們模擬實現(xiàn)mybatis-spring中代理類生成部分
對于Mybatis的使用中只需要定義接口不需要寫實現(xiàn)類就可以完成增刪改查操作,有疑問的小伙伴,在本章節(jié)中就可以學習到這部分知識。解析下來我們會通過實現(xiàn)一個這樣的代理類交給spring管理的核心過程,來講述代理類模式。
這樣的案例場景在實際的業(yè)務開發(fā)中其實不多,因為這是將這種思想運用在中間件開發(fā)上,而很多小伙伴經(jīng)常是做業(yè)務開發(fā),所以對Spring的bean定義以及注冊和對代理以及反射調(diào)用的知識了解的相對較少。但可以通過本章節(jié)作為一個入門學習,逐步了解。
五、代理類模式實現(xiàn)過程
接下來會使用代理類模式來模擬實現(xiàn)一個Mybatis中對類的代理過程,也就是只需要定義接口,就可以關聯(lián)到方法注解中的sql
語句完成對數(shù)據(jù)庫的操作。
這里需要注意一些知識點;
BeanDefinitionRegistryPostProcessor
,spring的接口類用于處理對bean的定義注冊。GenericBeanDefinition
,定義bean的信息,在mybatis-spring中使用到的是;ScannedGenericBeanDefinition
略有不同。FactoryBean
,用于處理bean工廠的類,這個類非常見。
1. 工程結構
itstack-demo-design-12-00
└── src├── main│ ├── java│ │ └── org.itstack.demo.design│ │ ├── agent│ │ │ ├── MapperFactoryBean.java│ │ │ ├── RegisterBeanFactory.java│ │ │ └── Select.java│ │ └── IUserDao.java│ └── resources │ └── spring-config.xml└── test└── java└── org.itstack.demo.test└── ApiTest.java
代理模式中間件模型結構
- 此模型中涉及的類并不多,但都是抽離出來的核心處理類。主要的事情就是對類的代理和注冊到spring中。
- 上圖中最上面是關于中間件的實現(xiàn)部分,下面對應的是功能的使用。
2. 代碼實現(xiàn)
2.1 自定義注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {String value() default ""; // sql語句}
- 這里我們定義了一個模擬mybatis-spring中的自定義注解,用于使用在方法層面。
2.2 Dao層接口
public interface IUserDao {@Select("select userName from user where id = #{uId}")String queryUserInfo(String uId);}
- 這里定義一個Dao層接口,并把自定義注解添加上。這與你使用的mybatis組件是一樣的。
- 2.1和2.2是我們的準備工作,后面開始實現(xiàn)中間件功能部分。
2.3 代理類定義
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;}@Overridepublic 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] + ",小傅哥,bugstack.cn - 沉淀、分享、成長,讓自己和他人都能有所收獲!";};return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, handler);}@Overridepublic Class<?> getObjectType() {return mapperInterface;}@Overridepublic boolean isSingleton() {return true;}}
- 如果你有閱讀過mybatis源碼,是可以看到這樣的一個類;
MapperFactoryBean
,這里我們也模擬一個這樣的類,在里面實現(xiàn)我們對代理類的定義。 - 通過繼承
FactoryBean
,提供bean對象,也就是方法;T getObject()
。 - 在方法
getObject()
中提供類的代理以及模擬對sql語句的處理,這里包含了用戶調(diào)用dao層方法時候的處理邏輯。 - 還有最上面我們提供構造函數(shù)來透傳需要被代理類,
Class<T> mapperInterface
,在mybatis中也是使用這樣的方式進行透傳。 - 另外
getObjectType()
提供對象類型反饋,以及isSingleton()
返回類是單例的。
2.4 將Bean定義注冊到Spring容器
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {@Overridepublic 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);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {// left intentionally blank}}
- 這里我們將代理的bean交給spring容器管理,也就可以非常方便讓我們可以獲取到代理的bean。這部分是spring中關于一個bean注冊過程的源碼。
GenericBeanDefinition
,用于定義一個bean的基本信息setBeanClass(MapperFactoryBean.class);
,也包括可以透傳給構造函數(shù)信息addGenericArgumentValue(IUserDao.class);
- 最后使用
BeanDefinitionReaderUtils.registerBeanDefinition
,進行bean的注冊,也就是注冊到DefaultListableBeanFactory
中。
2.5 配置文件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層包,這樣就可以減少這部分的配置。
3. 測試驗證
3.1 編寫測試類
@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工廠獲取我們的代理類的實例對象,之后調(diào)用方法返回結果。
- 那么這個過程你可以看到我們是沒有對接口先一個實現(xiàn)類的,而是使用代理的方式給接口生成一個實現(xiàn)類,并交給spring管理。
3.2 測試結果
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,小傅哥,bugstack.cn - 沉淀、分享、成長,讓自己和他人都能有所收獲!Process finished with exit code 0
- 從測試結果可以看到,我們打印了SQL語句,這部分語句是從自定義注解中獲取的;
select userName from user where id = 100001
,我們做了簡單的適配。在mybatis框架中會交給SqlSession
的實現(xiàn)類進行邏輯處理返回操作數(shù)據(jù)庫數(shù)據(jù) - 而這里我們的測試結果是一個固定的,如果你愿意更加深入的研究可以嘗試與數(shù)據(jù)庫操作層進行關聯(lián),讓這個框架可以更加完善。
六、總結
- 關于這部分代理模式的講解我們采用了開發(fā)一個關于
mybatis-spring
中間件中部分核心功能來體現(xiàn)代理模式的強大之處,所以涉及到了一些關于代理類的創(chuàng)建以及spring中bean的注冊這些知識點,可能在平常的業(yè)務開發(fā)中都是很少用到的,但是在中間件開發(fā)中確實非常常見的操作。 - 代理模式除了開發(fā)中間件外還可以是對服務的包裝,物聯(lián)網(wǎng)組件等等,讓復雜的各項服務變?yōu)檩p量級調(diào)用、緩存使用。你可以理解為你家里的電燈開關,我們不能操作220v電線的人肉連接,但是可以使用開關,避免觸電。
- 代理模式的設計方式可以讓代碼更加整潔、干凈易于維護,雖然在這部分開發(fā)中額外增加了很多類也包括了自己處理bean的注冊等,但是這樣的中間件復用性極高也更加智能,可以非常方便的擴展到各個服務應用中。