Java開發學習(二十三)----SpringMVC入門案例、工作流程解析及設定bean載入控制

2022-08-08 09:02:17

一、SpringMVC概述

SpringMVC是隸屬於Spring框架的一部分,主要是用來進行Web開發,是對Servlet進行了封裝。SpringMVC是處於Web層的框架,所以其主要的作用就是用來接收前端發過來的請求和資料然後經過處理並將處理的結果響應給前端,所以如何處理請求和響應是SpringMVC中非常重要的一塊內容。

咱們現在web程式大都基於三層架構來實現。

  • 瀏覽器傳送一個請求給後端伺服器,後端伺服器現在是使用Servlet來接收請求和資料

  • 如果所有的處理都交給Servlet來處理的話,所有的東西都耦合在一起,對後期的維護和擴充套件極為不利

  • 將後端伺服器Servlet拆分成三層,分別是webservicedao

    • web層主要由servlet來處理,負責頁面請求和資料的收集以及響應結果給前端

    • service層主要負責業務邏輯的處理

    • dao層主要負責資料的增刪改查操作

  • servlet處理請求和資料的時候,存在的問題是一個servlet只能處理一個請求

  • 針對web層進行了優化,採用了MVC設計模式,將其設計為controllerviewModel

    • controller負責請求和資料的接收,接收後將其轉發給service進行業務處理

    • service根據需要會呼叫dao對資料進行增刪改查

    • dao把資料處理完後將結果交給service,service再交給controller

    • controller根據需求組裝成Model和View,Model和View組合起來生成頁面轉發給前端瀏覽器

    • 這樣做的好處就是controller可以處理多個請求,並對請求進行分發,執行不同的業務操作。

隨著網際網路的發展,上面的模式因為是同步呼叫,效能慢慢的跟不上需求,所以非同步呼叫慢慢的走到了前臺,是現在比較流行的一種處理方式。

  • 因為是非同步呼叫,所以後端不需要返回view檢視,將其去除

  • 前端如果通過非同步呼叫的方式進行互動,後臺就需要將返回的資料轉換成json格式進行返回

  • SpringMVC主要負責的就是

    • controller如何接收請求和資料

    • 如何將請求和資料轉發給業務層

    • 如何將響應資料轉換成json發回到前端

介紹了這麼多,對SpringMVC進行一個定義

  • SpringMVC是一種基於Java實現MVC模型的輕量級Web框架

  • 優點

    • 使用簡單、開發便捷(相比於Servlet)

    • 靈活性強

二、SpringMVC入門案例

因為SpringMVC是一個Web框架,將來是要替換Servlet,所以先來回顧下以前Servlet是如何進行開發的?

1.建立web工程(Maven結構)

2.設定tomcat伺服器,載入web工程(tomcat外掛)

3.匯入座標(Servlet)

4.定義處理請求的功能類(UserServlet)

5.設定請求對映(設定對映關係)

SpringMVC的製作過程和上述流程幾乎是一致的,具體的實現流程是什麼?

1.建立web工程(Maven結構)

2.設定tomcat伺服器,載入web工程(tomcat外掛)

3.匯入座標(SpringMVC+Servlet)

4.定義處理請求的功能類(UserController)

5.設定請求對映(設定對映關係)

6.將SpringMVC設定載入到Tomcat容器中

2.1 案例製作

步驟1:建立Maven專案

開啟IDEA,建立一個新的web專案,指定maven-archetype-webapp骨架

步驟2:補全目錄結構

因為使用骨架建立的專案結構不完整,需要手動補全,將圖中language-level改為8

步驟3:匯入jar包

將pom.xml中多餘的內容刪除掉,再新增SpringMVC需要的依賴

<?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.itheima</groupId>
  <artifactId>springmvc_01_quickstart</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
​
  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
  </dependencies>
​
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <port>80</port>
          <path>/</path>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
​

說明:servlet的座標為什麼需要新增<scope>provided</scope>?

  • scope是maven中jar包依賴作用範圍的描述,

  • 如果不設定預設是compile在在編譯、執行、測試時均有效

  • 如果執行有效的話就會和tomcat中的servlet-api包發生衝突,導致啟動報錯

  • provided代表的是該包只在編譯和測試的時候用,執行的時候無效直接使用tomcat中的,就避免衝突

步驟4:建立設定類

@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}

步驟5:建立Controller類

@Controller
public class UserController {
    
    @RequestMapping("/save")
    public void save(){
        System.out.println("user save ...");
    }
}
​

步驟6:使用設定類替換web.xml

將web.xml刪除,換成ServletContainersInitConfig

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    //載入springmvc設定類
    protected WebApplicationContext createServletApplicationContext() {
        //初始化WebApplicationContext物件
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        //載入指定設定類
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
​
    //設定由springmvc控制器處理的請求對映路徑
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
​
    //載入spring設定類
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}

步驟7:設定Tomcat環境

