重學 Java 設計模式:實戰代理模式「模擬mybatis-spring中定義DAO接口,使用代理類方式操做數據庫原理實現場景」

做者:小傅哥
博客:https://bugstack.cn - 原創系列專題案例html

沉澱、分享、成長,讓本身和他人都能有所收穫!😄

1、前言

難以跨越的瓶頸期,把你拿捏滴死死的!java

編程開發學習過程當中遇到的瓶頸期,每每是因爲看不到前進的方向。這個時候你特別但願能有人告訴你,你還欠缺些什麼朝着哪一個方向努力。而致使這一問題的主要緣由是因爲平常的業務開發太過於複製過去,日復一日的重複。沒有太多的挑戰,也沒參與過較大致量的業務場景,除了這些開發場景因素外,還有缺乏組內的技術氛圍和技術分享,沒有人作傳播和佈道者,也缺乏本身對各項技術學習的熱情,從而致使一直遊蕩在瓶頸之下,難以提高。spring

小公司與大公司,選擇哪一個?sql

刨除掉薪資之外你會選擇什麼,是不有人建議小公司,由於能夠接觸到各個環境,也有人建議大公司,由於正規體量大能夠學習到更多。有些時候你的技術成長緩慢也是由於你的不一樣選擇而致使的,小公司確實要接觸各個環境,但每每若是你所作的業務體量不高,那麼你會用到的技術棧就會相對較少,同時也會技術棧研究的深度也會較淺。大公司中確實有時候你不須要去關心一個集羣的部署和維護、一箇中間件的開發、全套服務監控等等,但若是你願意瞭解這些技術在內部都是公開的,你會擁有無限的技術養分能夠補充。而這最主要的是提高視野和事業。數據庫

除了業務中的CRUD開發,有些技術你真的很難接觸到!編程

可能不少小夥伴認爲技術開發就是承接下產品需求,寫寫CRUD,不會的百度一下,就完事了,總以爲別人問的東西像再造火箭同樣。但在高體量、高併發的業務場景下,每一次的壓測優化,性能提高,都像在研究一道數學題同樣,反覆的錘鍊,壓榨性能。不斷的深究,找到最合適的設計。除了這些優化提高外,還有那麼廣闊的技術體系棧,均可能由於你只是注重CRUD而被忽略;字節碼編程、領域驅動設計架構、代理模式中間件開發、JVM虛擬機實現原理等等。設計模式

2、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. Spring 4.3.24.RELEASE
  4. 涉及工程三個,能夠經過關注公衆號bugstack蟲洞棧,回覆源碼下載獲取(打開獲取的連接,找到序號18)
工程 描述
itstack-demo-design-12-00 模擬MyBatis開發中間件代理類部分

3、代理模式介紹

代理模式,圖片來自 refactoringguru.cn

代理模式有點像老大和小弟,也有點像分銷商。主要解決的是問題是爲某些資源的訪問、對象的類的易用操做上提供方便使用的代理服務。而這種設計思想的模式常常會出如今咱們的系統中,或者你用到過的組件中,它們都提供給你一種很是簡單易用的方式控制本來你須要編寫不少代碼的進行使用的服務類。緩存

相似這樣的場景能夠想到;微信

  1. 你的數據庫訪問層面常常會提供一個較爲基礎的應用,以此來減小應用服務擴容時不至於數據庫鏈接數暴增。
  2. 使用過的一些中間件例如;RPC框架,在拿到jar包對接口的描述後,中間件會在服務啓動的時候生成對應的代理類,當調用接口的時候,實際是經過代理類發出的socket信息進行經過。
  3. 另外像咱們經常使用的MyBatis,基本是定義接口可是不須要寫實現類,就能夠對xml或者自定義註解裏的sql語句進行增刪改查操做。

4、案例場景模擬

場景模擬;實現mybatis-spring中代理類生成部分

在本案例中咱們模擬實現mybatis-spring中代理類生成部分mybatis

對於Mybatis的使用中只須要定義接口不須要寫實現類就能夠完成增刪改查操做,有疑問的小夥伴,在本章節中就能夠學習到這部分知識。解析下來咱們會經過實現一個這樣的代理類交給spring管理的核心過程,來說述代理類模式。

這樣的案例場景在實際的業務開發中其實很少,由於這是將這種思想運用在中間件開發上,而不少小夥伴常常是作業務開發,因此對Spring的bean定義以及註冊和對代理以及反射調用的知識瞭解的相對較少。但能夠經過本章節做爲一個入門學習,逐步瞭解。

5、代理類模式實現過程

接下來會使用代理類模式來模擬實現一個Mybatis中對類的代理過程,也就是隻須要定義接口,就能夠關聯到方法註解中的sql語句完成對數據庫的操做。

這裏須要注意一些知識點;

  1. BeanDefinitionRegistryPostProcessor,spring的接口類用於處理對bean的定義註冊。
  2. GenericBeanDefinition,定義bean的信息,在mybatis-spring中使用到的是;ScannedGenericBeanDefinition 略有不一樣。
  3. 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中。
  • 上圖中最上面是關於中間件的實現部分,下面對應的是功能的使用。

2. 代碼實現

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是咱們的準備工做,後面開始實現中間件功能部分。

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;
    }

    @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] + ",小傅哥,bugstack.cn - 沉澱、分享、成長,讓本身和他人都能有所收穫!";
        };
        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()返回類是單例的。

2.4 將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中。

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工廠獲取咱們的代理類的實例對象,以後調用方法返回結果。
  • 那麼這個過程你能夠看到咱們是沒有對接口先一個實現類的,而是使用代理的方式給接口生成一個實現類,並交給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的實現類進行邏輯處理返回操做數據庫數據
  • 而這裏咱們的測試結果是一個固定的,若是你願意更加深刻的研究能夠嘗試與數據庫操做層進行關聯,讓這個框架能夠更加完善。

6、總結

  • 關於這部分代理模式的講解咱們採用了開發一個關於mybatis-spring中間件中部分核心功能來體現代理模式的強大之處,因此涉及到了一些關於代理類的建立以及spring中bean的註冊這些知識點,可能在日常的業務開發中都是不多用到的,可是在中間件開發中確實很是常見的操做。
  • 代理模式除了開發中間件外還能夠是對服務的包裝,物聯網組件等等,讓複雜的各項服務變爲輕量級調用、緩存使用。你能夠理解爲你家裏的電燈開關,咱們不能操做220v電線的人肉鏈接,可是可使用開關,避免觸電。
  • 代理模式的設計方式可讓代碼更加整潔、乾淨易於維護,雖然在這部分開發中額外增長了不少類也包括了本身處理bean的註冊等,可是這樣的中間件複用性極高也更加智能,能夠很是方便的擴展到各個服務應用中。

7、推薦閱讀

相關文章
相關標籤/搜索