手写SpringBoot(三)之自动配置

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 启动user-service模块,并在MySpringApplication 中容器创建完成后,打开debug。

本节主要讲解SpringBoot的自动配置


回想一下,在用原生Spring aop功能的时候,需要哪些配置?


引入aspectjweaver jar包


定义切面


配置切面


开启aop功能


为什么在SpringBoot中,只需要引入spring-boot-starter-aop就可以直接使用aop功能?


那是因为SpringBoot定义了AOP配置类,帮我们完成了这些工作。


在spring-boot-auto-configure jar包中有很多SpringBoot帮我们配置好的类,其中就有AopAutoConfiguration,提供自动开启aop等功能


现在来写一个简易版的Springboot自动配置


当我们新增了AopConfiguration配置类后,需要在MySpringBootApplication中显示import该类,如果AutoConfiguration配置类很多的情况,就要在import标签上面引入很多类。


利用ImportSelector接口,可以将需要加载的类在selectImports方法中,返回该类的全限定名称。


public class MyImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[0];
    }
}

在该方法中,可以显示的将需要引入的配置类全路径全部加载一个数组中,然后返回,再通过@MySpringBootApplication Import 该MyImportSelector类就可以加载MyImportSelector中返回的类。


这样实现的弊端,


在代码中写死加载的类,需要新增时,则需要动代码。

无法通过外部配置来动态加载所需要的bean。

这里我们利用java spi机制来实现动态加载

Java SPI(Service Provider Interface)是Java官方提供的一种服务发现机制,它允许在运行时动态地加载实现特定接口的类,而不需要在代码中显式地指定该类,从而实现解耦和灵活性。


定义一个特定的接口类 AutoConfiguration

在resource文件夹下新建META-INF/services文件夹,在该services文件夹下新建AutoConfiguration全限定名的文件。

在AutoConfiguration全限定名的文件下配置需要加载的类全限定名称。

在MyImportSelector中实现加载java spi机制的类]

将@MySpringBootApplication 中的Import标签改为引入MyImportSelector

AutoConfiguration接口定义



package cn.axj.springboot.my.config;

/**
* 该类仅是一个标识作用
*/
public interface AutoConfiguration {
}

新建AutoConfiguration文件,cn.axj.springboot.my.config.AutoConfiguration, AutoConfiguration中配置如下


cn.axj.springboot.my.config.WebServerAutoConfiguration
cn.axj.springboot.my.config.AopAutoConfiguration

此时WebServerAutoConfiguration和AopAutoConfiguration需要实现AutoConfiguration


MyImportSelector实现

package cn.axj.springboot.my.config;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

public class MyImportSelector implements DeferredImportSelector {


    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        /**
         * 主要这里加载的是所有命名为cn.axj.springboot.my.config.AutoConfiguration的文件下的定义的类,不仅仅指该spring-boot jar包.
         * 其他jar包只要在resources目录下定义了AutoConfiguration的类,都会被加载进来。
         * 这里可以提供动态扩展能力。
         */
        ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class);
        List<String> list = new ArrayList<>();

        for (AutoConfiguration autoConfiguration : serviceLoader) {
            list.add(autoConfiguration.getClass().getName());
        }
        return list.toArray(new String[0]);
    }
}

MySpringBootApplication中改造

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@ComponentScan
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface MySpringBootApplication {
}

当前结构图



启动user-service模块,并在MySpringApplication 中容器创建完成后,打开debug。



可以看到AopAutoConfiguration 和 WebServerAutoConfiguration 均加载到了容器中。


当其他三方组件也想整合进springboot怎么办?由于其他组件无法获取到AutoConfiguration接口,所以需要将AutoConfiguration接口提出来,专门弄一个jar包供三方组件使用。


新建模块 my-spring-boot-configuration,并将AutoConfiguration类迁移过去。


在my-spring-boot模块中引入 my-spring-boot-configuration



项目结构如上


下面来构建mybatis 整合springboot的jar包


spring整合mybatis 主要是整合SqlSessionFactoryBean,将这个bean交给Spring管理


SqlSessionFactoryBean 主要设置DataSource,MapperLocation,ConfigLocaltion等信息。


新建my-mybatis-spring-boot-starter模块


引入依赖jar包



<?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>
    <parent>
        <groupId>cn.axj</groupId>
        <artifactId>spring-boot-base</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>my-mybatis-spring-boot-starter</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>

        <!-- MyBatis-Spring 整合包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.18</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.19</version>
        </dependency>

        <dependency>
            <groupId>cn.axj</groupId>
            <artifactId>my-spring-boot-configuration</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.18</version>
        </dependency>
    </dependencies>

</project>
  1. 创建自动配置的MybatisAutoConfiguration
