后端同学基本都会见过这种场景:在同一个工程中,有些页面使用jsp模版渲染,同时还有其他接口提供Json格式的返回值。为了同时支持这两种场景,我们一般是如何处理的呢?

其实非常简单:

1、在项目中为 SpringMvc 指定视图解析器 ViewResolver,并引入 jstl 和 apache-jsp 依赖,用于支持jsp页面的渲染。

2、在需要返回 Json 数据的方法上追加注解 @ResponseBody,并且配置对应的 Json 消息转换器。此时将不会使用指定的 ViewResolver 渲染页面,而是返回 Json 数据。

简单演示下:

1、配置Jsp视图解析器:

@Configuration
@AutoConfigureOrder
@AutoConfigureAfter({WebMvcAutoConfiguration.class})
public class SpringMvcConfig implements WebMvcConfigurer { /**
* jsp视图解析
*
* @return
*/
@Bean
public ViewResolver getViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
} /**
* json消息转换器
*
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); final MappingJackson2HttpMessageConverter jackson = new MappingJackson2HttpMessageConverter(objectMapper);
jackson.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8)); converters.add(0, new StringHttpMessageConverter(Charsets.UTF_8));
converters.add(1, jackson);
} ...
}

配置好jsp解析依赖的包:

        <!-- JSP视图解析 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- 必须引入该依赖,解析JSP -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jsp</artifactId>
<version>9.4.8.v20171121</version>
</dependency>

2、两种接口。一个返回Json数据,一个渲染Jsp页面:

@Controller
@Slf4j
public class MyController { /**
* 这个接口将会返回json数据
* 必须配置 @ResponseBody 注解
*/
@GetMapping("/toJson")
@ResponseBody
public Response toJson() {
Response response = new Response();
response.setCode(200);
response.setMsg("");
response.setData("Json数据");
return response;
} /**
* 这个接口将会渲染对应的jsp页面。
* 注:需要在WEB-INF/view目录下配置好对应的demojsp.jsp文件
*/
@GetMapping("/toJsp")
public String toJsp() {
return "demojsp";
}
} @Data
public class Response<T> implements Serializable { private int code; private String msg; private T data;
}

看起来非常简单,对不?

那么问题来了:为什么加上 @ResponseBody 这个注解后,就能返回 Json 数据,而不加的话就会渲染 Jsp页面?

从现象上来看,@ResponseBody 似乎把响应数据的渲染路径改变了,之前明明要渲染页面,现在硬生生改成了返回 Json 数据。

没错,就是这样。只要加了 @ResponseBody 注解,就会直接把接口返回的数据通过Json写到响应中,后续的视图解析器将不会被执行,也就不存在视图渲染一说了。

为了加深印象,我们看看源码是怎么实现的(我们聚焦这两个处理器相关的代码,不再阐述SpringMvc处理的主线)。

Spring 容器初始化时,会自动添加 RequestResponseBodyMethodProcessor 和 ViewNameMethodReturnValueHandler 这两个处理器,它们分别用于处理不同类型的响应数据。具体可以参见 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter类的getDefaultReturnValueHandlers方法,其中的关键代码如下(注意两个处理器的顺序,这个很关键):

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(); ... handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice)); handlers.add(new ViewNameMethodReturnValueHandler()); ... return handlers;
}

其中,RequestResponseBodyMethodProcessor 用于处理方法带有 @ResponseBody 的处理器,而 ViewNameMethodReturnValueHandler 用于预处理带有名称的页面渲染逻辑。它们都实现了HandlerMethodReturnValueHandler 这个接口的 supportsReturnType 和 handleReturnValue 方法:

    // RequestResponseBodyMethodProcessor

    @Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
} @Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { // 注意这行代码,setRequestHandled为true表示当前请求已经处理完毕,不需要后续的渲染处理了。
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // 这里会直接把响应数据写到输出流
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
    // ViewNameMethodReturnValueHandler

    @Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
} @Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else if (returnValue != null) {
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}

如果 supportsReturnType 方法返回 true,接口返回的 Response 就会由该处理器的 handleReturnValue进行处理或者初步处理。

细心的读者会发现,前面我们提到 ViewNameMethodReturnValueHandler 用于预处理带有名称的页面渲染逻辑。这里的“预处理”是指这个处理器只是设置了视图的名称等属性,具体的渲染还要交由 RequestMappingHandlerAdapter 中的后续逻辑进行处理。源码参见 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter 类的getModelAndView 方法:

    @Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer);
// 如果当前请求已经处理完毕,就不需要再渲染视图了
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}

从上面的流程可以看出,加了 @ResponseBody 注解后,RequestResponseBodyMethodProcessor处理器会得到执行,它会提前将响应数据写入输出流,并且标记请求已经被处理完成,从而阻止了后续的视图渲染流程。

思考题:如果接口 /toJson 对应的方法忘记使用注解,此时会发生什么?

提示:会根据返回值的类型落到对应的处理器中,对于我们的例子来说,会由 ModelAttributeMethodProcessor处理器执行:寻找 WEB-INF/view/toJson.jsp 页面尝试渲染,若找不到则重定向请求到 /error,进行后续的错误处理。

