springboot系列10,MVC的视图应用(下)

多视图处理器并存此处是指Thymeleaf视图处理器和jsp视图处理器并存,之前已经配置好了Thymeleaf,接下来把jsp引入进来,其对应的处理器为InternalResourceViewResolver加依赖 <dependency> <groupId>javax.servlet</groupId> ...
摘要由CSDN通过智能技术生成

多视图处理器并存

其实在之前测试的话可以发现,配置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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值