SpringBoot教程——檢視閱讀(上)

基于Spring Boot框架:Spring Boot 2.1.11.RELEASE

Spring Boot基礎(chǔ)入門

什么是Spring Boot

Spring Boot概述

Spring Boot 是所有基于 Spring Framework 5.0 開發(fā)的項目的起點。Spring Boot 的設(shè)計是為了讓你盡可能快的跑起來 Spring 應(yīng)用程序并且盡可能減少你的配置文件。

簡化了使用Spring的難度,簡省了繁重的配置,提供了各種啟動器,開發(fā)者能快速上手。

Spring Boot的優(yōu)點

  • 使用 Spring 項目引導(dǎo)頁面可以在幾秒構(gòu)建一個項目
  • 方便對外輸出各種形式的服務(wù),如 REST API、WebSocket、Web、Streaming、Tasks
  • 非常簡潔的安全策略集成
  • 支持關(guān)系數(shù)據(jù)庫和非關(guān)系數(shù)據(jù)庫
  • 支持運行期內(nèi)嵌容器,如 Tomcat、Jetty
  • 強大的開發(fā)包,支持熱啟動
  • 自動管理依賴自帶應(yīng)用監(jiān)控
  • 支持各種 IDE,如 IntelliJ IDEA 、NetBeans

Spring Boot核心功能

起步依賴

起步依賴本質(zhì)上是一個Maven項目對象模型(Project Object Model,POM),定義了對其他庫的傳遞依賴,這些東西加在一起即支持某項功能。

自動配置

Spring Boot的自動配置是一個運行時(更準確地說,是應(yīng)用程序啟動時)的過程,考慮了眾多因素,才決定Spring配置應(yīng)該用哪個,不該用哪個。該過程是Spring自動完成的。

Spring Boot快速入門

步驟:

  1. 創(chuàng)建一個普通的maven項目。
  2. pom.xml導(dǎo)入起步依賴 。
  3. 編寫引導(dǎo)類

示例:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.self</groupId>
    <artifactId>hellospringboot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 導(dǎo)入springboot父工程. 注意:任何的SpringBoot工程都必須有的?。。?-->
    <!-- 父工程的作用:鎖定起步的依賴的版本號,并沒有真正的依賴 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.11.RELEASE</version>
    </parent>

    <dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>

引導(dǎo)類,或者說叫啟動類

@SpringBootApplication
public class MyBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class,args);
    }
}
@Controller
public class HelloController {

    @RequestMapping("/hello")
    @ResponseBody
    public String sayHello(){
        return "Hello Spring Boot!";
    }
}

請求:<http://localhost:8080/hello>

輸出:

image.png

Spring Boot配置文件

Spring Boot的核心是自動配置(或者叫默認配置),通過自動配置大大減少Spring項目的配置編寫。但是在實際開發(fā)中,我們?nèi)匀恍枰鶕?jù)需求來適當(dāng)修改某些必要的參數(shù)配置,這時Spring Boot提供了兩種格式的配置方便開發(fā)者進行修改。

  • applicaiton*.properties
  • application.yml(或者application.yaml)

application*.properties

Spring Boot使用了一個全局的配置文件application.properties,放在src/main/resources目錄下或者src/main/resources/config下。在 src/main/resources/config 下的優(yōu)先級高 。Sping Boot的全局配置文件的作用是對一些默認配置的配置值進行修改。

Spring Boot內(nèi)置屬性

示例:

編寫修改Tomcat端口屬性 :

application.properties

server.port=9000

Spring Boot內(nèi)置屬性參考

自定義屬性

application.properties

server.port=9000

#自定義類型
#基本類型
name=Amy
age=21

#JavaBean類型
user.name=Amy
user.age=21

#數(shù)組/List集合
user.list=Amy,Jack,Roy
#或者
user.list[0]=Amy
user.list[1]=Jack
user.list[2]=Roy

#Map集合
user.map={name:"Amy",age:21}
#或者
user.map.name=Amy
user.map.age=21

Q:自定義配置怎么取值使用,什么場景使用?

A:自定義配置一般用@Value("${vv.schedule.mail.vcode.content}")注解來取值,一般我們有些自定義的配置需要時,就可以在yml文件中配置,或者nacos上配置,然后取出來用即可,@RefreshScope注解可以在配置發(fā)生改變時及時更新。

@Configuration
@RefreshScope
@Data
public class Config {

    @Value("${v.sche.mail.vcode.content}")
    private String mailContent;

    @Value("${v.sche.mail.vcode.title}")
    private String mailTitle;

    @Value("${v.sche.swagger.enable:false}")
    private boolean enableSwagger;

    @Value("${v.sche.login.fail.limit:6}")
    private Integer failLimit;

    @Value("${v.sche.login.fail.lock.expire.time:1800}")
    private Integer lockExpireTime;

    @Value("${v.sche.mail.send.limit:10}")
    private Integer emailLimit;

    @Value("${v.sche.mail.lock.expire.time:86400}")
    private Integer emailExpireTime;
}
Profile多環(huán)境配置

