SpringBoot学习笔记

约定大于配置

为了降低Java开发的复杂性,Spring采用了以下4种关键策略:

1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;

2、通过IOC,依赖注入(DI)和面向接口实现松耦合;

3、基于切面(AOP)和惯例进行声明式编程;

4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

M:数据和业务

C:交换

V:html

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化Web项目
  • 没有冗余代码生成和XML配置的要求

一.第一个springboot程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.codelorin.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @ClassName HelloController
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/20 23:56
* @Version 1.0
*/

@RestController
public class HelloController {
@GetMapping("/")
public String hello() {
return "Hello,SpringBoot!!!";
}

}

二.自动装配原理

父项目管理所有的依赖版本,是SpringBoot的版本控制中心

1
2
3
4
5
6
7

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>

启动器spring-boot-starter

1
2
3
4
5

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

springboot-boot-starter-xxx:就是spring-boot的场景启动器

spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 star

默认的启动器

1
2
3
4
5
6
7
8
9
10
11
//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {

public static void main(String[] args) {
//以为是启动了一个方法,没想到启动了一个服务
SpringApplication.run(SpringbootApplication.class, args);
}

}

这个类主要做了以下四件事情:

1、推断应用的类型是普通的项目还是Web项目

2、查找并加载所有可用初始化器 , 设置到initializers属性中

3、找出所有的应用程序监听器,设置到listeners属性中

4、推断并设置main方法的定义类,找到运行的主类

@ComponentScan

这个注解在Spring中很重要 ,它对应XML配置中的元素。

作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

@SpringBootConfiguration

作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;

我们继续进去这个注解查看

里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!

我们回到 SpringBootApplication 注解中继续看。

@EnableAutoConfiguration :开启自动配置功能**

以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

img

三.yaml语法初解

更加优秀的文件格式

  • yaml可以给实体类赋值
1
2
3
4
5
<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
person:
name: codelorin
age: 3
happy: false
birth: 2020/1/1
maps: { k1: v1,k2: v2 }
lists:
- code
- girl
- musi
dog:
name: 五百
age: 3


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
package com.codelorin.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
* @ClassName Person
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 10:00
* @Version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}

  • @ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加

  • 松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下

  • JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性

pringboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;

1
2
3
4
5
6
7
8
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {

@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
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

@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.

日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则
  • 复杂类型封装,yml中可以封装对象 , 使用value就不支持

四.多环境配置文件

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

我们需要通过一个配置来选择需要激活的环境:

1
spring.profiles.active=dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: prod

---
server:
port: 8083
spring:
profiles: dev #配置环境的名称


---

server:
port: 8084
spring:
profiles: prod #配置环境的名称
  • 优先级1:项目路径下的config文件夹配置文件
  • 优先级2:项目路径下配置文件
  • 优先级3:资源路径下的config文件夹配置文件
  • 优先级4:资源路径下配置文件

手动制定

1
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

五.web开发

静态资源解决

  • 静态资源

    • webjars /webjars
    • public static resources
  • 优先级

    • resource>static(默认)>public

首页和图标

只要把主页放在静态资源目录下就可以了

5.1模板引擎 Thymeleaf

  • 语法

image-20210921110715177

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>测试页面</h1>

<div th:text="${msg}"></div>
<!--不转义-->
<div th:utext="${msg}"></div>

<!--遍历数据-->
<!--th:each每次遍历都会生成当前这个标签:官网#9-->
<h4 th:each="user :${users}" th:text="${user}"></h4>

<h4>
<!--行内写法:官网#12-->
<span th:each="user:${users}">[[${user}]]</span>
</h4>

5.2.整合数据库

JDBC

JdbcTemplate主要提供以下几类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  • query方法及queryForXXX方法:用于执行查询相关语句;
  • call方法:用于执行存储过程、函数相关语句。
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
package com.codelorin.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

/**
* @ClassName JdbcController
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 12:44
* @Version 1.0
*/

@RestController
public class JdbcController {
@Autowired
JdbcTemplate jdbcTemplate;

//查询employee表中所有数据
//List 中的1个 Map 对应数据库的 1行数据
//Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
@GetMapping("/list")
public List<Map<String, Object>> userList() {
String sql = "select * from user ";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}

//新增一个用户
@GetMapping("/add")
public String addUser() {
//插入语句,注意时间问题
String sql = "insert into user(name,pwd) values ('codelorin','123123')";
jdbcTemplate.update(sql);
//查询
return "addOk";
}

//修改用户信息
@GetMapping("/update/{id}")
public String updateUser(@PathVariable("id") int id) {
//插入语句
String sql = "update user set name=?,pwd=? where id=" + id;
//数据
Object[] objects = new Object[2];
objects[0] = "codelorin";
objects[1] = "123123";
jdbcTemplate.update(sql, objects);
//查询
return "updateOk";
}

//删除用户
@GetMapping("/delete/{id}")
public String delUser(@PathVariable("id") int id) {
//插入语句
String sql = "delete from employee where id=?";
jdbcTemplate.update(sql, id);
//查询
return "deleteOk";
}
}

整合数据源Druid

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
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true

#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
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
package com.codelorin.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
* @ClassName DruidConfig
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 12:55
* @Version 1.0
*/


@Configuration
public class DruidConfig {

/*
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}

@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
initParams.put("loginPassword", "123456"); //后台管理界面的登录密码

//后台允许谁可以访问
//initParams.put("allow", "localhost"):表示只有本机可以访问
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
initParams.put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问

//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}

//配置 Druid 监控 之 web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());

//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);

//"/*" 表示过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

Mybatis整合

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
<?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.codelorin.mapper.UserMapper">

<select id="queryUserList" resultType="User">
select * from user
</select>
<select id="queryUserById" resultType="User">
select * from user
where id=#{id}
</select>
<insert id="addUser" parameterType="User">
insert into user(id, name, pwd)
values (#{id}, #{name}, #{pwd})
</insert>
<update id="updateUser" parameterType="User">
update user
set name =#{name},
pwd = #{pwd},
where id = #{id}
</update>
<delete id="deleteUser" parameterType="int">
delete
from user
where id = #{id}
</delete>
</mapper>
1
2
3
4
5
6
7
8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis.type-aliases-package=com.codelorin.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

六.Spring Security安全框架

认证 授权

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

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
package com.codelorin.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
* @ClassName SecurityConfig
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 13:57
* @Version 1.0
*/

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页只有对应权限的人才能访问
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限到登录页
http.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/toLogin")
.loginProcessingUrl("/login"); // 登陆表单提交请求

http.csrf().disable();
//注销
http.logout().logoutSuccessUrl("/");
//开启记住我
//定制记住我的参数!


http.rememberMe().rememberMeParameter("remember");
}

//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("codelorin").password(new BCryptPasswordEncoder().encode("lorin")).roles("vip2", "vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("lorin")).roles("vip1", "vip2", "vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("lorin")).roles("vip1");
}
}