package cn.axj.mybatis.springboot.config;

import cn.axj.springboot.my.config.AutoConfiguration;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.io.IOException;

@Configuration
public class MyBatisAutoConfiguration implements AutoConfiguration {

    @Bean
    public DataSource dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        druidDataSource.setUrl("jdbc:mysql://192.168.56.102:3306/springtest?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
        return druidDataSource;
    }


    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setDataSource(dataSource());
        sqlSessionFactoryBean.setConfigLocation(resourcePatternResolver.getResource("classpath:mybatis-config.xml"));
        sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("classpath:mapper/*Mapper.xml"));
        return sqlSessionFactoryBean;
    }
}

为了简便,将数据源这些信息写死,这些信息可以提供配置类,给用户使用。


  1. 在resources目录下创建mybatis-config.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>

  1. 创建自动配置的文件路径META-INF/services,并创建文件


cn.axj.springboot.my.config.AutoConfiguration,并将MyBatisAutoConfiguration的全限定名称配置进去


cn.axj.mybatis.springboot.config.MyBatisAutoConfiguration


下面开始测试


在user-service模块中引入my-mybatis-spring-boot-starter,并引入mysql连接包


<dependency>
      <groupId>cn.axj</groupId>
      <artifactId>my-mybatis-spring-boot-starter</artifactId>
      <version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.33</version>
</dependency>
  1. 创建Model包,并创建User
package cn.axj.user.model;

public class User {

    private Integer id;

    private String username;
  ...省略get set 方法
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" +
                username + '\'' +
                '}';
    }
}

  1. 创建mapper包,并新增UserMapper
package cn.axj.user.mapper;

import cn.axj.user.model.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    User selectById(Integer id);
}

  1. 在resources目录下创建mapper文件,并创建UserMapper.xml文件


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.axj.user.mapper.UserMapper">


    <select id="selectById" resultType="cn.axj.user.model.User">
        select id,username from user where id = #{id}
    </select>
</mapper>
  1. 改造UserService,引入UserMapper查询User
package cn.axj.user.service;

import cn.axj.user.mapper.UserMapper;
import cn.axj.user.model.User;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserService {
    @Resource
    UserMapper userMapper;
    public User test(Integer id){
        User user = userMapper.selectById(id);
        return user;
    }
}

  1. 改造TestController,查询返回User对象
package cn.axj.user.controller;

import cn.axj.user.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class TestController {
    @Resource
    private UserService userService;
    @GetMapping(value = "/test")
    public String test(Integer id){
        return userService.test(id).toString();
    }
}

这里由于没有配置HttpMessageConverter,无法将对象转换成字符串形式,所以用了toString()方法。


6.在UserApplication主类上,新增@MapperScan(“cn.axj.user.mapper”),扫描mapper文件。

这个问题,我在MyBatisAutoConfiguration中配置MapperScannerConfigurer,但是无法生效,mybatis不会去扫描响应的包,并生成代理对象,不知道什么原因,但是加在主类上就可以?难道mybatis将SqlSessionFactoryBean交给Spring容器后,就不自己扫描了吗?


项目结构图如下



至此,启动项目。这里记得引入默认的tomcat 容器,不然会无法启动。


通过浏览器访问 localhost:8080/test?id=1 或者 id = 2



可以看到整合mybatis成功,成功返回数据。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6月前
|
Java 容器 Spring
SpringBoot自动配置入门
SpringBoot自动配置入门
|
Java Spring
springboot的自动配置原理
springboot的自动配置原理
|
5月前
|
Java 应用服务中间件 容器
手写SpringBoot(二)之动态切换Servlet容器
我们在切换serlvet容器的时候,会将SpringBoot默认的tomcat jar包给排除掉,换上我们需要的jar包,比如jetty。
39 0
|
存储 Java
SpringBoot自动配置原理解析(三)
SpringBoot自动配置原理解析(三)
65 0
SpringBoot自动配置原理解析(三)
|
6月前
|
Java Spring 容器
SpringBoot的自动配置原理(一)
SpringBoot的自动配置原理(一)
|
6月前
|
Java 容器 Spring
Springboot自动配置原理
Springboot自动配置原理
|
Java 容器 Spring
10 SpringBoot自动配置原理
10 SpringBoot自动配置原理
58 0
|
XML Java Maven
【SpringBoot学习笔记 四】SpringBoot自动配置原理
【SpringBoot学习笔记 四】SpringBoot自动配置原理
96 0
|
前端开发 Java 数据库连接
SpringBoot入门到精通-SpringBoot自动配置原理(五)
SpringBoot入门到精通-SpringBoot自动配置原理
|
缓存 Java 数据库
手写SpringBoot启动器主要步骤
手写SpringBoot启动器主要步骤