當(dāng)應(yīng)用程序需要部署到不同運行環(huán)境時,一些配置細節(jié)通常會有所不同,最簡單的比如日志,生產(chǎn)日志會將日志級別設(shè)置為WARN或更高級別,并將日志寫入日志文件,而開發(fā)的時候需要日志級別為DEBUG,日志輸出到控制臺即可。如果按照以前的做法,就是每次發(fā)布的時候替換掉配置文件,這樣太麻煩了,Spring Boot的Profile就給我們提供了解決方案,命令帶上參數(shù)就搞定。

步驟:

  • 建立不同環(huán)境的application.properties文件
  • 每個文件里面的環(huán)境配置變量不同

示例:

application.properties

#啟用prod生產(chǎn)環(huán)境
spring.profiles.active= prod
#當(dāng)profiles不同環(huán)境變量文件里有配置值時,application.properties里配置的變量是會被啟用的環(huán)境如application-prod.properties里的值所覆蓋的。
server.port=9000

application-prod.properties

server.port=9004

輸出:

image.png
maven實現(xiàn)多環(huán)境配置

參考

  • 首選pom.xml中要有對應(yīng)的profiles配置
  • 運行打包命令mvn clean package -Dmaven.test.skip=true -P workdev-business
<profile>
    <id>dev-business</id>
    <properties>
        <profileActive>dev-business</profileActive>
        <discovery.namespace/>
        <application.name>vv-mater</application.name>
        <config.namespace>${discovery.namespace}</config.namespace>
        <nacos.config.address>172.16.6.126:9002</nacos.config.address>
        <nacos.discovery.address>172.16.6.126:9002</nacos.discovery.address>
    </properties>
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</profile>

bootstrap.yml配置

spring:
  application:
    name: ${application.name}
  cloud:
    nacos:
      config:
        server-addr: ${nacos.config.address}
        shared-dataids: common.yml,${spring.application.name}.yml
        refreshable-dataids: common.yml,${spring.application.name}.yml
        namespace: ${config.namespace}
        username: use
        password: use
      discovery:
        server-addr: ${nacos.discovery.address}
        namespace: ${discovery.namespace}
        register-enabled: true
        metadata:
          version: ${nacos.discovery.metadata.version}
          env: ${nacos.discovery.metadata.env}
        username: use
        password: use
  main:
    allow-bean-definition-overriding: true

application*.yml

YAML(/?j?m?l/,尾音類似camel駱駝)是一個可讀性高,用來表達數(shù)據(jù)序列化的格式。

yml或yaml所表示的YAML Ain’t Markup Language,YAML是一種簡潔的非標記語言,文件名后綴為yml,java中經(jīng)常用它描述配置文件application.yml。YAML以數(shù)據(jù)為中心,比json/xml等更適合做配置文件。使用空白,縮進,分行組織數(shù)據(jù),從而使得表示更加簡潔易讀。

在yml之前使用最多的配置文件形式是xml和properties文件。xml文件太過繁瑣,看過的人都知道,想要新加一個配置節(jié)點的話還需要包含在<>標簽里;而properties配置文件沒有了標簽,不過當(dāng)你的配置有很多層級的時候,寫完之后你會發(fā)現(xiàn)會有大量重復(fù)的代碼。而yml/yaml文件結(jié)合了兩者的優(yōu)勢,當(dāng)你新增節(jié)點配置的時候,不需要標簽,在寫多層級配置的時候也不會產(chǎn)生重復(fù)代碼。

yml格式書寫規(guī)則
  1. 大小寫敏感
  2. 使用縮進表示層級關(guān)系
  3. 禁止使用tab縮進,只能使用空格鍵
  4. 縮進長度沒有限制,只要元素對齊就表示這些元素屬于一個層級。
  5. 使用#表示注釋
  6. 字符串可以不用引號標注
Spring Boot內(nèi)置屬性

注意:

當(dāng)application.properties和application.yml同時存在時,生效的是application.properties。

#yml文件在寫時有提示,友好,優(yōu)先選擇使用
#修改端口
server:
  port: 9090
#基本類型 注意:屬性值大小寫敏感
name: Bruce Wayne
age: 29
#JavaBean類型
user:
  name: Bruce Wayne
  age: 29
#數(shù)組/List集合
#user:層級只能指定一個,后面的如果是在user層級下的只需要tab空格就可以了,再寫user層級則會報錯
#user:
  list: eric,jack,rose
  #下面這種寫法用@Value注解解析不了
  list:
  - Jack
  - Rose
  - Jerry
#Map集合
#user:
#yml語法格式要求key如map: 后面對應(yīng)的value值必需在冒號:后面空格,否則格式錯誤
  map: {name: Bruce Wayne,age: 29}
Profile多環(huán)境配置

application.yml

#啟用test測試環(huán)境
#當(dāng)profiles不同環(huán)境變量文件里有配置值時,application.yml里配置的變量是會被啟用的環(huán)境如application-test.yml里的值所覆蓋的。
#還有一點需要特別注意的是當(dāng)存在application-test.properties與application-test.yml兩個并行時,生效的是application-test.properties
spring:
  profiles:
    active: test

application-test.yml

server:
  port: 9092

Spring Boot讀取配置文件(properties和yml處理是一樣的)

Spring Boot里面有兩個注解可以讀取application.properties或application.yml文件的屬性值。

  1. @Value
  2. @ConfigurationProperties

注意:

