美文网首页
解决SpringBoot中URL有特殊字符,导致的自定义异常无效

解决SpringBoot中URL有特殊字符,导致的自定义异常无效

作者: 秋风落叶黄 | 来源:发表于2018-12-29 18:19 被阅读0次

        最近在项目中遇到了一个问题,需求变更要求,url中需要支持特殊字符,例如:“.”,“@”等。由于项目中包含了很多已经开放的接口,采用的是restful风格的,因此改接口方案直接被pass。在查找了很多资料,最终解决这个问题。

    问题主要包括三个:

    1. 包含特殊url请求时,@ResponseBody 注解的请求,返回时会抛出异常
    2. 如何匹配包含特殊字符的url,例如/email/huang@gmail.com
    3. 采用注解方式自定义异常以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<String> restfulAPIResponse = new RestfulAPIResponse<>("requestId");
            restfulAPIResponse.setRequestId("requestId");
            restfulAPIResponse.setResult("name", name);
            return restfulAPIResponse;
        }
    }
    
    public class RestfulAPIResponse<R> implements Serializable {
    
        private String requestId;
    
        private Map<String,R> result;
    
        public RestfulAPIResponse(String requestId){
            result = new HashMap<String,R>();
            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<String, R> map) {
            if (map != null && map.size() > 0) {
                result.putAll(map);
            }
        }
    }
    
    

    请求 url : http://127.0.0.1:8080/user/huangyichun_@.com 返回值如下:

    {
        "timestamp": 1546072805982,
        "status": 406,
        "error": "Not Acceptable",
        "exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
        "message": "Could not find acceptable representation",
        "path": "/user/huangyichun_@.com"
    }
    

    这个问题是由于使用外部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/huangyichun_@.com 返回值如下:

    {
        "requestId": "requestId",
        "result": {
            "name": "huangyichun_@"
        }
    }
    

    可以看出返回格式是正常了,但是发现返回的name和我们请求的name不太一样,这是因为Spring将url中的"."看做了分隔符,因此产生了问题二。

    问题二:如何匹配包含特殊字符的url,例如/email/huang@gmail.com

    解决方法如下:

    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": "huangyichun_@.com"
        }
    }
    

    问题三:采用注解方式自定义异常以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<String, String> 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<String,Object> map = new HashMap<>();
            Map<String, Object> 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;
        }
    }
    

    相关文章

      网友评论

          本文标题:解决SpringBoot中URL有特殊字符,导致的自定义异常无效

          本文链接:https://www.haomeiwen.com/subject/obdulqtx.html