我們來詳細分析一下視圖解析器 (ViewResolver) 的配置以及 Spring Boot 是如何自動配置它們的。
視圖解析器 (ViewResolver) 是什么?
在 Spring MVC 中,當控制器 (Controller) 方法處理完請求并返回一個邏輯視圖名 (String) 時,DispatcherServlet
會使用注冊的 ViewResolver
來將這個邏輯視圖名解析為一個實際的 View
對象。這個 View
對象負責渲染最終的響應(例如,生成 HTML)。
1. 如何手動配置視圖解析器?
可以手動配置 ViewResolver
,在 Spring 的配置類(使用 @Configuration
注解)中完成。
核心接口是 org.springframework.web.servlet.ViewResolver
。
以下是一些常見的視圖解析器及其手動配置示例:
a. InternalResourceViewResolver
(通常用于 JSP)
這是最常用的 JSP 視圖解析器。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView; // 如果使用 JSTL@Configuration
@EnableWebMvc // 如果不是 Spring Boot,通常需要這個
public class MvcConfig implements WebMvcConfigurer { // 實現 WebMvcConfigurer 以便自定義MVC配置@Beanpublic ViewResolver jspViewResolver() {InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setViewClass(JstlView.class); // 如果使用JSTL標簽庫resolver.setPrefix("/WEB-INF/jsp/"); // 視圖文件的前綴路徑resolver.setSuffix(".jsp"); // 視圖文件的后綴resolver.setOrder(1); // 如果有多個解析器,設置順序return resolver;}// 可以配置其他 ViewResolver// @Bean// public ViewResolver thymeleafViewResolver() { ... }
}
prefix
: 視圖文件在 Web 應用中的路徑前綴。suffix
: 視圖文件的擴展名。viewClass
: 指定要使用的視圖類,例如JstlView
(用于JSP + JSTL)。order
: 如果有多個視圖解析器,order
屬性決定了它們的查找順序,值越小優先級越高。
b. ThymeleafViewResolver
(用于 Thymeleaf 模板引擎)
需要先配置 SpringTemplateEngine
和 TemplateResolver
。
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.spring6.SpringTemplateEngine; // Spring 6, Spring 5 用 spring5
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;@Configuration
@EnableWebMvc
public class ThymeleafConfig implements WebMvcConfigurer {private ApplicationContext applicationContext;public ThymeleafConfig(ApplicationContext applicationContext) {this.applicationContext = applicationContext;}@Beanpublic SpringResourceTemplateResolver templateResolver() {SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();templateResolver.setApplicationContext(this.applicationContext);templateResolver.setPrefix("classpath:/templates/"); // Thymeleaf 模板通常放在 classpath 下templateResolver.setSuffix(".html");templateResolver.setTemplateMode(TemplateMode.HTML);templateResolver.setCharacterEncoding("UTF-8");templateResolver.setCacheable(false); // 開發時關閉緩存return templateResolver;}@Beanpublic SpringTemplateEngine templateEngine() {SpringTemplateEngine templateEngine = new SpringTemplateEngine();templateEngine.setTemplateResolver(templateResolver());templateEngine.setEnableSpringELCompiler(true); // 推薦開啟SpringEL編譯器// 可以添加額外的 Dialect,例如 SpringSecurityDialectreturn templateEngine;}@Beanpublic ViewResolver thymeleafViewResolver() {ThymeleafViewResolver resolver = new ThymeleafViewResolver();resolver.setTemplateEngine(templateEngine());resolver.setCharacterEncoding("UTF-8");resolver.setOrder(0); // 優先級高return resolver;}
}
c. FreeMarkerViewResolver
(用于 FreeMarker 模板引擎)
需要配置 FreeMarkerConfigurer
。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;@Configuration
@EnableWebMvc
public class FreeMarkerConfig implements WebMvcConfigurer {@Beanpublic FreeMarkerConfigurer freeMarkerConfigurer() {FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();configurer.setTemplateLoaderPath("classpath:/templates/freemarker/"); // FreeMarker模板路徑configurer.setDefaultEncoding("UTF-8");return configurer;}@Beanpublic ViewResolver freeMarkerViewResolver() {FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();resolver.setCache(true); // 生產環境建議開啟緩存resolver.setPrefix(""); // 前綴通常在 FreeMarkerConfigurer 中設置resolver.setSuffix(".ftl");resolver.setContentType("text/html;charset=UTF-8");resolver.setOrder(0);return resolver;}
}
d. ContentNegotiatingViewResolver
這是一個特殊的視圖解析器,它本身不解析視圖,而是委托給一個或多個其他的視圖解析器。它會根據請求的媒體類型 (Media Type,例如通過 Accept
HTTP頭或URL后綴) 來選擇合適的 ViewResolver
(進而選擇合適的 View
) 來渲染響應。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
// ... 其他 ViewResolver importsimport java.util.ArrayList;
import java.util.List;@Configuration
@EnableWebMvc
public class ContentNegotiationMvcConfig implements WebMvcConfigurer {@Overridepublic void configureContentNegotiation(ContentNegotiationConfigurer configurer) {configurer.favorParameter(true) // 是否通過請求參數(默認為format)來確定媒體類型.parameterName("mediaType") // 請求參數名.ignoreAcceptHeader(false) // 是否忽略Accept請求頭.defaultContentType(MediaType.TEXT_HTML) // 默認媒體類型.mediaType("html", MediaType.TEXT_HTML).mediaType("json", MediaType.APPLICATION_JSON).mediaType("xml", MediaType.APPLICATION_XML);}@Beanpublic ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();resolver.setContentNegotiationManager(manager);List<ViewResolver> resolvers = new ArrayList<>();// 添加你想要委托的 ViewResolverresolvers.add(jsonViewResolver()); // 假設有一個處理 JSON 的 ViewResolverresolvers.add(jspViewResolver()); // 上面定義的 JSP ViewResolver// ... 其他resolver.setViewResolvers(resolvers);resolver.setOrder(0); // CNVR 通常優先級最高return resolver;}// 示例:一個簡單的 JSON ViewResolver (通常會使用 MappingJackson2JsonView)// @Bean// public ViewResolver jsonViewResolver() { ... }@Beanpublic ViewResolver jspViewResolver() {InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setPrefix("/WEB-INF/jsp/");resolver.setSuffix(".jsp");return resolver;}
}
2. Spring Boot 是如何自動配置常見視圖解析器的?
Spring Boot 的核心思想是“約定優于配置” (Convention over Configuration)。它通過自動配置 (Auto-configuration) 機制,根據我們項目中添加的依賴來自動配置應用程序的各個方面,包括視圖解析器。
自動配置的觸發條件:
- 類路徑檢測:Spring Boot 會檢查類路徑下是否存在特定的類。例如,如果檢測到 Thymeleaf 的相關類,它就會嘗試配置 Thymeleaf。
@ConditionalOnClass
/@ConditionalOnMissingBean
:自動配置類通常使用這些注解。@ConditionalOnClass
: 只有當指定的類存在于類路徑上時,配置才會生效。@ConditionalOnMissingBean
: 只有當用戶沒有自己定義同類型的 Bean 時,Spring Boot 的自動配置 Bean 才會生效。
常見視圖解析器的自動配置:
Spring Boot 為多種模板引擎提供了自動配置支持。這些配置通常在 spring-boot-autoconfigure.jar
中的 org.springframework.boot.autoconfigure.web.servlet
(針對Servlet Web) 或 org.springframework.boot.autoconfigure.web.reactive
(針對Reactive WebFlux) 包下。
a. Thymeleaf (spring-boot-starter-thymeleaf
)
- 依賴:添加
spring-boot-starter-thymeleaf
依賴時,相關的 Thymeleaf 類會被引入。 - 自動配置類:
ThymeleafAutoConfiguration
- 行為:
- 會自動配置
SpringResourceTemplateResolver
、SpringTemplateEngine
和ThymeleafViewResolver
。 - 默認模板位置:
classpath:/templates/
- 默認模板后綴:
.html
- 默認編碼:
UTF-8
- 可以通過
application.properties
或application.yml
修改這些默認值:spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.thymeleaf.mode=HTML spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.cache=true # 生產環境建議 true,開發環境 false
- 會自動配置
b. FreeMarker (spring-boot-starter-freemarker
)
- 依賴:添加
spring-boot-starter-freemarker
。 - 自動配置類:
FreeMarkerAutoConfiguration
- 行為:
- 會自動配置
FreeMarkerConfigurer
和FreeMarkerViewResolver
。 - 默認模板位置:
classpath:/templates/
(注意,FreeMarker 傳統上有一個自己的路徑,但 Spring Boot 會配置FreeMarkerConfigurer
的templateLoaderPath
為spring.freemarker.template-loader-path
的值,默認為classpath:/templates/
)。 - 默認模板后綴:
.ftlh
(FreeMarker Template Language HTML,也可以是.ftl
) - 可以通過
application.properties
或application.yml
修改:spring.freemarker.template-loader-path=classpath:/templates/freemarker/ spring.freemarker.suffix=.ftl spring.freemarker.charset=UTF-8 spring.freemarker.cache=true # 更多配置...
- 會自動配置
c. Groovy Templates (spring-boot-starter-groovy-templates
)
- 依賴:添加
spring-boot-starter-groovy-templates
。 - 自動配置類:
GroovyTemplateAutoConfiguration
- 行為:
- 會自動配置
GroovyMarkupConfigurer
和GroovyMarkupViewResolver
。 - 默認模板位置:
classpath:/templates/
- 默認模板后綴:
.tpl
- 可以通過
application.properties
或application.yml
修改:spring.groovy.template.prefix=classpath:/templates/ spring.groovy.template.suffix=.tpl # 更多配置...
- 會自動配置
d. JSP (特殊情況)
- 依賴:JSP 通常不需要特定的 Spring Boot starter 來“啟用”,而是依賴于 Servlet 容器(如 Tomcat, Jetty, Undertow)對 JSP 的支持。如果使用嵌入式容器,需要確保它支持 JSP。例如,對于 Tomcat,
tomcat-embed-jasper
是必需的,它通常由spring-boot-starter-web
間接引入。 - 自動配置類:
WebMvcAutoConfiguration
內部有一個內部類WebMvcAutoConfigurationAdapter
,它會嘗試配置InternalResourceViewResolver
。 - 行為:
- 如果 Spring Boot 檢測到
javax.servlet.jsp.JspPage
(或 Jakarta EE 9+ 的jakarta.servlet.jsp.JspPage
) 在類路徑上,并且沒有其他更專門的視圖解析器(如 Thymeleaf 的)被配置,它可能會配置一個InternalResourceViewResolver
。 - 重要:JSP 在使用 Spring Boot 的可執行 JAR (fat JAR) 方式打包和運行時存在一些限制,因為 JSP 需要編譯。它們更適合傳統的 WAR 包部署。
- 可以通過
application.properties
或application.yml
修改(如果InternalResourceViewResolver
被自動配置):
但通常,如果使用了像 Thymeleaf 這樣的模板引擎,Spring Boot 會優先配置它們的 ViewResolver,而spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp
InternalResourceViewResolver
的自動配置優先級較低。
- 如果 Spring Boot 檢測到
e. ContentNegotiatingViewResolver
(自動配置)
- Spring Boot 默認會自動配置
ContentNegotiatingViewResolver
(WebMvcAutoConfiguration
的一部分)。 - 它會智能的包裝所有其他已配置的(包括自動配置和用戶自定義的)
ViewResolver
。 - 它使得應用可以根據請求(如
Accept
頭)返回不同格式的響應(HTML, JSON, XML 等),而控制器代碼無需改變。
f. BeanNameViewResolver
(自動配置)
- Spring Boot 也會自動配置
BeanNameViewResolver
。 - 如果控制器返回的邏輯視圖名與 Spring 應用上下文中某個
View
bean 的名稱匹配,則會使用該View
bean。它的order
值較高(優先級較低),通常在InternalResourceViewResolver
之后。
g. WelcomePageHandlerMapping
和靜態資源
- 雖然不是嚴格的
ViewResolver
,但 Spring Boot 也會自動配置對index.html
作為歡迎頁面的支持 (WelcomePageHandlerMapping
) 以及從classpath:/static/
,classpath:/public/
,classpath:/resources/
,classpath:/META-INF/resources/
等位置提供靜態資源的服務。
如何覆蓋或禁用自動配置?
- 提供自己的 Bean:如果定義了與自動配置相同類型的 Bean (例如,你自己定義了一個
ThymeleafViewResolver
Bean),那么 Spring Boot 的自動配置版本將不會生效 (因為@ConditionalOnMissingBean
)。 - 使用
application.properties
:對于大多數自動配置的屬性,我們可以通過application.properties
或application.yml
來覆蓋默認值。 - 排除自動配置類:如果想完全禁用某個自動配置,可以使用
@SpringBootApplication
(或@EnableAutoConfiguration
) 的exclude
屬性:@SpringBootApplication(exclude = ThymeleafAutoConfiguration.class) public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);} }
總結:
- 手動配置:給予完全的控制權,適用于非 Spring Boot 項目或需要深度定制的場景。
- Spring Boot 自動配置:極大的簡化了常見視圖技術的集成。通過添加相應的 starter 依賴,Spring Boot 會根據約定自動配置好視圖解析器,并允許通過屬性文件進行簡單的定制。如果需要更復雜的定制,仍然可以提供自己的 Bean 來覆蓋自動配置。