@ControllerAdvice 对Controller进行"切面"环绕

  1. 结合方法型注解 @ExceptionHandler
  2. 用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。
  3. @ControllerAdvice(basePackages = "mvc")
  4. public class ControllerAdvice {
  5. @ExceptionHandler(RuntimeException.class)
  6. public ModelAndView runtimeException(RuntimeException e) {
  7. e.printStackTrace();
  8. return new ModelAndView("error");
  9. }
  10. }
  11. 结合方法型注解 @InitBinder
  12. 用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的。
  13. @ControllerAdvice(basePackages = "mvc")
  14. public class ControllerAdvice {
  15. @InitBinder
  16. public void globalInitBinder(WebDataBinder binder) {
  17. binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
  18. }
  19. }
  20. 结合方法型注解 @ModelAttribute
  21. 表示其标注的方法将会在目标Controller方法执行之前执行。
  22. @ControllerAdvice(basePackages = "mvc")
  23. public class ControllerAdvice {
  24. @ModelAttribute(value = "message") //name或value属性则指定的是返回值的名称
  25. public String globalModelAttribute() {
  26. return "ld";
  27. }
  28. }

@Valid @Validated 参数校验

  1. @Valid 注解会导致 MethodArgumentNotValidException 验证失败时抛出该异常
  2. 1. 参数前加注解:@Valid
  3. 2. JavaBean属性注解:@NotNull@Max@Size
  4. @Validated 导致 ConstraintViolationException 抛出该异常
  5. @RequestParam或者@PathVariable结合@NotNull进行参数检验
  6. 1. 类上加注解:@Validated
  7. 2. 参数前加注解:@NotBlank(message = "姓名不能为空") @RequestParam("name") String name
  8. 3. SpringMVC注入org.springframework.validation.beanvalidation.MethodValidationPostProcessor
  9. @Bean
  10. public MethodValidationPostProcessor methodValidationPostProcessor() {
  11. return new MethodValidationPostProcessor();
  12. }

Controller 异常捕获

  1. @RestControllerAdvice({"run.halo.app.controller.admin.api", "run.halo.app.controller.content.api"})
  2. @Slf4j
  3. public class ControllerExceptionHandler {
  4. //试图插入或更新数据时引发异常(Dao异常)
  5. @ExceptionHandler(DataIntegrityViolationException.class)
  6. @ResponseStatus(HttpStatus.BAD_REQUEST)
  7. public BaseResponse handleDataIntegrityViolationException(DataIntegrityViolationException e) {
  8. BaseResponse<?> baseResponse = handleBaseException(e);
  9. if (e.getCause() instanceof org.hibernate.exception.ConstraintViolationException) {
  10. baseResponse = handleBaseException(e.getCause());
  11. }
  12. baseResponse.setMessage("字段验证错误,请完善后重试!");
  13. baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
  14. return baseResponse;
  15. }
  16. //参数校验异常
  17. @ExceptionHandler(ConstraintViolationException.class)
  18. @ResponseStatus(HttpStatus.BAD_REQUEST)
  19. public BaseResponse handleConstraintViolationException(ConstraintViolationException e) {
  20. BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
  21. baseResponse.setMessage("字段验证错误,请完善后重试!");
  22. //违反属性约束的Map(key是变量名,value是错误信息)
  23. baseResponse.setData(ValidationUtils.mapWithValidError(e.getConstraintViolations()));
  24. baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
  25. return baseResponse;
  26. }
  27. //参数校验异常
  28. @ExceptionHandler(MethodArgumentNotValidException.class)
  29. @ResponseStatus(HttpStatus.BAD_REQUEST)
  30. public BaseResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
  31. BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
  32. baseResponse.setMessage("字段验证错误,请完善后重试!");
  33. //违反属性约束的Map(key是变量名,value是错误信息)
  34. baseResponse.setData(ValidationUtils.mapWithFieldError(e.getBindingResult().getFieldErrors()));
  35. baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
  36. return baseResponse;
  37. }
  38. //缺少Servlet请求参数异常
  39. @ExceptionHandler(MissingServletRequestParameterException.class)
  40. @ResponseStatus(HttpStatus.BAD_REQUEST)
  41. public BaseResponse handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
  42. BaseResponse<?> baseResponse = handleBaseException(e);
  43. baseResponse.setMessage(String.format("请求字段缺失,类型为 %s,名称为 %s", e.getParameterType(), e.getParameterName()));
  44. baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
  45. return baseResponse;
  46. }
  47. /**
  48. * 异常处理基础方法
  49. */
  50. private <T> BaseResponse<T> handleBaseException(Throwable t) {
  51. log.error("捕获一个异常:", t);
  52. //构造响应体BaseResponse
  53. BaseResponse<T> baseResponse = new BaseResponse<>();
  54. //设置响应信息Message
  55. baseResponse.setMessage(t.getMessage());
  56. if (log.isDebugEnabled()) {
  57. //设置开发信息(堆栈跟踪信息)
  58. baseResponse.setDevMessage(ExceptionUtils.getStackTrace(t));
  59. }
  60. return baseResponse;
  61. }
  62. }

参数校验工具类

  1. public class ValidationUtils {
  2. private static Validator VALIDATOR;
  3. private ValidationUtils() {
  4. }
  5. /** 获取验证器 */
  6. @NonNull
  7. public static Validator getValidatorOrCreate() {
  8. if (VALIDATOR == null) {
  9. synchronized (ValidationUtils.class) {
  10. //初始化验证器
  11. VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
  12. }
  13. }
  14. return VALIDATOR;
  15. }
  16. /**
  17. * 手动校验Bean
  18. */
  19. public static void validate(Object obj, Class<?>... groups) {
  20. Validator validator = getValidatorOrCreate();
  21. Set<ConstraintViolation<Object>> constraintViolations = validator.validate(obj, groups);
  22. if (!CollectionUtils.isEmpty(constraintViolations)) {
  23. throw new ConstraintViolationException(constraintViolations);
  24. }
  25. }
  26. /**
  27. * ConstraintViolationException.class
  28. *
  29. * 将字段验证错误转换为标准的map型,key:value = field:message
  30. */
  31. @NonNull
  32. public static Map<String, String> mapWithValidError(Set<ConstraintViolation<?>> constraintViolations) {
  33. if (CollectionUtils.isEmpty(constraintViolations)) {
  34. return Collections.emptyMap();
  35. }
  36. Map<String, String> errMap = new HashMap<>(4);
  37. //格式化错误信息
  38. constraintViolations.forEach(
  39. constraintViolation ->
  40. //key:变量名(constraintViolation.getPropertyPath()),value:错误信息
  41. errMap.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage()));
  42. return errMap;
  43. }
  44. /**
  45. * MethodArgumentNotValidException.class
  46. *
  47. * 将字段验证错误转换为标准的map型,key:value = field:message
  48. */
  49. public static Map<String, String> mapWithFieldError(@Nullable List<FieldError> fieldErrors) {
  50. if (CollectionUtils.isEmpty(fieldErrors)) {
  51. return Collections.emptyMap();
  52. }
  53. Map<String, String> errMap = new HashMap<>(4);
  54. fieldErrors.forEach(
  55. //key:变量名(constraintViolation.getPropertyPath()),value:错误信息
  56. filedError -> errMap.put(filedError.getField(), filedError.getDefaultMessage()));
  57. return errMap;
  58. }
  59. }

堆栈跟踪信息

  1. public class ExceptionUtils {
  2. /** 从Throwable获取堆栈跟踪 */
  3. public static String getStackTrace(final Throwable throwable) {
  4. final StringWriter sw = new StringWriter();
  5. final PrintWriter pw = new PrintWriter(sw, true);
  6. //将异常信息打印到StringWriter中
  7. throwable.printStackTrace(pw);
  8. return sw.getBuffer().toString();
  9. }
  10. }

