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

结合方法型注解 @ExceptionHandler

	用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。

	@ControllerAdvice(basePackages = "mvc")
public class ControllerAdvice {
@ExceptionHandler(RuntimeException.class)
public ModelAndView runtimeException(RuntimeException e) {
e.printStackTrace();
return new ModelAndView("error");
}
} 结合方法型注解 @InitBinder 用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的。 @ControllerAdvice(basePackages = "mvc")
public class ControllerAdvice {
@InitBinder
public void globalInitBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
} 结合方法型注解 @ModelAttribute 表示其标注的方法将会在目标Controller方法执行之前执行。 @ControllerAdvice(basePackages = "mvc")
public class ControllerAdvice {
@ModelAttribute(value = "message") //name或value属性则指定的是返回值的名称
public String globalModelAttribute() {
return "ld";
}
}

@Valid @Validated 参数校验

@Valid 注解会导致 MethodArgumentNotValidException 验证失败时抛出该异常

	1. 参数前加注解:@Valid

	2. JavaBean属性注解:@NotNull,@Max,@Size

@Validated 导致 ConstraintViolationException 抛出该异常

	@RequestParam或者@PathVariable结合@NotNull进行参数检验

	1. 类上加注解:@Validated

	2. 参数前加注解:@NotBlank(message = "姓名不能为空") @RequestParam("name") String name

	3. 给SpringMVC注入org.springframework.validation.beanvalidation.MethodValidationPostProcessor

		@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}

Controller 异常捕获

@RestControllerAdvice({"run.halo.app.controller.admin.api", "run.halo.app.controller.content.api"})
@Slf4j
public class ControllerExceptionHandler { //试图插入或更新数据时引发异常(Dao异常)
@ExceptionHandler(DataIntegrityViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse handleDataIntegrityViolationException(DataIntegrityViolationException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
if (e.getCause() instanceof org.hibernate.exception.ConstraintViolationException) {
baseResponse = handleBaseException(e.getCause());
}
baseResponse.setMessage("字段验证错误,请完善后重试!");
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return baseResponse;
} //参数校验异常
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse handleConstraintViolationException(ConstraintViolationException e) {
BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
baseResponse.setMessage("字段验证错误,请完善后重试!");
//违反属性约束的Map(key是变量名,value是错误信息)
baseResponse.setData(ValidationUtils.mapWithValidError(e.getConstraintViolations()));
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return baseResponse;
} //参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
baseResponse.setMessage("字段验证错误,请完善后重试!");
//违反属性约束的Map(key是变量名,value是错误信息)
baseResponse.setData(ValidationUtils.mapWithFieldError(e.getBindingResult().getFieldErrors()));
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return baseResponse;
} //缺少Servlet请求参数异常
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
baseResponse.setMessage(String.format("请求字段缺失,类型为 %s,名称为 %s", e.getParameterType(), e.getParameterName()));
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return baseResponse;
} /**
* 异常处理基础方法
*/
private <T> BaseResponse<T> handleBaseException(Throwable t) {
log.error("捕获一个异常:", t); //构造响应体BaseResponse
BaseResponse<T> baseResponse = new BaseResponse<>();
//设置响应信息Message
baseResponse.setMessage(t.getMessage()); if (log.isDebugEnabled()) {
//设置开发信息(堆栈跟踪信息)
baseResponse.setDevMessage(ExceptionUtils.getStackTrace(t));
} return baseResponse;
}
}

参数校验工具类

public class ValidationUtils {

    private static Validator VALIDATOR;

    private ValidationUtils() {
} /** 获取验证器 */
@NonNull
public static Validator getValidatorOrCreate() {
if (VALIDATOR == null) {
synchronized (ValidationUtils.class) {
//初始化验证器
VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
}
}
return VALIDATOR;
} /**
* 手动校验Bean
*/
public static void validate(Object obj, Class<?>... groups) { Validator validator = getValidatorOrCreate(); Set<ConstraintViolation<Object>> constraintViolations = validator.validate(obj, groups); if (!CollectionUtils.isEmpty(constraintViolations)) {
throw new ConstraintViolationException(constraintViolations);
}
} /**
* ConstraintViolationException.class
*
* 将字段验证错误转换为标准的map型,key:value = field:message
*/
@NonNull
public static Map<String, String> mapWithValidError(Set<ConstraintViolation<?>> constraintViolations) {
if (CollectionUtils.isEmpty(constraintViolations)) {
return Collections.emptyMap();
} Map<String, String> errMap = new HashMap<>(4);
//格式化错误信息
constraintViolations.forEach(
constraintViolation ->
//key:变量名(constraintViolation.getPropertyPath()),value:错误信息
errMap.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage()));
return errMap;
} /**
* MethodArgumentNotValidException.class
*
* 将字段验证错误转换为标准的map型,key:value = field:message
*/
public static Map<String, String> mapWithFieldError(@Nullable List<FieldError> fieldErrors) {
if (CollectionUtils.isEmpty(fieldErrors)) {
return Collections.emptyMap();
} Map<String, String> errMap = new HashMap<>(4); fieldErrors.forEach(
//key:变量名(constraintViolation.getPropertyPath()),value:错误信息
filedError -> errMap.put(filedError.getField(), filedError.getDefaultMessage()));
return errMap;
}
}