七.Shiro安全框架

  • 快速入门
    • Subject 用户
    • SecurityManager 管理用户
    • 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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.lang.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* Simple Quickstart application showing how to use Shiro's API.
*
* @since 0.9 RC2
*/
public class Quickstart {

private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


public static void main(String[] args) {


Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();

SecurityUtils.setSecurityManager(securityManager);

// 获取当前对象Subject
Subject currentUser = SecurityUtils.getSubject();

// 通过当前用户获取session
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");

if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}

// 判断当前的用户是否被认证
if (!currentUser.isAuthenticated()) {
// token 令牌
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// 记住我
token.setRememberMe(true);
try {
//执行登录操作
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}

//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

//测试角色
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}

//粗粒度
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

//细粒度
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}

//注销
currentUser.logout();

System.exit(0);
}
}

  • springboot集成

    认证过滤器:
    anon:无需认证即可访问,游客身份。
    authc:必须认证(登录)才能访问。
    authcBasic:需要通过 httpBasic 认证。
    user:不一定已通过认证,只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求,比如 rememberMe。

    授权过滤器:
    perms:必须拥有对某个资源的访问权限(授权)才能访问。
    role:必须拥有某个角色权限才能访问。
    port:请求的端口必须为指定值才可以访问。
    rest:请求必须是 RESTful,method 为 post、get、delete、put。
    ssl:必须是安全的 URL 请求,协议为 HTTPS。

    • ShiroConfig

      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
      package com.codelorin.config;

      import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
      import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;

      /**
      * @ClassName ShiroConfig
      * @Description TODO
      * @Author CodeLorin
      * @Date 2021/9/21 15:19
      * @Version 1.0
      */

      @Configuration
      public class ShiroConfig {
      //ShiroFilterFactoryBean
      public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
      ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
      //设置安全管理器
      bean.setSecurityManager(defaultWebSecurityManager);
      return bean;


      }

      //DefaultWebSecurityManager
      @Bean("name=securityManager")
      public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
      DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
      //关联UserRealm
      securityManager.setRealm(userRealm);
      return securityManager;
      }

      //创建realm对象,需要自定义类
      @Bean
      public UserRealm userRealm() {
      return new UserRealm();
      }


      }

  • controller

    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
    package com.codelorin.controller;

    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;

    /**
    * @ClassName MyController
    * @Description TODO
    * @Author CodeLorin
    * @Date 2021/9/21 15:07
    * @Version 1.0
    */

    @Controller
    public class MyController {
    @RequestMapping({"/", "index"})
    public String index(Model model) {
    model.addAttribute("msg", "HelloShiro");
    return "index";
    }

    @RequestMapping("/user/add")
    public String add() {
    return "user/add";
    }

    @RequestMapping("/user/delete")
    public String delete() {
    return "user/delete";
    }

    @RequestMapping("/toLogin")
    public String toLogin() {
    return "login";
    }

    @RequestMapping("/login")
    public String login(String name, String pwd, Model model) {
    //获取当前用户
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(name, pwd);
    //执行登录方法
    try {
    subject.login(token);
    return "index";
    } catch (UnknownAccountException e) {
    model.addAttribute("msg", "用户名错误");
    return "login";
    } catch (IncorrectCredentialsException e) {
    model.addAttribute("msg", "密码错误");
    return "login";
    }

    }

    @RequestMapping("/unauth")
    @ResponseBody
    public String unauth() {
    return "未授权没有访问权限";
    }
    }

  • ShiroConfig

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
package com.codelorin.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* @ClassName ShiroConfig
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 15:19
* @Version 1.0
*/

