美文网首页Java 程序员Java
记一次普通Maven项目改造成SpringBoot项目的过程

记一次普通Maven项目改造成SpringBoot项目的过程

作者: 马小莫QAQ | 来源:发表于2022-03-07 15:21 被阅读0次

    背景概述

    团队有一个项目,是maven构建的普通Java项目。

    项目没有使用spring,

    用到了mysql、mybatis,还有其他大数据技术,比如flink、pulsar。

    项目里连接数据库的部分,需要用到多个配置文件,一个是mybatis配置文件,一个是数据库配置文件。如果用SpringBoot可以简化为一个application.yml文件。

    项目里打包方式复杂,依赖一个maven-assemble的插件,打出的包是两个jar,出现过由于配置文件读取方式的错误,导致jar包还运行不了。使用这个插件打包,还需要写一个自定义的配置文件,配置各个资源打包的参数。如果用SpringBoot,直接引入spring-boot-maven-plugin,打出的就是可执行jar包,不需要繁琐的配置,不需要自己写读取配置的代码。

    为什么要改造成SpringBoot项目呢,因为SpringBoot

    1. 简化配置,不用写这么多配置文件
    2. 自动配置,引入starter依赖,可以自动把默认配置配好
    3. 内嵌web容器
    4. 自动版本管理,maven和starter配置使用
    5. 生态集成容易,如果项目想要集成另外的能力,引一些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;
    }
    
    1. 首先使用@Autowired注入SqlSessionFactory
    2. 使用@PostConstruct修饰update方法,方法名任意,不能有参数。这样是为了保证这个顺序:依赖注入之后,才执行update方法。该注解的方法在整个Bean初始化中的执行顺序:Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

    RuleFunction类改造

    还要改一个地方,初始化数据库资源的入口方法是在RuleFunction类的构造函数中调用的。由于构造函数会先于依赖注入执行,需要把setFields和setExtInfo这两个方法提取出来,且需要在依赖注入后执行。

    修改成如下,并在RuleFunction类上加注解@Component。

    改造前的执行流程

    PulsarMainMybatisSessionFactorySqlSessionFactoryRuleFunctionmaininitnewinitqueryqueryPulsarMainMybatisSessionFactorySqlSessionFactoryRuleFunction

    1. main方法内部调用MybatisSessionFactory的init方法
    2. MybatisSessionFactory的init方法中new一个SqlSessionFactory
    3. RuleFunction初始化时,调用自身构造方法
    4. RuleFunction调用MybatisSessionFactory的query方法查询数据库

    改造后的执行流程

    PulsarMainMybatisSessionFactoryRuleFunctionSqlSessionFactorymaininitinitinitupdatequeryqueryPulsarMainMybatisSessionFactoryRuleFunctionSqlSessionFactory

    1. PulsarMain和MybatisSessionFactory是松耦合的,
    2. MybatisSessionFactory初始化时,因为通过@Autowired注解注入了SqlSessionFactory,所以需要初始化SqlSessionFactory,SqlSessionFactory初始化过程中会去使用配置文件中的数据库连接参数初始化。
    3. MybatisSessionFactory初始化完成后,由于MybatisSessionFactory.update方法使用了@PostConstruct注解,会执行update方法,将SqlSessionFactory赋值给静态属性sqlSessionFactory。
    4. 后续RuleFunction的setFields方法执行过程中,就可以使用MybatisSessionFactory的query方法查询数据库了

    总结

    这次改造过程,对类加载过程、对象的实例化、static关键字、spring bean的生命周期有了更深入的理解。

    • 类加载过程,会初始化调用static修饰的属性、方法、代码块 类加载过程[2]:加载、链接、初始化 其中链接的过程:验证、准备、解析
    • 类初始化后,可以通过new关键字实例化一个对象,其它方式:通过反射api实例化
    • spring bean的生命周期[3]:实例化、属性赋值、初始化、销毁

    扩展

    对于这个问题抽象一下:Spring项目中,如果需要在一个类初始化时加载数据库资源,可以有哪些方式?

    作者:白云漂
    链接:https://juejin.cn/post/7071157320247902222
    来源:稀土掘金

    相关文章

      网友评论

        本文标题:记一次普通Maven项目改造成SpringBoot项目的过程

        本文链接:https://www.haomeiwen.com/subject/fklnrrtx.html