建设目标
- 请求接口通过请求参数中设置的output参数=xml/json来返回对应的视图,默认使用json;
- 发生异常时,@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> <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> <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
|