手寫Mybatis和Spring整合簡單版示例窺探Spring的強大擴充套件能力

itxiaoshen發表於2021-12-12

Spring 擴充套件點

**本人部落格網站 **IT小神 www.itxiaoshen.com

官網地址****:https://spring.io/projects/spring-framework

The Spring Framework provides a comprehensive programming and configuration model for modern Java-based enterprise applications - on any kind of deployment platform.

Spring框架為現代基於java的企業應用程式提供了全面的程式設計和配置模型——適用於任何型別的部署平臺。

Spring之所以能打敗其他所有同型別Java開發框架屹立不倒的重要原因之一就是提供很多擴充套件點,讓其他元件和框架很容易就整合到Spring框架裡,所以也就誕生很多基於Spring的二次開發專案,接下來我們一起聊聊Spring提供哪些擴充套件點,這篇文章只是簡單說明擴充套件點但不深入,有興趣的夥伴可以後續一起學習交流,本篇最後我們再進行一個Mybatis和Spring整合工程簡易開發示例

Spring載入上下文方式

Spring載入容器上下文主要提供以下三大類方式,第一種基於配置類為常用的使用方式,AnnotationConfigApplicationContext傳入引數是一個Class陣列也即是支援多個配置類

public class ApplicationDemo {
    public static void main(String[] args) {
        //基於配置類載入Spring上下文
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        Student student = applicationContext.getBean(Student.class);
        System.out.println(student);

        //基於專案路徑xml載入Spring上下文
        ClassPathXmlApplicationContext applicationContextXml = new ClassPathXmlApplicationContext("spring.xml");
        student = applicationContextXml.getBean(Student.class);
        System.out.println(student);

        //基於檔案系統絕對路徑xml載入Spring上下文
        FileSystemXmlApplicationContext applicationContextFileXml = new FileSystemXmlApplicationContext("E://spring.xml");
        student = applicationContextXml.getBean(Student.class);
        System.out.println(student);
    }
}

@Configuration
@ComponentScan({"com.itxs.pojo","com.itxs.extend"})
public class MyConfig {
}
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

擴充套件點

ApplicationContextInitializer

這個是Spring Boot提供的擴充套件點,在整個 spring 容器在重新整理之前初始化 ConfigurableApplicationContext 的回撥介面,簡單來說,就是在容器重新整理之前呼叫此類的 initialize 方法。這個點允許被使用者自己擴充套件。使用者可以在整個 spring 容器還沒被初始化之前做一些事情。可以想到的場景可能為,在最開始啟用一些配置,或者利用這時候 class 還沒被類載入器載入的時機,進行動態位元組碼注入等操作

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("-----------------------MyApplicationContextInitializer initialize");
    }
}
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        //SpringApplication Main函式新增初始化器方式
        SpringApplication springApplication = new SpringApplication(MyApplication.class);
        springApplication.addInitializers(new MyApplicationContextInitializer());
        springApplication.run(args);
    }
}

image-20210818142114486

第二種配置檔案中配置

context:
  initializer:
    classes: com.itxs.extend.MyApplicationContextInitializer

第三種SpringBoot的SPI擴充套件---META-INF/spring.factories中配置

org.springframework.context.ApplicationContextInitializer=com.itxs.extend.MyApplicationContextInitializer

image-20210818142547817

BeanDefinitionRegistryPostProcessor-bean定義註冊後置處理器

BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子介面,BeanFactoryPostProcessor的作用是在bean的定義資訊已經載入但還沒有進行初始化的時候執行postProcessBeanFactory()的方法,BeanDefinitionRegistryPostProcessor是在BeanFactoryPostProcessor的前面執行,在bean定義之後提供的擴充套件點,比如可以在這裡動態註冊自己的 beanDefinition,載入 classpath 之外的 bean資訊。

image-20210818090205301

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanDefinitionRegistry");
        System.out.println("BeanDefinitionCount:"+beanDefinitionRegistry.getBeanDefinitionCount());
        String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println("beanDefinitionName:"+beanDefinitionName);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanFactory");
    }
}

BeanFactoryPostProcessor-Bean工廠後置處理器

