Springboot返回json和xml视图

建设目标

  1. 请求接口通过请求参数中设置的output参数=xml/json来返回对应的视图,默认使用json;
  2. 发生异常时,@ExceptionHandler能自动返回对应视图的异常信息

解决思路

由于spring默认xml的视图优先级高于json视图(视图顺序),需要将json视图的优先级调高。

controller方法中不指定返回视图类型,创建一个视图拦截器,根据output参数来调整视图MediaType顺序。

方法如下:

返回xml视图(默认,不需要设置):

1
2
3
4
5
Set<MediaType> mediaType = new HashSet<MediaType>();

mediaType.add(MediaType.APPLICATION_XML);

request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaType)

返回json视图:

1
2
3
4
5
Set<MediaType> mediaType = new HashSet<MediaType>();

mediaType.add(MediaType.APPLICATION_JSON);

request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaType)

由于在 @ExceptionHandler 中,httprequest中的视图顺序会被重置(具体原因未核实),所以需要在@ExceptionHandler中重新设置视图顺序。

如果需要在controller中指定返回类型视图,方法如下(与本文无关):

返回json视图:

1
@RequestMapping(value = "/api/test/json", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)

返回xml视图:

1
@RequestMapping(value = "/api/test/json", produces = MediaType.APPLICATION_XML_VALUE, method = RequestMethod.GET)

项目搭建

搭建一个简单的maven项目,引入spring boot的依赖,部分pom.xml配置如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<!-- spring boot 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<!-- xml和json视图序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-xml-provider</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>

编写实体类

编写UserBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class UserBean {
private String user;
private String name;
private String token;

public String getUser() {
return user;
}

public void setUser(String user) {
this.user = user;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}
}

编写ErrorBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ErrorBean {
private String errorCode;
private String errorInfo;

public String getErrorCode() {
return errorCode;
}

public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}

public String getErrorInfo() {
return errorInfo;
}

public void setErrorInfo(String errorInfo) {
this.errorInfo = errorInfo;
}

}

编写自定义异常类BaseException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class BaseException extends Exception {
private static final long serialVersionUID = 1L;
public static final String DEFAULT_CODE = "SR01";
public static final String PARAM_ERROR_CODE = "PV01";
private String errCode;
private String errMsg;
private int httpCode;

public int getHttpCode() {
return httpCode;
}

public void setHttpCode(int httpCode) {
this.httpCode = httpCode;
}

public BaseException(int httpCode, String errCode, String errMsg) {
this.httpCode = httpCode;
this.errCode = errCode;
this.errMsg = errMsg;
}

public String getErrCode() {
return errCode;
}

public String getErrMsg() {
return errMsg;
}

}

编写异常处理类ErrorHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class ErrorHandler {
public static final String OUTPUT_XML = "xml";
public static final String OUTPUT_JSON = "josn";
public static Set<MediaType> xmlMediaType = new HashSet<MediaType>();
public static Set<MediaType> jsonMediaType = new HashSet<MediaType>();
private static Logger logger = LoggerFactory.getLogger(ErrorHandler.class);

static {
xmlMediaType.add(MediaType.APPLICATION_XML) ;
jsonMediaType.add(MediaType.APPLICATION_JSON);
}

@ExceptionHandler
@ResponseBody
public ErrorBean exceptionHandle(Exception ex, HttpServletRequest request,
HttpServletResponse response) {
if (!ErrorHandler.OUTPUT_XML.equals(request.getParameter("output"))) {
request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE,
ErrorHandler.jsonMediaType);
logger.info("Exception 设置json视图 ...");
} else {
logger.info("Exception 设置xml视图 ...");
}
ErrorBean error = new ErrorBean();
if ("MissingServletRequestParameterException".equals(ex.getClass().getSimpleName())) {
ex = new BaseException(401, BaseException.PARAM_ERROR_CODE, "缺少必要的请求参数");
} else if ("MethodArgumentTypeMismatchException".equals(ex.getClass().getSimpleName())) {
ex = new BaseException(401, BaseException.PARAM_ERROR_CODE, "请求参数类型错误");
}
if (BaseException.class.isAssignableFrom(ex.getClass())) {
BaseException se = (BaseException) ex;
response.setStatus(se.getHttpCode());
error.setErrorCode(se.getErrCode());
error.setErrorInfo(se.getErrMsg());
} else {
response.setStatus(500);
error.setErrorCode(BaseException.DEFAULT_CODE);
error.setErrorInfo("服务繁忙");
}
return error;
}

}

编写controller类TestController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class TestController extends ErrorHandler {

@RequestMapping(value = "/test/api", method = RequestMethod.GET)
public Object testJsonView(@RequestParam(value = "token") String token) throws BaseException {
UserBean userBean = new UserBean();
userBean.setName("千山鸟飞绝");
userBean.setUser("万径人踪灭");
userBean.setToken(token);
if (System.currentTimeMillis() % 2 == 0) {
throw new BaseException(403, "哎呀,出错了", "时间不对");
}
return userBean;
}
}

编写权限认证业务类

1
2
3
4
5
6
7
8
9
10
@Service
public class AuthService {

public void auth(String token) throws BaseException {
if (!"123456789".equals(token)) {
throw new BaseException(401, BaseException.PARAM_ERROR_CODE, "token验证不通过");
}
}

}

编写拦截器

视图拦截器ModuleViewInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class ModuleViewInterceptor implements HandlerInterceptor {
private static Logger logger = LoggerFactory.getLogger(ModuleViewInterceptor.class);

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
logger.info("执行 ModuleViewInterceptor ...");
if (!ErrorHandler.OUTPUT_XML.equals(request.getParameter("output"))) {
request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE,
ErrorHandler.jsonMediaType);
logger.info("设置json视图 ...");
}
return true;
}

}

权限拦截器AuthInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private AuthService authService;
private static Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
logger.info("执行 AuthInterceptor ...");
if (authService == null) {
synchronized (AuthInterceptor.class.getName()) {
if (authService == null) {
logger.info("authService is null!!!");
BeanFactory factory = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
authService = (AuthService) factory.getBean("authService");
}
}
}
authService.auth(request.getParameter("token"));
return true;
}

}

配置类ComponentConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Configuration
public class ComponentConfig extends WebMvcConfigurationSupport {
@Autowired
private Environment env;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/test/api");
registry.addInterceptor(new ModuleViewInterceptor()).addPathPatterns("/test/api");
}

private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
}

@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}

}

启动主类WebTestApp

1
2
3
4
5
6
7
@SpringBootApplication
public class WebTestApp {

public static void main(String[] args) {
SpringApplication.run(WebTestApp.class, args);
}
}

测试验证

打开浏览器,输入:

1
2
3
4
5
6
7
8
9
10
11
http://localhost:8080/api/test

http://localhost:8080/api/test?token=123456

http://localhost:8080/api/test?token=123456789

http://localhost:8080/api/test?output=xml

http://localhost:8080/api/test?output=xml&token=123456

http://localhost:8080/api/test?output=xml&token=123456789