1、不能配置user.name=Amy屬性配置,因為取不到Amy的值,取到的是計算機的用戶名,在這臺電腦里我的用戶名是Castamere。應(yīng)該是個系統(tǒng)默認保留的取值配置,這里沒有深入去研究。

2、不能配置userName=Amy這個key為userName或者username的基本類型配置,否則取到的是還是計算機的用戶名。

@Value

基本類型

application.yml

#基本類型 
firstName: Bruce Wayne1111
age: 29

讀?。?/p>

@Controller
public class ConfigController {

    @Value("${firstName}")
    private String name;
    @Value("${age}")
    private Integer age;

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return name + " : " + age;
    }
}
JavaBean類型
#JavaBean類型
user:
  first: Bruce Wayne
  age: 31

讀取:

@Controller
public class ConfigController {
    @Value("${user.firstName}")
    private String firstName;
    @Value("${user.age}")
    private Integer age;

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return firstName + " : " + age;
    }
}
數(shù)組/List集合
user:
  list: Jack,Rose,Jerry

讀?。?/p>

@Value("#{'${user.list}'.split(',')}")
private List<String> list;

@RequestMapping("/show")
@ResponseBody
public String showConfig() {
    return JSON.toJSONString(list);
}
Map集合
user:
#yml語法格式要求key如map: 后面對應(yīng)的value值必需在冒號:后面空格,否則格式錯誤
#讀取的時候要加引號""是給@Value注解用的么?
# map: {nickname: erci,age: 20}
  map: "{name: 'SuperMan',age: 28}"

讀?。?/p>

@Value("#{${user.map}}")
private Map<String,Object> map;

@RequestMapping("/show")
@ResponseBody
public String showConfig() {
    return JSON.toJSONString(map);
}

注意,不加引號會報錯.

#報錯:Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'user.map' in value "#{${user.map}}"
user:
  map: {name: 'SuperMan',age: 28}

@ConfigurationProperties

注意以下幾點:

  • prefix:代表屬性的前綴,如果user.nickname前綴就是user
  • 屬性名稱必須和properties文件的屬性名保持一致
  • 屬性必須提供setter方法來注入文件的屬性值
#基本類型
firstName: Bruce Wayne
age: 30

讀?。?/p>

@Controller
@ConfigurationProperties
public class ConfigurationController {

    private String firstName;
    private Integer age;

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return firstName + " : " + age;
    }
}

輸出:

//成功
Bruce Wayne : 30
//沒有setter方法來注入文件的屬性值,不會報錯,但是沒有賦值
null : null

JavaBean類型

#JavaBean類型
user:
  firstName: Bruce
  age: 31

讀取:

@Controller
//兩種配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {

    private String firstName;
    private Integer age;

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return firstName + " : " + age;
        //return JSON.toJSONString(list);
        //return JSON.toJSONString(map);
    }
}
Bruce : 31

數(shù)組/List集合

#數(shù)組/List集合
#user:層級只能指定一個,后面的如果是在user層級下的只需要tab空格就可以了,再寫user層級則會報錯
#user:
#兩種list表達方式都可以,傾下第一種
  list: Jack,Rose,Jerry
  list2:
  - Jack
  - Morty
  - Jerry

讀?。?/p>

@Controller
//兩種配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {

    private List<String> list;

    public void setList(List<String> list) {
        this.list = list;
    }

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return JSON.toJSONString(list);
    }
}

Map集合

#Map集合
#yml語法格式要求key如map: 后面對應(yīng)的value值必需在冒號:后面空格,否則格式錯誤
#讀取的時候要加引號""是給@Value注解用的么?@ConfigurationProperties("user")讀取map不需要加引號,否則報錯,說明兩種讀取方式不同
#報錯:Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'user.map' in value "#{${user.map}}"
  map: {name: 'SuperMan',age: 28}
#  map: "{name: 'SuperMan',age: 28}"

讀?。?/p>

@Controller
//兩種配置方式都可以
@ConfigurationProperties(prefix ="user", ignoreInvalidFields = true)
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {
    private Map<String,Object> map;

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }
    
    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return JSON.toJSONString(map);
    }
}
{"name":"SuperMan","age":28}

Spring Boot熱部署

什么是熱部署

無法熱部署的缺點:

  • 在實際開發(fā)過程中,每次修改代碼就得將項目重啟,重新部署,對于一些大型應(yīng)用來說,重啟時間需要花費大量的時間成本。
  • 程序員開發(fā)過程中重啟緩慢影響開發(fā)。在 Java 開發(fā)領(lǐng)域,熱部署一直是一個難以解決的問題,目前的 Java 虛擬機只能實現(xiàn)方法體的修改熱部署,對于整個類的結(jié)構(gòu)修改,仍然需要重啟虛擬機,對類重新加載才能完成更新操作。

熱部署原理

深層原理是使用了兩個ClassLoader,一個Classloader加載那些不會改變的類(第三方Jar包),另一個ClassLoader加載會更改的類,稱為restart ClassLoader,這樣在有代碼更改的時候,原來的restart ClassLoader 被丟棄,重新創(chuàng)建一個restart ClassLoader,由于需要加載的類相比較少,所以實現(xiàn)了較快的重啟時間。

Spring Boot熱部署實現(xiàn)方式

Spring Boot有3種熱部署方式:

  1. 使用springloaded配置pom.xml文件,使用mvn spring-boot:run啟動
  2. 使用springloaded本地加載啟動,配置jvm參數(shù)
  3. 使用devtools工具包,操作簡單,但是每次需要重新部署

Spring Boot使用devtools工具包實現(xiàn)熱部署

需要說明以下4點:

  1. devtools可以實現(xiàn)頁面熱部署(即頁面修改后會立即生效,這個可以直接在application.properties文件中配置spring.thymeleaf.cache=false來實現(xiàn))
  2. 實現(xiàn)類文件熱部署(類文件修改后不會立即生效,過會兒生效)
  3. 實現(xiàn)對屬性文件的熱部署。即devtools會監(jiān)聽classpath下的文件變動,并且會立即重啟應(yīng)用(發(fā)生在保存的時候)。這里可能有疑問,為什么還要重啟?這樣就不是熱部署啦!注意:因為其采用的虛擬機機制,該項重啟比正常重啟會快非常多!
  4. scope配置為true,在修改java文件后就立即熱啟動,而且會清空Session中的數(shù)據(jù)。如果有用戶登陸的話,項目重啟后需要重新登陸。

示例:

pom.xml添加依賴:

<!--devtools熱部署-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    <scope>true</scope>
</dependency>

application.yml添加devtools的配置 :

spring:
 devtools:
   restart:
     enabled: true  #設(shè)置開啟熱部署
     additional-paths: src/main/java #重啟目錄
     exclude: WEB-INF/**
 freemarker:
   cache: false    #頁面不加載緩存,修改即時生效

修改了類文件后,IDEA不會自動編譯,必須修改IDEA設(shè)置。

1、
File-Settings-Compiler-Build Project automatically 
2、
ctrl + shift + alt + / ,選擇Registry,勾上 Compiler autoMake allow when app running
image.png
image.png

Spring Boot訪問靜態(tài)資源

Spring Boot默認靜態(tài)資源目錄

在Spring Boot應(yīng)用啟動過程中,會讀取加載一個靜態(tài)資源文件加載路徑這個屬性

# 默認值為
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

這個屬性的默認值代表靜態(tài)資源掃描目錄:

classpath:/META-INF/resources/ 
classpath:/resources/
classpath:/static/ 
classpath:/public/
/:當(dāng)前項目的根路徑

這意味著我們可以只要把靜態(tài)資源文件存放在以上目錄,即可以被訪問到!

需注意優(yōu)先級問題:根據(jù)前后關(guān)系確定優(yōu)先級,配置路徑在前面會優(yōu)先訪問前面的靜態(tài)資源。也就是說如果classpath:/resources/目錄和classpath:/public/都有一個test.html,那么根據(jù)默認的優(yōu)先級,會去訪問classpath:/resources/下的資源。

示例:分別建立了public、resources、static目錄,在目錄下建立html靜態(tài)頁面。項目啟動后,我們都可以直接訪問這些頁面 。

image.png
//請求
http://localhost:8080/index.html
http://localhost:8080/img/test.gif
http://localhost:8080/hello.html //在public和resources文件夾下都有,優(yōu)先訪問resources下的靜態(tài)資源
http://localhost:8080/success.jsp//jsp文件訪問會直接下載,而不會去解析。

修改Spring Boot靜態(tài)資源路徑

我們可以在application.yml文件中修改靜態(tài)資源路徑,如:

# 修改靜態(tài)資源加載路徑
spring:
  resources:
    static-locations: classpath:/download,classpath:/static/,classpath:/public/

注意:

如果按照以上寫法會覆蓋Spring Boot的默認路徑。如果希望保留默認路徑,那就要先寫上之前所有值,再最后加上新的路徑。

Spring Boot進階

Spring Boot異常處理

有5種處理方式:

  1. Spring Boot默認異常提示。
  2. 自定義error的錯誤頁面。
  3. @ExceptionHandler注解(Controller中自定義)。
  4. @ControllerAdvice注解 加 @ExceptionHandler注解 抽取所以共用的異常處理方法。
  5. 實現(xiàn)HandlerExceptionResovler。

Spring Boot默認異常提示

在Spring Boot應(yīng)用執(zhí)行過程中難免會出現(xiàn)各種錯誤,默認情況下,只要出現(xiàn)異常,就會跳轉(zhuǎn)到Spring Boot默認錯誤提示頁面,如下:

image.png

為了給用戶更好的體驗,我們可以使用以下四種手段來優(yōu)化異常捕獲的情況。

自定義error的錯誤頁面

SpringBoot應(yīng)用默認已經(jīng)提供一套錯誤處理機制:就是把所有后臺錯誤統(tǒng)一交給error請求,然后跳轉(zhuǎn)到了本身自己的錯誤提示頁面。這時,我們利用springboot的錯誤處理機制,重新建立了一個新的error.html,該頁面必須放在resources的templates目錄下 。

示例:

pom.xml ——頁面用到了Thymeleaf,所以項目中需要導(dǎo)入Thymeleaf的依賴。如果沒有加依賴則頁面加載失敗繼續(xù)調(diào)整到第一種springboot默認異常提示頁面上。

image.png

error.html

<head>
    <meta charset="UTF-8">
    <title th:text="${title}"></title>
</head>
<body>
<div >
    <div>
        <div>
            <p><span>頁面出現(xiàn)</span><span class="code" th:text="${status}"></span>錯誤,非常抱歉!</p>
            <a href="/" class="btn-back common-button">您可以點擊返回首頁</a>
            <div >
                <div th:text="${#dates.format(timestamp,'yyyy-MM-dd HH:mm:ss')}"></div>
                <div>錯誤原因:</div>
                <div th:text="${message}"></div>
                <div th:text="${error}"></div>
            </div>
        </div>
    </div>
</div>
</body>

異常展示:

image.png
image.png

@ExceptionHandler注解

 /**@ExceptionHandler 注解只能作用為對象的方法上,并且在運行時有效,value() 可以指定異常類。由該注解注*釋的方法可以具有靈活的輸入?yún)?shù)。
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
    Class<? extends Throwable>[] value() default {};
}

示例:

@Controller
public class HelloController {

    @Autowired
    private User user;

    @RequestMapping("/hello")
    @ResponseBody
    public String sayHello() {
        throw new NullPointerException();
        //return "Hello Spring Boot!";
    }

    @RequestMapping("/user")
    @ResponseBody
    public String helloUser() {
        int i = 10 / 0;
        return JSON.toJSONString(user);
    }

    // 處理java.lang.ArithmeticException
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public String handlerArithmeticException(Exception e) {
        return "數(shù)學(xué)運算錯誤:" + e.getMessage();
    }

    // 處理java.lang.NullPointerException
    @ExceptionHandler(value = {NullPointerException.class})
    @ResponseBody
    public String handlerNullPointerException(Exception e) {
        // e:該對象包含錯誤信息
        return "空指針錯誤:" + e;
    }
}

輸出:

image.png
image.png

@ControllerAdvice注解

剛才的@ExceptionHandler注解是用在控制器類里面的,這樣每個控制器都需要定義相關(guān)方法,比較繁瑣。這時可以使用@ControllerAdvice來抽取所有共同的@ExceptionHandler方法,從而簡化異常方法的定義。

注意:

當(dāng)業(yè)務(wù)Controller有自己的@ExceptionHandler注解處理方法時,生效的是Controller上的異常處理方法,@ControllerAdvice里的不會生效。

示例:

@ControllerAdvice
public class CommonExceptionHandler {

    // 處理java.lang.ArithmeticException
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public String handlerArithmeticException(Exception e) {
        return "數(shù)學(xué)運算錯誤1:" + e.getMessage();
    }

    // 處理java.lang.NullPointerException
    @ExceptionHandler(value = {NullPointerException.class})
    @ResponseBody
    public String handlerNullPointerException(Exception e) {
        // e:該對象包含錯誤信息
        return "空指針錯誤1:" + e;
    }
}

輸出:

image.png

HandlerExceptionResovler

注意:

異常處理優(yōu)先級順序:Controller層異常 > @ControllerAdvice > HandlerExceptionResovler

示例:

@Configuration
public class CommonHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView mv = new ModelAndView();
        //判斷不同異常類型,做不同處理
        if(e instanceof ArithmeticException){
            mv.setViewName("error1");
        }
        if(e instanceof NullPointerException){
            mv.setViewName("error2");
        }
        mv.addObject("error", e.toString());
        return mv;
    }
}

輸出:

image.png

Spring Boot表單數(shù)據(jù)驗證

在Spring Boot中我們經(jīng)常需要對表單數(shù)據(jù)進行合法性驗證。下面講解如何在Spring Boot中進行表單驗證。

更多校驗規(guī)則參考Spring,兩者是一樣的。

示例:

pom.xml

<dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 導(dǎo)入thymeleaf坐標 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

Pojo,添加驗證注解

public class User {

    private Integer id;

    @NotEmpty(message = "姓名不能為空")
    private String name;
    @Min(1)
    private Integer age;
    @Email(message = "郵箱地址不正確")
    private String email;
    @NotEmpty(message = "描述不能為空")
    @Length(min = 5, max = 100, message = "描述必須在5-100個字之間")
    private String desc;
    //...}
@Controller
@RequestMapping("/user")
public class UserController {
    /**
     * 跳轉(zhuǎn)到add.html
     * @return
     */
    @RequestMapping("/toAdd")
    public String toAdd() {
        return "add";
    }
    /**
     * 用戶添加
     * BindingResult: 用于封裝驗證對象(user)里面的驗證結(jié)果
     */
    @RequestMapping("/add")
    public String add(@Valid User user, BindingResult result) {
        if (result.hasErrors()) {
            return "add";
        }
        //save
        System.out.println("保存用戶:" + JSON.toJSONString(user));
        return "success";
    }
}