步驟8:啟動執行專案

步驟9:瀏覽器存取

瀏覽器輸入http://localhost/save進行存取,會報如下錯誤:

頁面報錯的原因是後臺沒有指定返回的頁面,目前只需要關注控制檯看user save ...有沒有被執行即可。

步驟10:修改Controller返回值解決上述問題

前面我們說過現在主要的是前端傳送非同步請求,後臺響應json資料,所以接下來我們把Controller類的save方法進行修改

@Controller
public class UserController {
    
    @RequestMapping("/save")
    public String save(){
        System.out.println("user save ...");
        return "{'info':'springmvc'}";
    }
}
​

再次重啟tomcat伺服器,然後重新通過瀏覽器測試存取,會發現還是會報錯,這次的錯是404

出錯的原因是,如果方法直接返回字串,springmvc會把字串當成頁面的名稱在專案中進行查詢返回,因為不存在對應返回值名稱的頁面,所以會報404錯誤,找不到資源。

而我們其實是想要直接返回的是json資料,具體如何修改呢?

步驟11:設定返回資料為json

@Controller
public class UserController {
    
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        System.out.println("user save ...");
        return "{'info':'springmvc'}";
    }
}
​

再次重啟tomcat伺服器,然後重新通過瀏覽器測試存取,就能看到返回的結果資料

至此SpringMVC的入門案例就已經完成。

注意事項

  • SpringMVC是基於Spring的,在pom.xml只匯入了spring-webmvcjar包的原因是它會自動依賴spring相關座標

  • AbstractDispatcherServletInitializer類是SpringMVC提供的快速初始化Web3.0容器的抽象類

  • AbstractDispatcherServletInitializer提供了三個介面方法供使用者實現

    • createServletApplicationContext方法,建立Servlet容器時,載入SpringMVC對應的bean並放入WebApplicationContext物件範圍中,而WebApplicationContext的作用範圍為ServletContext範圍,即整個web容器範圍

    • getServletMappings方法,設定SpringMVC對應的請求對映路徑,即SpringMVC攔截哪些請求

    • createRootApplicationContext方法,如果建立Servlet容器時需要載入非SpringMVC對應的bean,使用當前方法進行,使用方式和createServletApplicationContext相同。

    • createServletApplicationContext用來載入SpringMVC環境

    • createRootApplicationContext用來載入Spring環境

知識點1:@Controller

名稱 @Controller
型別 類註解
位置 SpringMVC控制器類定義上方
作用 設定SpringMVC的核心控制器bean

知識點2:@RequestMapping

名稱 @RequestMapping
型別 類註解或方法註解
位置 SpringMVC控制器類或方法定義上方
作用 設定當前控制器方法請求存取路徑
相關屬性 value(預設),請求存取路徑

知識點3:@ResponseBody

名稱 @ResponseBody
型別 類註解或方法註解
位置 SpringMVC控制器類或方法定義上方
作用 設定當前控制器方法響應內容為當前返回值,無需解析

三、工作流程解析

為了更好的使用SpringMVC,我們將SpringMVC的使用過程總共分兩個階段來分析,分別是啟動伺服器初始化過程單次請求過程

3.1 啟動伺服器初始化過程

  1. 伺服器啟動,執行ServletContainersInitConfig類,初始化web容器

    • 功能類似於以前的web.xml

  2. 執行createServletApplicationContext方法,建立了WebApplicationContext物件

    • 該方法載入SpringMVC的設定類SpringMvcConfig來初始化SpringMVC的容器

  3. 載入SpringMvcConfig設定類

  4. 執行@ComponentScan載入對應的bean

    • 掃描指定包及其子包下所有類上的註解,如Controller類上的@Controller註解

  5. 載入UserController,每個@RequestMapping的名稱對應一個具體的方法

    • 此時就建立了 /save 和 save方法的對應關係

  6. 執行getServletMappings方法,設定SpringMVC攔截請求的路徑規則

    • /代表所攔截請求的路徑規則,只有被攔截後才能交給SpringMVC來處理請求

3.2 單次請求過程

  1. 傳送請求http://localhost/save

  2. web容器發現該請求滿足SpringMVC攔截規則,將請求交給SpringMVC處理

  3. 解析請求路徑/save

  4. 由/save匹配執行對應的方法save()

    • 上面的第五步已經將請求路徑和方法建立了對應關係,通過/save就能找到對應的save方法

  5. 執行save()

  6. 檢測到有@ResponseBody直接將save()方法的返回值作為響應體返回給請求方

四、bean載入控制

4.1 問題分析

在入門案例中我們建立過一個SpringMvcConfig的設定類,再回想前面咱們介紹Spring的時候也建立過一個設定類SpringConfig。這兩個設定類都需要載入資源,那麼它們分別都需要載入哪些內容?

