記錄SpringBoot學習之旅

文章目錄

SpringBoot

感謝秦老師 指路: 秦老師

image-20200518143037005

什麼是Spring

Spring是一個開源框架, 2003 年興起的一個輕量級的gva開發框架,作者: Rod Johnson。
Spring是爲了解決企業級應用開發的複雜性而創建的,簡化開發。

Spring是如何簡化Java開發的

爲了降低Java開發的複雜性,Spring採用了以下4種關鍵策略:

  • 基於POJO的輕量級和最小侵入性編程;
  • 通過IOC,依賴注入(DI)和麪向接口實現鬆耦合;
  • 基於切面(AOP) 和慣例進行聲明式編程;
  • 通過切面和模版減少樣式代碼;

什麼 SpringBoot

學過javaweb的同學 就知道,開發-個web應用,從最初開始接觸Servlet結合Tomcat,跑出一個Hello Wolrld程序,是要經歷特別多的步驟;後來就用了框架Struts,再後來是SpringMVC,到了現在的SpringBoot,過一兩年又會有其他web框架出現;不知道你們有沒經歷過框架不斷的演進,然後自己開發項目所有的技術也再不斷的變化、改造,反正我是都經歷過了,哈哈。言歸正傳,什麼是SpringBoot呢,就是一個javaweb的開發框架,和SpringMVC類似,對比其他javaweb框 架的好處,官方說是簡化開發,約定大於配置,you can “just run”,能迅速的開發web應用,幾行代碼開發一個http接口.

所有的技術框架的發展似乎都遵循了-條主線規律:從一個複雜應用場景衍生-種規範框架,人們只需要進行各種配置而不需要自己去實現它,這時候強大的配置功能成了優點;發展到一定程度之後,人們根據實際生產應用情況,選取其中實用功能和設計精華,重構出一些輕量級的框架;之後爲了提高開發效率,嫌棄原先的各類配置過於麻煩,於是開始提倡”約定大於配置”,進而衍生出一些一站式的解決方案。

是的這就是Java企業級應用-> J2EE-> spring-> springboot的過程。
隨着Spring不斷的發展,涉及的領域越來越多,項目整合開發需要配合各種各樣的文件,慢慢變得不那麼易用簡單,違背了最初的理念,甚至人稱配置地獄。Spring Boot正是在這樣的一個背景下被抽象出來的開發框架,目的爲了讓大家更容易的使用Spring、更容易的集成各種常用的中間件、開源軟件;

Spring Boot基於Spring開發, Spirng Boot本身並不提供Spring框架的核心特性以及擴展功能,只是用於快速、敏捷地開發新一代基於Spring框架的應用程序。也就是說,它並不是用來替代Spring的解決方案,而是和Spring框架緊密結合用於提升Spring開發者體驗的工具。SpringBoot以約定大於配置的核心思想,默認幫我們進行了很多設置,多數Spring Boot應用只需要很少的Spring配置。同時它集成了大量常用的第三方庫配置(例如Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot應用中這些第三方庫幾乎可以零配置的開箱即用,

簡單來說就是SpringBoot其實不是什麼新的框架,它默認配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架
Spring Boot 出生名門,從一開始就站在-個比較高的起點,又經過這幾年的發展,生態足夠完善,Spring Boot 已經當之無愧成爲Java 領域最熱門的技術。

SpringBoot的主要優點:

  • 爲所有的Spring開發者更快的入門
  • 開箱即用,提供各種默認配置來簡化項目配置
  • 內嵌式容器簡化web項目
  • 沒有冗餘代碼生成和XML配置的要求

Springboot starter官方文檔 https://docs.spring.io/spring-boot/docs/2.2.7.RELEASE/reference/html/using-spring-boot.html#using-boot-starter

微服務

什麼是微服務?

微服務是-種架構風格,它要求我們在開發一個應用的時候,這個應用必須構建成一系 列小服務的組合;可以通過http的方式進行互通。要說微服務架構,先得說說過去我們的單體應用架構。

單體應用架構

所謂單體應用架構(all in one)是指,我們將一個應用的中的所有應用服務都封裝在一個應用中。
無論是ERP、CRM或是其他什麼系統,你都把數據庫訪問,web訪問,等等各個功能放到一個war包內。

  • 這樣做的好處是,易於開發和測試;也十分方便部署;當需要擴展時,只需要將war複製多份,然後放到多個服務器上,再做個負載均衡就可以了。
  • 單體應用架構的缺點是,哪怕我要修改一個非常小的地方,我都需要停掉整個服務,重新打包、部署這個應用war包。特別是對於一個大型應用,我們不可能吧所有內容都放在一個應用裏面,我們如何維護、如何分I合作都是問題。

微服務架構

image

all in one的架構方式,我們把所有的功能單元放在一個應用裏面。然後我們把整個應用部署到服務器上。如果負載能力不行,我們將整個應用進行水平復制,進行擴展,然後在負載均衡。
所謂微服務架構,就是打破之前all in one的架構方式,把每個功能元索獨立出來。把獨立出來的功能元索的動態組合,需要的功能元索纔去拿來組合,需要多一些時 可以整合多個功能元素。所以微服務架構是對功能元索進行復制,而沒有對整個應用進行復制。

這樣做的好處是:

  1. 節省了調用資源
  2. 每個功能元素的服務都是一個可替換的,可獨立升級的軟件代碼.

如何構建微服務

一個大型系統的微服務架構,就像一個複雜交織的神經網絡,每-個神經元就是- -個功能元素,它們各自完成自己的功能,然後通過http相互請求調用。比如一個電商系統,查緩存、連數據庫、瀏覽頁面、結賬、支付等服務都是一個個獨立的功能服務,都被微化了,它們作爲一個個微服務共同構建了一個龐大的系統。如果修改其中的一個功能,只需要更新升級其中-個功能服務單元即可。
但是這種龐大的系統架構給部署和運維帶來很大的難度。於是,spring爲我們帶來了構建大型分佈式微服務的全套、全程產品:

  • 構建-個個功能獨立的微服務應用單元,可以使用springboot, 可以幫我們快速構建一個 應用;
  • 大型分佈式網絡服務的調用,這部分由spring cloud來完成,實現分佈式;
  • 在分佈式中間,進行流式數據計算、批處理,我們有spring cloud data flow。
  • spring爲我們想清楚了整個從開始構建應用到大型分佈式應用全流程方案。

image-20200518145905217

第一個SpringBoot程序

  1. IDEA整合了Springboot,創建項目時選擇Spring Initializer創建項目
  2. 在啓動類同級目錄下建包

image-20200518160335795

  1. 編寫controller,加上@RestController註解,就可以不用寫@Responsebody了image-20200518160712836
  2. 啓動類本身就是spring的一個組件,@SpringbootApplication整合@Springbootconfiguration,再進去是@Configuration,最底層是一個@Componentimage-20200518160904227
  3. pom依賴[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8ijOENbH-1590996141553)(upload\image-20200518160954380.png)]
  4. springboot幫我們自動導入的web依賴[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HNmUlrlx-1590996141554)(upload\image-20200518161047662.png)]

