通過(guò)Spring Boot整合Mybatis分析自動(dòng)配置詳解
前言
SpringBoot憑借"約定大于配置"的理念,已經(jīng)成為最流行的web開(kāi)發(fā)框架,所以有必須對(duì)其進(jìn)行深入的了解;本文通過(guò)整合Mybatis類來(lái)分析SpringBoot提供的自動(dòng)配置(AutoConfigure)功能,在此之前首先看一個(gè)整合Mybatis的實(shí)例。
SpringBoot整合Mybatis
提供SpringBoot整合Mybatis的實(shí)例,通過(guò)Mybatis實(shí)現(xiàn)簡(jiǎn)單的增刪改查功能;
1.表數(shù)據(jù)
CREATE TABLE `role` ( `note` varchar(255) CHARACTER SET utf8 DEFAULT NULL, `role_name` varchar(255) DEFAULT NULL, `id` bigint(20) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8
提供創(chuàng)建role表相關(guān)的sql,對(duì)表進(jìn)行增刪改查操作;
2.整合Mybatis的依賴
主要是mybatis-spring-boot-starter和使用的mysql驅(qū)動(dòng):
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.29</version> </dependency>
3.配置application.properties
提供連接mysql相關(guān)的信息:url,驅(qū)動(dòng),用戶名,密碼;
spring.datasource.url=jdbc:mysql://localhost/mybatis spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver
4.提供bean和Dao
分別提供表對(duì)應(yīng)的bean類和操作數(shù)據(jù)庫(kù)的dao類;
public class Role { private long id; private String roleName; private String note; //省略get/set方法 }
@Mapper public interface RoleDao { @Select("SELECT id,role_name as roleName,note FROM role WHERE id = #{id}") Role findRoleById(@Param("id") long id); }
5.提供Service和Controller
public interface RoleService { public Role findRoleById(long roleId); } @Service public class RoleServiceImpl implements RoleService { @Autowired private RoleDao roleDao; @Override public Role findRoleById(long roleId) { return roleDao.findRoleById(roleId); } }
@RestController public class RoleController { @Autowired private RoleService roleService; @RequestMapping("/role") public String getRole(long id) { return roleService.findRoleById(id).toString(); } }
啟動(dòng)服務(wù),進(jìn)行簡(jiǎn)單的測(cè)試:http://localhost:8888/role?id=111
結(jié)果如下:
Role [id=111, roleName=zhaohui, note=hello]
6.提出問(wèn)題
如上實(shí)例中,我們使用了很少的配置,就通過(guò)mybatis實(shí)現(xiàn)了操作數(shù)據(jù)庫(kù);正常使用mybatis需要的SqlSessionFactory和SqlSession沒(méi)有看到被實(shí)例化,同時(shí)mybatis依賴的數(shù)據(jù)源也沒(méi)有看到被引用,那SpringBoot是如何幫我們自動(dòng)配置的,下面重點(diǎn)分析一下;
SpringBoot自動(dòng)配置
1.自動(dòng)配置注解
要想使用自動(dòng)配置功能,SpringBoot提供了注解@EnableAutoConfiguration,當(dāng)然不需要我們配置因?yàn)樵贎SpringBootApplication注解中默認(rèn)以及啟用了;
@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 { //...省略... }
可以看到@SpringBootApplication注解本身也有注解@EnableAutoConfiguration:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { //...省略... }
在注解@EnableAutoConfiguration中重點(diǎn)看一下@Import注解中使用的AutoConfigurationImportSelector類,此類是自動(dòng)注解的核心類,會(huì)有條件的加載我們默認(rèn)指定的配置類;這里有兩個(gè)概念一個(gè)是有條件,一個(gè)是配置類,分別簡(jiǎn)單介紹一下:配置類可以簡(jiǎn)單理解就是相關(guān)組件對(duì)接SpringBoot的對(duì)接類,此類可以做一些初始化的工作;有條件表示并不是有配置類就能被對(duì)接上,是有條件的,SpringBoot默認(rèn)提供了大量配置類,但并不是所有配置類都能被加載初始化的,是有條件的,比如mybatis在沒(méi)有數(shù)據(jù)源的情況下,沒(méi)有mybatis基礎(chǔ)包的情況下是不能被對(duì)接的;下面首先看一下SpringBoot提供的哪些條件類;
2.條件類
SpringBoot提供了很多條件類,可以在配置中上配置注解條件類,相關(guān)條件類可以在spring-boot-autoconfigure包下的org.springframework.boot.autoconfigure.condition下找到,主要包含如下:
- ConditionalOnBean:當(dāng)前容器有指定Bean的條件下;
- ConditionalOnClass:當(dāng)前類路徑下有指定類的條件下;
- ConditionalOnCloudPlatform:當(dāng)指定了云平臺(tái)的時(shí)候;
- ConditionalOnExpression:SpEL表達(dá)式作為判斷條件;
- ConditionalOnJava:JVM版本作為判斷條件;
- ConditionalOnJndi:在JNDI存在的條件下查找指定的位置;
- ConditionalOnMissingBean:當(dāng)容器里沒(méi)有指定Bean的情況下;
- ConditionalOnMissingClass:當(dāng)類路徑下沒(méi)有指定的類的條件下;
- ConditionalOnNotWebApplication:當(dāng)前項(xiàng)目不是WEB項(xiàng)目的條件下;
- ConditionalOnProperty:當(dāng)前應(yīng)用是否配置了指定屬性指定的值;
- ConditionalOnResource:只有當(dāng)指定的資源位于類路徑下;
- ConditionalOnSingleCandidate:bean工廠中只有一個(gè)或者有多個(gè)情況下是主要的候選bean;
- ConditionalOnWebApplication:當(dāng)前項(xiàng)目是WEB項(xiàng)目的條件下。
以上是注解類,注解本身沒(méi)有功能,只是提供標(biāo)記的功能,具體功能在@Conditional中指定的,比如ConditionalOnBean注解如下所示:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean { //...省略... }
相關(guān)功能的實(shí)現(xiàn)就在OnBeanCondition類中,同樣其他注解類的實(shí)現(xiàn)類也在包org.springframework.boot.autoconfigure.condition下找到;
3.自動(dòng)配置過(guò)程
Springboot應(yīng)用啟動(dòng)過(guò)程中使用ConfigurationClassParser分析配置類,此類中有一個(gè)processImports方法,此方法用來(lái)處理@Import注解,在@EnableAutoConfiguration注解存在@Import注解,這時(shí)候會(huì)實(shí)例化注解中的AutoConfigurationImportSelector,在其內(nèi)部有一個(gè)AutoConfigurationGroup內(nèi)部類,內(nèi)部類有兩個(gè)核心方法分別是:process和selectImports;
@Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } }
此方法主要獲取經(jīng)過(guò)條件過(guò)濾之后可用的自動(dòng)配置類,主要調(diào)用AutoConfigurationImportSelector中的getAutoConfigurationEntry完成的:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
首先獲取了所有備選的自動(dòng)配置類,然后刪除了重復(fù)和被排除的類,最后通過(guò)條件進(jìn)行篩選出可用的配置類,下面分別看一下,首先看一下如何獲取所有備選的配置類:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
通過(guò)SpringFactoriesLoader獲取類路徑下META-INF/spring.factories文件中key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置類,可以看一下spring-boot-autoconfigure.jar中的spring.factories內(nèi)容:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\ //...以下省略...
當(dāng)然這里只是截取了其中一個(gè)類路徑j(luò)ar下的部分配置,獲取所有配置類之后進(jìn)行去重,去被排除的類,然后進(jìn)行條件過(guò)濾,下面重點(diǎn)看一下:
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); String[] candidates = StringUtils.toStringArray(configurations); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); boolean[] match = filter.match(candidates, autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; candidates[i] = null; skipped = true; } } } if (!skipped) { return configurations; } List<String> result = new ArrayList<>(candidates.length); for (int i = 0; i < candidates.length; i++) { if (!skip[i]) { result.add(candidates[i]); } } if (logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } return new ArrayList<>(result); }
此方法大致就是首先獲取配置的AutoConfigurationImportFilter ,然后對(duì)之前獲取的所有配置類進(jìn)行過(guò)濾,最后返回過(guò)濾之后的配置類;AutoConfigurationImportFilter同樣也是通過(guò)SpringFactoriesLoader類進(jìn)行加載類路徑下META-INF/spring.factories,只不過(guò)當(dāng)前的key是:org.springframework.boot.autoconfigure.AutoConfigurationImportFilter,可以看一下SpringBoot默認(rèn)配置的filter:
# Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
可以看到Filter其實(shí)就是上文介紹的條件類,這里默認(rèn)了OnBeanCondition,OnClassCondition以及OnWebApplicationCondition,已這里使用的Mybatis為例看一下MybatisAutoConfiguration的注解:
@org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnSingleCandidate(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class MybatisAutoConfiguration implements InitializingBean { //...以下省略... }
可以看到其中有用到@ConditionalOnClass,表示必須提供SqlSessionFactory和SqlSessionFactoryBean類的情況下才加載此配置類,而整兩個(gè)是正式Mybatis基礎(chǔ)包中提供的;有了基礎(chǔ)包還不行,還需要DataSource,而且DataSource必須在MybatisAutoConfiguration實(shí)例化之前初始化好,SpringBoot是如何實(shí)現(xiàn),繼續(xù)看另外一個(gè)核心方法selectImports():
@Override public Iterable<Entry> selectImports() { if (this.autoConfigurationEntries.isEmpty()) { return Collections.emptyList(); } Set<String> allExclusions = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet()); Set<String> processedConfigurations = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream) .collect(Collectors.toCollection(LinkedHashSet::new)); processedConfigurations.removeAll(allExclusions); return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream() .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) .collect(Collectors.toList()); } private List<String> sortAutoConfigurations(Set<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata) .getInPriorityOrder(configurations); }
首先是對(duì)被排除類的一個(gè)過(guò)濾,然后接下來(lái)重點(diǎn)看一下對(duì)配置類進(jìn)行排序的一個(gè)方法,具體操作在類AutoConfigurationSorter中進(jìn)行的,具體方法為getInPriorityOrder():
public List<String> getInPriorityOrder(Collection<String> classNames) { AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory, this.autoConfigurationMetadata, classNames); List<String> orderedClassNames = new ArrayList<>(classNames); // Initially sort alphabetically Collections.sort(orderedClassNames); // Then sort by order orderedClassNames.sort((o1, o2) -> { int i1 = classes.get(o1).getOrder(); int i2 = classes.get(o2).getOrder(); return Integer.compare(i1, i2); }); // Then respect @AutoConfigureBefore @AutoConfigureAfter orderedClassNames = sortByAnnotation(classes, orderedClassNames); return orderedClassNames; }
首先使用order進(jìn)行排序,然后使用@AutoConfigureBefore和@AutoConfigureAfter就行排序;order其實(shí)就是通過(guò)注解@AutoConfigureOrder進(jìn)行排序的,值是一個(gè)整數(shù),結(jié)構(gòu)類似如下:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureBefore和@AutoConfigureAfter字面意思也很好理解,指定在其他配置類之前和之后,所以可以看到在MybatisAutoConfiguration中有如下配置:
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
表示在DataSourceAutoConfiguration配置類加載之后才會(huì)加載Mybatis配置類,這樣就解決了依賴關(guān)系;還有上文提到的Mybatis操作數(shù)據(jù)庫(kù)依賴的SqlSessionFactory和SqlSession,都在MybatisAutoConfiguration進(jìn)行了初始化操作;SpringBoot本身其實(shí)以及提供了大量常用組件的自動(dòng)配置類,我們只需要提供滿足的特定條件,SpringBoot自動(dòng)會(huì)幫我加載初始化等操作,但是肯定也有自定義配置類的需求,下面用一個(gè)簡(jiǎn)單的實(shí)例來(lái)看看如何自定義一個(gè)自動(dòng)配置類;
自定義配置類
接下來(lái)我們用很簡(jiǎn)單的實(shí)例來(lái)看一下自定義的流程,一個(gè)格式化大寫消息的實(shí)例;
1.pom文件引入依賴
<groupId>com.format</groupId> <artifactId>format-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>format-spring-boot-starter</name> <url>http://maven.apache.org</url> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <!-- Import dependency management from Spring Boot --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>1.5.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Spring 官方 Starter通常命名為spring-boot-starter-{name}如 spring-boot-starter-web,Spring官方建議非官方Starter命名應(yīng)遵循{name}-spring-boot-starter的格式;
2.服務(wù)類和屬性配置類
@ConfigurationProperties("format.service") public class FormatServiceProperties { private String type; //...get/set省略... } public class FormatService { private String type; public FormatService(String type) { this.type = type; } public String wrap(String word) { if(type.equalsIgnoreCase("Upper")){//大寫 return word.toUpperCase(); }else if(type.equalsIgnoreCase("Lower")){//小寫 return word.toLowerCase(); } return word; } }
屬性類提供了type參數(shù)可以在application.properties中配置,可配置值包括:upper,lower;
3.自動(dòng)配置類和創(chuàng)建spring.factories文件
@Configuration @ConditionalOnClass(FormatService.class) @EnableConfigurationProperties(FormatServiceProperties.class) public class FormatAutoConfigure { @Autowired private FormatServiceProperties properties; @Bean @ConditionalOnMissingBean FormatService formatService() { return new FormatService(properties.getType()); } }
這個(gè)就是自定義的自動(dòng)配置類,SpringBoot啟動(dòng)的時(shí)候會(huì)根據(jù)條件自動(dòng)初始化;最后在resources/META-INF/下創(chuàng)建spring.factories文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.format.FormatAutoConfigure
4.測(cè)試
在其他SpringBoot中可以引入上面創(chuàng)建的項(xiàng)目,引入方式也很簡(jiǎn)單:
<dependency> <groupId>com.format</groupId> <artifactId>format-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
同時(shí)在application.properties配置格式化類型:
format.service.type=upper
啟動(dòng)應(yīng)用,瀏覽器訪問(wèn)http://localhost:8888/format?word=hello,結(jié)果為:HELLO
總結(jié)
本文從使用SpringBoot整合Mybatis開(kāi)始,然后提出使用中產(chǎn)生的疑問(wèn),進(jìn)而通過(guò)分析源碼的方式來(lái)理解SpringBoot的自動(dòng)配置機(jī)制,最后自定義了一個(gè)自動(dòng)配置類來(lái)看看具體如何使用;SpringBoot通過(guò)自動(dòng)配置的方式幫助開(kāi)發(fā)者減少了很大的工作量,達(dá)到開(kāi)箱即用的效果;但是另一方面如果出現(xiàn)問(wèn)題需要調(diào)試可能不是那么好定位。
示例代碼地址
https://github.com/ksfzhaohui...
- [springboot]
- [format-spring-boot-starter]
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
- Mybatis分頁(yè)插件PageHelper配置及使用方法詳解
- Mybatis環(huán)境搭建及文件配置過(guò)程解析
- 通過(guò)springboot+mybatis+druid配置動(dòng)態(tài)數(shù)據(jù)源
- SpringBoot整合MyBatisPlus配置動(dòng)態(tài)數(shù)據(jù)源的方法
- mysql+spring+mybatis實(shí)現(xiàn)數(shù)據(jù)庫(kù)讀寫分離的代碼配置
- Spring Boot集成MyBatis實(shí)現(xiàn)通用Mapper的配置及使用
- idea mybatis配置log4j打印sql語(yǔ)句的示例
- MyBatis環(huán)境資源配置實(shí)現(xiàn)代碼詳解
相關(guān)文章
Java list與set中contains()方法效率案例詳解
這篇文章主要介紹了Java list與set中contains()方法效率案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08利用Postman和Chrome的開(kāi)發(fā)者功能探究項(xiàng)目(畢業(yè)設(shè)計(jì)項(xiàng)目)
這篇文章主要介紹了利用Postman和Chrome的開(kāi)發(fā)者功能探究項(xiàng)目(畢業(yè)設(shè)計(jì)項(xiàng)目),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12新手小白學(xué)JAVA 日期類Date SimpleDateFormat Calendar(入門)
本文主要介紹了JAVA 日期類Date SimpleDateFormat Calendar,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10Java使用OpenFeign管理多個(gè)第三方服務(wù)調(diào)用
最近開(kāi)發(fā)了一個(gè)統(tǒng)一調(diào)度類的項(xiàng)目,需要依賴多個(gè)第三方服務(wù),這些服務(wù)都提供了HTTP接口供我調(diào)用。感興趣的可以了解一下2021-06-06Java結(jié)構(gòu)型設(shè)計(jì)模式中建造者模式示例詳解
建造者模式,是一種對(duì)象構(gòu)建模式 它可以將復(fù)雜對(duì)象的建造過(guò)程抽象出來(lái),使這個(gè)抽象過(guò)程的不同實(shí)現(xiàn)方法可以構(gòu)造出不同表現(xiàn)的對(duì)象。本文將通過(guò)示例講解建造者模式,需要的可以參考一下2022-09-09關(guān)于Idea創(chuàng)建Java項(xiàng)目并引入lombok包的問(wèn)題(lombok.jar包免費(fèi)下載)
很多朋友遇到當(dāng)idea創(chuàng)建java項(xiàng)目時(shí),命名安裝了lombok插件卻不能使用注解,原因有兩個(gè)大家可以參考下本文,本文對(duì)每種原因分析給出了解決方案,需要的朋友參考下吧2021-06-06Java多線程中wait、notify、notifyAll使用詳解
這篇文章主要介紹了Java多線程中wait、notify、notifyAll使用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05