【Java EE】Spring MVC開發流程詳解

Spring MVC開發流程詳解


有了上文的初始化配置,開發Spring MVC流程並不困難。開發Spring MVC程序,需要掌握Spring MVC的組件和流程,所以開發過程中也會貫穿着Spring MVC的運行流程。

在目前的開發過程中,大部分都會採用註解的開發方式。使用註解在Spring MVC中十分簡單,主要是以一個註解@Controller標註,一般只需要通過掃描配置,就能夠將其掃描處理,只是往往還要結合註解@RequestMapping去配置它。@RequestMapping可以配置在類或者方法之上,它的作用是指定URI和哪個類(或者方法)作爲一個處理請求的處理器,爲了更加靈活,Spring MVC還定義了處理器的攔截器,當啓動Spring MVC的時候,Spring MVC就會去解析@Controller中@RequestMapping的配置,再結合所配置的攔截器,這樣它就會組成多個攔截器和一個控制器的形式,存放到一個HandlerMapping中去。當請求來到服務器,首先是通過請求信息找到對應的HandlerMapping,進而可以找到對應的攔截器和處理器,這樣就能夠運行對應的控制器和攔截器。

1. 配置@RequestMapping

@RequestMapping的源碼如下:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

	/**
	 * Assign a name to this mapping.
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used on both levels, a combined name is derived by concatenation
	 * with "#" as separator.
	 * @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
	 * @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
	 */
        // 請求路徑
	String name() default "";

	/**
	 * The primary mapping expressed by this annotation.
	 * <p>In a Servlet environment this is an alias for {@link #path}.
	 * For example {@code @RequestMapping("/foo")} is equivalent to
	 * {@code @RequestMapping(path="/foo")}.
	 * <p>In a Portlet environment this is the mapped portlet modes
	 * (i.e. "EDIT", "VIEW", "HELP" or any custom modes).
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this primary mapping, narrowing it for a specific handler method.
	 */
        // 請求路徑,可以是數組
	@AliasFor("path")
	String[] value() default {};

	/**
	 * In a Servlet environment only: the path mapping URIs (e.g. "/myPath.do").
	 * Ant-style path patterns are also supported (e.g. "/myPath/*.do").
	 * At the method level, relative paths (e.g. "edit.do") are supported within
	 * the primary mapping expressed at the type level. Path mapping URIs may
	 * contain placeholders (e.g. "/${connect}")
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this primary mapping, narrowing it for a specific handler method.
	 * @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE
	 * @since 4.2
	 */
        // 請求路徑,數組
	@AliasFor("value")
	String[] path() default {};

	/**
	 * The HTTP request methods to map to, narrowing the primary mapping:
	 * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this HTTP method restriction (i.e. the type-level restriction
	 * gets checked before the handler method is even resolved).
	 * <p>Supported for Servlet environments as well as Portlet 2.0 environments.
	 */
        // 請求類型,比如是HTTP的GET請求還是POST請求等,HTTP請求枚舉取值範圍爲:GET、HEAD、PUT、PATCH、DELETE、OPTIONS、TRACE,常用的是GET和POST請求
	RequestMethod[] method() default {};

	/**
	 * The parameters of the mapped request, narrowing the primary mapping.
	 * <p>Same format for any environment: a sequence of "myParam=myValue" style
	 * expressions, with a request only mapped if each such parameter is found
	 * to have the given value. Expressions can be negated by using the "!=" operator,
	 * as in "myParam!=myValue". "myParam" style expressions are also supported,
	 * with such parameters having to be present in the request (allowed to have
	 * any value). Finally, "!myParam" style expressions indicate that the
	 * specified parameter is <i>not</i> supposed to be present in the request.
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this parameter restriction (i.e. the type-level restriction
	 * gets checked before the handler method is even resolved).
	 * <p>In a Servlet environment, parameter mappings are considered as restrictions
	 * that are enforced at the type level. The primary path mapping (i.e. the
	 * specified URI value) still has to uniquely identify the target handler, with
	 * parameter mappings simply expressing preconditions for invoking the handler.
	 * <p>In a Portlet environment, parameters are taken into account as mapping
	 * differentiators, i.e. the primary portlet mode mapping plus the parameter
	 * conditions uniquely identify the target handler. Different handlers may be
	 * mapped onto the same portlet mode, as long as their parameter mappings differ.
	 */
        // 請求參數,當請求帶有配置的參數時,才匹配處理器
	String[] params() default {};

	/**
	 * The headers of the mapped request, narrowing the primary mapping.
	 * <p>Same format for any environment: a sequence of "My-Header=myValue" style
	 * expressions, with a request only mapped if each such header is found
	 * to have the given value. Expressions can be negated by using the "!=" operator,
	 * as in "My-Header!=myValue". "My-Header" style expressions are also supported,
	 * with such headers having to be present in the request (allowed to have
	 * any value). Finally, "!My-Header" style expressions indicate that the
	 * specified header is <i>not</i> supposed to be present in the request.
	 * <p>Also supports media type wildcards (*), for headers such as Accept
	 * and Content-Type. For instance,
	 * <pre class="code">
	 * &#064;RequestMapping(value = "/something", headers = "content-type=text/*")
	 * </pre>
	 * will match requests with a Content-Type of "text/html", "text/plain", etc.
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this header restriction (i.e. the type-level restriction
	 * gets checked before the handler method is even resolved).
	 * <p>Maps against HttpServletRequest headers in a Servlet environment,
	 * and against PortletRequest properties in a Portlet 2.0 environment.
	 * @see org.springframework.http.MediaType
	 */
        // 請求頭,當HTTP請求頭爲配置項時,才匹配處理器
	String[] headers() default {};

	/**
	 * The consumable media types of the mapped request, narrowing the primary mapping.
	 * <p>The format is a single media type or a sequence of media types,
	 * with a request only mapped if the {@code Content-Type} matches one of these media types.
	 * Examples:
	 * <pre class="code">
	 * consumes = "text/plain"
	 * consumes = {"text/plain", "application/*"}
	 * </pre>
	 * Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
	 * all requests with a {@code Content-Type} other than "text/plain".
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings override
	 * this consumes restriction.
	 * @see org.springframework.http.MediaType
	 * @see javax.servlet.http.HttpServletRequest#getContentType()
	 */
        // 請求類型爲配置類型才匹配處理器
	String[] consumes() default {};

	/**
	 * The producible media types of the mapped request, narrowing the primary mapping.
	 * <p>The format is a single media type or a sequence of media types,
	 * with a request only mapped if the {@code Accept} matches one of these media types.
	 * Examples:
	 * <pre class="code">
	 * produces = "text/plain"
	 * produces = {"text/plain", "application/*"}
	 * produces = "application/json; charset=UTF-8"
	 * </pre>
	 * <p>It affects the actual content type written, for example to produce a JSON response
	 * with UTF-8 encoding, {@code "application/json; charset=UTF-8"} should be used.
	 * <p>Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
	 * all requests with a {@code Accept} other than "text/plain".
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings override
	 * this produces restriction.
	 * @see org.springframework.http.MediaType
	 */
        // 處理器之後的響應用戶的結果類型,比如{“application/json;charset=UTF-8”,"text/plain","application/*"}
	String[] produces() default {};

}