主要有四個部分:

  • 項目元數據信息:創建時候輸入的Project Metadata部分, 也就是Maven項目的基本元素,包括: groupld、 artifactld、 version、 name、 description等
  • parent:繼承spring-boot -starter-parent的依賴管理,控制版本與打包等內容
  • dependencies: 項目具體依賴,這裏包含了spring- boot-starter -web用於實現HTTP接口(該依賴中包含了Spring MVC),官網對它的描述是:使用Spring MVC構建Web (包括RESTful) 應用程序的入門者,使用Tomcat作爲默認嵌入式容器。spring-boot-starter-test用於編寫單元測試的依賴包。更多功能模塊的使用我們將在後面逐步展開。
  • build: 構建配置部分。默認使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot應用打包成JAR來直接運行。

自動裝配原理初探

pom.xml

  • spring-boot-dependencies :核心依賴在父工程中
  • 我們在寫或者引入一些Springboot依賴的時候,不需要指定版本,就是因爲有這些版本倉庫

啓動器:

  • <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spri ng-boot-starter</artifactId>
    </dependency>
    
  • 啓動器:說白了就是springboot的啓動場景

  • 比如spring-boot-starter-web,他就會幫我們自動導入web環境所有的依賴

  • springboot會將所有的功能場景,都變成一個個的啓動器

  • 如果我們要使用什麼功能,就只需要找到對應的啓動器就可以了 Starter

主程序

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloworldApplication {

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

}

註解

  • @SpringBootConfiguration	:	springboot的配置
    	@Configuration	:	spring配置類
    	@Component	:	說明這也是一個spring的組件
            
    @EnableAutoConfiguration	:	自動配置
        @AutoConfigurationPackage   	:	自動配置包
            @Import({Registrar.class})	:	自動配置'包註冊'
        @Import({AutoConfigurationImportSelector.class})	:	自動配置導入選擇   
            
    @ComponentScan	:	掃描當前主啓動類同級的包
        
    //獲取所有的配置
    List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    

    獲取候選的配置

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
            Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
            return configurations;
    }
    

    META-INF/spring.factories:自動配置的核心文件

    image-20200518164312472

image-20200518164537691

每一個自動裝配的類(bean)最終會對應一個properties或者yml配置文件,通過導入的方式進行參數綁定,以達到自動配置的效果.

@Conditional擴展註解

image-20200519143606147

結論:

雖然是有這麼多自動配置,但爲什麼不是每一個都會生效呢?原因是因爲在對應的自動配置類中會進行條件判斷’@ConditionOnxxx’,條件成立了之後配置纔會生效.例如我們導入了web的starter,web相關的配置就會生效,而我們沒有開啓AOP,那AOP的相關配置就不會生效

  1. springboot在啓動的時候,從類路徑下META-INF/spring.factories獲取指定的值
  2. 將這些自動配置的類導入容器,自動配置就會生效,幫我門進行自動配置
  3. 以前我們需要自動配置的東西,現在springboot幫我門做了
  4. 整個javaEE,解決方案和自動配置的東西都在spring-boot-autoconfigure包下
  5. 他會把所有需要導入的組件,以類名的方式返回,這些組件就會被添加到容器
  6. 容器中也會存在非常多的xxxxAutoConfiguration的文件(@Bean),就是這些類給容器導入了這個場景需要的所有組件

SpringApplication

這個類主要做了以下四個事情

  1. 推斷應用的類型是普通的項目還是web項目
  2. 查找並加載所有可用初始化器,設置到initializers屬性中
  3. 找出所有的應用程序監聽器,設置到listeners屬性中
  4. 推斷並設置main方法的定義類,找到運行的主類

查看構造器

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

springboot啓動流程

關於springboot,談談你的理解

  • 自動裝配
  • run()

SpringBoot配置

配置文件

SpringBoot使用一個全局的配置文件,配置文件名稱是固定的

  • application.properties
    • 語法結構: key=value
  • application.yaml
    • 語法結構: key: 空格 value

配置文件的作用: 修改springboot自動配置的默認值,因爲springboot在底層都給我們自動配置好了

YAML

YAML是"YAML Ain’t a Markup Language" (YAML不是一 種置標語言)的遞歸縮寫。
在開發的這種語言時,YAML的意思其實是: “Yet Another Markup Language” (仍是一種置標語言)
YAML A Markup Language :是一個標記語言
YAML isnot Markup Language :不是一個標記語言

標記語言

以前的配置文件,大多數都是使用xml來配置;比如一個簡單的端口配置,我們來對比下yaml和xml

yaml配置:

server:
	port: 8080

xml配置:

<server>
    	<port>8081<port>
</server>

YAML語法

基本語法

k:(空格) v

以此來表示一對鍵值對(空格不能省略) ;以空格的縮進來控制層級關係,只要是左邊對齊的一-列數據都是同一個層級的。
注意:屬性和值的大小寫都是十分敏感的。例子:

server:
	port: 8081
	path: /hello

值的寫法

字面量: 普通的值 [數字,布爾值,字符串]

k: v

字面量直接寫在後面就可以,字符串默認不用加上雙引號或者單引號;

“” 雙引號,不會轉義字符串裏面的特殊字符,特殊字符會作爲本身想表示的意思;

比如: name: “kang \n yuan” 輸出: kang 換行 yuan

# k=v
# 對空格的要求十分嚴格
# 普通的key-value

# 可以注入到配置類中
name: deemo

# 對象
student:
  name: deemo
  age: 21

#行內寫法
student1: {name: deemo, age: 21}

# 數組
pets:
  - cat
  - dog
  - pig

pets1: [cat,dog,pig]

@ConfigurationProperties(prefix = “yaml配置中的對象”)

把這個註解加在類上,prefix中寫yaml中對應的key,就可以爲POJO賦值,注意,k-v和POJO中的字段要一一對應

舉一反三

我們可以寫一個Mybatis的配置類,然後把配置信息寫在yaml配置文件中,加上@ConfigurationProperties註解爲Mybatis配置類注入值

如果非要用properties呢?

注意:此時必須是UTF-8

用@PropertiesSource(value=“classpath:xxx.properties”)註解,然後在字段上用@Value註解,寫SPEL表達式去除配置文件的值就可以

一點騷操作

image-20200518203737813

鬆散綁定

image-20200518204539986

結論

  • 配置yml和配置properties都可以獲取到值,強烈推薦 yml

  • 如果我們在某個業務中,只需要獲取配置文件中的某個值,可以使用一下@value

  • 如果說,我們專門編寫了-個JavaBean來和配置文件進行映射,

    就直接使用@configurationProperties,不要猶豫!

JSR303校驗

在類上加上@Validated註解開啓數據校驗

image-20200518205100431

配置文件位置優先級

image-20200518211729626

強制:必須以前綴爲application爲開頭

在生產環境中,我們可以在不同位置進行配置,然後達到覆蓋的效果

第一種方式.指定properties配置文件:

image-20200518212503203

image-20200518212616081

第二種方式:yaml配置文件:

image-20200518212925477

爲什麼可以完成自動配置

在我們這配置文件中能配置的東西,都存在一個固有的規律

xxxAutoConfiguration:默認值 <—注入----- xxxProperties -----綁定— 配置文件

精髓

  1. SpringBoot啓動會加載大量的自動配置類

  2. 我們看我們需要的功能有沒有在SpringBoot默認寫好的自動配置類當中;

  3. 我們再來看這個自動配置類中到底配置了哪些組件; (只要我們要用的組件存在在其中,我們就不需要再手動配置了)

  4. 給容器中自動配置類添加組件的時候,會從properties類中獲取某些屬性。我們只需要在配置
    文件中指定這些屬性的值即可;

    xxxxAutoConfigurartion: 自動配置類; 給容器中添加組件
    xxxxProperties: 封裝配置文件中相關屬性;

查看哪些配置類生效

debug: true

如果配置了log4j之類的,設置log的level爲debug,也可以看到

自動配置了並且生效的

image-20200519151115420

大量的沒有生效的

image-20200519151158381

沒有條件所以沒生效的

image-20200519151244998

SpringBoot Web開發

要解決的問題:

  • 導入靜態資源
  • 首頁
  • jsp, 模板引擎 Thymeleaf
  • 裝配擴展SpringMVC
  • 增刪改查
  • 攔截器
  • 國際化

靜態資源

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    //第一種方式,webjars
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
        CacheControl cacheControl = this.resourceProperties.getCache()
            .getCachecontrol().toHttpCacheControl();
        if (!registry.hasMappingForPattern("/webjars/**")) {
            this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
        }
		
        //第二種方式,某路徑下的靜態資源
        String staticPathPattern = this.mvcProperties.getStaticPathPattern();
        if (!registry.hasMappingForPattern(staticPathPattern)) {
            this.customizeResourceHandlerRegistration(
                registry.addResourceHandler(new String[]{staticPathPattern})
.addResourceLocations(
    WebMvcAutoConfiguration.getResourceLocations(
        this.resourceProperties.getStaticLocations()))
                .setCachePeriod(this.getSeconds(cachePeriod))
                .setCacheControl(cacheControl));
        }
    }
}

什麼是webjars?

給靜態資源的一層封裝,比如jquery,以maven的方式引入的話,會給他包上一層webjars封裝

image-20200519161822815

如上述代碼 “/webjars/**” 對應的是 “classpath:/META-INF/resources/webjars/” 直接輸入/webjars/jquery/3.5.1/jquery.js就可以訪問到js文件了

靜態資源目錄

優先級即爲,他們的位置關係 /META-INF/resources/ 優先級最高,最低的是classpath:/public/

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

總結

  1. 在springboot中,我們可以使用以下方式處理靜態資源

    • webjars localhost:8080/webjars/
    • public, static, /**, resources localhost:8080/
  2. 優先級: resources > static(默認) > public

首頁如何定製

    @Bean
    public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
        WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
        welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
        welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
        return welcomePageHandlerMapping;
    }

    private Optional<Resource> getWelcomePage() {
        String[] locations = WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations());
        return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
    }

    private Resource getIndexHtml(String location) {
        return this.resourceLoader.getResource(location + "index.html");
    }

注意:此處需要模板引擎的支持(Thymeleaf)

image-20200519171514559

模板引擎

模板引擎的作用就是我們來寫一個頁面模板,比如有些值呢,是動態的,我們寫一些表達式。而這些值,從哪來呢,我們來組裝一些數據, 我們把這些數據找到。然後把這個模板和這個數據交給我們模板引擎,模板弓|擎按照我們這個數據幫你把這表達式解析、填充到我們指定的位置,然後把這個數據最終生成一個我們想要的內容給我們寫出去, 這就是我們這個模板弓|擎,不管是jsp還是其他模板引擎,都是這個思想。只不過呢,就是說不同模板弓|擎之間,他們可能這個語法有點不一樣。其他的我就不介紹了,我主要來介紹一下SpringBoot給我們推薦的Thymeleaf模板引擎,這模板引擎呢,是一個高級語言的模板引擎,他的這個語法更簡單。而且呢,功能更強大。

image-20200519174530461

默認路徑是在classpath下的templates路徑下,文件後綴.html

導入依賴:

官方文檔 https://docs.spring.io/spring-boot/docs/2.2.7.RELEASE/reference/html/appendix-dependency-versions.html#appendix-dependency-versions

<!--thymeleaf,都是基於3.0以上開發-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.2.7.RELEASE</version>
</dependency>

現在就能跳轉成功了

image-20200519174941652

image-20200519174954838

使用thymeleaf:

導入約束

<html xmlns:th="http://www.thymeleaf.org">

表達式

官方文檔

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

所有這些功能可以組合和嵌套:

  • Simple expressions:
    • Variable Expressions: ${...}
    • Selection Variable Expressions: *{...}
    • Message Expressions: #{...}
    • Link URL Expressions: @{...}
    • Fragment Expressions: ~{...}
  • Literals
    • Text literals: 'one text', 'Another one!',…
    • Number literals: 0, 34, 3.0, 12.3,…
    • Boolean literals: true, false
    • Null literal: null
    • Literal tokens: one, sometext, main,…
  • Text operations:
    • String concatenation: +
    • Literal substitutions: |The name is ${name}|
  • Arithmetic operations:
    • Binary operators: +, -, *, /, %
    • Minus sign (unary operator): -
  • Boolean operations:
    • Binary operators: and, or
    • Boolean negation (unary operator): !, not
  • Comparisons and equality:
    • Comparators: >, <, >=, <= (gt, lt, ge, le)
    • Equality operators: ==, != (eq, ne)
  • Conditional operators:
    • If-then: (if) ? (then)
    • If-then-else: (if) ? (then) : (else)
    • Default: (value) ?: (defaultvalue)
  • Special tokens:
    • No-Operation: _

簡單測試一下

@RequestMapping("/index")
    public String index(Model model){
        model.addAttribute("msg","hello thymeleaf");
        model.addAttribute("users", Arrays.asList("花玲","kk"));
        return "index";
    }
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>

<!--所有的html元素都可以被 thymeleaf替換接管:    th:元素名  -->
<div th:text="${msg}"></div>
<!--foreach-->
<h3 th:each="user:${users}" th:text="${user}"></h3>

</body>
</html>

輸出

image-20200519182311041

注意

熱部署(Ctrl+F9)需要關閉緩存:

spring.thymeleaf.cache=false

修改springboot的默認配置

方式一

這麼多的自動配置,原理都是一樣的, 通過這個WebMVC的自動配置原理分析,我們要學會一種學習方式,通過源碼探究,得出結論;這個結論一定是屬於自己的,而且一通百通。

SpringBoot的底層,大量用到了這些設計細節思想,所以,沒事需要多閱讀源碼!得出結論;

SpringBoot在自動配置很多組件的時候,先看容器中有沒有用戶自己配置的(如果用戶自己配置@bean) ,如果有就用用戶配置的,如果沒有就用自動配置的;如果有些組件可以存在多個,比如我們的視圖解析器,就將用戶配置的和自己默認的組合起來!

擴展裝配SpringMVC

官方文檔

https://docs.spring.io/spring-boot/docs/2.2.7.RELEASE/reference/html/spring-boot-features.html#boot-features-developing-web-applications

擴展SpringMVC

image-20200519183225354

我們要做的就是編寫一個@Configuration註解類, 並且類型要爲WebMvcConfigurer, 還不能標註@EnableWebMvc註解;我們去自己寫一個;
我們新建一個包叫config, 寫一個類MyMvcConfig;

package com.ky.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

//如果,你想diy一些定製化的功能,只要寫這個組件,然後將它交給springboot,springboot就會幫我們自動裝配
//擴展springMvc dispatcherServlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {


    //ViewResolver 實現了視圖解析器接口的類,我們就可以把它看做視圖解析器
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }


    //自定義了一個自己的視圖解析器MyViewResolver
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }
}

官網中有這麼一句話:

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

@EnableWebMvc是幹嘛的?

//如果我們要擴展springmvc,官方建議我們這樣去做
@Configuration
@EnableWebMvc   //導入了一個類   DelegatingWebMvcConfiguration    作用:從容器中獲取所有的webmvcconfig
public class MyMvcConfig implements WebMvcConfigurer {

    //視圖跳轉
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/kang").setViewName("index");
    }
}

先說結論,@EnableWebMvc相當於覆蓋所有默認設置

爲什麼?

WebMvcAutoConfiguration這個類的主要作用是完成對MVC設置的自動裝配

但是條件是在沒有導入WebMvcConfigurationSupport這個類的情況下才會生效

image-20200519231251603

@EnableWebMvc導入了DelegatingWebMvcConfiguration這個類

image-20200519231022797

DelegatingWebMvcConfiguration這個類繼承了WebMvcConfigurationSupport

image-20200519231225893

所以WebMvcAutoConfiguration這個類對MVC的自動設置會全部失效,這是完全接管SpringMVC,而不是對SpringMVC的擴展.

Springboot整合ORM

Springboot整合JDBC

簡介

對於數據訪問層,無論是SQL(關係型數據庫)還是NOSQL(非關係型數據庫),Spring Boot底層都是採用Spring Data的方式進行統一處理。Spring Boot底層都是採用Spring Data的方式進行統一處理各種數據庫, Spring Data也是Spring中與Spring Boot、Spring Cloud等齊名的知名項目。

Spring data官網: https://spring.io/projects/spring-data

引入SQL相關依賴

image-20200530002829655

pom依賴

image-20200530003017711

配置信息

image-20200530133002875

測試,默認數據源

image-20200530133141478

測試jdbcTemplate

image-20200530133726706

增刪改查之類的就不演示了,見名知意直接看api就好.

Sringboot整合Druid

簡介

Druid是阿里巴巴開源平臺上一個數據庫連接池實現,結合了C3P0、DBCP、PROXOOL 等DB池的優點,同時加入了日誌監控。
Druid可以很好的監控DB池連接和SQL的執行情況,天生就是針對監控而生的DB連接池。
Spring Boot 2.0以上默認使用Hikari數據源,可以說Hikari與Driud都是當前Java Web.上最優秀的數據源,我們來重點介紹SpringBoot如何集成Druid數據源,如何實現數據庫監控。

導入依賴

<!--Druid-->
<dependency>
	<groupId>com. alibaba</ groupId>
	<artifactId>druid</artifactId>
	<version>1.1.21</version>
</dependency>

配置信息

image-20200530144647266

image-20200530145016301

當然,如果要用日誌功能,需要引入日誌依賴

<!--log4j-->|
<dependency>
	<groupId>1og4j</groupId>
	<artifactId>1og4j</artifactId>
	<version>1.2.17</version>
</dependency>

DruidConfig配置類

image-20200530145823972

這樣spring容器中就會有我們的Druid數據源了,可以實現一些功能的配置

實現後臺監控功能

//後臺監控
//因爲SpringBoot內置了servlet 容器,所以沒有web.xmL,替代方法: ServletRegistrationBean
@Bean
public ServletRegistrationBean statViewServlet(){
	ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(
        new StatViewServ1et("/druid/**")){
	//後臺需要有人登陸,賬號密碼配置
	HashMap<String, String> initParameters = new HashMap<>();
	//增加配置
	initParameters.put( "loginUsername", "admin"); //登key是固定的LoginUsername LoginPassword
	initParameters.put("loginPassword" , "123456");
        
	//允許誰可以訪問,爲空表示所有人可訪問
	initParameters.put("a11ow", "";
                       
	//禁止誰能訪問initParameters.put( "deny", "192.168.11.123"); 
	bean.setInitParameters(initParameters); // 設置初始化參數
	return bean;
}

自帶monitor

image-20200530150903153

image-20200530151000980

filter

//filter
@Bean
public FilterRegistrationBean webStatFilter(){
	FilterRegistrationBean bean = new FilterRegistrationBean();
	bean.setFilter(new WebStatFilter());
	//可以過濾哪些請求呢?
	Map<String, String> initParameters = new HashMap<>(); .
	//這些東西不進行統計
	initParameters.put("exclusions","*.js,*.css,/druid/*");
	bean.setInitParameters(initParameters);
	return bean;
}

Springboot整合Mybatis

引入依賴

<!--mybatis-spring- boot-starter-->
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.1.1</version>
</dependency>

application.properties(yml也可)

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.ur1=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysq1.cj.jdbc.Driver

測試

pojo

//此處是導入了lombok的依賴
@Data
@NoArgsConstructor
@A11ArgsConstructor
public class User {
	private int id;
	private String name ; 
	private String pwd ;
}

mapper

//這個註解表示了這是一一個 mybatis的mapper類;  我們原來用的@MapperScan,現在用這個就不用寫了,都可
@Mapper
@Repository
public interface UserMapper {
	List<User> queryUserList();
	User queryUserById(int id);
	int addUser(User user);
	int updateUser(User user);
	int deleteUser(int id);
}

mybatis官方中文文檔 https://mybatis.org/mybatis-3/zh/getting-started.html

xxxMapper.xml(這裏是官方示例)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>

image-20200530154305052

整合mybatis配置信息

#整合mybatis
mybatis.type-aliases-package=com.kuang.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

xml一一對應(當然我們如果用tk的通用mapper就沒那麼麻煩了)

image-20200530154713636

搞一個Controller測試

@RestController
public class UserController {
	@Autowired
	private UserMapper userMapper;
	@GetMapping(/queryUserList" )
	public List<User> queryUserList(){
		List<User> userlist = userMapper.queryUserList();
		for (User user : userList) {
			System.out.print1n(user);
		}
		return userList;
	}
}

總的來說

  1. 導入包
  2. 配置文件
  3. mybatis配置
  4. 編寫sq
  5. service層調用dao層
  6. controller調用service層

SpringSecurity

web開發中,過濾器攔截器都能做安全相關的東西,安全並不是一個功能性需求(不做安全網站也能跑起來)

但是安全應該是在設計之初就考慮的內容

可以實現的功能

  • 功能權限
  • 訪問權限
  • 菜單權限

我們原來實現這些功能–>攔截器,過濾器—>大量的原生代碼—>繁瑣冗餘

環境引入

RouteController

通過輸入的id直接路由到對應的網頁,展示對應的內容

image-20200530162003669

頁面展示

因爲沒有加權限控制,所以了展示所有的信息

image-20200530162432964

Springsecurity是一種AOP的思想,就是橫切嘛,不懂得同學瞭解下就好

簡介

Spring Security是針對Spring項目的安全框架,也是Spring Boot底層安全模塊默認的技術選型,他可以實現強大的Web安全控制,對於安全控制,我們僅需要引入spring boot-starter-security模塊,進行少量的配置,即可實現強大的安全管理!

記住幾個類:

  • WebSecurityConfigurerAdapter: 自定義Security策略 適配器模式
  • AuthenticationManagerBuilder: 自定義認證策略 建造者模式
  • @EnableWebSecurity: 開啓WebSecurity模式 @Enablexxxx—>開啓某個功能

Spring Security的兩個主要目標是“認證”和"授權”(訪問控制)

“認證”(Authentication)

"授權”(Authorization)

這個概念是通用的,而不是隻在Spring Security中存在。

參考官網: https://spring.io/projects/spring-security#overview

查看項目中的版本,找到對應的幫助文檔: https://docs.spring.io/spring-security/site/docs/5.2.4.RELEASE/reference/htmlsingle/

引入依賴

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

核心的就是AOP,config,web

image-20200530163842554

官方示例

https://docs.spring.io/spring-security/site/docs/5.2.4.RELEASE/reference/htmlsingle/#hello-web-security-java-configuration

@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .apply(customDsl())
                .flag(true)
                .and()
            ...;
    }
}

授權

image-20200530174934993

效果

image-20200530175014118

設置跳轉登錄頁面之後可以自動跳轉到登錄頁

image-20200530175157528

認證

image-20200530190430061

注意:此處在springboot2.1.x中可以直接使用,在Spring Security 5.0中新增了很多加密方法,如果未設置會報錯

解決方案:

image-20200530190516316

註銷

image-20200531110703040

問題

現在我們只是可以通過security達到讓用戶只能點擊對應權限的模塊,但是還不能做到只能讓用戶直接看到他自己的模塊

image-20200531111848972

解決方案

SpringSecurity整合thymeleaf

引入依賴:

<!--security-thymeleaf整合包-->
<dependency>
	<groupId>org.thymeleaf.extras</groupId>
	<artifactId>thymeleaf-extras-springsecurity4</artifactId>
	<version>3.0.4.RELEASE</version>
</dependency>

前端頁面修改

導入命名空間

image-20200531114319243

修改頁面

image-20200531115239243

image-20200531115545063

SecurityConfig修改

image-20200531115334346

記住我

image-20200531120015898

效果

image-20200531120041769

其實是往cookie和session中丟了個value

image-20200531120124012

害,兄弟萌自己看吧,這裏有點繞,筆記文字不好描述 https://www.bilibili.com/video/BV1PE411i7CV?p=37

Shiro

簡介

  • Apache Shiro是一個Java的安全(權限)框架。
  • Shiro 可以非常容易的開發出足夠好的應用,其不僅可以用在JavaSE環境,也可以用在JavaEE環境。
  • Shiro可以完成,認證,授權,加密,會話管理,Web集成,緩存等。
  • 下載地址: http://shiro.apache.org/
  • image-20200531172924314

有哪些功能

image-20200531173228437

  • Authentication:身份認證、登錄,驗證用戶是不是擁有相應的身份;
  • Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限,即判斷用戶能否進行什麼操作,如:驗證某個用戶是否擁有某個角色,或者細粒度的驗證某個用戶對某個資源是否具有某個權限!
  • Session Manager:會話管理,即用戶登錄後就是第一次會話, 在沒有退出之前,它的所有信息都在會話中; 會話可以是普通的JavaSE環境,也可以是Web環境;
  • Cryptography:加密,保護數據的安全性,如密碼加密存儲到數據庫中,而不是明文存儲;
  • Web Support: Web支持,可以非常容易的集成到Web環境;
  • Caching:緩存,比如用戶登錄後,其用戶信息,擁有的角色、權限不必每次去查,這樣可以提高效率
  • Concurrency: Shiro支持多線程應用的併發驗證,即,如在一個線程中開啓另- 個線程,能把權限自動的傳播過去
  • Testing:提供測試支持;
  • Run As:允許一個用戶假裝爲另-一個用戶(如果他們允許)的身份進行訪問;
  • Remember Me:記住我,這個是非常常見的功能,即一次登錄後,下次再來的話不用登錄了

shiro架構(外部)

從外部程序來看shiro,即從應用程序角度來觀察如何使用shiro完成工作:

image-20200531173445075

  • subject:應用代碼直接交互的對象是Subject, 也就是說Shiro的對外API核心就是Subject, Subject代表了當前的用戶,這個用戶不-定是- -個具體的人,與當前應用交互的任何東西都是Subject, 如網絡爬蟲,機器人等,與ubject的所有交互都會委託給SecurityManager; Subject其實是一 個門面, SecurityManageer 纔是實際的執行者
  • SecurityManager:安全管理器,即所有與安全有關的操作都會與SercurityManager交互,並且它管理着所有的Subject,可以看出它是Shiro的核心,它負責與Shiro的其他組件進行交互,它相當於SpringMVC的DispatcherServlet的角色
  • Realm: Shiro從Realm獲取安全數據(如用戶,角色,權限),就是說SecurityManager 要驗證用戶身份,那麼它需要從Realm獲取相應的用戶進行比較,來確定用戶的身份是否合法;也需要從Realm得到用戶相應的角色、權限,進行驗證用戶的操作是否能夠進行,可以把Realm看 成DataSource;

shiro架構(內部)

image-20200531173905154

  • Subject:任何可以與應用交互的’用戶’;
  • Security Manager:相當於SpringMVC中的DispatcherServlet; 是Shiro的心臟,所有具體的交互都通過Security Manager進行控制,它管理者所有的Subject,且負責進行認證,授權,會話,及緩存的管理。
  • Authenticator:負責Subject認證,是一個擴展點,可以自定義實現;可以使用認證策略(Authentication Strategy),即什麼情況下算用戶認證通過了;
  • Authorizer:授權器,即訪問控制器,用來決定主體是否有權限進行相應的操作;即控制着用戶能訪問應用中的那些功能;
  • Realm:可以有一一個或者多個的realm,可以認爲是安全實體數據源,即用於獲取安全實體的,可以用JDBC實現,也可以是內存實現等等,由用戶提供;所以一般在應用中都需要實現自己的realm
  • SessionManager:管理Session生命週期的組件,而Shiro並不僅僅可以用在Web環境, 也可以用在普通的JavaSE環境中
  • CacheManager:緩存控制器,來管理如用戶,角色,權限等緩存的;因爲這些數據基本.上很少改變,放到緩存中後可以提高訪問的性能;
  • Cryptography:密碼模塊,Shiro 提高了- -些常 見的加密組件用於密碼加密,解密等

HelloWorld快速實踐

官方文檔: http://shiro.apache.org/tutorial.html

下載地址: http://shiro.apache.org/download.html#latestGit

github: https://github.com/apache/shiro

quickstart

git pull之後可以看到quickstart文件夾,可以去看看裏面的start代碼

image-20200601082654971

新建一個helloshiro工程

image-20200601083437731

配置log4j.properties配置文件

image-20200601083622605

shiro.ini配置文件

如果想要語法高亮需要安裝ini插件

image-20200601083928083

把QuickStart.java類建好

/**
 * 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) {

        // 創建帶有配置領域、用戶、角色和權限的Shiro SecurityManager的最簡單方法是使用簡單的INI配置。
        // 我們將使用一個工廠讀取.ini文件,並返回一個SecurityManager實例:

        // 使用類路徑根目錄下的shiro.ini文件
        // (file:和url前綴分別從文件和url加載):
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // 對於這個簡單的示例quickstart,可以將SecurityManager作爲一個JVM單例進行訪問。
        // 大多數應用程序不會這樣做,而是依賴於它們的容器配置或web.xml。
        // 這超出了簡單的快速入門的範圍,所以我們只做最基本的,感知感知強烈。
        SecurityUtils.setSecurityManager(securityManager);

        // 現在,簡單的shiro環境已經設置好了,讓我們看看您可以做些什麼:

        // 獲取當前執行用戶: 		重點,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()) {
            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 (AuthenticationException ae) {
                // 意料之外的條件,是個錯誤?
            }
        }

        // 說出他們是誰:打印它們的標識主體(在本例中爲用戶名):
        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!");
        }

        // done! 登出
        currentUser.logout();

        System.exit(0);
    }
}

總的來說主要的就幾個方法:

Subject currentuser = SecurityUtils.getSubject();
Session session = currentuser.getSession();
currentuser.isAuthenticated()
currentuser.getPrincipa1()
currentuser.hasRole("schwartz")
currentuser.isPermitted("lightsaber:wie1d")
currentUser.logout();

運行結果

image-20200601084008363

Springboot集成shiro

Subject 用戶
Securi tyManager 管理所有用戶
Realm 連接數據

結構

image-20200601143314818

pom依賴

<!--thymeleaf模板-->
<dependency>
	<groupId>org.thymeleaf</ groupId>
	<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
	<groupId>org.thymeleaf.extras</ groupId>
	<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>

<!--shiro-thymeleaf整合-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
<!--shiro整合springboot-->	
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring-boot-starter</artifactId>
  <version>1.5.3</version>
</dependency>

<!--springboot-web整合shiro-->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring-boot-web-starter</artifactId>
  <version>1.5.3</version>
</dependency>

UserRealm.java

//自定義的UserRealm 	extends Authoriz ingReaLm
public class UserRealm extends AuthorizingRealm {
	
    //授權
    @Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("執行了授權");
		return null;
	}

    //認證
    @Override
	protected AuthenticationInfo doGetAuthenticat ionInfo( AuthenticationToken token) throws AuthenticationException{
		System.out.println("執行了認證");
        
        //用戶名,密碼  數據庫中取	此處僞裝一下
        String name = "root";
        String password = "123456";
        UsernamePasswordToken userToken = (UsernamePas swordToken) token;
		
        if(!userToken.getUsername().equals(name)){
            return null; //拋出異常 	UnknownAccountException
        }
        
        //密碼認證,shiro做
        return new SimpleAuthenticationInfo("",password,"");
	}

}

ShiroConfig.java

@Configuration
public class ShiroConfig {
    
	//ShiroFilterFactoryBean :3
    @Bean
    public ShiroFilterFactoryBean getshiroFilterFactoryBean(@Qualifler("securityManager") DefaultWebSecurityManager 				defaultWebSecurityManager){
		ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //設置安全管理器
		bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro的內置過濾器
        /**
        	anon:	無需認 證就可以訪問
			authc:	必須認證了才能讓問
			user:	必須擁有記住我功能才能用
			perms:	擁有對菜 個資源的權限才能訪間:
			role:	擁有某個角色權限1能訪間
        */
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/add", "authc");
		filterMap.put("/user/update", "authc");
		
        bean.setFilterChainDefinitionMap(filterMap);

        //設置登錄請求
        bean.setLoginUrl("/toLogin");
        
		bean.setFilterChainDefinitionMap(filterMap);
        
		return bean;
	}
   
	//DafaultWebSecurityManager :2
    @Bean(name="securityManager")
	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRea1m") UserRealm userRealm){
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		//關聯UserRealm
		securityManager.setRealm(userRea1m) ; .
		return securityManager;
    }
    
	//創建realm 對象 需要自定義類 :1
    @Bean(name="userRealm")
	public UserRealm userRealm() {
		return new UserRealm();
	}

}

