Halo(七)
@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(七)的更多相关文章
- Halo 开源项目学习(七):缓存机制
基本介绍 我们知道,频繁操作数据库会降低服务器的系统性能,因此通常需要将频繁访问.更新的数据存入到缓存.Halo 项目也引入了缓存机制,且设置了多种实现方式,如自定义缓存.Redis.LevelDB ...
- 使用Docker快速搭建Halo个人博客到阿里云服务器上[附加主题和使用域名访问]
一.前言 小编买了一个服务器也是一直想整个网站,一直在摸索,看了能够快速搭建博客系统的教程.总结了有以下几种方式,大家按照自己喜欢的去搭建: halo wordpress hexo vuepress ...
- Halo 开源项目学习(一):项目启动
项目简介 Halo 是一个优秀的开源博客发布应用,在 GitHub 上广受好评,正好最近在练习写博客,借此记录一下学习 Halo 的过程. 项目下载 从 GitHub 上拉取项目源码,Halo 从 1 ...
- 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文
阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/D ...
- CRL快速开发框架系列教程七(使用事务)
本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...
- 《LoadRunner12七天速成宝典》来了
看到自己的新书又要发行了,算算从09年第一本书开始,不知不觉已经是第四本书了(帮朋友合写的书不算),每次写完之后都会说太累了,不想再写了,但是却又次次反悔,吞下食言的苦果.如果非要说第四本书的感受,那 ...
- 【SAP业务模式】之ICS(七):IDOC配置
这是ICS业务模式系列的最后一篇了,主要讲解IDOC的配置. 一.指定EDI传输的供应商逻辑地址 事务代码:WEL1 注意:上面逻辑地址是生产公司+内部客户.有以下两种情形: 1.如果内部客户都是纯数 ...
- 我的MYSQL学习心得(七) 查询
我的MYSQL学习心得(七) 查询 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类 ...
- Nodejs之MEAN栈开发(七)---- 用Angular创建单页应用(下)
上一节我们走通了基本的SPA基础结构,这一节会更彻底的将后端的视图.路由.控制器全部移到前端.篇幅比较长,主要分页面改造.使用AngularUI两大部分以及一些优化路由.使用Angular的其他指令的 ...
随机推荐
- Anaconda在Python3和Python2之间切换,Conda命令,anaconda中python的升级和降级
当在pycharm IDE中指定不同的Python版本时,设置方法 File->Setting->Project:XXXX->Project Interpreter 选择不同位 ...
- 多个springboot项目部署到tomcat,Error deploying web application archive
每个springboot单独部署到tomcat下可以正常启动,多个一个就发生异常 Error deploying web application archive 解决:配置文件加上配置区分 sprin ...
- 重写hashCode方法,导致内存泄漏
package com.nchu.learn.base.reflect; import org.junit.Test; import java.util.Collection; import java ...
- Vagrant 手册之 Provisioning - Shell 配置程序
原文地址 Provisioner 命令:"shell" 示例: node.vm.provision "shell" do |s| s.inline = < ...
- HTML批量修改——正则表达式实践
目录 1.问题描述 2.初步研究 3.进一步研究 3.1提取2.*中的序号* 3.2提取标题 3.3选取全文 3.4替换 参考资料 1.问题描述 如下所示的一段HTML代码: ... <h2 a ...
- linux 查看 python 安装包路径
[root]# python -c "import fasttext;print(fasttext)"<module 'fasttext' from '/root/anaco ...
- Python模块之pdfkit
1.安装依赖 pip install python-docx #Python下的Microsoft Word 2007工具 pip install PyPDF2 #Python下的PDF工具 pip ...
- Lua中C API栈操作
向栈中压入数据: lua_pushnil(lua_State*); lua_pushboolean(lua_State*, bool); lua_pushnumber(lua_State*, lua_ ...
- 关于URL和URI的最简单理解
以下面网址为例: http://www.sina.com/news/1.html 那么,http://www.sina.com/news/1.html就表示URL,用于标识互联网中的某一资源:/new ...
- pthread_cond_timedwait
该函数用于在同时等待条件变量时提供超时功能,不过该函数的超时时间是一个绝对时间.默认使用系统时间,这意味这,若修改系统时间,那么超时就不准确,有可能提前返回,也可能要几年才返回.这在某些需求下会导致b ...