BeanFactoryPostProcessor 介面是 Spring 初始化 BeanFactory 的擴充套件介面,在 Spring 在讀取 beanDefinition 資訊之後例項化 bean 之前讀取 bean 的定義並可以修改它。在這個時機,使用者可以通過實現這個擴充套件介面來自行處理一些東西,比如修改已經註冊的 beanDefinition 的元資訊。下面將student bean型別修改為teacher bean型別

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("BeanFactoryPostProcessor-postProcessBeanFactory");
        BeanDefinition student = configurableListableBeanFactory.getBeanDefinition("student");
        student.setBeanClassName(Teacher.class.getName());
    }
}

image-20210818093711037

BeanPostProcessor-Bean後置處理器

該介面也可稱為後置處理器,而我們Spring提供很多種bean後置處理器(比如在Spring原始碼中九大後置處理器九次呼叫後置處理器地方的說法),其作用是在Bean物件例項化和依賴注入完畢後,在顯示呼叫初始化方法的前後新增我們自己的業務邏輯;注意是Bean例項化完畢後及依賴注入完成後觸發的;在這個擴充套件點我們可以修改bean的屬性,可以給bean生成一個動態代理例項等等。Spring AOP的底層處理主要也是通過實現BeanPostProcessor來執行代理包裝邏輯。方法中輸入是一個個的bean,返回值則是bean修改的物件,預設為null則是不修改;bean後置處理器可以有多個,可以通過實現Ordered介面或者標記@Order註解來決定其處理順序。

· postProcessBeforeInitialization:初始化 bean 之前,相當於把 bean 注入 spring 上下文之前

· postProcessAfterInitialization:初始化 bean 之後,相當於把 bean 注入 spring 上下文之後

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization beanName:"+beanName);
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization beanName:"+beanName);
        if (bean.getClass().isAssignableFrom(Student.class)){
            return Teacher.class;
        }
        return null;
    }
}

image-20210818103026098

InstantiationAwareBeanPostProcessor

該介面也是 BeanPostProcessor的子介面,而BeanPostProcessor介面只在 bean 的初始化階段進行擴充套件(Bean例項化完畢後及依賴注入完成後觸發的),而 InstantiationAwareBeanPostProcessor 介面在此基礎上增加了 3 個方法,把可擴充套件的範圍增加了例項化階段和屬性注入階段,該類主要的擴充套件點有以下 5 個方法,主要在 bean 生命週期的兩大階段:例項化階段初始化階段**,而初始化階段兩個方法也即是上一節BeanPostProcessor提供的兩個方法**

· postProcessBeforeInstantiation:例項化 bean 之前,相當於 new 這個 bean 之前

· postProcessAfterInstantiation:例項化 bean 之後,相當於 new 這個 bean 之後

· postProcessPropertyValues:bean 已經例項化完成,在屬性注入時階段觸發,@Autowired,@Resource 等註解原理基於此方法實現

使用場景:這個擴充套件點非常有用 ,無論是寫中介軟體和業務中,都能利用這個特性;比如對實現了某一類介面的 bean 在各個生命期間進行收集,或者對某個型別的 bean 進行統一的設值等等。

@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInstantiation beanName" + beanName);
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInstantiation beanName" + beanName);
        return false;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        System.out.println("postProcessProperties beanName" + beanName);
        return null;
    }
}

image-20210818103200874

**### **

該介面也是 InstantiationAwareBeanPostProcessor 的子介面

· predictBeanType:該觸發點發生在 postProcessBeforeInstantiation 之前,這個方法用於預測 Bean 的型別,返回第一個預測成功的 Class 型別,如果不能預測返回 null;當你呼叫 BeanFactory.getType(name) 時當通過 bean 的名字無法得到 bean 型別資訊時就呼叫該回撥方法來決定型別資訊。

· determineCandidateConstructors:該觸發點發生在 postProcessBeforeInstantiation 之後,用於確定該 bean 的建構函式之用,返回的是該 bean 的所有建構函式列表。使用者可以擴充套件這個點,來自定義選擇相應的構造器來例項化這個 bean。

· getEarlyBeanReference:該觸發點發生在 postProcessAfterInstantiation 之後,當有迴圈依賴的場景,當 bean 例項化好之後,為了防止有迴圈依賴,Spring主要解決是的屬性的迴圈依賴,會提前暴露回撥方法,用於 bean 例項化的後置處理,這個方法就是在提前暴露的回撥方法中觸發。