Controller

@Controller
public class MyController {
   
	@RequestMapping({"/","/index"})
	public String toIndex(Model model){
		model.addAttribute( "msg","hello,Shiro");
		return "index";
	}
    
	@RequestMapping("/user/add")
	public String add(){
		return "user/add";
	}
    
	@RequestMapping("/user/update")
	public String update(){
		return "user/update" ;
	}
    
    @RequestMapping("/toLogin")
	public String toLogin(){
		return "login";
	}
    
    @RequestMapping("/login")
	public String login(String username, String password,Model model){
		//獲取當前的用戶
		Subject subject = SecurityUtils.getSubject();
		//封裝用戶的登錄數據
		UsernamePas swordToken token = new UsernamePasswordToken(username , password);
        
        try{
           subject.login(token); //執行登錄方法,如果沒有異常就說明oK了 
           //Subject currentSubject = SecurityUtils.getSubject();
           //Sesson session = currentSubject.getSession();
           return "index";
        }catch (UnknownAccountException uae) { //用戶名不存在
            model.addAttribute("msg","用戶名錯誤");
            return "login";
        } catch (IncorrectCredentialsException ice) {
             model.addAttribute("msg","密碼錯誤"); //密碼不存在
            return "login";
        }
	}

    @RequestMapping("/noauth")
    @ResponseBody
    public String unauthorized( ){
    	return "未經投權無法訪間此頁面";
    }

}

