Spring爲展現層提供了一個優秀的Web框架--SpringMVC。和衆多Web框架一樣,它基於MVC設計理念,此外,它採用了鬆散耦合可插拔組件結構,比其他MVC框架更具擴展性和靈活性。
1、SpringMVC概述
SpringMVC框架是圍繞DispatcherServlet這個核心展開的,DispatcherServlet是Spring MVC的總策劃,它負責接貨請求並將其分派給相應的處理器處理。SpringMVC框架包括註解驅動控制器、請求及響應的信息處理、視圖解析、本地化解析、上傳文件解析、異常處理以及表單標籤綁定等內容。
1.1、體系結構
從接收請求到返回響應,Spring框架的衆多組件通力合作、各司其職,有條不紊地完成分內工作。在整個框架中,DispatcherServlet處於核心位置,它負責協調和組織不同組件以完成請求處理並返回響應的工作。和大多數WebMVC框架一樣,SpringMVC通過一個前端Servlet接收所有請求,並將具體工作委託給其他組件進行處理,DispatcherServlet就是SpringMVC的前端Servlet。SpringMVC處理請求的整體過程如下。
整個過程始於客戶端發出一個HTTP請求,Web應用服務器接收到這個請求,如果匹配DispatcherServlet的請求映射路徑(在web.xml中指定),Web容器就將該請求轉交給DispatcherServlet處理。
DispatcherServlet接收到這個請求後,將根據請求的信息(包括URL、HTTP方法、請求報文頭、請求參數、Cookie等)以及HandlerMapping的配置找到處理請求的處理器Handler。
當DispatcherServlet根據HandlerMapping得到對應當前請求的Handler後,通過HandlerAdapter對Handler進行封裝,再以統一的適配器接口調用Handler。HandlerAdapter是SpringMVC的框架級接口,它用統一的接口對各種handler方法進行調用。
處理器完成業務邏輯處理的處理後將返回一個ModelAndView給DispatcherServlet,ModelAndView包含了視圖邏輯名和模型數據信息。
ModelAndView中包含的是“邏輯視圖名”而非真正的視圖對象,DispatcherServlet藉由ViewResolver完成邏輯視圖名到真正視圖對象的解析工作。
當得到真正視圖對象View後,DispatcherServlet就使用這個View對象對ModelAndView中的模型數據進行視圖渲染
最終客戶端得到響應消息可能是一個普通的HTML頁面,也可能是一個XML或JSON串等不同的媒體形式。
1.2、配置DispatcherServlet
和任何Servlet一樣,用戶必須在web.xml文件中配置好DispatcherServlet。要了解Spring MVC框架的工作原理,必須回答一下三個問題
DispatcherServlet框架如何截獲特定的HTTP請求,交由SpringMVC框架處理的?
位於Web層的Spring容器(WebApplicationContext)如何與位於業務層的Spring容器(ApplicationContext)建立關聯,以使Web層的Bean可以調用業務層的Bean
如何初始化SpringMVC的各個組件,並將它們裝配到DispatcherServlet中?
1.2.1、配置DispatcherServlet,截獲特定的URL
我們可以在web.xml中配置一個Servlet,並通過<servlet-mapping>指定其處理的URL。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name></display-name> <!-- 使用Spring提供的日誌配置方法 --> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/classes/log4j.properties</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <context-param> <param-name>log4jRefreshInterval</param-name> <param-value>3000</param-value> </context-param> <context-param> <param-name>webAppRootKey</param-name> <param-value>onlineEdu.root</param-value> </context-param> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>springDispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springDispatcher</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list> </web-app>
通過contextConfigLocation參數指定業務層Spring容器的配置文件(多個文件用逗號隔開),ContextLoaderListener是一個ServletContextListener,它通過contextConfigLocation參數所指定的Spring配置文件啓動“業務層”的Spring容器
一個web.xml可以配置多個DispatcherServlet
2、註解驅動的控制器
2.1、使用@RequestMapping映射請求
在POJO類定義處標註@Controller,再通過<context:componect-scan/>掃描相對應的類包,即可使POJO成爲一個能處理HTTP請求的控制器。可以創建數量不限的控制器,分別處理不同的業務請求。每個控制器可以有多個處理請求的方法,每個方法負責不同的請求操作。如何將請求映射到對應的控制器方法中是Spring MvC框架最重要的任務之一,這項任務由@RequestMapping承擔。
在控制器的類定義及方法定義處都可標註@RequestMapping,類定義出的@RequestMapping提供初步的請求映射信息,方法處的@RequestMapping提供進一步的細分映射信息。
@RequestMapping除了可以使用URL映射請求外,還可以使用請求方法、請求頭參數以及請求參數映射請求。@RequestParam("userid") -- 獲取請求參數userid的值
package com.zzia.controller.admin; ...//省略import @Controller @RequestMapping(value="/admin") public class AdminController implements Serializable { private static final long serialVersionUID = 1L; private static Logger logger=Logger.getLogger(AdminController.class); @Autowired private IAdminService adminService; @Autowired private ILoginLogService loginLogService; //管理員登陸方法 @RequestMapping("/login") public String login(Admin admin,HttpServletRequest request,HttpSession session){ logger.warn(admin.getAdminName()+"試圖登陸"); ... } //更新管理員信息方法 @RequestMapping("/updateAdmin") public String updateAdminInfo(Admin admin,@RequestParam("img") CommonsMultipartFile file,HttpServletRequest request){ logger.info("更新"+admin.getAdminName()+"的信息"); ... } //得到管理員的更新信息 @RequestMapping("/getAdminInfo") public String getAdminInfo(int adminId,HttpServletRequest request){ logger.info("根據Id得到管理員詳細信息"+adminId); ... } //退出登錄的方法 @RequestMapping("/loginOut") public String loginOut(HttpSession session){ logger.info(((Admin)session.getAttribute("adminInfo")).getAdminName()+"退出登錄"); ... } }
2.2、處理模型數據
對於MVC框架來說,模型數據是最重要的。Spring提供了以下幾個途徑將模型數據輸出給視圖。
ModelAndView:處理方法返回值類型爲ModelAndView時,方法體即可通過該對象添加模型數據。
@ModelAttrbute:方法入參標註該註解後,入參的對象就會放到數據模型中
Map及Model:入參爲Model和ModelMap或者Map時,處理方法返回時,Map中的數據會自動添加到模型中
@SessionAttributes:將模型中的某個屬性暫存到HttpSession中,以便多個請求之間可以共享這個屬性
2.3、數據校驗
應用程序在執行業務邏輯前,必須通過數據校驗保證接收到的輸入數據是正確合法的。爲了避免數據的冗餘校驗,將驗證邏輯和相應的域模型進行綁定,將代碼驗證的邏輯集中起來管理。
2.3.1、Spring校驗框架
Spring3.0擁有自己獨立的數據校驗框架,同事支持JSR 303標準的校驗框架。Spring的DataBinder在進行數據綁定時,可同時調用校驗框架完成數據校驗工作。在SpringMVC中,可直接通過註解驅動的方式進行數據校驗。
Spring的org.springframework.validation是校驗框架所在的包,Validator接口擁有以下兩個方法:
boolean supports(Class<?> clazz):該校驗器能夠對clazz類型的對象進行校驗
void validate(Object target,Errors erros):對目標類target進行校驗,並將校驗錯誤記錄在errors中
LocalValidatorFactoryBean既實現了Spring的Validator接口,也實現了JSR 303的Validator接口路,只要再Spring容器中定義一個LocalValidatorFactoryBean,即可將其注入需要數據校驗的Bean中。定義一個LocalValidatorFactoryBean非常簡單
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
注意:Spring本身沒有提供JSR 303的實現,所以必須將JSR 303的實現者(如Hibernate Validator)的jar文件放到類路徑下,Spring將自動加載並裝配好JSR 303的實現者。<mvc:annotaion-driver/>會默認裝配好一個LocalValidatorFactoryBean,通過在處理方法的入參上標註@Valid註解即可讓SpringMVC在完成數據綁定後執行數據校驗工作。
2.3.2、如何獲得校驗結果
只要再表單/命令對象類中標註校驗註解,在處理方法對應的入參前添加@Valid,springMVC就會實施校驗並將校驗結果保存在被校驗入參對象之後的BindingResult或Error入參中。在處理方法內部可以通過BindingResult或Errors入參對象獲取錯誤信息。例如通過BindingResult對象的hashErrors()方法判斷入參對象是否存在校驗錯誤。
package org.worm.biz.springmvc.controller.hibernate; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.worm.biz.springmvc.dao.User; import org.worm.biz.springmvc.service.hibernate.IUserService; /** * @ClassName: UserController * @Description: TODO 用戶操作控制器 * @author Administrator * @date 2016年7月13日 上午10:12:44 * */ @Controller @RequestMapping(value="/user") public class UserController { @Autowired private IUserService userService; @RequestMapping("/valid") public String handleValid(@Valid @ModelAttribute("user") User user,BindingResult bindResult){ if(bindResult.hasErrors()){ return "/user/register"; }else{ userService.addEntity(user); return "/user/showUser"; } } } //對User進行校驗 package org.worm.biz.springmvc.dao; import javax.persistence.*; import javax.validation.constraints.Pattern; @Entity //@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @Table(name = "t_user") public class User{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_no") @Pattern(regexp = "w{4,30}") //通過正則表達式進行校驗,匹配4~30個數字和字母以及下劃線 protected int userId; @Column(name = "user_nick_name") protected String userName; protected String password; @Column(name = "user_age") protected String userAge; public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUserAge() { return userAge; } public void setUserAge(String userAge) { this.userAge = userAge; } }
3、視圖和視圖解析器
在請求處理方法執行完成後,最終返回一個ModelAndView對象,對應那些返回String、View或者ModelMap等類型的處理方法,Spring MVC內部也會在將它們裝配成一個ModelAndView對象,它包含了視圖邏輯名和模型對象的信息。Spring MVC藉助視圖解析器(ViewResolver)得到最終的視圖對象(View),這可能是常見的JSP視圖,也可能是一個基於FreeMarker、Velocity模板技術的視圖,還可能是PDF、Excel、XML、JSON等各種形式的視圖。
3.1、認識視圖
視圖的作用:渲染視圖模型數據,將模型裏的數據以某種形式呈現給客戶。Spring提供了一個高度抽象的View接口。該接口中定義了兩個方法
String getContentType():視圖對應的MIME類型,如text/html、imge/jpeg等
void render(Map model,HttpServletRequest request,HttpServletResponse response):將模型數據以某種MIME類型渲染出來
視圖類型
3.2、認識視圖解析器
SpringMVC爲邏輯視圖名的解析提供了不同的策略,可以在Spring Web上下文中配置一種或多種解析策略,並指定他們之間的先後順序。視圖解析器的工作比較單一:將邏輯視圖名解析爲一個具體的視圖對象。解析器都實現了ViewResolver接口,該接口僅有一個方法View resolverViewName(String viewName,Locale locale);
3.3、JSP和JSTL
JSP是最常見的視圖技術,InternalResourceViewResolver默認使用InternalResourceView作爲視圖實現類。如果JSP文件使用了JSTL的國際化功能,也即JSP頁面使用了JSTL的<fmt:message>等標籤是,用戶需要使用JstlView替換默認的視圖實現類。
使用Spring MVC表單標籤,可以很容易地將模型數據中的表單/命令對象綁定到HTML表單元素中。和使用任何JSP擴展標籤一樣,在使用Spring表單標籤之前,必須先在JSP頁面中添加引用
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <html> <!-- ...--> <!-- 使用<form:form/>標籤實例,無須通過action屬性指定表單提交的目標url --> <form:form modelAttributes="user"> user:<form:input path="userName"/><br/> password:<form:password path="password"/><br/> <input type="submit" value="登陸" name="testSubmit"/> <input type="rest" value="重置"/> </form:form> </html>
3.4、模板視圖
FreeMarker和Velocity是除JSP外使用最多的頁面模板技術。頁面模板編寫好頁面結構,並使用一些特殊的變量標識符綁定Java對象的動態數據。Spring對FreeMarker和Velocity都提供了支持。由於我對它沒什麼興趣,有興趣的童鞋可自行學習。
3.5、文件上傳
SpringMVC爲文件上傳提供了直接的支持,這種支持是通過即插即用的MultipartResolver實現的。Spring使用Jakarta Commons FileUpload技術實現了一個MultipartResolver實現類:CommonsMultipartResolver
3.5.1、配置MultipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8" /> <!-- 設置文件上傳的最大尺寸 --> <property name="maxUploadSize" value="10485760000" /> <property name="maxInMemorySize" value="40960" /> <property name="uploadTempDir" value="upload/temp"/> <!-- 上傳文件的臨時路徑--> </bean>
3.5.2、編寫控制器和文件上傳表單頁面
package com.zzia.controller.admin; ... @Controller @RequestMapping(value="/admin") public class AdminController { private static Logger logger=Logger.getLogger(AdminController.class); @Autowired private IAdminService adminService; @Autowired private ILoginLogService loginLogService; //更新管理員信息方法 @RequestMapping("/updateAdmin") public String updateAdminInfo(Admin admin,@RequestParam("img") CommonsMultipartFile file,HttpServletRequest request){ logger.info("更新"+admin.getAdminName()+"的信息"); String updateResult="更新失敗"; if(!file.isEmpty()){ String type=file.getOriginalFilename().substring(file.getOriginalFilename().indexOf("."));//讀取文件後綴 String fileName=System.currentTimeMillis()+type;//取當前時間戳爲文件名 String path=request.getSession().getServletContext().getRealPath("/")+"upload/admin/"+fileName; //System.out.println(path); File destFile = new File(path); try { FileUtils.copyInputStreamToFile(file.getInputStream(), destFile);//複製臨時文件到指定目錄下 } catch (IOException e) { logger.debug("文件上傳異常"); } admin.setAdminHead("upload/admin/"+fileName); } if(adminService.updateAdminInfo(admin)>0){ updateResult="更新成功"; logger.info("更新成功"); } request.setAttribute("updateResult", updateResult); return "/admin/getAdminInfo.do?adminId="+admin.getAdminId(); } }
SpringMVC會將上傳文件綁定到MultipartFile對象中,MultipartFile提供了獲取上傳文件內容、文件名等內容,通過其transferTo()方法還可將文件存儲到硬盤中,具體說明如下:
byte[] getBytes():獲取文件數據
String getContentType():獲取文件MIME類型,如p_w_picpath/pjpeg,text/plain等
InputStream getInputStream():獲取文件流
String getName():獲取表單中文件組件的名稱
String getOriginalFileName():獲取上傳文件的原名
long getSize():獲取文件的字節大小,單位爲byte
boolean isEmpty():是否有文件上傳
void transferTo(File dest):可以使用該文件將上傳文件保存到一個目標文件中
負責上傳文件的表單頁面和一般表單有一些區別,表單的編碼類型必須是”multipart/form-data“
<form action="admin/updateAdmin.do" method="post" enctype="multipart/form-data"> <ul class="forminfo"> <li><label>管理員編號</label><input name="adminId" type="text" readonly="readonly" class="dfinput" value="${admin.adminId}" /></li> <li><label>管理員名稱</label><input name="adminName" type="text" class="dfinput" value="${admin.adminName }" /></li> <li><label>管理員舊密碼</label><input type="password" class="dfinput" value="${admin.adminPassword }"/></li> <li><label>管理員新密碼</label><input name="adminPassword" type="password" class="dfinput" /></li> <li><label>更新頭像</label><input type="file" name="img"/> <a href="javascript:void(0);" onclick="yulan('${admin.adminId}')" class="infolist" style="display: block;float: left;position: relative;left:200px;">預覽</a> </li> </ul> <div id="adminHead"> <img alt="管理員頭像" src="${admin.adminHead }" style="text-align: center;vertical-align: middle;"> </div> <input style="margin-left: 150px;" type="submit" class="btn" value="確認保存"/> </form>