zoukankan      html  css  js  c++  java
  • @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 自定义注册的。


    欢迎点赞关注和收藏

    强者自救 圣者渡人
  • 相关阅读:
    MFC listcontrol 分列 添加行数据 点击列头排序
    MFC 设置控件事件对应的函数
    MFC CString to char* (Visual Studio 2015 亲测可用)
    MFC MessageBox AfxMessageBox
    iOS 注意事项
    iOS instancetype or id ?
    iOS Aspect Fit,Aspect Fill,Scale To Fill
    iOS UIImageView用代码添加点击事件
    iOS 安装Cocoapods以及安装第三方库的操作流程
    iOS 获取当前用户的用户路径并写入文件
  • 原文地址:https://www.cnblogs.com/agilestyle/p/14917975.html
Copyright © 2011-2022 走看看