Springboot项目-项目整合
Springboot项目-项目整合
[TOC]
Springboot项目整合说明
【1】后台接口返回格式统一规范
a.前后端数据交互
返回数据格式统一:https://blog.csdn.net/qq_37813031/article/details/105351187
项目前后端数据交互参考说明:
https://blog.csdn.net/qq_20957669/article/details/89227840
https://www.jianshu.com/p/33cc0c4e070b
https://blog.csdn.net/yelllowcong/article/details/79711429
后端接收数据处理:https://www.jianshu.com/p/33cc0c4e070b
参数处理:https://blog.csdn.net/qq_20957669/article/details/89227840
(1)后台接口响应JSON数据定义
参考代码:
自定义响应JSON规范
// 响应数据定义
public class AjaxResult {
@JsonProperty(index = 1)
private Integer errCode;
@JsonProperty(index = 2)
private String errMsg;
/**
* 在引用的时候需要初始化变量,避免空指针异常
**/
@JsonProperty(index = 3)
private Map<String, Object> extend;
public AjaxResult(Integer errCode, String errMsg, Map<String, Object> extend) {
this.errCode = errCode;
this.errMsg = errMsg;
this.extend = extend;
}
public AjaxResult(Integer errCode, String errMsg) {
this.errCode = errCode;
this.errMsg = errMsg;
this.extend = new HashMap<String, Object>();
}
public AjaxResult(ResultEnum codeEnum) {
this.errCode = codeEnum.getCode();
this.errMsg = codeEnum.getMsg();
this.extend = new HashMap<String, Object>();
}
public AjaxResult(ResultEnum codeEnum, Map<String, Object> extend) {
this.errCode = codeEnum.getCode();
this.errMsg = codeEnum.getMsg();
this.extend = extend;
}
public Integer getErrCode() {
return errCode;
}
public void setErrCode(Integer errCode) {
this.errCode = errCode;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public Map<String, Object> getExtend() {
return extend;
}
public void setExtend(Map<String, Object> extend) {
this.extend = extend;
}
}
// 响应数据工具类
public class AjaxResultUtil {
// public static ResponseEntity<AjaxResult> success() {
// AjaxResult result = new AjaxResult(ResultEnum.RESPONSE_FAIL);
// return new ResponseEntity<AjaxResult>(result, HttpStatus.NOT_FOUND);
// }
// 数据响应成功
public static AjaxResult success() {
return new AjaxResult(ResultEnum.RESPONSE_SUCCESS);
}
public static AjaxResult success(String msg) {
return new AjaxResult(ResultEnum.RESPONSE_SUCCESS.getCode(), msg);
}
public static AjaxResult success(Map<String, Object> data) {
return new AjaxResult(ResultEnum.RESPONSE_SUCCESS, data);
}
public static AjaxResult success(String key, Object data) {
AjaxResult ajaxResult = new AjaxResult(ResultEnum.RESPONSE_SUCCESS);
ajaxResult.getExtend().put(key, data);
return ajaxResult;
}
// 数据响应失败
public static AjaxResult fail() {
return new AjaxResult(ResultEnum.RESPONSE_FAIL);
}
public static AjaxResult fail(ResultEnum codeEnum) {
return new AjaxResult(codeEnum);
}
public static AjaxResult fail(Map<String, Object> data) {
return new AjaxResult(ResultEnum.RESPONSE_FAIL, data);
}
public static AjaxResult fail(String errMsg) {
return new AjaxResult(ResultEnum.RESPONSE_FAIL.getCode(), errMsg);
}
// 异常处理
public static AjaxResult error(ResultEnum codeEnum) {
return new AjaxResult(codeEnum);
}
public static AjaxResult error(Integer errCode, String errMsg) {
return new AjaxResult(errCode, errMsg);
}
public static AjaxResult error(String errMsg) {
return new AjaxResult(ResultEnum.ERROR.getCode(), errMsg);
}
// 数据交互失败
public static AjaxResult notFound() {
return new AjaxResult(ResultEnum.NOT_FOUND);
}
// 参数异常
public static AjaxResult paramError() {
return new AjaxResult(ResultEnum.PARAM_ERROR);
}
// 系统异常
public static AjaxResult systemError() {
return new AjaxResult(ResultEnum.SYSTEM_ERROR);
}
}
// 响应代码枚举说明
public enum ResultEnum {
/**
* 返回参数枚举定义
*/
RESPONSE_SUCCESS(0, "[响应成功]"),
RESPONSE_FAIL(1, "[响应失败]"),
ERROR(-1, "ERROR [未知处理异常]"),
// 后台处理相关异常
BACK_OPER_ERROR(-1001, "BACK OPER ERROR [后台处理异常,请联系管理员进行处理]"),
SERVER_ERROR(-1001, "SERVER ERROR [服务器响应异常,请联系管理员进行处理]"),
SYSTEM_ERROR(-1001, "SYSTEM ERROR [系统处理异常]"),
PARAM_ERROR(-1001, "PARAM ERROR [数据处理异常]"),
OPER_FAIL_ERROR(-1001, "OPER FAIL ERROR [操作处理失败异常]"),
// 前后端交互触发异常
SYSTEM_IDENTIFY_EMPTY_ERROR(-1002, "指定系统标识不能为空"),
AUTH_ERROR(-2, "AUTH ERROR [用户角色权限相关异常]"),
LOGIN_PARAM_EMPTY_ERROR(-2001, "登录账号(用户编号)、密码不能为空"),
UASS_VALID_ERROR(-2001, "UASS登录账号或密码验证失败"),
LOCAL_VALID_ERROR(-2001, "本地登录账号或密码验证失败"),
NO_USER_DATA_BY_LOCAL(-2001, "当前系统不存在该用户信息,请联系管理员进行处理"),
ACCOUNT_LOCK_BY_LOCAL(-2001, "当前系统账号尚未激活,请联系管理员进行处理"),
LOGIN_FAIL_ERROR(-2001, "SHIRO认证失败"),
NO_LOGIN_ERROR(-2002, "当前用户尚未登录或登录信息已过期"),
NO_AUTH_ERROR(-2003, "当前用户角色权限不足,请联系管理员进行处理"),
BUSINESS_EXP(-3, "业务处理异常"),
FRONT_TRANSFER_DATA_ERROR(-3001, "FRONT_TRANSFER_DATA_ERROR [前端传入数据异常]"),
OPER_NOT_ALLOW(-3001, "OPER_NOT_ALLOW [当前%s操作不被允许]"),
NOT_FOUND(-3001, "NOT FOUND [数据不存在或数据为空]"),
REPEAT_OPER_NOT_ALLOW(-3001, "REPEAT OPER NOT ALLOW [当前系统检验到重复操作,不允许执行]"),
;
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
b.配置注解说明
(1)@Controller与@RestController的区别
@RestController说明
@RestController会自动封装返回数据为json数据
在@RestController注解下,getUser、getUser1、getUser2都能正常访问接口并返回相应的字符串数据,对象返回JSON字符串
@Controller
在@Controller注解下,可借助ResponseEntity<T>
或者@ ResponseBody约定后端返回的数据格式
getUser:ResponseEntity<AjaxResult>处理返回的数据能够正常响应数据
getUser1:返回的String被解析成视图,页面访问404
getUser2:借助@ ResponseBody处理返回的数据能够正常响应数据,如果不使用@ ResponseBody注解则返回的结果会被处理成视图,页面访问404
(2)@ResponseEntity与@ResponseBody的区别
@ResponseEntity的优先级高于@ResponseBody。在不是ResponseEntity的情况下才去检查有没有@ResponseBody注解。如果响应类型是ResponseEntity可以不写@ResponseBody注解。ResponseEntity 是在 org.springframework.http.HttpEntity 的基础上添加了http status code(http状态码),用于RestTemplate以及@Controller的HandlerMethod。它在Controller中或者用于服务端响应时,作用是和@ResponseStatus与@ResponseBody结合起来的功能一样的。用于RestTemplate时,它是接收服务端返回的http status code 和 reason的。
@ResponseBody可以直接返回Json结果, @ResponseEntity不仅可以返回json结果,还可以自定义返回的HttpHeaders和HttpStatus
综上所述,在编写代码的时候可以考虑将页面跳转和数据处理的Controller拆分开来,页面跳转的Controller使用@Controller注解实现(如果类里面有需要返回JSON数据的可借助ResponseEntity封装返回数据或者是借助@ ResponseBody注解),数据处理的Controller使用@RestController注解
为了统一返回后台处理格式,暂定保留ResponseEntity<T>返回格式(可用来处理文件下载等),自定义AjaxResult统一定义接口返回格式,可考虑定义BaseController方法或者是创建AjaxResultUtil封装公共方法避免继承体系臃肿
ResponseEntity响应http下载:https://www.jianshu.com/p/9091f8c8ea65
ResponseEntity使用及原理:https://blog.csdn.net/u010900754/article/details/105329256/
(3)Springboot普通工具类使用@Value注入参数配置
参考链接:https://blog.csdn.net/w2006009/article/details/103476002
Springboot注解不能实例化static参数问题:
@Autowire
private ParamUtil paramUtil;(如果使用static则初始化失败)
/** 自定义参数工具类 **/
@Component
public class ParamUtil {
@Value("${boxing.uass-sso.serveruass-ip}")
private String serverIp;
@Value("${boxing.uass-sso.des3privacykey}")
private String des3privacykey;
public String getServerIp() {
return serverIp;
}
public String getDes3privacykey() {
return des3privacykey;
}
}
【2】代码规范-自定义持久层(Mapper)架构
Mp继承baseMapper,可以在mapper中自定义实现(MP生成代码)
如果后续在开发过程需要调整数据库字段,在不影响原有业务逻辑的情况下只需要变动实体类和mapper中的映射,其余自定义内容不需要调整:
调整实体类(一般是字段,其余少变动)
替换实体映射,自定义mapper、service的方法大体框架不动,结合需求进行调整
(注意自定义的接口方法尽量避免与基类同名)
【3】代码规范-自定义服务层架构
沿用mybatis-plus自带的mapper、service层数据
@Component, @Repository, @Service的区别
官网引用
引用spring的官方文档中的一段描述:
在Spring2.0之前的版本中,@Repository注解可以标记在任何的类上,用来表明该类是用来执行与数据库相关的操作(即dao对象),并支持自动处理数据库操作产生的异常
在Spring2.5版本中,引入了更多的Spring类注解:@Component,@Service,@Controller。@Component是一个通用的Spring容器管理的单例bean组件。而@Repository, @Service, @Controller就是针对不同的使用场景所采取的特定功能化的注解组件。
因此,当你的一个类被@Component所注解,那么就意味着同样可以用@Repository, @Service, @Controller来替代它,同时这些注解会具备有更多的功能,而且功能各异。
最后,如果你不知道要在项目的业务层采用@Service还是@Component注解。那么,@Service是一个更好的选择。
就如上文所说的,@Repository早已被支持了在你的持久层作为一个标记可以去自动处理数据库操作产生的异常(译者注:因为原生的java操作数据库所产生的异常只定义了几种,但是产生数据库异常的原因却有很多种,这样对于数据库操作的报错排查造成了一定的影响;而Spring拓展了原生的持久层异常,针对不同的产生原因有了更多的异常进行描述。所以,在注解了@Repository的类上如果数据库操作中抛出了异常,就能对其进行处理,转而抛出的是翻译后的spring专属数据库异常,方便我们对异常进行排查处理)。
https://blog.csdn.net/fansili/article/details/78740877
在尽量不改动原有mybatis-plus代码的基础上,扩展自己的功能模块(如果数据库实体改变,则相应model、service等需要调整)
调用service扩展自己的BL(业务逻辑层)
在service中继承IService的基础上直接扩展自己的方法,当实体类更新的时候不变更自身的service层方法
在调用公共服务层的时候,定义一个xxxBL再次封装自己的业务逻辑!供自身子模块的controller调用
Controller层:可继承BaseController实现对通用方法的调用
或者是AjaxResultUtil:将通用的方法放在工具类,借助工具类调用
【4】代码规范-自定义异常处理
a.自定义全局异常处理器:捕捉异常
(1)实现思路
方式1:定义一个BaseException,后续结合不同的业务需求和业务模块完善
异常属性说明:异常管理模块module、异常编码errCode、异常信息errMsg、错误码对应的参数;可结合实际项目需求扩展自定义异常的属性,方便调试是快速定位错误
参考ruoyi的异常管理机制,例如用户管理中定义UserException继承BaseException,然后用户管理模块中每个功能模块又拆分为多个xxxException继承UserException,根据实际业务需求处理不同的异常情况
方式2:直接定义一个通用的Exception类,例如CommonException,定义相应的枚举用于拆分不同的业务异常情况
例如子模块eoas定义EOASException继承BusinessException(初始化指定了模块标识,用于区分哪个模块),在抛出异常的时候根据枚举指定抛出的异常处理数据;子模块可结合业务需求相应配置自定义的异常过滤处理器
为统一规范,直接提供BusinessException供子模块调用
- 通过class判断是否是一个类的子类
// 定义父子类
class Person {}
class Man extends Person {}
// 如果已知子类对象,判断Man对象是否是Person的子类
Man man = new Man();
System.out.println(man instanceof Person); // true
// 如果已知子类class,判断Person是否为Man的父类
Class<?> clazz = Man.class;
System.out.println(Person.class.isAssignableFrom(clazz)); // true
如果没有明确指定是捕捉何类异常,则根据全局异常处理寻找匹配的内容。例如定义RuntimeException、BusinessException(继承RuntimeException)、BaseException(继承RuntimeException)、xxxException(继承BaseException);如果明确指定异常类型处理,则根据捕捉的异常进行相应处理(注意异常处理的先后顺序)
(2)实现参考
步骤说明:
1.自定义异常处理类(可考虑采用通过基类异常扩展的思路)
2.Springboot整合项目中通过配置注解实现自定义异常拦截配置(自定义全局异常处理器)
参考链接:统一异常处理(可配置返回json格式)-https://blog.csdn.net/qq_44741038/article/details/93588403?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1
自定义异常处理类(可考虑采用通过基类异常扩展的思路)
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 所属模块
*/
private String module;
/**
* 错误码
*/
private Integer errCode;
/**
* 指定操作参数列表
*/
private Object[] params;
/**
* 错误消息
*/
private String errMsg;
public BaseException() {
}
public BaseException(String module, Integer errCode, Object[] params, String errMsg) {
super(errMsg);
this.module = module;
this.errCode = errCode;
this.params = params;
this.errMsg = errMsg;
}
public BaseException(String module, Integer errCode, Object[] params, String errMsg, Throwable e) {
// 填充异常信息
super(errMsg, e);
this.module = module;
this.errCode = errCode;
this.params = params;
this.errMsg = errMsg;
}
public BaseException(String module, Integer errCode, Object[] params) {
this(module, errCode, params, null, null);
}
public BaseException(String module, Integer errCode, String errMsg) {
this(module, errCode, null, errMsg);
}
public BaseException(String module, String errMsg) {
this(module, ResultEnum.ERROR.getCode(), null, errMsg);
}
public BaseException(String module, String errMsg, Throwable e) {
super(errMsg, e);
this.module = module;
}
public BaseException(String module, String errMsg, Object[] params) {
this(module, ResultEnum.ERROR.getCode(), params, errMsg);
}
public BaseException(String module, ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.errCode = resultEnum.getCode();
this.errMsg = resultEnum.getMsg();
this.module = module;
}
public BaseException(String module, ResultEnum resultEnum, Object[] params) {
super(resultEnum.getMsg());
this.errCode = resultEnum.getCode();
this.errMsg = resultEnum.getMsg();
this.module = module;
this.params = params;
}
public String getModule() {
return module;
}
public void setModule(String module) {
this.module = module;
}
public Integer getErrCode() {
return errCode;
}
public void setErrCode(Integer errCode) {
this.errCode = errCode;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
}
子模块或子系统扩展:
Springboot整合项目中通过配置注解实现自定义异常拦截配置(自定义全局异常处理器)
// 结合项目需求对项目指定异常进行自定义处理,可自行处理异常日志的打印
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 请求方式不支持
*/
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public AjaxResult handleException(HttpRequestMethodNotSupportedException e) {
log.error(e.getMessage(), e);
return AjaxResultUtil.error("不支持'" + e.getMethod() + "'请求");
}
/**
* 请求参数异常
*/
@ExceptionHandler({MissingServletRequestParameterException.class})
public AjaxResult handleException(MissingServletRequestParameterException e) {
log.error(e.getMessage(), e);
return AjaxResultUtil.error("接口请求参数异常,请检查传递参数");
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult unKnowErr(RuntimeException e) {
log.error("未知的运行时异常:", e);
return AjaxResultUtil.error("未知的运行时异常:" + e.getMessage());
}
/**
* 自定义业务异常
*/
@ExceptionHandler(BusinessException.class)
public AjaxResult businessException(BusinessException e) {
log.error(e.getMessage(), e);
return AjaxResultUtil.error(e.getErrCode(), e.getMessage());
}
/**
* 自定义系统异常处理(根据不同的模块划分)
*/
@ExceptionHandler(BaseException.class)
public AjaxResult handleException(BaseException e) {
// 如果是BaseException相关
if (e instanceof BaseException) {
StringBuilder errMsg = new StringBuilder();
errMsg.append("异常触发所属模块:" + e.getModule() + ";");
errMsg.append("异常触发错误代码:" + e.getErrCode() + ";");
errMsg.append("异常触发错误消息:" + e.getErrMsg() + ";");
errMsg.append("异常触发参数列表:" + e.getParams() + ";");
// 打印日志信息
log.error(errMsg.toString(), e);
}
// return AjaxResultUtil.error(ResultEnum.SERVER_ERROR);
return AjaxResultUtil.error(e.getErrCode(), e.getErrMsg());
}
/**
* 处理Get请求中,使用@Valid验证路径中请求实体校验失败后抛出的异常
*/
//
@ExceptionHandler(BindException.class)
public AjaxResult BindExceptionHandler(BindException e) {
String message = e.getBindingResult()
.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining());
return AjaxResultUtil.error(e.getMessage());
}
/**
* 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
*/
@ExceptionHandler(ConstraintViolationException.class)
public AjaxResult resolveConstraintViolationException(ConstraintViolationException ex) {
// WebResult errorWebResult = new WebResult(WebResult.FAILED);
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
if (!CollectionUtils.isEmpty(constraintViolations)) {
StringBuilder msgBuilder = new StringBuilder();
for (ConstraintViolation constraintViolation : constraintViolations) {
msgBuilder.append(constraintViolation.getMessage()).append(",");
}
String errorMessage = msgBuilder.toString();
if (errorMessage.length() > 1) {
errorMessage = errorMessage.substring(0, errorMessage.length() - 1);
}
// errorWebResult.setInfo(errorMessage);
// return errorWebResult;
return AjaxResultUtil.error(errorMessage);
}
return AjaxResultUtil.error(ex.getMessage());
// errorWebResult.setInfo(ex.getMessage());
// return errorWebResult;
}
/**
* 处理请求参数格式错误
* @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常
**/
@ExceptionHandler(MethodArgumentNotValidException.class)
public AjaxResult resolveMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
// WebResult errorWebResult = new WebResult(WebResult.FAILED);
List<ObjectError> objectErrors = ex.getBindingResult().getAllErrors();
if (!CollectionUtils.isEmpty(objectErrors)) {
StringBuilder msgBuilder = new StringBuilder();
for (ObjectError objectError : objectErrors) {
msgBuilder.append(objectError.getDefaultMessage()).append(",");
}
String errorMessage = msgBuilder.toString();
if (errorMessage.length() > 1) {
errorMessage = errorMessage.substring(0, errorMessage.length() - 1);
}
// errorWebResult.setInfo(errorMessage);
// return errorWebResult;
return AjaxResultUtil.error(errorMessage);
}
// errorWebResult.setInfo(ex.getMessage());
// return errorWebResult;
return AjaxResultUtil.error(ex.getMessage());
}
/**
* 处理Shiro权限拦截异常
* 如果返回JSON数据格式则@ControllerAdvice与@ResponseBody注解结合使用
*/
@ExceptionHandler(value = AuthorizationException.class)
public AjaxResult defaultErrorHandler() {
return AjaxResultUtil.error(ResultEnum.NO_AUTH_ERROR);
}
/**
* shiro相关异常过滤:密码校验异常
**/
@ExceptionHandler(IncorrectCredentialsException.class)
public AjaxResult handleIncorrectCredentialsException(IncorrectCredentialsException e) {
log.error(e.getMessage(), e);
return AjaxResultUtil.error(ResultEnum.LOCAL_VALID_ERROR);
}
@ExceptionHandler(AuthenticationException.class)
public AjaxResult handleAuthenticationException(AuthenticationException e) {
// 如果是BaseException相关
if (e instanceof AccountException) {
// 打印日志信息
log.error(e.toString(), e);
return AjaxResultUtil.error(ResultEnum.AUTH_ERROR.getCode(),e.getMessage());
}else{
log.error(e.getMessage(), e);
return AjaxResultUtil.error(ResultEnum.LOGIN_FAIL_ERROR);
}
// return AjaxResultUtil.error(ResultEnum.SERVER_ERROR);
}
}
b.自定义扩展问题
(1)ExceptionHandler异常处理顺序
参考链接: https://blog.csdn.net/mfkarj/article/details/105227825?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1
场景说明:在同一个全局异常处理其中定义了两个自定义异常处理方法,恰好这两个自定义异常有重叠的部分(项目运行触发异常恰好可以满足这两个自定义异常的条件),需通过配置对二者进行先后校验的区分
// 例如自定义业务异常BusinessException继承了RuntimeException
@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public ResultVO handlerSellerException(BusinessException e) {
return ResultVOUtil.error(e.getExceptionCode(), e.getExceptionMessage());
}
@ExceptionHandler(value = RuntimeException.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, RuntimeException e) throws Exception {
e.printStackTrace();
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
解决思路:将两个异常单独进行配置,即在不同的异常处理器(处理类)中分别定义这两个自定义异常,随后在类定义中通过@Order来设定执行异常的顺序判断
(2)@RestControllerAdvice和@ControllerAdvice的使用区别
一般作用于含有注解@RestController的Controller层,对@RequestMapping注解的方法起作用。
@RestControllerAdvice和@ControllerAdvice说明:
都在org.springframework.web.bind.annotation包下面;
都用于注解类( => @Target(ElementType.TYPE) <= ),类中会包含有@ExceptionHandler等注解的方法
但在具体使用上有所不同
注解有@ControllerAdvice的类, 需要在具体方法上同时添加@ExceptionHandler和@ResponseBody注解;
注解有@RestControllerAdvice的类,只需要在具体方法上添加@ExceptionHandler注解
即在实际应用中结合具体接口要响应的数据来选择注解配置,例如配置了@RestControllerAdvice则该类对应的所有接口响应返回的是JSON数据,如果需要实现捕获异常后实现自定义页面跳转则应该结合@ControllerAdvice与@ResponseBody的灵活配置
测试异常触发:
整合常见问题及处理
【1】项目配置问题处理
a.多模块mybatis-plus扫描包配置(子模块引用)
Unsatisfied dependency expressed through field 'baseMapper'
扫描包:mybatis-plus配置文件中添加包扫描(系统不抛空指针异常,但是不调用方法)
项目启动最常报错:Maven多模块下没有扫描到子模块或本模块的Mapper相关文件
结合上述报错,排除出错原因:
检查mapper映射路径和文件配置是否正确:
如果一个Maven项目是由一个主模块和多个子模块构成,其中如果需要在主模块中引用子模块的mapper和xml文件,则相应需要在主配置模块中引入参考配置:
主模块中定义的application.yml配置文件中,配置mybatis-plus的mapper-locations指定的是mapper的扫描路径,如果仅仅是classpath:mapper/**/Mapper.xml,则只会扫描当前moduler的class,如果调整为classpath则相应扫描所有jar(前提是主模块中引用了该子模块)
检查Mapper文件接口是否有@Mapper注解映射或在启动类中配置@MapperScan属性():
检查路径配置,查看系统启动是否正常扫描所有指定mapper
在检查每一层的依赖注入时,发现经由Mapper-plus代码生成器生成的MVC相关代码,Mapper层缺失了@Mapper注解导致项目启动报错,加上@Mapper注解后项目正常启动引用
检查springboot项目配置和mybatis-plus版本兼容问题:
如果是引用springboot框架,最好是引用mybatis-plus-boot-starter,无需整合各式各样的jar
例如将项目springboot1.5.8升级到2.0.4,以至于将Mybatis-Plus 升级,springBoot启动之后报错Unsatisfied dependency expressed through field 'baseMapper',检查MP自动生成的代码,实体类的主键注解(之前的版本是@Id、更新的版本是@TableId)有所不同,手动在Mapper中加入@Mapper注解,上述问题解决。之后遇到类似的问题相应也要参考相关思路进行错误排查!
b.SpringMVC依赖注入问题(DI-依赖注入空指针异常)
借助Maven构建多模块项目的时候,常常需要依赖其他模块的mapper或者是service数据,下述简单说明在依赖的时候可能会遇到的问题:
Service空指针异常,说明Service对象没有正常被注入
场景应用:mip-portal作为主配置启动模块,其中依赖了mip-rest公共服务子模块,如果想要在mip-portal中正常引用mip-rest的内容可有下述两种思路:
正常直接引用:
例如mip-rest中有UserService类,则可在mip-portal中直接通过@Autowired注入UserService,正常调用方法
封装方法引用:
如果mip-rest提供了一个RestService类,在其中封装了UserService并调用相关方法,对外提供公有服务。则mip-protal可通过访问RestService间接实现对UserService的引用。但处理这种情况的前提必须事RestService必须沿用SpringMVC的DI属性去封装,否则不在统一体系下定义的内容会导致程序在处理的时候会报空指针异常
例如:在mip-rest中RestService借助@Service注解实现声明,随后可在RestService中任意通过注解加载其他mapper、service对象;在mip-portal中引入RestService是也要遵循DI注入原则,通过@Autowired注入RestService对象,再进行引用(Spring生态沿用)
具体实现参考说明如下:
由此可知,在排查对象注入空指针异常的时候需要检查其引用的一整套内容是否在Spring管理机制的体系下,如果不是则可能造成对象依赖注入失败,项目访问的时候便会报空指针异常。因此代码编码格式规范习惯要养成!
【2】mybatis-plus整合问题处理
a.mybatis-plus驼峰命名问题
参考链接:https://www.jianshu.com/p/a5c9bab9584a
不同mybatis-plus版本,其相应配置属性不同,结合实际引用版本参考官方文档说明进行设定
b.mybatis-plus代码生成问题
代码生成依赖jar:
参考链接:https://blog.csdn.net/weixin_39520967/article/details/99704442
表名与实体名配置不一致,在代码生成器中去除数据表前缀:
数据库设置、包名设置、类名设置、生成位置配置(代码生成器是独立项目 与主数据源无关),需要依赖代码模板相关jar例如freemarker
c.mybatis-plus版本升级
mybatis-plus不同数据库版本限定:MP版本不追高,追求稳定性
MP代码生成器:
Mysql:freemarker、驱动、类型转换
Oracle:velocity、驱动、类型转换
d.注解引用问题
(1)使用SpringMVC参数注解@PathVariable时出错提示
Missing URI template variable 'employeeNumber' for method parameter of type String
@RequestMapping(value = "/findByNumber/{empNum}",method = RequestMethod.GET)
public ResponseEntity<TodoUser> findByNumber(@PathVariable @Valid String employeeNumber){
...
}
如果@RequestMapping中表示为”item/{id}”,id和形参名称一致,@PathVariable不用指定名称。如果不一致,例如”item/{ItemId}”则需要指定名称@PathVariable(“itemId”)。
因此原代码中的参数 @RequestMapping(value = "/findUserByEmployeeNumber/{EmployeeNumber}
中{EmployeeNumber}变量名需要和@PathVariable @Valid String employeeNumber
中一样
解决方式:
// 方式1:保持参数名一致
@RequestMapping(value = "/findByNumber/{employeeNumber}",method = RequestMethod.GET)
public ResponseEntity<TodoUser> findByNumber(@PathVariable @Valid String employeeNumber){
...
}
// 方式2:添加别名
@RequestMapping(value = "/findByNumber/{empNum}",method = RequestMethod.GET)
public ResponseEntity<TodoUser> findByNumber(@PathVariable("empNum") @Valid String employeeNumber){
...
}
区分:@PathVariable是获取url上数据的;@RequestParam获取请求参数的(包括post表单提交)
(2)springboot注入问题:Springboot循环依赖问题
Bean with name 'xxxxxx' has been injected into other beans:
https://www.cnblogs.com/changemax/p/12311472.html
依赖循环,比如:我现在有一个ServiceA需要调用ServiceB的方法,那么ServiceA就依赖于ServiceB,那在ServiceB中再调用ServiceA的方法,就形成了循环依赖。Spring在初始化bean的时候就不知道先初始化哪个bean就会报错。
public class ClassA {@Autowired ClassB classB;}
public class ClassB { @Autowired ClassA classA ;}
解决办法是进行解耦,通过@Lazy进行延时加载
@Autowired @Lazy
private ClassA classA;
@Autowired @Lazy
private ClassB classB;
【3】常见基础问题处理
a.文件下载异常处理
java.net.MalformedURLException: unknown protocol: f
Unhandled exception java.net.malformedurlexception 异常,需要额外处理异常!
参考链接:https://blog.csdn.net/hou6gang/article/details/99640165
解决方式:将文件的参数赋值为 file:///F://daily.txt
b.404:接口访问路径错误或指定页面视图不存在
postman接口测试:
!
c.500:程序处理出现异常
postman接口测试:
d.项目devtools热部署报错
参考链接:https://www.cnblogs.com/liunianmt/p/12727573.html
<!--devtools热部署引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
<scope>runtime</scope>
</dependency>
com.alibaba.druid.pool.DruidDataSource : {dataSource-1} closed
org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is com.alibaba.druid.pool.DataSourceClosedException: dataSource already closed at Fri Sep 18 14:07:08 CST 2020
Springboot项目启动reload:一直重复启动访问dataSource停不下来
Thread[flowable-event-change-detector-1] keeping write log and cannot stop it
存在问题:使用debug模式启动不断重启出现:
com.alibaba.druid.pool.DruidDataSource : {dataSource-0} closing,一直重启
(先clean后debug则正常,再次debug则重复启动)
取消application.yml:type: com.alibaba.druid.pool.DruidDataSource配置查看错误
参考:https://blog.csdn.net/haochaoguo1988/article/details/86666134
考虑是在appliaction.yml中mysql配置:
修改为oracle配置debug重启还是报错:Springboot默认使用的是HikariPool连接池
尝试多种方案还是无法解决,考虑是devtools配置的问题,将devtools关闭(enabled开关设置为false)则正常
使用jrebel开启则正常,如果程序运行过程中还是存在这个问题则clean在debug
# 生产模式取消热部署
devtools:
restart:
enabled: true #设置开启热部署
additional-paths: src/main/java #重启目录
exclude: WEB-INF/** # WEB-INF下的文件内容修改不重启
linux上部署:
【4】Springboot项目部署到linux
a.静态资源访问失败
(1)项目部署到linux静态资源无法访问
/usr/local/software/develop/tomcat/tomcat-aps/webapps/boxing/WEB-INF
将静态资源文件放置在项目中,启动测试项目能够正常访问
/** 启动类 **/
@MapperScan("com.sz.**.mapper")
@SpringBootApplication
public class BoxingApplication {
public static void main(String[] args) {
SpringApplication.run(BoxingApplication.class, args);
}
}
在pom.xml文件中配置packaging打包方式为war,此处需要注意的是配置了static访问路径,需要调整启动类配置,否则war部署到tomcat接口会报404
springBoot项目在IDEA中运行正常, 但是打包成war包部署到tomcat后, 接口不能访问,静态资源也不能访问.
重写application启动类的configure方法后则可
/** 启动类 **/
@MapperScan("com.sz.**.mapper")
@SpringBootApplication
public class BoxingApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(BoxingApplication.class, args);
}
// 不重写打包war部署到tomcat接口会报404
// 问题分析:(vue.config.js中module.exports配置publicPath:'/boxing/static'\''),原来没有加下述配置,通过‘’不能访问到index.html页面
// 后续项目部署到linux的时候发现访问404,加上下述配置后方能正常访问,也能通过''访问
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(BoxingApplication.class);
}
}
(2)linux项目启动访问静态资源后台报错
# 在pom.xml中配置build
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<!--解决vue部署在linux的tomcat下页面数据无法加载的问题-->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>woff</nonFilteredFileExtension>
<nonFilteredFileExtension>woff2</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>
b.项目部署catalina.out不停打印flowable日志造成日志不断积累
排查:
检查devtools配置在生产环境的影响
检查项目日志打印规则,发现项目自定义的日志文件中的日志打印并没有上述内容,而是在catalina.out中不断重复打印上述错误
检查tomcat进程:
ps -ef | grep tomcat
ps -ef | grep java
排查的时候发现:在一个tomcat下有多个tomcat进程,考虑是在同一个tomcat下放置了多个项目,而在部署或者更新项目的时候没有清理掉相关的进程(通过shutdown关闭tomcat没有清理掉进程),导致重复启动多个进程,而flowable项目在这个环境配置下则报错
解决方式:每次重新部署项目的时候检查tomcat进程,通过kill的方式清理掉tomcat相关进程,随后通过shutdown关闭tomcat,随后重启启动。启动后检查相应的tomcat进程
c.项目打包问题
clean-compile-package:打包出来的war,项目启动报错
直接执行clean->package操作
LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.impl.Log4jLoggerFactory loaded from file:/usr/local/software/develop/tomcat/tomcat-aps/webapps/boxing/WEB-INF/lib/slf4j-log4j12-1.7.30.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.slf4j.impl.Log4jLoggerFactory
linux部署项目报错:并非打包步骤问题,而是jar冲突导致
参考链接:https://blog.csdn.net/zhanggonglalala/article/details/88953345
日志框架冲突,取消
或者是springboot中去除: