Spring Boot 單元測(cè)試和集成測(cè)試實(shí)現(xiàn)詳解
學(xué)習(xí)如何使用本教程中提供的工具,并在 Spring Boot 環(huán)境中編寫單元測(cè)試和集成測(cè)試。
1. 概覽
本文中,我們將了解如何編寫單元測(cè)試并將其集成在 Spring Boot 環(huán)境中。你可在網(wǎng)上找到大量關(guān)于這個(gè)主題的教程,但很難在一個(gè)頁(yè)面中找到你需要的所有信息。我經(jīng)常注意到初級(jí)開(kāi)發(fā)人員混淆了單元測(cè)試和集成測(cè)試的概念,特別是在談到 Spring 生態(tài)系統(tǒng)時(shí)。我將嘗試講清楚不同注解在不同上下文中的用法。
2. 單元測(cè)試 vs. 集成測(cè)試
維基百科是這么說(shuō)單元測(cè)試的:
在計(jì)算機(jī)編程中,單元測(cè)試是一種軟件測(cè)試方法,用以測(cè)試源代碼的單個(gè)單元、一個(gè)或多個(gè)計(jì)算機(jī)程序模塊的集合以及相關(guān)的控制數(shù)據(jù)、使用過(guò)程和操作過(guò)程,以確定它們是否適合使用。
“集成測(cè)試(有時(shí)也稱集成和測(cè)試,縮寫為 I&T)是軟件測(cè)試的一個(gè)階段,在這個(gè)階段中,各個(gè)軟件模塊被組合在一起來(lái)進(jìn)行測(cè)試?!?/p>
簡(jiǎn)而言之,當(dāng)我們?cè)谧鰡卧獪y(cè)試時(shí),只是測(cè)試了一個(gè)代碼單元,每次只測(cè)試一個(gè)方法,不包括與正測(cè)試組件相交互的其他所有組件。
另一方面,在集成測(cè)試中,我們測(cè)試各組件之間的集成。由于單元測(cè)試,我們可知這些組件行為與所需一致,但不清楚它們是如何在一起工作的。這就是集成測(cè)試的職責(zé)。
3. Java 單元測(cè)試
所有 Java 開(kāi)發(fā)者都知道 JUnit 是執(zhí)行單元測(cè)試的主要框架。它提供了許多注解來(lái)對(duì)期望進(jìn)行斷言。
Hamcrest 是一個(gè)用于軟件測(cè)試的附加框架。Hamcrest 允許使用現(xiàn)有的 matcher 類來(lái)檢查代碼中的條件,還允許自定義 matcher 實(shí)現(xiàn)。要在 JUnit 中使用 Hamcrest matcher,必須使用 assertThat 語(yǔ)句,后跟一個(gè)或多個(gè) matcher。
在這里,你可以看到使用這兩種框架的簡(jiǎn)單測(cè)試:
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.both;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.everyItem;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import org.hamcrest.core.CombinableMatcher;
import org.junit.Test;
public class AssertTests {
@Test
public void testAssertArrayEquals() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
assertArrayEquals("failure - byte arrays not same", expected, actual);
}
@Test
public void testAssertEquals() {
assertEquals("failure - strings are not equal", "text", "text");
}
@Test
public void testAssertFalse() {
assertFalse("failure - should be false", false);
}
@Test
public void testAssertNotNull() {
assertNotNull("should not be null", new Object());
}
@Test
public void testAssertNotSame() {
assertNotSame("should not be same Object", new Object(), new Object());
}
@Test
public void testAssertNull() {
assertNull("should be null", null);
}
@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
assertSame("should be same", aNumber, aNumber);
}
// JUnit Matchers assertThat
@Test
public void testAssertThatBothContainsString() {
assertThat("albumen", both(containsString("a")).and(containsString("b")));
}
@Test
public void testAssertThatHasItems() {
assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
}
@Test
public void testAssertThatEveryItemContainsString() {
assertThat(Arrays.asList(new String[] { "fun", "ban", "net" }), everyItem(containsString("n")));
}
// Core Hamcrest Matchers with assertThat
@Test
public void testAssertThatHamcrestCoreMatchers() {
assertThat("good", allOf(equalTo("good"), startsWith("good")));
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));
}
@Test
public void testAssertTrue() {
assertTrue("failure - should be true", true);
}
}
4. 介紹我們的案例
讓我們來(lái)寫一個(gè)簡(jiǎn)單的程序吧。其目的是為漫畫提供一個(gè)基本的搜索引擎。
4.1. Maven 依賴
首先,需要添加一些依賴到我們的工程中。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> <scope>provided</scope> </dependency>
4.2. 定義 Model
我們的模型非常簡(jiǎn)單,只有兩個(gè)類組成:Manga 和 MangaResult
4.2.1. Manga 類
Manga 類表示系統(tǒng)檢索到的 Manga 實(shí)例。使用 Lombok 來(lái)減少樣板代碼。
package com.mgiglione.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
public class Manga {
private String title;
private String description;
private Integer volumes;
private Double score;
}
4.2.2. MangaResult
MangaResult 類是包含了一個(gè) Manga List 的包裝類。
package com.mgiglione.model;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter @Setter @NoArgsConstructor
public class MangaResult {
private List<Manga> result;
}
4.3. 實(shí)現(xiàn) Service
為實(shí)現(xiàn)本 Service,我們將使用由 Jikan Moe 提供的免費(fèi) API 接口。
RestTemplate 是用來(lái)對(duì) API 進(jìn)行發(fā)起 REST 調(diào)用的 Spring 類。
package com.mgiglione.service;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.mgiglione.model.Manga;
import com.mgiglione.model.MangaResult;
@Service
public class MangaService {
Logger logger = LoggerFactory.getLogger(MangaService.class);
private static final String MANGA_SEARCH_URL="http://api.jikan.moe/search/manga/";
@Autowired
RestTemplate restTemplate;
public List<Manga> getMangasByTitle(String title) {
return restTemplate.getForEntity(MANGA_SEARCH_URL+title, MangaResult.class).getBody().getResult();
}
}
4.4. 實(shí)現(xiàn) Controller
下一步就是寫一個(gè)暴露了兩個(gè)端點(diǎn)的 REST Controller,一個(gè)是同步的,一個(gè)是異步的,其僅用于測(cè)試目的。該 Controller 使用了上面定義的 Service。
package com.mgiglione.controller;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.mgiglione.model.Manga;
import com.mgiglione.service.MangaService;
@RestController
@RequestMapping(value = "/manga")
public class MangaController {
Logger logger = LoggerFactory.getLogger(MangaController.class);
@Autowired
private MangaService mangaService;
@RequestMapping(value = "/async/{title}", method = RequestMethod.GET)
@Async
public CompletableFuture<List<Manga>> searchASync(@PathVariable(name = "title") String title) {
return CompletableFuture.completedFuture(mangaService.getMangasByTitle(title));
}
@RequestMapping(value = "/sync/{title}", method = RequestMethod.GET)
public @ResponseBody <List<Manga>> searchSync(@PathVariable(name = "title") String title) {
return mangaService.getMangasByTitle(title);
}
}
4.5. 啟動(dòng)并測(cè)試系統(tǒng)
mvn spring-boot:run
然后,Let's try it:
curl http://localhost:8080/manga/async/ken curl http://localhost:8080/manga/sync/ken
示例輸出:
{
"title":"Rurouni Kenshin: Meiji Kenkaku Romantan",
"description":"Ten years have passed since the end of Bakumatsu, an era of war that saw the uprising of citizens against the Tokugawa shogunate. The revolutionaries wanted to create a time of peace, and a thriving c...",
"volumes":28,
"score":8.69
},
{
"title":"Sun-Ken Rock",
"description":"The story revolves around Ken, a man from an upper-class family that was orphaned young due to his family's involvement with the Yakuza; he became a high school delinquent known for fighting. The only...",
"volumes":25,
"score":8.12
},
{
"title":"Yumekui Kenbun",
"description":"For those who suffer nightmares, help awaits at the Ginseikan Tea House, where patrons can order much more than just Darjeeling. Hiruko is a special kind of a private investigator. He's a dream eater....",
"volumes":9,
"score":7.97
}
5. Spring Boot 應(yīng)用的單元測(cè)試
Spring Boot 提供了一個(gè)強(qiáng)大的類以使測(cè)試變得簡(jiǎn)單: @SpringBootTest 注解
可以在基于 Spring Boot 運(yùn)行的測(cè)試類上指定此注解。
除常規(guī) Spring TestContext Framework 之外,其還提供以下功能:
- 當(dāng) @ContextConfiguration (loader=…) 沒(méi)有特別聲明時(shí),使用 SpringBootContextLoader 作為默認(rèn) ContextLoader。
- 在未使用嵌套的 @Configuration 注解,且未顯式指定相關(guān)類時(shí),自動(dòng)搜索 @SpringBootConfiguration。
- 允許使用 Properties 來(lái)自定義 Environment 屬性。
- 對(duì)不同的 Web 環(huán)境模式提供支持,包括啟動(dòng)在已定義或隨機(jī)端口上的完全運(yùn)行的 Web 服務(wù)器的功能。
- 注冊(cè) TestRestTemplate 和 / 或 WebTestClient Bean,以便在完全運(yùn)行在 Web 服務(wù)器上的 Web 測(cè)試中使用。
此處,我們僅有兩個(gè)組件需要測(cè)試:MangaService 和 MangaController
5.1. 對(duì) MangaService 進(jìn)行單元測(cè)試
為了測(cè)試 MangaService,我們需要將其與外部組件隔離開(kāi)來(lái)。本例中,只需要一個(gè)外部組件:RestTemplate,我們用它來(lái)調(diào)用遠(yuǎn)程 API。
我們需要做的是模擬 RestTemplate Bean,并讓它始終以固定的給定響應(yīng)進(jìn)行響應(yīng)。Spring Test 結(jié)合并擴(kuò)展了 Mockito 庫(kù),通過(guò) @MockBean 注解,我們可以配置模擬 Bean。
package com.mgiglione.service.test.unit;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import com.mgiglione.model.Manga;
import com.mgiglione.model.MangaResult;
import com.mgiglione.service.MangaService;
import com.mgiglione.utils.JsonUtils;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MangaServiceUnitTest {
@Autowired
private MangaService mangaService;
// MockBean is the annotation provided by Spring that wraps mockito one
// Annotation that can be used to add mocks to a Spring ApplicationContext.
// If any existing single bean of the same type defined in the context will be replaced by the mock, if no existing bean is defined a new one will be added.
@MockBean
private RestTemplate template;
@Test
public void testGetMangasByTitle() throws IOException {
// Parsing mock file
MangaResult mRs = JsonUtils.jsonFile2Object("ken.json", MangaResult.class);
// Mocking remote service
when(template.getForEntity(any(String.class), any(Class.class))).thenReturn(new ResponseEntity(mRs, HttpStatus.OK));
// I search for goku but system will use mocked response containing only ken, so I can check that mock is used.
List<Manga> mangasByTitle = mangaService.getMangasByTitle("goku");
assertThat(mangasByTitle).isNotNull()
.isNotEmpty()
.allMatch(p -> p.getTitle()
.toLowerCase()
.contains("ken"));
}
}
5.2. 對(duì) MangaController 進(jìn)行單元測(cè)試
正如在 MangaService 的單元測(cè)試中所做的那樣,我們需要隔離組件。在這種情況下,我們需要模擬 MangaService Bean。
然后,我們還有一個(gè)問(wèn)題……Controller 部分是管理 HttpRequest 的系統(tǒng)的一部分,因此我們需要一個(gè)系統(tǒng)來(lái)模擬這種行為,而非啟動(dòng)完整的 HTTP 服務(wù)器。
MockMvc 是執(zhí)行該操作的 Spring 類。其可以以不同的方式進(jìn)行設(shè)置:
- 使用 Standalone Context
- 使用 WebApplication Context
- 讓 Spring 通過(guò)在測(cè)試類上使用 @SpringBootTest、@AutoConfigureMockMvc 這些注解來(lái)加載所有的上下文,以實(shí)現(xiàn)自動(dòng)裝配
- 讓 Spring 通過(guò)在測(cè)試類上使用 @WebMvcTest 注解來(lái)加載 Web 層上下文,以實(shí)現(xiàn)自動(dòng)裝配
package com.mgiglione.service.test.unit;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.context.WebApplicationContext;
import com.mgiglione.controller.MangaController;
import com.mgiglione.model.Manga;
import com.mgiglione.service.MangaService;
@SpringBootTest
@RunWith(SpringRunner.class)
public class MangaControllerUnitTest {
MockMvc mockMvc;
@Autowired
protected WebApplicationContext wac;
@Autowired
MangaController mangaController;
@MockBean
MangaService mangaService;
/**
* List of samples mangas
*/
private List<Manga> mangas;
@Before
public void setup() throws Exception {
this.mockMvc = standaloneSetup(this.mangaController).build();// Standalone context
// mockMvc = MockMvcBuilders.webAppContextSetup(wac)
// .build();
Manga manga1 = Manga.builder()
.title("Hokuto no ken")
.description("The year is 199X. The Earth has been devastated by nuclear war...")
.build();
Manga manga2 = Manga.builder()
.title("Yumekui Kenbun")
.description("For those who suffer nightmares, help awaits at the Ginseikan Tea House, where patrons can order much more than just Darjeeling. Hiruko is a special kind of a private investigator. He's a dream eater....")
.build();
mangas = new ArrayList<>();
mangas.add(manga1);
mangas.add(manga2);
}
@Test
public void testSearchSync() throws Exception {
// Mocking service
when(mangaService.getMangasByTitle(any(String.class))).thenReturn(mangas);
mockMvc.perform(get("/manga/sync/ken").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].title", is("Hokuto no ken")))
.andExpect(jsonPath("$[1].title", is("Yumekui Kenbun")));
}
@Test
public void testSearchASync() throws Exception {
// Mocking service
when(mangaService.getMangasByTitle(any(String.class))).thenReturn(mangas);
MvcResult result = mockMvc.perform(get("/manga/async/ken").contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(request().asyncStarted())
.andDo(print())
// .andExpect(status().is2xxSuccessful()).andReturn();
.andReturn();
// result.getRequest().getAsyncContext().setTimeout(10000);
mockMvc.perform(asyncDispatch(result))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].title", is("Hokuto no ken")));
}
}
正如在代碼中所看到的那樣,選擇第一種解決方案是因?yàn)槠涫亲钶p量的一個(gè),并且我們可以對(duì) Spring 上下文中加載的對(duì)象有更好的治理。
在異步測(cè)試中,必須首先通過(guò)調(diào)用服務(wù),然后啟動(dòng) asyncDispatch 方法來(lái)模擬異步行為。
6. Spring Boot 應(yīng)用的集成測(cè)試
對(duì)于集成測(cè)試,我們希望提供下游通信來(lái)檢查我們的主要組件。
6.1. 對(duì) MangaService 進(jìn)行集成測(cè)試
這個(gè)測(cè)試也是非常簡(jiǎn)單的。我們不需要模擬任何東西,因?yàn)槲覀兊哪康木褪且{(diào)用遠(yuǎn)程 Manga API。
package com.mgiglione.service.test.integration;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.mgiglione.model.Manga;
import com.mgiglione.service.MangaService;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MangaServiceIntegrationTest {
@Autowired
private MangaService mangaService;
@Test
public void testGetMangasByTitle() {
List<Manga> mangasByTitle = mangaService.getMangasByTitle("ken");
assertThat(mangasByTitle).isNotNull().isNotEmpty();
}
}
6.2. 對(duì) MangaController 進(jìn)行集成測(cè)試
這個(gè)測(cè)試和單元測(cè)試很是相似,但在這個(gè)案例中,我們無(wú)需再模擬 MangaService。
package com.mgiglione.service.test.integration;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.context.WebApplicationContext;
import com.mgiglione.controller.MangaController;
@SpringBootTest
@RunWith(SpringRunner.class)
public class MangaControllerIntegrationTest {
// @Autowired
MockMvc mockMvc;
@Autowired
protected WebApplicationContext wac;
@Autowired
MangaController mangaController;
@Before
public void setup() throws Exception {
this.mockMvc = standaloneSetup(this.mangaController).build();// Standalone context
// mockMvc = MockMvcBuilders.webAppContextSetup(wac)
// .build();
}
@Test
public void testSearchSync() throws Exception {
mockMvc.perform(get("/manga/sync/ken").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.*.title", hasItem(is("Hokuto no Ken"))));
}
@Test
public void testSearchASync() throws Exception {
MvcResult result = mockMvc.perform(get("/manga/async/ken").contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(request().asyncStarted())
.andDo(print())
.andReturn();
mockMvc.perform(asyncDispatch(result))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.*.title", hasItem(is("Hokuto no Ken"))));
}
}
7. 結(jié)論
我們已經(jīng)了解了在 Spring Boot 環(huán)境下單元測(cè)試和集成測(cè)試的主要不同,了解了像 Hamcrest 這樣簡(jiǎn)化測(cè)試編寫的框架。當(dāng)然,也可以在我的 GitHub 倉(cāng)庫(kù) 里找到所有代碼。
原文:https://dzone.com/articles/unit-and-integration-tests-in-spring-boot-2
作者:Marco Giglione
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
淺談java 字符串,字符數(shù)組,list間的轉(zhuǎn)化
下面小編就為大家?guī)?lái)一篇淺談java 字符串,字符數(shù)組,list間的轉(zhuǎn)化。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11
mybatis-plus多表關(guān)聯(lián)查詢功能的實(shí)現(xiàn)
本文給大家介紹mybatis-plus多表關(guān)聯(lián)查詢功能的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-11-11
java selenium XPath 定位實(shí)現(xiàn)方法
本文主要介紹java selenium XPath,這里整理了XPath的資料,并附實(shí)現(xiàn)方法,有需要的小伙伴可以參考下2016-08-08
Java判斷一個(gè)時(shí)間是否在當(dāng)前時(shí)間區(qū)間代碼示例
這篇文章主要給大家介紹了關(guān)于使用Java判斷一個(gè)時(shí)間是否在當(dāng)前時(shí)間區(qū)間的相關(guān)資料,在日常開(kāi)發(fā)中我們經(jīng)常會(huì)涉及到時(shí)間的大小比較或者是判斷某個(gè)時(shí)間是否在某個(gè)時(shí)間段內(nèi),需要的朋友可以參考下2023-07-07
SpringBoot中使用Redis?Stream實(shí)現(xiàn)消息監(jiān)聽(tīng)示例
本文主要介紹了SpringBoot中使用Redis?Stream實(shí)現(xiàn)消息監(jiān)聽(tīng)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
SpringBoot中短時(shí)間連續(xù)請(qǐng)求時(shí)出現(xiàn)Cookie獲取異常問(wèn)題的解決方案
在 Spring Boot Web 應(yīng)用中,每個(gè)請(qǐng)求都會(huì)攜帶 HttpServletRequest,其中包含 Cookie 等關(guān)鍵信息,如果某個(gè)請(qǐng)求對(duì)象的 cookieParsed 標(biāo)記在異步線程中被錯(cuò)誤修改,可能會(huì)導(dǎo)致 短時(shí)間內(nèi)的后續(xù)請(qǐng)求無(wú)法正確解析 Cookie,本文給大家介紹了詳細(xì)解決方法,需要的朋友可以參考下2025-04-04
基于Java語(yǔ)言開(kāi)發(fā)的一個(gè)高效的敏感詞過(guò)濾工具
這篇文章主要為大家詳細(xì)介紹了如何基于Java語(yǔ)言開(kāi)發(fā)的一個(gè)高效的敏感詞過(guò)濾工具,文中的示例代碼簡(jiǎn)潔易懂,有需要的小伙伴可以參考一下2025-01-01
java多線程編程之慎重使用volatile關(guān)鍵字
volatile關(guān)鍵字相信了解Java多線程的讀者都很清楚它的作用。volatile關(guān)鍵字用于聲明簡(jiǎn)單類型變量,下面看一下為什么要慎重使用volatile關(guān)鍵字2014-01-01

