在前面两篇文章1.1Spring @Enable 模块装配、1.2spring条件装配我们大致了解了一下在springframwork中对Bean是如何装配的有所了解了。我们这篇文章来看下对于颠覆Java开发方式的springboot是如何做到如此令人发指的简洁的。
重新认识@SpringBootApplication注解
springboot之所以能做到如此简洁开发,他遵循了一个原则,规约大于配置。这个原则下,springboot工程会自动帮我们装载很多Bean。
springboot工程我们一般会打上这么一个注解@SpringBootApplication。我们来看下这个注解到底做了什么能让我们开发简化。我们看下源码:
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
/**
...
* @since 1.2.0
*/
@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 {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
从源码中我们可以知晓他是一个组合注解,我们重点关注一下@EnableAutoConfiguration注解,让我们继续往下看,@EnableAutoConfiguration源码:
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.SpringFactoriesLoader;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
是不是很熟悉,原来他也是通过选择器来加载需要的Bean。我们稍后来看下这个AutoConfigurationImportSelector类的源码。
源码看了一部分,我们能不能自己来实现自动装配一个自定义的类呢?此处先给出代码实现,稍后我们通过debug的方式来看下具体内部逻辑。
前面我们模拟实现了MyEnableEurekaServer,我们在此基础上做些补充。
先建立一个引导类EnableAutoConfigurationBootstrap:注意打上@EnableAutoConfiguration注解
package com.wangming.bootstrap;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
@EnableAutoConfiguration
public class EnableAutoConfigurationBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// helloWorld Bean 是否存在
String hello =
context.getBean("hello", String.class);
System.out.println("hello Bean : " + hello);
// 关闭上下文
context.close();
}
}
建立一个MyEurekaServerMarkerAutoConfiguration类:
package com.wangming.config;
import com.wangming.annotation.MyEnableEurekaServer;
import com.wangming.condition.MyConditionOnProperty;
import org.springframework.context.annotation.Configuration;
/**
* 模拟EurekaServerMarkerConfiguration
*/
@Configuration
@MyEnableEurekaServer
@MyConditionOnProperty(prefix = "pass")
public class MyEurekaServerMarkerAutoConfiguration {
}
此处为了演示,加上了三个注解分别是模式注解,@Enable模块装配和条件装配。
接下来我们需要在resource目录下建立META-INF/spring.factories目录和文件。
# 自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wangming.config.MyEurekaServerMarkerAutoConfiguration
我们运行一下引导类,发现hello Bean已经被装配进来了。
我们把这个思路理一下,在EnableAutoConfigurationBootstrap引导类中打上了@EnableAutoConfiguration注解,在启动时springboot会去resource/META-INF目录下寻找spring.factories文件,该文件内容是以key-value的形式保存的要加载的类。此处key为org.springframework.boot.autoconfigure.EnableAutoConfiguration即@EnableAutoConfiguration注解,value为com.wangming.config.MyEurekaServerMarkerAutoConfiguration。
很明显这个我们自定义的类中通过springframwork中的三种装配方式(此处是为了演示)将Bean装配进容器。
大致思路就是这样,我们来debug一下验证一下我们的猜想。我们回到之前遗留的一个疑问上面,EnableAutoConfiguration注解类中AutoConfigurationImportSelector这个类,我们看下这个类的源码:
package org.springframework.boot.autoconfigure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
* auto-configuration}. This class can also be subclassed if a custom variant of
* {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Madhura Bhave
* @since 1.3.0
* @see EnableAutoConfiguration
*/
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
...
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}
...
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
...
直接看debug截图:
image.png
我们发现我们自定义的类MyEurekaServerMarkerAutoConfiguration被装载了进来。至于为甚么这个集合有这么多bean,其实我们可以在spring-boot-autoconfigure
网友评论