@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);

//anon:所有人都可以 authc:必须认证 user:记住我才可以访问 role:某个用户权限才可以访问
Map<String, String> filterMap = new LinkedHashMap<>();
//filterMap.put("/user/add", "authc");

filterMap.put("/user/add", "perms[user:add]");
filterMap.put("/user/delete", "perms[user:delete]");

bean.setFilterChainDefinitionMap(filterMap);
//设置登录请求
bean.setLoginUrl("/toLogin");
//未授权页面
bean.setUnauthorizedUrl("/unauth");
return bean;


}

//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}

//创建realm对象,需要自定义类
@Bean
public UserRealm userRealm() {
return new UserRealm();
}

//整合 ShiroDialect
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}

}

  • UserRealm
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
package com.codelorin.config;

import com.codelorin.pojo.User;
import com.codelorin.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;


/**
* @ClassName UserRealm
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 15:21
* @Version 1.0
*/


public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;

@Override
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权方法");

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

//从认证获取用户信息
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
//授予用户权限
info.addStringPermission(currentUser.getPerms());
//设置session


Session session = subject.getSession();
session.setAttribute("loginUser", currentUser);
return info;
}

@Override
//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证方法");

UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User user = userService.queryUserByName(userToken.getUsername());

if (user == null) {
return null;
}
return new SimpleAuthenticationInfo(user, user.getPwd(), "");

}
}

八.Swagger-Ui

  • 了解Swagger的概念及作用
  • 掌握在项目中集成Swagger自动生成API文档

前后端分离

  • 前端 -> 前端控制层、视图层
  • 后端 -> 后端控制层、服务层、数据访问层
  • 前后端通过API进行交互
  • 前后端相对独立且松耦合

产生的问题

  • 前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发

解决方案

  • 首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险

Swagger

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API
  • 支持多种语言 (如:Java,PHP等)
  • 官网:https://swagger.io/
1
2
3
4
5
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>>

生产环境关闭swagger

