日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

bootdefault和configuration_springboot常用注解、包引入和自动配置功能解读

發(fā)布時(shí)間:2023/12/10 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 bootdefault和configuration_springboot常用注解、包引入和自动配置功能解读 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

springboot使用起來確實(shí)很方便,做到開箱即用,減少了許多繁瑣的配置。不過在使用過程中時(shí)常會(huì)想,為啥會(huì)這樣方便,springboot為我們做哪些工作。或者是我們?cè)谑褂玫倪^程中,會(huì)遇到springboot不滿足的情況,我們要去了解內(nèi)部實(shí)現(xiàn)機(jī)制,然后才能改進(jìn)。

過去我們對(duì)這種方式確很少去了解,或者是了解了一些但是沒有徹底搞清楚。今天我們就學(xué)習(xí)一下springboot相關(guān)的幾個(gè)問題,希望能夠揭開一些疑問。相信對(duì)這些看似比較基礎(chǔ)的知識(shí)地理解,會(huì)給我們?cè)O(shè)計(jì)程序帶來好的思路。

注解

spring最開始大量使用xml進(jìn)行配置,當(dāng)然也支持注解進(jìn)行配置,springboot做了很多自動(dòng)化的工作,進(jìn)行默認(rèn)配置,在此過程中將注解發(fā)揮到極致。所以在此之前先回顧一下注解的基本知識(shí)。

注解為代碼添加信息提供了一種形式化的方法,使得我們可以在后面某個(gè)時(shí)刻非常方便地使用這些數(shù)據(jù)。java5引入注解的,有需要多好處,完整地描述程序需要的信息,相比于增加其他非java語言的文件對(duì)程序描述,這樣使得代碼可讀性變差,并且不容易檢查。可以生成新的描述符文件設(shè)置是新的類定義,可以減少許多重復(fù)的模板代碼。

注解定義和常見的注解

java中內(nèi)置了幾個(gè)常見的注解,這也是我們經(jīng)常在代碼中見到的。

@Override 用來表示覆蓋父類中的方法,如果方法簽名寫錯(cuò),編譯器將會(huì)報(bào)錯(cuò),該注解是可選的。

@Deprecated 表示廢棄的方法或者字段,如果程序中使用了會(huì)報(bào)出警告。

@SuppressWarnings 關(guān)閉不當(dāng)?shù)木幾g器警告信息

注解的定義需要使用到元注解,顧名思義,元注解就是用來定義注解的注解,例如:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Test{}

這樣就定義了一個(gè)注解,注解的定義和接口定義非常類似,編譯后也會(huì)生成一個(gè)class文件。如果像上面的注解一樣,不包含任何元素,叫做標(biāo)記注解。可以包含元素,就類似于接口的方法定義。但是和方法定義又有一些區(qū)別,訪問權(quán)修飾符為默認(rèn)或者public,類型為八種基本類型和String,Enum,Class,annotations類型,以及它們的數(shù)組。

值得說明的是如果為annotation類型,說明這個(gè)注解是嵌套注解。成員名字自定義,另外有一點(diǎn)不同的是,相比接口方法的定義,這里可以設(shè)置默認(rèn)值:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Test{

public String value() default "some value";

public int test();//沒有默認(rèn)值

}

如果成員里面有一個(gè)名字是value,并且是唯一一個(gè)被賦值的成員,那么不需要寫出成員名字直接賦值,例如:

@Test("a")

public void hello(){

}

四種元注解:

@Target 表示注解作用的對(duì)象,ElementType參數(shù)包括:

CONSTRUCTOR,構(gòu)造器聲明

FIELD,域聲明

LOCAL_VARIABLE,局部變量聲明

METHOD,方法聲明

PACKAGE,包聲明

PARAMETER,參數(shù)聲明

TYPE,類,接口包括注解類型或enum聲明,對(duì)于注解類型特別要提到的是,這樣可以自定義注解的注解。

@Retention Retention是保留的意思,這個(gè)注解說的是被注解的注解被保留的級(jí)別,RetentionPolicy可選有:

SOURCE,保留在源碼中,也就是說編譯成class文件不會(huì)有該注解,被編譯器丟棄了

CLASS,保留在class文件中,但是在虛擬機(jī)運(yùn)行的時(shí)候會(huì)丟棄該注解

RUNTIME,保留在運(yùn)行期,一般都是通過反射機(jī)制讀取注解的信息

@Documented 表示注解包含在javadoc中

@Inherited 表示子類可以繼承父類的注解

上面說到RUNTIME的注解,類都實(shí)現(xiàn)了AnnotatedElement接口,具有g(shù)etAnnotation()的實(shí)現(xiàn)方法,可以獲取到某個(gè)類型的注解:

Test test = method.getAnnocation(Test.class);

@Enable*注解

既然我們搞清楚了注解的基本使用方法,那么讓我們還看一看springboot是怎樣使用注解的。對(duì)于很多功能的啟用的開關(guān)是加上了@Enable*的注解,例如開啟eureka:

@EnableDiscoveryClient

以此為例,搞清楚這類注解的使用原理,首先看這個(gè)注解的定義:

@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; //將該應(yīng)用注冊(cè)到eureka server

}

元素就一個(gè)boolean類型的,直接說明的是這里使用了一個(gè)自定義的元注解,也就是該注解的@Target是ElementType.TYPE,其中的TYPE指的是一個(gè)注解定義的類:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface Import {

/**

* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}

* or regular component classes to import.

*/

Class>[] value();

}

這個(gè)@Import注解非常重要,可以看到有一個(gè)唯一的默認(rèn)的元素,這個(gè)元素名字叫value,后面為元素賦值的時(shí)候,可以不寫value,類型是一個(gè)class數(shù)組。我們知道這個(gè)@Import注解是為了引入一個(gè)類,下面來看一下注解處理器做了什么操作:

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的類加到集合中,后續(xù)生成bean進(jìn)行加載。也就是說會(huì)把EnableDiscoveryClientImportSelector這個(gè)類生成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;

}

}

這個(gè)類由于繼承了SpringFactoryImportSelector,spring會(huì)進(jìn)行import操作,在這個(gè)類里面,引入了org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration,這個(gè)類,所以使得該應(yīng)用開啟了eureka客戶端的功能。

自動(dòng)配置

通過源碼可以發(fā)現(xiàn)@SpringBootApplication注解定義中有,有元注解@EnableAutoConfiguration,讓我們看一下這個(gè)定義:

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 {};

}

所有自動(dòng)配置功能都由@AutoConfigurationPackage和@Import(EnableAutoConfigurationImportSelector.class)這兩個(gè)注解來決定。

@AutoConfigurationPackage

這個(gè)注解的源碼如下:

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 {

}

通過注釋可以知道,這個(gè)注解的作用是將被該注解進(jìn)行注解的類所在的包進(jìn)行掃描。EnableAutoConfiguration被注解了,而該類在org.springframework.boot.autoconfigure包下面,所以會(huì)掃描該包下的類,聲明成bean的,將會(huì)生成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注冊(cè)在了注冊(cè)中心,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);

}

}

這個(gè)bean的名字叫做AutoConfigurationPackages,需要說明的是,此時(shí)并沒有掃描包。

正如該類的注釋所說,是為了存儲(chǔ)需要自動(dòng)配置的包,以便于后面的掃描器進(jìn)行掃描。

* Class for storing auto-configuration packages for reference later (e.g. by JPA entity

* scanner).

也許此時(shí)會(huì)有個(gè)疑問,為啥不用@ComponentScan進(jìn)行包掃描?

實(shí)際上,在org.springframework.boot.autoconfigure包下面,并不是所有的組件都需要包掃描,只有幾個(gè)組件的集成才需要,例如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了。

需要說明的是該注解,不是每個(gè)組件的自動(dòng)配置都會(huì)用到,只有一部分用到,比較重要的是下面的自動(dòng)配置功能。

