一、SpringMVC概述
SpringMVC是隸屬於Spring框架的一部分,主要是用來進行Web開發,是對Servlet進行了封裝。SpringMVC是處於Web層的框架,所以其主要的作用就是用來接收前端發過來的請求和數據然後經過處理並將處理的結果響應給前端,所以如何處理請求和響應是SpringMVC中非常重要的一塊內容。
咱們現在web程序大都基於三層架構來實現。
-
瀏覽器發送一個請求給後端服務器,後端服務器現在是使用Servlet來接收請求和數據
-
如果所有的處理都交給Servlet來處理的話,所有的東西都耦合在一起,對後期的維護和擴展極爲不利
-
將後端服務器Servlet拆分成三層,分別是
web
、service
和dao
-
web層主要由servlet來處理,負責頁面請求和數據的收集以及響應結果給前端
-
service層主要負責業務邏輯的處理
-
dao層主要負責數據的增刪改查操作
-
-
servlet處理請求和數據的時候,存在的問題是一個servlet只能處理一個請求
-
針對web層進行了優化,採用了MVC設計模式,將其設計爲
controller
、view
和Model
-
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-webmvc
jar包的原因是它會自動依賴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 啓動服務器初始化過程
-
服務器啓動,執行ServletContainersInitConfig類,初始化web容器
-
功能類似於以前的web.xml
-
-
執行createServletApplicationContext方法,創建了WebApplicationContext對象
-
該方法加載SpringMVC的配置類SpringMvcConfig來初始化SpringMVC的容器
-
-
加載SpringMvcConfig配置類
-
執行@ComponentScan加載對應的bean
-
掃描指定包及其子包下所有類上的註解,如Controller類上的@Controller註解
-
-
加載UserController,每個@RequestMapping的名稱對應一個具體的方法
-
此時就建立了
/save
和 save方法的對應關係
-
-
執行getServletMappings方法,設定SpringMVC攔截請求的路徑規則
-
/
代表所攔截請求的路徑規則,只有被攔截後才能交給SpringMVC來處理請求
-
3.2 單次請求過程
-
發送請求
http://localhost/save
-
web容器發現該請求滿足SpringMVC攔截規則,將請求交給SpringMVC處理
-
解析請求路徑/save
-
由/save匹配執行對應的方法save()
-
上面的第五步已經將請求路徑和方法建立了對應關係,通過/save就能找到對應的save方法
-
-
執行save()
-
檢測到有@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) |