注解@EnableAutoConfiguration :将该 App 启动类当成一个自动化可以支持配置的 bean ,并且能够开启整个工程类基于 springboot 的自动化配置。
允许 Spring Boot 自动配置注解,开启这个注解之后,Spring Boot 就能根据当前类路径下的包或者类来配置 Spring Bean。
如:当前类路径下有 Mybatis 这个 JAR 包,MybatisAutoConfiguration 注解就能根据相关参数来配置 Mybatis 的各个 Spring Bean。
Mybatis自动生成器使用方式
- 配置mybatis-generator-maven-plugin插件,要在maven的pom.xml的<build></build>节点内添加。
- mybatis-generator.xml配置文件可以去官网下载,下载地址为
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<classPathEntry location="E:\maven\newmaven\mavenjar\mysql\mysql-connector-java\5.1.35\mysql-connector-java-5.1.35.jar"/>
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- jdbc的数据库链接地址账号密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/e-commerce?serverTimezone=Asia/Shanghai"
userId="root"
password="root">
</jdbcConnection>
<!-- 生成DataObject(Model)类存放位置 -->
<!--Model模型生成器,用来生成含有主键key的类,记录类 以及查询Example类
targetPackage 指定生成的model生成所在的包名
targetProject 指定在该项目下所在的路径
-->
<javaModelGenerator targetPackage="org.example.dataobject" targetProject="src/main/java">
<!-- 是否允许子包,即targetPackage.schemaName.tableName -->
<property name="enableSubPackages" value="true" />
<!-- 是否对类CHAR类型的列的数据进行trim操作 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 生成映射文件存放的位置 -->
<!-- mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 -->
<sqlMapGenerator targetPackage="mapping" targetProject="src/main/resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- 生成Dao类存放位置 -->
<!--客户端代码,生成易于使用的针对Model对象和XML配置文件的代码
type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
-->
<javaClientGenerator type="XMLMAPPER" targetPackage="org.example.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 生成对应表及类名 -->
<table tableName="user_info" domainObjectName="UserDO" ></table>
<table tableName="user_password" domainObjectName="UserPasswordDO"></table>
</context>
</generatorConfiguration>
- 在集成 mybatis-generator 插件生成映射文件时一直报错,找了很多没有和我一样错误的,最后是跟着完成该项目的视频,将 pom.xml 中的增加的相关依赖的版本号都改成和视频一样的,最后成功创建了 dao 、dataobject 、mapping 包下的所有文件。
- IDEA报错:Could not autowire. No beans of 'UserDOMapper' type found.
报错并不影响启动类的运行,最终访问成功,如下所示:
① 通过添加注解@Repository解决该错误
②将@Autowired改为@Resource注解,一个是spring注解,一个是java自带的
使用SpringMVC方式开发用户信息
@Controller 只是定义了一个控制器类,而使用 @RequestMapping 注解的方法才是处理请求的处理器。
用 @RequestMapping 给出外界访问方法的路径。
用 @ResponseBody 标记 Controller 类中的方法,把 return 的结果变成 JSON对象返回。(如果没有这个注解,这个方法只能返回要跳转的路径即跳转的 html/JSP 页面。有这个注解,可以不跳转页面,只返回 JSON数据)
@Controller标识的类为控制器类(控制层/表现层)
控制层里面的每个方法,都可以去调用@Service标识的类(业务逻辑层)
@Service标识的类中的方法可以继续调用@Resposity标识的接口实现类(Dao层/持久层)
从controller调用service接口,service实现类调用mapper接口,mapper接口调用xml里面对应的sql方法。
dao层:定义XXMapper接口、对应的XXXMapper.xml在resource目录下的mapper文件夹里,使用数据模型DO(Data Object)
service层:定义XXXService接口、实现类XXXServicelmpl、领域模型Model
controller层:定义XXXController类、视图模型VO(View Object)
- Mybatis为我们自动生成的userPasswordDOMapper方法里面只有selectByPrimaryKey。但是业务逻辑是要求必须通过用户ID查询到对应的加密密码。所以需要改动对应的UserPasswordDOMapper.java以及UserPasswordDOMapper.xml两个文件。
//在UserPasswordDOMapper.xml文件中加入以下代码
<select id="selectByUserId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user_password
where user_id = #{userId,jdbcType=INTEGER}
</select>
- 以下将用于业务逻辑处理的领域模型返回给了前端用户;把用户加密的字符串可以在前端显示,也就是说在该系统设计中,任何一个攻击者可以通过遍历userId的方式拿到用户加密后的密码。
http://localhost:8090/user/get?id=1
访问后显示以下内容
{"id":1,"name":"第一个用户","gender":1,"age":30,"telphone":"13521234859","registerMode":"byphone","thirdPartyId":"","encrptPassword":"sdjsadhsajfkasfh"}
解决方式:添加一个UserVO类,和UserModel类差不多,但是把不必要给前端的信息消除掉了。并在UserController.java中修改成以下代码。
@GetMapping("/get")
@ResponseBody //将java对象转为json格式的数据
public UserVO getUser(@RequestParam(name = "id") Integer id) {
//调用service服务获取对应id的用户对象并返回给前端
UserModel userModel = userService.getUserById(id);
//将核心领域模型用户对象转化为可供UI使用的viewobject
return convertFromeModel(userModel);
}
private UserVO convertFromeModel(UserModel userModel){
if(userModel == null){
return null;
}
UserVO userVO = new UserVO();
BeanUtils.copyProperties(userModel,userVO);
return userVO;
}
dataobject(dto/do) 纯粹的对应数据库表字段建立对象
model (mo)可能由多个表字段组成的对象模型,方便业务处理
viewobject(vo) 用于给前端ui使用的对象模型,可以过滤掉一些敏感信息
- 在运行程序之前习惯性的install了一下,然后报了以下异常:这是由于多次创建代码导致的。在项目构建完毕后的初次使用的时候可以双击install,不要每次运行都这样!!!!
定义通用的返回对象
- 返回正确信息:新建CommonReturnType类,创建create方法,归一化Controller层的响应信息,避免错误时http状态码无用的信息,要返回给用户有意义的错误信息,采用返回状态加数据的格式。修改UserController.java中getUser方法的返回值,拿掉返回的不应该出现的数据。
//表明对应请求的返回处理结果"success"或"fail"
//通过status让前端判定,某个请求服务端有没有正确受理
private String status;
//若status=success,则data内返回前端需要的json数据
//若status=fail,则data内使用通用的错误码格式
private Object data;
//定义一个通用的创建方法,使用了方法重载的方式
//当controller完成了处理,然后调用对应的create方法。如果它不带任何status,参数则给出默认值"success"。
//返回正确信息
public static CommonReturnType create(Object result){
return CommonReturnType.create(result,"success");
}
//返回错误信息,这里的result为通用错误信息
public static CommonReturnType create(Object result,String status){
CommonReturnType type = new CommonReturnType();
type.setStatus(status);
type.setData(result);
return type;
}
UserController.java
@GetMapping("/get")
@ResponseBody //将java对象转为json格式的数据
//将返回值改为CommonReturnType
public CommonReturnType getUser(@RequestParam(name = "id") Integer id) {
//调用service服务获取对应id的用户对象并返回给前端
UserModel userModel = userService.getUserById(id);
//将核心领域模型用户对象转化为可供UI使用的viewobject
UserVO userVO = convertFromModel(userModel); //获得一个userVO对象
//返回通用对象
return CommonReturnType.create(userVO);
}
统一返回到前端的数据格式,无论后端代码是否报错,前端都应该收到一个status和data的json串结果,具体封装就在CommonReturnType这个类中,这样是为了更好的用户体验和方便前端人员去解析响应来返回页面。代码成功运行成功以及测试接口的页面如下:
- 返回错误信息:
包装器业务异常类实现:BusinessException和EmBusinessError共同继承CommonError的接口方法,以至于外部不仅可以通过new EmBusinessError或new BusinessException都可以有errCode、errMsg对应的组装定义,并且共同实现了一个setErrMsg()方法,可以用于将原本EmBusinessError中定义的errMsg给覆盖掉。
声明一个接口
public interface CommonError {
public int getErrCode();
public String getErrMsg();
public CommonError setErrMsg(String errMsg);
}
定义枚举类(实现CommonError中的接口方法)
public enum EmBusinessError implements CommonError{
//通用错误类型00001
PARAMETER_VALIDATION_ERROR(00001,"参数不合法"),
//10000开头为用户信息相关错误定义
USER_NOT_EXIST(10001,"用户不存在")
;
private int errCode; //错误码
private String errMsg; //错误信息
//定义构造函数
private EmBusinessError(int errCode,String errMsg){
this.errCode = errCode;
this.errMsg = errMsg;
}
@Override
public int getErrCode() {
return this.errCode;
}
@Override
public String getErrMsg() {
return this.errMsg;
}
//需要一个接口去改动errMsg
//以下通过定制化的方式去改动errMsg,并且return自己
//有了这个东西之后,要往对应的错误信息里面填值,只要在通用错误码无限填值就可以了
@Override
public CommonError setErrMsg(String errMsg) {
this.errMsg = errMsg;
return this;
}
}
通过Exception的方式来进行流程控制,程序出了任何跑不下去的异常,统一抛一个Exception,这个Exception会被最后的Controller层的一个SpringBoot的handler捕获并做一些处理。
//包装器业务异常类实现
public class BusinessException extends Exception implements CommonError {
//内部强关联对应的commonError
private CommonError commonError;
//直接接收EmBusinessError的传参用于构造业务异常
public BusinessException(CommonError commonError){
super(); //子类调用了父类的"无参数构造方法";对应父类Exception自身的一些初始化机制
this.commonError = commonError;
}
//接收自定义errMsg的方式构造业务异常
//接收CommonError的实现类EmBusinessError,并且自定义一个errMsg
public BusinessException(CommonError commonError,String errMsg){
super();
this.commonError = commonError;
this.commonError.setErrMsg(errMsg); //通过二次改写errMsg方式
}
@Override
public int getErrCode() {
return this.commonError.getErrCode(); //实现对应接口
}
@Override
public String getErrMsg() {
return this.commonError.getErrMsg();
}
@Override
public CommonError setErrMsg(String errMsg) {
this.commonError.setErrMsg(errMsg);
return this;
}
}
在UserController.java中getUser方法,添加获取用户信息不存在的代码。同时在该方法上面加上对应的标注throws BusinessException,因为该业务异常是让程序跑不下去的一个必须被catch的异常
因为将异常抛出去后,却没有对异常做任何处理,所以还是显示这种无意义的错误信息页面。
- 异常处理:
目前new出来的BusinessException直接被抛到了Tomcat的容器层,Tomcat的容器层对应处理这样一个异常的方式为返回500的错误页。
有没有一种机制可以拦截掉对应Tomcat的异常处理的方式,然后去解决掉对应的问题?
通过使用SpringBoot自带的一个SpringMVC的handlerException去解决一个通用的异常处理的方式
UserController.java
//定义exceptionhandler解决未被controller层吸收的exception(对于我们来说,BusinessException这种异常类其实为业务逻辑处理上的问题或业务逻辑错误而并非是服务端不能处理的错误,导致返回了一个500Internal Server Error的错误)
//对于web的系统来说,controller层的异常在某种意义来说是业务处理的最后一道关口。如果controller层的异常被处理掉,那么返回web前端之前会有一个很好的钩子(hook);不处理掉用户体验会变差。
@ExceptionHandler(Exception.class)//需要指明收到什么样的exception之后才会进入它的处理环节,此处定义为根类
@ResponseStatus(HttpStatus.OK)//捕获到controller抛出的exception,并返回HttpStatus.OK,即status=200
public Object handlerException(HttpServletRequest request,Exception ex){
CommonReturnType commonReturnType = new CommonReturnType();
commonReturnType.setStatus("fail");
commonReturnType.setData(ex);
return commonReturnType;
}
@ResponseStatus(HttpStatus.OK)的意义:
如果没有写这个,浏览器会收到500状态码,但是服务器是响应了我们的请求的,只不过没有找到对应的用户,也就是业务逻辑出了问题,但是服务器是正确的响应了发出的请求,不应该是500错误。
参考常见的HTTP状态码(HTTP Status Code)
https://blog.csdn.net/qq_35779969/article/details/80753197
调试后发现commonReturnType被完美的构造出来了
出现上方错误信息页面的原因是:handlerException使用这种方式处理(返回的Object会寻找本地页面文件),仅仅只能返回一个页面路径,无法处理UserVO类对应的@ResponseBody形式,加上@ResponseBody注解即可解决。
对应的data返回的是Exception异常类的反序列化的json方式
将 ex 强转为 BusinessException ,然后使用其 getErrCode、getErrMsg 方法获取前端需要的异常信息,将其封装为 Map 后封装到 CommonReturnType 对象中,然后再返回给前端。
UserController.java
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handlerException(HttpServletRequest request,Exception ex){
BusinessException businessException = (BusinessException)ex;
CommonReturnType commonReturnType = new CommonReturnType();
commonReturnType.setStatus("fail");
Map<String,Object> responseData = new HashMap<>();
responseData.put("errCode",businessException.getErrCode());
responseData.put("errMsg",businessException.getErrMsg());
commonReturnType.setData(responseData);
return commonReturnType;
}
优化:使用 CommonReturnType 的静态方法 create 构造对象并返回。
UserController.java
public Object handlerException(HttpServletRequest request,Exception ex){
BusinessException businessException = (BusinessException)ex;
Map<String,Object> responseData = new HashMap<>();
responseData.put("errCode",businessException.getErrCode());
responseData.put("errMsg",businessException.getErrMsg());
return CommonReturnType.create(responseData,"fail");
}
如果 ex 返回的不是 BusinessException 类型,我们该如何解决?
判断 exception 是否为 BusinessException 类型,如果不是则为 CommonReturnType 对象赋值 responseData 为 EmBusinessError 枚举中的 UNKNOWN_ERROR 的 errCode 和 errMsg。
UserController.java
//若获取的对应用户信息不存在
if(userModel == null){
userModel.setEncrptPassword("123"); //让程序抛出一个空指针,设置了一个不存在的对象
//throw new BusinessException(EmBusinessError.USER_NOT_EXIST);
}
public Object handlerException(HttpServletRequest request,Exception ex){
Map<String,Object> responseData = new HashMap<>();
//ex对象是BusinessException类的实例
if(ex instanceof BusinessException){
BusinessException businessException = (BusinessException)ex;
responseData.put("errCode",businessException.getErrCode());
responseData.put("errMsg",businessException.getErrMsg());
}else{
responseData.put("errCode",EmBusinessError.UNKNOWN_ERROR.getErrCode());
responseData.put("errMsg",EmBusinessError.UNKNOWN_ERROR.getErrMsg());
}
return CommonReturnType.create(responseData,"fail");
}
在json序列化的时候默认把int类型(00002)变成一个只有个位数的数字,修改一下策略
UserController.java中的handlerException方法,即异常公共处理的方式,其实是所有controller都想用的公用逻辑,因此将其抽象为 BaseController 中的方法,然后让其他 controller 组件去继承该 controller。
定义通用的返回对象总结:
- 定义一个 CommonReturnType,能够用对应的 status+data 的方式返回所有 json 序列化方式的固定对象,供前端解析使用,摒弃掉了使用 HTTP Status Code + 内线 tomcat 自带的 error 页面方式去处理响应数据以及异常。
- 并且定义了一个 common 的 BusinessErr(EmBusinessError)的方式 ,统一管理我们自定义的错误码。
- 然后,在 BaseController 中定义通用exceptionhandler类解决未被controller层吸收的exception,并且使用errCode和errMsg统一的定义方式吃掉了所有内部不可预知的异常。即定义了一个所有 Controller 的基类,使用其中注解了 @ExceptionHandler 的方法来处理所有被 Controller 层捕获的异常。按照异常的种类有2种处理方式,一种是自定义 BusinessException,其中有自定义的 error 的 code 和 msg;一种是未知的异常,采用统一处理方式。
- 该异常公共处理方法上还可以添加日志相关组件,方便项目运行记录与错误排查。
参考文章:
常见的HTTP状态码(HTTP Status Code)_不断前行的程序辕的博客-CSDN博客_httpstatuscode
SpringBoot构建电商基础秒杀项目课程视频: