bootdefault和configuration_springboot常用注解、包引入和自动配置功能解读
springboot使用起来确实很方便,做到开箱即用,减少了许多繁琐的配置。不过在使用过程中时常会想,为啥会这样方便,springboot为我们做哪些工作。或者是我们在使用的过程中,会遇到springboot不满足的情况,我们要去了解内部实现机制,然后才能改进。
过去我们对这种方式确很少去了解,或者是了解了一些但是没有彻底搞清楚。今天我们就学习一下springboot相关的几个问题,希望能够揭开一些疑问。相信对这些看似比较基础的知识地理解,会给我们设计程序带来好的思路。
注解
spring最开始大量使用xml进行配置,当然也支持注解进行配置,springboot做了很多自动化的工作,进行默认配置,在此过程中将注解发挥到极致。所以在此之前先回顾一下注解的基本知识。
注解为代码添加信息提供了一种形式化的方法,使得我们可以在后面某个时刻非常方便地使用这些数据。java5引入注解的,有需要多好处,完整地描述程序需要的信息,相比于增加其他非java语言的文件对程序描述,这样使得代码可读性变差,并且不容易检查。可以生成新的描述符文件设置是新的类定义,可以减少许多重复的模板代码。
注解定义和常见的注解
java中内置了几个常见的注解,这也是我们经常在代码中见到的。
@Override 用来表示覆盖父类中的方法,如果方法签名写错,编译器将会报错,该注解是可选的。
@Deprecated 表示废弃的方法或者字段,如果程序中使用了会报出警告。
@SuppressWarnings 关闭不当的编译器警告信息
注解的定义需要使用到元注解,顾名思义,元注解就是用来定义注解的注解,例如:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}
这样就定义了一个注解,注解的定义和接口定义非常类似,编译后也会生成一个class文件。如果像上面的注解一样,不包含任何元素,叫做标记注解。可以包含元素,就类似于接口的方法定义。但是和方法定义又有一些区别,访问权修饰符为默认或者public,类型为八种基本类型和String,Enum,Class,annotations类型,以及它们的数组。
值得说明的是如果为annotation类型,说明这个注解是嵌套注解。成员名字自定义,另外有一点不同的是,相比接口方法的定义,这里可以设置默认值:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
public String value() default "some value";
public int test();//没有默认值
}
如果成员里面有一个名字是value,并且是唯一一个被赋值的成员,那么不需要写出成员名字直接赋值,例如:
@Test("a")
public void hello(){
…
}
四种元注解:
@Target 表示注解作用的对象,ElementType参数包括:
CONSTRUCTOR,构造器声明
FIELD,域声明
LOCAL_VARIABLE,局部变量声明
METHOD,方法声明
PACKAGE,包声明
PARAMETER,参数声明
TYPE,类,接口包括注解类型或enum声明,对于注解类型特别要提到的是,这样可以自定义注解的注解。
@Retention Retention是保留的意思,这个注解说的是被注解的注解被保留的级别,RetentionPolicy可选有:
SOURCE,保留在源码中,也就是说编译成class文件不会有该注解,被编译器丢弃了
CLASS,保留在class文件中,但是在虚拟机运行的时候会丢弃该注解
RUNTIME,保留在运行期,一般都是通过反射机制读取注解的信息
@Documented 表示注解包含在javadoc中
@Inherited 表示子类可以继承父类的注解
上面说到RUNTIME的注解,类都实现了AnnotatedElement接口,具有getAnnotation()的实现方法,可以获取到某个类型的注解:
Test test = method.getAnnocation(Test.class);
@Enable*注解
既然我们搞清楚了注解的基本使用方法,那么让我们还看一看springboot是怎样使用注解的。对于很多功能的启用的开关是加上了@Enable*的注解,例如开启eureka:
@EnableDiscoveryClient
以此为例,搞清楚这类注解的使用原理,首先看这个注解的定义:
@Target(ElementType.TYPE) //作用在类上面
@Retention(RetentionPolicy.RUNTIME) //jvm使用该注解
@Documented //javadoc包含该注解
@Inherited //子类可以继承该注解
@Import(EnableDiscoveryClientImportSelector.class) //自定义元注解
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
*/
boolean autoRegister() default true; //将该应用注册到eureka server
}
元素就一个boolean类型的,直接说明的是这里使用了一个自定义的元注解,也就是该注解的@Target是ElementType.TYPE,其中的TYPE指的是一个注解定义的类:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class>[] value();
}
这个@Import注解非常重要,可以看到有一个唯一的默认的元素,这个元素名字叫value,后面为元素赋值的时候,可以不写value,类型是一个class数组。我们知道这个@Import注解是为了引入一个类,下面来看一下注解处理器做了什么操作:
package org.springframework.context.annotation;
/**
* Recursively collect all declared {@code @Import} values. Unlike most
* meta-annotations it is valid to have several {@code @Import}s declared with
* different values; the usual process of returning values from the first
* meta-annotation on a class is not sufficient.
*
For example, it is common for a {@code @Configuration} class to declare direct
* {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
* annotation.
* @param sourceClass the class to search
* @param imports the imports collected so far
* @param visited used to track visited classes to prevent infinite recursion
* @throws IOException if there is any problem reading metadata from the named class
*/
private void collectImports(SourceClass sourceClass, Set imports, Set visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
上面的代码片段是从spring中摘录出来,将@Import注解中value的类加到集合中,后续生成bean进行加载。也就是说会把EnableDiscoveryClientImportSelector这个类生成bean加载到上下文中。
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
boolean autoRegister = attributes.getBoolean("autoRegister");
if (autoRegister) {
List importsList = new ArrayList<>(Arrays.asList(imports));
importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = importsList.toArray(new String[0]);
}
return imports;
}
@Override
protected boolean isEnabled() {
return new RelaxedPropertyResolver(getEnvironment()).getProperty(
"spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
}
@Override
protected boolean hasDefaultFactory() {
return true;
}
}
这个类由于继承了SpringFactoryImportSelector,spring会进行import操作,在这个类里面,引入了org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration,这个类,所以使得该应用开启了eureka客户端的功能。
自动配置
通过源码可以发现@SpringBootApplication注解定义中有,有元注解@EnableAutoConfiguration,让我们看一下这个定义:
package org.springframework.boot.autoconfigure;
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
所有自动配置功能都由@AutoConfigurationPackage和@Import(EnableAutoConfigurationImportSelector.class)这两个注解来决定。
@AutoConfigurationPackage
这个注解的源码如下:
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Indicates that the package containing the annotated class should be registered with
* {@link AutoConfigurationPackages}.
*
* @author Phillip Webb
* @since 1.3.0
* @see AutoConfigurationPackages
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
通过注释可以知道,这个注解的作用是将被该注解进行注解的类所在的包进行扫描。EnableAutoConfiguration被注解了,而该类在org.springframework.boot.autoconfigure包下面,所以会扫描该包下的类,声明成bean的,将会生成bean。
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
可以看到将BasePackages注册在了注册中心,BasePackages中含有包名:
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition
.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0,
addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
这个bean的名字叫做AutoConfigurationPackages,需要说明的是,此时并没有扫描包。
正如该类的注释所说,是为了存储需要自动配置的包,以便于后面的扫描器进行扫描。
* Class for storing auto-configuration packages for reference later (e.g. by JPA entity
* scanner).
也许此时会有个疑问,为啥不用@ComponentScan进行包扫描?
实际上,在org.springframework.boot.autoconfigure包下面,并不是所有的组件都需要包扫描,只有几个组件的集成才需要,例如cassandra、jpa等,一般都是这样的代码:
@Bean
@ConditionalOnMissingBean
public CassandraMappingContext cassandraMapping(
CassandraCustomConversions conversions) throws ClassNotFoundException {
CassandraMappingContext context = new CassandraMappingContext();
List packages = EntityScanPackages.get(this.beanFactory)
.getPackageNames();
if (packages.isEmpty() && AutoConfigurationPackages.has(this.beanFactory)) {
packages = AutoConfigurationPackages.get(this.beanFactory);
}
if (!packages.isEmpty()) {
context.setInitialEntitySet(CassandraEntityClassScanner.scan(packages));
}
if (StringUtils.hasText(this.properties.getKeyspaceName())) {
context.setUserTypeResolver(new SimpleUserTypeResolver(this.cluster,
this.properties.getKeyspaceName()));
}
context.setCustomConversions(conversions);
return context;
}
上述代码说明,如果存在@EntityScan注解的包,则只需要scan该包即可,如果不存在则将autoconfigure都scan了。
需要说明的是该注解,不是每个组件的自动配置都会用到,只有一部分用到,比较重要的是下面的自动配置功能。
EnableAutoConfigurationImportSelector的作用
下面是最关键的源码:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(AutoConfigurationImportSelector.class)) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
这个类会选择哪些类会被引入并且生成一个Bean,如果属性spring.boot.enableautoconfiguration为false,将会关闭自动配置。如果开启的话,会将META-INFO目录下的配置文件的配置读进来,选择需要引入的Bean。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
...
以RabbitMq进行举例说明,需要引入RabbitAutoConfiguration这个Bean。
RabbitAutoConfiguration的作用
下面是该类的定义的一部分:
@Configuration
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
@Configuration
@ConditionalOnMissingBean(ConnectionFactory.class)
@Configuration注解说明是一个配置Bean。
@EnableConfigurationProperties(RabbitProperties.class),该注解的源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
/**
* Convenient way to quickly register {@link ConfigurationProperties} annotated beans
* with Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@link ConfigurationProperties} annotated beans to register
*/
Class>[] value() default {};
}
其中EnableConfigurationPropertiesImportSelector在生成Bean的时候,如果发现没有RabbitProperties这个bean,会生成这个bean,然后我们又发现这个bean有@ConfigurationProperties,凡事有这个注解的Bean都会将配置文件的属性映射到这个Bean的字段上。
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {
所以此时会有RabbitProperties这个Bean存在。基于此会生成许多其他配置Bean,例如CachingConnectionFactory,这个Bean就含有文件的配置信息,供后面RabbitMq连接通信使用。这样就完成了自动配置。
@Conditional条件注解
上面的注解中用到了条件注解,是值得关注的地方。上面通过自动配置,得到了RabbitMq的连接配置Bean也就是CachingConnectionFactory,这个类的定义是在org.springframework.amqp.rabbit.connection中,实际上使用rabbit的核心类可能是在另外一个jar包中,也就是说了配置类,但是没有rabbitmq的操作类也没有作用,怎样确保有核心类,然后再进行加载配置Bean呢?还有一个问题需要思考,如果用户自定义了一个连接配置Bean,而不是使用自动配置Bean?这些问题该怎么解决呢?
实际上springboot定义了非常灵活的条件注解:
@ConditionalOnMissingBean(ConnectionFactory.class)
再去看看这个注解的定义:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
/**
* The class type of bean that should be checked. The condition matches when each
* class specified is missing in the {@link ApplicationContext}.
* @return the class types of beans to check
*/
Class>[] value() default {};
/**
* The class type names of bean that should be checked. The condition matches when
* each class specified is missing in the {@link ApplicationContext}.
* @return the class type names of beans to check
*/
String[] type() default {};
/**
@Conditional这个注解是spring中定义的,它的成员是一些条件类,比如现在是OnBeanCondition.class。这里面核心的方法是判断ConditionalOnMissingBean中的value这个Bean是否存在:
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnMissingBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
if (matchResult.isAnyMatched()) {
String reason = createOnMissingBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnMissingBean.class, spec)
.because(reason));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
.didNotFind("any beans").atAll();
}
除了这些条件以外,还有其他条件,列举如下:
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean;
@ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项
这样我们就把整个自动配置大致流程搞清楚了。
4+
bootdefault和configuration_springboot常用注解、包引入和自动配置功能解读相关推荐
- Spring Boot 常用注解
@SpringBootApplication 查看源码可发现,@SpringBootApplication是一个复合注解,包含了@SpringBootConfiguration,@EnableAuto ...
- SpringBoot2.1.5 (4)---SpringBoot 常用注解说明
SpringBoot2.1.5 (4)---SpringBoot 常用注解说明 @SpringBootApplication Spring Boot的项目一般都会有*Application的入口类,入 ...
- Spring Boot 自动配置的原理、核心注解以及利用自动配置实现了自定义 Starter 组件
本章内容 自定义属性快速入门 外化配置 自动配置 自定义创建 Starter 组件 摘录:读书是读完这些文字还要好好用心去想想,写书也一样,做任何事也一样 图 2 第二章目录结构图 第 2 章 Spr ...
- springboot学习(五)自动配置原理及@Conditional派生注解
一.自动配置原理 1.SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration 分析源码发现 2.@EnableAutoConfiguratio ...
- 40 个 Spring Boot 常用注解
以下文章来源方志朋的博客,回复"666"获面试宝典 作者 | 谭朝红 链接 | ramostear.com 一.Spring Web MVC 与 Spring Bean 注解 Sp ...
- 40 个 SpringBoot 常用注解
以下文章来源方志朋的博客,回复"666"获面试宝典 来源:https://ramostear.com/ 一.Spring Web MVC 与 Spring Bean 注解 Spri ...
- Spring系列之Spring常用注解总结
参看博客:https://www.cnblogs.com/xiaoxi/p/5935009.html 传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做有两个缺 ...
- Spring常用注解总结
传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做有两个缺点: 1.如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大:如果按需求分开.xml文 ...
- spring autowired idea都匹配上了_你清楚这几个Spring常用注解吗?
作者:平凡希http://cnblogs.com/xiaoxi/p/5935009.html 传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop.事物,这么做有两个缺点: 如 ...
最新文章
- java保留两位小数_java使double保留两位小数的多方法 java保留两位小数
- 06Chrome调试工具
- 熟练Linux ,先从这 26 个命令开始吧
- Tapestry IoC Decorator
- centos7二进制安装php,Centos7下编译安装配置Nginx+PHP+MySql环境
- c语言控制数码管显示时间,数码管显示时间整点报时C语言实例
- webpack 3 零基础入门教程 #12 - 如何使用模块热替换 HMR 来处理 CSS
- 字典树 之 hdu 1247
- POJ 1915 经典马步 双向bfs
- Hbase ImmutableBytesWritable数据类型
- Android Screen Orientation
- Apache Tika源码研究(三)
- 微信小程序 获取用户昵称、头像、手机号
- Java零基础P20使用IDEA开发
- 实战goldengate:安装配置+数据初始化+单向DML复制
- switch完成输出星期一到七
- attrib批量显示文件夹_怎样批量修改文件夹或文件的系统隐藏属性
- 安全测试:xss,cookie,xst注入攻防
- Ubuntu 18.04 从零开始安装显卡驱动、配置MMDetection3D环境
- 对android小程序的结论,微信小程序引入外部字体总结(针对安卓加载缓慢问题)...
热门文章
- [工具] 解决sublime text运行javascript console无输出问题
- 关于驰骋表单引擎中字段扩展设置对文本框Pop窗体返回值的升级说明 2012/11/13...
- 线性直接变换方法对摄像机进行标定
- 汉邦高科范俊峰:关于透雾摄像机那点事
- I2C总线之(三)---以C语言理解IIC
- 数字后端——时钟树综合
- HALCON示例程序classify_image_class_gmm.hdev使用GMM分类器分割彩色图片
- 学习笔记(16):Python网络编程并发编程-开启子进程的两种方式
- oracle中区间大小,Oracle的逻辑结构(表空间、段、区间、块)——总结
- Java点击按钮div缩放_[Java教程]怎样给div增加resize事件