解决SpringBoot中URL有特殊字符,导致的自定义异常无效以及匹配问题

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

问题主要包括三个:

  1. 包含特殊url请求时,@ResponseBody 注解的请求,返回时会抛出异常
  2. 如何匹配包含特殊字符的url,例如/email/[email protected]
  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 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;
    }
}

你可能感兴趣的:(解决SpringBoot中URL有特殊字符,导致的自定义异常无效以及匹配问题)