SpringBoot中高级用法
生产项目中一般会有项目改进
1、我们一般不会在controller中捕获异常进行处理,一般通过全局异常处理器进行拦截处理
2、为了让响应的格式进行统一,一般会对响应结果进行统一包装。
3、为了能够快速定位问题,一般会结合日志打印框架让每次http请求都能够打印一个唯一标识方便问题定位
1、统一异常处理
定义异常枚举
public enum ResultCode { /** * 成功 */ SUCCESS(0, "success"), FAIL(501,"操作失败"), /** * 未知错误 */ UNKNOWN_ERROR(500, "unkonwn error"), /** * 用户名错误或不存在 */ USERNAME_ERROR(401, "username error or does not exist"), /** * 密码错误 */ PASSWORD_ERROR(402, "password error"), /** * 用户名不能为空 */ USERNAME_EMPTY(403, "username can not be empty"); /** * 结果码 */ private int code; /** * 结果码描述 */ private String msg; ResultCode(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } }
自定义业务异常
import cn.educate.boot.demo.enu.ResultCode; /** * @author yinchong * @create 2021/4/25 16:24 * @description 业务异常类 */ public class BizRuntimeExcption extends RuntimeException { int code; String msg; public BizRuntimeExcption(ResultCode resultCode) { super(resultCode.getMsg()); this.code = resultCode.getCode(); this.msg = resultCode.getMsg(); } public BizRuntimeExcption(int code, String msg) { super(msg); this.code = code; this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
添加全局异常处理器
import cn.educate.boot.demo.enu.ResultCode; import cn.educate.boot.demo.model.dto.ResultDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice public class GlobalExceptionResolver { private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionResolver.class); /** * 处理所有不可知异常 * * @param e 异常 * @return json结果 */ @ExceptionHandler(Exception.class) @ResponseBody public ResultDTO handleException(Exception e) { // 打印异常堆栈信息 LOG.error(e.getMessage(), e); return ResultDTO.of(ResultCode.UNKNOWN_ERROR); } /** * 处理所有业务异常 * * @param e 业务异常 * @return json结果 */ @ExceptionHandler(BizRuntimeExcption.class) @ResponseBody public ResultDTO handleOpdRuntimeException(BizRuntimeExcption e) { // 不打印异常堆栈信息 LOG.error(e.getMsg()); return ResultDTO.of(e.getCode(),e.getMsg()); } }
业务代码使用
service层超时失败抛出业务异常
import cn.educate.boot.demo.dao.UserMapper; import cn.educate.boot.demo.enu.ResultCode; import cn.educate.boot.demo.model.dto.UserDTO; import cn.educate.boot.demo.model.po.UserPO; import cn.educate.boot.demo.model.vo.UserQuery; import cn.educate.boot.demo.model.vo.UserVO; import cn.educate.boot.demo.service.IUserService; import cn.educate.boot.demo.system.BizRuntimeExcption; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import tk.mybatis.mapper.entity.Example; import java.util.LinkedList; import java.util.List; /** * @author yinchong * @create 2021/4/25 9:35 * @description */ @Service public class UserServiceImpl implements IUserService { @Autowired private UserMapper userDao; @Override public void addUser(UserVO vo) { int rowCount = userDao.addUser(vo); checkRow(rowCount); } @Override public void updateUser(UserVO vo) { int rowCount = userDao.updateUser(vo); checkRow(rowCount); } @Override public void deleteUser(Long id) { int rowCount = userDao.deleteUser(id); checkRow(rowCount); } @Override public List<UserDTO> listUser(UserQuery query) { Example example = new Example(UserPO.class); Example.Criteria criteria = example.createCriteria(); if(query.getId()!=null){ criteria.andEqualTo("id",query.getId()); }else if(Strings.isNotBlank(query.getName())){ criteria.andLike("name","%"+query.getName()+"%"); } List<UserPO> list = userDao.selectByExample(example); List<UserDTO> result = new LinkedList<>(); for(UserPO po:list){ UserDTO dto = new UserDTO(); BeanUtils.copyProperties(po,dto); result.add(dto); } return result; } private void checkRow(int row){ if(row<=0){ throw new BizRuntimeExcption(ResultCode.FAIL); } } }
controller层取消try、catch
import cn.educate.boot.demo.model.dto.UserDTO; import cn.educate.boot.demo.model.vo.UserQuery; import cn.educate.boot.demo.model.vo.UserVO; import cn.educate.boot.demo.service.IUserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @author yinchong * @create 2021/4/25 9:29 * @description */ @RestController @RequestMapping("user") @Slf4j public class UserController { @Autowired private IUserService userService; /*** * 添加用户 * @param vo * @return */ @RequestMapping("add") public String add(@RequestBody UserVO vo) { log.info("add user vo:{}", vo); userService.addUser(vo); return "SUCCESS"; } /** * 更新用户 * * @param vo * @return */ @RequestMapping("update") public String update(@RequestBody UserVO vo) { log.info("update user vo:{}",vo); userService.updateUser(vo); return "SUCCESS"; } /*** * 删除用户 * @param query * @return */ @RequestMapping("delete") public String delete(@RequestBody UserQuery query) { log.info("delete User vo:{}",query); userService.deleteUser(query.getId()); return "SUCCESS"; } /*** * 查询用户 * @param query * @return */ @RequestMapping("listUser") public List<UserDTO> listUser(@RequestBody UserQuery query) { log.info("query user vo:{}",query); return userService.listUser(query); } }
统一格式输出
import cn.educate.boot.demo.model.dto.ResultDTO; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /** * @author yinchong * @create 2021/4/25 16:33 * @description */ @Slf4j @ControllerAdvice public class ResultResponseBodyAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { //TODO 这里可以根据自己需求比如有些方法不需要进行拦截,可以在这里进行改造 return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof ResultDTO) { return body; } return ResultDTO.success(body); } }
http请求唯一标识打印
代码改造 import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; @Configuration public class RequestIdLogAppendConfiguration extends WebMvcConfigurerAdapter { /** * 每次请求生成唯一标识 **/ public static final String REQUEST_ID = "requestId"; private static Logger logger = LoggerFactory.getLogger(WebMvcConfigurerAdapter.class); @Override public void addInterceptors(InterceptorRegistry registry) { HandlerInterceptor handlerInterceptor = new HandlerInterceptor() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //生成请求id String requestId = UUID.randomUUID().toString().replaceAll("-", ""); MDC.put(REQUEST_ID, requestId); return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { //销毁请求id MDC.clear(); } }; registry.addInterceptor(handlerInterceptor).addPathPatterns("/**"); } }
log4j配置变动
添加requestId输出
<!--变量配置--> <Properties> <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <!-- %logger{36} 表示 Logger 名字最长36个字符 --> <property name="LOG_PATTERN" value="%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{requestId}] %logger{36} - %msg%n" /> <!-- 定义日志存储的路径 --> <property name="FILE_PATH" value="D://data/log/boot-educate" /> <property name="FILE_NAME" value="boot-educate" /> </Properties>
github地址:https://github.com/yinkaihuang/boot-educate.git 分支:advance