欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?Boot?詳細(xì)分析Conditional自動(dòng)化配置注解

 更新時(shí)間:2022年07月13日 09:50:12   作者:麥神-mirson  
首先我們先了解一下@Conditional注解,@Conditional是Spring4新提供的注解,它的作用是按照一定的條件進(jìn)行判斷,需要注入的Bean滿足給定條件才可以注入到Spring?IOC容器中

1. Spring Boot Condition功能與作用

@Conditional是基于條件的自動(dòng)化配置注解, 由Spring 4框架推出的新特性。

在一個(gè)服務(wù)工程, 通常會(huì)存在多個(gè)配置環(huán)境, 比如常見(jiàn)的DEV(開(kāi)發(fā)環(huán)境)、SIT(系統(tǒng)內(nèi)部集成測(cè)試環(huán)境)、UAT(用戶驗(yàn)收測(cè)試環(huán)境)、PRD(生產(chǎn)環(huán)境)等。在Spring3系列版本中通過(guò)@Profile實(shí)現(xiàn),傳入對(duì)應(yīng)的環(huán)境標(biāo)識(shí), 系統(tǒng)自動(dòng)加載不同環(huán)境的配置。spring4版本正式推出Condition功能, 在spring5版本, @Profile做了改進(jìn),底層是通過(guò)Condition實(shí)現(xiàn), 看下Condition接口的UML結(jié)構(gòu):

可以看到兩個(gè)抽象類應(yīng)用實(shí)現(xiàn)了Condition接口, 一個(gè)是Spring Context下的ProfileCondition, 另一個(gè)就是SpringBootCondition。

SpringBootCondition下面有很多實(shí)現(xiàn)類,也是滿足Spring

Boot的各種Condition需要, 圖中只是列出了部分實(shí)現(xiàn), 每個(gè)實(shí)現(xiàn)類下面, 都會(huì)有對(duì)應(yīng)的注解來(lái)協(xié)助處理。

2. Conditional條件化系列注解介紹

Conditional的注解Conditional的處理類Conditional的說(shuō)明
@ConditionalOnBeanOnBeanConditionSpring容器中是否存在對(duì)應(yīng)的實(shí)例??梢酝ㄟ^(guò)實(shí)例的類型、類名、注解、昵稱去容器中查找(可以配置從當(dāng)前容器中查找或者父容器中查找或者兩者一起查找)
@ConditionalOnClassOnClassCondition類加載器中是否存在對(duì)應(yīng)的類??梢酝ㄟ^(guò)Class指定(value屬性)或者Class的全名指定(name屬性)如果是多個(gè)類或者多個(gè)類名的話,關(guān)系是”與”關(guān)系,也就是說(shuō)這些類或者類名都必須同時(shí)在類加載器中存在
@ConditionalOnExpressionOnExpressionCondition判斷SpEL 表達(dá)式是否成立
@ConditionalOnMissingBeanOnBeanConditionSpring容器中是否缺少對(duì)應(yīng)的實(shí)例。可以通過(guò)實(shí)例的類型、類名、注解、昵稱去容器中查找(可以配置從當(dāng)前容器中查找或者父容器中查找或者兩者一起查找)
@ConditionalOnMissingClassOnClassCondition跟ConditionalOnClass的處理邏輯一樣,只是條件相反,在類加載器中不存在對(duì)應(yīng)的類
@ConditionalOnPropertyOnPropertyCondition應(yīng)用環(huán)境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing屬性。prefix表示屬性名的前綴,name是屬性名,havingValue是具體的屬性值,matchIfMissing是個(gè)boolean值,如果屬性不存在,這個(gè)matchIfMissing為true的話,會(huì)繼續(xù)驗(yàn)證下去,否則屬性不存在的話直接就相當(dāng)于匹配不成功
@ConditionalOnResourceOnResourceCondition是否存在指定的資源文件。只有一個(gè)屬性resources,是個(gè)String數(shù)組。會(huì)從類加載器中去查詢對(duì)應(yīng)的資源文件是否存在
@ConditionalOnSingleCandidateOnBeanConditionSpring容器中是否存在且只存在一個(gè)對(duì)應(yīng)的實(shí)例。只有3個(gè)屬性value、type、search。跟ConditionalOnBean中的這3種屬性值意義一樣
@ConditionalOnWebApplicationOnWebApplicationCondition應(yīng)用程序是否是Web程序,沒(méi)有提供屬性,只是一個(gè)標(biāo)識(shí)。會(huì)從判斷Web程序特有的類是否存在,環(huán)境是否是Servlet環(huán)境,容器是否是Web容器等