@Component
public class MySmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor{
    @Override
    public Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
        System.out.println("predictBeanType beanName:"+beanName);
        return null;
    }

    @Override
    public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
        if (!beanClass.isAssignableFrom(Student.class)){
            System.out.println("determineCandidateConstructors beanName:"+beanName);
        }
        return null;
    }

    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        System.out.println("getEarlyBeanReference beanName:"+beanName);
        return null;
    }
}

image-20210818104902491

ApplicationContextAwareProcessor

這個是一個實現了BeanPostProcessor的實現類,該類本身並沒有擴充套件點,但是該類內部卻有 多個擴充套件點可供實現 ,這些類觸發的時機在 bean 例項化之後,初始化之前,可以看到,該類用於執行各種驅動介面,在 bean 例項化之後,屬性填充之後,通過執行以上紅框標出的擴充套件介面,來獲取對應容器的變數。所以這裡應該來說是有 6 個擴充套件點,這裡就放一起來說了

· EnvironmentAware:用於獲取 EnviromentAware 的一個擴充套件類,這個變數非常有用, 可以獲得系統內的所有引數。當然個人認為這個 Aware 沒必要去擴充套件,因為 spring 內部都可以通過注入的方式來直接獲得。

· EmbeddedValueResolverAware:用於獲取 StringValueResolver 的一個擴充套件類, StringValueResolver 用於獲取基於 String 型別的 properties 的變數,一般我們都用 @Value 的方式去獲取,如果實現了這個 Aware 介面,把 StringValueResolver 快取起來,通過這個類去獲取 String 型別的變數,效果是一樣的。

· ResourceLoaderAware:用於獲取 ResourceLoader 的一個擴充套件類,ResourceLoader 可以用於獲取 classpath 內所有的資源物件,可以擴充套件此類來拿到 ResourceLoader 物件。

· ApplicationEventPublisherAware:用於獲取 ApplicationEventPublisher 的一個擴充套件類,ApplicationEventPublisher 可以用來發布事件,結合 ApplicationListener 來共同使用,下文在介紹 ApplicationListener 時會詳細提到。這個物件也可以通過 spring 注入的方式來獲得。

· MessageSourceAware:用於獲取 MessageSource 的一個擴充套件類,MessageSource 主要用來做國際化。

· ApplicationContextAware:用來獲取 ApplicationContext 的一個擴充套件類,ApplicationContext 應該是很多人非常熟悉的一個類了,就是 spring 上下文管理器,可以手動的獲取任何在 spring 上下文註冊的 bean,我們經常擴充套件這個介面來快取 spring 上下文,包裝成靜態方法。同時 ApplicationContext 也實現了 BeanFactory,MessageSource,ApplicationEventPublisher 等介面,也可以用來做相關介面的事情。

