記一次Maven項目改造成SpringBoot項目的過程實踐
背景概述
團隊有一個項目,是maven構建的普通Java項目。
項目沒有使用spring,用到瞭mysql、mybatis,還有其他大數據技術,比如flink、pulsar。
項目裡連接數據庫的部分,需要用到多個配置文件,一個是mybatis配置文件,一個是數據庫配置文件。如果用SpringBoot可以簡化為一個application.yml文件。
項目裡打包方式復雜,依賴一個maven-assemble的插件,打出的包是兩個jar,出現過由於配置文件讀取方式的錯誤,導致jar包還運行不瞭。使用這個插件打包,還需要寫一個自定義的配置文件,配置各個資源打包的參數。如果用SpringBoot,直接引入spring-boot-maven-plugin,打出的就是可執行jar包,不需要繁瑣的配置,不需要自己寫讀取配置的代碼。
為什麼要改造成SpringBoot項目呢,因為SpringBoot
- 簡化配置,不用寫這麼多配置文件
- 自動配置,引入starter依賴,可以自動把默認配置配好
- 內嵌web容器
- 自動版本管理,maven和starter配置使用
- 生態集成容易,如果項目想要集成另外的能力,引一些starter依賴,少量的配置就可以快速接入
此外也是一次技術提升的機會,技術的優勢,SpringBoot早就熟爛瞭。 所以打算改造成SpringBoot項目。
過程
加依賴
改造過程一步步來, 先把SpringBootStarter引入進來
<properties> <spring-boot.version>2.3.0.RELEASE</spring-boot.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- 此處省略其他的依賴 --> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.12.RELEASE</version> <configuration> <mainClass>com.xxx.pulsar.PulsarMain</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
修改main方法所在類
在原先的main方法上加上註解
引入數據庫依賴
首先把main函數中配置的數據庫連接硬編碼刪除,後面將要使用application.yml來配置
<!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.13</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> <exclusions> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </exclusion> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> </exclusion> </exclusions> </dependency>
啟動類上加入mapper掃描
@MapperScan("com.xxx.pulsar.mapper")
添加application.yml
# 端口 server: port: 8001 mybatis: # mapper映射文件 mapper-locations: classpath:mapper/*.xml spring: application: # 應用名稱 name: pulsar_demo datasource: type: com.alibaba.druid.pool.DruidDataSource druid: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db_test?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 initial-size: 10 max-active: 100 min-idle: 10 max-wait: 60000 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 60000 max-evictable-idle-time-millis: 300000 validation-query: SELECT 1 FROM DUAL # validation-query-timeout: 5000 test-on-borrow: false test-on-return: false test-while-idle: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 #filters: #配置多個英文逗號分隔(統計,sql註入,log4j過濾) filters: stat,wall stat-view-servlet: enabled: true url-pattern: /druid/*
sqlSessionFactory空指針異常分析
啟動測試,報錯,數據庫連接的地方報sqlSessionFactory空指針異常
查看錯誤堆棧,項目啟動的時候,會從RuleFunction這個類的構造函數裡面開始初始化資源。
setFields和setExtInfo這兩個方法寫在構造函數中,在類初始化時,就會調用,從數據庫查初始化資源,
這兩個方法內部會去查數據庫獲取基礎資源,見下圖
RuleFunction初始化時,Spring還沒有幫我們將MybatisSessionFactory類實例化,所以報瞭空指針異常。
改造MybatisSessionFactory類
改造前的MybatisSessionFactory類代碼如下
public class MybatisSessionFactory { private volatile static SqlSessionFactory sqlSessionFactory; private MybatisSessionFactory() {} public static void init(String configStr, Properties prop) { if (sqlSessionFactory == null) { synchronized (MybatisSessionFactory.class) { if (sqlSessionFactory == null) { InputStream is = new ByteArrayInputStream(configStr.getBytes()); sqlSessionFactory = new SqlSessionFactoryBuilder().build(is, prop); } } } } public interface Action<RESULT, MAPPER> { RESULT action(MAPPER mapper); } public static <MAPPER, RESULT> RESULT query(Class<MAPPER> mapperClass, Action<RESULT, MAPPER> action) { if (sqlSessionFactory == null) { throw new NullPointerException("Mybatis未初始化"); } try (SqlSession sqlSession = sqlSessionFactory.openSession()) { MAPPER mapper = sqlSession.getMapper(mapperClass); return action.action(mapper); } } }
這個MybatisSessionFactory類,是在main方法中去初始化的,main方法中調用MybatisSessionFactory.init方法,傳入配置文件和配置參數,從而初始化SqlSesstionFactory。
改造的過程中,我們把main方法中調用MybatisSessionFactory.init方法給刪除瞭,導致SqlSesstionFactory未初始化。
為什麼不在main方法中調用MybatisSessionFactory.init,從而初始化SqlSesstionFactory?因為我希望通過Spring註入和管理SqlSesstionFactory的對象。
在static工具類方法裡調用Spring托管的bean對象[1]
這裡遇到一個問題,註意SqlSessionFactory聲明方式上用瞭static關鍵字。即這個屬性是類的,不是對象的。生命周期比較早,在類初始化時就會初始化。
private volatile static SqlSessionFactory sqlSessionFactory;
我使用下面的方式,在MybatisSessionFactory類中加入下面代碼,並在MybatisSessionFactory類上加註解@Component。
@Autowired private SqlSessionFactory sqlSessionFactory1; @PostConstruct public void update(){ sqlSessionFactory = sqlSessionFactory1; }
- 首先使用@Autowired註入SqlSessionFactory
- 使用@PostConstruct修飾update方法,方法名任意,不能有參數。這樣是為瞭保證這個順序:依賴註入之後,才執行update方法。該註解的方法在整個Bean初始化中的執行順序:Constructor(構造方法) -> @Autowired(依賴註入) -> @PostConstruct(註釋的方法)
RuleFunction類改造
還要改一個地方,初始化數據庫資源的入口方法是在RuleFunction類的構造函數中調用的。由於構造函數會先於依賴註入執行,需要把setFields和setExtInfo這兩個方法提取出來,且需要在依賴註入後執行。
修改成如下,並在RuleFunction類上加註解@Component。
改造前的執行流程
- main方法內部調用MybatisSessionFactory的init方法
- MybatisSessionFactory的init方法中new一個SqlSessionFactory
- RuleFunction初始化時,調用自身構造方法
- RuleFunction調用MybatisSessionFactory的query方法查詢數據庫
改造後的執行流程
- PulsarMain和MybatisSessionFactory是松耦合的,
- MybatisSessionFactory初始化時,因為通過@Autowired註解註入瞭SqlSessionFactory,所以需要初始化SqlSessionFactory,SqlSessionFactory初始化過程中會去使用配置文件中的數據庫連接參數初始化。
- MybatisSessionFactory初始化完成後,由於MybatisSessionFactory.update方法使用瞭@PostConstruct註解,會執行update方法,將SqlSessionFactory賦值給靜態屬性sqlSessionFactory。
- 後續RuleFunction的setFields方法執行過程中,就可以使用MybatisSessionFactory的query方法查詢數據庫瞭
總結
這次改造過程,對類加載過程、對象的實例化、static關鍵字、spring bean的生命周期有瞭更深入的理解。
- 類加載過程,會初始化調用static修飾的屬性、方法、代碼塊
- 類加載過程[2]:加載、鏈接、初始化
- 其中鏈接的過程:驗證、準備、解析
- 類初始化後,可以通過new關鍵字實例化一個對象,其它方式:通過反射api實例化
- spring bean的生命周期[3]:實例化、屬性賦值、初始化、銷毀
擴展
對於這個問題抽象一下:Spring項目中,如果需要在一個類初始化時加載數據庫資源,可以有哪些方式?
參考
- [1]靜態方法(工具類)中調用Spring管理的Bean
- [2]類加載過程
- [3]Spring Bean 的生命周期
- Markdown 基於 Mermaid 的時序圖、流程圖和甘特圖
到此這篇關於記一次Maven項目改造成SpringBoot項目的過程實踐的文章就介紹到這瞭,更多相關Maven改造成SpringBoot項目內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 解決SpringBoot整合MybatisPlus分模塊管理遇到的bug
- SpringBoot環境Druid數據源使用及特點
- springboot配置mybatis和事務管理方式
- springboot整合多數據源配置方式
- springboot多模塊化整合mybatis,mapper自動註入失敗問題及解決