Spring @Conditional注解從源碼層講解
本文通過(guò)閱讀@Conditional注解、Condition接口、ConditionEvaluator類以及@ConditionalOnProperty(Spring Boot提供)注解源碼,深入分析Spring Conditional的實(shí)現(xiàn)原理。
源碼版本
由于不同版本的spring代碼實(shí)現(xiàn)細(xì)節(jié)可能存在差異,此處記錄一下本文使用的源碼版本:
- spring 5.2.12.RELEASE
- spring-boot 2.3.8.RELEASE
@Conditional注解
標(biāo)注在類或方法上,當(dāng)所有指定Condition都匹配時(shí),才允許向spring容器注冊(cè)組件。
如果一個(gè)@Configuration類標(biāo)注了@Conditional注解,則與該類關(guān)聯(lián)的所有@Bean方法、@Import注解和@ComponentScan注解都將受指定Condition約束。
注解定義:
@Target({ElementType.TYPE, ElementType.METHOD}) public @interface Conditional { /** * 當(dāng)所有指定Condition都匹配時(shí),才允許向spring容器注冊(cè)組件。 * * 這個(gè)泛型的意思是接受所有Condition接口的實(shí)現(xiàn)類。 */ Class<? extends Condition>[] value(); }
Condition接口
Condition接口
匹配條件。
在注冊(cè)BeanDefinition之前立即檢查條件,并且可以根據(jù)當(dāng)時(shí)可以確定的任何條件取消組件注冊(cè)。
Condition必須遵循與BeanFactoryPostProcessor相同的限制,并注意不要與bean實(shí)例交互。如果需要對(duì)與@Configuration bean交互的Condition進(jìn)行更細(xì)粒度的控制,請(qǐng)考慮實(shí)現(xiàn)ConfigurationCondition接口。
接口定義:
public interface Condition { /** * 確定條件是否匹配。 */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
ConditionContext接口
用于獲取BeanDefinitionRegistry、BeanFactory、Environment等。
只有一個(gè)ConditionEvaluator.ConditionContextImpl實(shí)現(xiàn)類(一個(gè)內(nèi)部類):
class ConditionEvaluator { private final ConditionContextImpl context; public ConditionEvaluator(BeanDefinitionRegistry registry, Environment environment, ResourceLoader resourceLoader) { this.context = new ConditionContextImpl(registry, environment, resourceLoader); }
ConditionEvaluator類
用于處理Conditional相關(guān)注解。
核心的邏輯都在這個(gè)類里面:
public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) { // 1. 判斷目標(biāo)組件是否被Conditional標(biāo)注 if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } if (phase == null) { if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } // 2. 獲取到所有的Condition并實(shí)例化 List<Condition> conditions = new ArrayList<>(); for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } // 3. Condition排序 AnnotationAwareOrderComparator.sort(conditions); // 4. Condition匹配驗(yàn)證 for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }
核心功能4步:
- 判斷目標(biāo)組件是否被Conditional標(biāo)注
- 獲取到所有的Condition并實(shí)例化
- Condition排序
- Condition匹配驗(yàn)證
下文將展開(kāi)說(shuō)明這4個(gè)步驟。
判斷目標(biāo)組件是否被Conditional標(biāo)注
AnnotatedTypeMetadata中封裝了目標(biāo)組件的注解元信息,可以通過(guò)他獲取到目標(biāo)組件的注解相關(guān)信息,比如是否被某個(gè)注解標(biāo)注、某個(gè)注解的屬性等。
metadata.isAnnotated(Conditional.class.getName())
AnnotatedTypeMetadata中定義了該方法的實(shí)現(xiàn)方式:
default boolean isAnnotated(String annotationName) { // 通過(guò)MergedAnnotations來(lái)獲取注解標(biāo)注狀態(tài) return getAnnotations().isPresent(annotationName); }
我們?cè)谥暗摹禨pring-@Bean注解源碼分析》中介紹過(guò),此處使用的是StandardAnnotationMetadata實(shí)現(xiàn)類,而該類中的MergedAnnotations是使用以下方式創(chuàng)建和獲取的:
public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) { super(introspectedClass); // 這里使用的是TypeMappedAnnotations實(shí)現(xiàn)類 this.mergedAnnotations = MergedAnnotations.from(introspectedClass, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none()); this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; } @Override public MergedAnnotations getAnnotations() { return this.mergedAnnotations; }
所以isAnnotated的核心判斷邏輯在TypeMappedAnnotations的isPresent方法中:
public boolean isPresent(String annotationType) { if (this.annotationFilter.matches(annotationType)) { return false; } return Boolean.TRUE.equals(scan(annotationType, IsPresent.get(this.repeatableContainers, this.annotationFilter, false))); }
scan方法的詳細(xì)邏輯還是使用java反射的Class.getDeclaredAnnotations()方法來(lái)實(shí)現(xiàn)的,此處不展開(kāi)說(shuō)明,后續(xù)會(huì)有專門的章節(jié)介紹。
獲取到所有的Condition并實(shí)例化
// 2. 獲取到所有的Condition并實(shí)例化 List<Condition> conditions = new ArrayList<>(); // 獲取Condition集 for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { // 創(chuàng)建Condition實(shí)例 Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } // 這個(gè)方法從AnnotatedTypeMetadata中獲取所有Conditional注解指定的Condition類名集 private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true); Object values = (attributes != null ? attributes.get("value") : null); return (List<String[]>) (values != null ? values : Collections.emptyList()); }
AnnotatedTypeMetadata接口getAllAnnotationAttributes方法,這是一個(gè)default方法,用于獲取指定注解的Attribute集:
default MultiValueMap<String, Object> getAllAnnotationAttributes( String annotationName, boolean classValuesAsString) { Adapt[] adaptations = Adapt.values(classValuesAsString, true); return getAnnotations().stream(annotationName) // 獲取到所有的注解信息 .filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)) // 過(guò)濾 .map(MergedAnnotation::withNonMergedAttributes) .collect(MergedAnnotationCollectors.toMultiValueMap(map -> map.isEmpty() ? null : map, adaptations)); }
getAnnotations()返回的是TypeMappedAnnotations類型對(duì)象,他的stream方法:
public <A extends Annotation> Stream<MergedAnnotation<A>> stream(String annotationType) { if (this.annotationFilter == AnnotationFilter.ALL) { return Stream.empty(); } // 這里使用java api創(chuàng)建Stream return StreamSupport.stream(spliterator(annotationType), false); } private <A extends Annotation> Spliterator<MergedAnnotation<A>> spliterator(Object annotationType) { return new AggregatesSpliterator<>(annotationType, getAggregates()); } private List<Aggregate> getAggregates() { List<Aggregate> aggregates = this.aggregates; if (aggregates == null) { // 這里掃描所有注解 aggregates = scan(this, new AggregatesCollector()); if (aggregates == null || aggregates.isEmpty()) { aggregates = Collections.emptyList(); } this.aggregates = aggregates; } return aggregates; } // 這里掃描所有注解 private <C, R> R scan(C criteria, AnnotationsProcessor<C, R> processor) { if (this.annotations != null) { R result = processor.doWithAnnotations(criteria, 0, this.source, this.annotations); return processor.finish(result); } if (this.element != null && this.searchStrategy != null) { return AnnotationsScanner.scan(criteria, this.element, this.searchStrategy, processor); } return null; }
scan方法里面會(huì)通過(guò)遞歸方式使用java反射api從組件類getAnnotations以便獲取到所有的注解信息,代碼較多,此處不展開(kāi)記錄。
Condition排序
- 判斷Condition類實(shí)現(xiàn)了PriorityOrdered接口,并使用PriorityOrdered進(jìn)行排序
- 判斷Condition類實(shí)現(xiàn)了Ordered接口,并使用Ordered進(jìn)行排序
- 從Condition類獲取Order注解,并使用Order注解的值進(jìn)行排序
AnnotationAwareOrderComparator.sort(conditions); public static void sort(List<?> list) { if (list.size() > 1) { list.sort(INSTANCE); // 這里的Comparator使用的是AnnotationAwareOrderComparator實(shí)現(xiàn)類 } }
AnnotationAwareOrderComparator實(shí)現(xiàn)類:
public class AnnotationAwareOrderComparator extends OrderComparator { @Override protected Integer findOrder(Object obj) { // 先使用父類方法獲取排序,其實(shí)就是判斷Ordered實(shí)現(xiàn)并獲取排序,在本例中顯然獲取不到 Integer order = super.findOrder(obj); if (order != null) { return order; } // 從類標(biāo)注的注解中獲取排序 return findOrderFromAnnotation(obj); } private Integer findOrderFromAnnotation(Object obj) { AnnotatedElement element = (obj instanceof AnnotatedElement ? (AnnotatedElement) obj : obj.getClass()); MergedAnnotations annotations = MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY); // 從類標(biāo)注的注解中獲取排序,比如Order注解,此處不展開(kāi)記錄了 Integer order = OrderUtils.getOrderFromAnnotations(element, annotations); if (order == null && obj instanceof DecoratingProxy) { return findOrderFromAnnotation(((DecoratingProxy) obj).getDecoratedClass()); } return order; } @Override public Integer getPriority(Object obj) { if (obj instanceof Class) { return OrderUtils.getPriority((Class<?>) obj); } Integer priority = OrderUtils.getPriority(obj.getClass()); if (priority == null && obj instanceof DecoratingProxy) { return getPriority(((DecoratingProxy) obj).getDecoratedClass()); } return priority; } } public class OrderComparator implements Comparator<Object> { @Override public int compare(Object o1, Object o2) { return doCompare(o1, o2, null); } private int doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider) { // 優(yōu)先級(jí)排序判斷 boolean p1 = (o1 instanceof PriorityOrdered); boolean p2 = (o2 instanceof PriorityOrdered); if (p1 && !p2) { return -1; } else if (p2 && !p1) { return 1; } // 分別獲取到order并比較 int i1 = getOrder(o1, sourceProvider); int i2 = getOrder(o2, sourceProvider); return Integer.compare(i1, i2); } private int getOrder(Object obj, OrderSourceProvider sourceProvider) { Integer order = null; if (obj != null && sourceProvider != null) { Object orderSource = sourceProvider.getOrderSource(obj); if (orderSource != null) { if (orderSource.getClass().isArray()) { for (Object source : ObjectUtils.toObjectArray(orderSource)) { order = findOrder(source); if (order != null) { break; } } } else { order = findOrder(orderSource); } } } // 直接執(zhí)行這里 return (order != null ? order : getOrder(obj)); } protected int getOrder(Object obj) { if (obj != null) { // 繼續(xù)調(diào)用findOrder獲取排序 // 在此處場(chǎng)景下調(diào)用AnnotationAwareOrderComparator的findOrder方法 Integer order = findOrder(obj); if (order != null) { return order; } } return Ordered.LOWEST_PRECEDENCE; } protected Integer findOrder(Object obj) { return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null); } public Integer getPriority(Object obj) { return null; } }
Condition匹配驗(yàn)證
for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } }
@ConditionalOnProperty注解和OnPropertyCondition類
@ConditionalOnProperty注解
@Conditional that checks if the specified properties have a specific value. By default the properties must be present in the Environment and not equal to false. The havingValue() and matchIfMissing() attributes allow further customizations.
@Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {}; String prefix() default ""; String[] name() default {}; String havingValue() default ""; boolean matchIfMissing() default false; }
OnPropertyCondition從env中檢測(cè)指定env參數(shù)是否配置了指定的值,只有滿足時(shí)才允許裝配目標(biāo)組件。
如何判斷被@Conditional注解標(biāo)注
前面已經(jīng)介紹,spring是使用(AnnotatedTypeMetadata)metadata.isAnnotated(Conditional.class.getName())來(lái)判斷組件被@Conditional注解標(biāo)注的。但是@Conditional標(biāo)注在@ConditionalOnProperty注解上,使用普通的(Class)clazz.isAnnotationPresent(Conditional.class)方式無(wú)法判斷,那么metadata.isAnnotated方法是如何判斷的呢?
從上面@ConditionalOnProperty注解定義可以知道,目標(biāo)組件類標(biāo)注了@ConditionalOnProperty注解,@ConditionalOnProperty注解又標(biāo)注了@Conditional注解,這顯然是一個(gè)遞歸,只有從目標(biāo)組件類開(kāi)始遞歸解析,就可以解析出目標(biāo)組件類上的所有注解,以下是一個(gè)我自己編寫的簡(jiǎn)單示例:
@EnableAspectJAutoProxy @Configuration @ComponentScan("org.net5ijy.mybatis.test.config.aop") @ConditionalOnAop public class ServiceAopConfig {} @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(AopCondition.class) public @interface ConditionalOnAop {} @Test public void testAllResolveAnnotations() { // 不解析以下三個(gè)注解 Set<Class<?>> exclude = new HashSet<Class<?>>() {{ add(Target.class); add(Retention.class); add(Documented.class); }}; // 存放所有注解 List<Annotation> annotationList = new ArrayList<>(); // 首先獲取一次組件類直接標(biāo)注的注解 Annotation[] annotations = ServiceAopConfig.class.getDeclaredAnnotations(); // 遞歸解析 while (annotations.length > 0) { annotationList.addAll(Arrays.asList(annotations)); // 遞歸解析 List<Annotation> tmp = new ArrayList<>(); for (Annotation annotation : annotations) { Annotation[] list = annotation.annotationType().getDeclaredAnnotations(); for (Annotation a : list) { if (!exclude.contains(a.annotationType())) { tmp.add(a); } } } annotations = tmp.toArray(new Annotation[0]); } // 循環(huán)結(jié)束之后,ServiceAopConfig直接和間接標(biāo)注的所有注解就都解析出來(lái)了 for (Annotation annotation : annotationList) { System.out.println(annotation); } }
AnnotatedTypeMetadata接口和StandardAnnotationMetadata類
上文介紹了,spring是使用(AnnotatedTypeMetadata)metadata.isAnnotated(Conditional.class.getName())來(lái)判斷組件被@Conditional注解標(biāo)注的。
public interface AnnotatedTypeMetadata { /** * Return annotation details based on the direct annotations of the underlying element. * @return merged annotations based on the direct annotations * @since 5.2 */ MergedAnnotations getAnnotations(); /** * Determine whether the underlying element has an * annotation or meta-annotation of the given type defined. * @return whether a matching annotation is defined */ default boolean isAnnotated(String annotationName) { return getAnnotations().isPresent(annotationName); } // ... }
在我們分析的場(chǎng)景,此處使用的是StandardAnnotationMetadata實(shí)現(xiàn)類。StandardAnnotationMetadata實(shí)現(xiàn)類getAnnotations()返回TypeMappedAnnotations類型對(duì)象。
TypeMappedAnnotations類isPresent方法:
public boolean isPresent(String annotationType) { if (this.annotationFilter.matches(annotationType)) { return false; } // 在scan方法中 return Boolean.TRUE.equals(scan(annotationType, IsPresent.get(this.repeatableContainers, this.annotationFilter, false))); } private <C, R> R scan(C criteria, AnnotationsProcessor<C, R> processor) { if (this.annotations != null) { R result = processor.doWithAnnotations(criteria, 0, this.source, this.annotations); return processor.finish(result); } if (this.element != null && this.searchStrategy != null) { // 執(zhí)行到這里 return AnnotationsScanner.scan(criteria, this.element, this.searchStrategy, processor); } return null; }
AnnotationsScanner.scan方法:
static <C, R> R scan(C context, AnnotatedElement source, SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor) { R result = process(context, source, searchStrategy, processor); return processor.finish(result); } // 層層調(diào)用到processElement方法 private static <C, R> R processElement(C context, AnnotatedElement source, AnnotationsProcessor<C, R> processor) { try { R result = processor.doWithAggregate(context, 0); // 繼續(xù)調(diào)用processor.doWithAnnotations方法 // 這里的processor是TypeMappedAnnotations.IsPresent類型對(duì)象 return (result != null ? result : processor.doWithAnnotations( context, 0, source, getDeclaredAnnotations(source, false))); } catch (Throwable ex) { AnnotationUtils.handleIntrospectionFailure(source, ex); } return null; }
TypeMappedAnnotations.IsPresent類doWithAnnotations方法:
public Boolean doWithAnnotations(Object requiredType, int aggregateIndex, Object source, Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation != null) { Class<? extends Annotation> type = annotation.annotationType(); if (type != null && !this.annotationFilter.matches(type)) { if (type == requiredType || type.getName().equals(requiredType)) { return Boolean.TRUE; } Annotation[] repeatedAnnotations = this.repeatableContainers.findRepeatedAnnotations(annotation); if (repeatedAnnotations != null) { Boolean result = doWithAnnotations( requiredType, aggregateIndex, source, repeatedAnnotations); if (result != null) { return result; } } if (!this.directOnly) { // 這里會(huì)進(jìn)行遞歸解析 AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(type); for (int i = 0; i < mappings.size(); i++) { AnnotationTypeMapping mapping = mappings.get(i); if (isMappingForType(mapping, this.annotationFilter, requiredType)) { return Boolean.TRUE; } } } } } } return null; }
AnnotationTypeMappings.forAnnotationType方法:
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType) { return forAnnotationType(annotationType, AnnotationFilter.PLAIN); } static AnnotationTypeMappings forAnnotationType( Class<? extends Annotation> annotationType, AnnotationFilter annotationFilter) { return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(), annotationFilter); } static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType, RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) { return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType); }
AnnotationTypeMappings對(duì)象:
private AnnotationTypeMappings(RepeatableContainers repeatableContainers, AnnotationFilter filter, Class<? extends Annotation> annotationType) { this.repeatableContainers = repeatableContainers; this.filter = filter; this.mappings = new ArrayList<>(); addAllMappings(annotationType); this.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet); } private void addAllMappings(Class<? extends Annotation> annotationType) { Deque<AnnotationTypeMapping> queue = new ArrayDeque<>(); addIfPossible(queue, null, annotationType, null); // 遞歸解析 while (!queue.isEmpty()) { AnnotationTypeMapping mapping = queue.removeFirst(); this.mappings.add(mapping); addMetaAnnotationsToQueue(queue, mapping); } } private void addMetaAnnotationsToQueue(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source) { Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(), false); // ... } static Annotation[] getDeclaredAnnotations(AnnotatedElement source, boolean defensive) { boolean cached = false; Annotation[] annotations = declaredAnnotationCache.get(source); if (annotations != null) { cached = true; } else { // 這里使用java反射api獲取類標(biāo)注的注解 annotations = source.getDeclaredAnnotations(); if (annotations.length != 0) { // ... } } if (!defensive || annotations.length == 0 || !cached) { return annotations; } return annotations.clone(); }
OnPropertyCondition類
class OnPropertyCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { // 獲取所有的@ConditionalOnProperty注解屬性 List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap( metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName())); List<ConditionMessage> noMatch = new ArrayList<>(); List<ConditionMessage> match = new ArrayList<>(); // 遍歷@ConditionalOnProperty注解屬性 for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) { ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment()); (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage()); } if (!noMatch.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage.of(noMatch)); } return ConditionOutcome.match(ConditionMessage.of(match)); } private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) { Spec spec = new Spec(annotationAttributes); List<String> missingProperties = new ArrayList<>(); List<String> nonMatchingProperties = new ArrayList<>(); // 這里在匹配env參數(shù) // Spec類封裝了prefix, havingValue, names, matchIfMissing等配置 // collectProperties方法將env參數(shù)與prefix, havingValue, names, matchIfMissing等進(jìn)行匹配 spec.collectProperties(resolver, missingProperties, nonMatchingProperties); if (!missingProperties.isEmpty()) { return ConditionOutcome.noMatch(...); } if (!nonMatchingProperties.isEmpty()) { return ConditionOutcome.noMatch(...); } return ConditionOutcome .match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched")); } } public abstract class SpringBootCondition implements Condition { @Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName = getClassOrMethodName(metadata); try { // 此處需要子類實(shí)現(xiàn) ConditionOutcome outcome = getMatchOutcome(context, metadata); logOutcome(classOrMethodName, outcome); recordEvaluation(context, classOrMethodName, outcome); return outcome.isMatch(); } catch (NoClassDefFoundError ex) { throw new IllegalStateException("", ex); } catch (RuntimeException ex) { throw new IllegalStateException("", ex); } } public abstract ConditionOutcome getMatchOutcome( ConditionContext context, AnnotatedTypeMetadata metadata); }
(Spec)spec.collectProperties方法:
private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) { for (String name : this.names) { String key = this.prefix + name; if (resolver.containsProperty(key)) { if (!isMatch(resolver.getProperty(key), this.havingValue)) { nonMatching.add(name); } } else { if (!this.matchIfMissing) { missing.add(name); } } } }
到此這篇關(guān)于Spring @Conditional注解從源碼層講解的文章就介紹到這了,更多相關(guān)Spring @Conditional注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用springmvc的controller層獲取到請(qǐng)求的數(shù)據(jù)方式
這篇文章主要介紹了使用springmvc的controller層獲取到請(qǐng)求的數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08為spring get請(qǐng)求添加自定義的參數(shù)處理操作(如下劃線轉(zhuǎn)駝峰)
這篇文章主要介紹了為spring get請(qǐng)求添加自定義的參數(shù)處理操作(如下劃線轉(zhuǎn)駝峰),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09Map集合中獲取key-value值的實(shí)現(xiàn)方法
這篇文章主要介紹了Map集合中獲取key-value值的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Java給實(shí)體每一個(gè)字段賦默認(rèn)值詳細(xì)代碼示例
這篇文章主要給大家介紹了關(guān)于Java給實(shí)體每一個(gè)字段賦默認(rèn)值的相關(guān)資料,在編程過(guò)程中有時(shí)會(huì)出現(xiàn)這樣一種情況,在查詢無(wú)結(jié)果時(shí)我們需要給實(shí)體賦默認(rèn)值,需要的朋友可以參考下2023-09-09SpringBoot整合Druid數(shù)據(jù)庫(kù)連接池的方法
Druid是Java語(yǔ)言中最好的數(shù)據(jù)庫(kù)連接池。Druid能夠提供強(qiáng)大的監(jiān)控和擴(kuò)展功能。這篇文章主要介紹了SpringBoot整合Druid數(shù)據(jù)庫(kù)連接池的方法,需要的朋友可以參考下2020-07-07