一、内容说明
接着上一篇,SpringBoot2.x 教你快速入门,本篇内容我们来学习 SpringBoot2.X 进阶 Web 方面开发常用的一些知识点。
1.1、简介
步骤:
- 创建SpringBoot应用,选择相应的Starter
- 在配置文件中指定必要的少量配置
- 编写业务代码
Web开发的自动配置类:WebMvcAutoConfiguration
二、静态资源的映射
2.1、静态资源的位置
查看WebMvcAutoConfiguration——>getStaticLocations()
静态资源的默认位置:
- "classpath:/META-INF/resources/"
- "classpath:/resources/"
- "classpath:/static/"
- "classpath:/public/"
当然我们可以再配置文件中修改静态资源的路径:
代码语言:javascript复制# 指定静态资源的位置
spring.resources.static-locations=classpath:/static,classpath:/public
备注:static 下的静态资源图片以及静态html文件,通过浏览器可以直接访问到
2.2、欢迎页
将 favicon.ico 放到任意一个静态资源文件夹中即可!
三、表单验证
3.1、简介
前台提交一些表单时候,往往有一些字段内容需要我们校验一下,比如:姓名、密码、年龄、字段非空,字段长度限制,邮箱格式验证呀等等这些类型。当然前端也可以做一些校验,但后端如果也要做一些信息校验时候,我们如何来做呢?
3.2、校验相关的注解
代码语言:javascript复制@Null 只能是null
@NotNull 不能为null 注意用在基本类型上无效,基本类型有默认初始值
@AssertFalse 必须为false
@AssertTrue 必须是true
字符串/数组/集合检查:(字符串本身就是个数组)
@Pattern(regexp="reg") 验证字符串满足正则
@Size(max, min) 验证字符串、数组、集合长度范围
@NotEmpty 验证字符串不为空或者null
@NotBlank 验证字符串不为null或者trim()后不为空
数值检查:同时能验证一个字符串是否是满足限制的数字的字符串
@Max 规定值得上限int
@Min 规定值得下限
@DecimalMax("10.8") 以传入字符串构建一个BigDecimal,规定值要小于这个值
@DecimalMin 可以用来限制浮点数大小
@Digits(int1, int2) 限制一个小数,整数精度小于int1;小数部分精度小于int2
@Digits 无参数,验证字符串是否合法
@Range(min=long1,max=long2) 检查数字是否在范围之间
这些都包括边界值
日期检查:Date/Calendar
@Post 限定一个日期,日期必须是过去的日期
@Future 限定一个日期,日期必须是未来的日期
其他验证:
@Vaild 递归验证,用于对象、数组和集合,会对对象的元素、数组的元素进行一一校验
@Email 用于验证一个字符串是否是一个合法的右键地址,空字符串或null算验证通过
@URL(protocol=,host=,port=,regexp=,flags=) 用于校验一个字符串是否是合法URL
3.3、表单验证方法
这里简单举例来说明下,如何使用注解的方式来进行表单校验。
在实体 Bean 里需要校验的字段上面添加注解
代码语言:javascript复制package com.xmlvhy.girl.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* Author: 小莫
* Date: 2019-01-18 17:43
* Description:<描述>
*/
@Entity
@Data
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
//@Min(value = 18, message = "未成年禁止入内")
private Integer age;
@NotNull(message = "金钱不能为空")
private Integer money;
}
Controller 中接收参数时,使用 @Valid 注解进行校验
代码语言:javascript复制@PostMapping(value = "girls")
public Result <Girl> girlAdd(@Valid Girl girl, BindingResult result){
if (result.hasErrors()) {
return ResultUtil.fail(1,result.getFieldError().getDefaultMessage());
}
return ResultUtil.success(girlRepository.save(girl));
}
@Valid 和 BindingResult 是相对应的,如果有多个 @Valid,那么每个 @Valid 后面跟着的 BindingResult 就是这个 @Valid 的验证结果,顺序不能乱。
由于我们示例演示,返回都是json数据,这里定义了返回结果的类
定义返回结果的最外层实体类封装
代码语言:javascript复制package com.xmlvhy.girl.entity;
import lombok.Data;
/**
* Author: 小莫
* Date: 2019-01-25 15:43
* Description:<描述>
*/
@Data
public class Result<T> {
/*错误码*/
private Integer code;
/*提示信息*/
private String message;
/*具体的内容*/
private T data;
}
封装一个工具类
代码语言:javascript复制package com.xmlvhy.girl.util;
import com.xmlvhy.girl.entity.Result;
/**
* Author: 小莫
* Date: 2019-01-25 15:54
* Description:<描述>
*/
public class ResultUtil {
public static Result success(Object data){
Result result = new Result();
result.setCode(0);
result.setMessage("成功");
result.setData(data);
return result;
}
public static Result success(){
return success(null);
}
public static Result fail(Integer code, String message){
Result result = new Result();
result.setCode(code);
result.setMessage(message);
return result;
}
}
接下来我们来测试一下,这里我使用 postman 工具测试:
请求结果成功返回的情形:
请求结果失败的返回情形:
四、AOP 的使用
使用AOP统一处理请求日志。
4.1、什么是 AOP
代码语言:javascript复制1.AOP是一种编程方式
与语言无关,是一种程序设计思想
面向切面(AOP)Aspect Oriented Programming
面向对象(OOP)Object Oriented Programming
面向过程(POP)Procedure Oriented Programming
2.面向过程到面向对象
面向过程:假如下雨了,我打开了雨伞
面向对象:天气->下雨,我->打
3.换个角度看世界,换个姿势处理问题
4.将通用逻辑从业务逻辑中分离出来
通过一个简单的流程图,演示如何使用AOP去处理一个请求:
提取执行相同的代码为一个切面:
4.2、如何使用AOP
POM.xml 文件中,添加 aop 的依赖
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
编写一个切面通知类
代码语言:javascript复制package com.xmlvhy.girl.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* Author: 小莫
* Date: 2019-01-25 10:57
* Description:<描述>
*/
//定义一个切面
@Aspect
@Component
@Slf4j
public class HttpAspect {
//定义切点
@Pointcut("execution(public * com.xmlvhy.girl.controller.GirlController.*(..))")
public void log(){
}
//前置通知
//@Before("execution(public * com.xmlvhy.girl.controller.GirlController.*(..))")
@Before("log()")
public void doBefore(JoinPoint join){
//url
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
log.info("url= {}",request.getRequestURL());
//method
log.info("method= {}",request.getMethod());
//请求的ip
log.info("ip= {}",request.getRemoteAddr());
//类方法
log.info("class_method= {}", join.getSignature().getDeclaringTypeName() "." join.getSignature().getName());
//参数
log.info("args= {}",join.getArgs());
log.info("==============doBefore 所请求接口的方法执行之前执行============");
}
//后置通知
//@After("execution(public * com.xmlvhy.girl.controller.GirlController.*(..))")
@After("log()")
public void doAfter(){
log.info("==============doAfter 所请求接口的方法执行之后执行============");
}
//@AfterReturning可修饰AfterReturning增强处理,AfterReturning增强处理将在目标方法正常完成后被织入
@AfterReturning(pointcut = "log()",returning = "object")
public void doAfterReturning(Object object){
log.info("response= {}",object);
log.info("==============doAfterReturning 所请求接口的方法执行之后返回结果之前执行============");
}
}
4.3、常用的注解说明
代码语言:javascript复制@Aspect:声明当前类是一个切面处理类
@Component:声明当前类是一个Bean,由Spring的IOC容器进行管理
@Pointcut:声明需要处理的切点
spring aop 通知(advice)分成五类:
@Before:前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
@AfterReturning:正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
@AfterThrowing:异常返回通知[After throwing advice]:在连接点抛出异常后执行。
@After:返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
@Around:环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
4.4、定义一个接口
代码语言:javascript复制@GetMapping(value = "girls")
public List<Girl> girlList(){
log.info("get girlList");
return girlRepository.findAll();
}
使用 postman 访问一下,查看一下打印出的日志:
通过日志,我们可以清晰的看到相关方法的执行先后顺序。
五、全局异常处理
5.1、为什么要定义异常处理?
代码语言:javascript复制1、在框架层面封装checked exception,将其转化为unchecked exception,避免开发过程中编写繁冗的try...catch代码。
2、业务层面的开发,根据程序代码职责定义不同的RuntimeException(它就是unchecked exception,一般定义为RuntimeException的子类)
3、通过前两个观点,系统中自定义的异常将只存在unchecked exception,系统只在于客户端交换数据的上层,设置统一异常处理机制,并将一些异常转化为用户所能理解的信息传达给用户。
4、其他如业务层,数据持久层,等底层只负责将异常抛出即可,但要注意不要丢失掉异常堆栈(这一点是初学者容易犯的一个错误)。
5.2、什么是异常处理?
如果不加异常处理的话,程序出错了,用户可能不知道是啥原因。但加上异常处理后,用户能最快时间定位错误信息。例如,当一个SpringBoot 程序出现异常时,会默认的给出我们一个异常提示页面:Whitelabel Error Page。
但如果我们想要一些相对较友好的提示信息或页面,那么就需要我们进行全局的异常处理了。
- 定义错误码页面
- 定义异常通知
5.3、定义错误码页面的方式
创建错误状态码.html页面,放在templates/error目录中,当发生错误时会自动到该目录下查找对应的错误页面。
例如可以创建如4xx.html或5xx.html页面,用来匹配所有该类型的错误(会先进行精确匹配)
5.4、定义异常通知的方式
这里我们做一个简单的用例,获取某个人的年龄并判断,小于10 ,返回“你应该在上小学”,大于10且小于16 ,返回“你可能在上初中”。
我们先封装异常返回信息类:
定义一个枚举类:
代码语言:javascript复制package com.xmlvhy.girl.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Author: 小莫
* Date: 2019-01-25 18:59
* Description:<描述>
*/
@Getter
@AllArgsConstructor
public enum ResultEnum {
UNKNOWN_ERROR(-1,"未知错误"),
SUCCESS(100,"成功"),
PRIMARY_SCHOOL(100,"你可能在上小学"),
MIDDLE_SCHOOL(101,"你可能在上初中"),
;
private Integer code;
private String message;
}
定义一个自定义异常类:
代码语言:javascript复制package com.xmlvhy.girl.exception;
import com.xmlvhy.girl.enums.ResultEnum;
import lombok.Data;
/**
* Author: 小莫
* Date: 2019-01-25 18:36
* Description:<描述>
*/
@Data
public class GirlException extends RuntimeException {
private Integer code;
public GirlException(ResultEnum resultEnum) {
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
}
这里错误码以及异常信息,我们都统一定义在定义的枚举类中,这样看起来会比较清爽!另外,自定义的异常类,需要继承的RuntimeException 类而不是Exception 类,原因是:springboot 中只对 RuntimeException 类型进行捕获。
使用自定义异常:
代码语言:javascript复制@Override
public void getAge(Integer id) throws Exception {
if (girlRepository.findById(id).isPresent()) {
Girl girl = girlRepository.findById(id).get();
log.info("girl= {}",girl);
Integer age = girl.getAge();
if (age < 10) {
log.info("你还在上小学吧");
//throw new Exception("你还在上小学吧!");
//throw new GirlException(100,"你还在上小学吧!");
throw new GirlException(ResultEnum.PRIMARY_SCHOOL);
}else if(age > 10 && age < 16){
log.info("你可能在上中学");
//throw new Exception("你可能在上初中!");
//throw new GirlException(101,"你可能在上初中!");
throw new GirlException(ResultEnum.MIDDLE_SCHOOL);
}
}
}
定义一个全局异常处理类:
代码语言:javascript复制package com.xmlvhy.girl.exception;
import com.xmlvhy.girl.entity.Result;
import com.xmlvhy.girl.util.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Author: 小莫
* Date: 2019-01-25 16:33
* Description:<描述>
*/
//定义该类为全局异常捕获类
@ControllerAdvice
@Slf4j
public class ExceptionHandle {
//标记要捕获的异常
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result exceptionHandle(Exception e){
//判断异常是否是自定义 GirlException 异常的一个实例
if (e instanceof GirlException) {
GirlException girlException = (GirlException) e;
//log.info("[自定义异常] {}",girlException);
return ResultUtil.fail(girlException.getCode(),girlException.getMessage());
}else{
log.info("[系统异常] {}", e);
return ResultUtil.fail(-1, "未知错误");
}
}
}
Controller 中我们定义一个接口,来验证一下结果:
代码语言:javascript复制@GetMapping(value = "/girls/getAge/{id}")
public void getAge(@PathVariable("id") Integer id) throws Exception {
girlService.getAge(id);
}
首先我们先插入几条数据到数据库中:
同样使用 postman 工具来测试一下:
年龄小于10的情况:
年龄大于10小于16的情况:
出现系统异常而非自定义异常的情况:
以上,则完成全局异常的处理。
代码语言:javascript复制参考链接:
https://segmentfault.com/a/1190000008752288
本文涉及的相关 获取源码
本文作者: AI码真香
本文标题: SpringBoot 2.x 进阶 之 Web
本文网址: https://www.xmlvhy.com/article/70.html
版权说明: 自由转载-非商用-非衍生-保持署名 署名-非商业性使用4.0 国际 (CC BY-NC 4.0)