SpringBootCondition下面包含的主要條件化注解說(shuō)明:

  • @ConditionalOnBean: 當(dāng)Spring容器存在某個(gè)Bean則觸發(fā)實(shí)現(xiàn)。
  • @ConditionalOnMissingBean: 當(dāng)Spring容器不存在某個(gè)Bean則不觸發(fā)。
  • @ConditionalOnSingleCandidate: 當(dāng)Spring容器中只有一個(gè)指定Bean,或者多個(gè)時(shí)是首選 Bean。
  • @ConditionalOnClass: 當(dāng)環(huán)境路徑下有指定的類, 則觸發(fā)實(shí)現(xiàn)。
  • @ConditionalOnMissingClass: 當(dāng)環(huán)境路徑下沒(méi)有指定類則不觸發(fā)實(shí)現(xiàn)。
  • @ConditionalOnProperty: 判斷屬性如果存在指定的值則觸發(fā)實(shí)現(xiàn)。
  • @ConditionalOnResource: 判斷存在指定的資源則觸發(fā)實(shí)現(xiàn)。
  • @ConditionalOnExpression: 基于 某個(gè)SpEL 表達(dá)式作判斷實(shí)現(xiàn)。
  • @ConditionalOnJava:基于JDK的版本作判斷實(shí)現(xiàn)。
  • @ConditionalOnJndi:基于指定的 JNDI 作判斷實(shí)現(xiàn)。
  • @ConditionalOnNotWebApplication:判斷當(dāng)前項(xiàng)目定義如果不是 Web 應(yīng)用則不觸發(fā)實(shí)現(xiàn)。
  • @ConditionalOnWebApplication:判斷當(dāng)前項(xiàng)目定義如果是 Web 應(yīng)用則觸發(fā)實(shí)現(xiàn)。

它們內(nèi)部都是基于@Conditional實(shí)現(xiàn)。

3. Conditional條件化注解的實(shí)現(xiàn)原理

上面看到, Spring Boot 有很多內(nèi)置的多條件化注解, 都是基于@Conditional實(shí)現(xiàn),

那么@Conditionnal又是如何實(shí)現(xiàn)? 它的作用范圍是什么? 是如何生效的?

Conditional源碼

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
	/**
	 * contion條件的具體實(shí)現(xiàn)類, 必須實(shí)現(xiàn)Condition接口
	 */
	Class<? extends Condition>[] value();
}

@Target標(biāo)示它的作用范圍是在類或方法上。它是如何被調(diào)用生效的? 我們來(lái)寫(xiě)下測(cè)試類, 進(jìn)行調(diào)試,

分析調(diào)用棧。

自定義Conditional

創(chuàng)建com.mirson.spring.boot.research.condition.CustomerMatchCondition

@Log4j2
public class CustomerMatchCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        log.info("Process in CustomerMatchCondition.matches method. ");
        return false;
    }
}

創(chuàng)建引用該Condition的配置類,

com.mirson.spring.boot.research.startup.CusomterConditional

@Configuration
@Conditional(CustomerMatchCondition.class)
@Log4j2
public class CusomterConditional {
    public Object newObj() {
        log.info("Process in CusomterConditional.newObj method.");
        return new Object();
    }
}

啟動(dòng)調(diào)試,分析調(diào)用棧:

可以看到, 先從第一步調(diào)用refresh調(diào)用容器初始化,再到第二步處理Bean配置定義信息, 最后調(diào)用注解的doScan掃描方法,這樣就能夠找到我們自定義的CustomerMatchCondition,調(diào)用Condtion定義的matches接口實(shí)現(xiàn), 決定是否要執(zhí)行CustomerConditional 的newObject方法。

4. Conditional核心之matches匹配接口

matchs方法是做規(guī)則校驗(yàn)處理, SpringBootCondition源碼:

public abstract class SpringBootCondition implements Condition {
	private final Log logger = LogFactory.getLog(getClass());
	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 根據(jù)注解信息, 獲取類或方法名稱
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
            // 獲取實(shí)現(xiàn)類的處理匹配結(jié)果
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
            // 日志打印匹配結(jié)果
			logOutcome(classOrMethodName, outcome);
            // ConditionEvaluationReport中記錄處理結(jié)果信息
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
					+ ex.getMessage() + " not " + "found. Make sure your own configuration does not rely on "
					+ "that class. This can also happen if you are "
					+ "@ComponentScanning a springframework package (e.g. if you "
					+ "put a @ComponentScan in the default package by mistake)", ex);
		}
		catch (RuntimeException ex) {
			throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
		}
	}
    ...
}
  • 獲取使用了Conditional的類或方法名稱信息。
  • 根據(jù)Conditional條件規(guī)則判斷, 獲取返回處理結(jié)果。
  • 判斷是否開(kāi)啟日志記錄功能,打印處理結(jié)果。
  • 記錄處理結(jié)果至ConditionEvaluationReport的outcomes屬性中。最后返回布爾值的處理結(jié)果。它是通過(guò) ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法調(diào)用, 可以看到它是在Bean創(chuàng)建之前就先調(diào)用,歸屬Bean配置定義信息的邏輯處理,且在validate方法之前處理。調(diào)用機(jī)制要理解清楚,我們管理配置。

5. Conditional核心之條件化注解具體實(shí)現(xiàn)

以ConditionalOnBean為例, 進(jìn)行分析, 源碼:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
    ...
}

采用Conditional注解, 具體條件判斷邏輯在OnBeanCondition類中實(shí)現(xiàn), 源碼:

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
	/**
	 * Bean definition attribute name for factory beans to signal their product type (if
	 * known and it can't be deduced from the factory bean class).
	 */
	public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE;
	@Override
	public ConfigurationPhase getConfigurationPhase() {
		return ConfigurationPhase.REGISTER_BEAN;
	}
   ...
}

OnBeanCondition類的作用是判斷容器中有無(wú)指定的Bean實(shí)例, 如果存在, 則條件生效。

它實(shí)現(xiàn)了抽象類FilteringSpringBootCondition的getOutcomes方法,同時(shí)實(shí)現(xiàn)了SpringBootCondition的getMatchOutcome方法, 兩個(gè)核心方法接口,一個(gè)是獲取定義的匹配條件,一個(gè)是返回匹配的結(jié)果信息, OnBeanCondition子類去實(shí)現(xiàn)具體的判斷邏輯, 根據(jù)定義的條件輸出判斷結(jié)果。

getOutcomes方法

方法源碼:

@Override
	protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		// 創(chuàng)建數(shù)組, 記錄自動(dòng)化配置的類信息
        ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
        // 遍歷處理
		for (int i = 0; i < outcomes.length; i++) {
			String autoConfigurationClass = autoConfigurationClasses[i];
			if (autoConfigurationClass != null) {
                // 獲取具有ConditionalOnBean注解設(shè)置的Bean
				Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
                // 記錄outcomes, 條件配置信息
				outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
				if (outcomes[i] == null) {
                    // 為空, 則降級(jí)獲取ConditionalOnSingleCandidate配置信息
					Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
							"ConditionalOnSingleCandidate");
					outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
				}
			}
		}
		return outcomes;
	}

該方法作用是掃描在META-INF的spring.factories文件中定義的配置類, 檢測(cè)是否包含對(duì)應(yīng)的條件標(biāo)注,

也就是是否使用了@OnBeanCondition標(biāo)注,存在則會(huì)記錄, 進(jìn)入后續(xù)方法邏輯處理。

可以看到, 通過(guò)outcomes數(shù)組來(lái)記錄所有采用了Conditional的Autoconfiguration配置類。

擴(kuò)展分析:

我們講解的OnBeanCondition只是其中一個(gè)條件注解, 跟蹤代碼分析, 同組的還有OnClassConditional和OnWebApplicationCondition條件注解,啟動(dòng)處理順序是:

OnClassConditional->OnWebApplicationCondition->OnBeanCondition,

spring.factories中大部份配置的Autoconfiguration都是采用OnClassConditional來(lái)作依賴類的條件判斷。

getMatchOutcomes方法

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ConditionMessage matchMessage = ConditionMessage.empty();
        // 判斷注解類型, ConditionalOnBean處理邏輯
		if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
			BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class);
			MatchResult matchResult = getMatchingBeans(context, spec);
			if (!matchResult.isAllMatched()) {
				String reason = createOnBeanNoMatchReason(matchResult);
				return ConditionOutcome						.noMatch(ConditionMessage.forCondition(ConditionalOnBean.class, spec).because(reason));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec).found("bean", "beans")
					.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
		}
        // ConditionalOnSingleCandidate注解處理邏輯
		if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
			BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,
					ConditionalOnSingleCandidate.class);
			MatchResult matchResult = getMatchingBeans(context, spec);
			if (!matchResult.isAllMatched()) {
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, spec)
						.didNotFind("any beans").atAll());
			}
			else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
					spec.getStrategy() == SearchStrategy.ALL)) {
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, spec)
						.didNotFind("a primary bean from beans")
						.items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnSingleCandidate.class, spec)
					.found("a primary bean from beans").items(Style.QUOTE, matchResult.getNamesOfAllMatches());
		}
        // ConditionalOnMissingBean注解處理邏輯
		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();
		}
		return ConditionOutcome.match(matchMessage);
	}

上面的getOutcomes方法記錄了需要匹配處理的條目,該方法是作具體判斷實(shí)現(xiàn)。 這里支持三種條件注解: ConditionalOnBean、ConditionalOnSingleCandidate和ConditionalOnMissingBean。實(shí)際內(nèi)部邏輯都會(huì)調(diào)用getMatchingBeans方法。處理完成之后, 返回ConditionMessage對(duì)象,最后通過(guò)ConditionOutcome包裝返回處理結(jié)果。

getMatchingBeans方法

該方法是做具體檢測(cè)是否符合條件注解所配置的信息,主要包含三種類型判斷,

一種是Bean Type 也就是class類型, 第二種是annotation標(biāo)注, 最后一種是Name屬性判斷。

protected final MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) {
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 判斷bean的搜尋策略, ANCESTORS為搜索所有父容器的上下文定義
		if (beans.getStrategy() == SearchStrategy.ANCESTORS) {
			BeanFactory parent = beanFactory.getParentBeanFactory();
			Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS");
            // 父容器轉(zhuǎn)換
			beanFactory = (ConfigurableListableBeanFactory) parent;
		}
		MatchResult matchResult = new MatchResult();
        // 判斷bean的搜尋策略, 是否為CURRENT當(dāng)前上下文
		boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
		TypeExtractor typeExtractor = beans.getTypeExtractor(context.getClassLoader());
		List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(beans.getIgnoredTypes(), typeExtractor,
				beanFactory, context, considerHierarchy);
        // 根據(jù)bean的類型遍歷判斷是否符合規(guī)則
		for (String type : beans.getTypes()) {
            // type類型的具體處理邏輯, 內(nèi)部為嵌套調(diào)用
			Collection<String> typeMatches = getBeanNamesForType(beanFactory, type, typeExtractor,
					context.getClassLoader(), considerHierarchy);
			typeMatches.removeAll(beansIgnoredByType);
			if (typeMatches.isEmpty()) {
				matchResult.recordUnmatchedType(type);
			}
			else {
				matchResult.recordMatchedType(type, typeMatches);
			}
		}
        // 根據(jù)bean的注解遍歷判斷是否符合規(guī)則
		for (String annotation : beans.getAnnotations()) {
			List<String> annotationMatches = Arrays.asList(
                    // Annotation類型的具體處理邏輯, 內(nèi)部為嵌套調(diào)用
					getBeanNamesForAnnotation(beanFactory, annotation, context.getClassLoader(), considerHierarchy));
			annotationMatches.removeAll(beansIgnoredByType);
			if (annotationMatches.isEmpty()) {
				matchResult.recordUnmatchedAnnotation(annotation);
			}
			else {
				matchResult.recordMatchedAnnotation(annotation, annotationMatches);
			}
		}
        // 根據(jù)bean的名稱遍歷判斷是否符合規(guī)則
		for (String beanName : beans.getNames()) {
			if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
				matchResult.recordMatchedName(beanName);
			}
			else {
				matchResult.recordUnmatchedName(beanName);
			}
		}
		return matchResult;
	}

1) 首先會(huì)判斷搜尋策略,是否需要搜尋父容器上下文, 支持三種模式,CURRENT: 當(dāng)前上下文; ANCESTORS: 所有父容器的上下文定義; ALL: 就是支持以上兩種搜尋策略。

2) 其次就是根據(jù)注解的定義信息, 按三種方式進(jìn)行判斷, 內(nèi)部按這三種, 類型、注解和名稱做處理,如果是父級(jí)搜索,會(huì)采用遞歸調(diào)用, 檢測(cè)是否存在, 進(jìn)行匹配判斷。方法調(diào)用層級(jí):

getBeanNamesForType(…) -》collectBeanNamesForType(…)

getBeanNamesForAnnotation(…) -》collectBeanNamesForAnnotation(…)

以上就是以ConditionalOnBean為例, 對(duì)ConditionOnXXX的實(shí)現(xiàn)原理做了剖析, SpringBootCondition的其他實(shí)現(xiàn)類還有很多, 本章只抽取代表性常見(jiàn)的條件注解作分析,大家有興趣可再研究其他條件注解的實(shí)現(xiàn)機(jī)制, 這里就不一一例舉。

