原 SpringBoot 2.0 系列00

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏个人分享

java解析命令行参数(common-cli)练习

 A class that implements the CommandLineParser interface can parse a String arra...

812
来自专栏开发技术

从源码来理解slf4j的绑定,以及logback对配置文件的加载

  项目中的日志系统使用的是slf4j + logback。slf4j作为一个简单日志门面,为各种loging APIs(像java.util.logging,...

1344
来自专栏IT进修之路

原 荐 最新SpringCloud 服务注入

2332
来自专栏ASP.NET MVC5 后台权限管理系统

ASP.NET MVC5+EF6+EasyUI 后台管理系统(46)-工作流设计-设计分支

步骤设置完毕之后,就要设置好流转了,比如财务申请大于50000元(请假天数>5天)要总经理审批,否则财务审批之后就结束了。 ? 设置分支没有任何关注点,我们把关...

3007
来自专栏一个会写诗的程序员的博客

《Spring Boot极简教程》第8_章: Spring Boot集成Groovy混合Java开发第8_章: Spring Boot集成Groovy混合Java开发小结

本章我们使用SpringBoot集成Groovy混合Java开发一个极简的RestAPI。 数据库使用mysql,ORM层使用mybatis,模板引擎使用fre...

542
来自专栏IT笔记

SpringBoot开发案例之整合mongoDB

JDK1.7、Maven、Eclipse、SpringBoot1.5.2、mongodb3.4,Robomongo(可视化工具)

5336
来自专栏GopherCoder

『No18: Go 实现世界杯后台管理系统』

趁着周末更新一期,上一期讲到 如何快速熟悉一个项目, 文章的最后讲到,最好的方法是借用相同的技术栈重新实现一个项目。

881
来自专栏互联网开发者交流社区

配置嵌入式Servlet容器

1133
来自专栏FreeBuf

VMware更新 | 修复Apache Flex BlazeDS中的漏洞

VMware发布了数个产品的版本更新,目的是修复Apache Flex BlazeDS中的一个漏洞。 据VMware介绍,Flex BlazeDS组件应用在数个...

1845
来自专栏闻道于事

JFinal框架使用

表单直接提交页面,不用ajax 后台; /** * 修改 */ public void edit() { Strin...

3545

扫码关注云+社区