如何让 Spring MVC Controller 的同一个 URL 请求,根据逻辑判断返回 JSON 或者 HTML 视图?

时间:2023-02-04 21:56:07

如何让 Spring MVC Controller 的同一个 URL 请求,根据逻辑判断返回 JSON 或者 HTML 视图?


        需求
        有这样一个需求,用户注册时,对于浏览器终端和非浏览器终端发起的 HTTP 请求,都走同一个 URL:/user/register。如果是基于浏览器的终端,返回一个 HTML 视图;如果请求来自于非浏览器终端,返回一个 JSON 串。
        分析

        Spring MVC 返回 HTML 视图,Controller 一般需要返回一个 String 对象,形如以下代码:

//发送密码重置邮件user/sendPasswordEmail
@RequestMapping("/user/sendPasswordEmail")
public String sendPasswordEmail(HttpServletRequest request, @RequestParam("email") String email) throws Exception {
userService.forgetPassword(email, request);
return "user/passwordSent";
}

        Spring MVC 会根据 "user/passwordSent" 找到应该返回的 jsp 页面。

        或者一个 ModelAndView 对象:

//转发至resetPassword.jsp(后一个控制器可共享前一个控制器的参数与属性)
@RequestMapping("/user/resetPasswordInput")
public ModelAndView resetPasswordInput() {
return new ModelAndView("forward:/WEB-INF/jsp/user/resetPassword.jsp");
}

        Spring MVC 同样会根据 "forward:/WEB-INF/jsp/user/resetPassword.jsp" 找到应该返回的 jsp 页面。

        Spring MVC 返回 JSON,Controller 一般需要返回一个 Object 对象,或者 JSONObject,并非 ModelAndView 对象。形如以下代码:

/** 
* 测试返回JSON数据
* @param session
* @return
*/
@RequestMapping(value="/test")
@ResponseBody
public Object test(HttpSession session){

System.out.println("test....................");
return session.getAttribute("permit");
}


        返回值类型不一样。就算业务逻辑可以判断出请求来自浏览器终端或者其他非浏览器终端,又如何返回不同类型的对象呢?
        解决方案
        对于非浏览器终端,使用 MappingJacksonHttpMessageConverter 将 Model 转为 JSON,然后写入 HttpServletResponse 后返回。参考: http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/http/converter/AbstractHttpMessageConverter.html#write(T, org.springframework.http.MediaType, org.springframework.http.HttpOutputMessage)。这样子,对于非浏览器终端的 HTTP 请求,也可以接受一个 ModelAndView 对象的返回值了。浏览器终端和非浏览器终端共用一个 URL 请求。
        源码示例

        自定义 JsonView 类源码:

package com.defonds.oauth.common.util;

import java.io.IOException;

import javax.servlet.http.HttpServletResponse;

import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.servlet.ModelAndView;

public class JsonView {
public static ModelAndView Render(Object model, HttpServletResponse response)
{
MappingJacksonHttpMessageConverter jsonConverter = new MappingJacksonHttpMessageConverter();

MediaType jsonMimeType = MediaType.APPLICATION_JSON;


try {
jsonConverter.write(model, jsonMimeType, new ServletServerHttpResponse(response));
} catch (HttpMessageNotWritableException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

return null;
}
}


        浏览器终端和非浏览器终端注册共用一个 URL 的 Controller 源码:

package com.defonds.oauth.user.mvc;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.defonds.oauth.GlobalConstant;
import com.defonds.oauth.common.exception.UserOAuthException;
import com.defonds.oauth.common.util.JsonView;
import com.defonds.oauth.user.response.UserResponse;
import com.defonds.oauth.user.service.UserService;

@Controller
public class UserController {
private static final Log log = LogFactory.getLog(UserController.class);
private UserService userService;

/**
* 用户注册接口(浏览器终端、手机终端共同走此接口)
* 判断依据:浏览器接口传递过来一 hidden 变量 browser,其值为 true;否则判定为其他终端
* @param username 用户名【必须】
* @param password 密码 【必须】
* @param email 邮箱地址【必须】
* @param browser 浏览器【可选】,值为 true 者为浏览器
* @return ModelAndView:浏览器终端返回 jsp 页面;其他终端返回 JSON 串
* 备注:Spring 注解绑定时 @RequestParam("email") String email,@RequestParam 的 required 参数默认为 true
* 如果手机终端用户 HTTP 请求没有 email 参数,将产生异常
* 所以将 email 等传入参数是否包含的验证交由服务器处理,为空返回相应异常的 JSON 串
*/
@RequestMapping("/user/register")
public ModelAndView doRegister(HttpServletResponse response,
@RequestParam(value = "username", required = false) String username,
@RequestParam(value = "password", required = false) String password,
@RequestParam(value = "email", required = false) String email,
@RequestParam(value = "browser", required = false) String browser) {
if (browser != null && browser.equals("true")) {//有值且为 true,是浏览器终端发起的请求
try {
userService.addOauthUser(username, password, email);//处理注册相关业务逻辑
return new ModelAndView("user/registerSuccess");//返回到注册成功页面
} catch (UserOAuthException uoae) {//自定义业务逻辑异常
log.debug(uoae.getMessage());
ModelAndView mav = new ModelAndView("user/registerFailed");
mav.addObject("userOAuthException", uoae);
return mav;//返回注册失败页面
} catch (Exception e) {//系统异常
log.error(e.getMessage(), e);
UserOAuthException userOAuthException = new UserOAuthException(
UserOAuthException.ERROR_11001,
UserOAuthException.ERROR_CODE_11001,
UserOAuthException.ERROR_DESC_11001,
e.getMessage());
ModelAndView mav = new ModelAndView("user/registerFailed");
mav.addObject("userOAuthException", userOAuthException);
return mav;//返回注册失败页面
}
} else {//无值或不为 true,是浏览器之外的终端发起的请求
try {
userService.addOauthUser(username, password, email);//处理注册相关业务逻辑
return JsonView.Render(new UserResponse(
GlobalConstant.SUCCESS_CODE_11000,
username,
email), response);//其他终端,返回注册成功的 JSON 串
} catch (UserOAuthException uoae) {//自定义业务逻辑异常
log.debug(uoae.getMessage());
return JsonView.Render(uoae, response);//其他终端,返回注册失败的 JSON 串
} catch (Exception e) {//系统异常
log.error(e.getMessage(), e);
return JsonView.Render(new UserOAuthException(
UserOAuthException.ERROR_11001,
UserOAuthException.ERROR_CODE_11001,
UserOAuthException.ERROR_DESC_11001,
e.getMessage()), response);
}
}
}

public UserService getUserService() {
return userService;
}

public void setUserService(UserService userService) {
this.userService = userService;
}
}


        参考

http://*.com/questions/4917329/return-json-or-view-from-spring-mvc-controller

http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.html