SpringBoot自动装载机制
简单记录下springboot是如何隐式帮我们加载bean的
文章目录
- SpringBoot自动装载机制
- 一、ImportSelector
- 二、Spring 调用链
- 1.调用链
- 三、springBoot自动装载
- 四、Conditional
- 五、Spring Conditional
- 总结
提示:以下是本篇文章正文内容,下面案例可供参考
一、ImportSelector
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
ImportSelector接口只定义了一个selectImports(),用于指定需要注册为bean的Class名称。当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean
DeferredImportSelector接口继承ImportSelector,他和ImportSelector的区别在于装载bean的时机上,DeferredImportSelector需要等所有的@Configuration都执行完毕后才会进行装载
/**
* 定义一个configuration ,注意这里并没有使用@Configuration注解,spring扫描的时候并不会装载该类
*/
public class HelloWorldConfiguration {
@Bean
public String helloWorld() {
return "Hello,World 2018";
}
}
/**
* {@link ImportSelector} 实现
*/
public class HelloWorldImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//返回上面定义的Configuration类全称
return new String[]{HelloWorldConfiguration.class.getName()};
}
}
/**
* 定义一个注解通过@Import引入ImportSelector实现类HelloWorldImportSelector
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}
/**
* 测试类,引入上面的注解@EnableHelloWorld
**/
@EnableHelloWorld
public class EnableHelloWorldBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// helloWorld Bean 是否存在
String helloWorld =
context.getBean("helloWorld", String.class);
System.out.println("helloWorld Bean : " + helloWorld);
// 关闭上下文
context.close();
}
}
从上面的例子可以看出,我们并没有使用@Component、@Service这样的注解 而是使用编程的方式动态的载入bean
二、Spring 调用链
1.调用链
由于跳转过程过于复杂,所以下面罗列了各个方法之间的调用关系,从我们最熟悉的refresh动作开始
AbstractApplicationContext
(refresh -> invokeBeanFactoryPostProcessors)PostProcessorRegistrationDelegate
(invokeBeanFactoryPostProcessors -> invokeBeanDefinitionRegistryPostProcessors)ConfigurationClassParser
(postProcessBeanDefinitionRegistry -> processConfigBeanDefinitions -> parse -> parse -> processConfigurationClass -> doProcessConfigurationClass -> processImports)
三、springBoot自动装载
接下来来看看springboot自动装载过程
/**
* 一个非常普通的启动类,在类上引入@SpringBootApplication注解
**/
@SpringBootApplication
public class SpringBootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTestApplication.class, args);
System.out.println("启动啦~~~~~~~~~~~~~~");
}
}
/**
* @SpringBootApplication注解是一个功能集合,请注意@EnableAutoConfiguration
**/
@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 {
}
/**
* 可以看出似曾相识的部分@Import(AutoConfigurationImportSelector.class)
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
接着我们查看AutoConfigurationImportSelector具体实现逻辑
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//获取相关的配置信息
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
configurations = this.sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return (String[])configurations.toArray(new String[configurations.size()]);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
//这里看到非常熟悉的spring.factories配置文件。
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
//相关的映射信息都从 META-INF/spring.factories 读取
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
利用了SPI的思想,springboot 会自动加载spring.factories文件,解析为一个个BeanDefinition对象。当然很多时候,springboot 会根据当前是否有引用jar包而选择性的对某些类进行实例化,这是怎么做到的呢。spring 为我们提供了Conditional的接口,来解决这些依赖问题。
四、Conditional
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
直接来个小例子,大家感受下
public class LocalServerCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
public class RemoteServerCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
public interface OutputService {
public void sayHello();
}
public class LocalService implements OutputService {
@Override
public void sayHello() {
System.out.println("local Service");
}
}
public class RemoteService implements OutputService {
@Override
public void sayHello() {
System.out.println("remote Service");
}
}
@Configuration
public class OutputConfig {
@Bean
@Conditional(LocalServerCondition.class)
public OutputService localService() {
return new LocalService();
}
@Bean
@Conditional(RemoteServerCondition.class)
public OutputService remoteService() {
return new RemoteService();
}
}
@Test
public void test() {
applicationContext = new AnnotationConfigApplicationContext("com.test");
applicationContext.getBean(OutputService.class).sayHello();
}
输出为:remote Service
五、Spring Conditional
Springboot 为我们提供许多conditional实现类,不需要我们再去重写match方法。
接下来我们看看JPA是如何自动引入的
pom.xml springboot 自动启动关键
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>1.5.22.RELEASE</version>
</parent>
在该jar包下,找到spring.factories文件
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
@Configuration
@ConditionalOnBean({DataSource.class})
@ConditionalOnClass({JpaRepository.class})
@ConditionalOnMissingBean({JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class})
@ConditionalOnProperty(
prefix = "spring.data.jpa.repositories",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
@Import({JpaRepositoriesAutoConfigureRegistrar.class})
@AutoConfigureAfter({HibernateJpaAutoConfiguration.class})
public class JpaRepositoriesAutoConfiguration {
public JpaRepositoriesAutoConfiguration() {
}
}
可以看到有许多Conditional的要求,我们看下JpaRepository 这个接口,其实如果玩过JPA框架的应该都知道,我们对数据进行操作时,是通过某个接口(继承JpaRepository ),那么当我们项目有bean继承自JpaRepository,则条件成立。
总结
实际上,我们有多种方式可以注册beandefinition对象 例如项目内的类,可以使用@Component进行注册 第三方的jar包可以使用 @Bean注解和@Import注解进行注册。 当我们需要大批量取引入bean 时可以,自定义ImportSelector,重写selectImport来实现批量导入。spring boot有很多EnableXXX的注解,绝大多数多借助了ImportSelector和ImportBeanDefinitionRegistrar。