博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring-boot 启动自动加载配置的原理相关
阅读量:4074 次
发布时间:2019-05-25

本文共 8365 字,大约阅读时间需要 27 分钟。

    spring-boot 自动加载的原理

概述:

spring-boot项目中,我们只要创建一个启动类并且标注@SpringBootApplication注解,就可以完成自动化的配置,这其中的原理,主要就是@SpringBootApplication注解的作用,下面我们详细分析下这个注解。

 一、SpringBootApplication注解

@SpringBootApplication注解主要包装了三个子注解如下:

@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))public @interface SpringBootApplication {

其中@ComponentScan用来扫描包的作用,@SpringBootConfiguration是@Configuration的包装,@Configuration又是@Component的包装  表示这个类是一个配置类。

        
@EnableAutoConfiguration注解是我们主要分析的对象。

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

此注解主要起作用的有2个,一个是@AutoConfigurationPackage,一个是@Import(重点分析)

我们知道@Import除了加载指定class的类,还可以指定ImportSelector实现的子类,这个接口方法selectImports,返回的列表即是这个@Import注解需要加载的类。

EnableAutoConfigurationImportSelector的具体实现。

public String[] selectImports(AnnotationMetadata metadata) {			try {				AnnotationAttributes attributes = getAttributes(metadata);				List
configurations = getCandidateConfigurations(metadata, attributes); // 加载候选的配置类 // ....略 return configurations.toArray(new String[configurations.size()]); } catch (IOException ex) { throw new IllegalStateException(ex); } }

selectImports方法是调用getCandidateConfigurations方法加载候选类,而此方法实际是用SpringFactoriesLoader的loadFactoryNames方法来加载各种自动配置类。

protected List
getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List
configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); return configurations; }

SpringFactoriesLoader的loadFactoryNames方法传入了一个EnableAutoConfiguration的class全类名,然后主要是从META-INF/spring.factories路径下加载资源文件,遍历,然后找到符合这个全类名key的value值,进行解析处理。

public static List
loadFactoryNames(Class
factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration
urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); // 加载资源文件 List
result = new ArrayList
(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); // 解析成prop String factoryClassNames = properties.getProperty(factoryClassName); // 得到factoryClass的属性 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } catch (IOException ex) { } }

        下面我们从spring-boot-autoconfigure的jar包中META-INF下找到这个资源文件中的配置。

 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\        org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\        org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\        org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

    二、部分默认配置类的配置解释

1、DataSourceAutoConfiguration的分析

1) 类上注解的配置

@Configuration@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })@EnableConfigurationProperties(DataSourceProperties.class)@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })

这个注解一共包装了4个子注解,@Configuration是标注配置类,@ConditionalOnClass表示必须存在DataSource的类,@EnableConfigurationProperties是加载配置文件用途的。

 @Import引入的2个类,都是采用直接指定类加载的方式,其中一个Registrar是用来生成一个BeanPostProcessor,实现当datasource的bean初始化后,必须加载DataSourceInitializer的用途。而DataSourcePoolMetadataProvidersConfiguration就是提供数据源提供者类的配置。比如DBCP、TOMCAT、等等数据源提供类。

2) 方法和子类配置

方法1)dataSourceInitializer配置主要是初始化一个公共的DataSourceInitializer的bean,这个bean的主要作用就是把配置类配置的url

            、password等缺省赋值到已经创建的datasource中。

        @Bean            @ConditionalOnMissingBean // 表示            public DataSourceInitializer dataSourceInitializer() {                return new DataSourceInitializer();            }

