Java 22:Spring 5(Spring MVC 入門)
Spring MVC的流程經歷流程:
1、請求帶著使用者請求的資訊,到達DispatcherServlet。
Spring MVC所有的請求都會通過一個前端控制器Servlet。前端控制器是常用的Web應用程式模式。DispatcherServlet的任務是將請求傳送給Spring MVC控制器。控制器是一個用於處理請求的spring元件。
2、DispatcherServlet查詢處理器對映器以確定將請求傳送給哪一個控制器
DispatcherServlet會查詢一個或多個處理器對映器(handler mapping)來確定下一站到哪裡。處理器對映器會根據請求所攜帶的URL資訊進行決策。
3、請求資訊交由控制器處理
控制器處理使用者的請求資訊,通常需要將產生的資訊返回給使用者並在瀏覽器上顯示。這些資訊稱為模型。僅僅把原始資訊返回給使用者是不夠的,需要以使用者友好的方式進行格式化,一般是HTML,所以資訊需要傳送給一個檢視(view),通常是JSP。
4、控制器將模型及邏輯檢視名返回給DispatcherServlet,查詢檢視解析器匹配為特定的檢視實現。
這樣控制器就不會和特定的檢視耦合,傳遞的檢視名並不直接表示某個JSP,甚至不確定是否是JSP,這個名稱被用來查詢真正的檢視,DispatcherServlet通過查詢檢視解析器確定真正的檢視。
5、檢視將模型渲染輸出,輸出會通過響應物件傳遞給客戶端
下面就是Spring MVC的使用。
首先是需要的JAR包:
這些大概是Spring基本使用中最常見的包了,一開始像往常一樣:右鍵專案——build path——Add External JARs,將這些包導進去,但是實際執行的時候,說找不到類檔案,這和之前在InteliJ裡的現象有點像,後來改成不顯示Add External JARs,而是直接將這些包拷貝到專案WEB-INF的lib資料夾裡,等到它自動識別以後,Eclipse裡每個包的上面會多一個小標誌,這就真正能夠識別到了。
必備的檔案:
1、web.xml
2、spring mvc宣告的xml
3、控制器的java檔案
4、檢視的jsp檔案
1、web.xml:
DispatcherServlet繼承自HttpServlet,在web.xml中宣告,這裡給它起名叫dispather,預設情況下會在WEB-INF下找[<servlet-name>]-servlet.xml,這裡也就是dispather-servlet.xml,來檢視spring-mvc的更為具體的功能配置,比如檢視解析器,也可以選擇自定義路徑和檔名:<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:META-INF/spring/springmvc-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
這樣就會去src/MRTA-INF/spring目錄下找名為springmvc-context.xml的檔案
load-on-startup:
標記宣告是否容器在啟動的時候載入該servlet(例項化並呼叫init()方法),它的值是一個整數,表示其被載入的順序。當其大於等於0,表示容器在應用啟動時就載入這個servlet,當其小於0或沒有指定,表示容器在該servlet被選擇時才去載入。正數的值越小代表啟動時優先順序越高,允許同樣的優先順序出現(是優先順序,不是順序,也不是啟動的延時)
servlet-mapping:
在說明這個之前,先介紹一下servlet,感覺在各種框架的時代,一般很少需要直接接觸servlet,但是也離不開servlet的支援。servlet是“伺服器端小程式”,用於處理和響應客戶的請求。Servlet是一個特殊的Java類,繼承自HttpServlet。客戶端通常有GET和POST兩種請求方式,servlet相應地重寫了doGet和dePsot方法。大部分情況下,servlet對於所有請求響應是一樣的,只需要重寫service()方法即可響應所有客戶端請求。
servlet另有init()和destroy()方法用於初始化資源和回收資源。
servlet可以在以下情況建立例項:(1)客戶第一次請求某個servlet時,大部分servlet都是如此(2)web應用啟動時立即建立,比如這裡DispatcherServlet
終於說到servlet-mapping,它的作用也就是將URL對映到某一個servlet,也就是選擇某一個servlet來處理相應的URL。URL的對映不是一個完全確定的,而是根據一條條規則進行選擇。
精確匹配::比如servletA 的url-pattern為 /test,servletB的url-pattern為 /* ,這個時候,如果我訪問的url為http://localhost/test
,這個時候容器就會先進行精確路徑匹配,發現/test正好被servletA精確匹配,那麼就去呼叫servletA,也不會去理會其他的servlet了。
最長路徑匹配。例子:servletA的url-pattern為/test/*,而servletB的url-pattern為/test/a/*,此時訪問http://localhost/test/a時,容器會選擇路徑最長的servlet來匹配,也就是這裡的servletB。
擴充套件匹配,如果url最後一段包含擴充套件,容器將會根據擴充套件選擇合適的servlet。例子:servletA的url-pattern:*.action
如果前面三條規則都沒有找到servlet,容器會根據url選擇對應的請求資源。如果應用定義了一個default servlet,則容器會將請求丟給default
servlet。
總之,其實可以定義多個DispatcherServlet來處理不同的URL請求。
ContextLoaderListener:
在看別人的例子時,有的包含這個配置,有的沒有,查了一下,發現Springmvc中確實可以省略不用,它的作用是建立了一個WebApplicationContext,如果不使用該上下文就可以不配置。正常情況下,還是需要配置,因為spring IOC的兩種實現,不常用的BeanFactory和常用的ApplicationContext。WebApplicationContext是ApplicationContext的一種高階實現。
同時,很多地方也提到了ContextLoaderListener和ApplicationContext的關係,說ContextLoaderListener的作用是自動裝配ApplicationContext的配置資訊。如果web.xml沒有配置相關的ApplicationContext資訊,那麼預設的路徑是“WEB-INF/applicationContext.xml”,如果要自定義路徑,則要在web.xml中加入“contextConfigLocation”這個引數。
《Spring實戰》裡說,“我們希望DispatcherServlet載入包含Web元件的bean,如控制器、檢視解析器以及處理器對映器,而ContextLoaderListener載入其他應用的bean,比如驅動應用後端的中間層和資料層元件”
web.xml的其他作用包括:配置歡迎頁、servlet、filter、指定錯誤處理頁面
2、dispatcher-servlet.xml:
因為前面設定的DispatcherServlet的名字是dispatcher,所以相應的Spring MVC的配置要去dispatcher-servlet.xml裡去找。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 支援註解 -->
<mvc:annotation-driven />
<!-- 設定自動掃描的路徑,用於自動注入bean 這裡的路徑與自己的專案目錄對應 -->
<context:component-scan base-package="controller" />
<!-- 這兩個類用來啟動基於Spring MVC的註解功能,將控制器與方法對映加入到容器中 -->
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<!-- 這個類用於Spring MVC檢視解析 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
後面我們的控制器儘量採取註解的方式,並且把需要掃描的包含的bean的包配置進去。
HandlerMapping:處理器對映器,就和前面的servlet-mapping一樣,將一個請求的URL指定到對應的controller上,Spring內建了很多處理器對映策略。首先需要將Handler註冊到HandlerMapping中,然後根據規則從已註冊的Handler中匹配對應的Handler,及Controller。也可以設定多個處理器對映器,DispatcherServlet根據優先順序,依次詢問HandlerMapping,直到獲取一個可用的Handler為止。
HandlerAdapter:處理器介面卡,定位到Handler以後,DispatcherServlet將得到的Handler告訴HandlerAdapter,HandlerAdapter根據請求去定位具體的方法。
可能是因為,DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter是預設的配置,這裡把這兩行去除,也不會對程式有影響。
ViewResolver:檢視解析器,首先View介面表示一個響應給使用者的檢視,例如jsp、html等。ViewResolver介面定義瞭如何通過view名稱來解析對應View例項的行為,該介面只有一個resolveViewName方法,通過viewName解析出View。常用的
InternalResourceViewResolver:就是在試圖邏輯名前面加上prefix,後面加上suffix,注意如果prefix和suffix的配置順序調換,可能發生路徑錯誤
ResourceBundleViewResolver:將邏輯檢視名和真實檔案的對映關係放在配置檔案中。
由於可以存在多個ViewResolver,所以resolver裡有一個配置項order,表明其優先順序。
我們程式碼裡的配置,說明我們在WEB-INF下建立了一個名為jsp的資料夾,專門存放jsp檔案
3、HelloController.java 控制器程式碼
控制器相關的內容,在之前21章已經講了一些,這裡先放一個最簡單的
@Controller
public class HelloController {
@RequestMapping("")
public String Home(Model model){
return "home";
}
@RequestMapping("/a")
public String Home2(Model model){
return "home2";
}
}
我們在dispatcher-servlet.xml裡設定的自動掃描的包是controller,因此,該類檔案要放在名為controller的package裡才能掃描到。
兩個函式分別處理字尾為空和字尾為“/a”的請求,比如在我們的專案中,分別是:http://localhost:8321/mvclearn/和http://localhost:8321/mvclearn/a
分別訪問home.jsp和home2.jsp
4、jsp
這裡隨便寫一個jsp和controller里名稱匹配即可,路徑要和xml裡一樣,及前面配置的WEB-INF/jsp/home.jsp
5、返回model給view
前面為止已經是一個SpringMVC完成的簡單例子了,但是controller僅提供了jsp的名稱,沒有給介面返回任何值。我們新增一個User類,類很簡單,也就是name和id兩個欄位,並且將其宣告為@Component,也就是Bean,然後再來看HelloController.java
HelloController.java
@Controller
public class HelloController {
private User user;
@Autowired
public HelloController(User user){
this.user=user;
}
@RequestMapping("")
public String home2(Model model){
model.addAttribute("user", user);
return "home2";
}
}
我們用自動註解的方式,裝配了user進來,然後把它作為model的attribute的value,傳遞給view。
home2.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Spring MVC</title>
</head>
<body>
姓名:${user.name}
<br>
id:${user.id}
</body>
</html>
這裡要注意,不論是User的public或是private欄位,都可以由前端獲取,但前提是他們都設定了對應的getter和setter方法。
addAttribute有多個過載函式,其中有隻包含一個Object而沒有String(鍵)的。Model的本質就是一個Map的鍵值對,當addAttribute不指定key的時候,他會根據值的物件型別來推斷,比如我們改寫:
@Controller
public class HelloController {
private User a;
@Autowired
public HelloController(User a){
this.a=a;
}
@RequestMapping("")
public String home2(Model model){
model.addAttribute(a);
return "home2";
}
}
我們只在model了添加了User型別的變數a,但是home2.jsp呼叫時還是一樣的用${user.name},這裡之所以將private User user改成了User a,是避免混淆,預設的鍵是根據型別User產生的小寫user,而不是根據a,所以這個User叫“user”還是“a”,它的key都是“user”。
再次修改HelloController,把User不宣告為bean,返回給前端一個List<User>
@Controller
public class HelloController {
private List<User> users;
public HelloController(){
users=new ArrayList<>();
}
@RequestMapping("")
public String home2(Model model){
User a=new User("a",1);
User b=new User("b",2);
users.add(a);
users.add(b);
model.addAttribute("users",users);
return "home2";
}
}
此時對應的jsp是:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<title>Spring MVC</title>
</head>
<body>
<c:forEach items="${users}" var="user">
姓名:<c:out value="${user.name}"/> --
id:<c:out value="${user.id}"/>
<br/>
</c:forEach>
</body>
</html>
jsp裡用到了jstl表示式,需要相應的JAR,並且需要使用前宣告,<forEach>迴圈打印出User資訊。這裡傳過來的是List<User>,但它本身不是bean,也不是一個類,Use中依然需要宣告getter和setter,雖然這一次的User不是Bean,但只要有了getter、setter,依然能訪問。
將model的鍵省略:
model.addAttribute(users);
根據users的型別List<User>,對應的key變成了“userList”。
更進一步可以省略檢視名:
@RequestMapping("/home2")
public List<User> home2(Model model){
User a=new User("a",1);
User b=new User("b",2);
users.add(a);
users.add(b);
return users;
}
model的鍵還是根據List<User>生成的對應userList,檢視名則根據請求路徑得到,前面我們的請求路徑都是空,得到的就是“.jsp”,就出了問題,所以加上“/home2”,這樣預設的檢視名和前面一樣還是home2.jsp
到這裡相當於,已經把最開始說的Spring MVC的一整套流程走通了:
-web.xml配置了DispatcherServlet,攔截、分發一切請求
-controller上宣告的RequestMapping接收相應的URL
-具體的方法來處理請求,返回model,指定展示的view
-最終以一個jsp呈現結果