SpringBoot 2.0 系列006 --啟動實戰之注解使用
一些誤區說明
網上很多教程預設使用SpringBootApplcation注解,且隻用這個即可掃描啟動類包下所有的bean。 而官方預設教程使用的是@EnableAutoConfiguration,這個注解和SpringBootApplication注解的差別是什麼?
- 參閱2.0.1官方文檔說明
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<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
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<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
複制
- 相關圖
項目目錄結構
掃描到的目錄
掃描到類
@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;
}
複制
示範項目位址,歡迎fork和star
碼雲:SpringBootLearn