建议大家顺着源码调试一遍(包括将响应数据处理为 Json 的流程),以后遇到 @ResponseBody 注解后,能顺其自然地回想起相关的执行流程,跳出“它是用来将响应数据写入输出流”这样较为粗浅的认知。

最后,本文对应的完整演示项目已经上传到 Github

SpringMvc 如何同时支持 Jsp 和 Json 接口?的更多相关文章

  1. springmvc 支持对象与json 自动转换的配置

    基于maven的工程, 需要在pom.xml中添加如下依赖 <dependency> <groupId>javax.servlet</groupId> <ar ...

  2. java:Springmvc框架2(Ajax,Json,Interceptor,Upload,Exception)

    1.springmvcAjax: springmvc.xml: <?xml version="1.0" encoding="UTF-8"?> < ...

  3. Loadrunner模拟JSON接口请求进行测试

    Loadrunner模拟JSON接口请求进行测试     一.loadrunner脚本创建 1.Insert - New step -选择Custom Request -  web_custom_re ...

  4. 纯CSS菜单样式,及其Shadow DOM,Json接口 实现

    先声明,要看懂这篇博客要求你具备少量基础CSS知识, 当然如果你只是要用的话就随便了,不用了解任何知识 完整项目github链接:https://github.com/git-Code-Shelf/M ...

  5. SpringBoot(二)-- 支持JSP

    SpringBoot虽然支持JSP,但是官方不推荐使用.看网上说,毕竟JSP是淘汰的技术了,泪奔,刚接触 就淘汰.. SpringBoot集成JSP的方法: 1.配置application.prope ...

  6. 【web】支持jsp+mvc访问

    直接使用SpringMVC时配置访问jsp页面时很容易的事,但是由于spring Boot使用内嵌的servlet容器,所以对jsp的支持不是很好,而且也不建议使用jsp,但是为了满足这种返回jsp页 ...

  7. RestKit ,一个用于更好支持RESTful风格服务器接口的iOS库

    简介 RestKit 是一个用于更好支持RESTful风格服务器接口的iOS库,可直接将联网获取的json/xml数据转换为iOS对象. 项目主页: RestKit 最新示例: 点击下载 注意: 如果 ...

  8. SpringBoot-(2)-Web的json接口,静态网页,动态页面

    一, 了解注解@Controller和@RestController @Controller:处理Http请求 @RestController:Spring4以后新增注解,相当于@Controller ...

  9. [淘宝客技术篇008](无需登录)淘宝天猫优惠券JSON接口1

    今天,小星给大家分享的是一个非常重要,非常有意义的接口:获取淘宝天猫优惠券的JSON接口. 先上个链接: http://uland.taobao.com/cp/coupon_list?pid=mm_2 ...

随机推荐

  1. flask实现python方法转换服务

    一.flask安装 pip install flask 二.flask简介: flask是一个web框架,可以通过提供的装饰器@server.route()将普通函数转换为服务 flask是一个web ...

  2. 腾讯QQ快捷登陆

    腾讯QQ快捷 相关各语言对接qq快捷登录教程 [C#]QQ开放平台(QQ站外登录)_流程和源码示例 j2ee中实现QQ第三方登陆 web实现QQ第三方登录 asp.net网站接入QQ登录 php实现q ...

  3. Spring jdbctemplate和事务管理器 全注解配置 不使用xml

    /** * spring的配置类,相当于bean.xml */@Configuration//@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans ...

  4. FS2K人脸素描属性识别

    人脸素描属性识别 代码:https://github.com/linkcao/FS2K_extract 问题分析 需要根据FS2K数据集进行训练和测试,实现输入一张图片,输出该图片的属性特征信息,提取 ...

  5. 修改windows字符集

    手动 临时修改cmd默认字符集(代码页) chcp xxxx 自动<打开cmd后应该自动运行dhcp 65001,临时设置为utf-8> D:\Develope\apache-tomcat ...

  6. maven编译 出现Process terminated

    问题: 解决方案: 在Settings中配置一下maven

  7. 女朋友说:你要搞懂了MySQL三大日志,我就让你嘿嘿嘿!

    1. 背景 MySQL实现事务.崩溃恢复.集群的主从复制,底层都离不开日志,所以日志是MySQL的精华所在.只有了解MySQL日志,才算是彻底搞懂MySQL. 今天一灯就带你深入浅出的学习MySQL的 ...

  8. 扩展新的WCV到标准的WC后,不能在业务角色里面看见视图解决方法

    by zyi 感谢群主红枣的分享 1.把你的WCVIEW扩展进WC中 2.使用UI Designer打开你的WCVIEW 3.更改你的WCVIEW名字

  9. NC20032 [HNOI2003]激光炸弹

    NC20032 [HNOI2003]激光炸弹 题目 题目描述 一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标. 现在地图上有 \(n\) (\(N ≤ 10000\))个目标,用整数 ...

  10. Spring XmlBeanFactory 容器的基本实现

    容器的基本用法 熟悉 Spring 的朋友应该都很了解下段代码: public void testBeanFactory() { BeanFactory bf = new XmlBeanFactory ...