【黑马程序员SpringBoot3+Vue3全套视频教程,springboot+vue企业级全栈开发从基础、实战到面试一套通关】 https://www.bilibili.com/video/BV14z4y1N7pg/?p=11&share_source=copy_web&vd_source=e05c15f0310aaccfeffcd5be6bc8a392
10 11 自动配置原理 自定义starter

记录

1.遵循约定大约配置的原则,在boot程序启动后,起步依赖中的一些bean对象会自动注入到ioc容器
之前从jar包中导入两个类,conuntry和province两个类都不是自动注入IoC容器的,而之前引入Mybatis的时候只要引入Mybatis的起步依赖就可以实现自动注入Bean对象
验证spring-boot-starter-web自动往IoC中注入DispatcherServlet
引入:04代码\后端代码\spring-code
如何引入模块见https://blog.csdn.net/weixin_47708672/article/details/160410082?fromshare=blogdetail&sharetype=blogdetail&sharerId=160410082&sharerefer=PC&sharesource=weixin_47708672&sharefrom=from_link

从@springbootApplication开始看,在SpringbootAutoConfigApplication(要素1)
ctrl+点击@springbootApplication注释
在这里插入图片描述
@ComponentScan是bean对象的扫描注解以及讲过
@SpringBootConfiguration

  • 是一个组合注解:
    在这里插入图片描述
  • 组合注解中包含@Configuration–说明启动类也是一个配置类
    @EnableAutoConfiguration(要素2)
  • 组合注解
    在这里插入图片描述
  • @Import({AutoConfigurationImportSelector.class})自动配置的重点(要素3)
    • @Import导入注解导入了一个AutoConfigurationImportSelector 类,之前有提过,import注解通常用来导入配置类和importSelector实现类
    • AutoConfigurationImportSelector 类实现了DeferredImportSelector接口
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered 
  • DeferredImportSelector接口继承了ImportSelector接口
