SpringMVC開(kāi)發(fā)中十大常見(jiàn)問(wèn)題深度解析與解決方案
引言
在Java Web開(kāi)發(fā)領(lǐng)域,SpringMVC作為一款主流的Web框架,憑借其強(qiáng)大的功能和便捷的開(kāi)發(fā)體驗(yàn)深受開(kāi)發(fā)者喜愛(ài)。然而,在實(shí)際使用過(guò)程中,開(kāi)發(fā)者常常會(huì)遇到各種各樣的“坑”。本文將針對(duì)SpringMVC開(kāi)發(fā)中常見(jiàn)的十大問(wèn)題,結(jié)合實(shí)際案例和代碼,深入剖析問(wèn)題產(chǎn)生的原因,并提供詳細(xì)的解決方案,幫助大家在開(kāi)發(fā)過(guò)程中少走彎路。
一、自定義異??偪床欢??是設(shè)計(jì)邏輯出了問(wèn)題嗎?
在SpringMVC項(xiàng)目中,當(dāng)業(yè)務(wù)邏輯變得復(fù)雜時(shí),使用自定義異??梢愿逦靥幚聿煌愋偷腻e(cuò)誤情況。但有時(shí)開(kāi)發(fā)者會(huì)發(fā)現(xiàn)自定義異常難以理解,這往往是因?yàn)楫惓TO(shè)計(jì)邏輯不夠清晰。
問(wèn)題場(chǎng)景
假設(shè)我們正在開(kāi)發(fā)一個(gè)電商系統(tǒng),在用戶下單時(shí)需要檢查庫(kù)存是否充足。當(dāng)庫(kù)存不足時(shí),希望拋出一個(gè)自定義的InsufficientStockException異常。但在實(shí)際調(diào)試過(guò)程中,發(fā)現(xiàn)異常信息混亂,難以定位問(wèn)題根源。
原因分析
自定義異常設(shè)計(jì)不規(guī)范,沒(méi)有合理繼承已有的異常體系,或者異常信息沒(méi)有包含足夠的上下文信息,導(dǎo)致在捕獲和處理異常時(shí)無(wú)法準(zhǔn)確判斷異常情況。
解決方案
- 定義自定義異常類,合理繼承
RuntimeException或Exception。例如:
// 繼承RuntimeException,定義庫(kù)存不足異常
public class InsufficientStockException extends RuntimeException {
public InsufficientStockException(String message) {
super(message);
}
}
- 在業(yè)務(wù)邏輯中使用自定義異常。以庫(kù)存檢查為例:
@Service
public class OrderService {
private int stock = 10; // 模擬庫(kù)存數(shù)量
public void placeOrder(int quantity) {
if (quantity > stock) {
// 庫(kù)存不足時(shí)拋出自定義異常
throw new InsufficientStockException("庫(kù)存不足,無(wú)法下單");
}
// 正常下單邏輯
}
}
- 使用全局異常處理器統(tǒng)一處理異常,讓異常信息更清晰易讀。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(InsufficientStockException.class)
public String handleInsufficientStockException(InsufficientStockException e) {
return "錯(cuò)誤信息:" + e.getMessage();
}
}
二、自定義異常不生效?為何還在報(bào)500錯(cuò)誤?
開(kāi)發(fā)者定義好自定義異常并配置了異常處理器后,有時(shí)會(huì)發(fā)現(xiàn)自定義異常并沒(méi)有按照預(yù)期處理,頁(yè)面仍然顯示500錯(cuò)誤。
問(wèn)題場(chǎng)景
在上述電商系統(tǒng)中,配置好InsufficientStockException及其處理器后,下單時(shí)庫(kù)存不足依然顯示500錯(cuò)誤頁(yè)面。
原因分析
- 異常處理器配置錯(cuò)誤,沒(méi)有被Spring容器正確掃描到。
- 異常沒(méi)有被正確拋出,在拋出異常之前可能被其他代碼捕獲處理。
- 全局異常處理器的優(yōu)先級(jí)問(wèn)題,其他優(yōu)先級(jí)更高的異常處理機(jī)制先攔截了異常。
解決方案
- 確保異常處理器所在的類被
@RestControllerAdvice或@ControllerAdvice注解標(biāo)注,并且所在的包被Spring容器掃描。例如,在Spring Boot項(xiàng)目的啟動(dòng)類上添加@ComponentScan注解,掃描包含異常處理器的包:
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 檢查業(yè)務(wù)代碼中異常拋出的邏輯,確保異常能夠順利拋出到全局異常處理器。
- 如果存在多個(gè)異常處理器,調(diào)整其優(yōu)先級(jí)??梢酝ㄟ^(guò)實(shí)現(xiàn)
Ordered接口,重寫getOrder方法來(lái)設(shè)置優(yōu)先級(jí),數(shù)值越小優(yōu)先級(jí)越高:
@RestControllerAdvice
public class GlobalExceptionHandler implements Ordered {
@ExceptionHandler(InsufficientStockException.class)
public String handleInsufficientStockException(InsufficientStockException e) {
return "錯(cuò)誤信息:" + e.getMessage();
}
@Override
public int getOrder() {
return 1; // 設(shè)置優(yōu)先級(jí)
}
}
三、時(shí)間格式轉(zhuǎn)換失敗?POST請(qǐng)求的“陷阱”注意到了嗎?
在處理包含日期時(shí)間類型參數(shù)的POST請(qǐng)求時(shí),經(jīng)常會(huì)遇到時(shí)間格式轉(zhuǎn)換失敗的問(wèn)題。
問(wèn)題場(chǎng)景
前端通過(guò)POST請(qǐng)求發(fā)送一個(gè)包含日期時(shí)間字段的數(shù)據(jù)到后端,后端使用@RequestBody接收數(shù)據(jù)并綁定到實(shí)體類中,但在轉(zhuǎn)換過(guò)程中出現(xiàn)Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date'錯(cuò)誤。
原因分析
- 前端發(fā)送的日期時(shí)間格式與后端期望的格式不一致。
- SpringMVC默認(rèn)的日期時(shí)間格式轉(zhuǎn)換配置不符合需求。
- 在POST請(qǐng)求中,
@RequestBody解析數(shù)據(jù)時(shí),對(duì)于日期時(shí)間類型的轉(zhuǎn)換規(guī)則與GET請(qǐng)求不同,需要額外配置。
解決方案
- 在實(shí)體類的日期時(shí)間字段上使用
@DateTimeFormat注解指定日期時(shí)間格式。例如:
public class Order {
private Long id;
// 指定日期時(shí)間格式為"yyyy-MM-dd HH:mm:ss"
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date orderTime;
// 省略getter和setter方法
}
- 配置SpringMVC的日期時(shí)間格式化。在Spring Boot項(xiàng)目中,可以在
application.properties文件中添加以下配置:
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8
- 如果上述方法無(wú)效,可以自定義一個(gè)
Converter來(lái)處理日期時(shí)間格式轉(zhuǎn)換。例如:
@Component
public class CustomDateConverter implements Converter<String, Date> {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public Date convert(String source) {
try {
return sdf.parse(source);
} catch (ParseException e) {
throw new IllegalArgumentException("日期格式轉(zhuǎn)換失敗", e);
}
}
}
然后在配置類中注冊(cè)這個(gè)轉(zhuǎn)換器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private CustomDateConverter customDateConverter;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(customDateConverter);
}
}
四、調(diào)試斷點(diǎn)失效?是不是被多個(gè)Filter“攔截”了?
在調(diào)試SpringMVC項(xiàng)目時(shí),有時(shí)會(huì)發(fā)現(xiàn)設(shè)置的斷點(diǎn)無(wú)法進(jìn)入,導(dǎo)致調(diào)試工作無(wú)法正常進(jìn)行。
問(wèn)題場(chǎng)景
在控制器方法中設(shè)置了斷點(diǎn),啟動(dòng)調(diào)試模式后,請(qǐng)求到達(dá)該方法時(shí)斷點(diǎn)沒(méi)有生效,直接跳過(guò)執(zhí)行后續(xù)代碼。
原因分析
- 項(xiàng)目中存在多個(gè)Filter,請(qǐng)求在到達(dá)控制器之前被其他Filter攔截處理,導(dǎo)致無(wú)法進(jìn)入斷點(diǎn)所在的控制器方法。
- Filter的配置順序不合理,某些Filter在處理請(qǐng)求時(shí)消耗了請(qǐng)求資源,使得后續(xù)請(qǐng)求無(wú)法正常處理。
- 斷點(diǎn)設(shè)置的位置存在問(wèn)題,例如在靜態(tài)方法或沒(méi)有被Spring容器管理的類中設(shè)置斷點(diǎn)。
解決方案
- 檢查項(xiàng)目中的Filter配置,確保沒(méi)有不必要的Filter攔截請(qǐng)求。可以通過(guò)在Filter的
doFilter方法中添加日志輸出,查看請(qǐng)求是否經(jīng)過(guò)該Filter:
@Component
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("請(qǐng)求進(jìn)入CustomFilter");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("請(qǐng)求離開(kāi)CustomFilter");
}
@Override
public void destroy() {
}
}
- 調(diào)整Filter的順序,確保關(guān)鍵的Filter在合適的位置執(zhí)行。可以通過(guò)實(shí)現(xiàn)
Ordered接口,重寫getOrder方法來(lái)設(shè)置Filter的執(zhí)行順序:
@Component
public class CustomFilter implements Filter, Ordered {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
@Override
public int getOrder() {
return 1; // 設(shè)置Filter執(zhí)行順序
}
}
- 確保斷點(diǎn)設(shè)置在被Spring容器管理的類和方法中,并且方法不是靜態(tài)方法。
五、Request輸入流讀取后消失?響應(yīng)體處理遺漏了?
在處理請(qǐng)求和響應(yīng)時(shí),可能會(huì)遇到Request輸入流讀取一次后無(wú)法再次讀取,或者響應(yīng)體處理不當(dāng)導(dǎo)致數(shù)據(jù)丟失的問(wèn)題。
問(wèn)題場(chǎng)景
在一個(gè)需要多次讀取Request輸入流的場(chǎng)景中,第一次讀取后,后續(xù)讀取操作獲取到的輸入流為空。在處理響應(yīng)時(shí),發(fā)現(xiàn)響應(yīng)數(shù)據(jù)沒(méi)有按照預(yù)期輸出。
原因分析
HttpServletRequest的輸入流默認(rèn)只能讀取一次,讀取后流會(huì)被關(guān)閉或重置,導(dǎo)致后續(xù)無(wú)法再次讀取。- 在響應(yīng)體處理過(guò)程中,沒(méi)有正確設(shè)置響應(yīng)頭信息,或者沒(méi)有將數(shù)據(jù)正確寫入響應(yīng)體。
- 存在其他代碼在處理請(qǐng)求或響應(yīng)過(guò)程中,意外關(guān)閉了輸入流或響應(yīng)流。
解決方案
- 自定義一個(gè)可以重復(fù)讀取的
HttpServletRequest包裝類。例如:
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] body;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
body = IOUtils.toByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return bais.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
然后在Filter中使用這個(gè)包裝類來(lái)替換原始的HttpServletRequest:
@Component
public class RequestBodyCacheFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(httpServletRequest);
filterChain.doFilter(cachedBodyHttpServletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
- 正確處理響應(yīng)體,設(shè)置響應(yīng)頭信息并將數(shù)據(jù)寫入響應(yīng)體。例如:
@RestController
public class HelloController {
@GetMapping("/hello")
public void hello(HttpServletResponse response) throws IOException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("{\"message\":\"Hello, World!\"}");
writer.flush();
writer.close();
}
}
- 檢查項(xiàng)目中所有涉及請(qǐng)求和響應(yīng)處理的代碼,確保沒(méi)有意外關(guān)閉輸入流或響應(yīng)流的操作。
六、參數(shù)綁定總出錯(cuò)?是類型轉(zhuǎn)換規(guī)則沒(méi)吃透嗎?
在SpringMVC中進(jìn)行參數(shù)綁定時(shí),經(jīng)常會(huì)出現(xiàn)參數(shù)類型轉(zhuǎn)換錯(cuò)誤的問(wèn)題,導(dǎo)致請(qǐng)求無(wú)法正確處理。
問(wèn)題場(chǎng)景
前端傳遞一個(gè)字符串類型的參數(shù),后端控制器方法期望接收一個(gè)整數(shù)類型的參數(shù),但在綁定過(guò)程中出現(xiàn)Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'錯(cuò)誤。
原因分析
- 前端傳遞的參數(shù)類型與后端控制器方法參數(shù)類型不匹配,并且SpringMVC無(wú)法自動(dòng)進(jìn)行正確的類型轉(zhuǎn)換。
- 自定義的類型轉(zhuǎn)換規(guī)則沒(méi)有生效,或者類型轉(zhuǎn)換規(guī)則定義錯(cuò)誤。
- 參數(shù)名稱不一致,導(dǎo)致SpringMVC無(wú)法正確匹配參數(shù)。
解決方案
- 確保前端傳遞的參數(shù)類型與后端控制器方法參數(shù)類型兼容,并且SpringMVC支持自動(dòng)類型轉(zhuǎn)換。如果不支持自動(dòng)轉(zhuǎn)換,可以使用
@RequestParam注解的required屬性設(shè)置為false,避免參數(shù)不存在時(shí)拋出異常:
@GetMapping("/user")
public String getUser(@RequestParam(value = "age", required = false) Integer age) {
if (age == null) {
return "年齡參數(shù)未傳遞";
}
return "用戶年齡為:" + age;
}
- 對(duì)于復(fù)雜的類型轉(zhuǎn)換,可以自定義類型轉(zhuǎn)換器。例如,將字符串轉(zhuǎn)換為自定義的
User對(duì)象:
public class User {
private String name;
private int age;
// 省略getter和setter方法
}
@Component
public class UserConverter implements Converter<String, User> {
@Override
public User convert(String source) {
String[] parts = source.split(",");
User user = new User();
user.setName(parts[0]);
user.setAge(Integer.parseInt(parts[1]));
return user;
}
}
然后在配置類中注冊(cè)這個(gè)轉(zhuǎn)換器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private UserConverter userConverter;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(userConverter);
}
}
- 檢查參數(shù)名稱是否一致,確保前端傳遞的參數(shù)名與后端控制器方法中
@RequestParam或@RequestBody注解指定的參數(shù)名相同。
七、表單提交亂碼?編碼配置環(huán)節(jié)是否疏忽了?
在處理表單提交時(shí),有時(shí)會(huì)出現(xiàn)提交的數(shù)據(jù)在后端顯示為亂碼的情況。
問(wèn)題場(chǎng)景
用戶在前端表單中輸入中文內(nèi)容并提交,后端接收到的中文內(nèi)容顯示為亂碼。
原因分析
- 前端表單的
accept-charset屬性沒(méi)有正確設(shè)置,或者設(shè)置的編碼格式與后端不一致。 - SpringMVC的編碼過(guò)濾器配置錯(cuò)誤,沒(méi)有對(duì)請(qǐng)求進(jìn)行正確的編碼處理。
- 服務(wù)器的默認(rèn)編碼設(shè)置與項(xiàng)目要求的編碼不一致。
解決方案
- 在前端表單中設(shè)置
accept-charset屬性為UTF-8:
<form action="/submit" method="post" accept-charset="UTF-8">
<input type="text" name="username" />
<input type="submit" value="提交" />
</form>
- 在Spring Boot項(xiàng)目中,配置
CharacterEncodingFilter來(lái)處理請(qǐng)求編碼。在application.properties文件中添加以下配置:
spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true spring.http.encoding.force=true
- 如果上述配置無(wú)效,可以自定義一個(gè)
Filter來(lái)處理編碼問(wèn)題:
@Component
public class EncodingFilter implements Filter {
private static final String ENCODING = "UTF-8";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding(ENCODING);
servletResponse.setCharacterEncoding(ENCODING);
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
- 檢查服務(wù)器的默認(rèn)編碼設(shè)置,確保與項(xiàng)目要求的編碼一致。例如,在Tomcat服務(wù)器中,可以在
conf/server.xml文件中設(shè)置URIEncoding="UTF-8":
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8"/>
八、攔截器攔截范圍不對(duì)?匹配規(guī)則真的設(shè)置正確了?
在使用攔截器對(duì)請(qǐng)求進(jìn)行攔截處理時(shí),可能會(huì)出現(xiàn)攔截范圍不符合預(yù)期的問(wèn)題。
問(wèn)題場(chǎng)景
配置了一個(gè)攔截器用于攔截所有的用戶請(qǐng)求進(jìn)行權(quán)限驗(yàn)證,但某些請(qǐng)求卻沒(méi)有被攔截到;或者不應(yīng)該被攔截的請(qǐng)求反而被攔截了。
原因分析
- 攔截器的
addPathPatterns和excludePathPatterns方法設(shè)置的匹配規(guī)則不正確,沒(méi)有準(zhǔn)確覆蓋需要攔截或排除的請(qǐng)求路徑。 - 攔截器的注冊(cè)順序問(wèn)題,導(dǎo)致部分請(qǐng)求在攔截器生效之前就已經(jīng)被處理。
- 路徑匹配規(guī)則中使用的通配符(如
*、**)理解錯(cuò)誤,導(dǎo)致匹配范圍不準(zhǔn)確。
解決方案
1. 定義攔截器類,實(shí)現(xiàn) HandlerInterceptor 接口,在 preHandle 方法中進(jìn)行攔截邏輯處理:
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 簡(jiǎn)單示例:判斷請(qǐng)求中是否包含特定參數(shù)作為權(quán)限驗(yàn)證
String authToken = request.getParameter("authToken");
if (authToken == null || !"valid_token".equals(authToken)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "權(quán)限不足");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2. 在配置類中注冊(cè)攔截器,并設(shè)置攔截和排除路徑:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PermissionInterceptor())
.addPathPatterns("/user/**") // 攔截所有以 /user/ 開(kāi)頭的請(qǐng)求
.excludePathPatterns("/user/login", "/user/register"); // 排除登錄和注冊(cè)請(qǐng)求
}
}
3. 若存在多個(gè)攔截器,通過(guò)實(shí)現(xiàn) Ordered 接口控制執(zhí)行順序:
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AnotherInterceptor implements HandlerInterceptor, Ordered {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 攔截邏輯
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
@Override
public int getOrder() {
return 2; // 數(shù)值越小優(yōu)先級(jí)越高,假設(shè) PermissionInterceptor 優(yōu)先級(jí)為 1
}
}
并在配置類中注冊(cè)該攔截器,這樣就能按順序執(zhí)行攔截邏輯。
九、視圖解析失?。磕0逡媾渲贸鰡?wèn)題了嗎?
在使用模板引擎(如 Thymeleaf、Freemarker)時(shí),常常會(huì)遇到視圖解析失敗的情況,頁(yè)面無(wú)法正確渲染。
問(wèn)題場(chǎng)景
在 SpringMVC 項(xiàng)目中集成了 Thymeleaf 模板引擎,控制器方法返回視圖名稱后,頁(yè)面顯示 Whitelabel Error Page,提示找不到對(duì)應(yīng)的視圖。
原因分析
- 模板引擎的依賴沒(méi)有正確引入,或者版本不兼容。
- 模板引擎的配置不正確,如視圖前綴、后綴設(shè)置錯(cuò)誤,導(dǎo)致無(wú)法找到對(duì)應(yīng)的模板文件。
- 模板文件的存放位置不符合配置要求,或者文件名拼寫錯(cuò)誤。
解決方案
以 Thymeleaf 為例:
1. 確保在 pom.xml 文件中正確引入 Thymeleaf 依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2. 在 application.properties 文件中配置 Thymeleaf 的視圖前綴和后綴:
spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.thymeleaf.mode=HTML spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.content-type=text/html
上述配置表示 Thymeleaf 會(huì)在 classpath:/templates/ 目錄下尋找模板文件,并且模板文件的后綴為 .html。
3. 確保模板文件存放在正確的目錄下,并且文件名與控制器返回的視圖名稱一致。例如,控制器方法:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "index"; // 返回視圖名稱為 index,對(duì)應(yīng) templates 目錄下的 index.html 文件
}
}
同時(shí),檢查模板文件中是否存在語(yǔ)法錯(cuò)誤,如標(biāo)簽閉合不正確、表達(dá)式錯(cuò)誤等,這些也可能導(dǎo)致視圖解析失敗。
十、跨域請(qǐng)求被拒?CORS 配置是否完整?
在前后端分離項(xiàng)目中,經(jīng)常會(huì)遇到跨域請(qǐng)求被拒絕的問(wèn)題,影響前后端數(shù)據(jù)交互。
問(wèn)題場(chǎng)景
前端發(fā)起請(qǐng)求到后端接口,瀏覽器控制臺(tái)提示 Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy 錯(cuò)誤,請(qǐng)求無(wú)法成功發(fā)送。
原因分析
- 后端沒(méi)有配置 CORS(Cross-Origin Resource Sharing,跨域資源共享)相關(guān)規(guī)則,瀏覽器出于安全策略限制了跨域請(qǐng)求。
- CORS 配置不完整,如只允許了部分請(qǐng)求方法、沒(méi)有設(shè)置允許攜帶憑證等,導(dǎo)致請(qǐng)求不符合跨域規(guī)則。
解決方案
方式一:使用 @CrossOrigin 注解
在控制器類或方法上添加 @CrossOrigin 注解,簡(jiǎn)單快速地解決跨域問(wèn)題。例如:
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin(origins = "http://localhost:3000", allowedHeaders = "*", methods = {java.net.HttpURLConnection.HTTP_GET, java.net.HttpURLConnection.HTTP_POST})
public class ApiController {
@GetMapping("/data")
public String getData() {
return "Hello, Cross-Origin Data";
}
}
上述代碼中,@CrossOrigin 注解允許來(lái)自 http://localhost:3000 的請(qǐng)求,允許所有請(qǐng)求頭,支持 GET 和 POST 請(qǐng)求方法。
方式二:全局 CORS 配置
通過(guò)配置類實(shí)現(xiàn) WebMvcConfigurer 接口,重寫 addCorsMappings 方法進(jìn)行全局 CORS 配置:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
}
這里配置了對(duì)所有請(qǐng)求路徑(/**)的跨域支持,允許來(lái)自 http://localhost:3000 的請(qǐng)求,支持 GET、POST、PUT、DELETE 等請(qǐng)求方法,允許所有請(qǐng)求頭,并且允許攜帶憑證(如 Cookie)。
通過(guò)以上對(duì) SpringMVC 開(kāi)發(fā)中十大常見(jiàn)問(wèn)題的詳細(xì)解析和解決方案介紹,希望能幫助你在實(shí)際開(kāi)發(fā)中順利避開(kāi)這些“坑”。
以上就是SpringMVC開(kāi)發(fā)中十大常見(jiàn)問(wèn)題深度解析與解決方案的詳細(xì)內(nèi)容,更多關(guān)于SpringMVC開(kāi)發(fā)常見(jiàn)問(wèn)題的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何通過(guò)Java生成一個(gè)隨機(jī)數(shù)
當(dāng)我們需要在Java中生成隨機(jī)數(shù)時(shí),可以借助JDK中提供的Random類來(lái)實(shí)現(xiàn),通過(guò)使用Random類,我們可以輕松地生成各種類型的隨機(jī)數(shù),下面我們就來(lái)看看如何利用Random類生成隨機(jī)數(shù)吧2023-09-09
java中for循環(huán)執(zhí)行的順序圖文詳析
關(guān)于java的for循環(huán)想必大家非常熟悉,它是java常用的語(yǔ)句之一,這篇文章主要給大家介紹了關(guān)于java中for循環(huán)執(zhí)行順序的相關(guān)資料,需要的朋友可以參考下2021-06-06
Java實(shí)現(xiàn)數(shù)據(jù)連接池Druid舉例
本文主要介紹了Java實(shí)現(xiàn)數(shù)據(jù)連接池Druid舉例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
如何通過(guò)Java代碼實(shí)現(xiàn)KMP算法
這篇文章主要介紹了如何通過(guò)Java代碼實(shí)現(xiàn)KMP算法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
解決springboot無(wú)法注入JpaRepository的問(wèn)題
這篇文章主要介紹了解決springboot無(wú)法注入JpaRepository的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01

