异常返回:
需要将原本的错误格式转换为JSON格式
异常注解:
@ControllerAdvice:需要表明一个处理异常的类
@ExceptionHandler:异常的处理器 ,接受一个参数
Error 错误
Exception 异常

CheckedException:编译时进行处理
RuntimeException:运行时异常

HttpException extends RuntimeException
APIException extends Exception
可处理的BUG:Checked
不可处理:Runtime

已知异常 未知异常

未知异常:对于前端开发者和用户 都是无意义的。服务端开发者代码逻辑有问题

  1. @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) 修改响应码
  2. 需要动态改变code码的已知异常处理代码示例
  3. @ExceptionHandler(HttpException.class)
  4. public ResponseEntity handleHttpException(HttpServletRequest req,HttpException e){
  5. String requestUrl = req.getRequestURI();
  6. String method = req.getMethod();
  7. UnifyResponse message = new UnifyResponse(e.getCode(),"2212",method+" "+ req);
  8. //未使用@Response注解 所以需要自己写headers
  9. HttpHeaders headers = new HttpHeaders();
  10. headers.setContentType(MediaType.APPLICATION_JSON);
  11. //动态设置HttpStatus
  12. //使用spring的静态方法 不用NEW一个对象
  13. HttpStatus httpStatus = HttpStatus.resolve(e.getHttpStatusCode());
  14. ResponseEntity<UnifyResponse> r = new ResponseEntity<>(message,headers,httpStatus);
  15. return r ;
  16. }

自定义配置类管理配置文件

异常的message文本 通常会放到单独的配置文件中,而单独的配置文件则需要一个单独的类来调用,这是spring提倡的,而在单独的类中可以使用MAP(键值对)

需要@Component注解将类注入到容器中

指定文件的路径并与类相关联:@PropertySource(value=”classPath:”)类路径下

读取前缀: @ConfigurationProperties(prefix=”前缀名”)

  1. package com.lin.missyou.core.configuration;
  2. import org.springframework.boot.context.properties.ConfigurationProperties;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.context.annotation.PropertySource;
  5. import org.springframework.stereotype.Component;
  6. import java.util.HashMap;
  7. import java.util.Map;
  8. @Component
  9. @ConfigurationProperties(prefix="lin")
  10. @PropertySource(value = "classPath:config/exception-code.properties")
  11. public class ExceptionCodeConfiguration {
  12. private Map<Integer, String> codes = new HashMap<>();
  13. public Map<Integer, String> getCodes() {
  14. return codes;
  15. }
  16. public String getMessage(int code){
  17. String message = codes.get(code);
  18. return message;
  19. }
  20. }

根据目录结构自动生成路由前缀

在很多其他框架中,比如Python的Flask、node.js的KOA,Controller要想能够响应前端的请求都需要我们主动去注册到应用程序上。而Spring不需要我们自己去注册,由Spring通过扫描注解的方式去主动发现。

自定义RequestMappingInfo

Spring中的RequestMappingHandlerMapping专门来负责处理标注了@RequestMapping的控制器。创建一个类继承并覆盖其中的方法,从而实现对默认机制的修改。覆盖其中的getMappingForMethod方法,这个方法的返回值RequestMappingInfo就包含了请求的Url,修改RequestMappingInfo中的Url从而修改路由中的Url。

  1. package com.lin.missyou.hack;
  2. import org.springframework.beans.factory.annotation.Value;
  3. import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
  4. import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
  5. import java.lang.reflect.Method;
  6. public class AutoPrefixUrlMapping extends RequestMappingHandlerMapping {
  7. @Value("${missyou.api-package}")
  8. private String apiPackagePath ; //从配置文件中获取根包的路径
  9. @Override
  10. protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
  11. RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType);
  12. if(null != requestMappingInfo){
  13. //获取url前缀
  14. String prefix = getPrefix(handlerType);
  15. //根据url前缀生成RequestMappingInfo并与原有的RequestMappingInfo合并
  16. RequestMappingInfo mappingInfo = RequestMappingInfo.paths(prefix).build().combine(requestMappingInfo);
  17. return mappingInfo;
  18. }
  19. return requestMappingInfo;
  20. }
  21. private String getPrefix(Class<?> handlerType){
  22. String packageName = handlerType.getPackage().getName(); //获取控制器所在包路径
  23. String dotPath = packageName.replaceAll(this.apiPackagePath,""); //将包路径中多于的部分截取掉
  24. return dotPath.replace(".","/"); //将包路径中的.替换成/
  25. }
  26. }

通过接口的形式发现类

创建一个配置类AutoPrefixConfigurationAutoPrefixUrlMapping加入到容器。配置类AutoPrefixConfiguration实现接口WebMvcRegistrations并覆盖其中的getRequestMappingHandlerMapping方法

  1. package com.lin.missyou.core.config;
  2. import com.lin.missyou.hack.AutoPrefixUrlMapping;
  3. import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
  4. import org.springframework.stereotype.Component;
  5. import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
  6. @Component
  7. public class AutoPrefixConfiguration implements WebMvcRegistrations {
  8. @Override
  9. public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
  10. return new AutoPrefixUrlMapping();
  11. }
  12. }

在配置文件中指定根包

  1. missyou.api-package = com.lin.missyou.api

SprinBoot的 发现机制 有两种。一种是在控制器上标注特定注解,例如上一篇博文 SpringBoot全局异常处理中在GlobalExceptionAdvice 上标注@ControllerAdvice。另外一种是实现特定接口并覆盖其中的特定方法,例如上面的AutoPrefixConfiguration。
测试一下
Spring异常与自动配置URL前缀 - 图1
访问结果,访问路径/v1/banner/test可以访问到该控制器
Spring异常与自动配置URL前缀 - 图2
将访问路径改为/banner/test就访问不到了
Spring异常与自动配置URL前缀 - 图3
BannerController移动到sample文件夹下访问路径/v1/sample/banner/test可以访问到该控制器
Spring异常与自动配置URL前缀 - 图4
Spring异常与自动配置URL前缀 - 图5

这个方法存在一些争议。一方面认为根据目录结构自动生成url虽然比较简单,少写了一些代码,但是无法通过控制器上标注的@RequestMapping中的参数直接看出url,代码的可读性不是太好。另一方面认为,这个方法大大的简化了我们代码的编写,同时更加易于维护,控制器随意调整所在目录都不需要去修改代码。