SpringBoot測(cè)試WebMVC的4種實(shí)現(xiàn)方案
在項(xiàng)目開發(fā)中,測(cè)試是確保應(yīng)用質(zhì)量的關(guān)鍵環(huán)節(jié)。對(duì)于基于SpringBoot構(gòu)建的Web應(yīng)用,高效測(cè)試MVC層可以極大提高開發(fā)及聯(lián)調(diào)效率。一個(gè)設(shè)計(jì)良好的測(cè)試策略不僅能發(fā)現(xiàn)潛在問(wèn)題,還能提高代碼質(zhì)量、促進(jìn)系統(tǒng)穩(wěn)定性,并為后續(xù)的重構(gòu)和功能擴(kuò)展提供保障。
方案一:使用MockMvc進(jìn)行控制器單元測(cè)試
工作原理
MockMvc是Spring Test框架提供的一個(gè)核心類,它允許開發(fā)者在不啟動(dòng)HTTP服務(wù)器的情況下模擬HTTP請(qǐng)求和響應(yīng),直接測(cè)試控制器方法。這種方法速度快、隔離性好,特別適合純粹的單元測(cè)試。
實(shí)現(xiàn)步驟
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
編寫待測(cè)試控制器
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUserById(@PathVariable Long id) {
UserDto user = userService.findById(id);
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<UserDto> createUser(@RequestBody @Valid UserCreateRequest request) {
UserDto createdUser = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}
}
編寫MockMvc單元測(cè)試
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ExtendWith(MockitoExtension.class)
public class UserControllerUnitTest {
@Mock
private UserService userService;
@InjectMocks
private UserController userController;
private MockMvc mockMvc;
private ObjectMapper objectMapper;
@BeforeEach
void setUp() {
// 設(shè)置MockMvc實(shí)例
mockMvc = MockMvcBuilders
.standaloneSetup(userController)
.setControllerAdvice(new GlobalExceptionHandler()) // 添加全局異常處理
.build();
objectMapper = new ObjectMapper();
}
@Test
void getUserById_ShouldReturnUser() throws Exception {
// 準(zhǔn)備測(cè)試數(shù)據(jù)
UserDto mockUser = new UserDto(1L, "John Doe", "john@example.com");
// 配置Mock行為
when(userService.findById(1L)).thenReturn(mockUser);
// 執(zhí)行測(cè)試
mockMvc.perform(get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("John Doe"))
.andExpect(jsonPath("$.email").value("john@example.com"));
// 驗(yàn)證交互
verify(userService, times(1)).findById(1L);
}
@Test
void createUser_ShouldReturnCreatedUser() throws Exception {
// 準(zhǔn)備測(cè)試數(shù)據(jù)
UserCreateRequest request = new UserCreateRequest("Jane Doe", "jane@example.com");
UserDto createdUser = new UserDto(2L, "Jane Doe", "jane@example.com");
// 配置Mock行為
when(userService.createUser(any(UserCreateRequest.class))).thenReturn(createdUser);
// 執(zhí)行測(cè)試
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(2))
.andExpect(jsonPath("$.name").value("Jane Doe"))
.andExpect(jsonPath("$.email").value("jane@example.com"));
// 驗(yàn)證交互
verify(userService, times(1)).createUser(any(UserCreateRequest.class));
}
@Test
void getUserById_WhenUserNotFound_ShouldReturnNotFound() throws Exception {
// 配置Mock行為
when(userService.findById(99L)).thenThrow(new UserNotFoundException("User not found"));
// 執(zhí)行測(cè)試
mockMvc.perform(get("/api/users/99")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
// 驗(yàn)證交互
verify(userService, times(1)).findById(99L);
}
}
優(yōu)點(diǎn)與局限性
優(yōu)點(diǎn)
- 運(yùn)行速度快:不需要啟動(dòng)Spring上下文或嵌入式服務(wù)器
- 隔離性好:只測(cè)試控制器本身,不涉及其他組件
- 可精確控制依賴行為:通過(guò)Mockito等工具模擬服務(wù)層行為
- 便于覆蓋邊界情況和異常路徑
局限性
- 不測(cè)試Spring配置和依賴注入機(jī)制
- 不驗(yàn)證請(qǐng)求映射注解的正確性
- 不測(cè)試過(guò)濾器、攔截器和其他Web組件
- 可能不反映實(shí)際運(yùn)行時(shí)的完整行為
方案二:使用@WebMvcTest進(jìn)行切片測(cè)試
工作原理
@WebMvcTest是Spring Boot測(cè)試中的一個(gè)切片測(cè)試注解,它只加載MVC相關(guān)組件(控制器、過(guò)濾器、WebMvcConfigurer等),不會(huì)啟動(dòng)完整的應(yīng)用上下文。
這種方法在單元測(cè)試和集成測(cè)試之間取得了平衡,既測(cè)試了Spring MVC配置的正確性,又避免了完整的Spring上下文加載成本。
實(shí)現(xiàn)步驟
引入依賴
與方案一相同,使用spring-boot-starter-test依賴。
編寫切片測(cè)試
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(UserController.class)
public class UserControllerWebMvcTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
@Test
void getUserById_ShouldReturnUser() throws Exception {
// 準(zhǔn)備測(cè)試數(shù)據(jù)
UserDto mockUser = new UserDto(1L, "John Doe", "john@example.com");
// 配置Mock行為
when(userService.findById(1L)).thenReturn(mockUser);
// 執(zhí)行測(cè)試
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("John Doe"))
.andExpect(jsonPath("$.email").value("john@example.com"));
}
@Test
void createUser_WithValidationError_ShouldReturnBadRequest() throws Exception {
// 準(zhǔn)備無(wú)效請(qǐng)求數(shù)據(jù)(缺少必填字段)
UserCreateRequest invalidRequest = new UserCreateRequest("", null);
// 執(zhí)行測(cè)試
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(invalidRequest)))
.andExpect(status().isBadRequest())
.andDo(print()); // 打印請(qǐng)求和響應(yīng)詳情,便于調(diào)試
}
@Test
void testSecurityConfiguration() throws Exception {
// 測(cè)試需要認(rèn)證的端點(diǎn)
mockMvc.perform(delete("/api/users/1"))
.andExpect(status().isUnauthorized());
}
}
測(cè)試自定義過(guò)濾器和攔截器
@WebMvcTest(UserController.class)
@Import({RequestLoggingFilter.class, AuditInterceptor.class})
public class UserControllerWithFiltersTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@MockBean
private AuditService auditService;
@Test
void requestShouldPassThroughFiltersAndInterceptors() throws Exception {
// 準(zhǔn)備測(cè)試數(shù)據(jù)
UserDto mockUser = new UserDto(1L, "John Doe", "john@example.com");
when(userService.findById(1L)).thenReturn(mockUser);
// 執(zhí)行請(qǐng)求,驗(yàn)證經(jīng)過(guò)過(guò)濾器和攔截器后成功返回?cái)?shù)據(jù)
mockMvc.perform(get("/api/users/1")
.header("X-Trace-Id", "test-trace-id"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1));
// 驗(yàn)證攔截器調(diào)用了審計(jì)服務(wù)
verify(auditService, times(1)).logAccess(anyString(), eq("GET"), eq("/api/users/1"));
}
}
優(yōu)點(diǎn)與局限性
優(yōu)點(diǎn)
- 測(cè)試MVC配置的完整性:包括請(qǐng)求映射、數(shù)據(jù)綁定、驗(yàn)證等
- 涵蓋過(guò)濾器和攔截器:驗(yàn)證整個(gè)MVC請(qǐng)求處理鏈路
- 啟動(dòng)速度較快:只加載MVC相關(guān)組件,不加載完整應(yīng)用上下文
- 支持測(cè)試安全配置:可以驗(yàn)證訪問(wèn)控制和認(rèn)證機(jī)制
局限性
- 不測(cè)試實(shí)際的服務(wù)實(shí)現(xiàn):依賴于模擬的服務(wù)層
- 不測(cè)試數(shù)據(jù)訪問(wèn)層:不涉及實(shí)際的數(shù)據(jù)庫(kù)交互
- 配置復(fù)雜度增加:需要模擬或排除更多依賴
- 啟動(dòng)速度雖比完整集成測(cè)試快,但比純單元測(cè)試慢
方案三:基于@SpringBootTest的集成測(cè)試
工作原理
@SpringBootTest會(huì)加載完整的Spring應(yīng)用上下文,可以與嵌入式服務(wù)器集成,測(cè)試真實(shí)的HTTP請(qǐng)求和響應(yīng)。這種方法提供了最接近生產(chǎn)環(huán)境的測(cè)試體驗(yàn),但啟動(dòng)速度較慢,適合端到端功能驗(yàn)證。
實(shí)現(xiàn)步驟
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 可選:如果需要測(cè)試數(shù)據(jù)庫(kù)層 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
編寫集成測(cè)試(使用模擬端口)
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
// 準(zhǔn)備測(cè)試數(shù)據(jù)
User user = new User();
user.setId(1L);
user.setName("John Doe");
user.setEmail("john@example.com");
userRepository.save(user);
}
@Test
void getUserById_ShouldReturnUser() throws Exception {
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("John Doe"))
.andExpect(jsonPath("$.email").value("john@example.com"));
}
@Test
void createUser_ShouldSaveToDatabase() throws Exception {
UserCreateRequest request = new UserCreateRequest("Jane Doe", "jane@example.com");
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Jane Doe"));
// 驗(yàn)證數(shù)據(jù)是否實(shí)際保存到數(shù)據(jù)庫(kù)
Optional<User> savedUser = userRepository.findByEmail("jane@example.com");
assertTrue(savedUser.isPresent());
assertEquals("Jane Doe", savedUser.get().getName());
}
}
編寫集成測(cè)試(使用真實(shí)端口)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerServerIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
// 準(zhǔn)備測(cè)試數(shù)據(jù)
User user = new User();
user.setId(1L);
user.setName("John Doe");
user.setEmail("john@example.com");
userRepository.save(user);
}
@Test
void getUserById_ShouldReturnUser() {
ResponseEntity<UserDto> response = restTemplate.getForEntity("/api/users/1", UserDto.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("John Doe", response.getBody().getName());
}
@Test
void createUser_ShouldReturnCreatedUser() {
UserCreateRequest request = new UserCreateRequest("Jane Doe", "jane@example.com");
ResponseEntity<UserDto> response = restTemplate.postForEntity(
"/api/users", request, UserDto.class);
assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertNotNull(response.getBody().getId());
assertEquals("Jane Doe", response.getBody().getName());
}
@Test
void testCaching() {
// 第一次請(qǐng)求
long startTime = System.currentTimeMillis();
ResponseEntity<UserDto> response1 = restTemplate.getForEntity("/api/users/1", UserDto.class);
long firstRequestTime = System.currentTimeMillis() - startTime;
// 第二次請(qǐng)求(應(yīng)該從緩存獲?。?
startTime = System.currentTimeMillis();
ResponseEntity<UserDto> response2 = restTemplate.getForEntity("/api/users/1", UserDto.class);
long secondRequestTime = System.currentTimeMillis() - startTime;
// 驗(yàn)證兩次請(qǐng)求返回相同數(shù)據(jù)
assertEquals(response1.getBody().getId(), response2.getBody().getId());
// 通常緩存請(qǐng)求會(huì)明顯快于首次請(qǐng)求
assertTrue(secondRequestTime < firstRequestTime,
"第二次請(qǐng)求應(yīng)該更快(緩存生效)");
}
}
使用測(cè)試配置覆蓋生產(chǎn)配置
創(chuàng)建測(cè)試專用配置文件src/test/resources/application-test.yml:
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
# 禁用某些生產(chǎn)環(huán)境組件
app:
scheduling:
enabled: false
external-services:
payment-gateway: mock
在測(cè)試類中指定配置文件:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class UserControllerConfiguredTest {
// 測(cè)試內(nèi)容
}
優(yōu)點(diǎn)與局限性
優(yōu)點(diǎn)
- 全面測(cè)試:覆蓋從HTTP請(qǐng)求到數(shù)據(jù)庫(kù)的完整流程
- 真實(shí)行為驗(yàn)證:測(cè)試實(shí)際的服務(wù)實(shí)現(xiàn)和組件交互
- 發(fā)現(xiàn)集成問(wèn)題:能找出組件集成時(shí)的問(wèn)題
- 適合功能測(cè)試:驗(yàn)證完整的業(yè)務(wù)功能
局限性
- 啟動(dòng)速度慢:需要加載完整Spring上下文
- 測(cè)試隔離性差:測(cè)試可能相互影響
- 配置和設(shè)置復(fù)雜:需要管理測(cè)試環(huán)境配置
- 調(diào)試?yán)щy:出錯(cuò)時(shí)定位問(wèn)題復(fù)雜
- 不適合覆蓋全部場(chǎng)景:不可能覆蓋所有邊界情況
方案四:使用TestRestTemplate/WebTestClient進(jìn)行端到端測(cè)試
工作原理
此方法使用專為測(cè)試設(shè)計(jì)的HTTP客戶端,向?qū)嶋H運(yùn)行的嵌入式服務(wù)器發(fā)送請(qǐng)求,接收并驗(yàn)證響應(yīng)。TestRestTemplate適用于同步測(cè)試,而WebTestClient支持反應(yīng)式和非反應(yīng)式應(yīng)用的測(cè)試,并提供更流暢的API。
實(shí)現(xiàn)步驟
使用TestRestTemplate(同步測(cè)試)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerE2ETest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void testCompleteUserLifecycle() {
// 1. 創(chuàng)建用戶
UserCreateRequest createRequest = new UserCreateRequest("Test User", "test@example.com");
ResponseEntity<UserDto> createResponse = restTemplate.postForEntity(
"/api/users", createRequest, UserDto.class);
assertEquals(HttpStatus.CREATED, createResponse.getStatusCode());
Long userId = createResponse.getBody().getId();
// 2. 獲取用戶
ResponseEntity<UserDto> getResponse = restTemplate.getForEntity(
"/api/users/" + userId, UserDto.class);
assertEquals(HttpStatus.OK, getResponse.getStatusCode());
assertEquals("Test User", getResponse.getBody().getName());
// 3. 更新用戶
UserUpdateRequest updateRequest = new UserUpdateRequest("Updated User", null);
restTemplate.put("/api/users/" + userId, updateRequest);
// 驗(yàn)證更新成功
ResponseEntity<UserDto> afterUpdateResponse = restTemplate.getForEntity(
"/api/users/" + userId, UserDto.class);
assertEquals("Updated User", afterUpdateResponse.getBody().getName());
assertEquals("test@example.com", afterUpdateResponse.getBody().getEmail());
// 4. 刪除用戶
restTemplate.delete("/api/users/" + userId);
// 驗(yàn)證刪除成功
ResponseEntity<UserDto> afterDeleteResponse = restTemplate.getForEntity(
"/api/users/" + userId, UserDto.class);
assertEquals(HttpStatus.NOT_FOUND, afterDeleteResponse.getStatusCode());
}
}
使用WebTestClient(支持反應(yīng)式測(cè)試)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerWebClientTest {
@Autowired
private WebTestClient webTestClient;
@Test
void testUserApi() {
// 創(chuàng)建用戶并獲取ID
UserCreateRequest createRequest = new UserCreateRequest("Reactive User", "reactive@example.com");
UserDto createdUser = webTestClient.post()
.uri("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(createRequest)
.exchange()
.expectStatus().isCreated()
.expectBody(UserDto.class)
.returnResult()
.getResponseBody();
Long userId = createdUser.getId();
// 獲取用戶
webTestClient.get()
.uri("/api/users/{id}", userId)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.name").isEqualTo("Reactive User")
.jsonPath("$.email").isEqualTo("reactive@example.com");
// 驗(yàn)證查詢API
webTestClient.get()
.uri(uriBuilder -> uriBuilder
.path("/api/users")
.queryParam("email", "reactive@example.com")
.build())
.exchange()
.expectStatus().isOk()
.expectBodyList(UserDto.class)
.hasSize(1)
.contains(createdUser);
}
@Test
void testPerformance() {
// 測(cè)試API響應(yīng)時(shí)間
webTestClient.get()
.uri("/api/users")
.exchange()
.expectStatus().isOk()
.expectBody()
.consumeWith(response -> {
long responseTime = response.getResponseHeaders()
.getFirst("X-Response-Time") != null
? Long.parseLong(response.getResponseHeaders().getFirst("X-Response-Time"))
: 0;
// 驗(yàn)證響應(yīng)時(shí)間在可接受范圍內(nèi)
assertTrue(responseTime < 500, "API響應(yīng)時(shí)間應(yīng)小于500ms");
});
}
}
優(yōu)點(diǎn)與局限性
優(yōu)點(diǎn)
- 完整測(cè)試:驗(yàn)證應(yīng)用在真實(shí)環(huán)境中的行為
- 端到端驗(yàn)證:測(cè)試從HTTP請(qǐng)求到數(shù)據(jù)庫(kù)的全流程
- 符合用戶視角:從客戶端角度驗(yàn)證功能
- 支持高級(jí)場(chǎng)景:可測(cè)試認(rèn)證、性能、流量等
局限性
- 運(yùn)行慢:完整上下文啟動(dòng)耗時(shí)長(zhǎng)
- 環(huán)境依賴:可能需要外部服務(wù)和資源
- 維護(hù)成本高:測(cè)試復(fù)雜度和脆弱性增加
- 不適合單元覆蓋:難以覆蓋所有邊界情況
- 調(diào)試?yán)щy:?jiǎn)栴}定位和修復(fù)復(fù)雜
方案對(duì)比與選擇建議
| 特性 | MockMvc單元測(cè)試 | @WebMvcTest切片測(cè)試 | @SpringBootTest集成測(cè)試 | TestRestTemplate/WebTestClient |
|---|---|---|---|---|
| 上下文加載 | 不加載 | 只加載MVC組件 | 完整加載 | 完整加載 |
| 啟動(dòng)服務(wù)器 | 否 | 否 | 可選 | 是 |
| 測(cè)試速度 | 最快 | 快 | 慢 | 最慢 |
| 測(cè)試隔離性 | 最高 | 高 | 中 | 低 |
| 覆蓋范圍 | 控制器邏輯 | MVC配置和組件 | 全棧集成 | 全棧端到端 |
| 配置復(fù)雜度 | 低 | 中 | 高 | 高 |
| 適用場(chǎng)景 | 控制器單元邏輯 | MVC配置驗(yàn)證 | 功能集成測(cè)試 | 用戶端體驗(yàn)驗(yàn)證 |
| 模擬依賴 | 完全模擬 | 部分模擬 | 少量或不模擬 | 少量或不模擬 |
總結(jié)
SpringBoot為WebMVC測(cè)試提供了豐富的工具和策略,從輕量的單元測(cè)試到全面的端到端測(cè)試。選擇合適的測(cè)試方案,需要權(quán)衡測(cè)試覆蓋范圍、執(zhí)行效率、維護(hù)成本和團(tuán)隊(duì)熟悉度。
無(wú)論選擇哪種測(cè)試方案,持續(xù)測(cè)試和持續(xù)改進(jìn)都是軟件質(zhì)量保障的核心理念。
到此這篇關(guān)于SpringBoot測(cè)試WebMVC的4種方法詳解的文章就介紹到這了,更多相關(guān)SpringBoot測(cè)試WebMVC內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何將java -jar啟動(dòng)的服務(wù)設(shè)置為systemd服務(wù)管理方式
本文詳細(xì)介紹了如何將Java應(yīng)用程序配置為由systemd管理的服務(wù),包括創(chuàng)建和配置.service文件的步驟,以及如何啟動(dòng)、停止和查看服務(wù)狀態(tài)2025-01-01
Java中的ReentrantReadWriteLock實(shí)現(xiàn)原理詳解
這篇文章主要介紹了Java中的ReentrantReadWriteLock實(shí)現(xiàn)原理詳解,讀寫鎖實(shí)現(xiàn)了接口ReadWriteLock,適合于讀多寫少的情況,支持公平鎖和非公平鎖,支持可沖入(進(jìn)入讀鎖后可再進(jìn)入讀鎖,進(jìn)入寫鎖后可再進(jìn)入寫鎖和讀鎖),需要的朋友可以參考下2024-01-01
java中實(shí)現(xiàn)list或set轉(zhuǎn)map的方法
這篇文章主要介紹了java中實(shí)現(xiàn)list或set轉(zhuǎn)map的方法的相關(guān)資料,需要的朋友可以參考下2017-01-01
解決IDEA創(chuàng)建maven項(xiàng)目時(shí)pom.xml沒(méi)有變藍(lán)的問(wèn)題
這篇文章主要介紹了解決IDEA創(chuàng)建maven項(xiàng)目時(shí)pom.xml沒(méi)有變藍(lán)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
詳解Java實(shí)現(xiàn)批量壓縮圖片裁剪壓縮多種尺寸縮略圖一鍵批量上傳圖片
這篇文章主要介紹了Java實(shí)現(xiàn)批量壓縮圖片裁剪壓縮多種尺寸縮略圖一鍵批量上傳圖片,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
從0開始學(xué)習(xí)大數(shù)據(jù)之java spark編程入門與項(xiàng)目實(shí)踐
這篇文章主要介紹了從0開始學(xué)習(xí)大數(shù)據(jù)之java spark編程入門與項(xiàng)目實(shí)踐,結(jié)合具體入門項(xiàng)目分析了大數(shù)據(jù)java spark編程項(xiàng)目建立、調(diào)試、輸出等相關(guān)步驟及操作技巧,需要的朋友可以參考下2019-11-11
springboot項(xiàng)目獲取resources相對(duì)路徑的方法
這篇文章主要介紹了springboot項(xiàng)目獲取resources相對(duì)路徑的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12

