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


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM