SpringBoot自动装配原理及自定义start开发

Posted 愉悦滴帮主)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot自动装配原理及自定义start开发相关的知识,希望对你有一定的参考价值。

SpringBoot自动装配原理及自定义start开发

前言:

       大部分互联网公司都是使用SpringBoot,或SpringCloud作为开发框架。在我们开发中,很可能遇到要开发公共组件提供给其他人调用的场景。随着项目版本的迭代,很可能把公共组件打成jar包。我们在自己的业务类中通过使用@Autowired注解来调用jar包中的公共类,公共方法等。但是如果公共给组件要作升级,也就是二次开发,导致以前定义的一些类名,方法名等发生改变,那么也要更改对应业务代码所依赖jar包的相关代码。这就会造成对jar包的强依赖性。以上问题就会对公共组件的使用是否便捷,维护起来是否方便都有很高的要求。我们期望我们开发的公共组件的jar包,能够直接依赖到我们业务项目中的pom文件里面,而公共组件的版本迭代不影响我们的业务代码。这就运用到了SpringBoot的自动装配。

       在我们用Spring、SpringMVC(也就是Web框架)的时候通常都会配置web.xml、applicationContext.xml等配置文件,用Mybaties的时候通常也会配置SqlSessionFactory。但是SpringBoot并没有Web框架,和Mybaties等等这些功能,那么SpringBoot又是如何简化掉这些配置文件的呢?这就需要了解下面要讲的SpringBoot的自动装配。


SpringBoot可以省略版本号的原因:

为什么我们用SpringBoot时,通过在pom文件中加上下面的依赖(parent)就可以省略其他依赖的版本号呢?

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
<!--            <version>2.5.1</version>-->
        </dependency>

我们点开 <artifactId>spring-boot-starter-parent</artifactId>这个依赖,发现如下依赖,不难理解就是管理依赖的意思。

//定义了核心依赖包的版本号  
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.5.1</version>
  </parent>

 我们继续点开 <artifactId>spring-boot-dependencies</artifactId>,发现如下的pom文件,其中<dependencyManagement>标签就是用来管理依赖的作用。其实SpringBoot的父级工程已经帮我们把很大一部分的依赖都依赖好了,例如apache的activeMq,redis等等。所以我们再用的时候只需要依赖最外层的包就行。我们在开发中通常会遇到在导入pom依赖的时候经常因为各种依赖包的版本不匹配,依赖包冲突这样的问题。SpringBoot这么作的好处就是帮助我们进行版本以及依赖包的管理,防止版本号不匹配,依赖冲突这样问题的产生。

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-amqp</artifactId>
        <version>${activemq.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-blueprint</artifactId>
        <version>${activemq.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-broker</artifactId>
        <version>${activemq.version}</version>
      </dependency>
</dependencyManagement>
//省略部分代码,关键是dependencyManagement标签

application.yml(.yaml、.properties)文件如何引用的

我们知道SpringBoot默认使用的是Tomcat提供Web服务,但是如果我们想到替换成 jetty,undertow。就可以通过配置application.yml(.yaml、.properties)文件来进行配置覆盖。

那么SpringBoot如何把 .yml文件依赖进来呢?我们通过点开<artifactId>spring-boot-starter-parent</artifactId>这个依赖就会发现spring-boot-starter-parent这个依赖通过<resource>这个标签将.yml文件注入进来了。如下:

//加载资源文件    
 <resource>
        <directory>${basedir}/src/main/resources</directory>
        <filtering>true</filtering>
        <includes>
          <include>**/application*.yml</include>
          <include>**/application*.yaml</include>
          <include>**/application*.properties</include>
        </includes>
      </resource>

SpringBoot自动装配之@SpringBootApplication注解

@SpringBootApplication注解通常作为启动类的注解,大家都不陌生。而SpringBoot自动装配主要基于@SpringBootApplication注解。

这是一个启动类:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

 我们点开@SpringBootApplication注解。发现如下代码:

@SpringBootApplication注解是一个复合注解

者四个注解属于Java的元注解。元注解:修饰自定义注解的注解

@Target({ElementType.TYPE})    //定义注解的作用范围 (类、方法、属性)
@Retention(RetentionPolicy.RUNTIME)  //定义注解的生命周期。(注解保留多久,是在编译期起作用或者运行期起作用)
@Documented //Javadoc     可以配合@param,@see(超链接)注解使用
@Inherited //被它修饰的自定义注解可被子类继承

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
  .....省略
}

 显然上面的四个注解与SpringBoot的自动装配关系不大。


自动装配之@SpringBootConfiguration 

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

 我们发现@SpringBootConfiguration注解中只有一个默认属性,我们点开SpringBootConfiguration 注解上的@Configuration注解发现有同样的默认属性。也就是说@SpringBootConfiguration注解相当于@Configuration注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

  boolean proxyBeanMethods() default true这个属性是默认使用CGLIB动态代理。如果@Configuration这个属性值为false那么就与@Component注解起到一样的作用。

@Configuration修饰的类相当于一个配置类(可以理解为 .xml文件)。@Configuration注解的作用就是保证bean的唯一性,而@Component注解不能保证。也就是说在用@Configuration注解修饰的类中获取到的bean是唯一的。

注意:@Configuration注解的原因我们能够在启动类里面能够定义bean,而不用去单独写一个配置类。


自动装配之@EnableAutoConfiguration

我们点开@EnableAutoConfiguration注解发现,有两个注解对自动装配起到作用,分别是:@AutoConfigurationPackage、@Import

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})  //导入参数类到IOC容器
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

