关于java:原创Spring-Boot一口气说自动装配与案例

5次阅读

共计 7232 个字符,预计需要花费 19 分钟才能阅读完成。

关注Java 后端技术全栈”**

回复“面试”获取全套大厂面试材料

明天咱们来说说 Spring Boot 的外围——主动拆卸原理。

大家都记得咱们 SpringBoot 我的项目有个启动类 XXApplication.java 类。上面就是启动类:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);
    }
}

其中这里有外围注解 @SpringBootApplication

注解 @SpringBootApplication

其源码

@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 {// 先省略}

它次要加载了 @SpringBootApplication 注解主配置类,这个 @SpringBootApplication 注解主配置类里边最次要的性能就是 SpringBoot 开启了一个 @EnableAutoConfiguration 注解的主动配置性能。

这外面的重要关注的三个注解:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

注解 @EnableAutoConfiguration:

翻译过去就是开启主动配置,源码如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {// 省略}

它次要利用了一个 EnableAutoConfigurationImportSelector 选择器给 Spring 容器中来导入一些组件。其中的要害性能由 @Import 提供,其导入的 AutoConfigurationImportSelector 的 selectImports()办法通过 SpringFactoriesLoader.loadFactoryNames() 扫描所有具备 META-INF/spring.factories 的 jar 包。spring-boot-autoconfigure-x.x.x.x.jar 里就有一个这样的 spring.factories 文件。

这个 spring.factories 文件也是一组一组的 key=value 的模式,其中一个 key 是 EnableAutoConfiguration 类的全类名,而它的 value 是一个 xxxxAutoConfiguration 的类名的列表,这些类名以逗号分隔,如下图所示:

再来看看 spring.factories 的内容

每一个 xxxAutoConfiguration 类都是容器中的一个组件,并都退出到容器中。退出到容器中之后的作用就是用它们来做主动配置,这就是 Springboot 主动配置之源,也就是主动配置的开始,只有这些主动配置类进入到容器中当前,接下来这个主动配置类才开始进行启动。

这个 @EnableAutoConfiguration 注解通过 @SpringBootApplication 被间接的标记在了 Spring Boot 的启动类上。在 SpringApplication.run(…)的外部就会执行 selectImports()办法,找到所有 JavaConfig 主动配置类的全限定名对应的 class,而后将所有主动配置类加载到 Spring 容器中。

先看 EnableAutoConfigurationImportSelector 的类图

重要办法

protected Collection<String> loadFactoryNames(Class<?> source) {
        return SpringFactoriesLoader.loadFactoryNames(source,
                getClass().getClassLoader());
}
public final class SpringFactoriesLoader {
    // 读取配置文件 META-INF 下的 spring.factories
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {return result;}
        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
}

读取进去后如何使其失效呢?

主动配置失效

每一个 XxxxAutoConfiguration 主动配置类都是在某些条件之下才会失效的,这些条件的限度在 Spring Boot 中以注解的模式体现,常见的 条件注解 有如下几项:

@ConditionalOnBean:当容器里有指定的 bean 的条件下。

@ConditionalOnMissingBean:当容器里不存在指定 bean 的条件下。

@ConditionalOnClass:当类门路下有指定类的条件下。

@ConditionalOnMissingClass:当类门路下不存在指定类的条件下。

@ConditionalOnProperty:指定的属性是否有指定的值,比方 @ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当 xxx.xxx 为 enable 时条件的布尔值为 true,如果没有设置的状况下也为 true。

以 ServletWebServerFactoryAutoConfiguration 配置类为例,解释一下全局配置文件中的属性如何失效,比方:server.port=8081,是如何失效的(当然不配置也会有默认值,这个默认值来自于 org.apache.catalina.startup.Tomcat)。

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {return new ServletWebServerFactoryCustomizer(serverProperties);
    }
    @Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }
// 省略
}

这里看到一个 tomcat servlet 相干的类了。

public class TomcatServletWebServerFactoryCustomizer
        implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {
    private final ServerProperties serverProperties;
    public TomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {this.serverProperties = serverProperties;}
// 省略
}

再进 serverProperties 中,终于发现一个相熟的注解 @ConfigurationProperties 了,读取配置文件中 server 为前缀的配置项。

ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
    private Integer port;
    private InetAddress address;
    // 省略
}

@EnableConfigurationProperties 负责导入这个曾经绑定了属性的 bean 到 spring 容器中(见下面截图)。那么所有其余的和这个类相干的属性都能够在全局配置文件中定义,也就是说,真正“限度”咱们能够在全局配置文件中配置哪些属性的类就是这些 XxxxProperties 类,它与配置文件中定义的 prefix 关键字结尾的一组属性是惟一对应的。

至此,咱们大抵能够理解。在全局配置的属性如:server.port 等,通过 @ConfigurationProperties 注解,绑定到对应的 XxxxProperties 配置实体类上封装为一个 bean,而后再通过 @EnableConfigurationProperties 注解导入到 Spring 容器中。

而诸多的 XxxxAutoConfiguration 主动配置类,就是 Spring 容器的 JavaConfig 模式,作用就是为 Spring 容器导入 bean,而所有导入的 bean 所须要的属性都通过 xxxxProperties 的 bean 来取得。

借用网上一张图来回顾整个流程:

解锁面试被问

面试官可能看到你简历上写着把握 SpringBoot,通常都会让你说说 Spring Boot 主动拆卸原理。如果你能把下面过程中的源码讲一遍给他听,那是很 NB 了。然而通常只有如下这样答复就能够了。

Spring Boot 启动的时候会通过 @EnableAutoConfiguration 注解找到 META-INF/spring.factories 配置文件中的所有主动配置类,并对其进行加载,而这些主动配置类都是以 AutoConfiguration 结尾来命名的,它实际上就是一个 JavaConfig 模式的 Spring 容器配置类,它能通过以 Properties 结尾命名的类中获得在全局配置文件中配置的属性如:server.port,而 XxxxProperties 类是通过 @ConfigurationProperties 注解与全局配置文件中对应的属性进行绑定的。

ok,明天就聊到这里。

码字不易,点个在看 + 分享朋友圈呗!!!

举荐浏览

如何优雅的导出 Excel

终于明确为什么要加 final 关键字了!

正文完
 0