我们来详细分析一下视图解析器 (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 视图解析器。
java
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配置
@Bean
public 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
。
java
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;
}
@Bean
public 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;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true); // 推荐开启SpringEL编译器
// 可以添加额外的 Dialect,例如 SpringSecurityDialect
return templateEngine;
}
@Bean
public ViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(0); // 优先级高
return resolver;
}
}
c. FreeMarkerViewResolver
(用于 FreeMarker 模板引擎)
需要配置 FreeMarkerConfigurer
。
java
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 {
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/templates/freemarker/"); // FreeMarker模板路径
configurer.setDefaultEncoding("UTF-8");
return configurer;
}
@Bean
public 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
) 来渲染响应。
java
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 imports
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableWebMvc
public class ContentNegotiationMvcConfig implements WebMvcConfigurer {
@Override
public 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);
}
@Bean
public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
List<ViewResolver> resolvers = new ArrayList<>();
// 添加你想要委托的 ViewResolver
resolvers.add(jsonViewResolver()); // 假设有一个处理 JSON 的 ViewResolver
resolvers.add(jspViewResolver()); // 上面定义的 JSP ViewResolver
// ... 其他
resolver.setViewResolvers(resolvers);
resolver.setOrder(0); // CNVR 通常优先级最高
return resolver;
}
// 示例:一个简单的 JSON ViewResolver (通常会使用 MappingJackson2JsonView)
// @Bean
// public ViewResolver jsonViewResolver() { ... }
@Bean
public 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
修改这些默认值:propertiesspring.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
修改:propertiesspring.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
修改:propertiesspring.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
被自动配置):propertiesspring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp
但通常,如果使用了像 Thymeleaf 这样的模板引擎,Spring Boot 会优先配置它们的 ViewResolver,而
InternalResourceViewResolver
的自动配置优先级较低。
-
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
属性:java@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 来覆盖自动配置。