測試頁面

index

<!DOCTYPE html>
<html lang="en" xmlns :th="http://www.thymeleaf.org"
      xmlns :shiro="http:// Www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Tit1e</title>
</head>
<body>
<h1>首頁</h1>
<div shiro:notAuthenticated="">
    <a th:href="@{/toLogin}">登錄</a>
</div>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">update</a>
</div>
    
</body>
</html>

login

<!DOCTYPE html>
<html lang="en" xmlns :th="http://www.thymeleaf.org">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
</head>
<body>
    <h1>登錄</h1>    
    <hr>
    <p th:text="${msg}" style="color: red;"></p>
    <form th:action"@{/login}">
        <p>用戶名: <input type="text" name= "username"></p>
		<p>密碼: <input type="text" name= "password"></p>
		<p><input type= "submit"> </p>
    </form>
</body>
</html>

add

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
</head>
<body>
    <h1>add</h1>    
</body>
</html>

update

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
</head>
<body>
    <h1>update</h1>    
</body>
</html>

整合mybatis

<!--lombok-->
<dependency>
    <groupId>org.projectlombok</ groupId>
    <artifactId>lombokk</artifactId>
    <version>1.16.10</version>
</dependency>
<!--數據庫連接-->
<dependency>
    <groupId>mysq1</ groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--日誌 -->
