pring boot Starter和自动配置

经常会看见一些例如spring-cloud-starter-feign这种maven依赖,或者@EnableMongo注解,直接开始使用相关bean对象实现功能。那么这些东西是怎么做的,有哪几种方式去做。今天有空就梳理一下。

因为这里还是一个方法的总结归纳,原理解释还是得看参考资料:
【SpringBoot2.x】-自定义Spring boot Starter(原理、demo代码实现以及解决面试问题)

我总结了三种方式,三种方法有一些共同点,我提前说一下,后文中可以对照理解。

  1. 三种方式的重点基础都是 Import注解,这个注解是自动配置的基础
  2. 所有自动加载配置的类,都是要有@Configuration注解修饰的
  3. 所有自动加载配置的类上都可以使用@EnableConfigurationProperties注解加载属性文件,通过Condition相关的注解设置加载的条件,具体可以参见方法三

方法一:
使用SpringBootApplication,或者EnableAutoConfiguration注解。这个注解会去扫描所有JAR包中的spring.factories文件,加载配置在EnableAutoConfiguration属性后的类。

所以我们只需要在使用注解后,在spring.factories中定义要加载的类全路径名。例如 spring-boot-autoconfigure这个maven依赖中的spring.factories文件,这里的 \ 是为了换行后还能读取到属性,多个类使用 , 分隔

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,\

这种方式适合于你有一个比较完整的,复杂的,依赖较多的功能模块,通过二方包的形式提供给其他工程使用

那么这种方式的原理简单解释如下:
使用@Import加载EnableAutoConfigurationImportSelector类,在其父类AutoConfigurationImportSelector中,实现了ImportSelector接口

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
	String[] excludeName() default {};

}

ImportSelector#selectImports 返回了哪些类是需要被加载的,但是这些类是需要被@Configuration注解修饰的

public interface DeferredImportSelector extends 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);

}

在AutoConfigurationImportSelector#selectImports 方法中调用了如下方法,就是在这去加载spring.factories文件的,读取指定的属性EnableAutoConfiguration

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;
	}

方法二
方法二其实是从方法一演变而来的,方法一的本质是通过实现ImportSelector接口,返回需要自动夹杂的类全路径名,所以我们可以自定义一个实现ImportSelector接口的类。

例如spring-context中的@EnableCaching注解,先用Import注解加载CachingConfigurationSelector类。
CachingConfigurationSelector类实现了ImportSelector接口,在selectImports方法中返回了类全路径名。

方法二和方法一同样,适合于你有一个比较完整的,复杂的,依赖较多的功能模块,通过二方包的形式提供给其他工程使用

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
	boolean proxyTargetClass() default false;
	AdviceMode mode() default AdviceMode.PROXY;
	int order() default Ordered.LOWEST_PRECEDENCE;

}
@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null;
		}
	}

方法三:
该方法只加载单个被@Configuration注解修饰的类
例如@EnableMongo注解
通过Import注解,加载MongoAutoConfiguration类,同时MongoAutoConfiguration直接被@Configuration注解,类中定义了很多@Bean对象。

同时这些Bean对象依赖一个配置文件,配置文件通过@EnableConfigurationProperties定义。在配置文件中通过@ConfigurationProperties注解来定义配置属性的来源。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Import({MongoAutoConfiguration.class})
public @interface EnableMongo {
}
@Configuration
@EnableConfigurationProperties({MongoProperties.class})
public class MongoAutoConfiguration {
    @Autowired
    private MongoProperties mongoProperties;

    public MongoAutoConfiguration() {
    }

    @Bean
    public MongoClient mongo() {
        ……
    }

    @Bean
    public DB mongoDB(MongoClient mongo) {
		……
    }

    @Bean
    public MongoManager mongoManager(DB mongoDB) {
        return new MongoManager(mongoDB);
    }
}
@ConfigurationProperties(
    prefix = "mongo"
)
public class MongoProperties 

除此之外,我们还可以通过org.springframwork.boot.autoconfigure.condition的条件注解自定义在何种条件下加载不同的类,例如:当类路径下有HystrixCommand.class, HystrixFeign.class,feign.hystrix.enabled属性为true的时候加载HystrixFeign.builder

@Configuration
	@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
	protected static class HystrixFeignConfiguration {
		@Bean
		@Scope("prototype")
		@ConditionalOnMissingBean
		@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
		public Feign.Builder feignHystrixBuilder() {
			return HystrixFeign.builder();
		}
	}

条件注解如下

注解 说明
@ConditionalOnBean 当容器里有指定的Bean的条件下。
@ConditionalOnClass 当类路径下有指定的类的条件下。
@ConditionalOnExpression 基于SpEL表达式作为判断条件。
@ConditionalOnJava 基于JVM版本作为判断条件。
@ConditionalOnJndi 在JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean 当容器里没有指定Bean的情况下。
@ConditionalOnMissingClass 当类路径下没有指定的类的条件下。
@ConditionalOnNotWebApplication 当前项目不是Web项目的条件下。
@ConditionalOnProperty 指定的属性是否有指定的值。
@ConditionalOnResource 类路径是否有指定的值。
@ConditionalOnSingleCandidate 当指定Bean在容器中只有一个, 或者虽然有多个但是指定首选的Bean。
@ConditionalOnWebApplicatio 当前项目是Web项目的条件下。

你可能感兴趣的:(spring)