今天我们来说说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,今天就聊到这里。