SpringBoot运行原理和自动配置

SpringBoot核心注解

SpringBoot最简单的入口类:

@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

其中最重要的就是@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 {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

主要是组合了以下3个注解@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。

@ComponentScan注解作用

ComponentScan的意思是Component扫描包,它可以指定包扫描的根路径,让 SpringFramework 来扫描指定包及子包下的组件,也可以不指定路径,默认扫描当前配置类所在包及子包里的所有bean组件,这也是为什么 SpringBoot 的启动类要放到所有类所在包的最外层。从上面的源码中可以看出,@ComponentScan显示的指定了两个过滤条件。TypeExcludeFilter和AutoConfigurationExcludeFilter过滤条件。对于@ComponentScan注解来说,指定扫描那些包,除了value属性之外,还有两个重要的属性excludeFilters和includeFilters,分别用来指定扫描的时候按照什么规则排除那些组件和只需要包含哪些组件。类型是Filter[],这个Filter是注解的Filter,主要属性有FilterType和classes

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

	@AliasFor("basePackages")
	String[] value() default {};

	@AliasFor("value")
	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};
	
	Filter[] includeFilters() default {};

	Filter[] excludeFilters() default {};
	}
}

    @Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {

		FilterType type() default FilterType.ANNOTATION;

		@AliasFor("classes")
		Class<?>[] value() default {};

		@AliasFor("value")
		Class<?>[] classes() default {};

		String[] pattern() default {};

	}

FilterType主要包括:

public enum FilterType {
	ANNOTATION, //按照注解
	ASSIGNABLE_TYPE,//按照给定的类型
	ASPECTJ,//按照ASPECTJ表达式
	REGEX,//按照REGEX正则表达式
    /** Filter candidates using a given custom
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 */
	CUSTOM//自定义规则,实现TypeFilter
}

一般比较常用的有注解,类型和自定义规则,自定义规则需要实现TypeFilter接口,在执行包扫描的时候,按照这个includeFilters包含过滤器过滤,自定义的MyTypeFilter中,返回true的组件会被自动注册成Bean,而不需要任何注解,如有UserService(未添加注解)的情况下也能变成Spring容器的Bean,在默认的情况下是按照注解进行扫描包的,凡是标志了@Component相关的注解即可被扫描,并且注册成Bean,这也是为什么添加注解能注册成Bean.

@ComponentScan(value = "com.test", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class), //扫描所有Controller
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = UserService.class),//扫描所有UserService
        @ComponentScan.Filter(type =FilterType.CUSTOM, classes = MyTypeFilter.class)//由MyTypeFilter返回的boolean决定
})

public class MyTypeFilter implements TypeFilter {
    /*
    *  其中MetadataReader表示读取到当前正在扫描的类的信息
    *  MetadataReaderFactory表示可以获取到其他恩和类的信息
    * */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前扫描的类的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前扫描的类的类信息(ClassMetadata可以获取类名,父类,接口等相关信息)
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();
        if(classMetadata.getClassName().endsWith("Service")){
            return true;//加载Service组件
        }
        return false;
    }
}

TypeExcludeFilter类:实现TypeFilter,提供从 BeanFactory 加载并自动应用于 @SpringBootApplication 扫描的排除 TypeFilter 。 实现应提供一个向 BeanFactory 注册的子类,并重写 match(MetadataReader, MetadataReaderFactory) 方法。它们还应该实现一个有效的 hashCode 和 equals 方法,以便可以将它们用作Spring测试的应用程序上下文缓存的一部分。 注意,TypeExcludeFilters 在应用程序生命周期的很早就初始化了,它们通常不应该依赖于任何其他bean。它们主要在内部用于支持 spring-boot-test 。

public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {
    private BeanFactory beanFactory;
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        if (this.beanFactory instanceof ListableBeanFactory && this.getClass() == TypeExcludeFilter.class) {
            Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory)this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
            Iterator var4 = delegates.iterator();

            while(var4.hasNext()) {
                TypeExcludeFilter delegate = (TypeExcludeFilter)var4.next();
                if (delegate.match(metadataReader, metadataReaderFactory)) {
                    return true;
                }
            }
        }
        return false;
    }
}

