最近在项目中遇到了一个问题,需求变更要求,url中需要支持特殊字符,例如:“.”,“@”等。由于项目中包含了很多已经开放的接口,采用的是restful风格的,因此改接口方案直接被pass。在查找了很多资料,最终解决这个问题。
问题主要包括三个:
- 包含特殊url请求时,@ResponseBody 注解的请求,返回时会抛出异常
- 如何匹配包含特殊字符的url,例如/email/[email protected]
- 采用注解方式自定义异常以json格式返回的,在包含特殊Url时,无法抛出自己想要的异常格式
注意:本文spring boot版本是基于1.5.16 (2.0以后可能有所不同)
问题一:包含特殊url请求时,@ResponseBody 注解的请求,返回时会抛出异常
package com.example.demo.controller;
import com.example.demo.domain.RestfulAPIResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author huangyichun
* @date 2018/12/29
*/
@RestController
public class EmailController {
@GetMapping("/user/{name}")
public RestfulAPIResponse queryUser(@PathVariable("name") String name) {
RestfulAPIResponse restfulAPIResponse = new RestfulAPIResponse<>("requestId");
restfulAPIResponse.setRequestId("requestId");
restfulAPIResponse.setResult("name", name);
return restfulAPIResponse;
}
}
public class RestfulAPIResponse implements Serializable {
private String requestId;
private Map result;
public RestfulAPIResponse(String requestId){
result = new HashMap();
this.requestId = requestId;
}
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public Map getResult() {
return result;
}
public void setResult(String key,R value) {
this.result.put(key,value);
}
public void setResultMap(Map map) {
if (map != null && map.size() > 0) {
result.putAll(map);
}
}
}
请求 url : http://127.0.0.1:8080/user/[email protected] 返回值如下:
{
"timestamp": 1546072805982,
"status": 406,
"error": "Not Acceptable",
"exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
"message": "Could not find acceptable representation",
"path": "/user/[email protected]"
}
这个问题是由于使用外部Tomcat导致的,如果直接调用Spring boot的main方法启动的内部tomcat不会产生这个问题。
解决方法:添加如下配置,问题解决
package com.example.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @author huangyichun
* @date 2018/12/29
*/
@Configuration
public class SpringServiceConfiguration extends WebMvcConfigurationSupport {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// to avoid HttpMediaTypeNotAcceptableException on standalone tomcat
configurer.favorPathExtension(false);
}
}
调用url: 请求 url : http://127.0.0.1:8080/user/[email protected] 返回值如下:
{
"requestId": "requestId",
"result": {
"name": "huangyichun_@"
}
}
可以看出返回格式是正常了,但是发现返回的name和我们请求的name不太一样,这是因为Spring将url中的"."看做了分隔符,因此产生了问题二。
问题二:如何匹配包含特殊字符的url,例如/email/[email protected]
解决方法如下:
package com.example.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @author huangyichun
* @date 2018/12/29
*/
@Configuration
public class SpringServiceConfiguration extends WebMvcConfigurationSupport {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// to avoid HttpMediaTypeNotAcceptableException on standalone tomcat
configurer.favorPathExtension(false);
}
/**
* 解决url后缀包含特殊字符
* @param configurer
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
请求上面的url,返回结果如下:
{
"requestId": "requestId",
"result": {
"name": "[email protected]"
}
}
问题三:采用注解方式自定义异常以json格式返回的,在包含特殊Url时,无法抛出自己想要的异常格式
该场景出现的比较特殊,是在Controller方法中,使用RestTemplate调用其他服务,例如查询用户是否存在,结果为不存在,然后该服务抛出了一个not fount 404的json异常信息。项目中采用了注解方法进行异常处理,在返回时,Spring进行序列化后导致结果不是自己想要的格式。
解决思路:
让自定义异常处理类继承HandlerExceptionResolver,然后实现默认方法,对于抛出的异常进行最后的转换,结果符合自己需求。下面直接给出参考代码,和上面示例不是同一个项目,仅供参考
@ControllerAdvice
public class CustomExceptionHandler extends BaseController implements HandlerExceptionResolver {
private static Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
private ServiceError serviceError = new ServiceError();
@ExceptionHandler(HttpClientErrorException.class)
@ResponseBody
public OpenApiResponse httpClientErrorException(HttpServletRequest request, HttpServletResponse response, HttpClientErrorException ex){
logger.error("requestId:{} httpClientErrorException:{}",getRequestId(request),ex);
OpenApiResponse res = new OpenApiResponse(getRequestId(request));
String msg = ex.getResponseBodyAsString();
if(StringUtils.isNotBlank(msg)){
try{
JSONObject jsonObject = JSON.parseObject(msg);
int code = ex.getStatusCode().equals(HttpStatus.OK) ? 402 : ex.getStatusCode().value();
String status = jsonObject.getString("code");
String message = jsonObject.getString("message");
Map detail = JsonUtils.jsonObject2Map(jsonObject.getJSONObject("details"));
serviceError.setCode(code);
serviceError.setMessage(StringUtils.isNotBlank(message) ? message : "内部调用错误");
serviceError.setStatus(StringUtils.isNotBlank(status) ? status : "INVOKE_ERROR");
if (detail != null) {
serviceError.setDetails(ArrayUtils.toArray(detail));
}
res.setError(serviceError);
response.setStatus(code);
return res;
}
catch (Exception e){
logger.error("解析错误,",e);
}
}
serviceError.setCode(500);
serviceError.setMessage("内部调用错误");
serviceError.setStatus("INNER_ERROR");
response.setStatus(500);
res.setError(serviceError);
return res;
}
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public OpenApiResponse defaultException(HttpServletRequest request,HttpServletResponse response, Exception e){
logger.error("requestId:{} spring捕获运行时异常:{}",getRequestId(request),e);
OpenApiResponse res = new OpenApiResponse(getRequestId(request));
ServiceError serviceError = new ServiceError();
serviceError.setCode(500);
serviceError.setMessage("内部错误");
serviceError.setStatus("INNER_ERROR");
res.setError(serviceError);
response.setStatus(500);
return res;
}
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
FastJsonJsonView fastJsonJsonView = new FastJsonJsonView();
Map map = new HashMap<>();
Map result = new HashMap<>();
map.put("requestId", getRequestId(request));
map.put("result", result);
map.put("error", serviceError);
fastJsonJsonView.setAttributesMap(map);
ModelAndView mv =new ModelAndView();
mv.setView(fastJsonJsonView);
return mv;
}
}