ResponseBodyAdvice接口 + @ControllerAdvice 处理返回结果

  1. /**
  2. * 封装请求体body,解决JS跨域请求
  3. */
  4. @ControllerAdvice("run.halo.app.controller")
  5. public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object> {
  6. /**
  7. * 拦截条件:拦截Json数据
  8. *
  9. * @param returnType 返回类型
  10. * @param converterType 转换器类型
  11. */
  12. @Override
  13. public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
  14. //拦截转换器 AbstractJackson2HttpMessageConverter子类的Controller方法
  15. return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
  16. }
  17. @Override
  18. @NonNull
  19. public final Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType,
  20. MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType,
  21. ServerHttpRequest request, ServerHttpResponse response) {
  22. //将返回体body包装成MappingJacksonValue
  23. MappingJacksonValue container = getOrCreateContainer(body);
  24. //处理返回体body,设置状态码
  25. beforeBodyWriteInternal(container, response);
  26. return container;
  27. }
  28. /**
  29. * 将返回体body包装成MappingJacksonValue
  30. */
  31. private MappingJacksonValue getOrCreateContainer(Object body) {
  32. //JSONP:JS跨域请求数据的一中解决方案
  33. return (body instanceof MappingJacksonValue ? (MappingJacksonValue) body : new MappingJacksonValue(body));
  34. }
  35. /**
  36. * 处理返回体body,设置状态码
  37. */
  38. private void beforeBodyWriteInternal(MappingJacksonValue bodyContainer,
  39. ServerHttpResponse response) {
  40. //返回体body
  41. Object returnBody = bodyContainer.getValue();
  42. //如果返回体body是BaseResponse及其子类,设置状态码并返回
  43. if (returnBody instanceof BaseResponse) {
  44. BaseResponse<?> baseResponse = (BaseResponse) returnBody;
  45. //HttpStatus.resolve(baseResponse.getStatus():将给定的状态码解析为HttpStatus
  46. //response.setStatusCode:设置 response 状态码
  47. response.setStatusCode(HttpStatus.resolve(baseResponse.getStatus()));
  48. return;
  49. }
  50. //如果返回体body不是BaseResponse及其子类,将返回体包装成BaseResponse,设置状态码并返回
  51. BaseResponse<?> baseResponse = BaseResponse.ok(returnBody);
  52. bodyContainer.setValue(baseResponse);
  53. response.setStatusCode(HttpStatus.valueOf(baseResponse.getStatus()));
  54. }
  55. }

自定义序列化器

  1. /**
  2. * 分页对象的序列化
  3. */
  4. public class PageJacksonSerializer extends JsonSerializer<Page> {
  5. @Override
  6. public void serialize(Page page, JsonGenerator generator, SerializerProvider serializers) throws IOException {
  7. //写开始标记:'{'
  8. generator.writeStartObject();
  9. //写内容:属性是"content",值是page.getContent()
  10. generator.writeObjectField("content", page.getContent());
  11. generator.writeNumberField("pages", page.getTotalPages()); //总页数
  12. generator.writeNumberField("total", page.getTotalElements()); //总元素数
  13. generator.writeNumberField("page", page.getNumber()); //第几页
  14. generator.writeNumberField("rpp", page.getSize()); //当前页元素数
  15. generator.writeBooleanField("hasNext", page.hasNext()); //是否后面还有页
  16. generator.writeBooleanField("hasPrevious", page.hasPrevious()); //是否前面还有页
  17. generator.writeBooleanField("isFirst", page.isFirst()); //是否是第一页
  18. generator.writeBooleanField("isLast", page.isLast()); //是否是最后一页
  19. generator.writeBooleanField("isEmpty", page.isEmpty()); //当前页内容是否为空
  20. generator.writeBooleanField("hasContent", page.hasContent()); //当前页是否有内容
  21. //处理评论页
  22. if (page instanceof CommentPage) {
  23. CommentPage commentPage = (CommentPage) page;
  24. generator.writeNumberField("commentCount", commentPage.getCommentCount()); //总评论数(包含子评论)
  25. }
  26. //写结束标记:'}'
  27. generator.writeEndObject();
  28. }
  29. }
  30. 使用1
  31. @JsonSerialize(using = Date2LongSerialize.class)
  32. private Date time;
  33. 使用2
  34. /**
  35. * Http请求和响应报文本质上都是一串字符串(有格式文本)。
  36. *
  37. * Spring Boot底层通过HttpMessageConverter(消息转换器)将请求报文与响应报文转换为对象。
  38. *
  39. * MappingJackson2HttpMessageConverter处理application/json。
  40. */
  41. @Override
  42. public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
  43. converters.stream()
  44. .filter(c -> c instanceof MappingJackson2HttpMessageConverter)
  45. .findFirst().ifPresent(converter -> {
  46. MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) converter;
  47. Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
  48. // JsonComponentModule 来扫描被 @JsonComponent 注解的类
  49. // 并自动注册 JsonSerializer 和 JsonDeserializer。
  50. JsonComponentModule module = new JsonComponentModule();
  51. //指定PageImpl类型字段使用自定义的PageJacksonSerializer序列化器
  52. module.addSerializer(PageImpl.class, new PageJacksonSerializer());
  53. ObjectMapper objectMapper = builder.modules(module).build();
  54. mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
  55. });
  56. }