設(shè)計頁面,回顯錯誤信息

add.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>用戶添加</title>
</head>
<body>
<h3>用戶添加</h3>
<form action="/user/add" method="post">
    用戶名:<input type="text" name="name"/><font color="red" th:errors="${user.name}"></font><br/>
    描述:<input type="text" name="desc"/><font color="red" th:errors="${user.desc}"></font><br/>
    年齡:<input type="text" name="age"/><font color="red" th:errors="${user.age}"></font><br/>
    郵箱:<input type="text" name="email"/><font color="red" th:errors="${user.email}"></font><br/>
    <input type="submit" value="保存"/>
</form>
</body>
</html>

success.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>提示頁面</title>
</head>
<body>
保存成功
</body>
</html>
保存用戶:{"age":25,"desc":"黃金腦殿下","email":"roy@inc.com","name":"艾米"}

Spring Boot文件上傳

示例:

\resources\static\upload.html——在靜態(tài)資源文件夾下這樣我們就可以直接訪問靜態(tài)資源。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件上傳頁面</title>
</head>
<body>
文件上傳頁面

<hr/>
<form action="/upload" method="post" enctype="multipart/form-data">
    請選擇文件:<input type="file" name="fileName"/><br/>
    <input type="submit" value="開始上傳"/>
</form>
</body>
</html>
@Controller
public class UploadController {

