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类型,如image/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>
本文出自 “阿酷博客源” 博客,请务必保留此出处http://aku28907.blog.51cto.com/5668513/1826012
原文:http://aku28907.blog.51cto.com/5668513/1826012