private void invokeAwareInterfaces(Object bean) {
   if (bean instanceof EnvironmentAware) {
      ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
   }
   if (bean instanceof EmbeddedValueResolverAware) {
      ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
   }
   if (bean instanceof ResourceLoaderAware) {
      ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
   }
   if (bean instanceof ApplicationEventPublisherAware) {
      ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
   }
   if (bean instanceof MessageSourceAware) {
      ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
   }
   if (bean instanceof ApplicationStartupAware) {
      ((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
   }
   if (bean instanceof ApplicationContextAware) {
      ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
   }
}

SmartInitializingSingleton

這個介面中只有一個方法 afterSingletonsInstantiated,其作用是 在 spring 容器管理的所有單例物件(非懶載入物件)初始化完成之後呼叫的回撥介面。其觸發時機為 postProcessAfterInitialization 之後。使用場景:使用者可以擴充套件此介面在對所有單例物件初始化完畢後,做一些後置的業務處理。

@Component
public class MySmartInitializingSingleton implements SmartInitializingSingleton {
    @Override
    public void afterSingletonsInstantiated() {
        System.out.println("afterSingletonsInstantiated");
    }
}

FactoryBean

Spring為此提供了一個org.springframework.bean.factory.FactoryBean的工廠類介面,使用者可以通過實現該介面定製例項化Bean的邏輯。FactoryBean介面對於Spring框架來說佔用重要的地位,Spring自身就提供了70多個FactoryBean的實現;FactoryBean是一個介面,當在IOC容器中的Bean實現了FactoryBean後,通過getBean(String BeanName)獲取到的Bean物件並不是FactoryBean的實現類物件,而是這個實現類中的getObject()方法返回的物件。要想獲取FactoryBean的實現類,就要getBean(&BeanName),在BeanName之前加上&;

@Component
public class MyFactoryBean implements FactoryBean<Teacher> {

    @Override
    public Teacher getObject() throws Exception {
        return new Teacher();
    }

    @Override
    public Class<?> getObjectType() {
        return Teacher.class;
    }
}
public class ApplicationExtend {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        //Student student = applicationContext.getBean("student", Student.class); //這裡是原來獲取Student的型別,擴充套件點修改後需要註釋掉不然會型別轉換錯誤
        Object student = applicationContext.getBean("student");
        System.out.println("object:"+student);
        System.out.println("factoryBeanReturn:"+applicationContext.getBean("myFactoryBean"));
        System.out.println("factoryBeanSelf:"+applicationContext.getBean("&myFactoryBean"));
    }
}

image-20210818124412152

CommandLineRunner

這個是Spring Boot提供擴充套件介面,這個介面也只有一個方法:run(String... args),觸發時機為整個專案啟動完畢後,自動執行。如果有多個 CommandLineRunner,可以利用 @Order註解 來進行排序,值越小越優先執行。使用場景:使用者擴充套件此介面,進行啟動專案之後一些業務的預處理。

@Component
@Order(1)
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommandLineRunner run args:" +args);
    }
}
@Component
@Order(0)
public class MyTwoCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyTwoCommandLineRunner run args:" +args);
    }
}

image-20210818143546448

ApplicationListener

ApplicationListener 可以監聽某個事件的 event,觸發時機可以穿插在業務方法執行過程中,使用者可以自定義某個業務事件;但是 spring 內部也有一些內建事件,這種事件,可以穿插在啟動呼叫中。我們也可以利用這個特性,來自己做一些內建事件的監聽器來達到和前面一些觸發點大致相同的事情。接下來羅列下 spring 主要的內建事件:

· ContextRefreshedEvent

· ApplicationContext 被初始化或重新整理時,該事件被髮布。這也可以在 ConfigurableApplicationContext 介面中使用 refresh() 方法來發生。此處的初始化是指:所有的 Bean 被成功裝載,後處理 Bean 被檢測並啟用,所有 Singleton Bean 被預例項化,ApplicationContext 容器已就緒可用。

· ContextStartedEvent

· 當使用 ConfigurableApplicationContext (ApplicationContext 子介面)介面中的 start() 方法啟動 ApplicationContext 時,該事件被髮布。你可以調查你的資料庫,或者你可以在接受到這個事件後重啟任何停止的應用程式。

· ContextStoppedEvent

· 當使用 ConfigurableApplicationContext 介面中的 stop() 停止 ApplicationContext 時,釋出這個事件。你可以在接受到這個事件後做必要的清理的工作

· ContextClosedEvent

· 當使用 ConfigurableApplicationContext 介面中的 close() 方法關閉 ApplicationContext 時,該事件被髮布。一個已關閉的上下文到達生命週期末端;它不能被重新整理或重啟

· RequestHandledEvent

· 這是一個 web-specific 事件,告訴所有 bean HTTP 請求已經被服務。只能應用於使用 DispatcherServlet 的 Web 應用。在使用 Spring 作為前端的 MVC 控制器時,當 Spring 處理使用者請求結束後,系統會自動觸發該事件

@Configuration
public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("MyApplicationListener onApplicationEvent");
    }
}

BeanNameAware

BeanNameAware也是 Aware 介面擴充套件的子介面,觸發點在 bean 的初始化之前,也就是 postProcessBeforeInitialization 之前

如果bean實現了BeanNameAware介面,spring將bean的id傳給setBeanName()方法;這個類的觸發點方法只有一個:setBeanName使用場景為:使用者可以擴充套件這個點,在初始化 bean 之前拿到 spring 容器中註冊的的 beanName,來自行修改這個 ;