注意看if结构体中的第一句,它会从 BeanFactory (可以暂时理解成IOC容器)中获取所有类型为 TypeExcludeFilter 的组件,去执行自定义的过滤方法。由此可见,TypeExcludeFilter 的作用是做扩展的组件过滤。

AutoConfigurationExcludeFilter类:它的 match 方法要判断两个部分:是否是一个配置类,是否是一个自动配置类。具体使用细节后面介绍

ublic class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {

	private ClassLoader beanClassLoader;

	private volatile List<String> autoConfigurations;

	@Override
	public void setBeanClassLoader(ClassLoader beanClassLoader) {
		this.beanClassLoader = beanClassLoader;
	}

	@Override
	public boolean match(MetadataReader metadataReader,
			MetadataReaderFactory metadataReaderFactory) throws IOException {
		return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
	}

	private boolean isConfiguration(MetadataReader metadataReader) {
		return metadataReader.getAnnotationMetadata()
				.isAnnotated(Configuration.class.getName());
	}

	private boolean isAutoConfiguration(MetadataReader metadataReader) {
		return getAutoConfigurations()
				.contains(metadataReader.getClassMetadata().getClassName());
	}
}

@SpringBootConfiguration注解作用

@SpringBootConfiguaration:标识一个类作为 SpringBoot 的配置类,它是Spring原生的 @Configuration 的一种替换方案,本质就是一个@Configuration注解,被 @Configuration 标注的类,会被 Spring 的IOC容器认定为配置类。是配置bean的一种方式。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

@EnableAutoConfiguration注解

@EnableAutoConfiguration注解的作用是自动装配Bean,在学习SpringFramework 时候,需要使用bean的时候都需要手动装配,主要使用如下方法配置bean:

  • 使用注解 @Component 等(Spring2.5+)和注解扫描@ComponentScan包扫描(常用自己写的类)
  • 使用配置类 @Configuration 与 @Bean (Spring3.0+)(常用导入的第三方包里面的组件)
  • 使用Spring提供的FactoryBean工厂Bean创建Bean(将FactoryBean注册成Bean,此时这个工厂Bean获取时是调用getObejct方法返回创建的对象的Bean,而不是FactoryBean本身这个Bean,可以通过添加前缀&来获取FactoryBean这个类型的Bean)
  • 使用模块装配 @EnableXXX 与 @Import (Spring3.1+)(常用 快速给容器导入一个组件)

前两种最简单的方式不介绍,SpringFramework 提供了模块装配功能,通过给配置类标注 @EnableXXX 注解,再在注解上标注 @Import 注解,即可完成组件装配的效果。使用案例如下:创建自定义注解EnableColor,并声明 @Import

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({AuthConfiguration.class})
public @interface EnableColor {
    
}

@Import注解的可以传入四种类型:普通类、配置类、ImportSelector 的实现类,ImportBeanDefinitionRegistrar 的实现类。支持同时传入多个,具体如文档注释中描述:

