Spring MVC

    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處理請求的整體過程如下。

  1. 整個過程始於客戶端發出一個HTTP請求,Web應用服務器接收到這個請求,如果匹配DispatcherServlet的請求映射路徑(在web.xml中指定),Web容器就將該請求轉交給DispatcherServlet處理。

  2. DispatcherServlet接收到這個請求後,將根據請求的信息(包括URL、HTTP方法、請求報文頭、請求參數、Cookie等)以及HandlerMapping的配置找到處理請求的處理器Handler。

  3. 當DispatcherServlet根據HandlerMapping得到對應當前請求的Handler後,通過HandlerAdapter對Handler進行封裝,再以統一的適配器接口調用Handler。HandlerAdapter是SpringMVC的框架級接口,它用統一的接口對各種handler方法進行調用。

  4. 處理器完成業務邏輯處理的處理後將返回一個ModelAndView給DispatcherServlet,ModelAndView包含了視圖邏輯名和模型數據信息。

  5. ModelAndView中包含的是“邏輯視圖名”而非真正的視圖對象,DispatcherServlet藉由ViewResolver完成邏輯視圖名到真正視圖對象的解析工作。

  6. 當得到真正視圖對象View後,DispatcherServlet就使用這個View對象對ModelAndView中的模型數據進行視圖渲染

  7. 最終客戶端得到響應消息可能是一個普通的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類型渲染出來

    視圖類型

wKioL1eFtdGw2T0oAAO0BcHL50k356.png-wh_50

    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>





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