1
2
3
4
5
knife4j:
# 开启增强配置
enable: true
# 生产环境屏蔽
production: true
1
2
3
4
5
6
7
any() // 扫描所有,项目中的所有接口都会被扫描到
none() // 不扫描接口
// 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
withMethodAnnotation(final Class<? extends Annotation> annotation)
// 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)
basePackage(final String basePackage) // 根据包路径扫描接口
  1. 过滤什么路径我们通过PathSelectors类的4个方法来进行判断

    1. PathSelectors.any(): 所有的api都显示
    2. PathSelectors.none(): 所有的路径都不显示
    3. PathSelectors.regex(String pathRegex): 按照String的matches方法进行匹配。例如:PathSelectors.regex(“/user/*”)
    4. PathSelectors.ant(String antPattern): 按照Spring的AntPathMatcher提供的match方法进行匹配 例如:PathSelectors.ant(“/user/**“)

    AntPathMatcher.match(String pattern, String path) 可以做URLs匹配,规则如下

    ?匹配一个字符

    *匹配0个或多个字符

    ** 匹配0个或多个目录

  • 注解

只要接口返回中存在实体类就会被扫描到

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
package com.codelorin.pojo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @ClassName User
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 19:26
* @Version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value="user对象",description="用户对象user")
public class User {
@ApiModelProperty(value="用户名",name="name",example="codelorin")
private String username;
@ApiModelProperty(value="密码",name="password",example="123")
private String password;

}

常用注解

Swagger的所有注解定义在io.swagger.annotations包下

下面列一些经常用到的,未列举出来的可以另行查阅说明:

Swagger注解 简单说明
@Api(tags = “xxx模块说明”) 作用在模块类上
@ApiOperation(“xxx接口说明”) 作用在接口方法上
@ApiModel(“xxxPOJO说明”) 作用在模型类上:如VO、BO
@ApiModelProperty(value = “xxx属性说明”,hidden = true) 作用在类方法和属性上,hidden设置为true可以隐藏该属性
@ApiParam(“xxx参数说明”) 作用在参数、方法和字段上,类似@ApiModelProperty

我们也可以给请求的接口配置一些注释

1
2
3
4
5
6
@ApiOperation("接口")
@PostMapping("/kuang")
@ResponseBody
public String kuang(@ApiParam("这个名字会被返回")String username){
return username;
}

这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!

相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。

Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。

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
package com.codelorin.config;


import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;


import java.util.ArrayList;

/**
* @ClassName SwaggerConfig
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 18:19
* @Version 1.0
*/


@EnableKnife4j
@Configuration

public class SwaggerConfig {

@Bean(value = "apiV1")
public Docket apiV1() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName("v1")
.select()
.apis(RequestHandlerSelectors.basePackage("com.demo.controller.v1"))
.paths(PathSelectors.any())
.build();
}

@Bean(value = "apiV2")
public Docket apiV2() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName("v2")
.select()
.apis(RequestHandlerSelectors.basePackage("com.demo.controller.v2"))
.paths(PathSelectors.any())
.build();
}


private ApiInfo apiInfo() {
//相关信息
return new ApiInfo("Swagger3接口文档",
"如有疑问,请联系开发工程师CodeLorin",
"1.0",
"https://www.codelorin.cn",
new Contact("CodeLorin", "https://www.codelorin.cn", "codelorin@163.com"),
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}


}


九.异步开启

在启动类开启异步注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.codelorin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
//开启异步注解
@EnableAsync
@SpringBootApplication
public class SpringbootTestApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}

}

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
package com.codelorin.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
* @ClassName AsyncService
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 21:31
* @Version 1.0
*/

@Service
public class AsyncService {
@Async
public void hello() {

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("业务进行中....");
}
}
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
package com.codelorin.conrtoller;

import com.codelorin.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @ClassName HelloController
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 21:31
* @Version 1.0
*/

@RestController
public class HelloController {
@Autowired
AsyncService asyncService;

@RequestMapping("/")
public String hello() {
asyncService.hello();
return "ok";
}
}

十.邮件发送

1
2
3
4
5
6
7
spring.mail.host=smtp.163.com
spring.mail.username=bylorin@163.com
spring.mail.password=JZYFBIUAXSOTBJIM
# 开启加密验证
spring.mail.properties.mail.smtp.ssL.enable=true


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
package com.codelorin;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

@SpringBootTest
class SpringbootTestApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;

@Test
void contextLoads() {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject("你好啊!!");
simpleMailMessage.setText("这是springboot的测试");
simpleMailMessage.setTo("codelorin@163.com");
simpleMailMessage.setFrom("bylorin@163.com");
mailSender.send(simpleMailMessage);
}

@Test
void Test2() throws MessagingException {
//复杂邮件
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setSubject("你好啊!!");
helper.setText("<p style='color:red'>hello</p>", true);
helper.addAttachment("1.png", new File("C:\\Users\\lcary\\Desktop\\1.png"));
helper.setTo("codelorin@163.com");
helper.setFrom("bylorin@163.com");
mailSender.send(message);

}

}

十一.定时任务

  • @EnableScheduling //开始定时任务
  • @Scheduled //什么时候执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.codelorin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

//开启异步注解
@EnableAsync
//开启定时任务的注解
@EnableScheduling
@SpringBootApplication
public class SpringbootTestApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.codelorin.service;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

/**
* @ClassName ScheduledService
* @Description TODO
* @Author CodeLorin
* @Date 2021/9/21 22:09
* @Version 1.0
*/

@Service
public class ScheduledService {
//表达式去查就行
@Scheduled(cron = "0 41 22 * * ?")
public void hello() {
System.out.println("你被执行了!!");
}
}

十二.springboot整合redis

jedis: 采用的直连,不安全的,使用jedis pool连接池

lettuce: 采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据,更像Nio模式

1.导入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.配置连接

1
2
spring.redis.host=127.0.0.1
spring.redis.port=6379

Lili8080.!

3.