Controller层日志AOP切面类

  1. @Aspect
  2. @Component
  3. @Slf4j
  4. public class ControllerLogAop {
  5. //所有Controller方法
  6. @Pointcut("execution(* *..controller..*.*(..))")
  7. public void controller() {
  8. }
  9. @Around("controller()")
  10. public Object controller(ProceedingJoinPoint joinPoint) throws Throwable {
  11. //类名
  12. String className = joinPoint.getTarget().getClass().getSimpleName();
  13. //方法名
  14. String methodName = joinPoint.getSignature().getName();
  15. //参数数组
  16. Object[] args = joinPoint.getArgs();
  17. //获取HttpServletRequest对象
  18. ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  19. HttpServletRequest request = Objects.requireNonNull(requestAttributes).getRequest();
  20. //打印请求日志
  21. printRequestLog(request, className, methodName, args);
  22. long start = System.currentTimeMillis();
  23. //处理目标方法
  24. Object returnObj = joinPoint.proceed();
  25. //打印响应日志
  26. printResponseLog(className, methodName, returnObj, System.currentTimeMillis() - start);
  27. return returnObj;
  28. }
  29. private void printRequestLog(HttpServletRequest request, String clazzName, String methodName, Object[] args) throws JsonProcessingException {
  30. log.info("打印请求信息-----Request URL:[{}], URI:[{}], Request Method:[{}], IP:[{}]",
  31. request.getRequestURL(),
  32. request.getRequestURI(),
  33. request.getMethod(),
  34. ServletUtil.getClientIP(request));
  35. //将参数转为Json字符串
  36. String requestBody = JsonUtils.objectToJson(args);
  37. log.info("打印请求参数信息-----{}.{}的请求体:[{}]", clazzName, methodName, requestBody);
  38. }
  39. private void printResponseLog(String className, String methodName, Object returnObj, long usage) throws JsonProcessingException {
  40. String returningData = null;
  41. if (returnObj != null) {
  42. if (returnObj.getClass().isAssignableFrom(byte[].class)) {
  43. returningData = "byte[]二进制数据";
  44. } else {
  45. returningData = JsonUtils.objectToJson(returnObj);
  46. }
  47. }
  48. log.info("打印响应信息-----{}.{}的响应体:[{}], 处理时长:[{}]ms", className, methodName, returningData, usage);
  49. }
  50. }

Halo(七)的更多相关文章

  1. Halo 开源项目学习(七):缓存机制

    基本介绍 我们知道,频繁操作数据库会降低服务器的系统性能,因此通常需要将频繁访问.更新的数据存入到缓存.Halo 项目也引入了缓存机制,且设置了多种实现方式,如自定义缓存.Redis.LevelDB ...

  2. 使用Docker快速搭建Halo个人博客到阿里云服务器上[附加主题和使用域名访问]

    一.前言 小编买了一个服务器也是一直想整个网站,一直在摸索,看了能够快速搭建博客系统的教程.总结了有以下几种方式,大家按照自己喜欢的去搭建: halo wordpress hexo vuepress ...

  3. Halo 开源项目学习(一):项目启动

    项目简介 Halo 是一个优秀的开源博客发布应用,在 GitHub 上广受好评,正好最近在练习写博客,借此记录一下学习 Halo 的过程. 项目下载 从 GitHub 上拉取项目源码,Halo 从 1 ...

  4. 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文

    阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/D ...

  5. CRL快速开发框架系列教程七(使用事务)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  6. 《LoadRunner12七天速成宝典》来了

    看到自己的新书又要发行了,算算从09年第一本书开始,不知不觉已经是第四本书了(帮朋友合写的书不算),每次写完之后都会说太累了,不想再写了,但是却又次次反悔,吞下食言的苦果.如果非要说第四本书的感受,那 ...

  7. 【SAP业务模式】之ICS(七):IDOC配置

    这是ICS业务模式系列的最后一篇了,主要讲解IDOC的配置. 一.指定EDI传输的供应商逻辑地址 事务代码:WEL1 注意:上面逻辑地址是生产公司+内部客户.有以下两种情形: 1.如果内部客户都是纯数 ...

  8. 我的MYSQL学习心得(七) 查询

    我的MYSQL学习心得(七) 查询 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类 ...

  9. Nodejs之MEAN栈开发(七)---- 用Angular创建单页应用(下)

    上一节我们走通了基本的SPA基础结构,这一节会更彻底的将后端的视图.路由.控制器全部移到前端.篇幅比较长,主要分页面改造.使用AngularUI两大部分以及一些优化路由.使用Angular的其他指令的 ...

随机推荐

  1. 解决IDEA输入法输入中文候选框不显示问题

    本机环境为: 系统: win7        jdk版本:jdk1.8.0_65      idea版本:2017.2.3 解决方法:关掉idea,进入idea的安装目录找到jre64文件夹重命名为j ...

  2. LinkedHashSet -有序,不重合集合,但仍不可索引,结合for循环取元素,数据多可能效率低

    package cn.learn.collection.Set; import java.util.HashSet; import java.util.Iterator; import java.ut ...

  3. spring-第七篇之深入理解容器中的bean

    1.抽象bean与子bean 用于指定配置模板. 2.容器中的工厂bean 这种工厂bean必须实现FactoryBean接口,通过spring容器getBean()方法获取它时,容器返回的不是Fac ...

  4. HNOI2019fish

    \({\rm fish}\) 20分: 六个for,点积判锐角钝角. #include <vector> #include <queue> #include <cmath ...

  5. 【桶哥的问题——吃桶-简化版】【洛谷p2671】求和

    求和=>[链接] 题目相较起_rqy出的要简单很多,来自noip普及组2015 化简这个式子:x+z=2y,故x与z mod 2同余,因此和桶哥的问题——吃桶一样的思路就可以做出来啦qwq: # ...

  6. [BZOJ 4771]七彩树(可持久化线段树+树上差分)

    [BZOJ 4771]七彩树(可持久化线段树+树上差分) 题面 给定一棵n个点的有根树,编号依次为1到n,其中1号点是根节点.每个节点都被染上了某一种颜色,其中第i个节点的颜色为c[i].如果c[i] ...

  7. Pandas的高级操作

    pandas数据处理 1. 删除重复元素 使用duplicated()函数检测重复的行,返回元素为布尔类型的Series对象,每个元素对应一行,如果该行不是第一次出现,则元素为True keep参数: ...

  8. python 日产经销商

    ''' ajaxCallSiteInfo: {1C8B2BC6-35E2-460E-A63D-3576F3039D79} ''' import requests import json from db ...

  9. Oracle 汉字占用字节数

    在oracle中一个字符特别是中文字符占几个字节是与字符集有关的.      比如GBK,汉字就会占两个字节,英文1个:如果是UTF-8,汉字一般占3个字节,英文还是1个.但是一般情况下,我们都认为是 ...

  10. 图解git中的最常用命令

    图解git中的最常用命令 Git命令参考手册(文本版) git init                                                  # 初始化本地git仓库(创 ...