内部类2)PooledDataSourceConfiguration这个内部类,主要是提供几种数据源的类,是采用@Import直接提供的方式。

        @Configuration            @Conditional(PooledDataSourceCondition.class)            @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })            @Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class,                    DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,                    DataSourceConfiguration.Generic.class })            protected static class PooledDataSourceConfiguration {            }

     这个内部类必须要存在dbcp2数据源的类,并且spring.datasource.type正确的指定,或者不提供(matchIfMissing = true),如何创建一个数据源呢?   其创建方法最终执行的是 DataSourceBuilder.create().....type().build(),

   @ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)             @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource", matchIfMissing = true)            static class Dbcp2 extends DataSourceConfiguration {                        @Bean                @ConfigurationProperties("spring.datasource.dbcp2")                public org.apache.commons.dbcp2.BasicDataSource dataSource(                        DataSourceProperties properties) {                    return createDataSource(properties,                            org.apache.commons.dbcp2.BasicDataSource.class);                }            }

 最后列举一下我们自己创建的datasource,通过DataSourceBuilder创建,然后利用配置文件给定属性

       @ConfigurationProperties(prefix = "spring.datasource.xxx")            @Bean(destroyMethod = "close", name = "xxx")            public DataSource dataSource1() {                return DataSourceBuilder.create().type(dataSourceType).build();            }

    三、Conditional系列注解的源码分析

    我们在上面的配置类中各种ConditionalOnClass条件的使用,下面我们从一个ConditionalOnClass注解分析此类注解的原理。 首先Conditional注解的value值是一个实现Condition的接口,这个接口的matches方法实现如果返回true,则加载此bean,而ConditionalOnClass的这个注解其实包装了Conditional,实现Condition接口的类是OnClassCondition

1、OnClassCondition

            OnClassCondition继承了SpringBootCondition,SpringBootCondition实现matches的方法,而实际上matches调用的是子类的模板方法 getMatchOutcome。方法如下 (备注ConditionOutcome就是包装一个boolean类型的标记和信息的po类,match方法就是true,noMatch就是false)

 public ConditionOutcome getMatchOutcome(ConditionContext context,            AnnotatedTypeMetadata metadata) {            ConditionMessage matchMessage = ConditionMessage.empty();            MultiValueMap
onClasses = getAttributes(metadata,ConditionalOnClass.class); // 得到注解元素            if (onClasses != null) {                List
missing = getMatchingClasses(onClasses, MatchType.MISSING,context);  // 判断缺少的类                if (!missing.isEmpty()) {                    return ConditionOutcome                            .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)                                    .didNotFind("required class", "required classes")                                    .items(Style.QUOTE, missing));                }            }            //....            return ConditionOutcome.match(matchMessage);        }
private List
getMatchingClasses(MultiValueMap
attributes, MatchType matchType, ConditionContext context) { List
matches = new LinkedList
(); addAll(matches, attributes.get("value")); addAll(matches, attributes.get("name")); Iterator
iterator = matches.iterator(); while (iterator.hasNext()) { if (!matchType.matches(iterator.next(), context)) { iterator.remove(); // matchType传入的枚举是不符合,这里!就是符合的移除,剩余的就是没有提供的。 } } return matches; }
private enum MatchType {			PRESENT { // 提供了class				public boolean matches(String className, ConditionContext context) {					return ClassUtils.isPresent(className, context.getClassLoader());				}			},			MISSING {  // 没有提供calss				public boolean matches(String className, ConditionContext context) {					return !ClassUtils.isPresent(className, context.getClassLoader());				}			};			public abstract boolean matches(String className, ConditionContext context);		}

从上面的代码我们可以看到OnClassCondition的判断很简单,就是根据注解提供的value值的class类型,然后用class.forName 能够反射的就是为true,即是提供了这个类的,如果没有不提供的类,最终getMatchOutcome方法将返回匹配。

    完毕!

 

转载地址:http://uruni.baihongyu.com/

你可能感兴趣的文章
安装alien,DEB与RPM互换
查看>>
编译Android4.0源码时常见错误及解决办法
查看>>
Android 源码编译make的错误处理
查看>>
linux环境下C语言中sleep的问题
查看>>
ubuntu 12.04 安装 GMA3650驱动
查看>>
新版本的linux如何生成xorg.conf
查看>>
xorg.conf的编写
查看>>
启用SELinux时遇到的问题
查看>>
virbr0 虚拟网卡卸载方法
查看>>
No devices detected. Fatal server error: no screens found
查看>>
新版本的linux如何生成xorg.conf
查看>>
virbr0 虚拟网卡卸载方法
查看>>
Centos 6.0_x86-64 终于成功安装官方显卡驱动
查看>>
Linux基础教程:CentOS卸载KDE桌面
查看>>
db sql montior
查看>>
read humor_campus
查看>>
IBM WebSphere Commerce Analyzer
查看>>
Unix + OS IBM Aix FTP / wu-ftp / proftp
查看>>
my read work
查看>>
db db2 base / instance database tablespace container
查看>>