都2025年了,Spring Boot開發(fā)中的這些錯誤不要再犯了
環(huán)境:SpringBoot3.4.2
1. 簡介
Spring Boot是一個基于Spring框架的輕量級開發(fā)框架,它簡化了基于Spring的應(yīng)用開發(fā)。通過提供默認配置和一系列開箱即用的工具,Spring Boot使得開發(fā)者能夠快速構(gòu)建獨立、生產(chǎn)級別的Spring應(yīng)用。它采用“約定優(yōu)于配置”的理念,大大減少了XML配置的使用,讓開發(fā)者能夠更加專注于業(yè)務(wù)邏輯的實現(xiàn)。
現(xiàn)在是2025年了,但我仍然看到開發(fā)者,無論是初學(xué)者還是經(jīng)驗豐富的開發(fā)者,都在犯同樣的本可避免的錯誤。關(guān)鍵是有些錯誤反而寫入了公司的規(guī)范中。
2. 不該犯的錯
2.1 暴露敏感數(shù)據(jù)
這種錯誤是非常致命的,稍有不慎就可能將非常關(guān)鍵的數(shù)據(jù)暴露給了用戶。如下示例:
@RestController
@RequestMapping("/users")
public class UserController {
@Resource
private UserRepository userRepository;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id).orElseThrow() ;
}
}
應(yīng)該有不少公司都還在這樣做吧,直接將實體對象返回到客戶端。看起來無害,對吧?錯!如果你的User實體包含像密碼這樣的敏感字段怎么辦?即使你在它們上面加上了@JsonIgnore注解,你仍然是在暴露內(nèi)部實現(xiàn)細節(jié)。
解決方法 始終使用 DTO(數(shù)據(jù)傳輸對象)。如果你使用的Java版本是14以上,那么你可以直接使用record類型,如下示例:
public record UserDTO(Long id, String name, String email) {}
2.2 為什么不用Records
如果你已經(jīng)使用上了Java 14以上的版本,那么在編寫那種不可變的POJO對象時請使用record,不要再像如下定義了:
public class UserResponse {
private final String username ;
private final Integer age ;
public UserVO(String username, Integer age) {
this.username = username ;
this.age = age ;
}
// getters, setters
}
使用Records一行代碼搞定了:
public record UserResponse(String username, Integer age) {}
代碼更少,更清晰。
2.3 字段注入還在使用注解方式
很多開發(fā)者把@Autowired/@Reosurce當(dāng)作魔杖一樣使用。問題在于,對于構(gòu)造器注入來說,它完全是不必要的。
@Service
public class UserService {
@Resource
private UserRepository userRepository ;
}
正確的方式:
@Service
public class UserService {
private final UserRepository userRepository ;
public UserService(UserRepository userRepository) {
this.userRepository= userRepository ;
}
}
更簡潔、更易測試,并且避免了不必要的反射開銷。
2.4 還在使用RestTemplate
RestTemplate是基于阻塞IO的,在訪問量比較大的情況下,它可能會給業(yè)務(wù)系統(tǒng)帶來壓力,甚至影響到系統(tǒng)的穩(wěn)定性和可用性。并且RestTemplate目前處于維護模式,官方更推薦采用WebClient作為替代方案,以利用其基于Reactor的響應(yīng)式編程模型,實現(xiàn)非阻塞的HTTP客戶端功能。如下示例:
@Service
public class PaymentService {
private final WebClient webClient;
public PaymentService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("https://www.payment.com/api").build();
}
public Mono<PaymentResponse> processPayment(PaymentRequest request) {
return webClient.post()
.uri("/pay")
.bodyValue(request)
.retrieve()
.bodyToMono(PaymentResponse.class);
}
}
官方的建議如下:
圖片
2.5 臃腫的Controller
一股腦的將所有的東西都堆到Controller層,這非常容易讓你的代碼變得難以維護。
錯誤示例:
@RestController
@RequestMapping("/users")
public class UserController {
@Resource
private UserRepository userRepository;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id).orElseThrow() ;
}
}
這樣應(yīng)該是常識性錯誤吧。
正確示例:
@Service
public class UserService {
private final UserRepository userRepository;
public User getUser(Long id) {
return userRepository.findById(id).orElseThrow() ;
}
}
// Controller中調(diào)用該Service
確保控制器只能處理 HTTP 請求,而不能處理業(yè)務(wù)邏輯。
2.6 忽視的異常處理
不要再對于任何的錯誤都返回RuntimeException異常,而是應(yīng)該精細化的控制異常,如:查詢指定id用戶,如果不存在應(yīng)該返回404,而不是500錯誤。
錯誤示例:
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用戶不存在"));
}
RuntimeException是一種未檢查的異常,除非明確處理,否則會冒泡到Spring的默認異常處理器。Spring對于未處理異常的默認行為是返回500內(nèi)部服務(wù)器錯誤。
正確示例:
使用自定義異常和全局異常處理程序。
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message) ;
}
}
當(dāng)拋出該異常后,默認情況下,Spring MVC會通過ResponseStatusExceptionResolver進行處理,它會直接發(fā)送錯誤信息(狀態(tài)碼為404)到客戶端不夠優(yōu)雅;我們可以通過自定義的全局異常進行處理:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleNotFound(UserNotFoundException ex) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body("用戶不存在, " + ex.getMessage()) ;
}
}