1 | 作者: 夜泊1990 |
第一章 SpringBoot简介
第1节 SpringBoot是什么
1 | 1.SpringBoot是一个可以快速创建可运行的、独立的、生产级的基于Spring的应用程序 |
第2节 SpringBoot的优势
1 | 1.快速构建项目 |
第3节 SpringBoot的系统需求
1 | 由于每隔一段时间官网就会提升一次版本,当前授课采用的SpringBoot2.1.18版本(2020年11月) |
第二章 快速入门
第1节 SpringBoot的脚手架
- 脚手架的概念
在我们软件开发中的脚手架的概念,类似于我们的maven工具一样,以一种预先定义好的方式生成特定环境,特定的项目目录结构,并且预先定义好了每一个目录文件的具体功能
- 官网脚手架地址
- 阿里云脚手架地址
第2节 创建SpringBoot项目的方式
- 使用官网提供的工具创建(Spring官网下载sts)
- 使用IDEA创建
- 使用脚手架在线创建
第3节 SpringBoot项目结构以及依赖
- SpringBoot的目录结构
- SpringBoot的依赖样式
1
2
3
4
5<dependency>
<groupId>org.springframework.boot</groupId>
<!--带有starter样式-->
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
第4节 快速入门代码编写
- 使用IDEA创建项目(2.1.14版本)
- 勾选web依赖(首先创建一个web项目[相当于我们的SpringMVC项目])
- 编写CRUD方法(API方式)
- 编写代码需要的POJO类
1
2
3
4
5public class Book {
private Integer bookId;
private String bookName;
private Double price;
}
- 编写代码需要的POJO类
- CRUD代码
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110@RestController
public class BookController {
/**
* 添加图书(方式一)
* PostMapping: 是SpringBoot新增注解,由RequestMapping和RequestMethod.POST组合而成
*/
//@RequestMapping(value = "/addBook",method = RequestMethod.POST)
@PostMapping(value = "/addBook1")
public Object addBook1(Book book){
System.out.println("添加图书1...入参为: "+book);
Map<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg","OK");
return result;
}
/**
* 添加图书(方式二)
* 如果后台请求参数使用@RequestBody修饰那么前端发送数据需要设置请求头headers
* Content-Type: application/json
*/
@PostMapping(value = "/addBook2")
public Object addBook2(@RequestBody Book book){
System.out.println("添加图书2...入参为: "+book);
Map<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg","OK");
return result;
}
/**
* 删除图书(方式一)
*/
@DeleteMapping(value = "/deleteBook1")
public Object deleteBook1(Integer bookId){
System.out.println("删除图书1...入参为: "+bookId);
Map<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg","OK");
return result;
}
/**
* 删除图书(方式二)
*/
@DeleteMapping(value = "/deleteBook2/{bookId}")
public Object deleteBook2(@PathVariable("bookId") Integer bookId){
System.out.println("删除图书2...入参为: "+bookId);
Map<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg","OK");
return result;
}
/**
* 更新图书(方式一)
*/
@PutMapping(value = "/updateBook1")
public Object updateBook1(Book book){
System.out.println("修改图书1...入参为: "+book);
Map<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg","OK");
return result;
}
/**
* 更新图书(方式二)
*/
@PutMapping(value = "/updateBook2")
public Object updateBook2(@RequestBody Book book){
System.out.println("修改图书2...入参为: "+book);
Map<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg","OK");
return result;
}
/**
* 查询图书(方式一)
*/
@GetMapping(value = "/queryBooks1")
public Object queryBooks1(int pageNo,int pageSize){
System.out.println("查询图书1...入参为: pageNo:"+pageNo+" pageSize:"+pageSize);
Book book1 = new Book(1001,"三国演义",66.66);
Book book2 = new Book(1003,"红楼梦",99.66);
Book book3 = new Book(1002,"水浒传",22.66);
List<Book> books = new ArrayList<>();
books.add(book1);
books.add(book2);
books.add(book3);
return books;
}
/**
* 查询图书(方式二)
*/
@GetMapping(value = "/queryBooks2/{pageNo}/{pageSize}")
public Object queryBooks2(@PathVariable("pageNo") int pageNo,@PathVariable("pageSize") int pageSize){
System.out.println("查询图书2...入参为: pageNo:"+pageNo+" pageSize:"+pageSize);
Book book1 = new Book(1001,"三国演义",66.66);
Book book2 = new Book(1003,"红楼梦",99.66);
Book book3 = new Book(1002,"水浒传",22.66);
List<Book> books = new ArrayList<>();
books.add(book1);
books.add(book2);
books.add(book3);
return books;
}
}
- CRUD代码
- 文件上传/下载
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/**
* 单文件上传
*/
@PostMapping(value = "/uploadFile")
public Object uploadFile(@RequestParam("file")MultipartFile file) throws IOException {
System.out.println("文件上传...文件名为: "+file.getOriginalFilename());
//将文件保存到指定位置 比如保存到D盘
file.transferTo(new File("d:\\"+file.getOriginalFilename()));
Map<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg","OK");
return result;
}
/**
* 多文件文件上传
*/
@PostMapping(value = "/uploadFiles")
public Object uploadFiles(@RequestParam("file")MultipartFile[] files) throws IOException {
for (MultipartFile file : files) {
System.out.println("文件上传...文件名为: "+file.getOriginalFilename());
//将文件保存到指定位置 比如保存到D盘
file.transferTo(new File("d:\\"+file.getOriginalFilename()));
}
Map<String,Object> result = new HashMap<>();
result.put("code",200);
result.put("msg","OK");
return result;
}
/**
* 文件下载
*/
@GetMapping(value = "/downLoad")
public ResponseEntity<byte[]> downLoad() throws IOException {
System.out.println("文件下载...");
//获取下载文件的输入流
BufferedInputStream in = new BufferedInputStream(new FileInputStream(new File("d:\\logo-footer.png")));
//创建下载缓冲区
byte[] body = new byte[in.available()];
//将输入流数据读入缓冲区
in.read(body);
//创建响应头
HttpHeaders headers = new HttpHeaders();
//构建文件名称
String fileName="abc.png";
headers.add("Content-Disposition", "attachment;filename="+fileName);
//创建响应状态码
HttpStatus ok = HttpStatus.OK;
ResponseEntity<byte[]> response = new ResponseEntity<>(body,headers,ok);
return response;
}
- 文件上传/下载
- JSON数据交互
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
33public class User {
private Integer userId;
private String userName;
private Date hireDate;
}
/**
* JSON数据返回时间格式处理
* 当前jackson返回时间格式不符合我们中国人的习惯可以修改成我们的习惯格式
* 当前jackson返回时间和我们国家的东八区区时查了八个小时所以需要将时区修改为东八区(GMT+8)
*/
@GetMapping(value = "/queryUserJson")
public List<User> queryUserJson(){
List<User> list = new ArrayList<>();
User u1 = new User(1001,"user01",new Date());
User u2 = new User(1002,"user02",new Date());
User u3 = new User(1003,"user03",new Date());
list.add(u1);
list.add(u2);
list.add(u3);
return list;
}
修改方式:
1、可以在返回值类型的类中添加jackson注解
public class User {
private Integer userId;
private String userName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date hireDate;
}
2、采用全局配置方式
- JSON数据交互
第5节 SpringBoot的常用配置
1 | # 服务器端口号 |
第三章 启动方式
第1节 热部署
- 添加maven依赖
1
2
3
4
5<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional><!--设置子项是否依赖,如果不设置为true热加载也不会成功-->
</dependency> - 配置maven插件
1
2
3
4
5
6
7<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork><!-- 如果不配置fork热加载不会成功 -->
</configuration>
</plugin> - IDEA设置
1
21、打开IDEA 在当前路径下: File->Settings->Build, Execution, Deployment->Compiler 勾选Build project automatically 然后确定
2、继续在使用快捷键(Ctrl+Shift+Alt+/)打开面板,选择Registry勾选compiler.automake.allow.when.app.running
第2节 启动方式
1 | 1. 使用自带的main方法启动 |
第四章 配置文件
第1节 SpringBoot配置文件的两种格式
1 | 1、application.properties |
第2节 配置文件中自定义配置属性的获取
- @Value(“${名称}”) 获取单个属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
231. 在application.properties自定义配置文件
# 自定义配置
com.qianfeng.springboot=hello
name=Tom
age=18
2. 在我们的Java类中获取
@Value("${com.qianfeng.springboot}")
private String qianfeng;
@Value("${name}")
private String name;
@Value("${age}")
private Integer age;
/**
* 测试配置文件获取
*/
@GetMapping(value = "/getProperties")
public String getProperties(){
System.out.println(qianfeng);
System.out.println(name);
System.out.println(age);
return "ok";
} - @ConfigurationProperties 获取实体对象
- 自定义配置(application.yml)
1
2
3
4user:
userId: 1001
userName: Tom
hireDate: 2020-12-12 22:22:22
- 自定义配置(application.yml)
- 使用实体对象压入配置信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Component //加入IOC容器进行实例化
@ConfigurationProperties(prefix = "user") //指定配置前缀
public class User {
private Integer userId;
private String userName;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //时间格式
private Date hireDate;
}
注意:当使用ConfigurationProperties注解时会报一个警告,官网解释需添加一个依赖即可
<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@Autowired
private User user;
/**
* 测试配置文件获取
*/
@GetMapping(value = "/getProperties")
public String getProperties(){
System.out.println(user);
return "ok";
}
- 测试
第3节 SpringBoot多环境配置
多环境配置(方式一)
1
2
3
4
5
6
7SpringBoot多环境管理必须要遵循SpringBoot官方配置文件命名规则 application-{profile}.properties或者application-{profile}.yml
eg:
application-dev.yml 开发环境
application-test.yml 测试环境
application-pro.yml 生产环境按照这个格式编写好的配置默认不会被SpringBoot执行,在运行时springboot还会默认执行application.yml文件,可以在appliaction.yml配置文件中设置Spring.profiles.active=dev 这样在项目运行时就会走application-dev.yml配置,但是这样还是需要频繁的修改配置,所以SpringBoot还提供了一个方式在不修改配置的情况下动态的进行配置文件修改
多环境配置(方式二)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20在同一个文件中进行多环境配置的时候使用 --- 三个横线方式进行分割,在yml文件中配置
spring:
profiles:
active: dev
---
spring:
profiles: dev
server:
port: 8080
---
spring:
profiles: test
server:
port: 8081
---
spring:
profiles: pro
server:
port: 8082使用命令动态的选择环境
1
2
3
4
51、使用命令将项目打成jar包,然后使用java -jar xxx.jar 运行
java -jar thymeleaf01-0.0.1-SNAPSHOT.jar --spring.profiles.active=pro
2、使用maven命令运行(2.x版本)
mvn spring-boot:run -Dspring-boot.run.profiles=testmaven命令地址
1
https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/html/#run-example-active-profiles
第五章 异常处理
第1节 单个异常处理
1 | //设置指定捕获当前类中的哪些异常,当前设置为Exception异常以及其子类都会被捕获到 |
第2节 统一异常处理
1 | @RestController |
- 异常的优先级
1
先查找当前类里面的异常处理,如果不存在在去统一异常处理类中匹配
第六章 日志设置
第1节 SpringBoot的日志
1 | SpringBoot默认使用的是Slf4J+logback的日志 |
1.1 日志的配置
1 | # 修改日志级别 |
第2节 SpringBoot的统一日志处理
2.1 使用AOP进行日志处理
1 | <dependency> |
2.2 使用
1 | @Aspect |
第七章 Spring5.x与Spring4.x版本的差异化
Spring5.x版本相比较于4.x版本发生了很大的变化
第1节 注解的变化
1 | 1. @RestController : 组合注解(@ResponseBody + @Controller) |
第2节 配置文件的变化
1 | 传统的配置比如ssm框架采用的xml配置,而SpringBoot官方不建议使用xml配置,而是采用传统的API和注解的方式进行配置 |
举一个例子(整合一个不遵循SpringBoot约定优于配置的依赖[非start依赖])
- 添加依赖
1
2
3
4
5
6
7
8
9
10
11<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency> - 创建配置文件
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
35applicaton.yml
username1: root
password1: root
jdbcurl1: jdbc:mysql:///ssm
/**
* 如果maven的依赖满足start这样的类型,多半是不需要配置的,这样的类型的依赖可以和SpringBoot直接整合到一起
* 但是有一些框架或者工具没有提供start这样类型的依赖,在加入依赖之后不会自动整合到SpringBoot上面,这时候就需要我们配置
*/
//@ImportResource(locations = {"classpath:bean.xml"})
@Configuration
public class DruidConfiguration {
@Value("${username1}") //在进行配置时候不要取有歧义的名字
private String username;
@Value("${password1}")
private String password;
@Value("${jdbcurl1}")
private String jdbcurl;
/**
* 配置Druid
*/
//@Primary //自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
@Bean //将方法的返回值加入到IOC容器中,和我们的xml配置中的bean标签功能相同,内部有name属性,设置为IOC容器中的对象取别名
public DataSource createDruid(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(jdbcurl);
System.out.println("------------"+dataSource);
return dataSource;
}
} - 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@Autowired //从IOC容器中获取对象
private DataSource dataSource;
System.out.println("从ioc容器中获取对象:"+dataSource);
//获取数据库连接
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
String sql="SELECT * FROM user";
ResultSet rs = statement.executeQuery(sql);
while(rs.next()){
int userId = rs.getInt("user_id");
System.out.println("userId="+userId);
System.out.println();
}
第八章 CRUD练习(API版本)
- 添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency> - 基础配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17@Mapper: SpringBoot的Mapper层需要添加此注解
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=utf-8&useUnicode=true&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
# mybatis配置
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
mybatis.type-aliases-package=com.ssm.testdemo.entity
mybatis.config-location=classpath:mybatis-config.xml
# 中文编码(处理请求)
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8 - 单元测试(2.1.x版本)
1
2@RunWith(SpringRunner.class)
@SpringBootTest
第九章 Thymeleaf模板
Thymeleaf是一个web端的并且独立的Java模板引擎,他能够处理HTML、XML、JavaScript、CSS以及纯文本,Thymeleaf的理念是创建一种优雅和易维护的模板,为了实现这一点,它建立在自然模板之上,将逻辑注入到模板文件中,还不会影响到模板被用作设计原型。Thymeleaf一开始就设计了Web标准,SpringBoot官方推荐使用Thymeleaf 而不是JSP
第1节 快速入门
- 添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> - 配置文件
1
2
3
4
5spring:
thymeleaf:
mode: HTML
cache: false
encoding: utf-8 - 创建页面
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在templates目录下创建index.html和result.html
1、index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<!--第一个页面请求-->
<a href="/helloworld">Hello World</a>
</body>
</html>
-------------------------------------------------------------------
2、result.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>结果页</title>
</head>
<body>
<!--结果页面-->
<h1>结果页面</h1>
</body>
</html> - 创建控制器(@Controller)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* @Author 枫桥夜泊1990
* @BLOG https://hd1611756908.github.io/
* @BSITE https://space.bilibili.com/514155929/
* @DATE 2020/8/8
*/
@Controller
public class UserController {
/**
* 快速入门
*/
@GetMapping(value = "/helloworld")
public String helloworl(){
System.out.println("第一个thymeleaf请求...");
return "result";
}
} - 刷新前端页面快捷键
1
ctrl+F9
第2节 常见语法
- index.html
1
<a href="/HelloThymeleaf">HelloThymeleaf</a>
- result.html
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<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>结果页</title>
</head>
<body>
<!--结果页面-->
<h1>结果页面</h1>
<!--显示普通文本,从域对象中获取-->
<p th:text="${username}"></p>
<hr>
<!--显示带有样式的普通文本-->
<p th:utext="${desc}"></p>
<hr>
<!--显示对象,数据处理(thymeleaf提供了内置对象API可以操作数据,比如对前端显示时间的格式化)-->
<div>
<p th:text="${user.userId}"></p>
<p th:text="${user.userName}"></p>
<p th:text="${user.createTime}"></p>
<p th:text="${#dates.format(user.createTime,'yyyy-MM-dd HH:mm:ss')}"></p>
</div>
<hr>
<!--内置域对象-->
<p th:text="${#httpServletRequest.getAttribute('password')}"></p>
<hr>
<!--数据遍历 list集合-->
<table>
<tr>
<td>No.</td>
<td>UID</td>
<td>姓名</td>
<td>创建时间</td>
<td>偶数</td>
<td>奇数</td>
</tr>
<tr th:each="x,y:${users}">
<td th:text="${y.index+1}"/>
<td th:text="${x.userId}"/>
<td th:text="${x.userName}"/>
<td th:text="${x.createTime}"/>
<td th:text="${y.even}"/><!--偶数-->
<td th:text="${y.odd}"/><!--奇数-->
</tr>
</table>
<hr>
<!--数据遍历 map集合-->
<table>
<tr>
<td>No.</td>
<td>UID</td>
<td>姓名</td>
<td>创建时间</td>
<td>偶数</td>
<td>奇数</td>
</tr>
<tr th:each="x,y:${map}">
<td th:text="${y.index+1}"/>
<td th:text="${x.value.userId}"/>
<td th:text="${x.value.userName}"/>
<td th:text="${x.value.createTime}"/>
<td th:text="${y.even}"/><!--偶数-->
<td th:text="${y.odd}"/><!--奇数-->
</tr>
</table>
</body>
</html> - 控制器
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@GetMapping(value = "/HelloThymeleaf")
public String HelloThymeleaf(Model model, HttpServletRequest request){
//显示普通文本,从域对象中获取
model.addAttribute("username","Tom");
//显示带有样式的普通文本
model.addAttribute("desc","<span style='color:red'>你好,中国</span>");
//显示对象,数据处理(thymeleaf提供了内置对象API可以操作数据,比如对前端显示时间的格式化)
User user = new User(1001,"user01",new Date());
model.addAttribute("user",user);
//内置域对象
request.setAttribute("password","123456");
//数据遍历 list
User user1 = new User(1002,"user02",new Date());
User user2 = new User(1003,"user03",new Date());
User user3 = new User(1004,"user04",new Date());
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
users.add(user3);
model.addAttribute("users",users);
//数据遍历 map
Map<String,User> map = new HashMap<>();
map.put("user01",user1);
map.put("user02",user2);
map.put("user03",user3);
model.addAttribute("map",map);
return "result";
}
第3节 路径处理
1 | <script type="text/javascript" th:src="@{/js/main.js}"></script> |
第4节 条件语句
1 | th:if="boolean" th:if的表达式需为boolean值。如果为true,则标签显示,如果为false,则标签不显示 |
第5节 页面引入
- 页面引入介绍
1
我们常常需要在一个页面当中引入另一个页面,例如,公用的导航栏以及页脚页面。thymeleaf中提供了两种方式进行页面引入
- 引入方式一[th:replace(替换全部)]
- 引入方式二[th:include(替换内容)]
1
2
3
4
5
6
7
8
9
101、新建需要被引入的页面文件,路径为"/templates/footer.html"
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<footer th:fragment="companyInfo">
<p>设为首页 ©2018 SpringBoot 使用<span th:text="${name}"/>前必读意见反馈京ICP证030173号 </p>
</footer>
2、在目标页面引入footer.html有两种方式
- 2.1 <div th:include="footer :: companyInfo" th:with="name=${username}"/> 可以设置参数
- 2.2 <div th:replace="footer :: companyInfo"/> 设置参数无效,所以直接引入即可
第6节 使用thymeleaf实现CRUD
1 | 请求路径的参数设置 |
第十章 Shiro整合(注解与传统xml对比)
Spring5.x版本在SpringBoot中官方推荐使用Java配置的方式来进行与第三方框架的集成
我们这里以springBoot和Shiro的整合方式来讲解Java的配置.
- 版本介绍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15SpringBoot版本 : 2.1.14.RELEASE
shiro-spring版本 : 1.5.0
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.0</version>
</dependency>
//如果使用Shiro的注解功能需要添加springboot的aop依赖
//同时在application.properties中配置spring.aop.proxy-target-class=true,有的版本不需要配置(当前的2.1.17.RELEASE不需要)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency> - 整合用到的核心注解
1
2@Bean: 可以修饰方法和注解,功能和<bean>标签相同,将对象加入到IOC容器中
@Configuration : 修饰类,被次注解修饰的类,会被Spring扫描到,被认定为是一个配置类 - 集成Shiro配置代码
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
69
70
71
72
73
74
75
76/**
* @Author 枫桥夜泊1990
* @BLOG https://hd1611756908.github.io/
* @BSITE https://space.bilibili.com/514155929/
* @DATE 2020/8/12
*/
@Configuration
public class ShiroConfig {
/**
* 将Realm注入到IOC中
* @return
*/
@Bean
public UserRealm createUserRealm(HashedCredentialsMatcher hashedCredentialsMatcher){
//创建Realm实例
UserRealm userRealm = new UserRealm();
//设置加密规则,HashedCredentialsMatcher对象通过给方法入参注入到此方法中
userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return userRealm;
}
@Bean
public HashedCredentialsMatcher createHashedCredentialsMatcher(){
//创建加密对象
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//加密方式
credentialsMatcher.setHashAlgorithmName("md5");
//迭代次数
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
/**
* 安全管理器
*/
@Bean
public SecurityManager createSecurityManager(UserRealm userRealm){
//创建web的安全管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//配置realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 将ShiroFilter加入到IOC
*/
@Bean
public ShiroFilterFactoryBean createShiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
shiroFilter.setLoginUrl("/unauthentication");//未认证
shiroFilter.setUnauthorizedUrl("/unauthorized");//未授权控制器
Map<String,String> map = new HashMap<String, String>();
map.put("/login","anon");
map.put("/home1","authc,roles[admin]");
map.put("/home2","authc,roles[admin],perms[user:update]");
map.put("/**","authc");
//如果使用注解就不需要再这里配置拦截规则,可以注释掉
shiroFilter.setFilterChainDefinitionMap(map);
return shiroFilter;
}
/**
* 注解生效
* 如果使用Shiro注解,需要配置AuthorizationAttributeSourceAdvisor,否则注解不生效
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
} - 控制器测试方法
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@RestController
public class UserController {
/**
* 登陆请求
*/
@PostMapping(value = "/login")
public String login(SysUsers sysUsers){
UsernamePasswordToken token = new UsernamePasswordToken(sysUsers.getUsername(),sysUsers.getPassword());
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
}catch (AuthenticationException e) {
e.printStackTrace();
throw new RuntimeException("用户名错误");
//return "账号或密码错误!";
} catch (AuthorizationException e) {
e.printStackTrace();
throw new RuntimeException("密码错误");
//return "没有权限";
}
return "success";
}
/**
* 测试请求
*/
@RequiresRoles({"admin"})
@GetMapping(value = "/home1")
public String index(){
System.out.println("我是home1,需要认证用户的角色为admin才能访问");
return "我是home1...";
}
/**
* 测试请求
*/
@RequiresRoles({"admin"})
@RequiresPermissions({"user:update"})
@GetMapping(value = "/home2")
public String index1(){
System.out.println("我是home2,需要认证用户的角色是admin并且权限为user:update才可以访问");
return "我是home2...";
}
/**
* 处理未认证的请求
*/
@GetMapping("/unauthentication")
public void unAuthorization(){
System.out.println("当前请求未认证...");
throw new RuntimeException("当前请求未认证...");
}
/**
* 处理未授权的请求
*/
@GetMapping("/unauthorized")
public void unauthorized(){
System.out.println("当前请求未授权...");
throw new RuntimeException("当前请求未授权...");
}
} - 统一异常处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Component
@ControllerAdvice
public class ShiroExceptionHandler {
@ResponseBody
@ExceptionHandler(value = {AuthorizationException.class})
public Object processShiroAuthorizationException(){
return "授权失败...";
}
@ResponseBody
@ExceptionHandler(value = {AuthenticationException.class})
public Object processShiroAuthenticationException(){
return "认证失败...";
}
} - 自定义的Realm
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86/**
* @Author 枫桥夜泊1990
* @BLOG https://hd1611756908.github.io/
* @BSITE https://space.bilibili.com/514155929/
* @DATE 2020/8/11
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private SysUsersService sysUsersService;
@Autowired
private SysUsersRolesService sysUsersRolesService;
@Autowired
private SysRolesService sysRolesService;
@Autowired
private SysRolesPermissionsService sysRolesPermissionsService;
@Autowired
private SysPermissionsService sysPermissionsService;
/**
* 获取授权数据
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户名
String username = principalCollection.getPrimaryPrincipal().toString();
System.out.println("username:"+username);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//通过用户名查询当前用户的所有角色
SysUsers user = sysUsersService.getUserByUsername(username);
//根据用户ID查询角色信息
List<SysUsersRoles> usersRoles = sysUsersRolesService.getSysUsersRolesByUserId(user.getUserId());
//根据角色ID查询角色名称
Set<String> roleNames = new HashSet<>();
for (SysUsersRoles usersRole : usersRoles) {
SysRoles roles = sysRolesService.getSysRolesByRoleId(usersRole.getRoleId());
roleNames.add(roles.getRoleName());
//通过角色ID查询权限列表
List<SysRolesPermissions> sysRolesPermissionss = sysRolesPermissionsService.getSysRolesPermissionsByRoleId(roles.getRoleId());
//根据权限ID查询权限名称
Set<String > permissionsNames = new HashSet<>();
for (SysRolesPermissions rolesPermissionss : sysRolesPermissionss) {
SysPermissions permissions = sysPermissionsService.getSysPermissionsByPermissionsId(rolesPermissionss.getPermissionId());
if (null!=permissions){
permissionsNames.add(permissions.getPermissionName());
}
}
//将权限信息封装进info
info.addStringPermissions(permissionsNames);
}
info.setRoles(roleNames);
return info;
}
/**
* 获取认证数据
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取用户名
String username = authenticationToken.getPrincipal().toString();
System.out.println("realm: username:"+username);
if(username==null || username.length()<=0){
throw new UnknownAccountException("当前用户不存在");
}
//通过用户名查询数据库
SysUsers user = sysUsersService.getUserByUsername(username);
if(user==null){
throw new UnknownAccountException("当前用户不存在");
}
System.out.println("====="+user);
//从数据库中获取数据,并且将查询出来的认证数据封装进alt
ByteSource salt = ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),salt,getName());
return info;
}
} - 测试(postman)