1.  @Import({AutoConfigurationImportSelector.class})注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

参数如果是ImportSelect的实现类,注册selectImports返回的数组到IOC容器(类的全路径),也就是注册类。批量注册

 public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

 点开AutoConfigurationImportSelector

 protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            //获取注解属性
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //从META-INF/spring.factories加载EnableAutoConfiguration类            
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
           //去重
            configurations = this.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 AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

 protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

点开loadFactoryNames

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

发现路径:META-INF/spring.factories 

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();
            //发现路径:META-INF/spring.factories
            try {
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");

               ....后面省略
    }

 我们找到autoconfigure这个包,打开spring.factories文件,如下图:

 我们在spring.factories选择org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration打开发现DispatcherServletRegistrationConfiguration 上面有一个@bean标签。所以SpringBoot就是通过这种形式把DispatcherServletRegistrationConfiguration加载到IOC容器里面。

@Conditional({DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition.class})
    @ConditionalOnClass({ServletRegistration.class})
    @EnableConfigurationProperties({WebMvcProperties.class})
    @Import({DispatcherServletAutoConfiguration.DispatcherServletConfiguration.class})
    protected static class DispatcherServletRegistrationConfiguration {
        protected 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());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }
    }

2. @AutoConfigurationPackage注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class}) //保存扫描路径,提供给Spring-data-jpa查询     @Entity
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

@Import({Registrar.class}) //保存扫描路径,提供给Spring-data-jpa查询     @Entity

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }

 总结:@Import是SpringBoot自动装配的核心注解。

工作流程

  • 参数如果是ImportBeanDefinitionRegistrar的实现类,支持手工注册bean
  • 参数如果是ImportSelect的实现类,注册selectImports返回的数组到IOC容器(类的全路径),也就是注册类。批量注册
  • 参数类如果是普通类,将该类实例化交给IOC容器管理(除开上面两种都是普通类)

以上是关于SpringBoot自动装配原理及自定义start开发的主要内容,如果未能解决你的问题,请参考以下文章

从源码中理解Spring Boot自动装配原理

SpringBoot的自动装配原理(含例子和源码分析)

SpringBoot——SpringBoot四大核心之自动装配(源码解析)

SpringBoot——SpringBoot四大核心之自动装配(源码解析)

掌握了SpringBoot的自动装配原理后你会发现自定义Starter也是非常容易的哦!

掌握了SpringBoot的自动装配原理后你会发现自定义Starter也是非常容易的哦!