/**
 * Indicates one or more <em>component classes</em> to import &mdash; typically
 * {@link Configuration @Configuration} classes.
 *
 * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
 * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
 * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
 * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
 *
 * <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
 * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
 * injection. Either the bean itself can be autowired, or the configuration class instance
 * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
 * navigation between {@code @Configuration} class methods.
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
	Class<?>[] value();

}

导入普通类

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({Red.class})
public @interface EnableColor {

}

之后启动类标注 @EnableColor,引导启动IOC容器:

@EnableColor
@Configuration
public class ColorConfiguration {
    
}

public class App {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ColorConfiguration.class);
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        Stream.of(beanDefinitionNames).forEach(System.out::println);
    }
}

结果如下:看到Red类已经被注册配置成Spring中的bean。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
colorConfiguration
com.example.demo.enablexxx.Red

导入配置类

同理也可以将配置类放到@Import注解中进行导入,导入后配置类及配置类中的Bean都会被注册成Bean

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({Red.class, ColorRegistrarConfiguration.class})
public @interface EnableColor {

}

@Configuration
public class ColorRegistrarConfiguration {
    
    @Bean
    public Yellow yellow() {
        return new Yellow();
    }
    
}

导入ImportSelector

ImportSelector的源码如下:从注释可以看出,返回需要被导入的类的名称即可实现Bean注册,支持数组(多个类名)

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完成Bean的装配,测试后可以看出ColorImportSelector 没有注册到IOC容器中,两个新的颜色类被注册成Bean。

public class ColorImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {Blue.class.getName(), Green.class.getName()};
    }
    
}

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({Red.class, ColorRegistrarConfiguration.class, ColorImportSelector.class})
public @interface EnableColor {

}

导入ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar 接口源码如下,可以使用BeanDefinitionRegistry类来进行类的注册:

public interface ImportBeanDefinitionRegistrar {

	/**
	 * Register bean definitions as necessary based on the given annotation metadata of
	 * the importing {@code @Configuration} class.
	 * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
	 * registered here, due to lifecycle constraints related to {@code @Configuration}
	 * class processing.
	 * @param importingClassMetadata annotation metadata of the importing class
	 * @param registry current bean definition registry
	 */
	void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

}

使用案例如下:即可完成Black类的注册

public class ColorImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //第一个参数是bean name
        registry.registerBeanDefinition("black", new RootBeanDefinition(Black.class));
    }
    
}

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({Red.class, ColorRegistrarConfiguration.class, ColorImportSelector.class, ColorImportBeanDefinitionRegistrar.class})
public @interface EnableColor {

}

在上述的导入方法中,使用ImportSelector和ImportBeanDefinitionRegistrar接口导入时候,可以看出参数中有AnnotationMetadata和BeanDefinitionRegistry类,下面使用案例来说明这2个类的作用

需求:需要自定义一个注解,该注解和主业务完全解耦,主要作用是:使用注解判断中请求中token是否是复合要求的。

编写EnableAuth注解,添加属性:1.是否过滤,2.过滤的url

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthBeanDefinitionRegistrar.class})
public @interface EnableAuth {
    boolean isFilter() default false;
    String[] filterUrl() default {};
}

@Import使用ImportBeanDefinitionRegistrar接口,完成过滤器Bean的注册,通过AnnotationMetadata可以获取自定义注解的属性,即导入类的注解元数据,BeanDefinitionRegistry可以完成bean的注册及其他bean定义相关的API

@Slf4j
public class AuthBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //如果@Import注解用在某个@EnableXX注解上,那么可以获取到该注解的属性
        AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(
                annotationMetadata.getAnnotationAttributes(
                        EnableAuth.class.getName()));
        if(annotationAttributes.getBoolean("isFilter")){
            Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(EnableAuth.class.getName());
            List<String> urlPatterns = Arrays.asList((String[]) attributes.get("filterUrl"));
            BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FilterRegistrationBean.class);
            definition.addPropertyValue("filter",new AuthFilter());
            definition.addPropertyValue("urlPatterns", CollectionUtils.isEmpty(urlPatterns)?"/*":urlPatterns);
            definition.addPropertyValue("enabled",true);
            definition.addPropertyValue("order",1);
            AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
            BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, FilterRegistrationBean.class.getName());
            BeanDefinitionReaderUtils.registerBeanDefinition(holder, beanDefinitionRegistry);
        }
        log.info("init success");
    }
}


@Slf4j
public class AuthFilter implements Filter {

    private static final String AUTH_TOKEN = "token";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        final String token = req.getParameter(AUTH_TOKEN);
        final AuthTokenService tokenService = ApplicationContextUtils.getBean(AuthTokenService.class);
        final AuthResult result = (AuthResult) tokenService.checkToken(token);
        if(result.getCode() == 1 ){
            log.info("user auth token filter passed");
            req.setAttribute("user_info", result.getData());
            filterChain.doFilter(servletRequest, servletResponse);
        }else{
            throw new AuthTokenException(result.getMessage());
        }
    }

}

