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。

  
   
  
   
log4jConfigLocation
   
/WEB-INF/classes/log4j.properties
  
  
   
contextConfigLocation
   
classpath:applicationContext.xml
  
   
   
log4jRefreshInterval
   
3000
  
   
   
webAppRootKey
   
onlineEdu.root
  
  
   
org.springframework.web.util.Log4jConfigListener
  
  
   
org.springframework.web.context.ContextLoaderListener
  
  
   
springDispatcher
   
org.springframework.web.servlet.DispatcherServlet
   
1
  
  
   
springDispatcher
   
*.do
  
    
   
encodingFilter
   
org.springframework.web.filter.CharacterEncodingFilter
   
    
encoding
    
UTF-8
   
   
    
forceEncoding
    
true
   
  
  
   
encodingFilter
   
/*
  
  
    
login.jsp
  

    通过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非常简单

        注意: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" %>    
    
    
        user:
        password:
        
        
    

    3.4、模板视图

        FreeMarker和Velocity是除JSP外使用最多的页面模板技术。页面模板编写好页面结构,并使用一些特殊的变量标识符绑定Java对象的动态数据。Spring对FreeMarker和Velocity都提供了支持。由于我对它没什么兴趣,有兴趣的童鞋可自行学习。

    3.5、文件上传

        SpringMVC为文件上传提供了直接的支持,这种支持是通过即插即用的MultipartResolver实现的。Spring使用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver

        3.5.1、配置MultipartResolver

 
    
    
       
       
       
 
 

    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“

 
    
     
  •      
  •      
  •      
  •      
  •      
    预览     
  •