6. 總結(jié)

基于Conditional條件的自動(dòng)化配置, 從SpringBootCondition實(shí)現(xiàn)原理到OnBeanCondition、AutoConfigurationImportFilter的剖析, 綜合可以看出Spring Boot對(duì)于條件化注解的實(shí)現(xiàn), 無(wú)論從層次結(jié)構(gòu), 還是內(nèi)部邏輯處理的關(guān)聯(lián)性, 都比較清晰明了,值得借鑒的是它的良好的擴(kuò)展性設(shè)計(jì),比如策略模式, 模板模式等,抽象類的合理運(yùn)用設(shè)計(jì), 沒(méi)有出現(xiàn)接口泛濫, 強(qiáng)耦合性等問(wèn)題, 也便于Spring Boot后續(xù)版本的功能擴(kuò)展。

到此這篇關(guān)于Spring Boot 詳細(xì)分析Conditional自動(dòng)化配置注解的文章就介紹到這了,更多相關(guān)Spring Boot Conditional內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java 如何將圖片按照原尺寸比例存入word中

    java 如何將圖片按照原尺寸比例存入word中

    這篇文章主要介紹了java 如何將圖片按照原尺寸比例存入word中的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java 超詳細(xì)講解IO操作字節(jié)流與字符流

    Java 超詳細(xì)講解IO操作字節(jié)流與字符流

    本章具體介紹了字節(jié)流、字符流的基本使用方法,圖解穿插代碼實(shí)現(xiàn)。 JAVA從基礎(chǔ)開(kāi)始講,后續(xù)會(huì)講到JAVA高級(jí),中間會(huì)穿插面試題和項(xiàng)目實(shí)戰(zhàn),希望能給大家?guī)?lái)幫助
    2022-03-03
  • mybatis-plus生成mapper擴(kuò)展文件的方法

    mybatis-plus生成mapper擴(kuò)展文件的方法

    這篇文章主要介紹了mybatis-plus生成mapper擴(kuò)展文件的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • ReadWriteLock接口及其實(shí)現(xiàn)ReentrantReadWriteLock方法

    ReadWriteLock接口及其實(shí)現(xiàn)ReentrantReadWriteLock方法

    下面小編就為大家?guī)?lái)一篇ReadWriteLock接口及其實(shí)現(xiàn)ReentrantReadWriteLock方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • Java對(duì)List進(jìn)行排序的兩種實(shí)現(xiàn)方法

    Java對(duì)List進(jìn)行排序的兩種實(shí)現(xiàn)方法

    這篇文章主要給大家介紹了關(guān)于Java對(duì)List進(jìn)行排序的兩種實(shí)現(xiàn)方法,第一種是實(shí)體類自己實(shí)現(xiàn)比較,第二種是借助比較器進(jìn)行排序,下面開(kāi)一起看看詳細(xì)的介紹吧,有需要的朋友們可以參考借鑒。
    2016-12-12
  • Springboot啟動(dòng)原理詳細(xì)講解

    Springboot啟動(dòng)原理詳細(xì)講解

    這篇文章主要介紹了SpringBoot啟動(dòng)原理的分析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • 如何使用intellij IDEA搭建Spring Boot項(xiàng)目

    如何使用intellij IDEA搭建Spring Boot項(xiàng)目

    這篇文章主要介紹了如何使用intellij IDEA搭建Spring Boot項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java實(shí)現(xiàn)Android拼圖游戲設(shè)計(jì)過(guò)程解析

    Java實(shí)現(xiàn)Android拼圖游戲設(shè)計(jì)過(guò)程解析

    這篇文章主要介紹了Java實(shí)現(xiàn)Android拼圖游戲設(shè)計(jì)過(guò)程解析,下面文章要接受的這是一款基于 Java 開(kāi)發(fā)的移動(dòng)端安卓小游戲,可以作為大家在學(xué)習(xí)期間的一個(gè)小練習(xí),接下來(lái)和小編一起進(jìn)入文章學(xué)習(xí)具體內(nèi)容吧
    2022-02-02
  • Java轉(zhuǎn)JSON串的幾種方式

    Java轉(zhuǎn)JSON串的幾種方式

    本文給大家總結(jié)一下java轉(zhuǎn)json串的幾種方式,每種方式通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧
    2018-05-05
  • Java由淺入深刨析繼承

    Java由淺入深刨析繼承

    繼承就是子類繼承父類的特征和行為,使得子類對(duì)象(實(shí)例)具有父類的實(shí)例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為
    2022-03-03

最新評(píng)論