当然@Import注解除了搭配@EnableXxx注解,来实现添加@EnableXxx注解实现给容器注入Bean,@Import注解可以直接放在配置类上(即标记@Configuration的类上)来实现给容器注入Bean。可传入同上一样四种类型实现注入。

@Configuration
@Import({Red.class})
public class ColorRegistrarConfiguration {
    
    @Bean
    public Yellow yellow() {
        return new Yellow();
    }
    
}

 

@EnableAutoConfiguration注解

@EnableAutoConfiguration注解源码如下,主要包含了注解@AutoConfigurationPackage和AutoConfigurationImportSelector类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

@AutoConfigurationPackage

其中AutoConfigurationPackage的源码如下,主要是通过Import导入了Registrar类,

@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

Registrar的源码如下,其中new AutoConfigurationPackages.PackageImport(metadata)主要是用于保存导入的配置类所在的根包,把主配置所在根包保存起来以便后期扫描用的。这也是为啥需要将主启动类必须放在所有自定义组件的包的最外层,以保证Spring能扫描到它们。

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }


    PackageImport(AnnotationMetadata metadata) {
            this.packageName = ClassUtils.getPackageName(metadata.getClassName());
        }

在AutoConfigurationPackages.register方法中,它要判断当前IOC容器中是否包含 AutoConfigurationPackages 。如果有,就会拿到刚才传入的包名,设置到一个 basePackage 里面!basePackage 的意义很明显是根包。 换句话说,它要取主启动类所在包及子包下的组件。不过,在实际Debug时,并不是走的上面流程,因为 AutoConfigurationPackages 对应的 Bean 还没有创建,所以走的下面的 else 部分,直接把主启动类所在包放入 BasePackages 中,也是调用 addIndexedArgumentValue 方法。创建对应的Bean。

private static final String BEAN = AutoConfigurationPackages.class.getName();


public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(2);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }

    }

basePackage的作用

 basePackage 除了提供给 SpringFramework 和 SpringBoot 的内部使用,还可以提供给第三方整合使用,以整合 MyBatis 为例:引入 mybatis-spring-boot-starter 依赖后,在 IDEA 中打开 MyBatisAutoConfiguration 类。在这个配置类中,找到组件:AutoConfiguredMapperScannerRegistrar,代码如下:

public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (!AutoConfigurationPackages.has(this.beanFactory)) {
            logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
            return;
        }
        logger.debug("Searching for mappers annotated with @Mapper");

        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        // logger ......
        // 注册Mapper ......
    }

AutoConfiguredMapperScannerRegistrar类的作用是扫描 Mapper 并注册到 IOC 容器的 ImportBeanDefinitionRegistrar ,取扫描根包的动作就是 AutoConfigurationPackages.get(this.beanFactory) ,由此就可以把事先准备好的 basePackages 都拿出来,之后进行扫描。 到这里,就呼应了文档注释中的描述,也解释了为什么 SpringBoot 的启动器一定要在所有类的最外层。

@Import({AutoConfigurationImportSelector.class})

这个注解导入AutoConfigurationImportSelector类,AutoConfigurationImportSelector的源码如下:实现了DeferredImportSelector、Aware接口、Ordered类

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    @Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
        //获取自动配置的元数据信息,内部是一个Properties对象
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
        //自动加载配置类
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
}

其中Ordered接口:Spring就提供了Ordered这个接口或者@Ordered注解,来处理相同接口实现类的优先级问题。即决定Bean的执行顺序(不决定Bean的加载顺序和实例化顺序),Aware接口:在Spring容器初始化过程中,通过不同的Aware接口实现不同的初始化或者设置,具体的使用参见:最主要的就是DeferedImportSelector这个类,它实现ImportSelector,用来实现注册Bean的接口:

