- Spring Boot
- :用于确定每个月第几个星期几,只能出现在DayofWeek域。例如在4#2,表示在某月的第二个星期三
- 7.3 Quartz
- 三、日志
- 四、Web开发
- 五、Docker
- 4、Docker常用命令&操作
- 六、SpringBoot与数据访问
- 七、启动配置原理
- 八、自定义starter
- 更多SpringBoot整合示例
Spring Boot
1.概述
SpringBoot是对Spring框架的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
1.1 SpringBoot的特点
- 为基于Spring的开发提供更快的入门体验
- 开箱即用,没有代码生成,也无需XML配置。只需要少量配置即可构建一个
JAVA EE
项目- 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等
- 内置
Web
服务器,可以快速部署- SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring构建JavaEE项目的方式
2. 快速入门
利用IDEA工具通过Spring Initializr快速构建一个
SpringBoot
项目
2.1 Spring Initializr新建项目
点击下一步
配置好项目名称和jdk后点击下一步
勾选上Web
里面的Spring Web
依赖然后点击下一步
项目创建完成后的maven依赖如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
其中spring-boot-starter-parent是一个特殊的Starter,提供了一些maven的默认配置,同时还提供了dependency-management统一依赖管理,可以让开发者在引入其它依赖时不用输入版本号,方便依赖的管理,SpringBoot提供的starter很多,这些starter主要为第三方库提供的自动配置,例如spring-web依赖就是开发一个web项目必备的依赖。
2.2 编写启动类
@EnableAutoConfiguration
public class SpringBootDemoApplication {
public static void main(String[] args) {
System.out.println("http://localhost:3399");
}
}
- @EnableAutoConfiguration注解表示开启自动配置,因为添加了springweb依赖,所以使用此注解可以对spring和springmvc之间进行自动配置。在传统xml配置里需要配置大量的标签。
- 在main方法中通过SpringApplication中的run方法启动项目,参数一传入该类的.class,告诉spring主要的组件,参数二是运行时输入的其他参数。
2.3 编写控制器
在控制器指定一个/index接口,并在主启动类配置包扫描将控制器注册到spring容器中。
@RestController
public class IndexController {
@GetMapping("/index")
public String index() {
return "This is a SpringBoot Application";
}
}
启动类
@EnableAutoConfiguration
@ComponentScan //扫描所有的包到spring容器
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class);
System.out.println("http://localhost:3399");
}
}
上面的@EnableAutoConfiguration和@ComponentScan注解可以用一个组合注解代替,@SpringBootApplication,此注解默认覆盖了这两个注解的功能。
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class);
System.out.println("http://localhost:3399");
}
}
启动项目访问`http://localhost:3399/index`
3. SpringBoot基本配置
3.1 @SpringBootApplication
上面用到的@SpringBootApplication注解是一个组合注解,是Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,项目启动应该运行这个类的main方法来启动SpringBoot应用,源码如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
- @SpringBootConfiguration:Spring Boot的配置类,表示这是一个Spring Boot的配置类;
- @Configuration:配置类上来标注这个注解
- @Component:表示这是一个配置类,是容器中的一个组件;
@EnableAutoConfiguration:开启自动配置功能;
以前需要xml中配置的信息,Spring Boot可以自动配置;@**EnableAutoConfiguration**告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
3.2 自定义banner
SpringBoot项目在启动时会打印一个信息图标,如下图所示
上面这个图标可以通过自定义其它的内容显示,做法是在resources目录下创建一个banner.txt文件,在这个文件中写入文本,内容将会在项目启动时被打印出来,如果想替换各种艺术字则有以下几个网站可以定制。
- http://www.network-science.de/ascii/
- http://www.kammerl.de/asciiSignature.php
- http://patorjk.com/software/taag
如果想要关闭显示banner图标则可以在启动类设置。
SpringApplicationBuilder builder = new SpringApplicationBuilder(SpringBootDemoApplication.class);
builder.bannerMode(Banner.Mode.OFF).run(args);
3.3 Web容器配置
在SpringBoot项目中可以内置Tomcat、Jetty、Undertow、Netty等容器,当添加了springweb依赖时默认会使用tomcat作为Web服务器,可以在resources下的application.properties文件中进行其它配置。
3.3.1 常规配置
server.port=39 #配置项目访问端口号
server.error.path=/error #项目出错时跳转到error命名的页面
server.servlet.session.timeout=30m #设置session失效时间,30m为三十分钟
server.servlet.context-path=/hello #配置访问路径,默认为/
server.tomcat.uri-encoding=utf-8 #设置Tomcat请求编码
server.tomcat.basedir=/src/log #设置存放Tomcat运行日志和临时文件的目录
3.32 Jetty配置
将默认的Tomcat服务器切换为Jetty服务器,配置如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
首先排除Tomcat的starter依赖,然后再添加Jetty的依赖。启动项目查看控制台结果
3.4 properties配置
SpringBoot中采用大量的自动化配置,替换了繁杂的XML配置,但也需要少量的配置来满足项目的开发需求,这些配置通常写入到resources目录下的application.properties文件,或者application.yaml文件,这两种配置文件有着格式上的区别。
SpringBoot项目中的application.properties配置文件可以放置到以下四个路径位置
- 项目根目录下的config文件夹中
- 项目根目录下
- classpath下的config文件夹中
- classpath路径下
如果这四个位置都有properties文件则加载的时候按照优先级高低,上面从高到低分别为:1-2-3-4
3.5 类型安全配置属性
SpringBoot提供了@Value注解来将类中的属性通过配置注入数据,这样即使在数据量大的情况下,也可以方便的将配置文件中设置的数据注入到Bean中。
在properties中添加一段配置
user.name=admin
user.password=system
user.age=30;
新建一个类,将配置数据注入到Bean中
@Component
@ConfigurationProperties(prefix = "user")
public class User {
private String username;
private String password;
private Integer age;
//省略get&set方法
}
@ConfigurationProperties中的prefix属性表示要加载的配置类中指定的前缀,通过此前缀在配置文件中调用成员变量属性。
最后创建一个Controller测试
@Autowired
private User user; //注入user类
@GetMapping("/user")
public String user() {
return user.toString();
}
最后测试访问http://localhost:3399/user
地址
3.6 yaml配置
yaml是json的超集,是一种专门用来编写配置文件的语言,功能和properties文件一样,yaml采用缩进来表示层级的关系,并且大小写敏感,使用yaml文件只需要在resources目录下创建一个
application.yaml
文件。
3.6.1 语法示例
新建两个User实体类
@Data
@Component //注入到容器当中
@ConfigurationProperties(prefix = "user") //指定配置文件中对象的前缀
public class User implements Serializable {
private String username; //字符串类型
private Boolean showStatus; //布尔类型
private Date birthday; //日期类型
private Integer age; //数值类型
private Pet pet; //对象类型
private String[] hobby; //字符串数组
private List<String> pets; //List集合类型
private Map<String, Object> money; //Map集合类型
private Set<String> salaries; //Set集合类型
private Map<String, List<Pet>> allPets; //复杂Map集合类型
}
@Data
public class Pet {
private String name;
private Integer age;
}
- yaml语法配置
# 指定的suer前缀
user:
username: Ted #普通字符串类型
showStatus: true #布尔类型
birthday: 2020/12/30 22:22:22 #日期类型
age: 20 #数值类型
#对象类型配置
pet:
name: Bear
age: 3
#数组类型配置
hobby: [学习,睡觉]
#List集合类型配置
pets:
- Cat
- Dog
#Map集合类型
money:
ted:
bear: 20
dog: 30
cat: 40
#行内写法
admin: [1,2,3]
arthur: [deer: 30, horse: 40]
#Set集合类型
salaries: [2000,3000,4000]
#复杂Map集合类型
allPets:
big:
- {name: Bear}
- {name: elephant}
small: [{name: cat,age: 3}]
- 测试
@Autowired
User user; //注入user对象
@GetMapping("/user")
public User findAll() {
return user; //返回user对象
}
3.7 profile
为了替换不同开发环境的配置文件,springboot提供了profile表示配置环境,根据后缀配置不同的开发环境。
使用如下:
- 在resources目录下创建两个配置文件:application-dev.properties、application-prod.properties
- application-dev.properties
server.port=8080
- application-dev.properties
server.port=80
区分两个配置文件的端口号
- 接下来在application.properties文件指定开发的环境
spring.profiles.active=dev
上面这段内容表示指定并使用application-dev.properties配置文件中的内容,如果替换为prod,则是用application-prod.properties中的内容
- 在启动器中指定开发环境
SpringApplicationBuilder builder = new SpringApplicationBuilder(SpringBootDemoApplication.class);
builder.application().setAdditionalProfiles("dev");
builder.run(args);
4. Spring Boot整合视图层技术
分别整合Thymeleaf-FreeMarker两种视图层的技术
4.1 整合thymeleaf
- 添加thymeleaf依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 配置thymeleaf信息
#是否开启缓存,默认为true
spring.thymeleaf.cache=true
#文件编码
spring.thymeleaf.encoding=UTF-8
#模板文件位置
spring.thymeleaf.prefix=classpath:/templates/
#Content-Type配置
spring.thymeleaf.servlet.content-type=text/html
#模板文件后缀
spring.thymeleaf.suffix=.html
- 编写Controller测试
@GetMapping("/datas")
public String da(Model model) {
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
model.addAttribute("result",list);
return "data";
}
- 在templates目录下创建一个data.html视图
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${result[0]}"></div>
<div th:text="${result[1]}"></div>
</body>
</html>
视图层根据索引取两个添加的值,运行项目即可看到添加的内容
4.2 整合FreeMarker
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
- 编写FreeMarker配置
#是否开启缓存
spring.freemarker.cache=false
#文件编码
spring.freemarker.charset=UTF-8
#文件后缀
spring.freemarker.suffix=.ftl
#模板文件位置
spring.freemarker.template-loader-path=classpath:/templates/
- 编写Controller代码
@GetMapping("/datas")
public String da(Model model) {
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
model.addAttribute("result",list);
return "data";
}
- 创建一个.ftl后缀的视图文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>${result[0]}</div>
<div>${result[1]}</div>
</body>
</html>
5. SpringBoot整合Web开发
5.1 返回JSON数据
Json是目前主流的前后端数据传输方式,SpringMVC中使用的消息转换器HttpMessageConverter接口,对JSON的转换提供了支持。在SpringBoot中更进一步的对相关配置进行了简化。
在项目中测试返回json数据
添加web依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
这个依赖默认加入了jackson-databind作为JSON的处理器,此时不需要额外添加JSON的处理器就能返回一段JSON数据了。
- 创建User实体类
@Data
public class User {
private String username;
private String password;
private Double height;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthday;
private Integer age;
/*
省略get&set
*/
}
创建UserController测试
@RestController
public class UserController {
@GetMapping("/user")
public User user() {
User user = new User();
user.setUsername("Ted");
user.setPassword("123");
user.setHeight(180.5d);
user.setAge(20);
user.setBirthday(new Date());
return user;
}
}
@RestController是@ResponseBody和@Controller注解的组合,用于响应json数据到网页中
- 测试
启动项目访问http://localhost:8080/user
上面这种是采用SpringBoot默认的处理Json的方式,可以对字段忽略,日期格式化等需求可以通过注解实现。
5.2 自定义JSON
转换器
常见的JSON处理器除了jackson-databind之外,还有Gson和fastjson,这里采用常用的方法演示。
使用Gson转换器。
Gson是Google的一个开源JSON解析框架,使用Gson需要先去除SpringBoot默认的jackson-databind,然后加入Gson依赖
添加Gson依赖
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
SpringBoot中也默认提供了Gson的自动转换类`GsonHttpMessageConvertersConfiguration`,因此Gson的依赖添加成功后,就可以和默认的jackson-databind一样直接使用Gson了
- 自定义格式化处理类
处理对日期数据进行格式化的自定义转换代码
@Configuration
public class GsonConfigure {
@Bean //注册一个格式转换对象实例
public GsonHttpMessageConverter gsonHttpMessageConverter() {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
GsonBuilder builder = new GsonBuilder();
builder.setDateFormat("yyyy-MM-dd"); //设置日期格式化
Gson gson = builder.create(); //创建Gsno对象放入
converter.setGson(gson);
return converter;
}
}
- Controller层
@GetMapping("/user")
public User user() {
User user = new User();
user.setUsername("Ted");
user.setPassword("123");
user.setHeight(180.5d);
user.setAge(20);
user.setBirthday(new Date());
return user;
}
- 测试
启动项目访问http://localhost:8080/user
5.3 文件上传
SpringBoot中对文件上传做了更进一步的简化,使得文件上传更方便。
Java中文件上传一共涉及两个组件,一个是CommonsMultipartResolver
,另一个是StandardServletMultipartResolver
,其中CommonsMultipartResolver
是使用commons-fileupload
来处理multipart请求,而StandardServletMultipartResolver
则是基于Servlet 3.0来处理multipart请求。
5.3.1 文件上传功能实现
- 创建springboot项目添加web依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 在resource目录下创建upload.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upload</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="uploadFile" value="请选择文件">
<input type="submit" value="上传文件">
</form>
</body>
</html>
- 编写Controller层处理文件上传
package com.bear.springboot.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
@RestController
public class FileUploadController {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
@PostMapping("/upload")
public String upload(MultipartFile uploadFile, HttpServletRequest request) {
//指定文件上传路径为当前项目运行目录
String realPath = request.getSession().getServletContext().getRealPath("/uploadFile/");
//格式化日期
String format = sdf.format(new Date());
//判断是否是目录,不是就创建
File folder = new File(realPath + format);
if (!folder.isDirectory()) {
folder.mkdirs();
}
//获取原始文件名
String oldName = uploadFile.getOriginalFilename();
//使用随机数生成文件不同的名称。
String newName = UUID.randomUUID().toString()+oldName.substring(oldName.lastIndexOf("."),oldName.length());
try {
//调用transferTo方法进行文件上传操作
uploadFile.transferTo(new File(folder,newName));
//生成上传文件访问路径,并渲染到网页中。
return request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+"/uploadFile/"+format+newName;
} catch (IOException e) {
e.printStackTrace();
}
return "上传失败";
}
}
- 测试
访问`http://localhost:8080/upload.html` 上传文件,然后根据生成路径访问图片。
- 指定上传文件的配置
spring.servlet.multipart.enabled=true #是否开启上传文件的支持,默认为true
spring.servlet.multipart.file-size-threshold=0 #文件写入磁盘的阈值,默认为0
spring.servlet.multipart.location=D:\\file #文件临时保存的位置
spring.servlet.multipart.max-file-size=1MB #上传单个文件的最大大小。默认为1MB
spring.servlet.multipart.max-request-size=10MB #上传多文件的总大小,默认为10MB
spring.servlet.multipart.resolve-lazily=false #是否延迟解析,默认为false
5.4 全局异常处理
使用`@ControllerAdvice`注解进行全局的异常处理
ControllerAdvice
此注解通常用于处理项目的全局异常,全局异常就是只要项目中的任何一个类任何一行代码触发了全局异常配置类中的代码,则会报出定义的错误信息。- 此注解用于处理页面之间的跳转异常。
5.4.1 根据转发视图处理
- 下面定义一个全局处理算术的异常演示
1.引入所需的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 定义全局异常类
@ControllerAdvice
public class CustomException {
@ExceptionHandler(value = ArithmeticException.class)
public ModelAndView modelAndView(Exception e) {
ModelAndView mv = new ModelAndView();
mv.addObject("message", "执行了算术异常");
mv.setViewName("error"); //设置跳转的视图
return mv;
}
}
[@ExceptionHandler ](/ExceptionHandler ) 注解为指定要抛出的异常,如果抛出所有异常则调用父类Exception.class,这里演示算术异常
- error页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span th:text="${message}"></span>
</body>
在templates目录下建立一个error.html文件,并输出message信息。
- 编写控制器,定义一个错误代码
@Controller
public class ExceptionController {
@GetMapping("/index")
public void index() {
int a = 10/0;
}
}
在Controller中设定一个10除0的错误,以演示全局异常功能。
- 测试
访问http://localhost:8080/index 则会看到网页中抛出的自定义异常信息
5.4.2 根据返回字符串处理
直接在浏览器返回字符串,使用`@RestControllerAdvice`注解。
- 修改自定义异常类
@RestControllerAdvice
public class CustomException {
@ExceptionHandler(value = ArithmeticException.class)
public String error(Exception e) {
return "触发了算术异常" + e.getMessage();
}
}
直接定义一个String方法,并返回指定的异常信息。
- Controller
@Controller
public class ExceptionController {
@GetMapping("/index")
public void index() {
int a = 10/0;
}
}
5.5 注册拦截器
SpringMVC中的拦截器可以做到对项目运行之前、运行时、运行之后进行预处理。在SpringBoot中使用拦截器更加方便,只需要两个配置类即可对项目进行额外的处理。
在项目中配置拦截器
1.首先引入web依赖<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<dependency/>
- 编写自定义拦截器,并实现HandlerInterceptor接口
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle==>:执行了");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle==>:执行了");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion==>:执行了");
}
}
HandlerInterceptor接口中一共有三个方法,这三个方法会按照preHandle-Controller-postHandle-afterCompletion顺序执行,只有当第一个方法preHandle方法返回true时后面的方法才会执行,而postHandle方法则是当拦截器链内所有的拦截器返回成功后才会调用,afterCompletion则是只有preHandle方法返回true时才会调用。
3.配置拦截器拦截的资源。
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**")
.addPathPatterns("/hello")
.excludePathPatterns("/user");
}
}
这个配置类对应拦截器所配置的拦截资源,比如拦截路径、静态资源。
addInterceptors中的方法表示添加拦截的路径,excludePathPatterns表示排除的路径。
- 配置两个Controller接口
@RestController
public class Interceptor {
@GetMapping("/hello")
public String hello() {
return "hello";
}
@GetMapping("/user")
public String getUser() {
return "admin user";
}
}
提供一个hello、user路径的接口,由于在拦截器中配置了拦截/hello接口和排除了/user接口,所以当测试的时候访问/hello接口会触发拦截器中的方法,而访问/user接口则不会。
5.6 启动系统任务
有一些特殊的任务需要在启动器启动的时候执行,例如加载配置文件、数据库初始化等操作,springboot提供了两种方案,实现`ApplicationRunner`或`CommandLineRunner`接口,这两个接口基本一致,差别体现在参数上面。
5.6.1 CommandLineRunner
SpringBoot项目在启动时会遍历所有此接口的实现类,并调用其中的run方法,达到在项目启动时就执行的效果,如果配置了多个类继承CommandLineRunner接口则使用@Order注解指定任务执行的顺序
- 在项目中分别添加两个类继承CommandLineRunner接口
MyCommandLineRunner1
@Component
@Order(1)
public class MyCommandLineRunner1 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner1任务执行");
}
}
MyCommandLineRunner2
@Component
@Order(2)
public class MyCommandLineRunner2 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner2任务执行");
}
}
上面分别创建了两个类实现了CommandLineRunner接口,并使用@Component将类注入到spring容器中重写run方法打印一段信息,这些信息会在项目启动时在控制台打印。@Order注解是指定优先级顺序,数字越小优先级就越高,越先执行。
5.6.2 ApplicationRunner
5.7 整合Servlet、Filter、Listener
对传统web开发中的Servlet网页交互技术、filter过滤器、listener监听器整合到springboot项目当中
- 分别创建三个对应的类
- servlet
@WebServlet("/servlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getParameter("name"));
}
}
- Filter
@WebFilter("/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init方法执行");
}
@Override
public void destroy() {
System.out.println("destroy方法执行");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter方法执行");
filterChain.doFilter(request, response);
}
}
- Listener
@WebListener
public class MyListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("requestDestroyed方法执行");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("requestInitialized方法执行");
}
}
上面分别定义了对应三个组件,分别使用@WebServlet表示Servlet应用,@WebFilter表示过滤器应用,@Listener表示监听器应用进行表示。
- 在启动类中标上注解扫描Servlet 的应用
@SpringBootApplication
@ServletComponentScan //扫描servlet应用
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class);
}
}
- 测试
启动项目访问http://localhost:8080/servlet?name=ted 路径即可看到结果。
5.8 整合AOP功能
AOP是(Aspect Oriented Programming)的缩写,翻译为面向切面编程,AOP是通过预编译方式和运行期间基于动态代理实现程序功能的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑 的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时可提高开发的效率。
- AOP基本概念
- Joinpoint(连接点):类里面被增强的方法就成为连接点,比如修改哪个方法哪个方法即为连接点。
- Pointcut(切入点):对Joinpoint进行拦截的定义即为切入点,例如对某个方法进行拦截,这个定义即为切入点。
- Advice(通知):使对方法配置进行输出的方式,通知分为前置、后置、异常、最终、环绕通知
- Aspect(切面):Pointcut和Advice的结合
- Target(目标对象):增强的类称为Target
SpringBoot在原生Spring配置上做了简化的开发,省去了繁琐的XML配置。但需要导入额外的依赖
1.引入AOPstarter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 创建一个service供演示操作
package com.bear.springboot.service;
import org.springframework.stereotype.Service;
@Service //标识这是一个Service层的类,并注入到spring容器当中
public class UserService {
//模拟根据id获取用户
public String findUserById(int id) {
System.out.println(id);
return "获取" + id + "号用户";
}
//模拟删除用户
public void removeUserById(int id) {
System.out.println("删除了" + id + "号用户");
}
}
- 创建切面类,分别配置前置、后置、后置返回、后置异常、环绕通知方法。
package com.bear.springboot.log;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAspect {
//配置方法的切入点,为service包下的所有类。
@Pointcut("execution(* com.bear.springboot.service.*.*(..))")
public void log() {}
/**
* 配置前置通知
* @param joinPoint
*/
@Before(value = "log()") //value值为方法的切入点。
public void beforeAdvice(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法执行结束");
}
/**
* 配置后置通知
* @param joinPoint
*/
@After(value = "log()")
public void afterAdvice(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法执行结束");
}
/**
* 配置后置返回通知
* @param joinPoint
* @param result
*/
@AfterReturning(value = "log()",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result) {
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法的返回值为:" + result);
}
/**
* 配置后置异常通知
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = "log()",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint,Exception exception) {
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法抛出异常,异常为:" + exception);
}
/**
* 配置环绕通知
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("log()")
public Object aroudAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
}
在LogAspect类中一共定义了五种通知类型,首先类上的@Aspect注解表示这是一个AOP的切面类,然后在类中定义了一个log()方法,这个方法是用来指定类切入点用的,使用`execution`表达式去指定要影响的包名或类名,第一个_表示方法的任意返回值类型,第二个 _表示service包下的任意类都配置。
- @Before注解
这个注解表示前置通知,使用了该注解的方法会在方法执行前执行,通过`JoinPoint` 可以获取目标方法的方法名,修饰符等信息。
- @After
此注解和@Before注解相反,它表示后置通知,在方法执行后执行。
- @AfterReturning
此注解表示返回通知,在该方法执行后获取该方法的返回值,returning参数是指定返回值的变量名称,也就是对应的Object方法参数,在参数里定义了一个Object类型的变量,表示目标返回值可以是任意类型,如果参数类型为Long,则表示只能处理Long返回值的参数。
- @AfterThrowing
表示是一个后置异常类的通知,如果目标方法发生异常时则会触发该通知,形参类型指定为Exception,表示所有异常都会进入该方法中执行,如果指定其它子类异常类,则只会默认抛出指定的异常。
- @Around
此注解表示环绕型通知,环绕通知是所有通知功能里最强的功能,可以实现前置、后置通知、异常通知以及返回通知的功能。目标方法进入环绕通知后,通过调用ProceedingJoinPoint对象的proceed方法使目标方法继续执行,也可以修改目标方法的执行参数,返回值等。
5.9 自定义favicon
favicon.ico是浏览器左上角的显示图标,可以放在静态资源路径下或者类路径下,静态资源路径优先级要高于类路径下的favicon.ico
6. SpringBoot整合持久层技术
持久层技术是Java EE中对数据库进行访问操作的一种技术,SpringBoot对其持久层框架提供了自动化配置,例如JdbcTemplate、JPA等,MyBatis自动化配置则是Mybatis官方提供的。
6.1 整合JdbcTemplate
JdbcTemplate是Spring官方提供的一套jdbc模板框架,利用了AOP技术解决了原生jdbc大量重复代码的问题。
- 创建数据库和表
create database study;
use study;
create table user
(
id varchar(20) not null
primary key,
name varchar(20) null,
age varchar(20) null,
address varchar(20) null,
birth datetime null
);
insert into user value(1,'ted',"19","China",now())
- 创建SpringBoot项目添加以下依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
- 配置数据库连接信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT
username: root
password: system56
- 创建实体类
package com.bear.springboot.entities;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
public class User {
private String id;
private String name;
private String age;
private String address;
private Date birth;
}
由于添加了lombok依赖,可以使用@Data代替get&set方法
- 编写dao层
package com.bear.springboot.repository;
import com.bear.springboot.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int saveUser(User user) {
return jdbcTemplate.update("insert into user(id,name,age,address,birth) values(?,?,?,?,?)"
,user.getId(),user.getName(),user.getAge(),user.getAddress(),user.getBirth());
}
public int deleteUser(int id) {
return jdbcTemplate.update("delete from user where id = ?",id);
}
public int updateUser(User user) {
return jdbcTemplate.update("update user set name =?,age =?,address=?,birth=? where id =?"
,user.getName(),user.getAge(),user.getAddress(),user.getBirth(),user.getId());
}
public List<User> findAllByUser() {
return jdbcTemplate.query("select * from user",new BeanPropertyRowMapper<>(User.class));
}
}
注入使用JdbcTemplate类即可进行对数据库的操作,此类会被自动注册到Spring容器当中,以上dao层分别调用了update和query方法,update则对应增删改三种方式,而query则对应查询数据。在执行查询操作时,如果数据库字段和属性名称对应则直接使用BeanPropertyRowMapper对象,如果不对应则需要实现RowMapper接口将列字段和实体类字段一一对应。
- 创建Controller层调用dao层
package com.bear.springboot.controller;
import com.bear.springboot.entities.User;
import com.bear.springboot.repository.UserDao;
import com.bear.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserDao userDao;
@GetMapping("/crud")
public void saveUser() {
User user = new User();
user.setId("4");
user.setName("tony");
user.setAge("38");
user.setAddress("China");
user.setBirth(new Date());
System.out.println("添加数据==>"+userDao.saveUser(user));
User user2 = new User();
user2.setId("3");
user2.setName("teddy");
user2.setAge("20");
user2.setAddress("japan");
user2.setBirth(new Date());
System.out.println("修改数据==>"+userDao.updateUser(user2));
int result = userDao.deleteUser(4);
System.out.println("删除==>"+result);
List<User> userList = userDao.findAllByUser();
userList.forEach((item -> {
String value = item.toString();
System.out.println("查询所有数据==>"+value);
}));
}
}
- 测试
启动项目在浏览器地址栏输入http://localhost:8080/crud 即可看到控制台打印的内容
6.2 整合MyBatis
Mybatis是一款持久层框架,原名叫做iBatis。Mybatis支持定制化SQL,存储过程以及高级映射。不用像jdbc那样手动设置参数以及获取结果集,但需要配置大量的xml文件编写sql语句。在SpringBoot中由Mybatis官方提供了一套自动化配置方案。
- 创建springboot项目,添加以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
- 创建数据表
create database study;
use study;
create table user
(
id varchar(20) not null
primary key,
name varchar(20) null,
age varchar(20) null,
address varchar(20) null,
birth datetime null
);
insert into user value(1,'Bear',"19","China",now())
- 创建实体类
package com.bear.springboot.entities;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
public class User {
private String id;
private String name;
private String age;
private String address;
private Date birth;
}
- 配置yaml的mysql连接
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT
username: root
password: system56
- 创建Mapper接口,映射增删改查方法
package com.bear.springboot.repository;
import com.bear.springboot.entities.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
int saveUser(User user);
int updateUser(User user);
int deleteUser(int id);
List<User> findAllByUser();
}
@Mapper注解表示该接口是MyBatis中的Mapper。使用@Respository注解也可达到同样效果
- 编写sql映射的xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bear.springboot.repository.UserMapper">
<insert id="saveUser" parameterType="com.bear.springboot.entities.User">
insert into user(id,name,age,address,birth) values (#{id},#{name},#{age},#{address},#{birth});
</insert>
<update id="updateUser" parameterType="com.bear.springboot.entities.User">
update user set name=#{name},age=#{age}, address=#{address}, birth=#{birth} where id=#{id};
</update>
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id};
</delete>
<select id="findAllByUser" resultType="com.bear.springboot.entities.User">
select * from user ;
</select>
</mapper>
xml文件中的每个标签对应着增删改查的操作,标签体内则对应着对数据库进行操作的sql语句,#{}为占位符,用来获取实体类属性名对应数据库字段。
- 编写Controller
@GetMapping("/crud")
public void mybatisCrud() {
User user = new User();
user.setId("4");
user.setName("tony");
user.setAge("38");
user.setAddress("China");
user.setBirth(new Date());
System.out.println("添加数据==>"+userMapper.saveUser(user));
User user2 = new User();
user2.setId("3");
user2.setName("Bear");
user2.setAge("20");
user2.setAddress("China");
user2.setBirth(new Date());
System.out.println("修改数据==>"+userMapper.updateUser(user2));
int result = userMapper.deleteUser(4);
System.out.println("删除==>"+result);
List<User> userList = userMapper.findAllByUser();
userList.forEach((item -> {
String value = item.toString();
System.out.println("查询所有数据==>"+value);
}));
}
- 测试
访问http://localhost:8080/crud 地址查看控制台打印效果
6.3 整合Spring Data JPA
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发只用极简的代码即可实现对数据库的访问和操作。
- 创建数据库
create database `test` default character set utf8;
- JPA可以通过对象实体类进行对数据库的字段映射,这里只需创建一张空库即可
- 创建SpringBoot项目,导入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
- 配置yaml数据库信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT
username: root
password: system56
jpa:
hibernate:
ddl-auto: update #update表示根据实体类的变化更新数据库中的数据
- 新建一个实体类
package com.example.entity.relationship.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
private String email;
private String sex;
}
@Entity是javax.persistence包下的,表示该类是一个映射数据库的实体类,在项目启动时会根据该实体类自动生成一张数据表。
@Id注解表示id字段为数据表中的主键,@GeneratedValue注解表示主键自动生成,strategy则表示主键的生成策略
- 创建持久层接口,
package com.example.repository.OneToOne;
import com.example.entity.relationship.ManyToOne.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PersonRepository extends JpaRepository<Person,Long> {
//查询person表中以指定名字开头的数据
List<Person> findPersonByNameStartingWith(String name);
//查询年龄大于参数age的值
List<Person> getPersonByAgeGreaterThan(Integer age);
//查询年龄最大的人
@Query("select p from Person as p where p.age = (select max(age) from Person)")
Person findMaxAgePerson();
//查询id大于指定数值,并且name等于指定数值,
@Query("select p from Person as p where p.id>:id and p.name=:name")
List<Person> findPersonByIdAndName(@Param("id") Long id,@Param("name") String name);
//查询id小于指定数值,并且对name字段进行模糊查询
@Query("select p from Person as p where p.id <?1 and p.name like %?2%")
List<Person> findPersonsByIdAndName(Long id,String name);
}
数据层接口继承于JpaRepository接口,该接口内提供了一些基本的增删改查、分页查询、排序查询等方法。@Param注解则为id>:id为参数之间的绑定
- 创建service层接口
package com.example.service;
import com.example.entity.relationship.ManyToOne.Person;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PersonService {
//插入数据
void insertPerson(Person person);
//根据id删除
void deletePerson(Long id);
//更新数据
void updatePerson(Person person);
//分页查询所有
Page<Person> findPersonByPage(Pageable pageable);
//查询person表中以指定名字开头的数据
List<Person> findPersonByNameStartingWith(String name);
//查询年龄大于参数age的值
List<Person> findPersonByAgeGreaterThan(Integer age);
//查询年龄最大的人
Person findMaxAgePerson();
//查询id大于指定数值,并且name等于指定数值
List<Person> findPersonByIdAndName(Long id,String name);
//查询id小于指定数值,并且对name字段进行模糊查询
List<Person> findPersonsByIdAndName(Long id,String name);
}
//实现类
package com.example.service;
import com.example.entity.relationship.ManyToOne.Person;
import com.example.repository.OneToOne.PersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PersonServiceImpl implements PersonService{
@Autowired
private PersonRepository personRepository;
@Override
public void insertPerson(Person person) {
personRepository.save(person);
}
@Override
public void deletePerson(Long id) {
personRepository.deleteById(id);
}
@Override
public void updatePerson(Person person) {
personRepository.save(person);
}
@Override
public Page<Person> findPersonByPage(Pageable pageable) {
return personRepository.findAll(pageable);
}
@Override
public List<Person> findPersonByNameStartingWith(String name) {
return personRepository.findPersonByNameStartingWith(name);
}
@Override
public List<Person> findPersonByAgeGreaterThan(Integer age) {
return personRepository.getPersonByAgeGreaterThan(age);
}
@Override
public Person findMaxAgePerson() {
return personRepository.findMaxAgePerson();
}
@Override
public List<Person> findPersonByIdAndName(Long id, String name) {
return personRepository.findPersonByIdAndName(id,name);
}
@Override
public List<Person> findPersonsByIdAndName(Long id, String name) {
return personRepository.findPersonsByIdAndName(id,name);
}
}
在service层又提供了增删改查和一个分页的方法,默认只需要调用数据层接口继承的JpaRepository接口中的方法即可实现效果。
- Controller层
package com.example.controller;
import com.example.entity.relationship.ManyToOne.Person;
import com.example.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class PersonController {
@Autowired
private PersonService personService;
@GetMapping("/crud")
public String data() {
personService.insertPerson(new Person(1L,"ted",20,"123@qq.com","男"));
personService.updatePerson(new Person(1L,"Bear",19,"12345@qq.com","男"));
Page<Person> page = personService.findPersonByPage(PageRequest.of(2, 3));
System.out.println("总页数:==>"+ page.getTotalPages());
System.out.println("总记录数:==>"+ page.getTotalElements());
System.out.println("查询结果:==>"+ page.getContent());
System.out.println("当前页数:==>"+ page.getNumber() + 1);
System.out.println("当前页记录数:==>"+ page.getNumberOfElements());
System.out.println("每页记录数:==>"+ page.getSize());
//删除操作
personService.deletePerson(1L);
return "success";
}
@GetMapping(value = "/query")
public String querySQL() {
List<Person> list1 = personService.findPersonByIdAndName(1L, "Trump");
for (Person person : list1) {
String toString = person.toString();
System.out.println("list1 = " + toString);
}
List<Person> list2 = personService.findPersonByNameStartingWith("B");
for (Person person : list2) {
String toString = person.toString();
System.out.println("list2 = "+toString);
}
List<Person> list3 = personService.findPersonsByIdAndName(3L, "Bear");
for (Person person : list3) {
String toString = person.toString();
System.out.println("list3 = " + toString);
}
List<Person> list4 = personService.findPersonByAgeGreaterThan(18);
for (Person person : list4) {
String toString = person.toString();
System.out.println("list4 = " + toString);
}
Person person = personService.findMaxAgePerson();
System.out.println(person.getAge());
return "success";
}
}
在分页查询方法中调用的是PageRequest.of方法构造对象,of方法接收两个参数,第一个参数为页数,第二个参数为每页显示的数据条数。
- 测试
接下来访问http://localhost:8080/query 地址查看结果。
6.4 SpringBoot整合NoSQL
7. 定时任务
7.1 Timer
Timer是jdk提供的定时任务工具类,提供最基本的定时功能
- 示例
- 打印输出一段内容,定时为延迟10秒之后,每秒执行一次
首先需定义一个处理业务逻辑的类MyTimerTask
package com.renzhell.timed.timer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.TimerTask;
@EqualsAndHashCode(callSuper = true)
@Data
public class MyTimerTask extends TimerTask {
private String name;
public MyTimerTask(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("current name " + name);
}
}
其次是提供执行任务的Timer对象
package com.renzhell.timed.timer;
import java.util.Timer;
public class MyTimer {
public static void main(String[] args) {
// 1. 创建一个Timer对象实例
Timer timer = new Timer();
// 2. 创建一个MyTimerTask对象实例
MyTimerTask timerTask = new MyTimerTask("NO.1");
// 3. 通过timer对象调用MyTimerTask的业务逻辑
// 延迟十秒之后,每隔一秒钟执行一次
timer.schedule(timerTask, 10000L, 1000L);
}
}
7.1.1 Timer的定时调度函数
- schedule(task, time)
- task:安排的任务对象
- time:执行任务时间
- 作用:在时间等于或超过time的时候执行且仅执行一次
示例 ```java public class MyTimerTask extends TimerTask {
private String name;
public MyTimerTask(String name) {
this.name = name;
} @Override public void run() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 打印执行任务的当前时间
System.out.println("current execute time is: " + date.format(calendar.getTime()));
System.out.println("current name " + name);
} }
// 定时为当前时间的三秒之后执行任务,只执行一次 public static void test1() { Calendar calendar = Calendar.getInstance(); SimpleDateFormat date = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); System.out.println(date.format(calendar.getTime())); calendar.add(Calendar.SECOND, 3); Timer timer = new Timer(); MyTimerTask timerTask = new MyTimerTask(“schedule1”); timer.schedule(timerTask, calendar.getTime()); }
2. schedule(task, time, period)
1. task:安排的任务对象
1. time:执行任务时间
1. period:执行一次task的时间间隔,单位是毫秒。
1. 作用:时间等于或超过time时首次执行task、之后每隔period毫秒重复执行一次task
- 示例
```java
// 首次延迟三秒后执行,之后每隔2秒执行一次
public static void test2() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(date.format(calendar.getTime()));
calendar.add(Calendar.SECOND, 3); // 添加三秒的时间
Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask("schedule2");
timer.schedule(timerTask, calendar.getTime(), 2000);
}
- schedule(task, delay)
- task:安排的任务对象
- delay:执行任务前的延迟时间,单位是毫秒
- 作用:等待delay毫秒之后执行且执行一次task任务
- 示例
public static void test3() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(date.format(calendar.getTime()));
Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask("schedule1");
timer.schedule(timerTask, 2000); // 延迟2秒执行
}
- schedule(task, delay, period)
- task:安排的任务对象
- delay:执行任务前的延迟时间,单位是毫秒。
- period:执行一次task的间隔,单位是毫秒。
- 作用:等待delay毫秒后首次执行task、之后每隔period毫秒重复执行一次。
- 示例
// 等待三秒,之后每两秒执行一次任务
public static void test4() {
Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask("schedule4");
timer.schedule(timerTask, 3000, 2000);
}
两种额外的方法。
- scheduleAtFixedRate(task, time, period)
- task:安排的任务对象
- time:首次执行任务的时间
- period:执行一次task的间隔,单位是毫秒。
- 示例
// 第一次延迟三秒执行,之后的每一次都每隔两秒执行一次
public static void test5() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(date.format(calendar.getTime()));
calendar.add(Calendar.SECOND, 3);
Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask("schedule4");
timer.scheduleAtFixedRate(timerTask, calendar.getTime(), 2000);
}
- scheduleAtFixedRate(task, delay, period)
- task:安排的任务对象
- delay:执行任务前的延迟时间,单位是毫秒。
- period:执行一次task的间隔,单位是毫秒。
- 作用:等待delay毫秒后首次执行task、之后每隔period毫秒重复执行一次。
- 示例
// 第一次延迟三秒后执行,之后每隔两秒执行一次任务。
public static void test6() {
Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask("schedule4");
timer.scheduleAtFixedRate(timerTask, 3000, 2000);
}
7.1.2 Timer其它函数
- cancel():取消当前task的任务
示例: ```java public class MyTimerTask extends TimerTask {
private String name; private Integer count = 0; // 计数变量 public MyTimerTask(String name) {
this.name = name;
}
@Override public void run() {
if (count < 3) { // 小于3就继续执行
Calendar calendar = Calendar.getInstance();
SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("current execute time is: " + date.format(calendar.getTime()));
System.out.println("current name " + name);
count++;
} else {
cancel(); // 否则取消任务
System.out.println("Task Cancel");
}
} }
2. scheduledExecutionTime():返回此任务最近一次任务的执行时间
```java
public static void test7() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(date.format(calendar.getTime()));
calendar.add(Calendar.SECOND, 3);
Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask("schedule7");
timer.schedule(timerTask, 3000);
System.out.println(date.format(timerTask.scheduledExecutionTime()));
}
7.1.3 Timer综合应用
功能:实现两个机器人 第一个机器人会每隔两秒打印一次最近一次计划的时间、执行内容。 第二个机器人会模拟往桶里倒水,直到桶倒满为止。
DancingRobot
public class DancingRobot extends TimerTask {
@Override
public void run() {
// 获取最近的一次任务执行时间,并将其格式化
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Scheduled execution time is: " + sf.format(scheduledExecutionTime()));
System.out.println("Dancing happily");
}
}
WaterRobot
public class WaterRobot extends TimerTask {
private final Timer timer;
private Integer bucketCapacity = 0; // 最大容量为5L
public WaterRobot(Timer timer) {
this.timer = timer;
}
@Override
public void run() {
// 灌水直到桶满为止
if (bucketCapacity < 5) {
System.out.println("Add 1L water into the bucket!");
bucketCapacity++;
} else {
// 水满之后就停止执行
System.out.println("The number of canceled task in timer is: " + timer.purge());
cancel();
System.out.println("The waterRobot has been stopped!");
System.out.println("The number of canceled task in timer is: " + timer.purge());
System.out.println("The current water is" + bucketCapacity + "L");
// 等待两秒钟,终止timer里的所有内容
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
timer.cancel();
}
}
}
Executor
public class Executor {
public static void main(String[] args) {
Timer timer = new Timer();
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("Current time is: " + sf.format(calendar.getTime()));
DancingRobot dancingRobot = new DancingRobot();
WaterRobot waterRobot = new WaterRobot(timer);
timer.schedule(dancingRobot, calendar.getTime(), 2000);
timer.scheduleAtFixedRate(waterRobot, calendar.getTime(), 1000);
}
}
7.1.4 Timer的缺点
- 不能管理并发任务
- 任务抛出异常缺陷
- Timer只有一个线程去执行任务,如果存在多个任务且时间过长,会导致效果与预期不符
7.2 Spring Task
Spring Task是Spring官方提供的定时任务功能
示例代码
首先需要创建一个Springboot工程 ```java @EnableScheduling // 开启定时任务注解 @SpringBootApplication public class TimedTaskApplication {
public static void main(String[] args) { SpringApplication.run(TimedTaskApplication.class, args); }
/**
- 半小时执行一次 / // @Scheduled(fixedRate = 30 60 1000) // 半小时60一分钟六十秒 1000表示一毫秒 @Scheduled(fixedRate = 5 1000) // 5秒 * 1000表示一毫秒,五秒执行一次 public void drinkHotWater() { Calendar calendar = Calendar.getInstance(); SimpleDateFormat date = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); System.out.println(“多喝热水。。。” + date.format(calendar.getTime())); }
/**
- 两小时执行一次 / // @Scheduled(fixedRate = 2 60 60 1000) // 2小时60一小时六十分钟 60 一分钟六十秒 1000 表示一毫秒 @Scheduled(fixedRate = 10 1000) // 10 秒执行一次 public void haveMedicine() { Calendar calendar = Calendar.getInstance(); SimpleDateFormat date = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); System.out.println(“吃药。。。” + date.format(calendar.getTime())); }
}
<a name="fwtZ6"></a>
#### 7.2.1 cron表达式
> 通过手写表达式的方式来指定任务的执行时间
```java
@Scheduled(cron = "0 0/30 9-22 * * ?") // 从早上九点到晚上22点之间每隔30分钟执行一次
public void doSomething() {
System.out.println("do something");
}
@Scheduled(cron = "0 0 9-22/4 * * ?") // 从早上九点到晚上22点之间每隔四个小时执行一次
public void doSomethings() {
System.out.println("do something");
}
- 表达式符号解释
- ,(逗号):列出枚举值,例如5,20 表示在5分钟和20分钟的时间执行一次任务
- -(横杠):表示范围,例如5-20 表示5点-20点每分钟都执行任务
- (星号):表示匹配该区域的任意值,在Minutes域使用表示时间分钟数不作限制,每分钟都执行
- /(斜杠):起始时间开始触发,随后每隔固定时间触发一次。例如在Minutes域中使用5/20,表示时间分钟数为5时触发一次,随后20分钟即25、45时分别再触发一次。
- 专有符号?:只能设置在月或者周,并且只能设置一个
- L:表示最后的时间,只能出现在DayofWeek和DayofMonth域。如果使用5L表示则会在最后一个星期四触发任务,第一天是按照星期天计算
- W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近有效工作日触发任务
- 开启@EnableAsync注解
- 添加@Async注解。
7.3 Quartz
Quartz API的关键接:
- Scheduler - 与任务调度程序交互的主要API。
- Job - 你想要调度器执行的任务组件需要实现的接口
- JobDetail - 用于定义任务作业的实例。
- Trigger(即触发器) - 定义执行给定作业的计划的组件。
- JobBuilder - 用于定义/构建 JobDetail 实例,用于定义作业的实例。
- TriggerBuilder - 用于定义/构建触发器实例。
- Scheduler 的生命期,从 SchedulerFactory 创建它时开始,到 Scheduler 调用shutdown() 方法时结束Scheduler 被创建后,可以增加、删除和列举 Job 和 Trigger,以及执行其它与调度相关的操作(如暂停 Trigger)。但是,Scheduler 只有在调用 start() 方法后,才会真正地触发 trigger(即执行 job)。
三、日志
1、日志框架
小张;开发一个大型系统;
1、System.out.println("");将关键数据打印在控制台;去掉?写在一个文件?
2、框架来记录系统的一些运行时信息;日志框架 ; zhanglogging.jar;
3、高大上的几个功能?异步模式?自动归档?xxxx? zhanglogging-good.jar?
4、将以前框架卸下来?换上新的框架,重新修改之前相关的API;zhanglogging-prefect.jar;
5、JDBC---数据库驱动;
写了一个统一的接口层;日志门面(日志的一个抽象层);logging-abstract.jar;
给项目中导入具体的日志实现就行了;我们之前的日志框架都是实现的抽象层;
市面上的日志框架;
JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j….
日志门面 (日志的抽象层) | 日志实现 |
---|---|
Log4j JUL(java.util.logging) Log4j2 Logback |
左边选一个门面(抽象层)、右边来选一个实现;
日志门面: SLF4J;
日志实现:Logback;
SpringBoot:底层是Spring框架,Spring框架默认是用JCL;‘
**SpringBoot选用 SLF4j和logback;**
2、SLF4j使用
1、如何在系统中使用SLF4j https://www.slf4j.org
以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;
给系统里面导入slf4j的jar和 logback的实现jar
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
图示;
每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;
2、遗留问题
a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx
统一日志记录,即使是别的框架和我一起统一使用slf4j进行输出?
如何让系统中所有的日志都统一到slf4j;
1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现
3、SpringBoot日志关系
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
SpringBoot使用它来做日志功能;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
底层依赖关系
总结:
1)、SpringBoot底层也是使用slf4j+logback的方式进行日志记录
2)、SpringBoot也把其他的日志都替换成了slf4j;
3)、中间替换包?
@SuppressWarnings("rawtypes")
public abstract class LogFactory {
static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j";
static LogFactory logFactory = new SLF4JLogFactory();
4)、如果我们要引入其他框架?一定要把这个框架的默认日志依赖移除掉?
Spring框架用的是commons-logging;
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可;
4、日志使用;
1、默认配置
SpringBoot默认帮我们配置好了日志;
//记录器
Logger logger = LoggerFactory.getLogger(getClass());
@Test
public void contextLoads() {
//System.out.println();
//日志的级别;
//由低到高 trace<debug<info<warn<error
//可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效
logger.trace("这是trace日志...");
logger.debug("这是debug日志...");
//SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;root级别
logger.info("这是info日志...");
logger.warn("这是warn日志...");
logger.error("这是error日志...");
}
日志输出格式:
%d表示日期时间,
%thread表示线程名,
%-5level:级别从左显示5个字符宽度
%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
%msg:日志消息,
%n是换行符
-->
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
SpringBoot修改日志的默认配置
logging.level.com.atguigu=trace
#logging.path=
# 不指定路径在当前项目下生成springboot.log日志
# 可以指定完整的路径;
#logging.file=G:/springboot.log
# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件
logging.path=/spring/log
# 在控制台输出的日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
# 指定文件中日志输出的格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n
logging.file | logging.path | Example | Description |
---|---|---|---|
(none) | (none) | 只在控制台输出 | |
指定文件名 | (none) | my.log | 输出日志到my.log文件 |
(none) | 指定目录 | /var/log | 输出到指定目录的 spring.log 文件中 |
2、指定配置
给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的了
Logging System | Customization |
---|---|
Logback | logback-spring.xml , logback-spring.groovy , logback.xml or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
logback.xml:直接就被日志框架识别了;
logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot的高级Profile功能
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
可以指定某段配置只在某个环境下生效
</springProfile>
如:
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!--
日志输出格式:
%d表示日期时间,
%thread表示线程名,
%-5level:级别从左显示5个字符宽度
%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
%msg:日志消息,
%n是换行符
-->
<layout class="ch.qos.logback.classic.PatternLayout">
<springProfile name="dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern>
</springProfile>
</layout>
</appender>
如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误
no applicable action for [springProfile]
5、切换日志框架
可以按照slf4j的日志适配图,进行相关的切换;
slf4j+log4j的方式;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
<exclusion>
<artifactId>log4j-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
切换为log4j2
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
四、Web开发
1、简介
使用SpringBoot;
1)、创建SpringBoot应用,选中我们需要的模块;
2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
3)、自己编写业务代码;
自动配置原理?
这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx
xxxxAutoConfiguration:帮我们给容器中自动配置组件;
xxxxProperties:配置类来封装配置文件的内容;
2、SpringBoot对静态资源的映射规则;
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware {
//可以设置和静态资源有关的参数,缓存时间等
WebMvcAuotConfiguration:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(
registry.addResourceHandler("/webjars/**")
.addResourceLocations(
"classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
//静态资源文件夹映射
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
//配置欢迎页映射
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ResourceProperties resourceProperties) {
return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
//配置喜欢的图标
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration {
private final ResourceProperties resourceProperties;
public FaviconConfiguration(ResourceProperties resourceProperties) {
this.resourceProperties = resourceProperties;
}
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
//所有 **/favicon.ico
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
faviconRequestHandler()));
return mapping;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler
.setLocations(this.resourceProperties.getFaviconLocations());
return requestHandler;
}
}
1)、所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;
webjars:以jar包的方式引入静态资源;
localhost:8080/webjars/jquery/3.3.1/jquery.js
<!--引入jquery-webjar-->在访问的时候只需要写webjars下面资源的名称即可
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
2)、”/**” 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根路径
localhost:8080/abc === 去静态资源文件夹里面找abc
3)、欢迎页; 静态资源文件夹下的所有index.html页面;被”/**”映射;
localhost:8080/ 找index页面
4)、所有的 **/favicon.ico 都是在静态资源文件下找;
3、模板引擎
JSP、Velocity、Freemarker、Thymeleaf
SpringBoot推荐的Thymeleaf;
语法更简单,功能更强大;
1、引入thymeleaf;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
2.1.6
</dependency>
切换thymeleaf版本
<properties>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<!-- 布局功能的支持程序 thymeleaf3主程序 layout2以上版本 -->
<!-- thymeleaf2 layout1-->
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties>
2、Thymeleaf使用
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
//
只要我们把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染;
使用:
1、导入thymeleaf的名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
2、使用thymeleaf语法;
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>成功!</h1>
<!--th:text 将div里面的文本内容设置为 -->
<div th:text="${hello}">这是显示欢迎信息</div>
</body>
</html>
3、语法规则
1)、th:text;改变当前元素里面的文本内容;
th:任意html属性;来替换原生属性的值
2)、表达式?
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
1)、获取对象的属性、调用方法
2)、使用内置的基本对象:
#ctx : the context object.
#vars: the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
${session.foo}
3)、内置的一些工具对象:
#execInfo : information about the template being processed.
#messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
#uris : methods for escaping parts of URLs/URIs
#conversions : methods for executing the configured conversion service (if any).
#dates : methods for java.util.Date objects: formatting, component extraction, etc.
#calendars : analogous to #dates , but for java.util.Calendar objects.
#numbers : methods for formatting numeric objects.
#strings : methods for String objects: contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
#sets : methods for sets.
#maps : methods for maps.
#aggregates : methods for creating aggregates on arrays or collections.
#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
补充:配合 th:object="${session.user}:
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL;
@{/order/process(execId=${execId},execType='FAST')}
Fragment Expressions: ~{...}:片段引用表达式
<div th:insert="~{commons :: main}">...</div>
Literals(字面量)
Text literals: 'one text' , 'Another one!' ,…
Number literals: 0 , 34 , 3.0 , 12.3 ,…
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,…
Text operations:(文本操作)
String concatenation: +
Literal substitutions: |The name is ${name}|
Arithmetic operations:(数学运算)
Binary operators: + , - , * , / , %
Minus sign (unary operator): -
Boolean operations:(布尔运算)
Binary operators: and , or
Boolean negation (unary operator): ! , not
Comparisons and equality:(比较运算)
Comparators: > , < , >= , <= ( gt , lt , ge , le )
Equality operators: == , != ( eq , ne )
Conditional operators:条件运算(三元运算符)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:
No-Operation: _
4、SpringMVC自动配置
1. Spring MVC auto-configuration
Spring Boot 自动配置好了SpringMVC
以下是SpringBoot对SpringMVC的默认配置:(WebMvcAutoConfiguration)
- Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.- 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?))
- ContentNegotiatingViewResolver:组合所有的视图解析器的;
- 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;
- Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars
- Static
index.html
support. 静态首页访问 - Custom
Favicon
support (see below). favicon.ico - 自动注册了 of
Converter
,GenericConverter
,Formatter
beans.- Converter:转换器; public String hello(User user):类型转换使用Converter
Formatter
格式化器; 2017.12.17===Date;
@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")//在文件中配置日期格式化的规则
public Formatter<Date> dateFormatter() {
return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件
}
自己添加的格式化器转换器,我们只需要放在容器中即可
- Support for
HttpMessageConverters
(see below).- HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—-Json;
HttpMessageConverters
是从容器中确定;获取所有的HttpMessageConverter;
自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中(@Bean,@Component)
- Automatic registration of
MessageCodesResolver
(see below).定义错误代码生成规则 - Automatic use of a
ConfigurableWebBindingInitializer
bean (see below).
我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)初始化WebDataBinder;
请求数据=====JavaBean;
org.springframework.boot.autoconfigure.web:web的所有自动场景;
If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration
class of type WebMvcConfigurerAdapter
, but without @EnableWebMvc
. If you wish to provide custom instances of RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
or ExceptionHandlerExceptionResolver
you can declare a WebMvcRegistrationsAdapter
instance providing such components.
If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc
.
2、扩展SpringMVC
<mvc:view-controller path="/hello" view-name="success"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hello"/>
<bean></bean>
</mvc:interceptor>
</mvc:interceptors>
编写一个配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc ;
既保留了所有的自动配置,也能用我们扩展的配置;
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /atguigu 请求来到 success
registry.addViewController("/atguigu").setViewName("success");
}
}
原理:
1)、WebMvcAutoConfiguration是SpringMVC的自动配置类
2)、在做其他自动配置时会导入;@Import(**EnableWebMvcConfiguration**.class)
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//从容器中获取所有的WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用;
@Override
// public void addViewControllers(ViewControllerRegistry registry) {
// for (WebMvcConfigurer delegate : this.delegates) {
// delegate.addViewControllers(registry);
// }
}
}
}
3)、容器中所有的WebMvcConfigurer都会一起起作用;
4)、我们的配置类也会被调用;
效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
3、全面接管SpringMVC;
SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了
我们需要在配置类中添加@EnableWebMvc即可;
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /atguigu 请求来到 success
registry.addViewController("/atguigu").setViewName("success");
}
}
原理:
为什么@EnableWebMvc自动配置就失效了;
1)@EnableWebMvc的核心
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
2)、
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
3)、
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
//容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
4)、@EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
5)、导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;
5、如何修改SpringBoot的默认配置
模式:
1)、SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
2)、在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
3)、在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置
6、RestfulCRUD
1)、默认访问首页
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
//@EnableWebMvc 不要接管SpringMVC
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /atguigu 请求来到 success
registry.addViewController("/atguigu").setViewName("success");
}
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean //将组件注册在容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
};
return adapter;
}
}
2)、国际化
1)、编写国际化配置文件;
2)、使用ResourceBundleMessageSource管理国际化资源文件
3)、在页面使用fmt:message取出国际化内容
步骤:
1)、编写国际化配置文件,抽取页面需要显示的国际化消息
2)、SpringBoot自动配置好了管理国际化资源文件的组件;
@ConfigurationProperties(prefix = "spring.messages")
public class MessageSourceAutoConfiguration {
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
//我们的配置文件可以直接放在类路径下叫messages.properties;
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(this.basename)) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(this.basename)));
}
if (this.encoding != null) {
messageSource.setDefaultEncoding(this.encoding.name());
}
messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
messageSource.setCacheSeconds(this.cacheSeconds);
messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
return messageSource;
}
3)、去页面获取国际化的值;
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"/> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm">中文</a>
<a class="btn btn-sm">English</a>
</form>
</body>
</html>
效果:根据浏览器语言设置的信息切换了国际化;
原理:
国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties
.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
默认的就是根据请求头带来的区域信息获取Locale进行国际化
4)、点击链接切换国际化
/**
* 可以在连接上携带区域信息
*/
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String l = request.getParameter("l");
Locale locale = Locale.getDefault();
if(!StringUtils.isEmpty(l)){
String[] split = l.split("_");
locale = new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
3)、登陆
开发期间模板引擎页面修改以后,要实时生效
1)、禁用模板引擎的缓存
# 禁用缓存
spring.thymeleaf.cache=false
2)、页面修改完成以后ctrl+f9:重新编译;
登陆错误消息的显示
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
4)、拦截器进行登陆检查
拦截器
/**
* 登陆检查,
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
//目标方法执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if(user == null){
//未登陆,返回登陆页面
request.setAttribute("msg","没有权限请先登陆");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
//已登陆,放行请求
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
注册拦截器
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean //将组件注册在容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//super.addInterceptors(registry);
//静态资源; *.css , *.js
//SpringBoot已经做好了静态资源映射
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login");
}
};
return adapter;
}
5)、CRUD-员工列表
实验要求:
1)、RestfulCRUD:CRUD满足Rest风格;
URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
普通CRUD(uri来区分操作) | RestfulCRUD | |
---|---|---|
查询 | getEmp | emp—-GET |
添加 | addEmp?xxx | emp—-POST |
修改 | updateEmp?id=xxx&xxx=xx | emp/{id}—-PUT |
删除 | deleteEmp?id=1 | emp/{id}—-DELETE |
2)、实验的请求架构;
实验功能 | 请求URI | 请求方式 |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工(来到修改页面) | emp/1 | GET |
来到添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查出员工进行信息回显) | emp/1 | GET |
修改员工 | emp | PUT |
删除员工 | emp/1 | DELETE |
3)、员工列表:
thymeleaf公共页面元素抽取
1、抽取公共片段
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
2、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名
3、默认效果:
insert的公共片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}:
行内写法可以加上:[[~{}]];[(~{})];
三种引入公共片段的th属性:
th:insert:将公共片段整个插入到声明引入的元素中
th:replace:将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含进这个标签中
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
引入方式
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
效果
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
引入片段的时候传入参数:
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active"
th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}"
href="#" th:href="@{/main.html}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
Dashboard <span class="sr-only">(current)</span>
</a>
</li>
<!--引入侧边栏;传入参数-->
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
6)、CRUD-员工添加
添加页面
<form>
<div class="form-group">
<label>LastName</label>
<input type="text" class="form-control" placeholder="zhangsan">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" placeholder="zhangsan@atguigu.com">
</div>
<div class="form-group">
<label>Gender</label><br/>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input type="text" class="form-control" placeholder="zhangsan">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
提交的数据格式不对:生日:日期;
2017-12-12;2017/12/12;2017.12.12;
日期的格式化;SpringMVC将页面提交的值需要转换为指定的类型;
2017-12-12—-Date; 类型转换,格式化;
默认日期是按照/的方式;
7)、CRUD-员工修改
修改添加二合一表单
<!--需要区分是员工修改还是添加;-->
<form th:action="@{/emp}" method="post">
<!--发送put请求修改员工数据-->
<!--
1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
2、页面创建一个post表单
3、创建一个input项,name="_method";值就是我们指定的请求方式
-->
<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
<div class="form-group">
<label>LastName</label>
<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}">
</div>
<div class="form-group">
<label>Gender</label><br/>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<!--提交的是部门的id-->
<select class="form-control" name="department.id">
<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
</div>
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
</form>
8)、CRUD-员工删除
<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<td>[[${emp.lastName}]]</td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender}==0?'女':'男'"></td>
<td th:text="${emp.department.departmentName}"></td>
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
</td>
</tr>
<script>
$(".deleteBtn").click(function(){
//删除当前员工的
$("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit();
return false;
});
</script>
7、错误处理机制
1)、SpringBoot默认的错误处理机制
默认效果:
1)、浏览器,返回一个默认的错误页面
浏览器发送请求的请求头:
2)、如果是其他客户端,默认响应一个json数据

原理:
可以参照ErrorMvcAutoConfiguration;错误处理的自动配置;
给容器中添加了以下组件
1、DefaultErrorAttributes:
帮我们在页面共享信息;
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, requestAttributes);
addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
addPath(errorAttributes, requestAttributes);
return errorAttributes;
}
2、BasicErrorController:处理默认/error请求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = "text/html")//产生html类型的数据;浏览器发送的请求来到这个方法处理
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//去哪个页面作为错误页面;包含页面地址和页面内容
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
@RequestMapping
@ResponseBody //产生json数据,其他客户端来到这个方法处理;
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<Map<String, Object>>(body, status);
}
3、ErrorPageCustomizer:
@Value("${error.path:/error}")
private String path = "/error"; 系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
4、DefaultErrorViewResolver:
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认SpringBoot可以去找到一个页面? error/404
String errorViewName = "error/" + viewName;
//模板引擎可以解析这个页面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
//模板引擎可用的情况下返回到errorViewName指定的视图地址
return new ModelAndView(errorViewName, model);
}
//模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
return resolveResource(errorViewName, model);
}
步骤:
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被**BasicErrorController**处理;
1)响应页面;去哪个页面是由**DefaultErrorViewResolver**解析得到的;
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
//所有的ErrorViewResolver得到ModelAndView
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
2)、如果定制错误响应:
1)、如何定制错误的页面;
**1)、有模板引擎的情况下;error/状态码;** 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
2)、如何定制错误的json数据;
1)、自定义异常处理&返回定制json数据;
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
//没有自适应效果...
2)、转发到/error进行自适应响应效果处理
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
//传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
/**
* Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message",e.getMessage());
//转发到/error
return "forward:/error";
}
3)、将我们的定制数据携带出去;
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
自定义ErrorAttributes
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("company","atguigu");
return map;
}
}
最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容,
8、配置嵌入式Servlet容器
SpringBoot默认使用Tomcat作为嵌入式的Servlet容器;
问题?
1)、如何定制和修改Servlet容器的相关配置;
1、修改和server有关的配置(ServerProperties【也是EmbeddedServletContainerCustomizer】);
server.port=8081
server.context-path=/crud
server.tomcat.uri-encoding=UTF-8
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
2、编写一个EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定制器;来修改Servlet容器的配置
@Bean //一定要将这个定制器加入到容器中
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8083);
}
};
}
2)、注册Servlet三大组件【Servlet、Filter、Listener】
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。
注册三大组件用以下方式
ServletRegistrationBean
//注册三大组件
@Bean
public ServletRegistrationBean myServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
return registrationBean;
}
FilterRegistrationBean
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
ServletListenerRegistrationBean
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return registrationBean;
}
SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;
DispatcherServletAutoConfiguration中:
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
//默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp
//可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
2)、SpringBoot能不能支持其他的Servlet容器;
3)、替换为其他嵌入式Servlet容器
默认支持:
Tomcat(默认使用)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器;
</dependency>
Jetty
<!-- 引入web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--引入其他的Servlet容器-->
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
Undertow
<!-- 引入web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--引入其他的Servlet容器-->
<dependency>
<artifactId>spring-boot-starter-undertow</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
4)、嵌入式Servlet容器自动配置原理;
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置?
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
//导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件
//导入了EmbeddedServletContainerCustomizerBeanPostProcessor:
//后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作
public class EmbeddedServletContainerAutoConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })//判断当前是否引入了Tomcat依赖;
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
1)、EmbeddedServletContainerFactory(嵌入式Servlet容器工厂)
public interface EmbeddedServletContainerFactory {
//获取嵌入式的Servlet容器
EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers);
}
2)、EmbeddedServletContainer:(嵌入式的Servlet容器)
3)、以TomcatEmbeddedServletContainerFactory为例
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
//创建一个Tomcat
Tomcat tomcat = new Tomcat();
//配置Tomcat的基本环节
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
//将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并且启动Tomcat服务器
return getTomcatEmbeddedServletContainer(tomcat);
}
4)、我们对嵌入式容器的配置修改是怎么生效?
ServerProperties、EmbeddedServletContainerCustomizer
EmbeddedServletContainerCustomizer:定制器帮我们修改了Servlet容器的配置?
怎么修改的原理?
5)、容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor
//初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
if (bean instanceof ConfigurableEmbeddedServletContainer) {
//
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
this.beanFactory
//从容器中获取所有这葛类型的组件:EmbeddedServletContainerCustomizer
//定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
ServerProperties也是定制器
步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法
5)、嵌入式Servlet容器启动原理;
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;
获取嵌入式的Servlet容器工厂:
1)、SpringBoot应用启动运行run方法
2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext
3)、refresh(context);刷新刚才创建好的ioc容器;
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
4)、 onRefresh(); web的ioc容器重写了onRefresh方法
5)、webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();
6)、获取嵌入式的Servlet容器工厂:
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
从ioc容器中获取EmbeddedServletContainerFactory 组件;**TomcatEmbeddedServletContainerFactory**创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
7)、使用容器工厂获取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());
8)、嵌入式的Servlet容器创建对象并启动Servlet容器;
先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;
IOC容器启动创建嵌入式的Servlet容器
9、使用外置的Servlet容器
嵌入式Servlet容器:应用打成可执行的jar
优点:简单、便携;
缺点:默认不支持JSP、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);
外置的Servlet容器:外面安装Tomcat—-应用war包的方式打包;
步骤
1)、必须创建一个war项目;(利用idea创建好目录结构)
2)、将嵌入式的Tomcat指定为provided;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
3)、必须编写一个SpringBootServletInitializer的子类,并调用configure方法
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(SpringBoot04WebJspApplication.class);
}
}
4)、启动服务器就可以使用;
原理
jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;
servlet3.0(Spring注解版):
8.2.4 Shared libraries / runtimes pluggability:
规则:
1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:
2)、ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
3)、还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
流程:
1)、启动Tomcat
2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer:
Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer
3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set
4)、每一个WebApplicationInitializer都调用自己的onStartup;
5)、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
6)、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//1、创建SpringApplicationBuilder
SpringApplicationBuilder builder = createSpringApplicationBuilder();
StandardServletEnvironment environment = new StandardServletEnvironment();
environment.initPropertySources(servletContext, null);
builder.environment(environment);
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
builder = configure(builder);
//使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.getSources().add(getClass());
}
Assert.state(!application.getSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.getSources().add(ErrorPageFilterConfiguration.class);
}
//启动Spring应用
return run(application);
}
7)、Spring的应用就启动并且创建IOC容器
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新IOC容器
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
启动Servlet容器,再启动SpringBoot应用
五、Docker
1、简介
Docker是一个开源的应用容器引擎;是一个轻量级容器技术;
Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;
运行中的这个镜像称为容器,容器启动是非常快速的。
2、核心概念
docker主机(Host):安装了Docker程序的机器(Docker直接安装在操作系统之上);
docker客户端(Client):连接docker主机进行操作;
docker仓库(Registry):用来保存各种打包好的软件镜像;
docker镜像(Images):软件打包好的镜像;放在docker仓库中;
docker容器(Container):镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用
使用Docker的步骤:
1)、安装Docker
2)、去Docker仓库找到这个软件对应的镜像;
3)、使用Docker运行这个镜像,这个镜像就会生成一个Docker容器;
4)、对容器的启动停止就是对软件的启动停止;
3、安装Docker
1)、安装linux虚拟机
1)、VMWare、VirtualBox(安装);
2)、导入虚拟机文件centos7-atguigu.ova;
3)、双击启动linux虚拟机;使用 root/ 123456登陆
4)、使用客户端连接linux服务器进行命令操作;
5)、设置虚拟机网络;
桥接网络=选好网卡==接入网线;
6)、设置好网络以后使用命令重启虚拟机的网络
service network restart
7)、查看linux的ip地址
ip addr
8)、使用客户端连接linux;
2)、在linux虚拟机上安装docker
步骤:
1、检查内核版本,必须是3.10及以上
uname -r
2、安装docker
yum install docker
3、输入y确认安装
4、启动docker
[root@localhost ~]# systemctl start docker
[root@localhost ~]# docker -v
Docker version 1.12.6, build 3e8e77d/1.12.6
5、开机启动docker
[root@localhost ~]# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
6、停止docker
systemctl stop docker
4、Docker常用命令&操作
1)、镜像操作
操作 | 命令 | 说明 |
---|---|---|
检索 | docker search 关键字 eg:docker search redis | 我们经常去docker hub上检索镜像的详细信息,如镜像的TAG。 |
拉取 | docker pull 镜像名:tag | :tag是可选的,tag表示标签,多为软件的版本,默认是latest |
列表 | docker images | 查看所有本地镜像 |
删除 | docker rmi image-id | 删除指定的本地镜像 |
2)、容器操作
软件镜像(QQ安装程序)——运行镜像——产生一个容器(正在运行的软件,运行的QQ);
步骤:
1、搜索镜像
[root@localhost ~]# docker search tomcat
2、拉取镜像
[root@localhost ~]# docker pull tomcat
3、根据镜像启动容器
docker run --name mytomcat -d tomcat:latest
4、docker ps
查看运行中的容器
5、 停止运行中的容器
docker stop 容器的id
6、查看所有的容器
docker ps -a
7、启动容器
docker start 容器id
8、删除一个容器
docker rm 容器id
9、启动一个做了端口映射的tomcat
[root@localhost ~]# docker run -d -p 8888:8080 tomcat
-d:后台运行
-p: 将主机的端口映射到容器的一个端口 主机端口:容器内部的端口
10、为了演示简单关闭了linux的防火墙
service firewalld status ;查看防火墙状态
service firewalld stop:关闭防火墙
11、查看容器的日志
docker logs container-name/container-id
更多命令参看
https://docs.docker.com/engine/reference/commandline/docker/
可以参考每一个镜像的文档
3)、安装MySQL示例
docker pull mysql
错误的启动
[root@localhost ~]# docker run --name mysql01 -d mysql
42f09819908bb72dd99ae19e792e0a5d03c48638421fa64cce5f8ba0f40f5846
mysql退出了
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
42f09819908b mysql "docker-entrypoint.sh" 34 seconds ago Exited (1) 33 seconds ago mysql01
538bde63e500 tomcat "catalina.sh run" About an hour ago Exited (143) About an hour ago compassionate_
goldstine
c4f1ac60b3fc tomcat "catalina.sh run" About an hour ago Exited (143) About an hour ago lonely_fermi
81ec743a5271 tomcat "catalina.sh run" About an hour ago Exited (143) About an hour ago sick_ramanujan
//错误日志
[root@localhost ~]# docker logs 42f09819908b
error: database is uninitialized and password option is not specified
You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD;这个三个参数必须指定一个
正确的启动
[root@localhost ~]# docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
b874c56bec49fb43024b3805ab51e9097da779f2f572c22c695305dedd684c5f
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b874c56bec49 mysql "docker-entrypoint.sh" 4 seconds ago Up 3 seconds 3306/tcp mysql01
做了端口映射
[root@localhost ~]# docker run -p 3306:3306 --name mysql02 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
ad10e4bc5c6a0f61cbad43898de71d366117d120e39db651844c0e73863b9434
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ad10e4bc5c6a mysql "docker-entrypoint.sh" 4 seconds ago Up 2 seconds 0.0.0.0:3306->3306/tcp mysql02
几个其他的高级操作
docker run --name mysql03 -v /conf/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
把主机的/conf/mysql文件夹挂载到 mysqldocker容器的/etc/mysql/conf.d文件夹里面
改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下(/conf/mysql)
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
指定mysql的一些配置参数
六、SpringBoot与数据访问
1、JDBC
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.15.22:3306/jdbc
driver-class-name: com.mysql.jdbc.Driver
效果:
默认是用org.apache.tomcat.jdbc.pool.DataSource作为数据源;
数据源的相关配置都在DataSourceProperties里面;
自动配置原理:
org.springframework.boot.autoconfigure.jdbc:
1、参考DataSourceConfiguration,根据配置创建数据源,默认使用Tomcat连接池;可以使用spring.datasource.type指定自定义的数据源类型;
2、SpringBoot默认可以支持;
org.apache.tomcat.jdbc.pool.DataSource、HikariDataSource、BasicDataSource、
3、自定义数据源类型
/**
* Generic DataSource configuration.
*/
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {
@Bean
public DataSource dataSource(DataSourceProperties properties) {
//使用DataSourceBuilder创建数据源,利用反射创建响应type的数据源,并且绑定相关属性
return properties.initializeDataSourceBuilder().build();
}
}
4、DataSourceInitializer:ApplicationListener;
作用:
1)、runSchemaScripts();运行建表语句;
2)、runDataScripts();运行插入数据的sql语句;
默认只需要将文件命名为:
schema-*.sql、data-*.sql
默认规则:schema.sql,schema-all.sql;
可以使用
schema:
- classpath:department.sql
指定位置
5、操作数据库:自动配置了JdbcTemplate操作数据库
2、整合Druid数据源
导入druid数据源
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid(){
return new DruidDataSource();
}
//配置Druid的监控
//1、配置一个管理后台的Servlet
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String,String> initParams = new HashMap<>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("allow","");//默认就是允许所有访问
initParams.put("deny","192.168.15.21");
bean.setInitParameters(initParams);
return bean;
}
//2、配置一个web监控的filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
3、整合MyBatis
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
步骤:
1)、配置数据源相关属性(见上一节Druid)
2)、给数据库建表
3)、创建JavaBean
4)、注解版
//指定这是一个操作数据库的mapper
@Mapper
public interface DepartmentMapper {
@Select("select * from department where id=#{id}")
public Department getDeptById(Integer id);
@Delete("delete from department where id=#{id}")
public int deleteDeptById(Integer id);
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into department(departmentName) values(#{departmentName})")
public int insertDept(Department department);
@Update("update department set departmentName=#{departmentName} where id=#{id}")
public int updateDept(Department department);
}
问题:
自定义MyBatis的配置规则;给容器中添加一个ConfigurationCustomizer;
@org.springframework.context.annotation.Configuration
public class MyBatisConfig {
@Bean
public ConfigurationCustomizer configurationCustomizer(){
return new ConfigurationCustomizer(){
@Override
public void customize(Configuration configuration) {
configuration.setMapUnderscoreToCamelCase(true);
}
};
}
}
使用MapperScan批量扫描所有的Mapper接口;
@MapperScan(value = "com.atguigu.springboot.mapper")
@SpringBootApplication
public class SpringBoot06DataMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBoot06DataMybatisApplication.class, args);
}
}
5)、配置文件版
mybatis:
config-location: classpath:mybatis/mybatis-config.xml 指定全局配置文件的位置
mapper-locations: classpath:mybatis/mapper/*.xml 指定sql映射文件的位置
更多使用参照
http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
4、整合SpringData JPA
1)、SpringData简介
2)、整合SpringData JPA
JPA:ORM(Object Relational Mapping);
1)、编写一个实体类(bean)和数据表进行映射,并且配置好映射关系;
//使用JPA注解配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tbl_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user;
public class User {
@Id //这是一个主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
private Integer id;
@Column(name = "last_name",length = 50) //这是和数据表对应的一个列
private String lastName;
@Column //省略默认列名就是属性名
private String email;
2)、编写一个Dao接口来操作实体类对应的数据表(Repository)
//继承JpaRepository来完成对数据库的操作
public interface UserRepository extends JpaRepository<User,Integer> {
}
3)、基本的配置JpaProperties
spring:
jpa:
hibernate:
# 更新或者创建数据表结构
ddl-auto: update
# 控制台显示SQL
show-sql: true
七、启动配置原理
几个重要的事件回调机制
配置在META-INF/spring.factories
ApplicationContextInitializer
SpringApplicationRunListener
只需要放在ioc容器中
ApplicationRunner
CommandLineRunner
启动流程:
1、创建SpringApplication对象
initialize(sources);
private void initialize(Object[] sources) {
//保存主配置类
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//判断当前是否一个web应用
this.webEnvironment = deduceWebEnvironment();
//从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//从多个配置类中找到有main方法的主配置类
this.mainApplicationClass = deduceMainApplicationClass();
}
2、运行run方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
//获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories
SpringApplicationRunListeners listeners = getRunListeners(args);
//回调所有的获取SpringApplicationRunListener.starting()方法
listeners.starting();
try {
//封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成
Banner printedBanner = printBanner(environment);
//创建ApplicationContext;决定创建web的ioc还是普通的ioc
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
//准备上下文环境;将environment保存到ioc中;而且applyInitializers();
//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
//回调所有的SpringApplicationRunListener的contextPrepared();
//
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
//s刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);Spring注解版
//扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
refreshContext(context);
//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
//ApplicationRunner先回调,CommandLineRunner再回调
afterRefresh(context, applicationArguments);
//所有的SpringApplicationRunListener回调finished方法
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//整个SpringBoot应用启动完成以后返回启动的ioc容器;
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
3、事件监听机制
配置在META-INF/spring.factories
ApplicationContextInitializer
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer...initialize..."+applicationContext);
}
}
SpringApplicationRunListener
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
//必须有的构造器
public HelloSpringApplicationRunListener(SpringApplication application, String[] args){
}
@Override
public void starting() {
System.out.println("SpringApplicationRunListener...starting...");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
Object o = environment.getSystemProperties().get("os.name");
System.out.println("SpringApplicationRunListener...environmentPrepared.."+o);
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...contextPrepared...");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...contextLoaded...");
}
@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("SpringApplicationRunListener...finished...");
}
}
配置(META-INF/spring.factories)
org.springframework.context.ApplicationContextInitializer=\
com.atguigu.springboot.listener.HelloApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=\
com.atguigu.springboot.listener.HelloSpringApplicationRunListener
只需要放在ioc容器中
ApplicationRunner
@Component
public class HelloApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner...run....");
}
}
CommandLineRunner
@Component
public class HelloCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...run..."+ Arrays.asList(args));
}
}
八、自定义starter
starter:
1、这个场景需要使用到的依赖是什么?
2、如何编写自动配置
@Configuration //指定这个类是一个配置类
@ConditionalOnXXX //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter //指定自动配置类的顺序
@Bean //给容器中添加组件
@ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置
@EnableConfigurationProperties //让xxxProperties生效加入到容器中
自动配置类要能加载
将需要启动就加载的自动配置类,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
3、模式:
启动器只用来做依赖导入;
专门来写一个自动配置模块;
启动器依赖自动配置;别人只需要引入启动器(starter)
mybatis-spring-boot-starter;自定义启动器名-spring-boot-starter
步骤:
1)、启动器模块
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu.starter</groupId>
<artifactId>atguigu-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<!--启动器-->
<dependencies>
<!--引入自动配置模块-->
<dependency>
<groupId>com.atguigu.starter</groupId>
<artifactId>atguigu-spring-boot-starter-autoconfigurer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2)、自动配置模块
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu.starter</groupId>
<artifactId>atguigu-spring-boot-starter-autoconfigurer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>atguigu-spring-boot-starter-autoconfigurer</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--引入spring-boot-starter;所有starter的基本配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
package com.atguigu.starter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "atguigu.hello")
public class HelloProperties {
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
package com.atguigu.starter;
public class HelloService {
HelloProperties helloProperties;
public HelloProperties getHelloProperties() {
return helloProperties;
}
public void setHelloProperties(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
public String sayHellAtguigu(String name){
return helloProperties.getPrefix()+"-" +name + helloProperties.getSuffix();
}
}
package com.atguigu.starter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnWebApplication //web应用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
@Autowired
HelloProperties helloProperties;
@Bean
public HelloService helloService(){
HelloService service = new HelloService();
service.setHelloProperties(helloProperties);
return service;
}
}
更多SpringBoot整合示例
https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples