多视图处理器并存
其实在之前测试的话可以发现,配置Thymeleaf后,jsp是访问不了的,配置好jsp以后,Thymeleaf又不能访问,所以这里的多视图处理器并存就是解决这个问题的。此处是指Thymeleaf视图处理器和jsp视图处理器并存,之前已经配置好了Thymeleaf,接下来把jsp引入进来,其对应的处理器为InternalResourceViewResolver
加依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- jsp的配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
controller
@RequestMapping("")
public String index(@RequestParam(required = false , defaultValue = "0") int value) {
return "index";
}
ControllerAdvice
/**
* {@link HelloWorldController} 通知
*/
@ControllerAdvice(assignableTypes = {HelloWorldController.class})
public class HelloWorldControllerAdvice {
@ExceptionHandler(Throwable.class)
public ResponseEntity<String> onException(Throwable throwable){
return ResponseEntity.ok(throwable.getMessage());
}
@ModelAttribute("acceptLanguage")
public String acceptLanguage(@RequestHeader("Accept-Language") String acceptLanguage){
return acceptLanguage;
}
@ModelAttribute("jsessionId")
public String jsessionId(@CookieValue(value = "JSESSIONID" , required = false) String jsessionId){
return jsessionId;
}
@ModelAttribute("message")
public String message(){
return "Hello World";
}
}
MvcConfig
/**
* Spring Mvc配置类
*/
@Configuration
//@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setViewClass(JstlView.class);
internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截中。。。");
return true;
}
});
}
}
但运行后发现报错
原因:
在源码DispatcherServlet的视图处理器的方法那打个断点
追踪发现resolve列表中,Thymeleaf的比jsp的更先被加载
这是因为各自的order优先级不同,通过idea搜索ThymeleafViewResolver可以找到ThymeleafAutoConfiguration,其中有一段代码,优先级是倒数第六:
再看jsp的,通过InternalResourceViewResolver找到setorder,进一步看可以看出,优先级是最后:
所以报错的原因是先选择了Thymeleaf,所以jsp加载不出来,解决的方法将优先级提前就好,在mvcConfig里:
视图内容协商
视图内容协商的意思就是系统如何定位的问题,比如用jsp渲染xml,用Thymeleaf渲染html,这两种媒体类型的协商。
需求背景:Web客户端根据通过不同的请求策略,实现服务端响应对应视图的内容输出。
核心组件:
- 视图解析器ContentNegotiatingViewResolver
关联ViewResolver Bean列表
关联ContentNegotiationManager Bean
解析出最佳匹配View
- 内容协商管理器ContentNegotiationManager
由ContentNegotiationConfigurer配置
由ContentNegotiationManagerFactoryBean创建
关联ContentNegotiationStrategy集合
- 内容协商策略ContentNegotiationStrategy
Accept请求头:如Accept:text/*
请求查询参数:如/path?format=pdf
路径扩展名:如/abc.pdf
流程图:
1.ContentNegotiationConfigurer源码中,有个build,其中factory就是ContentNegotiationManagerFactoryBean
2.配置策略需要实现WebMvcConfigurer接口,然后设置策略,策略有很多,这里只举例两个
ContentNegotiationManagerFactoryBean中有afterPropertiesSet方法,从方法名就可以知道是配置以后调用
里面是个build方法
/**
* Actually build the {@link ContentNegotiationManager}.
* @since 5.0
*/
public ContentNegotiationManager build() {
List<ContentNegotiationStrategy> strategies = new ArrayList<>();
if (this.strategies != null) {
strategies.addAll(this.strategies);
}
else {
if (this.favorPathExtension) {
PathExtensionContentNegotiationStrategy strategy;
if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
}
else {
strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
}
strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
if (this.useRegisteredExtensionsOnly != null) {
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
}
strategies.add(strategy);
}
if (this.favorParameter) {
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
strategy.setParameterName(this.parameterName);
if (this.useRegisteredExtensionsOnly != null) {
strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
}
else {
strategy.setUseRegisteredExtensionsOnly(true); // backwards compatibility
}
strategies.add(strategy);
}
if (!this.ignoreAcceptHeader) {
strategies.add(new HeaderContentNegotiationStrategy());
}
if (this.defaultNegotiationStrategy != null) {
strategies.add(this.defaultNegotiationStrategy);
}
}
this.contentNegotiationManager = new ContentNegotiationManager(strategies);
return this.contentNegotiationManager;
}
看源码就知道,这里面会根据2设置的参数来选择不同的策略。
3.4.通过上面代码最后,也可以看出创建了一个含有策略的ContentNegotiationManager
顺便说一下,ContentNegotiationManagerFactoryBean中,有个getObject方法
5.看ContentNegotiatingViewResolver
当没有ContentNegotiationManager时创建
通过搜索可以找到 WebMvcAutoConfiguration中有关于ContentNegotiationManager的写的方法
这里就会把工厂类里面的ContentNegotiationManager设置进去。
6.ContentNegotiatingViewResolver类中有个initServletContext方法
会把除自己以外的ViewResolver加进去,因为ContentNegotiatingViewResolver由于优先级最高,所以系统启动时可以最先运行。
至此,初始化阶段完成。
7.请求到DispatcherServlet中
8.DispatcherServlet会根据请求进行解析,并发给ViewResolver
源码
其中ContentNegotiatingViewResolver包含了其他5个 ViewResolver
6.9.debug往下走,会根据条件筛选出三个适配的ViewResolver关联的view,这里为什么是三个?往上看第2步,WebMvcConfigurer配制的时候,配置了两个策略,再加上自带的jsp的策略,所以有三个,而这三个的优先级是自定义->Thymeleaf->jsp
10.根据系统算法来匹配出最佳view并返回。
这里说一下匹配规则
这是 ContentNegotiatingViewResolver的方法,其中第一个框会找到客户端支持的媒体类型,就是head中的Accept
接下来第二个框,根据语言和之前设置的策略,筛选出可用的view
在接下来进行选择,通过代码可看出,根据媒体类型和view一一匹配,第一个匹配的就会返回。
这里自定义jsp的html和客户端的匹配,所以直接就用了jsp的view。
但这是还有个问题,如果访问Thymeleaf的,同样是可以这样匹配的,那么就没法用html了,在WebMvcConfigurer中添加一个代码,将自定义的设置成xml再进行测试
这里就会匹配成Thymeleaf了,但这样一来jsp又不能用了。
所以现在有两种解决办法,我们之前在添加策略的时候添加了两种,一种是请求头,另一种是请求参数
首先说请求参数,也就是第二个,里面有个关键词是format,通过ParameterContentNegotiationStrategy源码可以看出,这个formate就是请求参数,源码在这不贴了。
那在请求中加http://localhost:8080/?format=xml,再看
需要注意的是,这时,ContentNegotiatingViewResolver无法匹配上,需要DispatcherServlet中的其他ViewResolver自己的resolveViewName进行匹配了。
通过这种方式,就可以将jsp和Thymeleaf区分开了。
还有一种就是通过修改请求头的方式,这种方式更加简单粗暴
当然这两种方法的一个前提,就是需要设置 WebMvcConfigurer实现类的接受类型,setContentType,否则自定义的jsp还是会匹配xml,这里是需要非常注意的。
视图组件自动装配
Web MVC:WebMVCAutoConfiguration
Thymeleaf Web MVC:ThymeleafWebMVCConfiguration
Thymeleaf Web Flux:ThymeleafWebFluxConfiguration
内容协商:ContentNegotiationManager
外部化配置:
WebMvcProperties
WebMvcProperties.Contentnegotiation
WebMvcProperties.View
Thymeleaf:ThymeleafProperties