信息发布→ 登录 注册 退出

记一次Maven项目改造成SpringBoot项目的过程实践

发布时间:2026-01-11

点击量:
目录
  • 背景概述
  • 过程
    • 加依赖
    • 修改main方法所在类
    • 引入数据库依赖
    • 启动类上加入mapper扫描
    • 添加application.yml
    • sqlSessionFactory空指针异常分析
    • 改造MybatisSessionFactory类
  • 改造前的执行流程
    • 改造后的执行流程
      • 总结
        • 扩展
          • 参考

            背景概述

            团队有一个项目,是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 的时序图、流程图和甘特图
            在线客服
            服务热线

            服务热线

            4008888355

            微信咨询
            二维码
            返回顶部
            ×二维码

            截屏,微信识别二维码

            打开微信

            微信号已复制,请打开微信添加咨询详情!