    @RequestMapping("/upload")
    public String upload(MultipartFile fileName, HttpServletRequest request){
        //處理文件
        System.out.println("文件原名稱:"+fileName.getOriginalFilename());
        System.out.println("文件類型:"+fileName.getContentType());
        String upload = UploadController.class.getResource("/").getFile()+"/upload";
        File file = new File(upload);
        if (!file.exists()) {
            file.mkdir();
        }
        //目標文件傳入地址路徑+名稱
        try {
            fileName.transferTo(new File(upload + "/" + fileName.getOriginalFilename()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

Spring Boot文件下載

示例:

\resources\static\download.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件下載</title>
</head>
<body>
<h3>文件下載</h3>
<a href="/download">下載</a>
</body>
</html>
@Controller
public class DownloadController {

    @RequestMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
        InputStream inputStream = new FileInputStream(DownloadController.class.getResource("/").getFile()+"/static/img/test.gif");
        //2.輸出文件
        //設(shè)置響應(yīng)頭
        response.setHeader("Content-Disposition","attachment;filename=export.gif");
        OutputStream outputStream = response.getOutputStream();
        byte[] buff = new byte[1024];
        int lenth = 0;
        while ((lenth= inputStream.read(buff))!= -1){
            outputStream.write(buff,0,lenth);
        }
        //3.關(guān)閉資源
        outputStream.close();
        inputStream.close();
    }
}

Spring Boot原理分析

@SpringBootApplication

首先,我從引導(dǎo)類開始:

@SpringBootApplication
public class MyBootApplication {

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

}

引導(dǎo)類代碼很簡單,但可以看出最關(guān)鍵的是@SpringBootApplication注解以及在main方法中運行的SpringAppliation.run()了,我們進去@SpringBootApplication的源碼:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
  ......
}

我們看到@SpringBootApplication其實是一個復(fù)合的注解,它就是由@SpringBootConfiguration@EnableAutoConfiguration以及@ComponentScan 三個注解組成,所以如果我們把SpringBoot啟動類改寫成如下方式,整個SpringBoot應(yīng)用依然可以與之前的啟動類功能一樣:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public class MyBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class, args);
    }
}

因為我們每次新建項目時都要寫上三個注解來完成配置,這顯然太繁瑣了,SpringBoot就為我們提供了@SpringBootApplication這樣注解來簡化我們的操作。接著,我們重點分析這三個注解的作用。

@SpringBootConfiguration

我們來看@SpringBootConfiguration注解的源碼:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

我們可以看到,SpringBoot為了區(qū)別@Configuration而新提供的專屬于SpringBoot的注解,功能其實和@Configuration一模一樣。而這里的@Configuration注解對于我們來說并不陌生,它就是是個IoC容器的配置類??吹竭@里,我們其實可以把SpringBoot的啟動類這樣來看就清楚了:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class MyBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class, args);
    }
}

啟動類MyBootApplication其實就是一個標準的Spring純注解下的啟動類,也并沒有什么特殊。

@EnableAutoConfiguration

看到這個注解,我們不禁聯(lián)想出Spring 中很多以“@Enable”開頭的注解,比如:@EnableScheduling、@EnableCaching以及@EnableMBeanExport等,@EnableAutoConfiguration注解的理念和工作原理和它們其實一脈相承。簡單的來說,就是該注解借助@Import注解的支持,Spring的IoC容器收集和注冊特定場景相關(guān)的Bean定義:

  • @EnableScheduling是通過@Import將Spring調(diào)度框架相關(guān)的bean都加載到IoC容器。
  • @EnableMBeanExport是通過@Import將JMX相關(guān)的bean定義加載到IoC容器。

@EnableAutoConfiguration注解也是借助@Import將所有符合配置條件的bean定義加載到IoC容器,僅此而已!@EnableAutoConfiguration注解的源碼如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}

這其中最關(guān)鍵的就是@Import(AutoConfigurationImportSelector.class)了,它借助AutoConfigurationImportSelector.class可以幫助SpringBoot應(yīng)用將所有符合條件的@Configuration配置類都加載到當(dāng)前SpringBoot創(chuàng)建并使用的IoC容器,就像下圖一樣。

image.png

下面我們給出AutoConfigurationImportSelector.java的部分源碼,來解釋和驗證上圖:

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
    protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
                this.beanClassLoader);
    }
    protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
                this.beanClassLoader);
    }
}

以上源碼可以看出,@EnableAutoConfiguration正是借助SpringFactoriesLoader的支持,才能完成所有配置類的加載!

SpringFactoriesLoader