<dependency>
    <groupId>1og4j</ groupId>
    <artifactId>1og4j</artifactId>
	<version>1.2.17</version>
</dependency>
<!-- druid數據源 -->
<dependency>
    <groupId>com. alibaba</ groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>
<!--引入myBatis,這是MyBatis官方提供的適配Spring Boot的,而不是Spring Boot自己的-->
<dependency>
    <groupId>org.mybatis.spring.boot</ groupId>
    <artifactId>mybatis-spring-boot-starter</ artifactId>
    <version>2.1.0</version>
</dependency>

在這裏BB一句,我覺得只引入一個mybatis的starter就可了

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

application.yml

spring: 
	datasource: 
		username: root
		password: 123456
		#?serverTimezone=UTC解決時區的報錯
		ur1: jdbc:mysql://1ocalhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
		driver-class-name: com.mysq1.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: sta,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSq1=true;druid.stat.slowSqlMillis=500

application.properties

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

pojo

User.java

@Data
@A11ArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
    private String perms;
}

mapper

@Repository
@Mapper
public interface UserMpper {
	public User queryUserByName(String name);
}

userMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.mapper.UserMapper">
    
    <select id="queryUserByName" parameterType= "String" resultType= "User">
		select * from mybatis.user where name = #{name}
	</select>

