SpringBoot 2.0 系列006

Stella981
• 阅读 666

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的?

  • 流程图

SpringBoot 2.0 系列006

从流程图可知,在refreshContext时,会调用上层的refresh方法触发invokeBeanFactoryPostProcessors,在到后边会触发到的ComponentScanAnnotationParser类中

ComponentScanAnnotationParser.java

在此方法中,我们可以判定出我们的basePackages目录以方便我们扫描。

public Set 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 scanCandidateComponents(String basePackage) { Set 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; }

  • 相关图

项目目录结构

SpringBoot 2.0 系列006

扫描到的目录

SpringBoot 2.0 系列006

扫描到类

SpringBoot 2.0 系列006

@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

最后

点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
spring注解
随着越来越多地使用Springboot敏捷开发,更多地使用注解配置Spring,而不是Spring的applicationContext.xml文件。Configuration注解:Spring解析为配置类,相当于spring配置文件Bean注解:容器注册Bean组件,默认id为方法名@Configurat
Easter79 Easter79
3年前
spring中@Scope控制作用域
  注解形势:通过@Scope注解控制作用域,默认使用单实例模式,可修改为多实例模式1/2Specifiesthenameofthescopetousefortheannotatedcomponent/bean.3<pDefaultstoanempty
Stella981 Stella981
3年前
Spring Boot的@Service和@Autowired和@ComponentScan注解
SpringBoot的@Service和@Autowired和@ComponentScan注解SpringBoot项目的Bean装配默认规则是根据Application类(SpringBoot项目入口类)所在的包位置从上往下扫描,即只扫描该类所在的包及其子包。1.如果需要扫描其上层包中的类,则将其移动到其上层包中2.使用@ComponentSc
Stella981 Stella981
3年前
SpringBoot 2.0 系列003
SpringBoot2.0系列003自定义Parent默认我们使用SpringBoot的方式是通过SB的parent项目的方式,此种之前的教程中我们已经演示过了,这里不做赘述。使用自定义parent管理SpringBoot项目
Stella981 Stella981
3年前
SpringBoot 2.0 系列004
SpringBoot2.0系列004启动实战之配置文件配置文件配置文件加载流程很多文档包括官方文档说SB的默认配置文件是application开头的文件,那么是为什么呢?我们先看下流程图 !(https://gitee.c
Wesley13 Wesley13
3年前
1.1Spring Boot 环境配置和常用注解
SpringBoot常用注解:@Service:注解在类上,表示这是一个业务层bean@Controller:注解在类上,表示这是一个控制层bean@Repository:注解在类上,表示这是一个数据访问层bean@Component:注解在类上,表示通用bean,value不写默认就是类名首字母小写@Auto
Easter79 Easter79
3年前
SpringBoot注解大全
一、注解(annotations)列表@SpringBootApplication:包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解。其中@ComponentScan让springBoot扫描到Configuration类并把它加入到程序上下文。@Configuratio
Easter79 Easter79
3年前
Spring注解@Resource和@Autowired区别对比、spring扫描的默认bean的Id、程序获取spring容器对象
\注解扫面的bean的ID问题0.前提需要明白注解扫描出来的bean的id默认是类名首字母小写,当然可以指定id:
Stella981 Stella981
3年前
SpringBoot注解大全
一、注解(annotations)列表@SpringBootApplication:包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解。其中@ComponentScan让springBoot扫描到Configuration类并把它加入到程序上下文。@Configuratio
Stella981 Stella981
3年前
SpringBoot 2.0 系列005
SpringBoot2.0系列005启动实战之SpringApplication应用2.X 官方示例(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fprojects.s