SpringFactoriesLoader屬于Spring框架專屬的一種擴展方案(其功能和使用方式類似于Java的SPI方案:java.util.ServiceLoader),它的主要功能就是從指定的配置文件META-INF/spring.factories中加載配置,spring.factories是一個非常經(jīng)典的java properties文件,內(nèi)容格式是Key=Value形式,只不過這Key以及Value都非常特殊,為Java類的完整類名(Fully Qualified Name),比如:

org.springframework.context.ApplicationListener=org.springframework.boot.autoconfigure.BackgroundPreinitializer

然后Spring框架就可以根據(jù)某個類型作為Key來查找對應(yīng)的類型名稱列表了,SpringFactories源碼如下:

public abstract class SpringFactoriesLoader {

    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader){
        ...
    }

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ...
    }
    // ...
}

對于@EnableAutoConfiguraion來說,SpringFactoriesLoader的用途和其本意稍微不同,它本意是為了提供SPI擴展,而在@EnableAutoConfiguration這個場景下,它更多的是提供了一種配置查找的功能的支持,也就是根據(jù)@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為Key來獲取一組對應(yīng)的@Configuration類:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

在SpringBoot的autoconfigure依賴包中的META-INF文件下的spring.factories文件中,我們可以找到以上內(nèi)容。

總結(jié)來說,@EnableAutoConfiguration能實現(xiàn)自動配置的原理就是:SpringFactoriesLoader從classpath中搜尋所有META-INF/spring.fatories文件,并將其中Key[org.springframework.boot.autoconfigure.EnableAutoConfiguration]對應(yīng)的Value配置項通過反射的方式實例化為對應(yīng)的標注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總到當(dāng)前使用的IoC容器中。

@ComponentScan

@ComponentScan注解在Spring Boot啟動的時候其實不是必需的!因為我們知道作為Spring框架里的老成員,@ComponentScan的功能就是自動掃描并加載復(fù)合條件的組件或Bean定義,最終將這些Bean定義加載到當(dāng)前使用的容器中。這個過程,我們可以手工單個進行注冊,不是一定要通過這個注解批量掃描和注冊,所以說@ComponentScan是非必需的。

所以,如果我們當(dāng)前應(yīng)用沒有任何Bean定義需要通過@ComponentScan加載到當(dāng)前SpringBoot應(yīng)用對應(yīng)的IoC容器,那么,去掉@ComponentScan注解,當(dāng)前的SpringBoot應(yīng)用依舊可以完美運行!

Spring Boot整合其他技術(shù)

Spring Boot整合Servlet

在Spring Boot應(yīng)用如果我們需要編寫Servlet相關(guān)組件(包括Servlet、Filter、Listener),需要怎么做呢?Spring Boot提供了兩種使用Servlet組件的方式

  1. 使用@ServletComponentScan注解注冊
  2. 使用@Bean注解注解

@ServletComponentScan

注意:在引導(dǎo)類類必須添加@ServletComponentScan注解,該注解用于掃描應(yīng)用中Servlet相關(guān)組件。

示例:

/**
 * 等同于web.xml配置
 *     <servlet>
 *         <servlet-name>helloServlet</servlet-name>
 *         <servlet-class>com.yiidian.controller.HelloServlet</servlet-class>
 *     </servlet>
 *     <servlet-mapping>
 *            <servlet-name>helloServlet</servlet-name>
 *         <url-pattern>/helloServlet</url-pattern>
 *     </servlet-mapping>
 *
 */
// @WebServlet:聲明該類為Servlet程序
@WebServlet("/hi")
//@WebServlet(name="helloServlet",urlPatterns="/hi")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HelloServlet");
        String docType = "<!DOCTYPE html> \n";
        String top = "Hello SpringBoot";
        resp.getWriter().println(docType +
                "<html>\n" +
                "<head><title>" + top + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + top + "</h1>\n" +
                "</body></html>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doGet(req,resp);
    }
}
//表示對所有servlet都執(zhí)行過濾操作
//@WebFilter
//表示對路徑為/ 的servlet執(zhí)行過濾操作
//@WebFilter("/")
//表示對路徑為/hi 的servlet執(zhí)行過濾操作,兩種寫法都可以
@WebFilter("/hi")
//@WebFilter(filterName="HiFilter",urlPatterns="/hi")
//定義Filter過濾器
public class HiFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("HiFilter init");

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("do HiFilter before");
        //放行執(zhí)行目標servlet資源
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("do HiFilter after");
    }

    @Override
    public void destroy() {
        System.out.println("HiFilter destroy");
    }
}
//定義Listener監(jiān)聽器
@WebListener
public class HiListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext對象創(chuàng)建了");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext對象消耗了");
    }
}

引導(dǎo)類

@SpringBootApplication
@ServletComponentScan //注冊Servlet組件
public class MyBootApplication {

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

}

輸出:

ServletContext對象創(chuàng)建了
HiFilter init
do HiFilter before
HelloServlet
do HiFilter after

注意:在測試時發(fā)現(xiàn)按照正確配置去寫代碼但是一直訪問不成功。可能的原因有兩個:

  • IDEA的問題,一直重啟卻識別不了@ServletComponentScan去掃描servlet,導(dǎo)致失敗。
  • 啟動類的@ServletComponentScan掃描默認是掃描與該啟動類同包以及其子包下的類。
image.png

@Bean

