@SpringBootApplication 之 @EnableAutoConfiguration

原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/14917975.html

 

原理

@SpringBootApplication 是一个复合注解,包括

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

这3个注解的作用就是把项目工程中定义的Bean 注入到 Spring IoC 容器中。

 

@EnableAutoConfiguration 源码

Note: 

@Import 作用就是将指定的类实例注入到Spring IoC 容器中

 

@Import 分析

Project Directory

 

Maven Dependency

<?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>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>org.fool</groupId>
    <artifactId>hellospring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
View Code

 

SRC

MockUserBean.java

package org.fool.spring.imports;

public class MockUserBean {

}

 

MockUserService.java

package org.fool.spring.imports;

import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

@Component
@Import({MockUserBean.class})
public class MockUserService {

}

Note: 

@Import({MockUserBean.class})

 

MainTest.java

package org.fool.spring.imports;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MockUserService.class);
        MockUserBean mockUserBean = context.getBean(MockUserBean.class);
        MockUserService mockUserService = context.getBean(MockUserService.class);
        System.out.println(mockUserBean);
        System.out.println(mockUserService);
    }
}

 

Run

反之,如果将@Import 注释掉

再次运行MainTest.java,会抛出异常

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.fool.spring.imports.MockUserBean' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
    at org.fool.spring.imports.MainTest.main(MainTest.java:9)

 

AutoConfigurationImportSelector 分析

Note: 重点关注 DeferredImportSelector

Note: 可以看到DeferredImportSelector 继承了 ImportSelector 接口

Note: ImportSelector 接口定义了一个selectImports 方法,英文注释写的很明白,选择哪些classes 需要被注册到Spring IoC 容器中,一般会和 @Import 注解一起使用。

 

自定义实现类似 @EnableAutoConfiguration 的注解

Project Directory

 

SRC

MockRoleBean.java

package org.fool.spring.selector;

public class MockRoleBean {
}

 

MockUserBean.java

package org.fool.spring.selector;

public class MockUserBean {
}

 

MockImportSelector.java

package org.fool.spring.selector;

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

public class MockImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"org.fool.spring.selector.MockUserBean", "org.fool.spring.selector.MockRoleBean"};
    }
}

Note: MockImportSelector.java 实现了 ImportSelector 接口

 

EnableMockConfig.java

package org.fool.spring.selector;

import org.springframework.context.annotation.Import;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(MockImportSelector.class)
public @interface EnableMockConfig {
}

Note:@Import 注解和 MockImportSelector 一起使用

 

MockConfig.java

package org.fool.spring.selector;

@EnableMockConfig
public class MockConfig {
}

Note:MockConfig 使用了 @EnableMockConfig 注解

 

MainTest.java

package org.fool.spring.selector;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MockConfig.class);
        MockUserBean mockUserBean = context.getBean(MockUserBean.class);
        MockRoleBean mockRoleBean = context.getBean(MockRoleBean.class);
        System.out.println(mockUserBean);
        System.out.println(mockRoleBean);
    }
}

 

Run

 

深度剖析 @EnableAutoConfiguration

Debug 运行 Application.java

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

Debug Step1:选择要注册到Spring IoC 容器的classes

Debug Step2:

Debug Step3:

Debug Step4:

Debug Step5:

Debug Step6:spring.factories 是SpringBoot 的解耦扩展机制,这种机制实际上是仿照了Java SPI 扩展机制来实现的

Debug Step7:执行完getCandidateConfigurations 方法后,可以看到在spring.factories 中的需要被装载到Spring IoC 容器中的127个classes 

 

自定义实现 spring.factories

Project Directory

 

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.fool.spring.factory.AppConfig

 

SRC

App.java

package org.fool.spring.factory;

public class App {
    public String info() {
        return "app desc";
    }
}

AppConfig.java

package org.fool.spring.factory;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public App app() {
        return new App();
    }
}

Application.java

package org.fool.core;

import org.fool.spring.factory.App;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        App app = context.getBean(App.class);
        System.out.println(app.info());
    }
}

 

Debug Run

Note:自定义的AppConfig 已经被装载到Spring IoC 容器中

查看最后的启动结果

Note:

AppConfig.java 是在 org.fool.spring.factory 包下,而标注了@SpringBootApplication 注解的 Application.java 是在 org.fool.core 包下,两个类属于不同包。

所以,如果SpringBoot 装载不在 标注了@SpringBootApplication 注解的启动类所在包及其子包目录的classes,需要在 META-INF/spring.factories 自定义注册,否则抛异常。

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.fool.spring.factory.App' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
    at org.fool.core.Application.main(Application.java:12)

反之,如果AppConfig.java 和 标注了@SpringBootApplication 注解的 Application.java 是在同一个包目录或者在其子包目录,是不需要在 META-INF/spring.factories 自定义注册的。

 


欢迎点赞关注和收藏

 

posted @ 2021-06-24 11:29  李白与酒  阅读(199)  评论(0编辑  收藏  举报