我們先來看下目前我們的專案目錄結構:

  • config目錄存入的是設定類,寫過的設定類有:

    • ServletContainersInitConfig

    • SpringConfig

    • SpringMvcConfig

    • JdbcConfig

    • MybatisConfig

  • controller目錄存放的是SpringMVC的controller類

  • service目錄存放的是service介面和實現類

  • dao目錄存放的是dao/Mapper介面

controller、service和dao這些類都需要被容器管理成bean物件,那麼到底是該讓SpringMVC載入還是讓Spring載入呢?

  • SpringMVC載入其相關bean(表現層bean),也就是controller包下的類

  • Spring控制的bean

    • 業務bean(Service)

    • 功能bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)

分析清楚誰該管哪些bean以後,接下來要解決的問題是如何讓Spring和SpringMVC分開載入各自的內容。

在SpringMVC的設定類SpringMvcConfig中使用註解@ComponentScan,我們只需要將其掃描範圍設定到controller即可,如

在Spring的設定類SpringConfig中使用註解@ComponentScan,當時掃描的範圍中其實是已經包含了controller,如:

從包結構來看的話,Spring已經多把SpringMVC的controller類也給掃描到,所以針對這個問題該如何解決,就是咱們接下來要學習的內容。

概括的描述下咱們現在的問題就是因為功能不同,如何避免Spring錯誤載入到SpringMVC的bean?

4.2 思路分析

針對上面的問題,解決方案也比較簡單,就是:

  • 載入Spring控制的bean的時候排除掉SpringMVC控制的bean

具體該如何排除:

  • 方式一:Spring載入的bean設定掃描範圍為精準範圍,例如service包、dao包等

  • 方式二:Spring載入的bean設定掃描範圍為com.itheima,排除掉controller包中的bean

  • 方式三:不區分Spring與SpringMVC的環境,載入到同一個環境中[瞭解即可]

4.3 設定bean載入控制

方式一:修改Spring設定類,設定掃描範圍為精準範圍。

@Configuration
@ComponentScan({"com.itheima.service","comitheima.dao"})
public class SpringConfig {
}

說明:

上述只是通過例子說明可以精確指定讓Spring掃描對應的包結構,真正在做開發的時候,因為Dao最終是交給MapperScannerConfigurer物件來進行掃描處理的,我們只需要將其掃描到service包即可。

方式二:修改Spring設定類,設定掃描範圍為com.itheima,排除掉controller包中的bean

@Configuration
@ComponentScan(value="com.itheima",
    [email protected](
        type = FilterType.ANNOTATION,
        classes = Controller.class
    )
)
public class SpringConfig {
}
  • excludeFilters屬性:設定掃描載入bean時,排除的過濾規則

  • type屬性:設定排除規則,當前使用按照bean定義時的註解型別進行排除

    • ANNOTATION:按照註解排除

    • ASSIGNABLE_TYPE:按照指定的型別過濾

    • ASPECTJ:按照Aspectj表示式排除,基本上不會用

    • REGEX:按照正規表示式排除

    • CUSTOM:按照自定義規則排除

    大家只需要知道第一種ANNOTATION即可

  • classes屬性:設定排除的具體註解類,當前設定排除@Controller定義的bean

如何測試controller類已經被排除掉了?

public class App{
    public static void main (String[] args){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        System.out.println(ctx.getBean(UserController.class));
    }
}

如果被排除了,該方法執行就會報bean未被定義的錯誤

注意:測試的時候,需要把SpringMvcConfig設定類上的@ComponentScan註解註釋掉,否則不會報錯

為啥需要註釋掉呢?出現問題的原因是,

  • Spring設定類掃描的包是com.itheima

  • SpringMVC的設定類,SpringMvcConfig上有一個@Configuration註解,也會被Spring掃描到

  • SpringMvcConfig上又有一個@ComponentScan,把controller類又給掃描進來了

  • 所以如果不把@ComponentScan註釋掉,Spring設定類將Controller排除,但是因為掃描到SpringMVC的設定類,又將其載入回來,演示的效果就出不來

  • 解決方案,也簡單,把SpringMVC的設定類移出Spring設定類的掃描範圍即可。

最後一個問題,有了Spring的設定類,要想在tomcat伺服器啟動將其載入,我們需要修改ServletContainersInitConfig

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    protected WebApplicationContext createRootApplicationContext() {
      AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
}

對於上述的設定方式,Spring還提供了一種更簡單的設定方式,可以不用再去建立AnnotationConfigWebApplicationContext物件,不用手動register對應的設定類,如何實現?

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
​
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }
​
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }
​
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

知識點1:@ComponentScan

名稱 @ComponentScan
型別 類註解
位置 類定義上方
作用 設定spring設定類掃描路徑,用於載入使用註解格式定義的bean
相關屬性 excludeFilters:排除掃描路徑中載入的bean,需要指定類別(type)和具體項(classes) includeFilters:載入指定的bean,需要指定類別(type)和具體項(classes)