SpringBoot之@SpringBootApplication注解详解

Posted 敲代码的小小酥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot之@SpringBootApplication注解详解相关的知识,希望对你有一定的参考价值。

前言

@SpringBootApplication注解中包含了SpringBoot的包扫描原理、自动装配原理等诸多原理,下面进行该注解的详细研究。
研究上面的原理,主要是研究@SpringBootApplication内部的组成结构,如下图:

下面对@SpringBootConfiguration和@EnableAutoConfiguration进行详解。

@SpringBootConfiguration注解

点进@SpringBootConfiguration内部,看其内部结构:

@Configuration是Spring的一个注解,其修饰的类会加入Spring容器。这就说明SpringBoot的启动类会加入Spring容器。

@EnableAutoConfiguration注解

看其内部结构:

重点看@AutoConfigurationPackage注解和@Import(AutoConfigurationImportSelector.class)注解。

@AutoConfigurationPackage注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

看其@Import进来的类AutoConfigurationPackages.Registrar类:
这是一个内部类,源码如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

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

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

	}

我们看registerBeanDefinitions方法中的register方法:

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(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

通过断点,看packageNames参数的传入值:

可见是在启动类中配置的scanBasePackages属性的包。
程序进入了else条件块儿,执行如下代码:

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);

该步骤就是创建BeanDefinition对象,然后将其注册到BeanDefinition注册器中。我们看其注册的BeanDefinition对象的详情:

可以看到,这个BeanDefinition封装的是一个Package对象。所以,BeanDefinition不只能封装类的属性,还可以封装整个包的属性。
这样,就把包下的类,全部注册到了Spring容器中。

@Import(AutoConfigurationImportSelector.class):
看导入的AutoConfigurationImportSelector类:




主要是在这个方法中,加载了jar包下的spring.facotries文件,源码如下:

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 factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

这个方法中,将jar包中spring.factories文件信息加载到了cache缓存中。 这些配置的类,都是加入Spring容器中的。所以,配置的这些类,自动就在Spring容器中有了,无需我们再进行配置,这就是自动配置的原理。

功能自动开启

在Spring项目中,我们使用Aop也好,事务也好,缓存也好,零配置MVC也好,都要手动开启功能,才能用。而在SpringBoot项目中,无需手动配置,直接能用,这是什么原因呢?
我们拿AOP举例,在spring-boot-autoconfigure jar包中的spring.factories文件中,看AOP的配置:

点进去这个类看源码:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
			matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration(proxyBeanMethods = false)
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}

}

类中的@EnableAspectJAutoProxy注解,开启了AOP功能,这就是自动开启的原理。

以上是关于SpringBoot之@SpringBootApplication注解详解的主要内容,如果未能解决你的问题,请参考以下文章

SpringBootapp 无法连接 mysql db

亲测接口自动化01_Eclipse运行springBootApp

SpringBoot启动原理解析

springboot学习系列

springboot添加@Scheduled定时任务多线程执行

Springboot 系列Spring Boot 自动配置