/**
 * A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans
 * have been processed. This type of selector can be particularly useful when the selected
 * imports are {@code @Conditional}.
 *
 * <p>Implementations can also extend the {@link org.springframework.core.Ordered}
 * interface or use the {@link org.springframework.core.annotation.Order} annotation to
 * indicate a precedence against other {@link DeferredImportSelector DeferredImportSelectors}.
 *
 * <p>Implementations may also provide an {@link #getImportGroup() import group} which
 * can provide additional sorting and filtering logic across different selectors.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @since 4.0
 */
public interface DeferredImportSelector extends ImportSelector {
}
  • DeferredImportSelector是ImportSelector 的一种扩展,在处理完所有 @Configuration 类型的Bean之后运行(DeferredImportSelector 的执行时机比 ImportSelector 更晚),当所选导入为 @Conditional 时,这种类型的选择器特别有用。
  • 实现类还可以扩展 Ordered 接口,或使用 @Order 注解来指示相对于其他 DeferredImportSelector 的优先级。
  • 实现类也可以提供导入组,该导入组可以提供跨不同选择器的其他排序和筛选逻辑。

AutoConfigurationImportSelector的核心代码是ImportSelector接口的selectImport方法,selectImport中主要包括以下两个方法:

loadMetadata方法:使用类加载器加载"META-INF/" + "spring-autoconfigure-metadata.properties"下的元数据。

final class AutoConfigurationMetadataLoader {

	protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

	public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

spring-autoconfigure-metadata.properties文件的部分数据如下:

org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.freemarker.FreeMarkerReactiveWebConfiguration.Configuration=
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration=
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.Configuration=

getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata)方法源码如下,主要是加载配置数据。对配置进行过滤,校验等后返回。

