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í),使用自定義異??梢愿逦靥幚聿煌?lèi)型的錯(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),在用戶(hù)下單時(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)確判斷異常情況。
解決方案
- 定義自定義異常類(lèi),合理繼承
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ī)制先攔截了異常。
解決方案
- 確保異常處理器所在的類(lèi)被
@RestControllerAdvice
或@ControllerAdvice
注解標(biāo)注,并且所在的包被Spring容器掃描。例如,在Spring Boot項(xiàng)目的啟動(dòng)類(lèi)上添加@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
接口,重寫(xiě)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í)間類(lèi)型參數(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í)體類(lèi)中,但在轉(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í)間類(lèi)型的轉(zhuǎn)換規(guī)則與GET請(qǐng)求不同,需要額外配置。
解決方案
- 在實(shí)體類(lèi)的日期時(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); } } }
然后在配置類(lèi)中注冊(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容器管理的類(lèi)中設(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
接口,重寫(xiě)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容器管理的類(lèi)和方法中,并且方法不是靜態(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ù)正確寫(xiě)入響應(yīng)體。
- 存在其他代碼在處理請(qǐng)求或響應(yīng)過(guò)程中,意外關(guān)閉了輸入流或響應(yīng)流。
解決方案
- 自定義一個(gè)可以重復(fù)讀取的
HttpServletRequest
包裝類(lèi)。例如:
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)來(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ù)寫(xiě)入響應(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ò)?是類(lèi)型轉(zhuǎn)換規(guī)則沒(méi)吃透嗎?
在SpringMVC中進(jìn)行參數(shù)綁定時(shí),經(jīng)常會(huì)出現(xiàn)參數(shù)類(lèi)型轉(zhuǎn)換錯(cuò)誤的問(wèn)題,導(dǎo)致請(qǐng)求無(wú)法正確處理。
問(wèn)題場(chǎng)景
前端傳遞一個(gè)字符串類(lèi)型的參數(shù),后端控制器方法期望接收一個(gè)整數(shù)類(lèi)型的參數(shù),但在綁定過(guò)程中出現(xiàn)Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'
錯(cuò)誤。
原因分析
- 前端傳遞的參數(shù)類(lèi)型與后端控制器方法參數(shù)類(lèi)型不匹配,并且SpringMVC無(wú)法自動(dòng)進(jìn)行正確的類(lèi)型轉(zhuǎn)換。
- 自定義的類(lèi)型轉(zhuǎn)換規(guī)則沒(méi)有生效,或者類(lèi)型轉(zhuǎn)換規(guī)則定義錯(cuò)誤。
- 參數(shù)名稱(chēng)不一致,導(dǎo)致SpringMVC無(wú)法正確匹配參數(shù)。
解決方案
- 確保前端傳遞的參數(shù)類(lèi)型與后端控制器方法參數(shù)類(lèi)型兼容,并且SpringMVC支持自動(dòng)類(lèi)型轉(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 "用戶(hù)年齡為:" + age; }
- 對(duì)于復(fù)雜的類(lèi)型轉(zhuǎn)換,可以自定義類(lèi)型轉(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; } }
然后在配置類(lèi)中注冊(cè)這個(gè)轉(zhuǎn)換器:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private UserConverter userConverter; @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(userConverter); } }
- 檢查參數(shù)名稱(chēng)是否一致,確保前端傳遞的參數(shù)名與后端控制器方法中
@RequestParam
或@RequestBody
注解指定的參數(shù)名相同。
七、表單提交亂碼?編碼配置環(huán)節(jié)是否疏忽了?
在處理表單提交時(shí),有時(shí)會(huì)出現(xiàn)提交的數(shù)據(jù)在后端顯示為亂碼的情況。
問(wèn)題場(chǎng)景
用戶(hù)在前端表單中輸入中文內(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è)攔截器用于攔截所有的用戶(hù)請(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. 定義攔截器類(lèi),實(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. 在配置類(lèi)中注冊(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 } }
并在配置類(lèi)中注冊(cè)該攔截器,這樣就能按順序執(zhí)行攔截邏輯。
九、視圖解析失?。磕0逡媾渲贸鰡?wèn)題了嗎?
在使用模板引擎(如 Thymeleaf、Freemarker)時(shí),常常會(huì)遇到視圖解析失敗的情況,頁(yè)面無(wú)法正確渲染。
問(wèn)題場(chǎng)景
在 SpringMVC 項(xiàng)目中集成了 Thymeleaf 模板引擎,控制器方法返回視圖名稱(chēng)后,頁(yè)面顯示 Whitelabel Error Page
,提示找不到對(duì)應(yīng)的視圖。
原因分析
- 模板引擎的依賴(lài)沒(méi)有正確引入,或者版本不兼容。
- 模板引擎的配置不正確,如視圖前綴、后綴設(shè)置錯(cuò)誤,導(dǎo)致無(wú)法找到對(duì)應(yīng)的模板文件。
- 模板文件的存放位置不符合配置要求,或者文件名拼寫(xiě)錯(cuò)誤。
解決方案
以 Thymeleaf 為例:
1. 確保在 pom.xml
文件中正確引入 Thymeleaf 依賴(lài):
<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. 確保模板文件存放在正確的目錄下,并且文件名與控制器返回的視圖名稱(chēng)一致。例如,控制器方法:
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { @GetMapping("/") public String index() { return "index"; // 返回視圖名稱(chēng)為 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
注解
在控制器類(lèi)或方法上添加 @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ò)配置類(lèi)實(shí)現(xiàn) WebMvcConfigurer
接口,重寫(xiě) 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)來(lái)實(shí)現(xiàn),通過(guò)使用Random類(lèi),我們可以輕松地生成各種類(lèi)型的隨機(jī)數(shù),下面我們就來(lái)看看如何利用Random類(lèi)生成隨機(jī)數(shù)吧2023-09-09java中for循環(huán)執(zhí)行的順序圖文詳析
關(guān)于java的for循環(huán)想必大家非常熟悉,它是java常用的語(yǔ)句之一,這篇文章主要給大家介紹了關(guān)于java中for循環(huán)執(zhí)行順序的相關(guān)資料,需要的朋友可以參考下2021-06-06Java實(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