文档
HelloWord 第一步:创建一个Maven工程
第二步:导入pom依赖
1 2 3 4 5 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.7.1</version > </parent >
第三步:添加web启动器
1 2 3 4 5 6 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > </dependencies >
第四步:编写MainApplication方法,核心运行方法
1 2 3 4 5 6 7 8 9 10 11 12 13 @SpringBootApplication public class MainApplication { public static void main (String[] args) { SpringApplication.run(MainApplication.class,args); } }
第五步:编写HelloController
1 2 3 4 5 6 7 @RestController public class HelloController { @GetMapping("/hello") public String hello () { return "hello spring boot2" ; } }
此时的项目结构
第六步:运行MainApplication
启动成功后再浏览器输入 http://localhost:8080/hello,页面成功响应 hello spring boot2
修改默认端口 SpringBoot默认启动的端口号是 8080,我们可通过配置文件修改默认启动的端口号。新建一个 application.properties,里面指定启动端口号
然后再次启动服务,可以看到在 8081 端口运行我们的项目
此时在浏览器输入 http://localhost:8081/hello,页面成功响应
自动装配组件的注意点
SpringBoot 默认自动装配声明了 @SpringBootApplication 注解的同级以及下面的包
如果想要改变这种默认的装配规则,可以在运行类中设置 @SpringBootApplication(scanBasePackages = "com.szx")
修改这种默认规则
底层注解 @Configuration 新建一个 MyConfig 类,作为一个配置类,在配置类中可以注册 bena 到容器中
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration(proxyBeanMethods = false) public class MyConfig { @Bean public User user () { return new User("张三" ,18 ); } @Bean public Pet pet () { return new Pet("猫" ); } }
如果在配置类中声明了 proxyBeanMethods = false,则通过配置类获取bean不会先在容器中判断是否存在,每次返回的都会是一个新的bean
在启动类中可以获取容器中的 bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @SpringBootApplication(scanBasePackages = "com.szx") public class MainApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); User user = run.getBean("user" , User.class); User user1 = run.getBean("user" , User.class); System.out.println(user == user1); MyConfig myConfig = run.getBean(MyConfig.class); User user2 = myConfig.user(); User user3 = myConfig.user(); boolean b = user2 == user3; System.out.println(b); } }
@Import 处理手动的在配置类中添加 bean 之外,也可在配置类上添加 @Import 注解导入组件
1 2 3 4 5 6 7 8 9 10 11 12 13 @Import({User.class,Pet.class}) @Configuration public class MyConfig { @Bean public User user () { return new User("张三" ,18 ,new Pet("金毛" )); } @Bean public Pet pet () { return new Pet("猫" ); } }
运行结果
使用 @Import 导入的bean名称默认为全类名
@Conditional条件装配
@ConditionalOnBean(name = "pet")
当容器中有某个bena时才会注册这个bena,要注意有先后顺序的问题
@ConditionalOnMissingBean
当容器中没有某个组件时,就注册那个组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration public class MyConfig { @Bean public Pet pet () { return new Pet("猫" ); } @ConditionalOnBean(name = "pet") @Bean public User user () { return new User("张三" ,18 ,new Pet("金毛" )); } }
可以通过 run.containsBean(“user”); 来判断是否存在 user bean
1 2 3 4 5 6 7 8 9 10 11 12 @SpringBootApplication public class MainApplication2 { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication2.class); boolean isUser = run.containsBean("user" ); System.out.println("是否存在user:" + isUser); boolean isPet = run.containsBean("pet" ); System.out.println("是否存在pet:" + isPet); } }
@ImportResource @ImportResource 注解允许导入一个Spring配置文件
例如新建一个 bean.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="haha" class ="com.szx.boot.beans.User" > <property name ="name" value ="haha" > </property > <property name ="age" value ="15" > </property > </bean > <bean id ="hehe" class ="com.szx.boot.beans.Pet" > <property name ="name" value ="dog" > </property > </bean > </beans >
然后通过 @ImportResource 注解导入这个配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.szx.boot.config;import com.szx.boot.beans.Pet;import com.szx.boot.beans.User;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.context.annotation.ImportResource;@ImportResource("classpath:beans.xml") @Import({User.class,Pet.class}) @Configuration public class MyConfig { @Bean public Pet pet () { return new Pet("猫" ); } @ConditionalOnBean(name = "pet") @Bean public User user () { return new User("张三" ,18 ,new Pet("金毛" )); } }
然后判断容易中是否存在 haha
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.szx.boot;import com.szx.boot.beans.User;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class MainApplication2 { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication2.class); boolean isUser = run.containsBean("user" ); System.out.println("是否存在user:" + isUser); boolean isPet = run.containsBean("pet" ); System.out.println("是否存在pet:" + isPet); boolean ishaha = run.containsBean("haha" ); System.out.println("是否存在ishaha:" + ishaha); } }
结果显示为 true
@ConfigurationProperties 配置绑定 在配置文件中配置一个类中属性默认值,可以通过 @ConfigurationProperties 注解绑定这个值
添加 Car 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.szx.boot.beans;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Component @ConfigurationProperties(prefix = "car") public class Car { String name; Integer price; public Car () { } public Car (String name, Integer price) { this .name = name; this .price = price; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getPrice () { return price; } public void setPrice (Integer price) { this .price = price; } @Override public String toString () { return "Car{" + "name='" + name + '\'' + ", price=" + price + '}' ; } }
在 application.properties 配置文件中添加 car 类属性的默认值
1 2 car.name =yd car.price =100000
在控制器中添加 getCar 方法,返回 car
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.szx.boot.controller;import com.szx.boot.beans.Car;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@RestController public class HelloController { @Autowired Car car; @GetMapping("/car") public Car getCar () { return car; } }
启动程序,访问 car
第二种方法:
不在类上添加 @Component 注解,在配置类中添加 @EnableConfigurationProperties(Car.class)
注解。表示在容器中注册 Car 类,并开启从配置文件中读取属性值
修改 Car 类
1 2 @ConfigurationProperties(prefix = "car") public class Car {
配置类中添加 @EnableConfigurationProperties 注解
1 2 3 4 5 @EnableConfigurationProperties(Car.class) @ImportResource("classpath:beans.xml") @Import({User.class,Pet.class}) @Configuration public class MyConfig {
开发小技巧 LomBok 简化bean开发 第一步导入依赖
1 2 3 4 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </dependency >
第二步:安装idea插件
第三步,使用相关注解
1 2 3 4 5 6 7 8 9 @Data @ToString @NoArgsConstructor @AllArgsConstructor public class Car { String name; Integer price; }
额外的还有 @Slf4j
注解,可以将信息直接输出在控制台中
在 Controller 类上添加 @Slf4j
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.szx.boot.controller;import com.szx.boot.beans.Car;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@Slf4j @RestController public class HelloController { @Autowired Car car; @GetMapping("/car") public Car getCar () { log.info("请求进来" ); return car; } }
然后浏览器访问 /car ,查看控制台打印
修改代码后热部署
引入依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <optional > true</optional > </dependency >
修改代码后需要按下 Ctrl + F9 进行重新编译
Spring Iintializr 初始化向导 通过 Spring Iintializr 创建项目,我们只需要通过选择的方式,就可以快速的生成一个工程
配置文件 yaml YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)。
非常适合用来做以数据为中心的配置文件
基本语法
key: value;kv之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
‘#’表示注释
字符串无需加引号,如果要加,’’与””表示字符串内容 会被 转义/不转义
数据类型 字面量:单个的、不可再分的值。date、boolean、string、number、null
对象:键值对的集合。map、hash、set、object
1 2 3 4 5 6 行内写法: k: {k1:v1 ,k2:v2 ,k3:v3 }k: k1: v1 k2: v2 k3: v3
数组:一组按次序排列的值。array、list、queue
1 2 3 4 5 6 行内写法: k: [v1 ,v2 ,v3 ]k: - v1 - v2 - v3
示例 新建一个 User 类和 Pet 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import java.util.Date;import java.util.List;import java.util.Map;import java.util.Set;@AllArgsConstructor @NoArgsConstructor @ToString @Data @Component @ConfigurationProperties(prefix = "user") public class User { String userName; boolean boss; Date birth; Integer age; String[] interests; List<String> animal; Map<String,Object> score; Set<Double> salarys; Pet pet; Map<String,List<Pet>> allPets; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @AllArgsConstructor @NoArgsConstructor @ToString @Data @ConfigurationProperties(prefix = "pet") @Component public class Pet { String petName; Double price; }
新建 application.yml 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server: port: 8081 user: userName: 张三 boss: false birth: 2020 /07/18 age: 15 interests: - a - b - c animal: - 羽毛球 - 乒乓球 - 篮球 score: weight: 60 height: 186 salarys: - 99.99 - 88.88 pet: petName: BYD price: 100000 allPets: sike: - {petName: bwm ,price: 99999 } healh: [{petName: wuling ,price: 6666 }]
新建 UserController 控制器,返回 user 类
1 2 3 4 5 6 7 8 9 @RestController public class UserController { @Autowired User user; @GetMapping("/getuser") public User user () { return user; } }
浏览器访问 /getuser
查看效果
配置提示 在设置自定义类的初始化值时可以自动提示相关属性
添加依赖
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency >
设置在打包时去掉提示的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <excludes > <exclude > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > </exclude > </excludes > </configuration > </plugin > </plugins > </build >
web场景 静态资源访问 默认情况下,Spring Boot 将类路径中名为/static
(或/public
或/resources
或/META-INF/resources
)的目录作为静态资源目录,我们只需要将文件放在这些文件夹中就可以访问到这些静态资源
启动服务,直接输入静态资源名称即可访问到
默认情况下,资源映射在 上/**
,但您可以使用该spring.mvc.static-path-pattern
属性对其进行调整。例如,将所有资源重新定位到/res/**
可以实现如下:
在 application.yml 中添加如下配置
1 2 3 4 spring: mvc: static-path-pattern: /res/**
这时访问静态资源应该以 res 开头
您还可以使用该spring.web.resources.static-locations
属性自定义静态资源位置(将默认值替换为目录位置列表),示例如下
1 2 3 4 5 6 7 8 spring: mvc: static-path-pattern: /res/** web: resources: static-locations: [classpath:/haha/ ]
设置完之后,其他的静态资源位置将会失效,只有 haha 文件夹会作为静态资源文件夹
欢迎页和favicon.ico图标 只要在静态资源文件夹放一个 index.html 和 favicon.ico图标,然后访问首页时就可以访问页面和显示网站小图标
但是在设置了静态资源访问前缀后这个功能不会生效
Rest映射 通过请求方式的不同,来实现不同业务的接口请求
请求方式
接口
说明
GET
/user
获取user信息
POST
/user
添加user信息
DELETE
/user
删除user信息
PUT
/user
修改user信息
添加实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @RestController public class UserRestController { @GetMapping("/user") public String getUser () { return "get user" ; } @PostMapping("/user") public String postUser () { return "post user" ; } @PutMapping("/user") public String putUser () { return "put user" ; } @DeleteMapping("/user") public String deleteUser () { return "delete user" ; } }
通过页面表单访问时,需要开启以下配置
1 2 3 4 5 6 7 8 spring: mvc: static-path-pattern: /** hiddenmethod: filter: enabled: true
然后通过表单发送 delet 或者 put 请求时需要携带 _method 参数
1 2 3 4 <form action ="/user" method ="post" > <input type ="hidden" name ="_method" value ="delete" > <input type ="submit" > </form >
改变默认的_method参数 添加配置类,调用setMethodParam方法设置属性名为 _m
1 2 3 4 5 6 7 8 9 @Configuration public class WebConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter () { HiddenHttpMethodFilter hhmf = new HiddenHttpMethodFilter(); hhmf.setMethodParam("_m" ); return hhmf; } }
然后修改表单中发送的参数即可
1 2 3 4 <form action ="/user" method ="post" > <input type ="hidden" name ="_m" value ="delete" > <input type ="submit" > </form >
请求参数 常用参数注解 接口定义:@GetMapping("/getCar/{id}/{name}")
请求地址:/getCar/1/lisi?age=18&inters=篮球&inters=羽毛球
注解
含义
@PathVariable(“id”) Integer id
获取路径上的id
@PathVariable Map<String,String> argmap
以map的形式将路径上所有的参数放在argmap中 键值必须都是String类型
@RequestHeader(“Referer”) String Referer
获取请求头信息中的Referer值
@RequestHeader Map<String,String> headerMap
获取所有的请求头信息,并放在map集合中
@RequestParam(“age”) Integer age
获取请求参数 age
@RequestParam(“inters”) List inters
以List集合的方式获取请求参数inters
@CookieValue(“_ga”) String _ga
获取Cookie中的_ga值
@CookieValue(“_ga”) Cookie cookie
获取Cookie中的_ga值,以Cookie的形式接收
@RequestAttribute(“msg”) String msg
获取在请求域中保存的msg属性
定义一个CarController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.szx.boot02initializr.controller;import org.springframework.web.bind.annotation.*;import javax.servlet.http.Cookie;import java.util.HashMap;import java.util.List;import java.util.Map;@RestController public class CarController { @GetMapping("/getCar/{id}/{name}") public Map<String,Object> getCar (@PathVariable("id") Integer id, @PathVariable("name") String name, @PathVariable Map<String,String> argmap, @RequestHeader("Referer") String Referer, @RequestHeader Map<String,String> headerMap, @RequestParam("age") Integer age, @RequestParam("inters") List<String> inters, @CookieValue("_ga") String _ga, @CookieValue("_ga") Cookie cookie) { Map<String, Object> map = new HashMap<>(); map.put("id" ,id); map.put("name" ,name); map.put("argmap" ,argmap); map.put("Referer" ,Referer); map.put("headerMap" ,headerMap); map.put("age" ,age); map.put("inters" ,inters); map.put("_ga" ,_ga); System.out.println(cookie.getName()); System.out.println(cookie.getValue()); return map; } }
前端发送请求
1 <a href ="/getCar/1/lisi?age=18&inters=篮球&inters=羽毛球" > getCar/1/lisi</a >
浏览器响应返回的数据
发送form表单请求
1 2 3 4 5 <form action ="/getFormArgs" method ="get" > <input type ="text" name ="age" > <input type ="email" name ="email" > <input type ="submit" > </form >
方式一:以map形式接收 1 2 3 4 5 6 7 8 9 @GetMapping("/getFormArgs") public Map getFormArgs (@RequestParam Map<String,Object> formarg) { System.out.println("formarg.get(\"age\") = " + formarg.get("age" )); System.out.println("formarg.get(\"email\") = " + formarg.get("email" )); return formarg; }
接收的数据
方式二:获取单个数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @GetMapping("/getFormArgs") public Map getFormArgs (@RequestParam("age") String age, @RequestParam("email") String email) { System.out.println("age = " + age); System.out.println("email = " + email); HashMap<String, Object> argMap = new HashMap<>(); argMap.put("age" ,age); argMap.put("email" ,email); return argMap; }
接收到的数据
方式三:以对象形式接收 首先简单封装一个对象
1 2 3 4 5 6 7 8 @Data @ToString @AllArgsConstructor @NoArgsConstructor public class FormBean { Integer age; String email; }
以FormBean的形式接收到传递过来的值
1 2 3 4 5 6 7 8 9 10 @GetMapping("/getFormArgs") public FormBean getFormArgs (FormBean formBean) { System.out.println("formBean.getAge() = " + formBean.getAge()); System.out.println("formBean.getEmail() = " + formBean.getEmail()); return formBean; }
接收到的参数
接收json数据 首先通过apiPost模拟发送post请求
方式一:以Map形式接收 1 2 3 4 5 @PostMapping("/getJsonArgs") public Map getPostArgs (@RequestBody Map<String,Object> jsons) { System.out.println("jsons = " + jsons); return jsons; }
接收到的数据
方式二:以对象形式接收 1 2 3 4 5 @PostMapping("/getJsonArgs") public FormBean getPostArgs (@RequestBody FormBean formBean) { System.out.println("formBean = " + formBean); return formBean; }
接收到的数据
获取请求域中的参数 新建 RequestController
,访问 goto 然后内部转发请求 success,在转发前往 httpServletRequest 中保存 msg 和 code 两个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Controller public class RequestController { @GetMapping("/goto") public String goToSuc (HttpServletRequest httpServletRequest) { httpServletRequest.setAttribute("msg" ,"成功" ); httpServletRequest.setAttribute("code" ,200 ); return "forward:/success" ; } @ResponseBody @GetMapping("/success") public Map success (@RequestAttribute("msg") String msg, @RequestAttribute("code") Integer code, HttpServletRequest request) { Object request_code = request.getAttribute("code" ); Object request_msg = request.getAttribute("msg" ); HashMap<String, Object> map = new HashMap<>(); map.put("annotation_msg" ,msg); map.put("annotation_code" ,code); map.put("request_msg" ,request_msg); map.put("request_code" ,request_code); return map; } }
在success方法中 @RequestAttribute("msg")
注解获取在请求与中保存的msg属性
浏览器发送请求查看返回值
获取矩阵变量参数 例如当浏览器发送如下格式的地址:/shell/car;age=12;name=张三
,后端可以通过 @MatrixVariable
注解来获取第一个分号后面的参数。我们称这种参数为矩阵变量。
注意:获取矩阵变量的数据必须前提是Rest风格的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @GetMapping("/shell/{path}") public Map getMatrix (@MatrixVariable("age") Integer age, @MatrixVariable("name") String name, @PathVariable("path") String path) { HashMap<String, Object> map = new HashMap<>(); map.put("age" ,age); map.put("name" ,name); map.put("path" ,path); return map; }
查看接口返回
除了获取单个矩阵,也可以获取多个。例如:
/boos/zhang;age=20/wang;age=32
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @GetMapping("/boos/{boos1}/{boos2}") public Map getMatrix2 (@MatrixVariable(value = "age",pathVar = "boos1") Integer zhangAge, @MatrixVariable(value = "age",pathVar = "boos2") Integer wangAge) { HashMap<String, Object> map = new HashMap<>(); map.put("zhangAge" ,zhangAge); map.put("wangAge" ,wangAge); return map; }
接口返回
请求拦截和静态资源放行 操作步骤:
添加interceptor拦截器,实现HandlerInterceptor接口
preHandle方法,接口方法之前拦截
postHandle方法,接口方法执行之后
afterCompletion方法,视图渲染器渲染完毕后执行
重写preHandle方法,从当前请求中获取session,从session中判断是否有用户信息
添加配置类,将拦截器添加到容器中
配置类实现WebMvcConfigurer接口,并重写addInterceptors方法,添加要拦截和放行的地址
代码示例
1.添加interceptor拦截器,实现HandlerInterceptor接口
2.重写preHandle方法,从当前请求中获取session,从session中判断是否有用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object userBean = session.getAttribute("userBean" ); if (userBean != null ){ return true ; } request.getRequestDispatcher("/login" ).forward(request,response); return false ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super .postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super .afterCompletion(request, response, handler, ex); } }
3.添加配置类,将拦截器添加到容器中
4.配置类实现WebMvcConfigurer接口,并重写addInterceptors方法,添加要拦截和放行的地址
addInterceptor 表示添加拦截器,传入我们上面定义的拦截器实例
addPathPatterns 表示添加要拦截的路径,/** 表示拦截所有请求
excludePathPatterns 表示设置不拦截那些路径,/css/** 表示不拦截以css开头的所有请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**" ) .excludePathPatterns("/" ,"/login" ,"/css/**" ,"/js/**" ,"/fonts/**" ,"/images/**" ); } }
添加完成后,在访问了除了 / 和 /login 页面外,其他页面都会被拦截器拦截,判断是否登录,如果未登录则强制跳转到登录页面
单文件上传和多文件上传 前端页面请求
1 2 3 4 5 6 7 8 9 10 11 <form th:action ="@{/saveForm}" enctype ="multipart/form-data" method ="post" > <label > 邮箱</label > <input type ="email" name ="email" > <label > 姓名</label > <input type ="text" name ="name" > <label > 头像</label > <input type ="file" name ="photo" > <label > 生活照</label > <input type ="file" multiple name ="photos" > <input type ="submit" > </form >
添加saveForm接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.szx.boot04webadmin.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RequestPart;import org.springframework.web.multipart.MultipartFile;import java.io.File;import java.io.IOException;@Slf4j @Controller public class SaveFormController { @PostMapping("/saveForm") public String saveForm (@RequestParam("email") String email, @RequestParam("name") String name, @RequestPart("photo") MultipartFile photo, @RequestPart("photos") MultipartFile[] photos) throws IOException { log.info("邮箱=>{},姓名=>{},头像大小=>{},生活照个数=>{}" ,email,name,photo.getSize(),photos.length); if (!photo.isEmpty()){ String filename = photo.getOriginalFilename(); photo.transferTo(new File("D:\\images\\" +filename)); } if (photos.length > 0 ){ for (MultipartFile file : photos) { if (!file.isEmpty()){ String filename = file.getOriginalFilename(); file.transferTo(new File("D:\\images\\" +filename)); } } } return "main" ; } }
查看打印
打开 D/images 文件夹,我们上传的文件都会保存在这个文件夹内
修改默认的文件大小限制,在yml配置文件中添加如下配置即可
1 2 3 4 5 spring: servlet: multipart: max-file-size: 10MB max-request-size: 100MB
默认错误处理机制 默认情况下,SpringBoot提供 /error 处理所有错误的映射。对于机器客户端,它将生成一个 JSON 响应,其中包含错误信息,Http状态和异常消息的详细信息。对于浏览器,则响应一个错误页面
默认情况下,提供的404页面是这样的
还有500页面如下
我们可以自定义这些错误页面
在 templates 文件夹下添加 error 文件夹,里面放上两个页面
现在我们的404和5xx页面都会是自定义的页面
定制化的常见方式
修改配置文件
xxxxCustimuzer
编写自定义的配置类,xxxConfiguration; + @Bean 替换、增加容器中默认组件,视图解析器
Web应用,编写一个配置类实现,WebMvcConfigurer 即可定制化web功能。 + @Bean 给容器中扩展一些组件
数据访问 基本数据访问操作 导入jdbc依赖,导入之后会自动帮我们引入数据源,jdbc,事务
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jdbc</artifactId > </dependency >
这里没有自动帮我们导入数据库驱动,这是因为SpringBoot并不知道我们用的是什么数据库,这里我们用MySQL来作为我们的数据库
打开 spring-boot-dependencies 搜索 mysql.version,发现SpringBoot有帮我们维护了mysql驱动的版本,我们只需引入mysql驱动即可
1 2 3 4 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency >
添加配置项,yml中密码要使用双引号括起来
1 2 3 4 5 6 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: "abc123" url: jdbc:mysql://localhost:3306/spring_boot_test
然后再 spring_boot_test 库中新建一个 user 表,增加如下数据
编写测试方法
1 2 3 4 5 6 7 8 9 10 11 @Slf4j @SpringBootTest class Boot04WebadminApplicationTests { @Autowired JdbcTemplate jdbcTemplate; @Test void contextLoads () { Integer total = jdbcTemplate.queryForObject("select count(*) from user" , Integer.class); log.info("数据总数->{}" , total); } }
运行效果
使用Druid数据源 自定义的方式使用Druid Druid官方文档:https://github.com/alibaba/druid
中文地址:https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
导入依赖
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.6</version > </dependency >
自定义我们自己的DataSource,新建一个MyDruidDataSourceController,当我们在容器中定义了一个DataSource类型的bean,则SpringBoot中默认的数据源就不会再生效了
1 2 3 4 5 6 7 8 9 10 @Configuration public class MyDruidDataSourceController { @Bean @ConfigurationProperties("spring.datasource") public DataSource driudDataSource () { DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } }
然后来测试当前容器中的DataSource类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Slf4j @SpringBootTest class Boot04WebadminApplicationTests { @Autowired JdbcTemplate jdbcTemplate; @Autowired DataSource dataSource; @Test void contextLoads () { Integer total = jdbcTemplate.queryForObject("select count(*) from user" , Integer.class); log.info("数据总数->{}" , total); log.info("dataSource类型->{}" ,dataSource.getClass()); } }
通过测试结果可以看到当前的数据源已经是我们自己定义的Druid数据源
使用Druid数据源的监控功能 1.打开Druid的监控统计功能
如果你要打开监控统计功能,配置StatFilter
1 2 3 4 5 6 7 8 @Bean @ConfigurationProperties("spring.datasource") public DataSource driudDataSource () throws SQLException { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setFilters("stat" ); return druidDataSource; }
2.使用Druid的内置监控页面
内置监控页面是一个Servlet,配置 StatViewServlet
在自定义DataSource的Controller中添加如下bean,/druid/*
表示我们要在那个路径下映射监控页面,固定写法,不能更改
1 2 3 4 5 6 7 @Bean public ServletRegistrationBean statViewServlet () { StatViewServlet statViewServlet = new StatViewServlet(); ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*" ); return registrationBean; }
然后启动SpringBoot项目,访问 /druid,即可看到Druid监控页面
编写一个测试方法,执行一个sql语句
1 2 3 4 5 6 7 8 @Autowired JdbcTemplate jdbcTemplate; @GetMapping("/druidsql") public String getUserTotal () { Integer total = jdbcTemplate.queryForObject("select count(*) from user" , Integer.class); return total.toString(); }
浏览器访问 /druidsql 后可以查看SQL的执行情况
开启Web应用监控功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Bean public FilterRegistrationBean webStatFilter () { WebStatFilter webStatFilter = new WebStatFilter(); FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(webStatFilter); registrationBean.setUrlPatterns(Arrays.asList("/*" )); registrationBean.addInitParameter("exclusions" ,"*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" ); return registrationBean; }
设置完成后就可以监控我们的页面跳转。启动项目,跳转页面时的状态都可以在这里看到
开启SQL防火墙功能 在DruidDataSource实例中往filters属性上添加一个值wall即可
1 2 3 4 5 6 7 8 9 @Bean @ConfigurationProperties("spring.datasource") public DataSource driudDataSource () throws SQLException { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setFilters("stat,wall" ); return druidDataSource; }
然后我们发送SQL请求时都会在这里记录
添加密码访问 我们不希望每个人都可以随意看到这个监控页面,可以设置账号密码来访问监控页面
在 ServletRegistrationBean 添加两个初始化参数即可
1 2 3 4 5 6 7 8 @Bean public ServletRegistrationBean statViewServlet () { StatViewServlet statViewServlet = new StatViewServlet(); ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*" ); registrationBean.addInitParameter("loginUsername" ,"admin" ); registrationBean.addInitParameter("loginPassword" ,"admin" ); return registrationBean; }
重启项目访问监控页面,自动出现登录页面
start方式配置druid 首先导入依赖
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.1.17</version > </dependency >
yml配置实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 spring: datasource: username: root password: "abc123" url: jdbc:mysql://localhost:3306/spring_boot_test driver-class-name: com.mysql.cj.jdbc.Driver druid: aop-patterns: com.szx.* filters: stat,wall stat-view-servlet: enabled: true login-username: admin login-password: admin resetEnable: false web-stat-filter: enabled: true urlPattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' filter: stat: slow-sql-millis: 1000 logSlowSql: true enabled: true wall: enabled: true config: drop-table-allow: false
配置完成后启动应用,Druid监控功能正常使用
整合Mybatis github文档:https://github.com/mybatis
配置版 导入mybatis官方start
1 2 3 4 5 <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.2.2</version > </dependency >
在配置文件中添加mybatis配置规则
新建UserBen对应数据库中的user表
1 2 3 4 5 @Data public class User { Integer id; String name; }
新建mapper.UserMapper.java接口,定义一个根据id查询user的方法
注意:mapper类必须标注@Mapper注解,才能被SpringBoot扫描并识别
1 2 3 4 5 @Mapper public interface UserMapper { User getUser (Integer id) ; }
新建UserService,自动注入UserMapper接口
1 2 3 4 5 6 7 8 9 @Service public class UserServer { @Autowired UserMapper userMapper; public User getUser (Integer id) { return userMapper.getUser(id); } }
然后在配置文件中新建 mapper.UserMapper.xml,映射UserMapper接口
1 2 3 4 5 6 7 8 9 10 11 12 <?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.szx.boot04webadmin.mapper.UserMapper" > <select id ="getUser" resultType ="com.szx.boot04webadmin.bean.User" > select * from user where id = #{id} </select > </mapper >
然后在yml配置文件中添加mybatis的相关属性配置
1 2 3 4 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true
上述配置完成之后,新建一个UserController测试sql
1 2 3 4 5 6 7 8 9 10 11 @RestController public class UserController { @Autowired UserServer userServer; @GetMapping("/getUserById") public User getUser (@RequestParam("id") Integer id) { User user = userServer.getUser(1 ); return user; } }
启动服务,浏览器访问 http://localhost:8080/getUserById?id=1
注解版 可以通过注解将SQL语句通过注解的方式添加到接口的方法上
1.新建一个cart表
2.编写对应的CartBean
1 2 3 4 5 6 @Data public class Cart { Integer id; String cartName; Double cartPrice; }
3.添加mapper接口映射
直接将查询SQL通过@Select注解放在方法上,省略mapper.xml 文件的编写
1 2 3 4 5 @Mapper public interface CartMapper { @Select("select * from cart where id = #{id}") public Cart getCartById (Integer id) ; }
4.添加service使用CartMapper接口返回Cart
1 2 3 4 5 6 7 8 9 @Service public class CartService { @Autowired CartMapper cartMapper; public Cart getCartById (Integer id) { return cartMapper.getCartById(id); } }
5.编写CartController,添加get请求
1 2 3 4 5 6 7 8 9 10 @RestController public class CartController { @Autowired CartService cartService; @GetMapping("getCartById") public Cart getCartById (@RequestParam("id") Integer id) { return cartService.getCartById(id); } }
6.启动项目,浏览器访问 http://localhost:8080/getCartById?id=1 查看效果
混合版 当有写SQL语句过长不能很好的在注解中使用时,可以使用混合方式,将SQL继续写在mapper文件中
例如往Cart表中添加数据
在Mapper接口中添加addCart方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Mapper public interface CartMapper { @Select("select * from cart where id = #{id}") Cart getCartById (Integer id) ; Integer addCart (Cart cart) ; }
新建CartMapper.xml接口映射文件
1 2 3 4 5 6 7 8 9 10 11 12 <?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.szx.boot04webadmin.mapper.CartMapper" > <insert id ="addCart" useGeneratedKeys ="true" keyProperty ="id" > insert into cart values(null,#{cartName},#{cartPrice}) </insert > </mapper >
在service中调用接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.szx.boot04webadmin.server;import com.szx.boot04webadmin.bean.Cart;import com.szx.boot04webadmin.mapper.CartMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class CartService { @Autowired CartMapper cartMapper; public Cart getCartById (Integer id) { return cartMapper.getCartById(id); } public Integer addCart (Cart cart) { return cartMapper.addCart(cart); } }
在controller中增加post请求
1 2 3 4 5 @PostMapping("cart") public Integer addCart (@RequestBody Cart cart) { cartService.addCart(cart); return cart.getId(); }
启动服务,发送post请求
注解属性配置 在xml中定义的 useGeneratedKeys,keyProperty 等属性也可以通过注解的方式来实现
1 2 3 @Insert("insert into cart values(null,#{cartName},#{cartPrice})") @Options(useGeneratedKeys = true,keyProperty = "id") Integer addCart (Cart cart) ;
整合mybatis-plus github地址:https://github.com/baomidou/mybatis-plus
中文文档地址:https://baomidou.com/
idea插件安装 搜索mybatisx安装即可
快速上手 引入依赖
1 2 3 4 5 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.5.2</version > </dependency >
引入依赖后,mybatis-plus自动帮我们完成如配置
SqlSessionFactory自动配置好。底层是使用容器中默认的数据源
mapperLocations自动配置好,有默认值。classpath*:/mapper/**/*.xml
;任意包的类路径下的所有mapper文件下任意路径的所有xml都是SQL映射文件,建议以后的SQL映射文件都放在mapper文件夹下
容器中也自动配置好的SqlSessionTemplate
@Mapper 标注的接口也会被自动配置扫描;建议直接@MapperScan(“com.xxx.xxx.mapper”)批量扫描
优点:
只需要我们的mapper继承BaseMapper就可以拥有crud的能力
首先新建一张表,并插入原始数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 DROP TABLE IF EXISTS plus_user;CREATE TABLE plus_user( id BIGINT (20 ) NOT NULL COMMENT '主键ID' , name VARCHAR (30 ) NULL DEFAULT NULL COMMENT '姓名' , age INT (11 ) NULL DEFAULT NULL COMMENT '年龄' , email VARCHAR (50 ) NULL DEFAULT NULL COMMENT '邮箱' , PRIMARY KEY (id) ); DELETE FROM plus_user;INSERT INTO plus_user (id, name, age, email) VALUES (1 , 'Jone' , 18 , 'test1@baomidou.com' ), (2 , 'Jack' , 20 , 'test2@baomidou.com' ), (3 , 'Tom' , 28 , 'test3@baomidou.com' ), (4 , 'Sandy' , 21 , 'test4@baomidou.com' ), (5 , 'Billie' , 24 , 'test5@baomidou.com' );
然后新建plususerBean
1 2 3 4 5 6 7 @Data public class PlusUser { Integer id; String name; Integer age; String email; }
然后新建PlusUserMapper,继承BaseMapper后会有一些基本的增删改查方法可以直接使用
1 2 3 4 5 6 7 8 9 @Mapper public interface PlusUserMapper extends BaseMapper <PlusUser > {}
接着新建PlusUserService,使用selectById根据id查询数据
1 2 3 4 5 6 7 8 9 10 11 @Service public class PlusUserService { @Autowired PlusUserMapper plusUserMapper; public PlusUser getUserInfo (Integer id) { PlusUser plusUser = plusUserMapper.selectById(id); System.out.println(plusUser); return plusUser; } }
新建 PlusUserController 新建get请求
1 2 3 4 5 6 7 8 9 10 @RestController public class PlusUserController { @Autowired PlusUserService plusUserService; @GetMapping("/plususer") public PlusUser getPlusUser (@RequestParam("id") Integer id) { return plusUserService.getUserInfo(id); } }
启动项目,浏览器访问http://localhost:8080/plususer?id=1查看返回的数据
@TableName指定数据库名称 mybatis-plus默认回去数据库中找和类名相同的数据表,另外我们也可以指定数据表名称,使用@TableName来指定表名
1 2 3 4 5 6 7 8 @Data @TableName("plus_user") public class PlusUser { Integer id; String name; Integer age; String email; }
分页显示数据 开始之前需要添加分页插件,在容器中添加 MybatisPlusInterceptor 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Configuration public class MyBatisConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); innerInterceptor.setMaxLimit(500L ); interceptor.addInnerInterceptor(innerInterceptor); return interceptor; } }
正常情况下,我们会先封装一个接口,然后通过service调用接口中的方法。所以接下来我们来规范一下代码的调用逻辑
1.新建一个PlusUserDao接口,让这个接口继承MybatisPlus中的IService接口,IService接口是带泛型的,传递过去我们定义的PlusUser
1 2 3 4 5 6 7 public interface PlusUserDao extends IService <PlusUser > {}
2.新建PlusUserImpl的接口实现类,同时继承MybatisPlus的ServiceImpl接口实现类
1 2 3 4 5 6 7 8 9 @Service public class PlusUserImpl extends ServiceImpl <PlusUserMapper , PlusUser > implements PlusUserDao {}
这样操作继承完后,我们就拥有了操作plus_user这张表的一些基本增删改查方法
3.新建一个Controller,来分页获取表中的数据
1 2 3 4 5 6 7 8 9 10 11 @GetMapping("/plususer/{pageNum}/{pageSize}") public Page getPlusUserByPage (@PathVariable("pageNum") Integer pageNum,@PathVariable("pageSize") Integer pageSize) { Page<PlusUser> page = new Page<>(pageNum, pageSize); Page<PlusUser> userPage = plusUserImpl.page(page,null ); return userPage; }
启动项目,浏览器访问 http://localhost:8080/plususer/1/2 查看返回的数据
删除用户 1 2 3 4 5 6 7 8 9 public Map deletePlusUser (@RequestBody Map map) { Integer id = (Integer) map.get("id" ); boolean b = plusUserImpl.removeById(id); HashMap<String, Object> hashMap = new HashMap<>(); hashMap.put("code" ,200 ); hashMap.put("success" ,b); hashMap.put("msg" ,b ? "删除成功" : "删除失败" ); return hashMap; }
发送请求,删除id为1的数据
查看数据库中的剩余数据,发现id为1的数据已经被成功删除了
整合redis 快速上手 导入依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
在配置文件中添加redis访问地址
1 2 3 spring: redis: url: redis://localhost:6379
编写测试方法,通过键值对的方式往redis中存储一个数据,然后读取并返回
1 2 3 4 5 6 7 8 9 10 11 12 @RestController public class RedisController { @Autowired StringRedisTemplate redisTemplate; @GetMapping("/getredisval") public String getRedisVal () { ValueOperations<String, String> opsForValue = redisTemplate.opsForValue(); opsForValue.set("hello" ,"word" ); return opsForValue.get("hello" ); } }
启动项目,浏览器访问 http://localhost:8080/getredisval 查看返回
打开redis客户端(Another Redis Desktop Manager),查看保存的数据
小案例,统计接口访问次数 首先添加拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.szx.boot04webadmin.interceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.ValueOperations;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Component public class Redisinlnterceptor implements HandlerInterceptor { @Autowired StringRedisTemplate redisTemplate; @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); ValueOperations<String, String> opsForValue = redisTemplate.opsForValue(); opsForValue.increment(uri); return true ; } }
然后在 webConfig 中添加新定义的拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired Redisinlnterceptor redisinlnterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**" ) .excludePathPatterns("/" ,"/login" ,"/css/**" ,"/js/**" ,"/fonts/**" ,"/images/**" ); registry.addInterceptor(redisinlnterceptor) .addPathPatterns("/**" ) .excludePathPatterns("/" ,"/login" ,"/css/**" ,"/js/**" ,"/fonts/**" ,"/images/**" ); } }
在 /main.html
接口中从redis获取请求的接口次数,并添加到model中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Autowired StringRedisTemplate redisTemplate; @GetMapping("/main.html") public String mainPage (HttpSession session,Model model) { ValueOperations<String, String> opsForValue = redisTemplate.opsForValue(); String mainCount = opsForValue.get("/main.html" ); String sqlCount = opsForValue.get("/druidsql" ); model.addAttribute("mainCount" ,mainCount); model.addAttribute("sqlCount" ,sqlCount); return "main" ; }
然后在 main.html 页面中通过 thymeleaf 将数据渲染出来
1 2 3 4 5 6 7 8 9 <div class ="state-value" > <div class ="value" th:text ="${mainCount}" > 230</div > <div class ="title" > mainCount</div > </div > <div class ="state-value" > <div class ="value" th:text ="${sqlCount}" > 3490</div > <div class ="title" > sqlCount</div > </div >
启动项目,我们来分别访问几次首页和/druidsql
,查看页面数据变化
通过redis客户端也可以看到数据情况
单元测试 常用测试方法
@Test 表示方法是测试方法
@DisplayName 为测试类或者测试方法添加一个名称,在方法执行后可以看到自定义的方法名称。方便查找
@BeforeEach 表示每个测试方法开始之前执行
@AfterEach 表示每个测试方法结束后执行
@BeforeAll 表示所有测试方法开始之前执行,必须是一个静态方法
@AfterAll 表示所有测试方法结束后执行,必须是一个静态方法
@RepeatedTest 表示可以重复执行的方法,接收一个参数表示重复执行的次数
@Disabled 表示这个测试方法已失效
@Timeout(500) 表示这个方法如果运行时间超过500毫秒就抛出异常,要设置unit表示时间类型,MILLISECONDS表示毫秒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 @Slf4j @SpringBootTest @DisplayName("测试类001") public class MySpringBootTset { @Autowired JdbcTemplate jdbcTemplate; @Test @DisplayName("测试方法:test001") public void test1 () { System.out.println(1 ); System.out.println(jdbcTemplate); } @BeforeEach public void testBeforeEach () { System.out.println("测试方法开始执行" ); } @AfterEach public void testAfterEach () { System.out.println("测试方法结束后执行" ); } @BeforeAll public static void testBeforeAll () { System.out.println("所有测试方法开始之前" ); } @AfterAll public static void testAfterAll () { System.out.println("所有测试方法结束后执行" ); } @RepeatedTest(5) public void testRepeatedTest () { System.out.println("重复执行的测试方法" ); } @Disabled @Test public void testDisabled () { System.out.println("这个方法被失效" ); } @Timeout(value = 500,unit = TimeUnit.MILLISECONDS) @Test public void testTimeout () throws InterruptedException { Thread.sleep(501 ); System.out.println("延迟输出" ); } }
断言测试
方法
说明
assertEquals
判断两个对象或者原始类型是否相等
assertNotEquals
判断两个对象或者原始类型是否不相等
assertSame
判断两个对象指向的是否是同一个地址
assertNotSame
判断两个对象指向的不是同一个地址
assertTrue
判断给定的运算是否返回true
assertFalse
判断给定的运算是否返回true
assertNull
判断结果是否返回null
assertNotNull
判断结果不是null
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.szx.boot04webadmin;import org.junit.jupiter.api.Assertions;import org.junit.jupiter.api.DisplayName;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import java.util.Date;@SpringBootTest public class AssertionTest { int cat (int a,int b) { return a + b; } @DisplayName("assertEquals判断两个对象或者原始类型是否相等") @Test public void testAsserEquals () { Assertions.assertEquals(5 ,cat(3 ,3 ),"结果不是5" ); } @DisplayName("assertNotEquals判断两个对象或者原始类型是否不相等") @Test public void testAssertNotEquals () { Assertions.assertNotEquals(5 ,cat(3 ,3 ),"结果竟然是5?" ); } @DisplayName("assertSame判断两个对象指向的是否是同一个地址") @Test public void testAssertSame () { Assertions.assertSame(new Date(),new Date(),"两个对象指向的不是一个地址" ); } @DisplayName("assertNotSame判断两个对象指向的不是同一个地址") @Test public void testAssertNotSame () { Assertions.assertNotSame(new Date(),new Date(),"两个对象指向的竟然是是一个地址" ); } @DisplayName("assertTrue判断给定的运算是否返回true") @Test public void testArrestTrue () { Assertions.assertTrue(1 >5 ,"结果不是true" ); } @DisplayName("assertFalse判断给定的运算是否返回true") @Test public void testAssertFalse () { Assertions.assertFalse(1 >5 ,"结果不是false" ); } @DisplayName("assertNull判断结果是否返回null") @Test public void testAssertNull () { Assertions.assertNull(null ,"结果不是null" ); } @DisplayName("assertNotNull判断结果不是null") @Test public void testAssertNotNull () { Assertions.assertNotNull(null ,"结果是null" ); } }
前置条件 assumptions Assumptions.assumeTrue
前置条件判断,如果前置条件不成功,则代码不会继续往下执行
1 2 3 4 5 6 7 8 9 @SpringBootTest public class TestAssumptions { @Test void testassumption () { Assumptions.assumeTrue(false ,"结果返回的不是true" ); System.out.println("如果上面的前置条件没有通过,则不会继续往下执行" ); } }
参数化测试 首先在方法上标注 @ParameterizedTest
注解表示这是一个参数化测试方法
利用 @ValueSource 等注解,指定参数,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就要新增一个测试方法,省去了很多的冗余代码
注解名称
含义
@ValueSource
为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource
表示为参数化测试方法提供一个null入参
@EnumSource
提供一个枚举入参
@CsvFileSource
从指定的csv文件中读取内容获取参数作为入参
@MethodSource
读取指定的方法的返回值作为入参,注意必须返回一个流,并且是静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 @ParameterizedTest @DisplayName("参数化测试") @ValueSource(ints = {1,2,3}) void testValueSource (int i) { System.out.println(i); } @ParameterizedTest @DisplayName("参数化测试") @MethodSource("getSystem") void testValueSource2 (String str) { System.out.println(str); }
指标监控 快速上手 未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
导入依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency >
添加配置文件
1 2 3 4 5 6 management: endpoints: enabled-by-default: true web: exposure: include: '*'
然后可以通过访问如下页面来查看各项监控信息
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
Boot Admin Serve 我们可以使用这个框架快速搭建一个可视化的监控面板,github 地址 https://github.com/codecentric/spring-boot-admin
首先新建一个 java web 项目
然后引入依赖
1 2 3 4 5 <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-server</artifactId > <version > 2.5.1</version > </dependency >
然后将 @EnableAdminServer
注解添加到启动类上
1 2 3 4 5 6 7 8 9 @SpringBootApplication @EnableAdminServer public class Boot05AdminServerApplication { public static void main (String[] args) { SpringApplication.run(Boot05AdminServerApplication.class, args); } }
注册客户端应用程序,将这个依赖添加到要监控的项目中
1 2 3 4 5 <dependency > <groupId > de.codecentric</groupId > <artifactId > spring-boot-admin-starter-client</artifactId > <version > 2.5.1</version > </dependency >
然后再客户端项目中添加如下配置
1 2 3 4 5 6 7 8 9 spring: boot: admin: client: url: http://localhost:8888 instance: prefer-ip: true application: name: boot04-webadmin
然后分别启动要监控的项目和监控面板项目
这是在浏览器访问 http://localhost:8888/ 就可以查看我们项目的各项信息
profiles的使用 环境切换 在实际开发场景中,我们会有多套环境,例如在本地测试环境连接测试库,生产环境则使用生产库。我们可以使用 profiles 来快速的切换不同环境
首先分别新建如下两个文件
application-dev.yml
application-uat.yml
写入如下内容
我们可以在 application 配置文件上通过 application-xxx 的方式自定义环境配置
然后再主配置文件中声明要使用的环境名称,没有声明环境名称的配置文件就是主配置文件,主配置文件在任何环境下都会生效
1 2 3 server.port =8888 spring.profiles.active =uat
编写一个 ProductController
1 2 3 4 5 6 7 8 9 10 11 @RestController public class Product { @Value("${product.name}") String name; @GetMapping("/product") public String getName () { return name; } }
启动项目,访问 http://localhost:8888/product 查看接口返回
在uat环境下,使用的是uat配置文件
然后切换主配置文件中的环境名称
1 2 spring.profiles.active =dev
重启项目再次访问,返回值会不同
@Profile注解 首先添加一个user接口
1 2 3 4 public interface user { void setName () ; void setAge () ; }
然后编写两个类来实现这个接口
Boos
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Profile("uat") @ConfigurationProperties(prefix = "product") @Component @Data public class Boos implements user { String name; Integer age; @Override public void setName () { } @Override public void setAge () { } }
Student
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Data @Component @ConfigurationProperties(prefix = "product") @Profile("dev") public class Student implements user { private String name; private Integer age; @Override public void setName () { } @Override public void setAge () { } }
修改 Product
1 2 3 4 5 6 7 8 9 10 11 @RestController public class Product { @Autowired user userimpl; @GetMapping("/product") public user getName () { return userimpl; } }
我们可以根据不同的环境,来实现一个接口返回不同的user实现类
配置环境组 我们可以让多个环境同时生效
首先新建一个 application-test.yml 文件
然后在主配置文件中添加环境组
1 2 3 4 5 6 spring.profiles.group.mygroup[0] =dev spring.profiles.group.mygroup[1] =test spring.profiles.active =mygroup
在 Student 中添加 weight 字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Data @Component @ConfigurationProperties(prefix = "product") @Profile("dev") public class Student implements user { private String name; private Integer age; private String weight; @Override public void setName () { } @Override public void setAge () { } }
启动项目,查看返回。通过结果可以看出,dev 和 test 两个环境都生效
配置文件的优先级
首先会加载主配置文件,然后加载声明环境的配置文件
当配置重复时,指定环境的配置文件优先级高,会覆盖主配置文件的设置
配置文件的查找位置
classpath 根目录
classpath 根目录下的config 目录
jar 包当前目录
jar 包当前目录的config目录
/config 子目录的直接子目录(仅针对与linux系统)
配置文件的加载顺序 优先级从低到高:
当前jar包内部的 application 和 application.yml
当前jar包内部的 application-(profile).properties 和 application-(profile).yml
引用的外部jar包的 application.properties 和 application.yml
引用的外部jar包的 application-(profile).properties 和 application-(profile).yml
总结:指定环境优先,外部优先,后面的可以覆盖前面的同名配置项目
自定义start 首先新建两个Module
szx-hello-spring-boot-start
szx-hello-spring-boot-start-autoconfigure
在szx-hello-spring-boot-start中引入szx-hello-spring-boot-start-autoconfigure
然后在szx-hello-spring-boot-start-autoconfigure中新建service.HelloService,默认不要放在容器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.example.szxhellospringbootstartautoconfigure.service;import com.example.szxhellospringbootstartautoconfigure.bean.HelloPropretes;import org.springframework.beans.factory.annotation.Autowired;public class HelloService { @Autowired HelloPropretes helloPropretes; public String sayHello (String name) { return helloPropretes.getPrefix() + ": " + name + " >" + helloPropretes.getSuffix(); } }
这里用到了 helloPropretes,新建bean.helloPropretes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.example.szxhellospringbootstartautoconfigure.bean;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties("hello") public class HelloPropretes { String prefix; 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; } }
然后新建 auto.HelloAutoConfiguration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.example.szxhellospringbootstartautoconfigure.auto;import com.example.szxhellospringbootstartautoconfigure.bean.HelloPropretes;import com.example.szxhellospringbootstartautoconfigure.service.HelloService;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration @EnableConfigurationProperties(HelloPropretes.class) public class HelloAutoConfiguration { @Bean @ConditionalOnMissingBean(HelloService.class) public HelloService helloService () { HelloService helloService = new HelloService(); return helloService; } }
接着在 resources 文件夹下新建 META-INF/spring.factories
1 2 org.springframework.boot.autoconfigure.EnableAutoConfiguration =\ com.example.szxhellospringbootstartautoconfigure.auto.HelloAutoConfiguration
szx-hello-spring-boot-start-autoconfigure 的目录结构如下
然后将两个项目安装到本地,分别执行一下 clean,install
然后新建一个测试项目 boot07-customer-start-test,引入 szx-hello-spring-boot-start
新建 controller.HelloController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.example.boot07customerstarttest.controller;import com.example.szxhellospringbootstartautoconfigure.service.HelloService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class HelloController { @Autowired HelloService helloService; @GetMapping("/hello") public String sayHello () { return helloService.sayHello("张三" ); } }
然后再配置文件中声明属性
启动项目,访问 http://localhost:8088/hello,结果可以看到成功引入
如果我们在容器中注册了一个 HelloService,则不会再使用引入的HelloService
新建 config.HelloConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.example.boot07customerstarttest.config;import com.example.szxhellospringbootstartautoconfigure.service.HelloService;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class HelloConfig { @Bean public HelloService helloService () { System.out.println("进入自己的HelloService" ); return new HelloService(); } }
重启项目,观察控制台打印
至此,我们自定义 start 就完成了。