SpringBoot 2.0 系列006 --启动实战之注解使用
一些误区说明
网上很多教程默认使用SpringBootApplcation注解,且只用这个即可扫描启动类包下所有的bean。 而官方默认教程使用的是@EnableAutoConfiguration,这个注解和SpringBootApplication注解的区别是什么?
If you don’t want to use @SpringBootApplication, the @EnableAutoConfiguration and @ComponentScan annotations that it imports defines that behaviour so you can also use that instead.
意思是如果你不想用@SpringBootApplication 可以使用@EnableAutoConfiguration and @ComponentScan 注解来代替它
@SpringBootApplication注解说明
很多开发者喜欢它们的app能auto-configuration, component scan 和在启动类中做额外配置。那么@SpringBootApplication可以满足你的这三项要求
- @EnableAutoConfiguration :开启自动配置
- @ComponentScan :开启application所在包下的扫描
- @Configuration :允许你注册额外的bean或者导入额外的配置classes
SpringBoot是如何扫描到我们的class的?
- 流程图
从流程图可知,在refreshContext时,会调用上层的refresh方法触发invokeBeanFactoryPostProcessors,在到后边会触发到的ComponentScanAnnotationParser类中
ComponentScanAnnotationParser.java
在此方法中,我们可以判定出我们的basePackages目录以方便我们扫描。
public Set
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
Set<String> basePackages = new LinkedHashSet<>();
String\[\] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String\[\] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG\_LOCATION\_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 默认使用@componentScan 不写basePackages则使用此处做为基础包路径
if (basePackages.isEmpty()) {
// declaringClass=com.ricky.SpringBootApplication05
// 即 com.ricky
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
.ClassPathScanningCandidateComponentProvider
会扫描我们启动类packageSearchPath所在目录下的所有class,最终装载到Set candidates对象中。
private Set
- 相关图
项目目录结构
扫描到的目录
扫描到类
@SpringBootApplication 使用
- 注解源码
源码可以解释 为什么官方说一般可以使用@EnableAutoConfiguration和 @ComponentScan 进行替代。
@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 {};
}
- exclude 废弃指定class的EnableAutoConfiguration类型
只支持org.springframework.boot.autoconfigure包下的
@SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)
- excludeName 废弃指定全类名的EnableAutoConfiguration类型
只支持org.springframework.boot.autoconfigure包下的
@SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.orm.jpaHibernateJpaAutoConfiguration.class")
- scanBasePackages 指定扫描的包
@SpringBootApplication(scanBasePackages = {"com.ricky","com.ricky02"})
- scanBasePackageClasses 指定含有ComponentScan的类
可以自定义
@Configuration @ComponentScan public class Ricky03Config { }
@SpringBootApplication(scanBasePackages = {"com.ricky","com.ricky02"},scanBasePackageClasses = Ricky03Config.class)
替代SpringBootApplication注解的方式
和实现上述功能一样的方式,需要EnableAutoConfiguration、ComponentScan或者配合Import
@Configuration @ComponentScan public class Ricky04Config { }
@EnableAutoConfiguration @ComponentScan(basePackages = {"com.ricky","com.ricky02","com.ricky03"},basePackageClasses = {Ricky04Config.class}) //@Import({Ricky04Config.class}) //ricky04目录的也扫描进来 public class SpringBootApplication06_3 { /** * 开启SpringBoot服务 * @param args */ public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringBootApplication06_3.class, args);
String\[\] names = context.getBeanDefinitionNames();
for (String name:names){
System.out.println(name);
}
context.close();
}
}
- Import
import一般可以引入我们的@configuration相关的类。 我们可以利用import和Configuration来实现开启 动态注入bean对象的场景。
- 说明
这里是假定我们redis实例都是通过公共模块来调用,类似单独的SB项目模块独立管理。在其他模块使用的时候需要引入该模块。
- 定义一个开关注解
公共模块中
/** * 自定义开启redis * */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(RedisConfig.class) public @interface EnableZDRedis { }
- redisConfig
公共模块中
@Configuration @ConditionalOnClass(Jedis.class) public class RedisConfig {
@Bean
@ConditionalOnMissingBean
public Jedis jedis(){
return new Jedis("127.0.0.1",6379);
}
}
- 使用
调用模块中,开启我们定义的注解即可。
@SpringBootApplication @EnableZDRedis public class SpringBootApplication06_2 { @AutoWired private Jedis jedis; }