public interface DeferredImportSelector extends ImportSelector {
  • 说明当前类AutoConfigurationImportSelector实现了ImportSelector接口,因此要重写SelectImport方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
       if (!this.isEnabled(annotationMetadata)) {
           return NO_IMPORTS;
       } else {
           AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
           return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
       }
   }
  • SelectImport方法会被SpringBoot自动调用从而得到它返回的全类名的字符串数组(要素4),从而将对应的类的bean对象注入到IoC容器中

  • 特别说明: AutoConfigurationImportSelector.class (要素3) 不是直接实现ImportSelector,而是实现DeferredImportSelector,DeferredImportSelector有另外一套很复杂的逻辑,因此重写的SelectImport方法不会被执行!但是通过这个方法去理解自动配置的原理是没有问题的

  • SelectImport方法的全类名不是写死的,因此SpringBoot也会去通过读取一个配置文件去获得这些方法名

  • AutoConfigurationImportSelector.class (要素3) 源码拆解:getAutoConfigurationEntry返回的对象转换成String后可以得到全类名**(要素4)**,说明这个对象应该知道配置文件的位置

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.<String>removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }
  • 返回的是一个新对象AutoConfigurationEntry,我们要找的是配置文件的位置,从名字知道configurations和配置相关而它最开是getCandidateConfigurations返回,那么追踪这个方法得到:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
  • load肯定是和加载相关,这里就先跟踪到此为止记住AutoConfiguration.class,
  • 看断言Assert:No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.这个就是配置文件的位置(要素5
  • 在pom中知道有个核心起步依赖spring-boot-starter,追踪
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>3.1.2</version>
      <scope>compile</scope>
    </dependency>

  • autoconfigure就是自动配置的意思,在外部库中找到autoconfigure
    在这里插入图片描述
    在这里插入图片描述
  • 配置文件的内容是很多类的全类名
    在这里插入图片描述

找到DispatcherServletAutoConfiguration要素6在这里插入图片描述
继续追踪
在这里插入图片描述
在这里插入图片描述

@AutoConfigureOrder(Integer.MIN_VALUE)
@AutoConfiguration(
    after = {ServletWebServerFactoryAutoConfiguration.class}
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({DispatcherServlet.class})
public class DispatcherServletAutoConfiguration {

  • 追踪AutoConfiguration注解–发现@configuration(如下)说明就DispatcherServletAutoConfiguration是一个自动配置类(要素6),这个类就是用于自动配置的,并且DispatcherServletAutoConfiguration上面还有一个注解**@ConditionalOnClass({DispatcherServlet.class})如果有DispatcherServlet.class,就自动注入一个Bean对象,如果环境中没有就不生效,DispatcherServlet.class存在性由之前@Import({AutoConfigurationImportSelector.class})(要素3)实现的DeferredImportSelector获取的全类名(要素4)决定
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(
    proxyBeanMethods = false
)
@AutoConfigureBefore
@AutoConfigureAfter
public @interface AutoConfiguration {

回到DispatcherServletAutoConfiguration 类内,也有一个DispatcherServletRegistrationConfiguration 配置类(要素7),有@Configuration说明也是配置类,这个类里面的方法是核心,在SpringBoot解析这个类的源码结构时发现它内部定义了 DispatcherServletConfiguration 类检查这个内部类是否有 @Configuration 注解如果有,将其作为配置类处理,解析这些类执行Bean方法,注意这里不是扫描包

    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({DispatcherServletRegistrationCondition.class})
    @ConditionalOnClass({ServletRegistration.class})
    @EnableConfigurationProperties({WebMvcProperties.class})
    @Import({DispatcherServletConfiguration.class})
    protected static class DispatcherServletRegistrationConfiguration {
	    @Bean(
	            name = {"dispatcherServletRegistration"}
	        )
        @ConditionalOnBean(
            value = {DispatcherServlet.class},
            name = {"dispatcherServlet"}
        )
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
            registration.setName("dispatcherServlet");
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            Objects.requireNonNull(registration);
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }
    }
  • 这个方法中new一个对象并返回让后加上了@Bean注解:,说明是在这里注入的Bean对象
  • 这个例子没有特别讲读取配置文件(要素8)
    在这里插入图片描述
    核心在于它把这个类写到核心的配置文件中了,因此只要调用了起步依赖就会调用这个类的方法
    在这里插入图片描述
    只要引入了web起步依赖,就会产生dispatcherServlet类,这个类就会生效,否则不生效(条件注册)
    **面试核心就是配置文件!
    提供一个自动配置类,然后把类名写到配置文件里面即可

2.自动配置类四要素与配置流程:

  • 启动类**(要素1)、@EnableAutoConfiguration (要素2)、整个模块的自动配置类(要素3)、全类名非实体(要素4)、imports文件(要素5)、imports导入自动配置类(要素6)、配置类实体(要素7),配置文件(要素8)**
  • 配置流程:
    • ① 启动类上的 @SpringBootApplication(要素1)
      这是一个组合注解,其中包含 @EnableAutoConfiguration。

    • @EnableAutoConfiguration(要素2) 通过 @Import(AutoConfigurationImportSelector.class)(要素3)得到全类名(要素4)
      该 ImportSelector 会读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件(要素5)

    • 注意,要素1-2-3-4的流程都是已经被 @SpringBootApplication写好了!

    • imports导入自动配置类(要素6) 被加载
      例如 RedisAutoConfiguration、DataSourceAutoConfiguration 。
      这些配置类内部通常会:
      @EnableConfigurationProperties(XxxProperties.class) → 绑定配置文件属性
      @ConditionalOnProperty / @ConditionalOnClass 等条件判断
      @Bean 方法创建组件(如 RedisTemplate)
      @Import(配置类)(要素7)–如果配置类不在这个类里的话

    • ④ 配置类(或具体的 @Bean 方法)(要素7)读取配置文件(要素8)
      通过 @ConfigurationProperties 注解的 XxxProperties Bean或者直接在 @Bean 方法中用 @Value / Environment


3.自动配置类实现

  • 实际上要实现自动配置需要的文件只有配置类(jar包提供)、自动配置类、imports文件并将自动配置类的全类名写入imports文件中即可完成自动配置
    (可以在延伸出一个配置文件,给配置类读取)
    导入02资料\03_自动配置\common-pojo-2.0-SNAPSHOT.jar
mvn install:install-file -Dfile=C:\Users\Administrator\Desktop\资料\03_自动配置\common-pojo-2.0-SNAPSHOT.jar -DgroupId=cn.itcast -DartifactId=common-pojo -Dversion=2.0 -Dpackaging=jar

在这里插入图片描述
在这里插入图片描述

自定义Starter

1.通常情况起步依赖由两个工程组成

  • XXXAutoConfiguration 自动配置
  • XXXStarter 依赖管理,在Starter中引入AutoConfiguration
    自定义starter需要实现这两个模块-文件-项目结构-±新建项目-建立两个maven archetype queckstart骨架,一个叫dmybatis-spring-boot-autoconfigure,一个叫dmybatis-spring-boot-starter
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    同上再建立dmybatis-spring-boot-starter
    先观察Mybatis,在Maven工具页面下
    在这里插入图片描述

项目dmybatis-spring-boot-autoconfigure下修改pom引入,注意修改父工程,
在这里插入图片描述
完整xml:

<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>org.rainsweet</groupId>
        <artifactId>Practice</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <groupId>org.rainsweet</groupId>
    <artifactId>dmybatis-spring-boot-autoconfigure</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>dmybatis-spring-boot-autoconfigure</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>3.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <version>3.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.11</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.0</version>
        </dependency>


        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

项目dmybatis-spring-boot-autoconfigure下新建包config,在这里插入图片描述
在**项目dmybatis-spring-boot-autoconfigure下(图片有误)**新建类MyBatisAutoConfig
在这里插入图片描述
添加注解@AutoConfiguration
在这里插入图片描述
技巧:当输入的类名为红色的时候可以尝试alt+Enter自动import
在这里插入图片描述
因为引入过jdbc的起步依赖,所以可以认到

        <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-jdbc</artifactId>
           <version>3.1.2</version>
       </dependency>

实现自动配置类

package org.rainsweet.config;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;
import java.util.List;

@AutoConfiguration //表示当前类是一个自动配置类,自动注入两个Bean对象
public class MyBatisAutoConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean
    //在方法签名中声明要用的对象SpringBoot会自动注入
    public MapperScannerConfigurer mapperScannerConfigurer(BeanFactory beanFactory){
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        //扫描的包 启动类所在的包机器子包--依靠SPringBoot的API在Spring-boot-starter中
        //返回一个由包名组成的集合
        List<String> packages = AutoConfigurationPackages.get(beanFactory);
        //获取第一个包名
        String p = packages.get(0);
        mapperScannerConfigurer.setBasePackage(p);
        //扫描的注解
        mapperScannerConfigurer.setAnnotationClass(Mapper.class);
        return mapperScannerConfigurer;
    }


}

将自动配置类写入imports文件:
查找文件名:
在这里插入图片描述
在main文件夹下新建目录resource/META-INF/spring,新建文件文件名要选择对应imports文件名
在这里插入图片描述

复制配置类全类名
在这里插入图片描述
在imports文件中粘贴
在这里插入图片描述
剩下的autoconfiguerAPP、test、gitingore可删除,因为不需要main方法和test
在这里插入图片描述


进入dmybatis-spring-boot-starter工程 依赖管理
引入dmybatis-spring-boot-autoconfigure依赖

        <dependency>
            <groupId>org.rainsweet</groupId>
            <artifactId>dmybatis-spring-boot-autoconfigure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

官方推荐将autoconfigure中的依赖同步复制到starter中一份,方便排除依赖的时候,直接在starter中排除

  • xxx-spring-boot-autoconfigure决定了“当什么条件满足时,就自动配置哪些 Bean”。这也是它叫 “autoconfigure” 的原因。
  • xxx-spring-boot-starter只做一件事——把 autoconfigure 包和其他必要的第三方依赖(比如数据库驱动、连接池等)打包在一起。这样用户只需要引入 starter 这一个依赖,就能得到全套功能
  • Spring Boot 的自动配置是“贪心”的。一个 Starter 为了满足大多数场景,可能会引入很多传递性依赖,比如,官方默认的 spring-boot-starter-web 会自动引入 Tomcat 作为 Web 服务器。但假如你的项目要求必须使用 Jetty 服务器时,你需要告诉 Maven/Gradle:“把那个自动带进来的依赖给我排除 (exclude) 掉。” 这种操作叫 排除依赖(Dependency Exclusion)。
  • 原本写:
       <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- 这里没写任何排除,说明 Tomcat 会被自动带进来 -->
</dependency>
  • 排除后:
<!-- 第一步:排除 Tomcat -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- 关键写法:exclusion 标签 -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 第二步:手动加入 Jetty -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
  • 排除多个依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!-- 排除 Tomcat -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
        <!-- 排除 Logback -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 手动加入 Jetty -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

<!-- 手动加入 Log4j2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

整个starter项目,没有写代码,src目录下的都可以删除,只留下pom
在这里插入图片描述

回到springboot-mybatis项目中,替换mybatis的起步依赖

        <!--mybatis的起步依赖-->
<!--        <dependency>-->
<!--            <groupId>org.mybatis.spring.boot</groupId>-->
<!--            <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!--            <version>3.0.0</version>-->
<!--        </dependency>-->

        <!--自定义的mybatis起步依赖-->
        <dependency>
            <groupId>org.rainsweet</groupId>
            <artifactId>dmybatis-spring-boot-starter</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

在AutoConfiguration和starter的pom中插入

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>21</source>
                    <target>21</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

保持编译的java版本一致的编译插件

更多推荐