springBoot是目前最受欢迎的web应用框架之一,其最大的优点在于其自动化配置,使得用户不需要花太多时间到框架配置上,更多的可以注重业务逻辑。
BUT,我们既需要知其然,也需要知其所以然。那么springboot是怎样自动化装配的呢?
其最大的秘密在于springboot使用了强大的注解库:java.lang.annotation。当然,ssm框架也有注解,ssm框架的注解在应用中也是给我们提供了很大的方便空间,不过在配置上却是较为繁琐。接下来看看springboot是怎样实现自动化配置的:
首先,springboot项目都会包含一个启动类,并且这个启动类需要在我们写的逻辑包的最外层。如下图所示就是一个简单的spring boot的项目结构:
那么问题来了,为什么springboot需要启动启动类才可以开始访问,并且启动类需要放在如图所示的最外层呢?
其实我们启动启动类的过程就是我们自动化配置的过程,那么如果需要知道自动化配置的过程,我们需要进入启动类看看:
这个注解基本上就完成了springboot的自动化配置,也就是完成了ssm加载的配置文件的过程。
接下来进一步探究其原理。
点击进去,发现:
@Target({ElementType.TYPE})//该注解的元素类型,具体的类型描述可以进入ElementType中去看
@Retention(RetentionPolicy.RUNTIME)/* 记录下生命周期:Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.*/
@Documented//表示该注解需要javadoc描述和记录
@Inherited//可被继承
@SpringBootConfiguration//springboot配置
@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 {};
}
读者需要注意,其中上面四个注解是元注解,元注解是指注解的注解,包括@Retention @Target @Document @Inherited四种。
也就是描述下注解的特性,应用范围,是否可继承,所以可以不用管。
那就剩下三个了
@Configuration(@SpringBootConfiguration继承它,故我们研究它)
@EnableAutoConfiguration
@ComponentScan
@Configuration@ComponentScan这两个简单,@Configuration是基于spring bean的三种配置方式,xml文件配置,自定义bean的java类配置,基于注解配置。这里就不多说了。
@ComponentScan是扫描包,请看,个人感觉这里可以不用太深入了:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")//默认扫面basePackages下的包
String[] value() default {};
@AliasFor("value")//扫描字符串路径的包
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};//扫面加载类下的东西
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
//扫描类并且继承BeanNameGenerator特性的包,等等
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
最后,就剩@EnableAutoConfiguration,一看名字就知道,“使自动化配置可以用”,神翻译,点进去一看,如图所示是的就是其核心注解配置。
先来看@AutoConfigurationPackage注解
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));
}
}
貌似有点看不懂呀
没关系,注意有个
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
作用是扫面当前包和当前包子包,这也就是为什么启动类要放在其他扫描包的外边的原因。
在注意一下这里注意有个register的方法,接着点进去:
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {//判断当前装配类名存不存在
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();//注册bean
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);
}
}
那个BEAN点进去会发现是这个
private static final String BEAN = AutoConfigurationPackages.class.getName();
到这里就是其自动装配的代码逻辑了,有兴趣的朋友可以接着一个方法一个方法的点进去看看,这里不再赘述。
第二,@Import(AutoConfigurationImportSelector.class)这个注解又是啥呢?
点进去,300多行代码。实际上仔细看不就是ssm xml文件加载的那些东西吗?这里比较全,撅个例子:
这里是主方法,其他方法是调用的,其目的就是获取各种路径下的资源加载:
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = this.getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, () -> {
return "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?";
});
return attributes;
}
这里是加载maven外部配置文件:
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;
}
这里META-INF/spring.factories注意一下,是maven包里的
也就是说上面的三个配置就在启动类启动的时候帮我们自动配置了运行环境,包括扫描包的位置,外部maven以及其他的文件加载路径和相关拦截器,转发器等的配置。
当然,抛砖引玉,自己慢慢多看看,多摸索摸索。
到这里,大家别忘了这些都是通过三个注解实现的,确切的说是一个@SpringBootApplication,我们用的非常的轻松,一个注解接搞定,但是这背后实现的操作可不少呢?其实框架很多人都在说“约定大于框架”。这些注解只是一种方式,我们也可以自己实现自定义注解。