【狂神說】SpringBoot
本文章僅個人學習筆記,內容來源b站up主:
遇見狂神說
SpringBoot
自動配置:
pom.xml
- spring-boot-dependencies:核心以來在父工程中!
- 我們在寫或者引入一些springboot依賴的時候,不需要制定版本,就因為有這些版本倉庫
啟動器
- 啟動器:說白了是Springboot的啟動場景
- 比如spring-boot-starter-web,他就會幫我們自動匯入web環境所有的依賴
- springboot會將所有的功能場景,變成一個個的啟動器
- 我們要什麼用什麼功能,就只需要找到對應的啟動器就可以了starter
主程式
//@SpringBootApplication 來標註一個主程式類
//說明這是一個Spring Boot應用
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//以為是啟動了一個方法,沒想到啟動了一個服務
SpringApplication.run(SpringbootApplication.class, args);
}
}
但是**一個簡單的啟動類並不簡單!**我們來分析一下這些註解都幹了什麼
@SpringBootApplication
作用:標註在某個類上說明這個類是SpringBoot的主配置類 , SpringBoot就應該執行這個類的main方法來啟動SpringBoot應用;
進入這個註解:可以看到上面還有很多其他註解!
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// ......
}
@ComponentScan
這個註解在Spring中很重要 ,它對應XML配置中的元素。
作用:自動掃描並載入符合條件的元件或者bean , 將這個bean定義載入到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置類 ,標註在某個類上 , 表示這是一個SpringBoot的配置類;
我們繼續進去這個註解檢視
// 點進去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}
@Component
public @interface Configuration {}
這裡的 @Configuration,說明這是一個配置類 ,配置類就是對應Spring的xml 配置檔案;
裡面的 @Component 這就說明,啟動類本身也是Spring中的一個元件而已,負責啟動應用!
我們回到 SpringBootApplication 註解中繼續看。
@EnableAutoConfiguration
@EnableAutoConfiguration :開啟自動配置功能
以前我們需要自己配置的東西,而現在SpringBoot可以自動幫我們配置 ;@EnableAutoConfiguration告訴SpringBoot開啟自動配置功能,這樣自動配置才能生效;
點進註解接續檢視:
@AutoConfigurationPackage :自動配置包
結論:
1、SpringBoot在啟動的時候從類路徑下的META-INF/spring.factories中獲取EnableAutoConfiguration指定的值
2、將這些值作為自動配置類匯入容器 , 自動配置類就生效 , 幫我們進行自動配置工作;
3、整個J2EE的整體解決方案和自動配置都在springboot-autoconfigure的jar包中;
4、它會給容器中匯入非常多的自動配置類 (xxxAutoConfiguration), 就是給容器中匯入這個場景需要的所有元件 , 並配置好這些元件 ;
5、有了自動配置類 , 免去了我們手動編寫配置注入功能元件等的工作;
6、使用Spring Initializer快速建立Spring Boot專案
1、IDEA:使用 Spring Initializer快速建立專案
IDE都支援使用Spring的專案建立嚮導快速建立一個Spring Boot專案;
選擇我們需要的模組;嚮導會聯網建立Spring Boot專案;
預設生成的Spring Boot專案;
主程式已經生成好了,我們只需要我們自己的邏輯
resources資料夾中目錄結構
static:儲存所有的靜態資源; js css images;
templates:儲存所有的模板頁面;(Spring Boot預設jar包使用嵌入式的Tomcat,預設不支援JSP頁面);可以使用模板引擎(freemarker、thymeleaf);
application.properties:Spring Boot應用的配置檔案;可以修改一些預設設定;
2、STS使用 Spring Starter Project快速建立專案
第8P yaml語法講解
https://www.bilibili.com/video/BV1PE411i7CV?p=8
二、配置檔案
1、配置檔案
SpringBoot使用一個全域性的配置檔案,配置檔名是固定的;
-
application.properties
-
application.yml
配置檔案的作用:修改SpringBoot自動配置的預設值;SpringBoot在底層都給我們自動配置好;
YAML(YAML Ain’t Markup Language)
YAML A Markup Language:是一個標記語言
YAML isn’t Markup Language:不是一個標記語言;
標記語言:
以前的配置檔案;大多都使用的是 xxxx.xml檔案;
YAML:以資料為中心,比json、xml等更適合做配置檔案;
YAML:配置例子
server:
port: 8081
<server>
<port>8081</port>
</server>
2、YAML語法:
1、基本語法
k:(空格)v:表示一對鍵值對(空格必須有);
以空格的縮排來控制層級關係;只要是左對齊的一列資料,都是同一個層級的
server:
port: 8081
path: /hello
屬性和值也是大小寫敏感;
2、值的寫法
字面量:普通的值(數字,字串,布林)
k: v:字面直接來寫;
字串預設不用加上單引號或者雙引號;
“”:雙引號;不會轉義字串裡面的特殊字元;特殊字元會作為本身想表示的意思
name: “zhangsan \n lisi”:輸出;zhangsan 換行 lisi
‘’:單引號;會轉義特殊字元,特殊字元最終只是一個普通的字串資料
name: ‘zhangsan \n lisi’:輸出;zhangsan \n lisi
物件、Map(屬性和值)(鍵值對):
k: v:在下一行來寫物件的屬性和值的關係;注意縮排
物件還是k: v的方式
friends:
lastName: zhangsan
age: 20
行內寫法:
friends: {lastName: zhangsan,age: 18}
陣列(List、Set):
用- 值表示陣列中的一個元素
pets:
- cat
- dog
- pig
行內寫法
pets: [cat,dog,pig]
第9P 給屬性賦值的幾種方式
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
3、配置檔案值注入
配置檔案
person:
lastName: hello
age: 18
boss: false
birth: 2017/12/12
maps: {k1: v1,k2: 12}
lists:
- lisi
- zhaoliu
dog:
name: 小狗
age: 12
javaBean:
/**
* 將配置檔案中配置的每一個屬性的值,對映到這個元件中
* @ConfigurationProperties:告訴SpringBoot將本類中的所有屬性和配置檔案中相關的配置進行繫結;
* prefix = "person":配置檔案中哪個下面的所有屬性進行一一對映
*
* 只有這個元件是容器中的元件,才能容器提供的@ConfigurationProperties功能;
*
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
1、properties配置檔案在idea中預設utf-8可能會亂碼
2、@Value獲取值和@ConfigurationProperties獲取值比較
配置檔案yml還是properties他們都能獲取到值;
如果說,我們只是在某個業務邏輯中需要獲取一下配置檔案中的某項值,使用@Value;
如果說,我們專門編寫了一個javaBean來和配置檔案進行對映,我們就直接使用@ConfigurationProperties;
第10P JSR303校驗
[ https://www.bilibili.com/video/BV1PE411i7CV?p=10]
3、配置檔案注入值資料校驗
@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {
/**
* <bean class="Person">
* <property name="lastName" value="字面量/${key}從環境變數、配置檔案中獲取值/#{SpEL}"></property>
* <bean/>
*/
//lastName必須是郵箱格式
@Email
//@Value("${person.last-name}")
private String lastName;
//@Value("#{11*2}")
private Integer age;
//@Value("true")
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
@ImportResource:匯入Spring的配置檔案,讓配置檔案裡面的內容生效;
Spring Boot裡面沒有Spring的配置檔案,我們自己編寫的配置檔案,也不能自動識別;
想讓Spring的配置檔案生效,載入進來;@ImportResource標註在一個配置類上
@ImportResource(locations = {"classpath:beans.xml"})
匯入Spring的配置檔案讓其生效
SpringBoot推薦給容器中新增元件的方式;推薦使用全註解的方式
1、配置類**@Configuration**------>Spring配置檔案
2、使用**@Bean**給容器中新增元件
/**
* @Configuration:指明當前類是一個配置類;就是來替代之前的Spring配置檔案
*
* 在配置檔案中用<bean><bean/>標籤新增元件
*
*/
@Configuration
public class MyAppConfig {
//將方法的返回值新增到容器中;容器中這個元件預設的id就是方法名
@Bean
public HelloService helloService02(){
System.out.println("配置類@Bean給容器中新增元件了...");
return new HelloService();
}
}
4、配置檔案佔位符
1、隨機數
${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}
2、佔位符獲取之前配置的值,如果沒有可以是用:指定預設值
person.last-name=張三${random.uuid}
person.age=${random.int}
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.hello:hello}_dog
person.dog.age=15
第11P 多環境配置及配置檔案位置
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
5、Profile
1、多Profile檔案
我們在主配置檔案編寫的時候,檔名可以是 application-{profile}.properties/yml
預設使用application.properties的配置;
2、yml支援多文件塊方式
server:
port: 8081
spring:
profiles:
active: prod
---
server:
port: 8083
spring:
profiles: dev
---
server:
port: 8084
spring:
profiles: prod #指定屬於哪個環境
3、啟用指定profile
1、在配置檔案中指定 spring.profiles.active=dev
2、命令列:
java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;
可以直接在測試的時候,配置傳入命令列引數
3、虛擬機器引數;
-Dspring.profiles.active=dev
6、配置檔案載入位置
springboot 啟動會掃描以下位置的application.properties或者application.yml檔案作為Spring boot的預設配置檔案
–file:./config/
–file:./
–classpath:/config/
–classpath:/
優先順序由高到底,高優先順序的配置會覆蓋低優先順序的配置;
SpringBoot會從這四個位置全部載入主配置檔案;互補配置;
7、外部配置載入順序
SpringBoot也可以從以下位置載入配置; 優先順序從高到低;高優先順序的配置覆蓋低優先順序的配置,所有的配置會形成互補配置
1.命令列引數
所有的配置都可以在命令列上進行指定
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc
多個配置用空格分開; --配置項=值
2.來自java:comp/env的JNDI屬性
3.Java系統屬性(System.getProperties())
4.作業系統環境變數
5.RandomValuePropertySource配置的random.*屬性值
由jar包外向jar包內進行尋找;
優先載入帶profile
6.jar包外部的application-{profile}.properties或application.yml(帶spring.profile)配置檔案
7.jar包內部的application-{profile}.properties或application.yml(帶spring.profile)配置檔案
再來載入不帶profile
8.jar包外部的application.properties或application.yml(不帶spring.profile)配置檔案
9.jar包內部的application.properties或application.yml(不帶spring.profile)配置檔案
10.@Configuration註解類上的@PropertySource
11.通過SpringApplication.setDefaultProperties指定的預設屬性
所有支援的配置載入來源;
第12P 多環境配置及配置檔案位置
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
8、自動配置原理
配置檔案到底能寫什麼?怎麼寫?自動配置原理;
配置檔案能配置的屬性參照
1、原理
1、SpringBoot啟動會載入大量的自動配置類
2、我們看我們需要的功能有沒有在SpringBoot預設寫好的自動配置類當中;
3、我們再來看這個自動配置類中到底配置了哪些元件;(只要我們要用的元件存在在其中,我們就不需要再手動配置了)
4、給容器中自動配置類新增元件的時候,會從properties類中獲取某些屬性。我們只需要在配置檔案中指定這些屬性的值即可;
**xxxxAutoConfigurartion:自動配置類;**給容器中新增元件
xxxxProperties:封裝配置檔案中相關屬性;
開啟debug可以在啟動的時候在控制檯輸出配置
debug: true
第35P 使用者認證和授權
[ https://www.bilibili.com/video/BV1PE411i7CV?p=35]
SpringSecurity(安全)
安全簡介
在 Web 開發中,安全一直是非常重要的一個方面。安全雖然屬於應用的非功能性需求,但是應該在應用開發的初期就考慮進來。如果在應用開發的後期才考慮安全的問題,就可能陷入一個兩難的境地:一方面,應用存在嚴重的安全漏洞,無法滿足使用者的要求,並可能造成使用者的隱私資料被攻擊者竊取;另一方面,應用的基本架構已經確定,要修復安全漏洞,可能需要對系統的架構做出比較重大的調整,因而需要更多的開發時間,影響應用的釋出程式。因此,從應用開發的第一天就應該把安全相關的因素考慮進來,並在整個應用的開發過程中。
市面上存在比較有名的:Shiro,Spring Security !
認證,授權
認識SpringSecurity
Spring Security 是針對Spring專案的安全框架,也是Spring Boot底層安全模組預設的技術選型,他可以實現強大的Web安全控制,對於安全控制,我們僅需要引入 spring-boot-starter-security 模組,進行少量的配置,即可實現強大的安全管理!
記住幾個類:
- WebSecurityConfigurerAdapter:自定義Security策略
- AuthenticationManagerBuilder:自定義認證策略
- @EnableWebSecurity:開啟WebSecurity模式
- 引入 Spring Security 模組
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 編寫基礎配置類
package com.kuang.config;
@EnableWebSecurity // 開啟WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
- 定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定製請求的授權規則
// 首頁所有人可以訪問
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
}
- 測試一下:發現除了首頁都進不去了!因為我們目前沒有登入的角色,因為請求需要登入的角色擁有對應的許可權才可以!
- 在configure()方法中加入以下配置,開啟自動配置的登入功能!
// 開啟自動配置的登入功能
// "/login" 請求來到登入頁
// /login?error 重定向到這裡表示登入失敗
http.formLogin();
此時如果沒有許可權就會跳轉至登陸頁
- 定義認定規則,重寫configure(AuthenticationManagerBuilder auth)方法
//認證
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//資料正常應該從資料庫中取,現在從記憶體中取
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");
這裡設定密碼加密auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())否則會報There is no PasswordEncoder mapped for the id "null"錯誤,建立的每個使用者也必須新增密碼加密**.password(new BCryptPasswordEncoder().encode(“123”))**
- 測試,發現,登入成功,並且每個角色只能訪問自己認證下的規則!搞定
第36P 許可權控制和登出
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
許可權控制和登出
登出
- 開啟自動配置的登出的功能
//定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
//....
//開啟自動配置的登出的功能
// /logout 登出請求
http.logout();
}
- 我們在前端,增加一個登出的按鈕,index.html 導航欄中
<a class="btn btn-primary" th:href="@{/logout}">登出</a>
- 測試發現,點選登出按鈕後會跳轉到登陸頁面,此時想要在登出成功後跳轉到指定頁面需要在請求後新增.logoutSuccessUrl("/");
// .logoutSuccessUrl("/"); 登出成功來到首頁
http.logout().logoutSuccessUrl("/");
許可權控制
**需求:**使用者沒有登入的時候,導航欄上只顯示登入按鈕,使用者登入之後,導航欄可以顯示登入的使用者資訊及登出按鈕!還有就是,比如admin這個使用者,它只有 vip2,vip3功能,那麼登入則只顯示這兩個功能,而vip1的功能選單不顯示!
- 匯入maven依賴
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
- 匯入名稱空間
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
- 修改index頁面,增加判斷
<div>
<!--如果未登入則顯示以下內容-->
<div sec:authorize="!isAuthenticated()">
<a class="btn btn-primary" th:href="@{/toLogin}">登陸</a>
</div>
<!--如果已登入則顯示以下內容-->
<div sec:authorize="isAuthenticated()">
使用者名稱<p sec:authentication="name"></p>
角色<p sec:authentication="principal.authorities"></p>
<a class="btn btn-primary" th:href="@{/logout}">登出</a>
</div>
</div>
<!--如果使用者擁有這個角色,則顯示該div內的內容,如果沒有則不顯示-->
<div class="div" sec:authorize="hasRole('vip1')">
<a th:href="@{/level1/1}">level1-1</a><br/>
<a th:href="@{/level1/2}">level1-2</a>
</div>
<div class="div" sec:authorize="hasRole('vip2')">
<a th:href="@{/level2/1}">level2-1</a><br/>
<a th:href="@{/level2/2}">level2-2</a>
</div>
<div class="div" sec:authorize="hasRole('vip3')">
<a th:href="@{/level3/1}">level3-1</a><br/>
<a th:href="@{/level3/2}">level3-2</a>
</div>
- 如果登出404了,就是因為它預設防止csrf跨站請求偽造,因為會產生安全問題,我們可以將請求改為post表單提交,或者在spring security中關閉csrf功能;我們試試:在 配置中增加 http.csrf().disable();
第37P 記住我及首頁配置
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
記住我
- 開啟記住我功能
//定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
//。。。。。。。。。。。
//記住我,儲存2周
http.rememberMe();
}
- 測試,關閉瀏覽器再次開啟使用者依舊存在
本質上是儲存到cookie,通過瀏覽器審查元素的application中可以看到 - 如果使用自己的頁面中的按鈕,可以給按鈕設定name,再在配置後面加上如下方法
http.rememberMe().rememberMeParameter("remember");
定製登入頁
- 在配置中設定,用.loginProcessingUrl("/login")並將前端form表單的action設定為括號內相同即可,但如果前端使用者名稱和密碼前後端不一樣,則需要進行設定,括號內的屬性為前端頁面的name屬性
http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login");
第38P Shiro 快速開始
[ https://www.bilibili.com/video/BV1PE411i7CV?p=39]
Shiro
shiro簡介
基本功能點
Shiro 可以非常容易的開發出足夠好的應用,其不僅可以用在 JavaSE 環境,也可以用在 JavaEE 環境。Shiro 可以幫助我們完成:認證、授權、加密、會話管理、與 Web 整合、快取等。其基本功能點如下圖所示:
- Authentication:身份認證 / 登入,驗證使用者是不是擁有相應的身份;
- Authorization:授權,即許可權驗證,驗證某個已認證的使用者是否擁有某個許可權;即判斷使用者是否能做事情,常見的如:驗證某個使用者是否擁
- 有某個角色。或者細粒度的驗證某個使用者對某個資源是否具有某個許可權;
- Session Manager:會話管理,即使用者登入後就是一次會話,在沒有退出之前,它的所有資訊都在會話中;會話可以是普通 JavaSE
環境的,也可以是如 Web 環境的; - Cryptography:加密,保護資料的安全性,如密碼加密儲存到資料庫,而不是明文儲存;
- Web Support:Web 支援,可以非常容易的整合到 Web 環境;
- Caching:快取,比如使用者登入後,其使用者資訊、擁有的角色 / 許可權不必每次去查,這樣可以提高效率;
- Concurrency:shiro 支援多執行緒應用的併發驗證,即如在一個執行緒中開啟另一個執行緒,能把許可權自動傳播過去;
- Testing:提供測試支援;
- Run As:允許一個使用者假裝為另一個使用者(如果他們允許)的身份進行訪問;
- Remember Me:記住我,這個是非常常見的功能,即一次登入後,下次再來的話不用登入了。
記住一點,Shiro 不會去維護使用者、維護許可權;這些需要我們自己去設計 / 提供;然後通過相應的介面注入給 Shiro 即可。
Shiro的架構
外部
我們從外部來看 Shiro ,即從應用程式角度的來觀察如何使用 Shiro 完成工作。如下圖:
可以看到:應用程式碼直接互動的物件是 Subject,也就是說 Shiro 的對外 API 核心就是 Subject;其每個 API 的含義:
**Subject:**主體,代表了當前 “使用者”,這個使用者不一定是一個具體的人,與當前應用互動的任何東西都Subject,如網路爬蟲,機器人等;即一個抽象概念;所有 Subject 都繫結到 SecurityManager,與 Subject 的所有互動都會委託給 SecurityManager;可以把 Subject 認為是一個門面;SecurityManager 才是實際的執行者;
**SecurityManager:**安全管理器;即所有與安全有關的操作都會與 SecurityManager 互動;且它管理著所有 Subject;可以看出它是 Shiro 的核心,它負責與後邊介紹的其他元件進行互動,如果學習過 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
**Realm:**域,Shiro 從 Realm 獲取安全資料(如使用者、角色、許可權),就是說 SecurityManager 要驗證使用者身份,那麼它需要從 Realm 獲取相應的使用者進行比較以確定使用者身份是否合法;也需要從 Realm 得到使用者相應的角色 / 許可權進行驗證使用者是否能進行操作;可以把 Realm 看成 DataSource,即安全資料來源。
也就是說對於我們而言,最簡單的一個 Shiro 應用:
- 應用程式碼通過 Subject 來進行認證和授權,而 Subject 又委託給 SecurityManager;
- 我們需要給 Shiro 的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法的使用者及其許可權進行判斷。
從以上也可以看出,Shiro 不提供維護使用者 / 許可權,而是通過 Realm 讓開發人員自己注入。
內部
接下來我們來從 Shiro 內部來看下 Shiro 的架構,如下圖所示:
**Subject:**主體,可以看到主體可以是任何可以與應用互動的 “使用者”;
**SecurityManager:**相當於 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心臟;所有具體的互動都通過 SecurityManager 進行控制;它管理著所有 Subject、且負責進行認證和授權、及會話、快取的管理。
**Authenticator:**認證器,負責主體認證的,這是一個擴充套件點,如果使用者覺得 Shiro 預設的不好,可以自定義實現;其需要認證策略(Authentication Strategy),即什麼情況下算使用者認證通過了;
**Authrizer:**授權器,或者訪問控制器,用來決定主體是否有許可權進行相應的操作;即控制著使用者能訪問應用中的哪些功能;
**Realm:**可以有 1 個或多個 Realm,可以認為是安全實體資料來源,即用於獲取安全實體的;可以是 JDBC 實現,也可以是 LDAP 實現,或者記憶體實現等等;由使用者提供;注意:Shiro 不知道你的使用者 / 許可權儲存在哪及以何種格式儲存;所以我們一般在應用中都需要實現自己的 Realm;
**SessionManager:**如果寫過 Servlet 就應該知道 Session 的概念,Session 呢需要有人去管理它的生命週期,這個元件就是 SessionManager;而 Shiro 並不僅僅可以用在 Web 環境,也可以用在如普通的 JavaSE 環境、EJB 等環境;所有呢,Shiro 就抽象了一個自己的 Session 來管理主體與應用之間互動的資料;這樣的話,比如我們在 Web 環境用,剛開始是一臺 Web 伺服器;接著又上了臺 EJB 伺服器;這時想把兩臺伺服器的會話資料放到一個地方,這個時候就可以實現自己的分散式會話(如把資料放到 Memcached 伺服器);
**SessionDAO:**DAO 大家都用過,資料訪問物件,用於會話的 CRUD,比如我們想把 Session 儲存到資料庫,那麼可以實現自己的 SessionDAO,通過如 JDBC 寫到資料庫;比如想把 Session 放到 Memcached 中,可以實現自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 進行快取,以提高效能;
**CacheManager:**快取控制器,來管理如使用者、角色、許可權等的快取的;因為這些資料基本上很少去改變,放到快取中後可以提高訪問的效能
**Cryptography:**密碼模組,Shiro 提高了一些常見的加密元件用於如密碼加密 / 解密的。
shiro元件
身份驗證
身份驗證,即在應用中誰能證明他就是他本人。一般提供如他們的身份 ID 一些標識資訊來表明他就是他本人,如提供身份證,使用者名稱 / 密碼來證明。
在 shiro 中,使用者需要提供 principals (身份)和 credentials(證明)給 shiro,從而應用能驗證使用者身份:
**principals:**身份,即主體的標識屬性,可以是任何東西,如使用者名稱、郵箱等,唯一即可。一個主體可以有多個 principals,但只有一個 Primary principals,一般是使用者名稱 / 密碼 / 手機號。
**credentials:**證明 / 憑證,即只有主體知道的安全值,如密碼 / 數字證照等。
最常見的 principals 和 credentials 組合就是使用者名稱 / 密碼了。接下來先進行一個基本的身份認證。
另外兩個相關的概念是之前提到的 Subject 及 Realm,分別是主體及驗證主體的資料來源。
快速開始(helloworld)
- 匯入依賴
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
- 配置檔案(shiro.ini)
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
- log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
- Quickstart.java
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author lee
* @date 2020/8/11 - 6:24 下午
*/
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
//----------------a方法 開始----------------
//此段可能會過期,如過期用b方法
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//----------------a方法 結束----------------
//---------------b方法 開始-----------------
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
defaultSecurityManager.setRealm(iniRealm);
//---------------b方法 結束-----------------
//獲取當前的使用者物件 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("Subject=>session[" + 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.");
//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//test a typed permission (not instance-level)
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.");
}
//a (very powerful) Instance Level permission:
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!");
}
//all done - log out!
currentUser.logout();
System.exit(0);
}
}
以下方法Spring Security都有:
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated()
currentUser.getPrincipal()
currentUser.hasRole("schwartz")
currentUser.isPermitted("lightsaber:wield")
currentUser.logout();
第39P Shiro的Subject分析
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
第40P 在SpringBoot中整合Shiro
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
- 引入maven
<!--
Subject 使用者
SecurityManager 管理所有使用者
Realm 連線資料
-->
<!--shiro整合spring的包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!-- thymeleaf模板-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
- 編寫UserRealm類
//自定義的UserRealm
public class UserRealm extends AuthorizingRealm {
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("執行了=》授權");
return null;
}
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("執行了=》認證");
return null;
}
}
- 編寫Shiroconfig類(三個方法,從下往上寫)
package com.example.demo.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;
@Configuration
public class shiroConfig {
//shiroFilterFactoryBean:3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//設定安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//DefaultWebSecurityManager:2
@Bean(name="securityManager")
public DefaultWebSecurityManager getdefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
//建立realm物件:1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
- 做一些前期準備
MyController.java
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","hello,shiro");
return "index";
}
@RequestMapping("/user/add")
public String toAdd(Model model){
model.addAttribute("msg","add");
return "user/add";
}
@RequestMapping("/user/update")
public String toUpdate(Model model){
model.addAttribute("msg","add");
return "user/update";
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首頁</h1>
<p th:text="${msg}"></p>
<a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>
</body>
</html>
add.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>update</h1>
</body>
</html>
第41P Shiro實現登入攔截
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
實現登陸攔截
- 配置ShiroConfig配置類
//新增shiro的內建過濾器
/*
* anon:無需認證就可以訪問
* authc:必須認證了才可以訪問
* user:必須 記住我 才可以訪問
* perms: 擁有對某個資源的許可權才可以訪問
* role: 必須擁有角色許可權才可以訪問
*
* */
Map<String,String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//設定登入的請求
bean.setLoginUrl("/toLogin");
- 在controller類實現/toLogin請求
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
- 登入頁
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登入</h1>
<hr>
<form action="">
<p>使用者名稱<input type="text" name="username"></p>
<p>密碼<input type="text" name="password"></p>
<input type="submit">
</form>
</body>
</html>
第42P Shiro實現使用者認證
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
實現使用者認證
- 在controller配置請求
@RequestMapping("/login")
public String login(String username ,String password,Model model){
//獲取當前的使用者
Subject subject = SecurityUtils.getSubject();
//封裝使用者的登陸資料
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
subject.login(token);//執行登陸方法,如果沒有異常就說明登陸成功
return "index";
}catch (UnknownAccountException e){//使用者名稱不存在
model.addAttribute("msg","使用者名稱不存在");
return "login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密碼錯誤");
return "login";
}
}
- 在UserRealm類中配置認證
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("執行了=》認證");
//使用者名稱密碼 資料庫取
String name = "root";
String password = "123";
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
System.out.println(userToken.toString());
if(!userToken.getUsername().equals(name)){
return null; //丟擲異常 UnknownAccountException
}
return new SimpleAuthenticationInfo("",password,"");
}
- login.html 改造
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
使用者名稱驗證自己來做,密碼驗證shiro來做
第43P Shiro整合Mybatis
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
整合Mybatis
1.引入pom
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
2.建立實體類User
package com.example.demo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String id;
private String name;
private String pwd;
}
3.建立Mapper.java
package com.example.demo.mapper;
import com.example.demo.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Repository
@Mapper
public interface UserMapper {
public User queryUserByName(String name);
}
4.建立mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=繫結一個對應的Dao/Mapper介面-->
<mapper namespace="com.example.demo.mapper.UserMapper">
<!-- 查詢語句 id相當與要重寫的方法名-->
<select id="queryUserByName" resultType="User" parameterType="String">
select * from User where name = #{name}
</select>
</mapper>
5、建立service及imp
package com.example.demo.service;
import com.example.demo.pojo.User;
public interface UserService {
public User queryUserByName(String name);
}
package com.example.demo.service;
import com.example.demo.mapper.UserMapper;
import com.example.demo.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String username) {
return userMapper.queryUserByName(username);
}
}
6.測試
package com.example.demo;
import com.example.demo.service.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Autowired
UserServiceImpl userService;
@Test
void contextLoads() {
System.out.println( userService.queryUserByName("a"));
}
}
第44P Shiro請求授權實現
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
實現授權
- 對資源設定許可權
//攔截
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/add","perms[user:add]");
filterMap.put("/update","perms[user:update]");
bean.setFilterChainDefinitionMap(filterMap);
//未授權就跳轉到此請求
bean.setUnauthorizedUrl("/noauth");
- 對登陸使用者進行授權,該使用者擁有的許可權從資料庫取得
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("執行了=》授權");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//拿到當前登陸的物件
Subject subject = SecurityUtils.getSubject();
User currentUser = (User)subject.getPrincipal();//拿到User
info.addStringPermission(currentUser.getPerms());//授權
return info;
}
第45P Shiro整合Thymeleaf
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
第45P Swagger介紹及整合
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
Swagger
介面文件對於前後端開發人員都十分重要。尤其近幾年流行前後端分離後介面文件又變成重中之重。介面文件固然重要,但是由於項 目週期等原因後端人員經常出現無法及時更新,導致前端人員抱怨接 口文件和實際情況不一致。
很多人員會抱怨別人寫的介面文件不規範,不及時更新。當時當 自己寫的時候確實最煩去寫介面文件。這種痛苦只有親身經歷才會牢 記於心。
如果介面文件可以實時動態生成就不會出現上面問題。
Swagger 可以完美的解決上面的問題。
Swagger 簡介:
Swagger 是一套圍繞 Open API 規範構建的開源工具,可以幫助設 計,構建,記錄和使用 REST API。
Swagger 工具包括的元件:
- Swagger Editor :基於瀏覽器編輯器,可以在裡面編寫 Open API規範。類似 Markdown
具有實時預覽描述檔案的功能。 - Swagger UI:將 Open API 規範呈現為互動式 API 文件。用視覺化UI 展示描述檔案。
- Swagger Codegen:將 OpenAPI 規範生成為伺服器存根和客戶端 庫。通過 Swagger Codegen
可以將描述檔案生成 html 格式和 cwiki 形 式的介面文件,同時也可以生成多種言語的客戶端和服務端程式碼。
Swagger Inspector:和 Swagger UI 有點類似,但是可以返回更多 資訊,也會儲存請求的實際引數資料。
Swagger Hub:整合了上面所有專案的各個功能,你可以以專案 和版本為單位,將你的描述檔案上傳到 Swagger Hub 中。在 Swagger Hub 中可以完成上面專案的所有工作,需要註冊賬號,分免費版和收費版。
使用 Swagger,就是把相關的資訊儲存在它定義的描述檔案裡面(yml 或 json 格式),再通過維護這個描述檔案可以去更新介面文件, 以及生成各端程式碼。
Swagger整合
- 匯入maven依賴
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
- 建立SwaggerConfig.java
@Configuration
@EnableSwagger2 //開啟Swagger2
public class SwaggerConfig{
}
第48P 配置swagger資訊
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
Swagger配置
在SwaggerConfig進行如下配置即可自定義Swagger配置
@Configuration
@EnableSwagger2 //開啟Swagger2
public class SwaggerConfig{
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
private ApiInfo apiInfo(){
//作者資訊
Contact contact = new Contact("李海洋", "codekitty.cn:8000", "L18249290950@126.com");
return new ApiInfo("codekitty's Swagger API Documentation", "千里之行,始於足下", "1.0", "codekitty.cn:8000",contact , "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
}
}
第49P 配置掃描介面及開關
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
Swagger掃描介面
- Docket(DocumentationType.SWAGGER_2).select()
new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(b)//是否啟用Swagger,false則不啟用
.select()
//RequestHandlerSelectors,配置要掃描介面的方式
//basePackage,指定掃描包
// .apis(RequestHandlerSelectors.any()),掃描全部
// .apis(RequestHandlerSelectors.none()),不掃描
// withMethodAnnotation(GetMapping.class)) //掃描類上的註解
// withClassAnnotation(GetMapping.class)) //掃描方法上的註解
.apis(RequestHandlerSelectors.basePackage("com.controller"))
//Path過濾路徑
// .paths(PathSelectors.ant("/com/**"))
.build();
- 實現開發環境中使用Swagger,執行上線環境中不使用Swagger
public Docket docket(Environment environment){
//設定要顯示的Swagger環境
Profiles profiles = Profiles.of("dev","test");
//獲取專案的環境:判斷是否處在自己設定的環境中
boolean flag = environment.acceptsProfiles(profiles);
}
在properties中選擇使用的環境,將flag傳入Enable()
- 通過groupName("")配置多個Docket,(在多人開發中,每個開發者配置一個自己的Swagger,方便管理)
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("C");
}
實體類配置
@ApiModel("使用者實體類")
public class User {
@ApiModelProperty("使用者名稱")
private String username;
@ApiModelProperty("密碼")
private String password;
在Controller進行配置請求
@ApiOperation("user請求")
//只要介面中返回值存在實體類,它就會被掃描到Swagger中
@PostMapping(value = "/user")
public String user(@ApiParam("使用者名稱")String username){
return "hello" + username;
}
就可以在Swagger顯示實體類的資訊,如果屬性是private需要加get,set方法
總結:
- 我們可以通過Swagger給一些比較難理解的屬性或介面增加註釋資訊
- 介面文件實時更新
- 可以線上測試
【注意】在正式釋出時要關閉Swagger!!!可以保證安全和避免浪費效能
第51P 非同步任務
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
任務
非同步任務
定義一個Service
@Service
public class AsyncService {
//告訴spring這是非同步任務
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("資料正在處理");
}
}
定義一個請求,在請求中呼叫service中的方法
@ResponseBody
@RequestMapping("/hello")
public String hello(){
asyncService.hello();
return "OK";
}
在主入口上使用@EnableAsync開啟非同步任務
@EnableAsync
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
兩個步驟:
- 在Service的方法中使用@Async
- 在主入口上使用@EnableAsync開啟非同步任務
第52P 郵件傳送
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
郵件傳送
在properties中配置自己的郵件資訊
spring.mail.username=1012074120@qq.com
spring.mail.password=jtnhlvabxxqsbbga
spring.mail.host=smtp.qq.com
#開啟加密驗證(qq郵箱)
spring.mail.properties.mail.smtp.ssl.enable=true
簡單郵件傳送
直接呼叫SpringBoot的JavaMailSenderImpl類,使用SimpleMailMessage傳送簡單郵件
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject("你好");
simpleMailMessage.setText("Hello world");
simpleMailMessage.setTo("L18249290950@126.com");
simpleMailMessage.setFrom("1012074120@qq.com");
mailSender.send(simpleMailMessage);
}
複雜郵件傳送
呼叫mailSender.createMimeMessage()並使用MimeMessageHelper配置郵件內容,傳送即可,郵件內容後設定為true可以解析html格式的內容
public void SendMail(Boolean html,String title, String text, File file, String sendTo, String sendFrom) throws MessagingException {
//複雜郵件
MimeMessage mimeMessage = mailSender.createMimeMessage();
//組裝
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,true);
mimeMessageHelper.setSubject(title);
mimeMessageHelper.setText(text,true);//true,開啟html解析
mimeMessageHelper.addAttachment("1.jpg",file);
mimeMessageHelper.setTo(sendTo);
mimeMessageHelper.setFrom(sendFrom);
mailSender.send(mimeMessage);
}
第53P 定時執行任務
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
定時任務
Scheduled
TaskScheduler //任務排程程式
TaskExecutor //任務執行者
@EnableScheduling //開啟定時功能的註解,放在主入口
@Scheduled //什麼時候執行
cron表示式
在方法上面加上@Scheduled(cron = "0 43 14 * * ?")
並加上相應的表示式即可
常用表示式例子:
(1)0 0 2 1 * ? * 表示在每月的1日的凌晨2點調整任務
(2)0 15 10 ? * MON-FRI 表示週一到週五每天上午10:15執行作
(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每個月的最後一個星期五上午10:15執行作
(4)0 0 10,14,16 * * ? 每天上午10點,下午2點,4點
(5)0 0/30 9-17 * * ? 朝九晚五工作時間內每半小時
(6)0 0 12 ? * WED 表示每個星期三中午12點
(7)0 0 12 * * ? 每天中午12點觸發
(8)0 15 10 ? * * 每天上午10:15觸發
(9)0 15 10 * * ? 每天上午10:15觸發
(10)0 15 10 * * ? * 每天上午10:15觸發
(11)0 15 10 * * ? 2005 2005年的每天上午10:15觸發
(12)0 * 14 * * ? 在每天下午2點到下午2:59期間的每1分鐘觸發
(13)0 0/5 14 * * ? 在每天下午2點到下午2:55期間的每5分鐘觸發
(14)0 0/5 14,18 * * ? 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
(15)0 0-5 14 * * ? 在每天下午2點到下午2:05期間的每1分鐘觸發
(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44觸發
(17)0 15 10 ? * MON-FRI 週一至週五的上午10:15觸發
(18)0 15 10 15 * ? 每月15日上午10:15觸發
(19)0 15 10 L * ? 每月最後一日的上午10:15觸發
(20)0 15 10 ? * 6L 每月的最後一個星期五上午10:15觸發
(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最後一個星期五上午10:15觸發
(22)0 15 10 ? * 6#3 每月的第三個星期五上午10:15觸發
quartz
參考:https://blog.csdn.net/qq_42829628/article/details/106767803
分散式Dubbo+Zokeeper+SpringBoot
第56P 分散式系統理論
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
什麼是分散式系統?
在《分散式系統原理與範型》一書中有如下定義:“分散式系統是若干獨立計算機的集合,這些計算機對於使用者來說就像單個相關係統”;
分散式系統是由一組通過網路進行通訊、為了完成共同的任務而協調工作的計算機節點組成的系統。分散式系統的出現是為了用廉價的、普通的機器完成單個計算機無法完成的計算、儲存任務。其目的是利用更多的機器,處理更多的資料。
分散式系統(distributed system)是建立在網路之上的軟體系統。
首先需要明確的是,只有當單個節點的處理能力無法滿足日益增長的計算、儲存任務的時候,且硬體的提升(加記憶體、加磁碟、使用更好的CPU)高昂到得不償失的時候,應用程式也不能進一步優化的時候,我們才需要考慮分散式系統。因為,分散式系統要解決的問題本身就是和單機系統一樣的,而由於分散式系統多節點、通過網路通訊的拓撲結構,會引入很多單機系統沒有的問題,為了解決這些問題又會引入更多的機制、協議,帶來更多的問題。。。
第57P 什麼是RPC
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
什麼是RPC
RPC【Remote Procedure Call】是指遠端過程呼叫,是一種程式間通訊方式,他是一種技術的思想,而不是規範。它允許程式呼叫另一個地址空間(通常是共享網路的另一臺機器上)的過程或函式,而不用程式設計師顯式編碼這個遠端呼叫的細節。即程式設計師無論是呼叫本地的還是遠端的函式,本質上編寫的呼叫程式碼基本相同。
也就是說兩臺伺服器A,B,一個應用部署在A伺服器上,想要呼叫B伺服器上應用提供的函式/方法,由於不在一個記憶體空間,不能直接呼叫,需要通過網路來表達呼叫的語義和傳達呼叫的資料。為什麼要用RPC呢?就是無法在一個程式內,甚至一個計算機內通過本地呼叫的方式完成的需求,比如不同的系統間的通訊,甚至不同的組織間的通訊,由於計算能力需要橫向擴充套件,需要在多臺機器組成的叢集上部署應用。RPC就是要像呼叫本地的函式一樣去調遠端函式;
Dubbo基本概念
**服務提供者(Provider):**暴露服務的服務提供方,服務提供者在啟動時,向註冊中心註冊自己提供的服務。
**服務消費者(Consumer):**呼叫遠端服務的服務消費方,服務消費者在啟動時,向註冊中心訂閱自己所需的服務,服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一臺提供者進行呼叫,如果呼叫失敗,再選另一臺呼叫。
**註冊中心(Registry):**註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連線推送變更資料給消費者
**監控中心(Monitor):**服務消費者和提供者,在記憶體中累計呼叫次數和呼叫時間,定時每分鐘傳送一次統計資料到監控中心
呼叫關係說明
l 服務容器負責啟動,載入,執行服務提供者。
l 服務提供者在啟動時,向註冊中心註冊自己提供的服務。
l 服務消費者在啟動時,向註冊中心訂閱自己所需的服務。
l 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連線推送變更資料給消費者。
l 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一臺提供者進行呼叫,如果呼叫失敗,再選另一臺呼叫。
l 服務消費者和提供者,在記憶體中累計呼叫次數和呼叫時間,定時每分鐘傳送一次統計資料到監控中心。
第59P Dubbo-admin安裝測試
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
安裝Dubbo-admin
- 下載dubbo-admin
地址 :https://github.com/apache/dubbo-admin/tree/master - 進行打包(可以是Idea打包,也可以是命令列打包)
- 使用命令java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
【注意:zookeeper的服務一定要開啟!】 - 訪問localhost:7001即可訪問Dubbo-admin頁面
第60P 服務註冊發現實戰
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
實現跨專案訪問類
- 提供者配置檔案
#服務應用名字
dubbo.application.name=provider-server
#註冊中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服務要被註冊(掃描包)
dubbo.scan.base-packages=com.servic
-
編寫提供者的介面和實現類
-
消費者配置檔案
#消費者去哪裡拿服務需要暴露自己的名字
dubbo.application.name=customer-server
#註冊中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
- 在消費者和提供者相同的包下建立提供者的介面
- 消費者服務類
@Service //注入到容器中
public class UserService {
@Reference //遠端引用指定的服務,他會按照全類名進行匹配,看誰給註冊中心註冊了這個全類名
TicketService ticketService;
public void bugTicket(){
String ticket = ticketService.getTicket();
System.out.println("在註冊中心買到"+ticket);
}
- 編寫測試類
@Autowired
UserService userService;
@Test
public void contextLoads() {
userService.bugTicket();
}
啟動測試
-
開啟zookeeper
-
開啟dubbo-admin實現監控【可以不用做】
-
開啟服務者
-
消費者消費測試
第61P 回顧,架構!
[ https://www.bilibili.com/video/BV1PE411i7CV?p=8]
回顧,架構!
層架構+MVC
架構 -->解耦
開發框架
Spring
IOC : 控制反轉
泡溫泉,泡茶泡友
附近的人,打招呼。加微信,聊天,天天聊>約泡
浴場(容器):溫泉,茶莊泡友
直接進溫泉,就有人和你一起了!
原來我們都是自己一步步操作,現在交給容器了!我們需要什麼就去拿就可以了
AOP:切面(本質,動態代理)
為了解什麼?不影響業本來的情況下,實現動態增加功能,大量應用在日誌,事務等等
Spring是一個輕量級的Java開源框架,容器
目的:解決企業開發的複雜性問題
Spring是春天,但配置檔案繁瑣
SpringBoot
SpringBoot ,新代javaEE的開發標準,開箱即用!>拿過來就可以用,它自
動幫我們配置了非常多的東西,我們拿來即用,特性:約定大於配置!
隨著公司體系越來越大,使用者越來越多
微服務架構—>新架構
模組化,功能化!
使用者,支付,簽到,娛樂…;
人多餘多一臺伺服器解決不了就再增加一臺伺服器! --橫向擴充套件
假設A伺服器佔用98%資源B伺服器只佔用了10%.–負載均衡;
將原來的整體項,分成模組化,使用者就是一個單獨的專案,簽到也是一個單獨的專案,專案和專案之前需要通訊,如何通訊
使用者非常多而到十分少給使用者多一點伺服器,給簽到少一點伺服器
微服務架構問題?
分散式架構會遇到的四個核心題?
- 這麼多服務,客戶端該如何去訪?
- 這麼多服務,服務之間如何進行通訊?
- 這麼多服務,如何治理呢?
- 服務掛了,怎麼辦?
解決方案:SpringCloud
Springcloud是一套生態,就是來解決以上分散式架構的4個問題
想使用Spring Clould ,必須要掌握 springBoot , 因為Springcloud是基於springBoot ;
-
spring Cloud NetFlix ,出來了一套解決方案!一站式解決方案。可以直接使用
- Api閘道器 , zuul元件
- Feign --> Httpclient —> http通訊方式,同步並阻塞
- 服務註冊與發現 , Eureka
- 熔斷機制 , Hystrix
2018年年底,NetFlix 宣佈無限期停止維護。生態不再維護,就會脫節。
-
Apache Dubbo zookeeper
- API:沒有!要麼找第三方元件,要麼自己實現
- Dubbo 是一個高效能的基於ava實現的RPC通訊框架!2.6.x
- 服務註冊與發現 , zookeeper :動物管理者 ( Hadoop , Hive )
- 沒有:藉助了Hystrix
-
SpringCloud Alibaba 一站式解決方案
總而言之,要解決的問題就是4個
- API閘道器 , 服務路由
- HTTP,RPC框架,非同步呼叫
- 服務註冊與發現,高可用
- 熔斷機制,服務降級
為什麼要解決這個問題?因為網路是不可靠的
相關文章
- 【狂神說Java】網路程式設計Java程式設計
- 【狂神說】Docker(三) - 高階進階Docker
- 狂神說Java Web學習筆記_CookieJavaWeb筆記Cookie
- 狂神說Java Web學習筆記_TomcatJavaWeb筆記Tomcat
- JVM狂神說視訊學習筆記JVM筆記
- MySQL總結及JDBC__狂神說JavaMySqlJDBCJava
- 對狂神說的MybatisPlus的學習總結MyBatis
- Java基礎(向B站狂神說Java學習)Java
- Git最新教程通俗易懂----狂神說Java -- ---學習筆記GitJava筆記
- SpringBoot整合說明Spring Boot
- 看了幾集狂飆,大佬說我變了?
- 學習狂神Spring5_課堂筆記(更新中)Spring筆記
- 讓SpringBoot自動化配置不再神祕Spring Boot
- SpringBoot魔法堂:說說帶智慧提示的spring-boot-starterSpring Boot
- springboot的註解的作用說明(全)Spring Boot
- 一年狂攬20億美元,米哈遊《原神》到底做了什麼?
- SpringBoot系列教程之RedisTemplate Jedis配置說明文件Spring BootRedis
- 狂攬3.4億美元破紀錄,《原神》成9月全球最吸金手遊
- 面試官:說說SpringBoot為什麼可以使用Jar包啟動?面試Spring BootJAR
- TGA 2023 |《黑神話 悟空》定檔!《博德之門3》狂攬五項大獎
- SpringBoot Jar包瘦身 - 跟大檔案說再見!Spring BootJAR
- 朱曄和你聊Spring系列S1E2:SpringBoot並不神祕Spring Boot
- 元宇宙狂熱元宇宙
- 音樂狂3.9
- 工控安全的神總結,說出了多少人心裡的問題?
- 曹工改bug:cpu狂飆,old gc頻繁,執行緒神祕死亡連環案件調查報告GC執行緒
- 《神骰傳說》現已發售:肉鴿“寶可夢”,骰子玩出花
- 大咖說丨雲端計算:數字世界的“中樞神經”
- 建木神樹降臨,《煉仙傳說》衝元測試開啟
- 四說大資料時代“神話”:從大資料到深資料大資料
- 瘋狂學習——DP!
- 瘋狂ajax講義
- 小丑路人瘋狂吧
- 春運狂響曲
- 瘋狂的沙王
- 【深度學習基礎-08】神經網路演算法(Neural Network)上--BP神經網路例子計算說明深度學習神經網路演算法
- 五個月狂賺8.74億美元?以原神為代表的中國出海手遊是怎樣做ASO的?
- 這些豆瓣評分9.2以上的「神作」小說,你都看過嘛?