EnableAutoConfigurationImportSelector的作用

下面是最關(guān)鍵的源碼:

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

}

這個(gè)類會(huì)選擇哪些類會(huì)被引入并且生成一個(gè)Bean,如果屬性spring.boot.enableautoconfiguration為false,將會(huì)關(guān)閉自動(dòng)配置。如果開啟的話,會(huì)將META-INFO目錄下的配置文件的配置讀進(jìn)來,選擇需要引入的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進(jìn)行舉例說明,需要引入RabbitAutoConfiguration這個(gè)Bean。

RabbitAutoConfiguration的作用

下面是該類的定義的一部分:

@Configuration

@ConditionalOnClass({ RabbitTemplate.class, Channel.class })

@EnableConfigurationProperties(RabbitProperties.class)

@Import(RabbitAnnotationDrivenConfiguration.class)

public class RabbitAutoConfiguration {

@Configuration

@ConditionalOnMissingBean(ConnectionFactory.class)

@Configuration注解說明是一個(gè)配置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的時(shí)候,如果發(fā)現(xiàn)沒有RabbitProperties這個(gè)bean,會(huì)生成這個(gè)bean,然后我們又發(fā)現(xiàn)這個(gè)bean有@ConfigurationProperties,凡事有這個(gè)注解的Bean都會(huì)將配置文件的屬性映射到這個(gè)Bean的字段上。

@ConfigurationProperties(prefix = "spring.rabbitmq")

public class RabbitProperties {

所以此時(shí)會(huì)有RabbitProperties這個(gè)Bean存在。基于此會(huì)生成許多其他配置Bean,例如CachingConnectionFactory,這個(gè)Bean就含有文件的配置信息,供后面RabbitMq連接通信使用。這樣就完成了自動(dòng)配置。

@Conditional條件注解

上面的注解中用到了條件注解,是值得關(guān)注的地方。上面通過自動(dòng)配置,得到了RabbitMq的連接配置Bean也就是CachingConnectionFactory,這個(gè)類的定義是在org.springframework.amqp.rabbit.connection中,實(shí)際上使用rabbit的核心類可能是在另外一個(gè)jar包中,也就是說了配置類,但是沒有rabbitmq的操作類也沒有作用,怎樣確保有核心類,然后再進(jìn)行加載配置Bean呢?還有一個(gè)問題需要思考,如果用戶自定義了一個(gè)連接配置Bean,而不是使用自動(dòng)配置Bean?這些問題該怎么解決呢?

實(shí)際上springboot定義了非常靈活的條件注解:

@ConditionalOnMissingBean(ConnectionFactory.class)

再去看看這個(gè)注解的定義:

@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這個(gè)注解是spring中定義的,它的成員是一些條件類,比如現(xiàn)在是OnBeanCondition.class。這里面核心的方法是判斷ConditionalOnMissingBean中的value這個(gè)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 系統(tǒng)的java版本是否符合要求

@ConditionalOnBean 容器中存在指定Bean;

@ConditionalOnMissingBean 容器中不存在指定Bean;

@ConditionalOnExpression 滿足SpEL表達(dá)式指定

@ConditionalOnClass 系統(tǒng)中有指定的類

@ConditionalOnMissingClass 系統(tǒng)中沒有指定的類

@ConditionalOnSingleCandidate 容器中只有一個(gè)指定的Bean,或者這個(gè)Bean是首選Bean

@ConditionalOnProperty 系統(tǒng)中指定的屬性是否有指定的值

@ConditionalOnResource 類路徑下是否存在指定資源文件

@ConditionalOnWebApplication 當(dāng)前是web環(huán)境

@ConditionalOnNotWebApplication 當(dāng)前不是web環(huán)境

@ConditionalOnJndi JNDI存在指定項(xiàng)

這樣我們就把整個(gè)自動(dòng)配置大致流程搞清楚了。

4+

總結(jié)

以上是生活随笔為你收集整理的bootdefault和configuration_springboot常用注解、包引入和自动配置功能解读的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。