</mapper>

Service

//service接口
public interface UserService {
	public User queryUserByName(String name);
}
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    UserMapper userMapper;
    
    @Override
    pub1ic User queryUserByName(String name) {
    	return userMapper.queryUserByName(name);
    }
}

測試一下

@SpringBootTest
class ShiroSpringbootApplicationTests {
    @Autowired
    UserServiceImpl userService;
    
    @Test
    void contextLoads() {
    	System.out.print1n(userService.queryUserByName("kuangshen"));
    }
}

可以,底層沒問題了

image-20200601111027291

改造UserRealm

//自定義的UserRealm 	extends Authoriz ingReaLm
public class UserRealm extends AuthorizingRealm {
	
    @Autowired
    UserService userService;
    
    //授權
    @Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("執行了授權");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		//info.addStringPermission("user:add");
		//拿到當前登錄的這個對象
		Subject subject = SecurityUtils.getSubject();
		User currentUser = (User)subject.getPrincipal();		//取得是下面認證時return的user
		//設置當前用戶的權限
        info.addStringPermission(currentUser.getPerms());
        
		return info;
	}

    //認證
    @Override
	protected AuthenticationInfo doGetAuthenticat ionInfo( AuthenticationToken token) throws AuthenticationException{
        System.out.println("執行了認證");
        UsernamePasswordToken userToken = (UsernamePas swordToken) token;
		//連接數據庫查詢用戶
        User user = userService.queryUserByName(userToken.getUsername());
       	if(user == null){
            //沒有這個人
            return null;
        }
        
        //可以加密:		MD5: 123456:e10adc3949ba59abbe56e057f20f883e	MD5加鹽:123456+username	f052d3f8344c2afe6263843456d15be2
        //密碼認證,shiro做
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");	//傳遞user,上面授權才能拿到
	}

}

完善ShiroConfig.java

@Configuration
public class ShiroConfig {
    
	//ShiroFilterFactoryBean :3
    @Bean
    public ShiroFilterFactoryBean getshiroFilterFactoryBean(@Qualifler("securityManager") DefaultWebSecurityManager 				defaultWebSecurityManager){
		ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //設置安全管理器
		bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro的內置過濾器
        /**
        	anon:	無需認 證就可以訪問
			authc:	必須認證了才能讓問
			user:	必須擁有記住我功能才能用
			perms:	擁有對菜 個資源的權限才能訪間:
			role:	擁有某個角色權限1能訪間
        */
        //filterMap.put("/user/add","authc");
		//filterMap.put("/user/update", "authc");
        
        //攔截
        Map<String, String> filterMap = new LinkedHashMap<>();
        //授權,正常情況下會跳轉到未授權頁面
        filterMap.put("/user/add", "perms[user:add]");
        filterMap.put("/user/update", "perms[user:update]");
        
        filterMap.put("/user/*", "authc");
        bean.setFilterChainDefinitionMap(filterMap);

        //設置登錄請求
        bean.setLoginUrl("/toLogin");
        //設置未授權頁面
        bean.setUnauthorizedUr1("/noauth");
        
		bean.setFilterChainDefinitionMap(filterMap);
        
		return bean;
	}
   
	//DafaultWebSecurityManager :2
    @Bean(name="securityManager")
	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRea1m") UserRealm userRealm){
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		//關聯UserRealm
		securityManager.setRealm(userRea1m) ; .
		return securityManager;
    }
    
	//創建realm 對象 需要自定義類 :1
    @Bean(name="userRealm")
	public UserRealm userRealm() {
		return new UserRealm();
	}
    
    //整合ShiroDialect: 用來整合shiro和thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章