第二種方式的代碼和第一種幾乎一樣,就是引導(dǎo)類的差別,引導(dǎo)類改為使用@Bean注解來注解Servlet、Filter和Listener。

示例:

@SpringBootApplication
public class MyBeanBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBeanBootApplication.class,args);
    }
    //注冊Servlet程序
    @Bean
    public ServletRegistrationBean getServletRegistrationBean(){
        ServletRegistrationBean bean = new ServletRegistrationBean(new HelloServlet());
        bean.addUrlMappings("/hi");
        return bean;
    }
    //注冊Filter
    @Bean
    public FilterRegistrationBean getFilterRegistrationBean(){
        FilterRegistrationBean bean = new FilterRegistrationBean(new HiFilter());
        bean.addUrlPatterns("/hi");
        return bean;
    }
    //注冊Listener
    @Bean
    public ServletListenerRegistrationBean getServletListenerRegistrationBean(){
        return new ServletListenerRegistrationBean(new HiListener());
    }
}

注意:如果兩個都配置則filter和listener會執(zhí)行兩遍。當(dāng)使用@Bean整合Servlet時,Servlet,F(xiàn)ilter,Listener不需要加對應(yīng)的注解,因為我們在@Bean中已經(jīng)把new HelloServlet()創(chuàng)建出來了。

Spring Boot整合JSP

示例:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.self</groupId>
    <artifactId>hellospringboot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 導(dǎo)入springboot父工程. 注意:任何的SpringBoot工程都必須有的?。?! -->
    <!-- 父工程的作用:鎖定起步的依賴的版本號,并沒有真正到依賴 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.11.RELEASE</version>
    </parent>

    <dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>
        <!--devtools熱部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>true</scope>
        </dependency>
        <!-- jsp依賴 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
 <!--       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>-->
    </dependencies>
</project>

注意:Error resolving template [userlist], template might not exist or might not be accessible by any of the configured Template Resolvers.這是因為整合jsp就不能依賴thymeleaf。

org.thymeleaf.exceptions.TemplateInputException: Error resolving template [userlist], template might not exist or might not be accessible by any of the configured Template Resolvers
    at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:869) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
    at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:607) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]

        <!-- 導(dǎo)入thymeleaf坐標 整合jsp就不能依賴thymeleaf,否則會因為請求不到頁面而報錯-->
<!--        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>-->
image.png

起步依賴tomcat。因為spring-boot-starter-web已經(jīng)有spring-boot-starter-tomcat依賴了,如下,因此不再需要再依賴tomcat。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
  <version>2.1.11.RELEASE</version>
  <scope>compile</scope>
</dependency>

擴展:

pom中<scope></scope>一些理解
compile:默認值,表示當(dāng)前依賴包,要參與當(dāng)前項目的編譯,后續(xù)測試,運行時,打包
provided:代表在編譯和測試的時候用,運行,打包的時候不會打包進去
test:表示當(dāng)前依賴包只參與測試時的工作:比如Junit
runtime:表示當(dāng)前依賴包只參與運行周期,其他跳過了
system:從參與度和provided一致,不過被依賴項不會從maven遠程倉庫下載,而是從本地的系統(tǒng)拿。需要
systemPath屬性來定義路徑

配置application.yml

#springmvc視圖解析器配置
spring:
  mvc:
    view:
      prefix: /WEB-INF/jsp/  # 前綴,注意最后的文件夾jsp后面要加斜杠/
      suffix: .jsp  # 后綴
@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/list")
    public String list(Model model) {
        //模擬用戶數(shù)據(jù)
        List<User> list = new ArrayList<User>();
        for (int i = 0; i < 3; i++) {
            User user = new User();
            user.setId(ThreadLocalRandom.current().nextInt());
            user.setAge(ThreadLocalRandom.current().nextInt(100));
            user.setName(UUID.randomUUID().toString());
            list.add(user);
        }
        //把數(shù)據(jù)存入model
        model.addAttribute("list", list);
        //跳轉(zhuǎn)到j(luò)sp頁面: userlist.jsp
        return "userlist";
    }
}

編寫userlist.jsp頁面

注意:Spring Boot項目并不會到templates目錄查找JSP頁面,它是到/src/main/webapp目錄下查找。所以我們需要創(chuàng)建webapp目錄,然后在里面創(chuàng)建我們需要的目錄和JSP文件。

image.png
<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>用戶列表展示</title>
</head>
<body>

<h3>用戶列表展示</h3>
<table border="1">
    <tr>
        <th>編號</th>
        <th>姓名</th>
        <th>年齡</th>
    </tr>
    <c:forEach items="${list}" var="user">
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.age}</td>
        </tr>
    </c:forEach>
</table>
</body>
</html>
@SpringBootApplication
public class MyBootApplication {

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

修改項目運行目錄,經(jīng)過測試,不修改也行,估計和IDEA版本有關(guān)。

Spring Boot應(yīng)用默認情況下,會到應(yīng)用的classes目錄下加載內(nèi)容,因為JSP頁面并不在classes下面,所以需要把運行目錄手動改為應(yīng)用的根目錄下,這樣才能加載到JSP頁面。 如下:

image.png

請求:http://localhost:8080/user/list

輸出:

image.png

報錯:

如下圖,是因為application.yml配置jsp頁面解析器路徑名稱弄錯。

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容