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方法。

springboot封装相应结果result springboot importselector_List


接下来我们看看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。