前面5篇Blog详细介绍了SpringBoot的实现原理,本篇Blog从实战的角度来看下SpringBoot开发时我们还需要做哪些工作,这里我选择了三个比较重要的模块来进行介绍,包括我们的数据验证,日志配置以及静态资源的映射,这篇博客结束后我们即将进入到SpringBoot的各种整合和集成的学习中,可以理解为SpringBoot基础学习的最后一篇吧。
JSR303数据校验
JSR 是 Java Specification Requests 的缩写,即 Java 规范提案。存在各种各样的 JSR,简单的理解为 JSR 是一种 Java 标准。JSR 303 就是数据检验的一个标准(Bean Validation (JSR 303))
为什么使用JSR303
处理一段业务逻辑,首先要确保数据输入的正确性,所以需要先对数据进行检查,保证数据在语义上的正确性,再根据数据进行下一步的处理。前端可以通过 js 程序校验数据是否合法,后端同样也需要进行校验。而后端最简单的实现就是直接在业务方法中对数据进行处理,但是不同的业务方法可能会出现同样的校验操作,这样就出现了数据的冗余。首先可以想象下在使用SR 303 规范之前Struts是如何对传入参数进行校验的,需要我们自己编写validate。
@Override
public void validate() {
if (username == null || username.length() < 4 || username.length() > 6) {
this.addActionError("用户名长度必须为4-6位");
this.addFieldError(username, "filed用户名长度必须为4-6位");
}
if (password.length() < 4 || password.length() > 6) {
this.addActionError("密码长度必须在4-6位之间");
} else if (repassword.length() < 4 || repassword.length() > 6) {
this.addActionError("确认密码长度必须在4-6位之间");
} else if (!password.equals(repassword)) {
this.addActionError("确认密码不等于密码");
}
if (age < 0) {
this.addActionError("年龄不能为负");
}
if (null == birthday) {
this.addActionError("出生日期不能为空");
}
if (null == graduate) {
this.addActionError("毕业日期不能为空");
}
if (!(null == birthday) && !(null == graduate)) {
Calendar c1 = Calendar.getInstance();
c1.setTime(birthday);
Calendar c2 = Calendar.getInstance();
c2.setTime(graduate);
if (c2.before(c1)) {
this.addActionError("毕业日期不能早于出生日期");
}
}
}
而JSR 303 使用 Bean Validation,即在 Bean 上添加相应的注解,去实现数据校验。这样在执行业务方法前,都会根据注解对数据进行校验,从而减少自定义的校验逻辑,减少代码冗余
JSR303常见参数
JSR303常见参数如下,在被检查项属性加注解即可。
我们最常用的一些检查项和示例如下:
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern(value) 验证 String 对象是否符合正则表达式的规则
JSR303使用实践
我们编写一个Controller传一些参数进行测试:
1 引入validation依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2 创建要校验的入参Model
这里我们可以注解一些信息,进行数据验证。
package com.example.springboot.model;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.springframework.stereotype.Component;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@Data
@Component
public class PersonDTO implements Serializable {
private static final long serialVersionUID = 281903912367009575L;
@NotBlank(message = "用户名不能为空")
private String username;
@Range(min = 15,max = 60,message = "年龄必须在15岁到60岁之间")
private String age;
@Email(message = "必须是合法的邮箱地址")
private String email;
}
3 创建PersonController
请求到来时只有数据都通过验证才打印
package com.example.springboot.controller;
import com.example.springboot.model.PersonDTO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
public class HelloSpringController {
@GetMapping ("/hello")
public String hello(@Valid @ModelAttribute PersonDTO personDTO) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String jsonStr = mapper.writeValueAsString(personDTO);
return jsonStr;
}
}
4 测试实现方式
我们请求一下,先填一个错的信息,由于没有前端信息处理错误页面,所以展示默认的, 由于没有前端信息处理错误页面,所以展示默认的,我们可以从控制台看输出的错误日志 然后我们按照要求填写入参则可以正确显示:
日志配置及输出
在项目开发中,日志十分的重要,不管是记录运行情况还是定位线上问题,都离不开对日志的分析。在 Java 领域里存在着多种日志框架,如 JCL、SLF4J、Jboss-logging、jUL、log4j、log4j2、logback 等等
日志框架的选择
市面上常见的日志框架有很多,它们可以被分为两类:日志门面(日志抽象层)和日志实现 通常情况下,日志由一个日志门面与一个日志实现组合搭建而成,Spring Boot 选用 SLF4J + Logback 的组合来搭建日志系统,SLF4J 是目前市面上最流行的日志门面,使用 Slf4j 可以很灵活的使用占位符进行参数占位,简化代码,拥有更好的可读性
SLF4J 的使用
在项目开发中,记录日志时不应该直接调用日志实现层的方法,而应该调用日志门面(日志抽象层)的方法。在使用 SLF4J 记录日志时,我们需要在应用中导入 SLF4J 及日志实现,并在记录日志时调用 SLF4J 的方法
package com.example.springboot.controller;
import com.example.springboot.model.PersonDTO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@Slf4j
public class HelloSpringController {
Logger logger = LoggerFactory.getLogger(HelloSpringController.class);
@GetMapping ("/hello")
public String hello(@Valid @ModelAttribute PersonDTO personDTO) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String jsonStr = mapper.writeValueAsString(personDTO);
logger.info(jsonStr);
return jsonStr;
}
}
请求时可以看到控制台的日志输出:
静态资源映射
在 Web 应用中会涉及到大量的静态资源,例如 JS、CSS 和 HTML 等。我们知道,Spring MVC 导入静态资源文件时,需要配置静态资源的映射,我在【Spring MVC学习笔记 三】深入实践Spring MVC控制器中提到过SpringMVC的处理方式。但在 SpringBoot 中则不再需要进行此项配置,因为 SpringBoot 已经默认完成了这一工作。
Spring Boot 默认为我们提供了 3 种静态资源映射规则:WebJars 映射;默认资源映射;静态首页(欢迎页)映射
WebJars 映射
为了让页面更加美观,让用户有更多更好的体验,Web 应用中通常会使用大量的 JS 和 CSS,例如 jQuery,Backbone.js 和 Bootstrap 等等。通常我们会将这些 Web 前端资源拷贝到 Java Web 项目的 webapp 相应目录下进行管理。但是 Spring Boot 项目是以 JAR 包的形式进行部署的,不存在 webapp 目录,那么 Web 前端资源该如何引入到 Spring Boot 项目中呢?WebJars 可以完美的解决上面的问题,它可以 Jar 形式为 Web 项目提供资源文件。
WebJars 可以将 Web 前端资源(JS,CSS 等)打成一个个的 Jar 包,然后将这些 Jar 包部署到 Maven 中央仓库中进行统一管理,当 Spring Boot 项目中需要引入 Web 前端资源时,只需要访问 WebJars 官网,找到所需资源的 pom 依赖,将其导入到项目中即可。
所有通过 WebJars 引入的前端资源都存放在当前项目类路径(classpath)下的/META-INF/resources/webjars/ 目录中,Spring Boot 通过 MVC 的自动配置类 WebMvcAutoConfiguration 为这些 WebJars 前端资源提供了默认映射规则,部分源码如下
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
在 Spring Boot 项目 的 pom.xml 中添加以下依赖,将 jquery 引入到该项目中
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
如下图所示: 启动 Spring Boot,浏览器访问http://localhost:8080/webjars/jquery/3.6.0/jquery.js访问 jquery.js
默认资源映射
当访问项目中的任意资源(即/** )时,Spring Boot 会默认从以下路径中查找资源文件(优先级依次降低):
classpath:/META-INF/resources/ classpath:/resources/ classpath:/static/ classpath:/public/
这些路径又被称为静态资源文件夹当我们请求某个静态资源(即以.html结尾的请求)时,Spring Boot 会先查找优先级高的文件夹,再查找优先级低的文件夹,直到找到指定的静态资源为止,在 项目的 src/main/resources 下的 static 目录中创建一个 hello.html,代码如下
<!DOCTYPE html>
<html lang="en">
<head></head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<body>
<h1>第一个SpringBoot静态资源</h1>
</body>
</html>
启动 Spring Boot,浏览器访问 http://localhost:8080/hello.html
静态首页(欢迎页)映射
静态资源文件夹下的所有 index.html 被称为静态首页或者欢迎页,它们会被 /** 映射,换句话说就是,当我们访问/ 或者/index.html 时,都会跳转到静态首页(欢迎页),在项目的 src/main/resources 下的 public 目录中创建一个 index.html,代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我是首页,也就是欢迎页,你一个看到的就是我</h1>
</body>
</html>
访问静态首页或欢迎页时,其查找顺序也遵循默认静态资源的查找顺序,即先查找优先级高的目录,在查找优先级低的目录,直到找到 index.html 为止
总结一下
这篇Blog从实战角度简单了解了下SpringBoot的一些常用操作手段,例如通过JSR303进行便捷的数据校验,通过日志配置及输出实现日志的打印,而这一切都已经给我们封装好了,静态资源映射也都固定好了目录和位置,可以说SpringBoot真的是为我们开发操碎了心,只要大概了解规则就能只关心业务的像个傻瓜一样开发了
|