堆栈跟踪信息

public class ExceptionUtils {

    /** 从Throwable获取堆栈跟踪 */
public static String getStackTrace(final Throwable throwable) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw, true);
//将异常信息打印到StringWriter中
throwable.printStackTrace(pw);
return sw.getBuffer().toString();
}
}

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

/**
* 封装请求体body,解决JS跨域请求
*/
@ControllerAdvice("run.halo.app.controller")
public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object> { /**
* 拦截条件:拦截Json数据
*
* @param returnType 返回类型
* @param converterType 转换器类型
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
//拦截转换器 AbstractJackson2HttpMessageConverter子类的Controller方法
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
} @Override
@NonNull
public final Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType,
MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request, ServerHttpResponse response) {
//将返回体body包装成MappingJacksonValue
MappingJacksonValue container = getOrCreateContainer(body);
//处理返回体body,设置状态码
beforeBodyWriteInternal(container, response);
return container;
} /**
* 将返回体body包装成MappingJacksonValue
*/
private MappingJacksonValue getOrCreateContainer(Object body) {
//JSONP:JS跨域请求数据的一中解决方案
return (body instanceof MappingJacksonValue ? (MappingJacksonValue) body : new MappingJacksonValue(body));
} /**
* 处理返回体body,设置状态码
*/
private void beforeBodyWriteInternal(MappingJacksonValue bodyContainer,
ServerHttpResponse response) {
//返回体body
Object returnBody = bodyContainer.getValue(); //如果返回体body是BaseResponse及其子类,设置状态码并返回
if (returnBody instanceof BaseResponse) {
BaseResponse<?> baseResponse = (BaseResponse) returnBody;
//HttpStatus.resolve(baseResponse.getStatus():将给定的状态码解析为HttpStatus
//response.setStatusCode:设置 response 状态码
response.setStatusCode(HttpStatus.resolve(baseResponse.getStatus()));
return;
} //如果返回体body不是BaseResponse及其子类,将返回体包装成BaseResponse,设置状态码并返回
BaseResponse<?> baseResponse = BaseResponse.ok(returnBody);
bodyContainer.setValue(baseResponse);
response.setStatusCode(HttpStatus.valueOf(baseResponse.getStatus()));
}
}

自定义序列化器

/**
* 分页对象的序列化
*/
public class PageJacksonSerializer extends JsonSerializer<Page> { @Override
public void serialize(Page page, JsonGenerator generator, SerializerProvider serializers) throws IOException {
//写开始标记:'{'
generator.writeStartObject(); //写内容:属性是"content",值是page.getContent()
generator.writeObjectField("content", page.getContent());
generator.writeNumberField("pages", page.getTotalPages()); //总页数
generator.writeNumberField("total", page.getTotalElements()); //总元素数
generator.writeNumberField("page", page.getNumber()); //第几页
generator.writeNumberField("rpp", page.getSize()); //当前页元素数
generator.writeBooleanField("hasNext", page.hasNext()); //是否后面还有页
generator.writeBooleanField("hasPrevious", page.hasPrevious()); //是否前面还有页
generator.writeBooleanField("isFirst", page.isFirst()); //是否是第一页
generator.writeBooleanField("isLast", page.isLast()); //是否是最后一页
generator.writeBooleanField("isEmpty", page.isEmpty()); //当前页内容是否为空
generator.writeBooleanField("hasContent", page.hasContent()); //当前页是否有内容 //处理评论页
if (page instanceof CommentPage) {
CommentPage commentPage = (CommentPage) page;
generator.writeNumberField("commentCount", commentPage.getCommentCount()); //总评论数(包含子评论)
} //写结束标记:'}'
generator.writeEndObject();
}
} 使用1 @JsonSerialize(using = Date2LongSerialize.class)
private Date time; 使用2 /**
* Http请求和响应报文本质上都是一串字符串(有格式文本)。
*
* Spring Boot底层通过HttpMessageConverter(消息转换器)将请求报文与响应报文转换为对象。
*
* MappingJackson2HttpMessageConverter处理application/json。
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.findFirst().ifPresent(converter -> {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) converter;
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
// JsonComponentModule 来扫描被 @JsonComponent 注解的类
// 并自动注册 JsonSerializer 和 JsonDeserializer。
JsonComponentModule module = new JsonComponentModule();
//指定PageImpl类型字段使用自定义的PageJacksonSerializer序列化器
module.addSerializer(PageImpl.class, new PageJacksonSerializer());
ObjectMapper objectMapper = builder.modules(module).build();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
});
}

Controller层日志AOP切面类

@Aspect
@Component
@Slf4j
public class ControllerLogAop { //所有Controller方法
@Pointcut("execution(* *..controller..*.*(..))")
public void controller() {
} @Around("controller()")
public Object controller(ProceedingJoinPoint joinPoint) throws Throwable {
//类名
String className = joinPoint.getTarget().getClass().getSimpleName();
//方法名
String methodName = joinPoint.getSignature().getName();
//参数数组
Object[] args = joinPoint.getArgs(); //获取HttpServletRequest对象
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(requestAttributes).getRequest(); //打印请求日志
printRequestLog(request, className, methodName, args);
long start = System.currentTimeMillis();
//处理目标方法
Object returnObj = joinPoint.proceed();
//打印响应日志
printResponseLog(className, methodName, returnObj, System.currentTimeMillis() - start);
return returnObj;
} private void printRequestLog(HttpServletRequest request, String clazzName, String methodName, Object[] args) throws JsonProcessingException {
log.info("打印请求信息-----Request URL:[{}], URI:[{}], Request Method:[{}], IP:[{}]",
request.getRequestURL(),
request.getRequestURI(),
request.getMethod(),
ServletUtil.getClientIP(request)); //将参数转为Json字符串
String requestBody = JsonUtils.objectToJson(args);
log.info("打印请求参数信息-----{}.{}的请求体:[{}]", clazzName, methodName, requestBody);
} private void printResponseLog(String className, String methodName, Object returnObj, long usage) throws JsonProcessingException {
String returningData = null;
if (returnObj != null) {
if (returnObj.getClass().isAssignableFrom(byte[].class)) {
returningData = "byte[]二进制数据";
} else {
returningData = JsonUtils.objectToJson(returnObj);
}
}
log.info("打印响应信息-----{}.{}的响应体:[{}], 处理时长:[{}]ms", className, methodName, returningData, usage);
}
}

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. VS2017中英文切换

    离线安装完成, --fix 检查下是否完整 安装时没有勾选的项, 可以下次再补安装 安装时出现缺少*.vsix 如:microsoft.visualstudio.webtoolsextensions. ...

  2. ajax跨域处理 No 'Access-Control-Allow-Origin' header is present on the requested resource 问题

    Controller层的类上增加@CrossOrign注解,当前文件的所有接口就都可以被调用 spring注解@CrossOrigin不起作用的原因 1.是springMVC的版本要在4.2或以上版本 ...

  3. linux常用符号命令

    1.符号: 在linux中,&和&&,|和||介绍如下: & 表示任务在后台执行,如要在后台运行redis-server,则有 redis-server & & ...

  4. Vagrant 手册之 Provisioning - Shell 配置程序

    原文地址 Provisioner 命令:"shell" 示例: node.vm.provision "shell" do |s| s.inline = < ...

  5. TensorFlow学习笔记2-性能分析工具

    TensorFlow学习笔记2-性能分析工具 性能分析工具 在spyder中运行以下代码: import tensorflow as tf from tensorflow.python.client ...

  6. mybatis中Oracle分页语句的写法

    最近一段时间使用oracle数据库查询分页, 用的是springboot. Oracle数据库中没有像mysql中limit的写法, 只能换其他方式写. 考虑到oracle中的ROWNUM变量, 使用 ...

  7. sql server 2008查询时报错,消息:8155,没有为'a'的列2指向任何列

    解决方法1: 关掉Sql Server再打开, 重新查询 解决方法2: select T.* from(   select row_number() over(order by age desc) a ...

  8. NOI Day1T1归程(Kruskal重构树+Dijkstra)

    NOI Day1T1归程(Kruskal重构树+Dijkstra) 题目 洛谷题目传送门 题解 其实我不想写......,所以...... 挖个坑......我以后一定会补的 luogu的题解讲的还是 ...

  9. tomcat内存使用情况

    预发布阿里云服务器的容器 tomcat会自己无缘无故重启,故引出一些查看tomcat内存使用情况观察的细枝末节: 1️⃣当前端口号进程信息和GC使用情况(1)显示端口的PID:lsof -i:端口示例 ...

  10. Codeforces - 1194F - Crossword Expert - 组合数学

    https://codeforc.es/contest/1194/problem/F 下面是错的. 看起来有点概率dp的感觉? 给你T秒钟时间,你要按顺序处理总共n个事件,每个事件处理花费的时间是ti ...