1. 程式人生 > >Java 22:Spring 5(Spring MVC 入門)

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:

<?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>
DispatcherServlet繼承自HttpServlet,在web.xml中宣告,這裡給它起名叫dispather,預設情況下會在WEB-INF下找[<servlet-name>]-servlet.xml,這裡也就是dispather-servlet.xml,來檢視spring-mvc的更為具體的功能配置,比如檢視解析器,也可以選擇自定義路徑和檔名:
<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呈現結果