暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

@SpringBootApplication 之 @EnableAutoConfiguration

TechStyle 2021-06-24
1296

原理

@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>


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 自定义注册的。




泰克风格 只讲干货 不弄玄虚

文章转载自TechStyle,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论