@Component
public class MyBeanNameAware implements BeanNameAware {
    @Override
    public void setBeanName(String s) {
        System.out.println("setBeanName:"+s);
    }
}

BeanFactoryAware

BeanFactoryAware也是 Aware 介面擴充套件的子介面,只有一個觸發點,發生在 bean 的例項化之後,注入屬性之前,也就是 Setter 之前。這個類的擴充套件點方法為 setBeanFactory,可以拿到 BeanFactory 這個屬性。使用場景為,你可以在 bean 例項化之後,但還未初始化之前,拿到 BeanFactory,在這個時候,可以對每個 bean 作特殊化的定製。也或者可以把 BeanFactory 拿到進行快取,日後使用,如果bean實現了BeanFactoryAware介面,spring將呼叫setBeanFactory方法,將BeanFactory例項傳進來;

@Component
public class MyBeanFactoryAware implements BeanFactoryAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryAware setBeanFactory:"+beanFactory.getBean("myBeanFactoryAware").getClass().getSimpleName());
    }
}

Bean初始化及銷燬回撥方法

  • InitializingBean是用來初始化 bean 的。InitializingBean 介面為 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet 方法,凡是繼承該介面的類,在初始化 bean 的時候都會執行該方法。這個擴充套件點的觸發時機在 postProcessAfterInitialization 之前。
  • DisposableBean這個擴充套件點也只有一個方法:destroy(),其觸發時機為當此物件銷燬時,會自動執行這個方法。比如說執行 applicationContext.registerShutdownHook 時,就會觸發這個方法。
  • @PostConstruct這個並不算一個擴充套件點,其實就是一個標註。其作用是在 bean 的初始化階段,如果對一個方法標註了 @PostConstruct,會先呼叫這個方法。這裡重點是要關注下這個標準的觸發點,這個觸發點是在 postProcessBeforeInitialization 之後,InitializingBean.afterPropertiesSet 之前。使用場景:使用者可以對某一方法進行標註,來進行初始化某一個屬性
  • @PreDestroy修飾的方法會在伺服器關閉Spring容器的時候執行,並且只會呼叫一次

使用場景:使用者實現此介面,來進行系統啟動的時候一些業務指標的初始化工作。

@Repository
public class Student implements InitializingBean, DisposableBean {

    @PostConstruct
    public void init() throws Exception {
        System.out.println("Student init");
    }

    @PreDestroy
    public void close() throws Exception {
        System.out.println("Student close");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Student destroy");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Student afterPropertiesSet");
    }
}

image-20210818140449722

整合示例

mybatis-spring gitee原始碼地址
https://gitee.com/yongzhebuju/mybatis-spring

我們從這些 Spring&Spring Boot 的擴充套件點當中,大致可以窺視到整個 bean 的生命週期。在業務開發或者寫中介軟體業務的時候,可以合理利用 Spring 提供給我們的擴充套件點,在 Spring 啟動的各個階段內做一些事情,以達到自定義初始化的目的。接下來我們一起學習一個常見Mybatis和Spring整合開發簡易示例,上面內容我們初步對於Spring擴充套件點有了一些理解,由於本人閱讀過部分Mybatic與Spring整合原始碼,思路也是來源於此,由於目前是簡易示例,不在工程加入Mybatis依賴,著重在整合部分

思考問題

  • Spring的Bean是如何生成的?
  • Spring提供哪些擴充套件點來整合第三方框架?
  • Spring是如何來整理Mybatis的?Mybatis代理物件都是介面,不能直接通過New方式將Mapper Bean註冊Spring容器裡

主體思路

  • 首先可以明確一點,我們需要利用到Jdk動態代理及反射機制
  • 藉助FactoryBean特性,FactoryBean可以返回動態代理的物件及型別
  • 通過ImportBeanDefinitionRegistrar通過Import註解將FactoryBean通過BeanDefinition註冊到BeanDefinitionRegistry通過後續Bean的生命週期最終放到Spring的容器裡

Mybatic快速入門

從官網的就可以直接找到Java程式設計快速入門** ,這裡只是大概說明一下**

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

整合示例實現