/**
	 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
	 * of the importing {@link Configuration @Configuration} class.
	 * @param autoConfigurationMetadata the auto-configuration metadata
	 * @param annotationMetadata the annotation metadata of the configuration class
	 * @return the auto-configurations that should be imported
     * 根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry。
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		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 new AutoConfigurationEntry(configurations, exclusions);
	}

其中主要的方法是getCandidateConfigurations()方法,这个方法又调用了SpringFactoriesLoader.loadFactoryNames 方法,传入的Class就是 @EnableAutoConfiguration,拿到所有的配置信息configurations,然后做一些校验、去重、排除、过滤等操作;

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // SPI机制加载自动配置类
    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;
}

SpringFactoriesLoader.loadFactoryNames 方法如下:通过类加载加载 "META-INF/spring.factories" 的配置(初始化时候会加载),将配置添加到缓存Cache中(本地缓存的类型是MultiValueMap<String, String> ),后续在加载loadFactoryNames时直接从缓存中获取org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的所有自动配置类(是一个很大的字符串,里面都是自动配置类的全限定类名),装配到IOC容器中,之后自动配置类就会通过ImportSelector 和 @Import 的机制被创建出来,这些Bean就存在生效了。 这也是SpringBoot不需要任何配置就可以正常运行的原因。

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

注意MultiValueMap的类型其实就是Map<K, List<V>>,一个key对应多个value,即需要自动配置的配置类有很多。对应spring.facroty文件的类型,多个value值都被加载并放到缓存中。

public interface MultiValueMap<K, V> extends Map<K, List<V>>

下面是META-INF下spring.factory文件的部分摘录:其中org.springframework.boot.autoconfigure.AutoConfigurationImportFilter(应用于过滤一些不需要注册Configuration的配置)和org.springframework.boot.autoconfigure.EnableAutoConfiguration(需要自动配置的Configuration)

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
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.CloudServiceConnectorsAutoConfiguration,\
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,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

从上面的 Properties 中发现,所有配置的 EnableAutoConfiguration 的自动配置类都以 AutoConfiguration 结尾!可以看出,如果需要自定义一个配置类(Starter),也可以按照这个逻辑实现,并将其添加在spring.factories文件的key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的value上即可

总结就是

  • AutoConfigurationImportSelector 配合 SpringFactoriesLoader 可加载 “META-INF/spring.factories” 中配置的 @EnableAutoConfiguration 对应的自动配置类。
  • DeferredImportSelector 的执行时机比 ImportSelector 更晚。
  • SpringFramework 实现了自己的SPI技术,相比较于Java原生的SPI更灵活。

自定义SpringBoot-Starter

官方对 Starter 包定义的 artifactId 是有规范要求,Spring 官方 Starter 通常命名为 spring-boot-starter-{name},如:spring-boot-starter-web,spring-cloud-starter-openfeign,Spring 官方建议非官方的 Starter 命名应遵守 {name}-spring-boot-starter 的格式,如:mybatis-spring-boot-starter:

先简单分析下mybatis-spring-boot-starter的结构:
父包:mybatis-spring-boot 核心功能模块
主要子包:
mybatis-spring-boot-starter
mybatis-spring-boot-autoconfigure

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot</artifactId>
    <version>2.1.4</version>
  </parent>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <name>mybatis-spring-boot-starter</name>
  <properties>
    <module.name>org.mybatis.spring.boot.starter</module.name>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </dependency>
  </dependencies>
</project>

项目结构如下:mybatis-spring-boot-starter工程本身是个空项目,只有pom文件和pom.properties文件(标记maven坐标的值),mybatis-spring-boot-autoconfigure工程中有spring.facoties文件和org.mybatis.spring.boot.autoconfigure包

其中spring.facoties文件内容如下:添加了一个key=org.springframework.boot.autoconfigure.EnableAutoConfiguration,需要自动注入的value有:MybatisLanguageDriverAutoConfiguration,MybatisAutoConfiguration

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

核心文件:

MybatisProperties类:主要是加载mybatis的相关配置,通过@EnableConfigurationProperties({MybatisProperties.class})注入到spring容器中,使得在MybatisAutoConfiguration中可以直接注入使用配置属性

@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties {
    public static final String MYBATIS_PREFIX = "mybatis";
    private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    private String configLocation;
    private String[] mapperLocations;
    private String typeAliasesPackage;
    private Class<?> typeAliasesSuperType;
    private String typeHandlersPackage;
    private boolean checkConfigLocation = false;
    private ExecutorType executorType;
    private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
    private Properties configurationProperties;
    @NestedConfigurationProperty
    private Configuration configuration;

    public MybatisProperties() {
    }
}

MybatisAutoConfiguration类:自动配置类,根据条件将相关的类注册成Bean

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
  

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        ...
        return factory.getObject();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }

    @Configuration
    @Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
    @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
    public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
        public MapperScannerRegistrarNotFoundConfiguration() {
        }

}

MybatisLanguageDriverAutoConfiguration类:自动配置类,配置不同驱动的相关Bean

@Configuration
@ConditionalOnClass({LanguageDriver.class})
public class MybatisLanguageDriverAutoConfiguration {
    private static final String CONFIGURATION_PROPERTY_PREFIX = "mybatis.scripting-language-driver";

    public MybatisLanguageDriverAutoConfiguration() {
    }

    @Configuration
    @ConditionalOnClass({ThymeleafLanguageDriver.class})
    public static class ThymeleafConfiguration {
        public ThymeleafConfiguration() {
        }

        @Bean
        @ConditionalOnMissingBean
        ThymeleafLanguageDriver thymeleafLanguageDriver(ThymeleafLanguageDriverConfig config) {
            return new ThymeleafLanguageDriver(config);
        }
}

总结自定义Starter的相关事项:

编写自己的properties类(用来加载属性文件进行默认的配置)和核心服务类(需要自动配置成bean的类)
自定义配置类XxxAutoConfiguration ,通过@Condition*系列注解控制自动配置条件,@EnableConfigurationProperties({XxxProperties.class})加载properties类
在src/main/resources新建文件夹META-INF,然后新建一个spring.factories文件,使用org.springframework.boot.autoconfigure.EnableAutoConfiguration指定我们自定义的自动配置类的全路径。
这样,一个自定义的xxx-spring-boot-start工程就完成了,执行mvn clean install将项目打成一个jar包。在需要时引用即可。
 

自定义customer-spring-boot-starter

1.新建maven项目,导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>customer.spring.boot.starter</groupId>
    <artifactId>customer-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

创建CustomerProperties:

@ConfigurationProperties(prefix = "customer")
public class CustomerProperties {

    private String name = "jack";
    private String[] addresses = {"beijing", "shanghai"};
    private boolean marry = false;

    //省略setter/getter方法
}

创建自动配置类,自动配置Customer这个Bean

@Configuration
@EnableConfigurationProperties(CustomerProperties.class)
@ConditionalOnClass(Customer.class)
@ConditionalOnProperty(prefix = "customer", value = "enable", matchIfMissing=true)
public class CustomerAutoConfiguration {

    private final CustomerProperties properties;

    public CustomerAutoConfiguration(CustomerProperties properties) {
        this.properties = properties;
    }

        @Bean
        public Customer customer(){
            Customer customer = new Customer();
            customer.setName(properties.getName());
            customer.setAddresses(properties.getAddresses());
            customer.setMarry(properties.isMarry());
            return customer;
        }
}

在resources资源目录下创建META-INF/spring.factories文件,添加自动配置类,整个项目如下:

测试:在项目中引入依赖,直接可以注入Customer这个Bean,然后使用:

 @Autowired
    private Customer customer;

    @RequestMapping(value = "/autoconfig",method = RequestMethod.GET)
    public String test() throws IOException {
        return customer.getName();
    }

测试结果:

拓展:@ConfigurationProperties和@EnableConfigurationProperties的关系

在spring-boot中,一个Properties类上仅添加@ConfigurationProperties注解,则必须在@Coniguration对应配置类上使用@EnableConfigurationProperties注解去指定这个类,使得spring会扫描它,添加为bean,并识别@ConfigurationProperties注解,然后填充属性。当然也可以直接通过@Component或者其他注入Bean的方式将Properties类标记为Bean,那么spring容器会自动使该类上的@ConfigurationProperties生效(创建一个该类的bean实例,把propertis/yml配置文件对应属性填充设置到该实例中,这样其它地方就可以通过注入该实例,从而获取相应的配置)而不需要使用@EnableConfigurationProperties注解,总结就是:

标记@ConfigurationProperties注解的配置类要么是Spring中的Bean,要么通过在配置类中添加@EnableConfigurationProperties注解去指定这个类,使得spring会扫描它,添加它为Bean,这样才可以填充对应的属性。

 

SpringBoot使用的工厂机制

SpringBoot 在非常多的位置都利用类似于上面 通过读取 spring.factories 加载一组预先配置的类的机制,而这个机制的核心源码来自 SpringFactoriesLoader 。SpringFactoriesLoader  在 SpringFramework3.2 就已经有了,SpringFramework的SpringFactoriesLoader官方文档注释如下:

General purpose factory loading mechanism for internal use within the framework. SpringFactoriesLoader loads and instantiates factories of a given type from "META-INF/spring.factories" files which may be present in multiple JAR files in the classpath. The spring.factories file must be in Properties format, where the key is the fully qualified name of the interface or abstract class, and the value is a comma-separated list of implementation class names. For example: example.MyService=example.MyServiceImpl1,example.MyServiceImpl2 where example.MyService is the name of the interface, and MyServiceImpl1 and MyServiceImpl2 are two implementations.

它是一个框架内部使用的通用工厂加载机制。

SpringFactoriesLoader 从 META-INF/spring.factories 文件中加载并实例化给定类型的工厂,这些文件可能存在于类路径中的多个jar包中。spring.factories 文件必须采用 properties 格式,其中key是接口或抽象类的全限定名,而value是用逗号分隔的实现类的全限定类名列表。

例如:example.MyService=example.MyServiceImpl1,example.MyServiceImpl2

其中 example.MyService 是接口的名称,而 MyServiceImpl1 和 MyServiceImpl2 是两个该接口的实现类。

核心方法就是:使用给定的类加载器从 META-INF/spring.factories 中加载给定类型的工厂实现的全限定类名。spring.factories文件是key-value格式的文件,key可以随意定义(上述的org.springframework.boot.autoconfigure.EnableAutoConfiguration 是一个注解)。

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值