這裏最常用到的時請求路徑和請求類型,其他的大部分作爲限定項,根據需要進行配置。例如在MyController中加入一個index2方法,代碼如下:

@RequestMapping(value = "/index2",method = RequestMethod.GET)
	public ModelAndView  index2() {
		ModelAndView mv = new ModelAndView();
		mv.setViewName("index");
		return mv;
	}

這樣對於/my/index2.do的HTTP GET請求提供響應了。

2. 控制器的開發

控制器開發是Spring MVC的核心內容,其步驟一般會分爲3步。

  • 獲取請求參數
  • 處理業務邏輯
  • 綁定模型和視圖

2.1 獲取請求參數

在Spring MVC中接收參數的方法很多,建議不要使用Servlet容器所給予的API,因爲這樣控制器將會依賴於Servlet容器,比如:

@RequestMapping(value = "/index2",method = RequestMethod.GET)
	public ModelAndView  index2(HttpSession session, HttpServletRequest request) {
		ModelAndView mv = new ModelAndView();
		mv.setViewName("index");
		return mv;
	}

Spring MVC會自動解析代碼中的方法參數session、request,然後傳遞關於Servlet容器的API,所以是可以獲取到的。通過request或者session都可以很容易地得到HTTP請求過來的參數,這固然是一個方法,但並非一個好的方法。因爲如果這樣做了,那麼對於index2方法而言,它就和Servlet容器緊密關聯了,不利於擴展和測試。爲了給予更好的靈活性,Spring MVC給予了更多的方法和註解以獲取參數。

如果要獲取一個HTTP請求的參數——id,它是一個長整型,那麼可以使用註解@RequestParam來獲取它,代碼修改爲:

@RequestMapping(value = "/index2",method = RequestMethod.GET)
	public ModelAndView  index2(@RequestParam("id") Long id) {
		System.out.println("params[id] = " + id);
		ModelAndView mv = new ModelAndView();
		mv.setViewName("index");
		return mv;
	}

在默認的情況下對於註解了@RequestParam的參數而言,它要求參數不能爲空,也就是當獲取不到HTTP請求參數的時候,Spring MVC將會拋出異常。有時候還希望給參數一個默認值,爲了解決這樣的困難,@RequestParam還給了兩個有用的配置項:

  • required是一個布爾值(boolean),默認是true,也就是不允許參數爲空,如果要允許爲空,則配置它爲false。
  • defaultValue的默認值爲"\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n",可以通過配置修改它爲想要的內容。 

獲取session中的內容,假設當前的Session中設置了userName,那麼應該如何獲取它呢?Spring MVC還提供了註解@SessionAttribute去從Session中獲取對應的數據。代碼如下:

@RequestMapping(value = "/index3",method = RequestMethod.GET)
	public ModelAndView  index3(@SessionAttribute("userName") String userName) {
		System.out.println("session[userName] = " + userName);
		ModelAndView mv = new ModelAndView();
		mv.setViewName("index");
		return mv;
	}

 

2.2 實現邏輯和綁定視圖

一般而言,實現的邏輯和數據庫有關聯,如果採用XML的方式,那麼只需要在applicationContext.xml中配置關於數據庫的部分就可以了;如果使用Java配置的方式,那麼需要在配置類WebConfig中的getRootConfigClasses加入對應的配置類即可。

有時候在使用第三方包開發的時候,使用XML方式會比註解方式方便一些,因爲不需要設計太多關於第三方包內容的Java代碼,甚至可以是混合使用,示例配置如下:

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-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">
	<!-- 使用註解驅動 -->
	<context:annotation-config />
	<!-- 數據庫連接池 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/chapter14" />
		<property name="username" value="root" />
		<property name="password" value="123456" />
		<property name="maxActive" value="255" />
		<property name="maxIdle" value="5" />
		<property name="maxWait" value="10000" />
	</bean>

	<!-- 集成mybatis -->
	<bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:/mybatis/mybatis-config.xml" />
	</bean>

	<!-- 配置數據源事務管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 採用自動掃描方式創建mapper bean -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	    <property name="basePackage" value="com.ssm.chapter14" />
	    <property name="SqlSessionFactory" ref="SqlSessionFactory" />
	    <property name="annotationClass" value="org.springframework.stereotype.Repository" />
	</bean>
</beans>

假設上述的XML配置文件,已經通過掃描的方式初始化了一個Spring IoC容器中的Bean——RoleService,而且它提供了一個參數爲long型的方法getRole來獲取角色,那麼可以通過自動裝配的方式在控制器中注入它。角色控制器代碼如下:

/**************import ***************/
@Controller
@RequestMapping("/role")
public class RoleController {
	// 注入角色服務類
	@Autowired
	private RoleService roleService = null;

	@RequestMapping(value = "/getRole", method = RequestMethod.GET)
	public ModelAndView getRole(@RequestParam("id") Long id) {
		Role role = roleService.getRole(id);
		ModelAndView mv = new ModelAndView();
		mv.setViewName("roleDetails");
		// 給數據模型添加一個角色對象
		mv.addObject("role", role);
		return mv;
	}
}

 從代碼中注入了RoleService,這樣就可以通過這個服務類使用傳遞的參數id來獲取角色,最後把查詢出來的角色添加給模型和視圖以便將來使用。

3. 視圖渲染 

一般地,Spring MVC會默認使用JstlView進行渲染,也就是它將查詢出來的模型綁定到JSTL(JSP標準標籤庫)模型中,這樣通過JSTL就可以把數據模型在JSP中讀出展示數據了,在Spring MVC中,還存在着大量的視圖可供使用,這樣就可以方便地將數據渲染到視圖中,用以響應用戶的請求。

在上文的代碼中使用了roleDetails的視圖名,根據配置,它會使用文件/WEB-INF/jsp/roleDetail.jsp去響應,也就是要在這個文件中編寫JSTL標籤將模型數據讀出即可,例如:

<%@ page pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>out標籤的使用</title>
</head>
<body>
</body>
<center>
	<table border="1">
		<tr>
			<td>標籤</td>
			<td>值</td>
		</tr>
		<tr>
			<td>角色編號</td>
			<td><c:out value="${role.id}"></c:out></td>
		</tr>
		<tr>
			<td>角色名稱</td>
			<td><c:out value="${role.roleName}"></c:out></td>
		</tr>
		<tr>
			<td>角色備註</td>
			<td><c:out value="${role.note}"></c:out></td>
		</tr>
	</table>
</center>
</html>

在目前的前端技術中,普遍使用Ajax技術,在這樣的情況下,往往後臺需要返回JSON數據給前端使用,對此,Spring MVC在模型和視圖也給予了良好的支持。getRole的代碼修改爲:

// 獲取角色
@RequestMapping(value = "/getRole2", method = RequestMethod.GET)
public ModelAndView getRole2(@RequestParam("id") Long id) {
	Role role = roleService.getRole(id);
	ModelAndView mv = new ModelAndView();
	mv.addObject("role", role);
	// 指定視圖類型
	mv.setView(new MappingJackson2JsonView());
	return mv;
}

代碼中視圖類型爲MappingJackson2JsonView,這就要下載關於Jackson2的包。由於這是一個JSON視圖,這樣Spring MVC就會通過這個視圖去渲染所需的結果。於是會在我們請求後得到需要的JSON數據,提供給Ajax異步請求使用了。它的執行流程如下:

JSON數據流程

只是這不是將結果變爲JSON的唯一方法,使用註解@ResponeBody是更爲簡單和廣泛使用的方法

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章