pom檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itxs</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.9</version>
        </dependency>
    </dependencies>
</project>

image-20210819151245998

註解介面,通過@Import同時將ItxsImportBeanDefinitionRegistrar匯入到Spring容器裡

@Retention(RetentionPolicy.RUNTIME)
@Import({ItxsImportBeanDefinitionRegistrar.class})
public @interface ItxsScan {
    String value() default "";
}

配置類,@ItxsScan為我們自定義的註解,主要標記掃描mapper路徑

@Configuration
@ComponentScan({"com.itxs"})
@ItxsScan("com.itxs.dao")
public class AppConfig {
}

mapper介面,@ItxsSelect為我們自定義的註解,主要標記mapper介面方法sql語句

package com.itxs.dao;


import com.itxs.annotation.ItxsSelect;
import com.itxs.pojo.User;

public interface UserMapper {

    @ItxsSelect("select * from user where user_id = #{userId}")
    User selectByUserId(Integer userId);
}

service實現類

@Component
public class UserService {

    @Autowired
    UserMapper userMapper;

    @Autowired
    OrderMapper orderMapper;

    public void getUser(){
        userMapper.selectByUserId(1);
        orderMapper.selectByOrderId(1);
    }
}

FactoryBean實現,這裡通過建構函式傳入介面的Class型別,將型別通過Jdk動態代理生成並返回物件,當呼叫目標物件後會執行代理物件invoke方法,從invoke方法通過反射與註解獲取到sql語句,後續流程就可以利用Mybatis提供運算元據庫流程,這裡就不繼續深入了

package com.itxs.utils;

import com.itxs.annotation.ItxsSelect;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ItxsFactoryBean implements FactoryBean {

    private Class mapper;

    public ItxsFactoryBean(Class mapper) {
        this.mapper = mapper;
    }

    @Override
    public Object getObject() throws Exception {
        //使用動態代理機制
        Object o = Proxy.newProxyInstance(ItxsFactoryBean.class.getClassLoader(), new Class[]{mapper}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (Object.class.equals(method.getDeclaringClass())){
                    return method.invoke(this,args);
                }
                ItxsSelect annotation = method.getAnnotation(ItxsSelect.class);
                System.out.println("呼叫方法名稱是"+method.getName()+",sql語句為"+annotation.value());
                //後面可執行Mybatic運算元據庫的相關操作
                return null;
            }
        });
        return o;
    }

    @Override
    public Class<?> getObjectType() {
        return mapper;
    }
}

ImportBeanDefinitionRegistrar的實現,通過獲取@import上的註解找到mapper的掃描路徑,通過classLoader載入磁碟下Class檔案生成BeanDefinition並設定建構函式mapper型別引數,最終將BeanDefinition註冊到BeanDefinitionRegistry

package com.itxs.utils;

import com.itxs.annotation.ItxsScan;
import com.itxs.dao.OrderMapper;
import com.itxs.dao.UserMapper;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class ItxsImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        //獲取註解ItxsScan物件,並取出value值作為掃描的路徑
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ItxsScan.class.getName());
        String mapperPath = annotationAttributes.get("value").toString();
        List<Class> mappers = scan(mapperPath);
        for (Class mapper : mappers) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
            AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
            beanDefinition.setBeanClass(ItxsFactoryBean.class);
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapper);
            registry.registerBeanDefinition(StringUtils.toLowerCaseFirstOne(mapper.getSimpleName()),beanDefinition);
        }
    }

    private List<Class> scan(String path) {
        List<Class> classList = new ArrayList<Class>();
        path = path.replace(".", "/");
        ClassLoader classLoader = ItxsImportBeanDefinitionRegistrar.class.getClassLoader();
        URL url = classLoader.getResource(path);
        File file = new File(url.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                String absolutePath = files[i].getAbsolutePath();
                absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                absolutePath = absolutePath.replace("\\", ".");
                try {
                    Class<?> aClass = classLoader.loadClass(absolutePath);
                    classList.add(aClass);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        return classList;
    }
}

Main程式

package com.itxs;

import com.itxs.config.AppConfig;
import com.itxs.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MybatisApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.getUser();
    }
}

執行結果

image-20210819152707884

相關文章