整合的步骤是固定的, 过程是可以复制的, 人家告诉我们怎么整合就怎么来做就可以了.
具体来说, 第一是你手头有现成的资料照着做即可; 第二如果你没有资料, 怎么办? 这时候如果能记住思路, 我们就可以上网去找资料. 但如果思路记不住就连资料怎么找都不知道了, 所以说思路是很重要的,要记住.
首先,先划分大层次,整合后分为三层:表现层 à 业务层 à 数据访问层,再加上web.xml这是整体的骨架;
其次,再逐层规划每一层具体要干什么。
ssm整合的思路如下:
Spring与MyBatis的整合在MyBatis第二天的时候已经学习过了,这里仍然沿用。SpringMVC与Spring又是一家的,所以它们之间是不需要整合配置的,即所谓的无缝整合,直接就可以用,只不过需要在web.xml中配置它们两个。具体规划如下:
SSM整合分三层:DAO层、Service层、Controller层
代码目录:
·Controller层:
controller:Controller类——用@Controller注解修饰,让SpringMVC来扫描
·Service层:
service:service接口和实现类——用@Service注解修饰,让Spring来扫描
·DAO层:
dao:dao接口文件和映射文件——用Mybatis动态代理包扫描
pojo:java bean
配置目录:
·config:
SpringMVC.xml——注解扫描(Controller层的组件扫描)、注解驱动、视图解析器(可能带有前缀和后缀)
ApplicationContext-service.xml——注解扫描(service层的组件扫描)、事务管理
ApplicationContext-dao.xml——数据源、连接池、会话工厂、MyBatis动态代理包扫描
(以上两个配置文件可以配置在一个文件中,这里为了体现分层的思路所以分开配置。)
MyBatisConfig.xml——空配置(预留,好扩展)
web.xml:
Spring监听(管理service层和dao层)
SpringMVC前端控制器(管理Controller层)
前端:
Jsp视图——在【/WEB-INF/】创建jsp目录,存放jsp页面
静态资源文件(js、css。。。)——在【WebContent】创建js、css等存放这些资源文件。
面试题:事务配置在哪一层?为什么?
事务配置在service层,因为service层控制的是业务,在一个service中有可能调用多个DAO中的方法进行一系列的操作,这些操作要么都成功,要么都失败。比如汇款,汇出操作成功了,但是存入给另一个人时发现卡被注销了,这个时候要整个业务回滚,否则钱就会丢失,所以必须在service层做事务控制。
注意:
不要纠结Controller层的类域Service层的类能否放到一个目录下。这个问题没有意义,因为分层的意思就是强制将它们分开,Controller层的目录就放Controller的类,Service层的目录就放Service的类,DAO层的目录就放Dao的类。
配置文件是否合并的问题,这个问题也不要纠结,看个人喜好,如果想让配置文件单一化,就可以把两个ApplicationContext文件合并,但SpringMVC.xml与ApplicationContext不要合并,因为前者是表现层由Spring的子集框架SpringMVC专门负责,后者是业务层以及DAO层由Spring本身自己负责。
====================================================
第一天
1. SpringMvc介绍
2. SpringMvc入门程序
3. Springmv框架讲解
a) 框架结构
b) 组件说明
4. SSM整合(SpringMvc + Spring + MyBatis)
5. 参数绑定(重点)
a) Springmvc默认支持的类型
b) 简单数据类型
c) Pojo类型
d) Pojo包装类型
e) 自定义转换器
6. Springmvc和struts2的区别
第二天
1. 高级参数绑定(重点)
a) 数组类型的参数绑定
b) List类型的绑定
2. @RequestMapping注解的使用(重点)
3. Controller方法返回值(重点)
4. Springmvc中异常处理
5. 图片上传处理
6. Json数据交互
7. Springmvc实现Restful
8. 拦截器
SpringMVC是Spring组织下的一个表现层框架。和Struts2一样。它是Spring框架组织下的一部分。我们可以从Spring的整体结构中看得出来:
1. 接收Web请求中的参数
2. 把处理好的数据返回给页面
1. Struts2太老
Struts2繁多的XML配置、非容器化的设计都已经不符合现代软件系统的需求,特别是分布式和微服务的大型系统平台中更使得Struts2捉襟见肘。
2. Struts2的安全性差
之前的某些版本曾经存在重大安全隐患,并成功被黑客利用,也造成过一些企业的损失,这也加速了Struts2的迅速老去。
所以说SpringMVC是完全对Struts2的更新换代。同时SpringMVC是Spring自家产品,因此它们直接的整合是无缝的,自然通用的,性能兼容性都是最好的。
要想引入SpringMVC做表现层开发,最基本的需要完成两件事:
1. 配置前端控制器。
2. 开发后端控制器。
Jdk:jdk1.7.0_72
Eclipse:mars
Tomcat:apache-tomcat-7.0.53
Springmvc:4.1.3
说明:
作为本课练习对jdk,eclipse版本的要求不严格。
但在实际项目中团队的开发版本是有严格要求的,最新版本不代表是最好的,实际项目都选用最稳定的版本。
1. 创建一个java web工程
2. 导入jar包
在web工程中拷贝到lib文件夹下的jar包可以自动被导入工程
spring原生jar包:
Spring常用依赖jar包:
【web.xml】是整个web请求的入口配置文件
<说明>
·前端控制器:
org.springframework.web.servlet.DispatcherServlet,它是SpringMVC接收web请求的第一入口,也是唯一入口。这是一个servlet对象,因此需要在web.xml中进行配置。
·常用URL样式:
[/*]: SpringMvc禁止使用,不支持。
[*.action]: 以.action为结尾的url地址请求可以进入DispatcherServlet, 放行所有资源文件后缀的url。
[/]: 域名后面所有url地址目录均被DispatcherServlet拦截, 只放行.jsp为结尾的url,其他资源文件后缀的url都不放行(这个明天的RESTful的时候会具体用到, 今天先不讲)
资源文件请求url:
·<load-on-startup>:
此项配置是控制当前servlet是否随tomcat启动而被加载
配置的值必须是整数
值 >= 0:表示当前servlet随着tomcat启动而被加载,值的大小表示加载的顺序,越小越优先
值 < 0:表示当前servlet不会随着tomcat启动而被加载,只有当它被使用的时候才加载。
DispatcherServlet启动后会立刻去找SpringMVC的配置文件,然后根据配置文件中的内容进行加载和扫描
·<init-param>:
在DispatcherServlet的初始化过程中加载SpringMVC的配置文件
<param-name>为【contextConfigLocation】
<param-value>为【classpath:SpringMVC配置文件的类目录下的相对路径】
·SpringMVC默认配置文件:
如果没有显示的配置SpringMVC的核心配置文件,SpringMVC会去[/WEB-INF/]下找默认的核心配置文件。默认核心配置文件的命名:servlet-name的值 + -servlet.xml。在实际工作中要知道:当发现web.xml没有配置核心配置文件时要知道去[/WEB-INF/]下找它的默认配置文件。
因为各种框架的默认配置文件的位置可能不同,所以企业很少采用默认的配置文件路径,因此我们需要统一规划配置文件的存放位置,通过手动配置完成配置文件的加载。
xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>springmvcdisplay-name> <welcome-file-list> <welcome-file>index.htmlwelcome-file> <welcome-file>index.htmwelcome-file> <welcome-file>index.jspwelcome-file> <welcome-file>default.htmlwelcome-file> <welcome-file>default.htmwelcome-file> <welcome-file>default.jspwelcome-file> welcome-file-list>
<servlet> <servlet-name>springmvcservlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param> <param-name>contextConfigLocationparam-name> <param-value>classpath:SpringMvc.xmlparam-value> init-param>
<load-on-startup>1load-on-startup> servlet>
<servlet-mapping> <servlet-name>springmvcservlet-name>
<url-pattern>*.actionurl-pattern> servlet-mapping> web-app> |
新建一个source folder: config
在config下创建一个xml文件: SpringMvc.xml,文件头可以从其他地方直接拷贝过来。
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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
beans> |
到此为止SpringMvc基本框架搭建完成,启动tomcat,日志中不报错,就说明环境搭建成功。
下面就是入门程序的开发。
使用SpringMVC实现商品列表的展示。
1. 要展示全部商品列表,所以不需要参数,请求url: http://localhost:8080/<具体web应用的名字>/list.action
2. 主要关注SpringMvc的入门程序,所以暂不考虑连接数据库等多余功能,在业务处理中只做假的模拟数据返回给页面。
创建jsp页面的目录:
前端页面不是本课内容,参考: 参考资料\参考案例\jsp\itemList.jsp直接拷贝到工程jsp目录中。
问题:放在WebContent目录下和放到WEB-INFO目录下的区别?
WEB-INFO是受tomcat保护目录,它里面的文件只能由servlet去访问,不能通过url地址栏去请求访问。
WebContent下的文件可以直接通过url地址栏去访问,一般的欢迎页和静态资源文件都放在这里。
1. 创建包:
cn.itcast.controller用于存放后端控制器,cn.itcast.pojo用于存放保存返回数据的bean
2. 创建后端控制器:
<说明>
·@Controller:
用这个注解将一个Java类声明成SpringMVC的后端控制器,可以被扫描。
package cn.itcast.controller; import org.springframework.stereotype.Controller; // 定义一个java类,@Controller注解标注在类定义的上方表明这个类需要SpringMVC扫描。 @Controller public class ItemsController {
} |
在SpringMvc.xml中配置组件扫描
<说明>
<context:component-scan base-package="要扫描的包路径" />
扫描指定包及子包下的@Controller、@Service、@Repository、@Component等注解修饰的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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.itcast.controller" />
beans> |
注意:
如果想扫描多个包,可以配置多个<context:component-scan base-package="指定的包名" />
注解类型的Controller类中使用@RequestMapping修饰的方法响应url请求。一个url对应一个方法。不可能多个url同时对应一个方法,也不可能多个方法同时响应一个url请求。
<说明>
@RequestMapping("/具体URL") 或 @RequestMapping(value="/具体URL"):
是SpringMVC注解,表示如果请求路径匹配,被注解的方法将被调用。以【/】开头,如果不写【/】也可以,推荐写。
SpringMVC启动加载时会扫描@RequestMapping注解,将注解中的url作为key,方法作为value,形成url到方法的映射关系,放到一个map对象里,具体过程:
<说明>
模型对象,是SpringMVC默认支持的一种形参类型,由SpringMVC自动创建,使用时只需定义在方法形参上即可。
作用:可以向Model参数中添加页面需要的变量(属性名)和结果值(属性值),SpringMVC会负责将Model中的变量赋值给request对象,这样JSP就可以用EL表达式从request对象中取得这个变量(属性名)的数据了。(赋值request对象就是调用了request.setAttribute(属性名, 属性值)方法完成的。)
package cn.itcast.controller;
import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import cn.itcast.pojo.Items;
// 定义一个java类,@Controller注解标注在类定义的上方表明这个类需要SpringMVC扫描。 @Controller public class ItemsController { // 标注url到请求方法的key/value对应关系 @RequestMapping("/list") public String list(Model model) throws Exception { //商品列表(临时数据) List Items items1 = new Items(); items1.setName("联想笔记本"); items1.setPrice(6000f); items1.setDetail("ThinkPad T430 联想笔记本电脑!");
Items items2 = new Items(); items2.setName("苹果手机"); items2.setPrice(5000f); items2.setDetail("iphone6苹果手机!");
itemsList.add(items1); itemsList.add(items2); // 1. 设置返回给页面的数据 // 第一个参数是属性名称, 第二个参数是属性值 model.addAttribute("itemsList", itemsList); // 2. 指定返回页面的地址 return "/WEB-INF/jsp/itemList.jsp"; } } |
ModelAndView形参类似Model,但额外提供了一个视图名称,因此上述代码也可以改成如下:
@Controller public class ItemsController { // 标注url到请求方法的key/value对应关系 @RequestMapping("/list") public ModelAndView list(ModelAndView modelAndView) throws Exception { //商品列表(临时数据) List 。。。。。。 // 1. 设置返回给页面的数据 // 第一个参数是属性名称, 第二个参数是属性值 modelAndView.addObject("itemsList", itemsList); // 2. 指定返回页面的地址 modelAndView.setViewName("/WEB-INF/jsp/itemList.jsp"); return modelAndView; } } |
ModelAndView因为是一个类,所以它既可以通过方法声明(由SpringMVC帮我们构造,如上例),也可以在方法中自己构造,上面的例子也可以写成:
@Controller public class ItemsController { // 标注url到请求方法的key/value对应关系 @RequestMapping("/list") public ModelAndView list() throws Exception { //商品列表(临时数据) List 。。。。。。 ModelAndView modelAndView = new ModelAndView(); // 1. 设置返回给页面的数据 // 第一个参数是属性名称, 第二个参数是属性值 modelAndView.addObject("itemsList", itemsList); // 2. 指定返回页面的地址 modelAndView.setViewName("/WEB-INF/jsp/itemList.jsp"); return modelAndView; } } |
注意:Model不能在方法中自己构造,因为Model本身是一个接口,所以只能由SpringMVC帮助实例化。
总结:上面三种给页面赋值的方法中推荐使用第一种和第二种,第三种麻烦,所以很少使用。
访问地址: http://localhost:8080/springmvc/list.action
上面直接返回jsp视图的物理名称很直接,但是每个方法都返回一长串路径看着有点乱和冗余,每个方法返回的路径中【/WEB-INF/jsp/】和【.jsp】都是重复的。
通过配置视图解析器的前缀和后缀可以简化视图名称。(配置视图解析器的作用)
【SpringMVC.xml】中视图解析器前缀和后缀的配置:
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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.itcast.controller" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" /> bean> beans> |
|
配置之后,代码简化:(直接把去掉前缀和后缀剩下的部分返回即可)
······ // 2. 指定返回页面的地址 return "itemList"; ······ |
(只给出了第一中使用Model的简化代码,其他的可以自行完成。)
逻辑视图名:Controller方法中简化后的字符串。
物理视图名 = 前缀 + 逻辑视图名 + 后缀。
SpringMVC能保证每次返回的字符串都会自动走视图解析器,然后按照上面的方式拼接,再进行后续处理。
掌握(重点):后端控制器开发中@Controller、@RequestMapping、Model(ModelAndView)的使用,会在SpringMVC的配置文件中配置注解扫描和视图解析器的前缀和后缀。
了解(非重点):前端控制器在web.xml中的配置方式。
注意:对于配置文件中出现的很长的类名不需要完全记忆,能叫出一个大概的名字即可,或者能说出这个类是干什么的而记不住名字也没关系。
1. 学习框架结构的目的: 开发不用, 但面试时会问到
2. 框架结构详细内容:(面试题)
说明:前端控制器是接收web请求的入口,地位最重要。如果还要做其他具体的工作会使它的负担过于繁重,因此SpringMVC找来了四个帮手,叫做四个组件,来帮助前端控制器承担一些具体的工作,这样前端控制器就只发号司令做为集中控制调度中心,具体的工作交给四个组件来完成。
具体工作分为两部分:
1. 处理请求:两个组件,一个来找Handler,一个来执行Handler
1) 处理器映射器负责找到URL对应的Handler对象
2) 处理器适配器负责执行找到的Handler对象。
2. 返回结果:两个组件,一个来找页面,一个来执行页面渲染
1) 视图解析器负责找到要返回的页面
2) 视图对象负责渲染页面,渲染页面需要的数据由前端控制器传给它。
●啥是freemaker?
就是模版, 通过模版和数据生成输出的文本(HTML网页, 电子邮件等). 比如网易首页点击进入的一个html页面, 这些静态的html都是由freemarker生成的, 而struts2只支持jsp.
1. 用户发送请求至前端控制器DispatcherServlet
2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3. 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet
4. DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5. 执行处理器(handler也叫后端控制器-->Service-->DAO)
6. Handler执行完成返回ModelAndView
7. HandlerAdapter将handler执行结果ModelAndView返回给DispatcherServlet
8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9. ViewReslover根据handler中设置的页面信息解析成一个View视图对象并返回它.
10.DispatcherServlet调用View对象的接口方法对实际的视图文件(如: jsp文件)进行渲染(即将模型数据填充至视图文件中)
11. DispatcherServlet响应用户
SpringMVC的整个控制层采用组件式的结构。
SpringMVC的组件有:
·前端控制器DispatcherServlet、
·处理器映射器HandlerMapping、
·处理器适配器HandlerAdapter、
·视图解析器ViewResolver
·处理器Handler(相对于前端控制器的后端控制器)、
·视图对象View。
组件的重要性:
·前端控制器最重要,是整个Spring表现层的中枢;
·除了前端控制器,最重要的是HandlerMapping、HandlerAdapter、ViewResolver,它们称为SpringMVC的三大核心组件,为接收用户请求和返回结果给用户在各自岗位发挥着关键作用;
·Handler是SpringMVC用来包装方法对象的类,保存在HandlerMapping的map中;
·View由ViewResolver生成,负责渲染页面。
DispatcherServlet负责接收用户请求,是整个流程的控制中心,但它几乎不做任何具体的工作,只进行任务调度。具体的工作由具体的组件来完成。这就是组件式结构的优势,专项事情又专门的组件来做,这样能提高专项的处理能力同时集中调度的存在降低了组件之间的耦合性。
在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。
HandlerMapping负责为每个请求找到一个合适的处理器handler,其实简单来说就是维持了一个url到handler的映射Map。springmvc提供了不同的映射器,实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
比如注解方式的映射器会根据核心配置文件中配置的
1. 什么是适配器:
2. 适配器的作用:统一调用接口,好扩展。
3. Springmvc中的处理器适配器:
处理器适配器HandlerAdapter负责执行具体的处理器。
SpringMVC有多个适配器,当HandlerMapping找到了url对应的handler对象后,前端控制器会挑选一个能够执行这个handler的处理器适配器,然后给这个适配器发送指令,同时把这个handler对象传给这个适配器,让它执行handler,最终将handler的返回值和逻辑视图名字符串返回给前端控制器。
视图解析器(ViewResolver)负责解析出视图文件的物理路径,并根据这个路径生成视图View对象。
ViewResolver首先把逻辑视图名解析成实际的页面位置,再生成视图View对象并返回给前端控制器。
1. View视图对象
View对象负责渲染视图文件,将数据结果通过视图文件展示给用户。前端控制器调用View对象的接口方法render(),参数就是后台返回的数据,在render方法拿数据渲染视图文件生成返回给客户端的结果。
springmvc框架提供了很多的View类型,包括:jstlView、freemarkerView、pdfView等。
2. 视图文件
视图文件可以是jsp、pdf、freemaker等,最常用的就是jsp。一般情况下需要通过页面jsp标签或页面模版将处理器返回的model数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
Springmvc三大组件配置在核心配置文件中,springmvc支持组件免配置、显示配置和企业的配置方法
1. 何为免配置:
即什么都不配。此时SpringMVC仍然可以正常运行,全凭它自己的DispatcherServlet.properties,会从中找到合适的组件去执行。这个属性文件的位置:
2. DispatcherServlet.properties的作用:
如果没有显示配置三大组件(其中一个或者全部),依靠这个属性文件中的默认配置组件,springmvc也能正确的执行。
3. 上面的入门程序中自动选择的三大组件分别是:
a) 处理器映射器
b) 处理器适配器
c) 视图解析器
默认的视图解析器只有一个。
4. 坏处:
每次请求都要去这里去找合适的组件,所以执行效率很低,因此什么都不配置是不可取的。需要我们显示配置来提高执行效率。
1. 显式配置默认选择的处理器映射器和处理器适配器(旧版)
2. 显式配置官方推荐的处理器映射器和处理器适配器(新版)
3. 企业中的配置方法:注解驱动
作用:帮助我们显式配置当前Spring版本的最新的注解形式的处理器映射器和处理器适配器
好处:简化配置,一劳永逸
4. 面试题: springmvc是否要配置注解驱动和注解扫描? 有什么区别?
答:
都需要配置,两个东西的作用完全不一样,不要混淆视听。
注解驱动:作用就是替我们显式的配置当前spring版本下最新版本的注解形式的处理器映射器和处理器
适配器
注解扫描:扫描指定包及子包下的@Controller、@Service、@Repository等注解修饰的java类,其
中@Controller修饰的类注册为SpringMVC的组件,其它注解修饰的类注册为Spring的
组件。
前面已经学过,此处只给出配置的例子。
处理器映射器、适配器:提高执行效率
视图解析器(有前缀和后缀):简化编码
掌握(重点):
此处主要是把框架结构图理解成自己的话能够描述出来。
SpringMVC三大组件的最终形态的显示配置方法。
了解:
Springmvc默认组件配置;
单独配置新版的处理器映射器和处理器适配器。
下面是完整的springmvc核心配置文件的配置:SpringMVC.xml
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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.itcast.controller" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" /> bean>
beans> |
整合的步骤是固定的, 过程是可以复制的, 人家告诉我们怎么整合就怎么来做就可以了.
具体来说, 第一是你手头有现成的资料照着做即可; 第二如果你没有资料, 怎么办? 这时候如果能记住思路, 我们就可以上网去找资料. 但如果思路记不住就连资料怎么找都不知道了, 所以说思路是很重要的,要记住.
首先,先划分大层次,整合后分为三层:表现层 à 业务层 à 数据访问层,再加上web.xml这是整体的骨架;
其次,再逐层规划每一层具体要干什么。
ssm整合的思路如下:
Spring与MyBatis的整合在MyBatis第二天的时候已经学习过了,这里仍然沿用。SpringMVC与Spring又是一家的,所以它们之间是不需要整合配置的,即所谓的无缝整合,直接就可以用,只不过需要在web.xml中配置它们两个。具体规划如下:
SSM整合分三层:DAO层、Service层、Controller层
代码目录:
·Controller层:
controller:Controller类——用@Controller注解修饰,让SpringMVC来扫描
·Service层:
service:service接口和实现类——用@Service注解修饰,让Spring来扫描
·DAO层:
dao:dao接口文件和映射文件——用Mybatis动态代理包扫描
pojo:java bean
配置目录:
·config:
SpringMVC.xml——注解扫描(Controller层的组件扫描)、注解驱动、视图解析器(可能带有前缀和后缀)
ApplicationContext-service.xml——注解扫描(service层的组件扫描)、事务管理
ApplicationContext-dao.xml——数据源、连接池、会话工厂、MyBatis动态代理包扫描
(以上两个配置文件可以配置在一个文件中,这里为了体现分层的思路所以分开配置。)
MyBatisConfig.xml——空配置(预留,好扩展)
web.xml:
Spring监听(管理service层和dao层)
SpringMVC前端控制器(管理Controller层)
前端:
Jsp视图——在【/WEB-INF/】创建jsp目录,存放jsp页面
静态资源文件(js、css。。。)——在【WebContent】创建js、css等存放这些资源文件。
面试题:事务配置在哪一层?为什么?
事务配置在service层,因为service层控制的是业务,在一个service中有可能调用多个DAO中的方法进行一系列的操作,这些操作要么都成功,要么都失败。比如汇款,汇出操作成功了,但是存入给另一个人时发现卡被注销了,这个时候要整个业务回滚,否则钱就会丢失,所以必须在service层做事务控制。
注意:
不要纠结Controller层的类域Service层的类能否放到一个目录下。这个问题没有意义,因为分层的意思就是强制将它们分开,Controller层的目录就放Controller的类,Service层的目录就放Service的类,DAO层的目录就放Dao的类。
配置文件是否合并的问题,这个问题也不要纠结,看个人喜好,如果想让配置文件单一化,就可以把两个ApplicationContext文件合并,但SpringMVC.xml与ApplicationContext不要合并,因为前者是表现层由Spring的子集框架SpringMVC专门负责,后者是业务层以及DAO层由Spring本身自己负责。
Jdk: jdk1.7.0_72
Eclipse: mars
Tomcat: apache-tomcat-7.0.53
Springmvc: 4.1.3
MyBatis: mybatis-3.2.7
MySql: 5.1.28
SM整合包: 1.2.2
使用【资料\参考案例\sql】下的创建库建表脚本【springmvc.sql】创建我们使用的数据库环境。
将【资料\jar\ssm整合后jar全集】下的jar包拷贝到WebContent/WEB-INF/lib下, 会自动关联到工程中
根据思路创建工程目录:
1. 将【资料\MyBatis逆向工程】下的【MyBatisGeneration】工程导入eclipse。
2. 修改配置文件符合当前的开发环境和目录结构。
a) 数据库名
b) Pojo包名
c) Mybatis映射文件的包名
d) Mybatis映射接口的包名(与c相等)
e) 需要生成代码的数据库表
3. 注意:
a) 执行前要把原先旧的代码全部删除。
b) 执行完生成程序后第一次刷新的时候表现出来的包名不对,再刷新一次就好了。这是eclipse的一个bug。
4. 将生成的代码拷贝到我们的工程中去。
1. 在【资料\参考案例\config】下是mybatis课程中sm整合的时候需要的配置文件,整体导入。
2. 整理mybatis的配置文件:
a) MyBatisConfig.xml:清空
b) ApplicationContext.xml:保留其中的属性文件、数据源(连接池)、会话工厂、动态代理包扫描的配置,其他的都删除。根据上面的思路它应该属于dao层的spring配置文件,改名为ApplicationContext-dao.xml
c) 其他配置文件保持不变。
3. 修改后的配置文件:
【MyBatisConfig.xml】
xml version="1.0" encoding="UTF-8"?> DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration>
configuration> |
【ApplicationContext-dao.xml】
xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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/aop http://www.springframework.org/schema/aop/spring-aop-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/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="10" /> <property name="maxIdle" value="5" /> bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:MyBatisConfig.xml" /> <property name="dataSource" ref="dataSource" /> bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.itcast.dao" /> bean>
beans> |
创建【ApplicationContext-service.xml】,@Service注解组件的扫描和事务管理
【ApplicationContext-service.xml】
xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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/aop http://www.springframework.org/schema/aop/spring-aop-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/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<context:component-scan base-package="cn.itcast.service" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/> bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes>
<tx:method name="save*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> tx:attributes> tx:advice>
<aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.itcast.service.*.*(..))" /> aop:config> beans> |
创建【SpringMvc.xml】,配置@Controller注解组件扫描,注解驱动,视图解析器
配置文件:
【SpringMvc.xml】
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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.itcast.controller" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="WEB-INF/jsp/" />
<property name="suffix" value=".jsp" /> bean> beans> |
配置spring的容器监听、springmvc前端控制器以及它可接收的url地址。
配置文件:
【web.xml】
xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>ssmdisplay-name> <welcome-file-list> <welcome-file>index.htmlwelcome-file> <welcome-file>index.htmwelcome-file> <welcome-file>index.jspwelcome-file> <welcome-file>default.htmlwelcome-file> <welcome-file>default.htmwelcome-file> <welcome-file>default.jspwelcome-file> welcome-file-list>
<context-param> <param-name>contextConfigLocationparam-name> <param-value>classpath:ApplicationContext-*.xmlparam-value> context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class> listener>
<servlet> <servlet-name>springMvcservlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param> <param-name>contextConfigLocationparam-name> <param-value>classpath:SpringMVC.xmlparam-value> init-param>
<load-on-startup>1load-on-startup> servlet>
<servlet-mapping> <servlet-name>springMvcservlet-name> <url-pattern>*.actionurl-pattern> servlet-mapping>
web-app> |
本节的重点是要掌握整合的思路,有了思路就可以寻着线索进行整合了,不用死记具体的过程。
使用SSM从数据库查询数据, 实现真正的商品列表的展示.
1. 沿用SpringMvc入门程序.
2. 在此基础上完成service层的代码, 定义service接口和实现类, 并用@autowired自动注入DAO接口对象.
3. 完善controller层代码, 用@autowired自动注入service接口对象.
1. 定义ItemsService.java接口
2. 定义ItemsServiceImpl.java实现类,实现这个接口
代码:
【ItemsService.java】
package cn.itcast.service;
import java.util.List;
import cn.itcast.pojo.Items;
public interface ItemsService {
public List
} |
【ItemsServiceImpl.java】
package cn.itcast.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import cn.itcast.dao.ItemsMapper; import cn.itcast.pojo.Items;
@Service public class ItemsServiceImpl implements ItemsService {
@Autowired ItemsMapper itemsMapper;
@Override public List // 通过自动生成的接口方法查询商品列表 List return list; } } |
注意:selectByExample(null)只检索除了大对象数据类型之外的字段,但items表中有一个detail字段的类型是text,为了把它也检索出来可以使用selectByExampleWithBLOBs(null)这个方法。
package cn.itcast.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import cn.itcast.dao.ItemsMapper; import cn.itcast.pojo.Items;
public class ItemsServiceImpl implements ItemsService {
@Autowired ItemsMapper itemsMapper;
@Override public List /* * 注意: * selectByExample只检索除了大对象数据类型之外的项目 * selectByExampleWithBLOBs检索包含大对象数据类型的项目 */ // 通过自动生成的接口方法查询商品列表 List return list; } } |
1. 把入门程序中的【ItemsController.java】拷贝过来
2. 修改里面的【lsit()】方法,调用service接口的方法完成查询。
代码:
【ItemsController.java】
package cn.itcast.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView;
import cn.itcast.pojo.Items; import cn.itcast.service.ItemsService;
@Controller public class ItemsController {
@Autowired private ItemsService itemsService;
@RequestMapping("/list") public ModelAndView list() throws Exception { List
// 1. 设置返回页面需要的数据 2. 指定返回页面的地址 ModelAndView modelAndView = new ModelAndView(); // 1. 设置返回页面需要的数据 modelAndView.addObject("itemList", itemsList); // 2. 指定返回页面的地址 modelAndView.setViewName("itemList");
return modelAndView; } } |
从【资料\参考案例\jsp】中导入【itemList.jsp】到工程的jsp目录下
访问地址: http://localhost:8080/springmvc/list.action
数据库:
到本节为止,一个完整的ssm整合就完成了。我们从最初的springmvc入门程序,到认识三大核心组件,到最后ssm整合形成完整的web系统,这就是一个web系统的学习进化过程。
掌握(重点):
整合的分层思路以及各层代码的编写
了解:
SSM开发环境搭建,这个环境搭建不是大家掌握的重点,只是作为了解,基本会搭即可。
Springmvc作为表现层框架,是后端程序的入口,它负责接收页面HTTP请求(POST/GET)过来的参数,参数可以是【url后面拼接的】,也可以是【页面控件的值】,也可以是【hidden变量的值】,也可以是【ajax提交的js对象】,也可以是【ajax提交的json字符串】等等。这也体现了本课开始时说的SpringMVC的第一个作用:“接收请求中的参数”。
接收的参数具体有哪些类型呢?
1. 默认支持的参数类型:HttpServletRequest,HttpServletResponse,HttpSession,
Model(ModelAndView)
2. Java简单类型:int,long,double,String,Long,Double,Boolean,Integer等
3. POJO类型
4. POJO的包装类型-QueryVo
前面我们做完了商品列表画面的展示,下面继续做修改页面和保存修改。
【修改页面】:根据id查询这条数据的详细信息,然后显示在修改页面
【保存修改】:在修改页面修改信息,然后点【保存】,把信息存到数据库,保存成功迁移到success页面。
下面主要通过简单示例演示各种类型参数的传递,这里的重点是参数如何接收,并不是页面功能的实现,因此有些功能不真正实现,只做到参数正常接收即可。
·用默认支持的参数类型定义的方法接收显示【修改页面】的查询参数;(实现)
·用Java简单类型参数定义的方法接收更新商品信息功能的更新参数;(实现)
·用POJO类型参数定义的方法改进上面的更新功能;(实现)
·用POJO的包装POJO的参数类型定义的方法接收查询条件参数;(不实现)
·针对日期类型,利用自定义转换器Converter实现字符串到日期类型转换,进一步丰富【保存修改】功能;(实现)
所谓默认支持的参数类型就是传不传它们都会存在的参数,想用时就在Controller方法中定义即可,用哪个定义哪个,不用不定义。
默认参数有:
HttpServletRequest:通过request对象获取请求信息
HttpServletResponse:通过response处理响应信息
HttpSession:通过session对象得到session中存放的对象
Model(ModelAndView):通过数据模型参数返回需要传递给页面的数据。
1. 【itemList.jsp】的【修改】:
2. 【ItemsController.java】新定义一个方法
<说明>
·HttpServletRequest:
可以接收页面传递过来的参数。
·Model:前面讲过,略
/** * 演示默认支持的类型参数:HttpServletRequest、HttpServletResponse、HttpSession、Model * 默认支持的参数类型就是传不传它们都存在的参数,想用时就在Controller方法中定义即可, * 用哪个就定义哪个,不用就不定义。 */ @RequestMapping("/toEdit") public String itemEdit(HttpServletRequest request, Model model) throws Exception { // 取得页面传过来的主键id Integer id = Integer.valueOf(request.getParameter("id")); Items itemsResult = itemsService.getItemsDetailById(id); // 设置返回给页面的数据 model.addAttribute("item", itemsResult); // 返回页面的逻辑视图名 return "editItem"; } |
3. 【ItemsService.java】新定义一个接口
public Items findItemsById(Integer id) throws Exception; |
4. 【ItemsServiceImpl.java】实现上面的接口方法
public Items findItemsById(Integer id) throws Exception { Items items = itemsMapper.selectByPrimaryKey(id); return items; } |
5. 访问【http://localhost:8080/ssm2/list.action】,点击【修改】
默认参数类型有一个缺点:用request.getParameter来取值可能需要额外的类型转换,从String转成其他类型。
Springmvc可不可以直接接收这些类型的参数呢?答案是可以的,即直接接收简单类型的参数。Springmvc不仅可以直接接收多个简单类型参数,还可以自动进行简单的类型转换。
页面提交的控件name属性值必须等于Controller方法的形参名。
1. 从【资料\参考案例\jsp】中导入【editItem.jsp】到工程的jsp目录下。
2. 【ItemsController.java】新定义一个方法
/** * 演示接收简单类型:String, Integer, Double, Boolean等 * 要求:页面上input框的name属性值必须等于controller方法中接收时的参数的变量名称 */ @RequestMapping("/itemUpdate") public String itemUpdate(Integer id, String name, Float price, String detail) throws Exception { Items items = new Items(); items.setId(id); items.setName(name); items.setPrice(price); items.setDetail(detail); itemsService.updateItems(items); return "success"; } |
3. 【ItemsService.java】新定义一个接口
public void updateItems(Items items) throws Exception; |
4. 【ItemsServiceImpl.java】实现上面的接口方法
public void updateItems(Items items) throws Exception { itemsMapper.updateByPrimaryKeySelective(items); } |
5. 从【资料\参考案例\jsp】中导入【success.jsp】到工程的jsp目录下。
6. 进入修改页面修改信息并保存。
web页面默认的编码是ISO8859-1,但这个编码不支持汉字,所以汉字参数传递过来会出现乱码。
post请求乱码的解决方法:在web.xml中加一个过滤器解决。
xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> 。。。。
<filter> <filter-name>CharacterEncodingFilterfilter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class> <init-param> <param-name>encodingparam-name> <param-value>utf-8param-value> init-param> filter> <filter-mapping> <filter-name>CharacterEncodingFilterfilter-name> <url-pattern>/*url-pattern> filter-mapping> 。。。。 web-app> |
对于get请求中文参数乱码,只在Tomcat7中存在,T8和T9已经解决了这个问题。Tomcat7解决方法有两个:
1. 修改tomcat配置文件【server.xml】添加编码与工程编码一致,如下:(常用)
2. 另外一种方法对参数进行重新编码:
如果Controller方法形参不符合传参规范,可以使用@RequestParam注解进一步限定请求参数到方法形参的映射关系。
属性:
·value:指明请求的参数名称,参数名和形成名一致时,value属性可以省略。
·required:boolean类型,声明此请求参数是否必须有,true的时候如果没有,则报错。
·defaultValue:字符类型,如果请求参数没有提供,可以指定一个默认字符串,Spring会自动将字符串转化为形参的目标类型(当然这种类型转化必须是简单类型转化)。
/** * 演示接收简单类型:String, Integer, Double, Boolean等 * 使用@RequestParam注解限定请求参数到方法形参的映射关系 */ @RequestMapping("/updateitem") public String updateItemsById( @RequestParam(required=true) Integer id, @RequestParam String name, @RequestParam(defaultValue="100") Float price, @RequestParam(value="detail") String itemDetail) throws Exception { Items items = new Items(); items.setId(id); items.setName(name); items.setPrice(price); items.setDetail(itemDetail); itemsService.updateItemById(items);
return "common/success"; } |
实现对修改保存的改进,适应参数个数的变化,几十上百个参数时候是绝对不可能用简单类型传递参数的。
页面提交的控件name属性值必须等于Controller方法形参对象中的属性名。
【ItemsController.java】新定义一个保存更新的方法,将旧的注释掉
/** * 演示接收简单类型:String, Integer, Double, Boolean等 * 要求:页面上input框的name属性值必须等于controller方法中接收时的参数的变量名称 */ // @RequestMapping("/itemUpdate") // public String itemUpdate(Integer id, String name, Float price, String detail) // throws Exception { // Items items = new Items(); // items.setId(id); // items.setName(name); // items.setPrice(price); // items.setDetail(detail); // items.setCreatetime(new Date()); // itemsService.updateItems(items); // return "success"; // }
/** * 演示接收POJO类型的参数 * 要求:页面上input框的name属性值必须等于pojo中的属性名称 * * @return * @throws Exception */ @RequestMapping("/itemUpdate") public String itemUpdate(Items items) throws Exception { itemsService.updateItems(items); return "success";
} |
我们要想在列表页面加一个综合查询功能,查询条件可能有商品信息、用户信息、订单信息,因此我们需要一个QueryVo来包装这些查询信息。那如何传递包装的参数呢?
页面提交控件的name属性值必须等于Controller方法形参对象中的属性.属性.属性....。
1. 新定义【QueryVo.java】
package cn.itcast.pojo;
public class QueryVo {
// 用户对象 // ...... // 订单对象 // ......
// 商品对象 private Items items;
public Items getItems() { return items; }
public void setItems(Items items) { this.items = items; } } |
2. 【itemList.jsp】中增加两个查询条件作为POJO的包装类型的示范说明:控件name属性的名称要符合要求。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>查询商品列表title> head> <body> <form action="${pageContext.request.contextPath }/search.action" method="post"> 查询条件: <table width="100%" border=1> <tr> <td>商品名称: <input type="text" name="items.name">td> <td>商品价格: <input type="text" name="items.price">td> <td><input type="submit" value="查询"/>td> tr> table> 商品列表: <table width="100%" border=1> <tr> <td>商品名称td> <td>商品价格td> <td>生产日期td> <td>商品描述td> <td>操作td> tr> <c:forEach items="${itemList }" var="item"> <tr> <td>${item.name }td> <td>${item.price }td> <td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>td> <td>${item.detail }td>
<td><a href="${pageContext.request.contextPath }/toEdit.action?id=${item.id}">修改a>td> tr> c:forEach> table> form> body> html> |
3. 【ItemsController.java】新定义一个方法,这里不做具体的查询过滤,我们主要学习的是参数的传递。
/** * 演示接收POJO的包装类型 - QueryVo * 要求: 页面上input框的name属性值必须等于POJO中属性.属性.属性..... * * @param vo * @return * @throws Exception */ @RequestMapping("/search") public String searchItems(QueryVo vo) throws Exception { System.out.println(vo); return "success"; } |
当前端参数传递过来时SpringMVC会根据方法形参的类型定义进行类型转换,但是如果转换的类型比较复杂SpringMVC并没有针对复杂类型转换的处理对象时,就需要自定义转换器来处理,否则会报错。
比如:前台传递的是一个时间格式的字符串,后台数据库存储的是一个日期类型的数据,如果我们自己不做任何处理直接传递的话,会报错(400错误),需要我们自定义日期格式字符串到Date的转化方式。
1. 将【editItem.jsp】中的【商品生产日期】项目的注释打开
2. 自定义转换器Converter
Converter的包名可以随意,我们这里定义一个全局的String到Date的转换器。
都要继承【Converter】接口,【S - source源的类型】,【T - target目标的类型】,我们这里的S是String,T是Date。
package cn.itcast.controller.converter;
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date;
import org.springframework.core.convert.converter.Converter;
/** * S - source源的类型 * T - target目标的类型 */ public class CustomGlobalStrToDateConverter implements Converter
public Date convert(String source) { try { Date date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(source); return date; } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } } |
3. Converter的配置方式:
【SpringMVC.xml】
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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.itcast.controller" />
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="cn.itcast.controller.converter.CustomGlobalStrToDateConverter"/> set> property> bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="WEB-INF/jsp/" />
<property name="suffix" value=".jsp" /> bean> beans> |
4. Converter的配置方式2(了解)
这种方式放到工程里不好用,就是用来理解Converter具体作用在处理器适配器上。
xmlversion="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scanbase-package="cn.itcast.springmvc.controller"/>
<beanclass="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<beanid="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <propertyname="converters"> <set> <beanclass="cn.itcast.springmvc.convert.DateConverter"/> set> property> bean>
<beanid="customBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <propertyname="conversionService"ref="conversionService"/> bean>
<beanclass="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <propertyname="webBindingInitializer"ref="customBinder">property> bean>
<beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"> <propertyname="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<propertyname="prefix"value="/WEB-INF/jsp/"/>
<propertyname="suffix"value=".jsp"/> bean> beans> |
注意:此方法需要独立配置处理器映射器、适配器,不再使用
5. 【ItemsController.java】
/** * 演示接收POJO类型的参数 * 要求:页面上input框的name属性值必须等于pojo中的属性名称 */ @RequestMapping("/itemUpdate") public String itemUpdate(Items items) throws Exception { itemsService.updateItems(items); return "success"; } |
再次启动运行。
由上例可知自定义转换器的作用:特定类型之间的复杂转化。凡是符合自定义的源类型和目标类型的参数都会被转换器处理。比如:上例中String到Date的转换。
再比如:实现输入一个字符串转换成一个自定义的业务对象。(这种时候,这个字符串应该是有一定格式的,根据格式进行内容的分离,然后创建出对应的业务对象)
如果仅仅是日期类型的转化可以不用自定义转换器,还有一种更简单的做法:直接在pojo对应的日期属性变量上面加注解 @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss"),因此SpringMVC.xml恢复原来的配置
【SpringMVC.xml】
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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.itcast.controller" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" /> bean> beans> |
【Items.java】在pojo中对应的日期属性变量上使用注解@DateTimeFormat
public class Items { 。。。。。。 @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") private Date createtime; 。。。。。。 } |
启动测试,这样也是可以进行String到Date的类型转换的。
注意格式:【yyyy-MM-dd HH:mm:ss】
SpringMVC负责接收HTTP请求的参数,各种类型参数要遵守各自的规范,具体如下:
1. 默认支持的参数类型:HttpServletRequest,HttpServletResponse,HttpSession,Model(ModelAndView)。用哪个就在方法的形参中定义哪个,不用的不定义。
2. 简单类型:String,long,double,boolean,Integer等
要求:页面提交的控件name属性值必须等于Controller方法的形参名。
适合单个或少数参数的请求。
也可以用@RequestParam注解来接收参数,解决名称对不上或者对参数做进一步的限定。
3. POJO类型
要求:页面提交的控件name属性值必须等于Controller方法形参对象中的属性名。
适合多参数、不固定参数个数的请求。
4. POJO的包装类型-QueryVo
要求: 页面提交控件的name属性值必须等于Controller方法形参对象中的属性.属性.属性....。
适合综合查询条件参数的传递。
5. 自定义转换器Converter
作用:特定类型之间的复杂转化。
日期转化用@DateTimeFormat更方便。
以根据id查询商品详细信息的请求为例:(Chrome中的请求信息截图)
GET请求的参数是以k1=v1&k2=v2&k3=v3&……这样的样式拼接在url的后面用问号?分开。
以更新商品信息的请求为例:(Chrome中的请求信息截图)
POST请求的参数也是以k1=v1&k2=v2&k3=v3&……这样的样式拼接的,只不过是包装在Form表单中传递,在url的后面看不到。
大家不要被HTTP请求的形式给蒙蔽了,参数样式的实质都相同。它们都是一样的HTTP请求参数,即都是k1=v1&k2=v2&k3=v3&……的形式连接成一个字符串整体提交给后台,其中汉字由浏览器转码。
所以在SpringMVC端接收GET/POST请求参数的方法和规则都是相同的。
1. 入口不同:
springmvc的入口是一个servlet即前端控制器,而struts2入口是一个filter过虑器。
2. 请求的拦截级别:
Struts2是类级别的拦截,一个Action类对应一个request上下文;
SpringMVC是方法级别的拦截,一个Controller类的方法对应一个request上下文。
3. 单例与多例:
Struts2是基于类开发,传递参数是通过Action类的属性,只能设计为多例。
Springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(默认是单例)。
4. 接收参数和返回数据:
Struts接收的请求参数存放于Action的属性中,是诸多方法共享的,程序可读性差。Struts采用值栈存储请求和响应的数据,通过OGNL存取数据;值栈存储方式太过原始。
Springmvc通过参数解析器是将request请求内容解析并给方法形参赋值,即请求参数是方法之间独立的。Springmvc对数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques对象传输到页面。
============================================================================
1. 高级参数绑定(重点)
a) 数组类型的参数绑定
b) List类型的绑定
2. @RequestMapping注解的使用(重点)
3. Controller方法返回值(重点)
4. SpringMVC中异常处理
5. Json数据交互
6. Springmvc实现Restful
7. 拦截器
8. 图片上传处理
数组类型的参数可以传递一批相同的数据到Controller的方法中。
批量删除:在商品列表页面选中多个商品,然后删除。
此功能要求商品列表页面中的每个商品前有一个checkbook,选中多个商品后点击删除按钮把商品id传递给Controller,根据商品id删除商品信息。
功能分解
前端:1)能选中多个商品;2)能提交选中的多个商品
后端:1)能接收到选中商品的id;2)进行删除处理
总结上面两种规范:页面提交的控件name属性值必须等于Controller方法数组形参名或者形参对象中的数组属性名。
1. Jsp
可以重新创建一个专门演示批量删除的画面【itemListDelBatch.jsp】,利用原来的itemList.jsp拷贝一个,然后在表格的最前面增加一列checkbox。
【itemListDelBatch.jsp】
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>查询商品列表title> head> <body> <form action="${pageContext.request.contextPath }/delAll.action" method="post"> 查询条件: <table width="100%" border=1> <tr> <td><input type="submit" value="批量删除"/>td> tr> table> 商品列表: <table width="100%" border=1> <tr> <td>td> <td>商品名称td> <td>商品价格td> <td>生产日期td> <td>商品描述td> <td>操作td> tr> <c:forEach items="${itemList }" var="item"> <tr> <td><input type="checkbox" name="ids" value="${item.id }" />td> <td>${item.name }td> <td>${item.price }td> <td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>td> <td>${item.detail }td> <td><a href="${pageContext.request.contextPath }/toEdit.action?id=${item.id}">修改a>td> tr> c:forEach> table> form> body> html> |
提交相同名称的参数时,SpringMVC会自动把它们处理成数组。
2. Controller
【ItemsController.java】先定义一个方法用于itemListDelBatch.jsp页面的显示:
@RequestMapping("/listForDel") public ModelAndView getListForDel() throws Exception { List ModelAndView modelAndView = new ModelAndView(); // request.setAttribute(key, value) // 底层仍然是把属性名和属性值放到request对象中 // jsp页面永远是从request对象中取得数据的 modelAndView.addObject("itemsList", itemsList); modelAndView.setViewName("items/itemListDelBatch"); return modelAndView; } |
然后再定义一个执行删除的方法(这里主要是学习如何传参数,不做具体的删除操作)
方式一:直接传递数组参数
·传参规范:页面上input框的name属性值必须等于接收时数组参数的变量名称。
/** * 演示接收数组(直接接收数组) */ @RequestMapping("/delAll") public String delAll(Integer[] ids) throws Exception { // 批量删除:可以循环这个ids数组,逐条删除即可。 return "success"; } |
方式二:在Vo中传递数组参数
【QueryVo.java】
package cn.itcast.pojo;
import java.util.List;
public class QueryVo {
private Integer[] ids;
// getter/setter方法。。。。。。 } |
|
·传参规范:页面上input框的name属性值必须等于接收时Vo中数组类型属性的变量名称。
/** * 演示接收数组(用Vo传递数组) */ @RequestMapping("/delAll") public String delAll(QueryVo vo) throws Exception { // 批量删除:可以循环这个Vo中的ids数组,逐条删除即可。 return "success"; } |
这种方式是对直接接收的另一种处理方式,可以解决HTTP参数与形参名称不一致时的参数接收:
/** * 批量删除 * 演示:@RequestParam注解修饰形参,接收HTTP数组参数(直接传递数组类型) */ @RequestMapping("/deleteitem") public String deleteItems(@RequestParam(value="ids") Integer[] itemsIds) throws Exception { // 遍历数组,逐条进行删除....
return "common/success"; } |
可以利用List集合类型的参数传递多条数据进行批量处理。比如批量更新。
批量更新:实现商品数据的批量修改。
要想实现商品数据的批量修改,需要在商品列表中可以对商品信息进行修改,并且可以批量提交修改后的商品数据。提交的数据应该是一个List。
功能分解:
前端:1)列表改成input输入框;2)定义改好的input框的name属性;
后端:1)能接收到提交过来的更新数据;2)批量更新处理
1. 接收商品列表的pojo
注意:SpringMVC不能直接传递List集合类型的参数,必须包装在Vo中。这是SpringMVC框架的强制要求。
【QueryVo.java】
package cn.itcast.pojo;
import java.util.List;
public class QueryVo {
private List
// setter/getter方法。。。。。。。
} |
|
2. Jsp
可以重新创建一个专门演示批量更新的画面【itemListUpdBatch.jsp】,利用原来的itemList.jsp拷贝一个,然后将表格中的项目都改成input输入框,可以直接修改数据。
【itemListUpdBatch.jsp】代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>查询商品列表title> head> <body> <form action="${pageContext.request.contextPath }/updateAll.action" method="post"> 查询条件: <table width="100%" border=1> <tr> <td><input type="submit" value="批量修改"/>td> tr> table>
商品列表: <table width="100%" border=1>
<tr> <td>商品名称td> <td>商品价格td> <td>生产日期td> <td>商品描述td> <td>操作td> tr>
<c:forEach items="${itemList }" var="item" varStatus="status"> <tr> <td><input type="text" name="updateItemsList[${status.index }].name" value="${item.name }" />td> <td><input type="text" name="updateItemsList[${status.index }].price" value="${item.price }" />td> <td><input type="text" name="updateItemsList[${status.index }].createtime" value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>" />td> <td><input type="text" name="updateItemsList[${status.index }].detail" value="${item.detail }"/>td>
<td><input type="hidden" name="updateItemsList[${status.index }].id" value="${item.id}" /> <a href="${pageContext.request.contextPath }/toEdit.action?id=${item.id}">修改a> td> tr> c:forEach> table> form> body>
html> |
HTTP参数提交的样子:
HTTP参数传输的实际样子:
附:
varStatus属性常用参数总结下:
${status.index} 输出行号,从0开始。
${status.count} 输出行号,从1开始。
${status.current} 当前这次迭代的(集合中的)项
${status.first} 判断当前项是否为集合中的第一项,返回值为true或false
${status.last} 判断当前项是否为集合中的最后一项,返回值为true或false
begin属性、end属性、step属性分别表示:起始序号,结束序号,跳跃步伐。
其中[]被encode了,仔细辨认可以看出来。
提交名称开头相同,又带有下标和后缀名的参数,SpringMVC会根据形参中的List属性的定义,按照这个List属性中泛型的类型进行参数绑定。
3. Controller
【ItemsController.java】先定义一个方法用于itemListDelBatch.jsp页面的显示:
@RequestMapping("/listForUpd") public ModelAndView getListForUpd() throws Exception { List ModelAndView modelAndView = new ModelAndView(); // request.setAttribute(key, value) // 底层仍然是把属性名和属性值放到request对象中 // jsp页面永远是从request对象中取得数据的 modelAndView.addObject("itemsList", itemsList); modelAndView.setViewName("items/itemListUpdBatch"); return modelAndView; } |
再定义一个方法,用来更新处理。这里不做具体的更新,我们主要学习如何接收List参数。
/** * 演示接收List */ @RequestMapping("/updateAll") public String updateAll(QueryVo vo) throws Exception { System.out.println(vo);
// 批量修改:遍历List,逐个修改。 return "success"; } |
1. SpringMVC不能直接传递List集合类型的参数,必须包装在java bean中。这是SpringMVC框架的强制要求。
2. 页面上input框的name属性值必须等于Controller方法形参java bean中List属性名 + [集合下标] + . + List泛型中的属性名。
例如:
<input type="text" name="itemsUpdLst[${status.index }].name" value="${item.name }" />
注解@RequestMapping控制着url到请求方法的映射,对url能访问到Controller中正确的响应方法起到了至关重要的作用。
标记url到请求方法的映射,即通过最终的url找到Controller中对应的方法。这个在以前的示例中已经练习了。
官方的说法叫做窄化请求映射,其实就是为了防止你和你的同事起的url重名,在类上多给url加了一层目录。
【ItemsController.java】的修改:
@Controller @RequestMapping("/items") public class ItemsController { 。。。。。。 } |
访问地址从【http://localhost:8080/ssm-1/list.action】变成了【http://localhost:8080/ssm-1/items/list.action】,多了一层【/items】目录。
注意:
此时SpringMVC.xml中的视图解析器的前缀的开头要加斜杠/WEB-INF/jsp,如果写成WEB-INF/jsp就会被SpringMVC认为是相对于【/items】下的相对路径,直接拼在【/items】的后面了。
1. Http请求类型:post、get、put、delete等,不过在传统web开发中put、delete很少使用了。
2. post与get:
必须明确指定是post时,才是post请求;否则默认是get请求。
在浏览器中输入url提交的请求是get请求。
3. @RequestMapping使用方式:
a) 默认方式:之前使用@RequestMapping都是它的默认使用方式,默认的@RequestMapping支持所有的Http请求类型。
b) 正常方式:@RequestMapping(value="具体url路径", method=某一种http请求类型)
指定了Http请求类型就限制只能用指定的请求类型提交请求。
c) 多请求方式:@RequestMapping(value="具体url路径", method={请求类型1, 请求类型2,……})
4. 示例:
◆限定只允许GET方法访问:
@RequestMapping(value="/list", method = RequestMethod.GET)
◆限定只允许POST方法访问:
@RequestMapping(value="/list", method = RequestMethod.POST)
◆GET和POST方法都可以:
@RequestMapping(value="/list", method = {RequestMethod.POST, RequestMethod.GET })
或
@RequestMapping("/list")
以商品列表画面的访问为例,是通过get方式访问的,因此可以在@RequestMapping的method属性中设置请求的类型是【RequestMethod.GET】:
@RequestMapping(value="/list", method=RequestMethod.GET) public ModelAndView itemsList() throws Exception {
List
// 1. 设置返回页面需要的数据 2. 指定返回页面的地址 ModelAndView modelAndView = new ModelAndView();
// 1. 设置返回页面需要的数据 modelAndView.addObject("itemList", itemsList);
// 2. 逻辑视图名称的设置(就是视图文件的文件名) modelAndView.setViewName("itemList");
return modelAndView; } |
尝试是否能访问成功。
然后把【RequestMethod.GET】改成【RequestMethod.POST】,再试一次,会报405错误:
HTTP Status 405 - Request method 'GET' not supported
这说明请求访问受限了。
同样如果用POST方法访问【RequestMethod.GET】修饰的URL也会报405错误:
HTTP Status 405 - Request method 'POST' not supported
大家是不是有一个疑问,感觉这个功能多余,还不如不限制。是的,如果在传统web系统中这个限制功能使用的很少。但在RESTful的url中十分有用。
提到Controller方法的返回值主要指两方面内容:
1. 怎样指定返回页面的路径?
2. 怎样指定返回页面的数据?
可以调用里面的方法指定页面的地址;还可以调用里面的方法指定返回给页面的数据。
这个在第一天的内容中已经介绍过了。这里就不多说了。
Controller方法如果返回的是普通字符串,那就是视图的逻辑视图名或物理视图名。这个在第一天的代码示例中已经介绍过了。
请求转发:【forward:】开头的字符串,后面跟转发的URL路径,URL如果有后缀(.action)要加上。
重定向:【redirect:】开头的字符串,后面跟重定向的URL路径,URL如果有后缀(.action)要加上。
注意:关键字后面的冒号是半角。
请求转发是后台程序方法之间跳转时使用的,由于是后台之间的跳转,因此浏览器中URL不发生改变,这也说明还是在同一个Request请求中,因此转发后的方法仍然可以接收转发前从浏览器提交上来的Request对象的HTTP参数。
重定向相当于再次从浏览器发起一个新的Request请求,由于是从前台重新发起的,因此浏览器中的URL发生改变。由于不是同一个Request请求,因此重定向后的方法不能接收重定向前从浏览器提交上来的HTTP参数。
1. 试验目的:验证请求转发的特征,理解请求转发。
2. 试验方法:一览页面点击【修改】进入编辑页面,在编辑页面点击【保存】后再次回到本编辑页面。
3. 试验过程:
【itemList.jsp】页面提交:
...... <c:forEach items="${itemList }" var="item"> <tr> <td>${item.name }td> <td>${item.price }td> <td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>td> <td>${item.detail }td> <td><a href="${pageContext.request.contextPath }/items/itemEdit.action?id=${item.id}">修改a>td> tr> c:forEach> ...... |
【editItem.jsp】页面,对应方法接收名为id的参数。
/** * 根据id查询商品的详细信息 */ @RequestMapping("/itemEdit") public String getItemById(HttpServletRequest request, Model model) throws Exception { // 取得Request对象的HTTP参数id Integer id = Integer.parseInt(String.valueOf(request.getParameter("id")));
Items items = itemsService.getItemById(id);
// 将商品详细信息返回给页面 model.addAttribute("item", items);
return "items/editItem"; } |
【保存】提交更新内容:提交上来的参数中包含名为id的参数,还有其他商品信息:
/** * 演示请求转发: * 转发前的更新处理 */ @RequestMapping("/updateitem") public String updateItemsById(Items items) throws Exception {
itemsService.updateItemById(items);
return "forward:itemEdit.action"; } |
因为请求转发的特征是同一个Request请求,因此保存更新时提交上来的全部HTTP参数都能被转发后的方法取得。
然后请求转发回根据id查询商品详细信息的方法,重新查询并再次显示编辑页面:
从debug中可以看到,上面保存更新时候Request对象提交上来的参数都可以取得,因为它就是同一个request对象,其中刚好也有名为id的参数,因此可以执行查询:
同时也看到浏览器的url没有发生变化,还是保存更新时候的URL【/updateitem】,这样证明了没有惊动客户端,说明请求转发完全是后台的事情:
1. 试验目的:验证重定向的特征,理解重定向。
2. 试验方法:同请求转发。
3. 试验过程:
【itemList.jsp】页面提交:
...... <c:forEach items="${itemList }" var="item"> <tr> <td>${item.name }td> <td>${item.price }td> <td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>td> <td>${item.detail }td> <td><a href="${pageContext.request.contextPath }/items/itemEdit.action?id=${item.id}">修改a>td> tr> c:forEach> ...... |
【editItem.jsp】页面,对应方法接收名为id的参数。
/** * 根据id查询商品的详细信息 */ @RequestMapping("/itemEdit") public String getItemById(HttpServletRequest request, Model model) throws Exception { // 取得Request对象的HTTP参数id Integer id = Integer.parseInt(String.valueOf(request.getParameter("id")));
Items items = itemsService.getItemById(id);
// 将商品详细信息返回给页面 model.addAttribute("item", items);
return "items/editItem"; } |
【保存】提交进行更新:提交上来的参数中包含名为id的商品全部信息:
/** * 根据id更新商品信息 * 演示:SpringMVC的重定向 */ @RequestMapping("/updateitem") public String updateItemsById(Items items) throws Exception { itemsService.updateItemById(items); return "redirect:itemEdit.action"; } |
因为重定向的特征是重新从浏览器发起一个新的Request请求,因此保存更新时提交上来的全部HTTP参数都不能被重定向后的方法取得。
然后重定向回根据id查询商品详细信息的方法,重新查询并再次显示编辑页面:
由于是新的request,那问题出现了,新的Request中没有更新时提交的HTTP参数,自然也没有id参数:
解决方法:利用SpringMVC的Model可以给重定向后的方法传递参数
/** * 根据id更新商品信息 * 演示:SpringMVC的重定向 */ @RequestMapping("/updateitem") public String updateItemsById(Items items, Model model) throws Exception { itemsService.updateItemById(items); // 使用model对象传递id model.addAttribute("id", items.getId()); return "redirect:itemEdit.action"; } |
再次启动运行:
浏览器的url不再是【/updateitem】:说明重定向操作不是后台的跳转,而是由浏览器重新发起的请求。
为什么重定向可以使用Model传递参数?
因为重定向的字符串返回时,SpringMVC看到是redirect开头最终由视图解析器解析后会生成一个专门处理重定向的视图View对象:org.springframework.web.servlet.view.RedirectView,这个View会把前端控制器给它的Model数据放到新Request对象的Parameter(HTTP参数)中,这样重定向后就会得到参数了。
如果是非重定向的字符串返回(逻辑视图名或请求转发)时,视图解析器会生成普通的视图View对象:org.springframework.web.servlet.view.InternalResourceView,这个View会把前端控制器给它的Model数据放到同一个Request对象的Attribute(属性)中。
因此大家在使用SpringMVC时要善用Model(ModelAndView)。
Controller方法的返回jsp页面是请求转发还是重定向?
Jsp本身就是一个Servlet对象,Controller方法在向jsp页面跳转后URL并没有发生改变,仍然是提交给方法时候的URL,而且放置在Model(ModelAndView)参数中的数据会被SpringMVC放置到Request对象中,也都在Jsp页面通过EL表达式得到了,这说明它们是一个Request对象,EL表达式通过属性名从Request对象的Attribute中取得了结果值。
因此跳转到JSP页面是请求转发而不是重定向。
Request对象的Parameter是指从请求发起端提交上来的参数,出于安全的考虑,这些参数在我们的后台java程序中是无法篡改的。因此只有request.getParameter(“参数名”)方法,但没有setParamete()方法。
Request对象的Attribute是指后台方法与方法对象与对象之间请求转发时,传递数据的,这个可以在后台方法中随便进行set/getAttribute()。
因此,不要混淆这两个集合的用途和概念。
如果使用void为返回值,SpringMVC是处理不了的,只能采用java web原生的HttpServletRequest,HttpServletResponse处理,这样就不走SpringMVC的视图解析器,就破坏了SpringMVC的体系结构,所以一般不要使用。这里只是提一下。
示例代码【ItemsController.java】
/** * 演示void */ @RequestMapping("/update") public void updateItems(Items items, HttpServletRequest request, HttpServletResponse response)throws Exception { itemsService.updateItems(items); request.setAttribute("id", items.getId()); request.getRequestDispatcher("/WEB-INF/jsp/editItem.jsp").forward(request, response); response.setCharacterEncoding("utf-8"); response.setContentType("application/json;charset=utf-8"); response.getWriter().write("json串"); } |
在JavaEE项目的开发中,不管是持久层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免的遇到各种可预知的、不可预知的异常需要处理。如果每个过程都单独处理异常,那么系统的代码冗余度会很高,工作量大且不好统一,维护的工作量也很大。
那么,能不能将所有类型的异常处理从各处理过程提取出来呢?如果能提取出来,那么既保证了各层程序的处理逻辑的功能较单一(只专注业务逻辑的实现),也实现了异常信息的统一处理和维护。答案是肯定的。下面将介绍使用Spring MVC统一处理异常的解决和实现过程。
SpringMVC异常处理的思路总的来说就是dao、service、controller层的程序出现异常都通过throws Exception向外抛出,抛出的异常就会逐层向它的上层传递,最后异常有DispatcherServlet接收,它接到之后就会转给统一的异常处理组件HandlerExceptionResolver(处理器异常解析器)进行异常处理,如下图:
因为HandlerExceptionResolver(处理器异常解析器)只是一个接口,SpringMVC不提供实现类进行异常处理,所以异常的具体处理需要由我们继承这个接口自己实现。
在实现自定义异常解析器之前要明确一点认识:
我们不能把404、500这样的错误异常信息展示给用户,也就一旦展示给用户会产生很不友好的印象。说的不好听点就是对外要掩饰错误,给出一些善意的托词,比如:系统繁忙,请稍后再试,或者一个可爱卖萌的动画图片等等。目的是求得用户暂时的理解。
创建package【cn.itcast.exception】在其中创建【CustomExceptionResolver.java】
package cn.itcast.exception;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView;
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override public ModelAndView resolveException(HttpServletRequest arg0, HttpServletResponse arg1, Objectarg2, Exception exc) { // 异常信息 String msg = "系统繁忙,请稍候再试"; ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("msg", msg); modelAndView.setViewName("common/error"); return modelAndView; } } |
【SpringMVC.xml】
<bean class="cn.itcast.exception.CustomExceptionResolver" /> |
只要在SpringMVC核心配置文件中把这个bean配置上就可以。由于它继承了HandlerExceptionResolver,所以SpringMVC可以自动加载这个自定义的组件。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>title> head> <body> ${msg } body> html> |
特意把Controller中的一个方法改错,【ItemsController.java】:运行时异常
@RequestMapping("/list") public ModelAndView itemsList() throws Exception { // 程序错误,自动抛出异常 int i = 0 / 0; List // 1. 设置返回页面需要的数据 2. 指定返回页面的地址 ModelAndView modelAndView = new ModelAndView(); // 1. 设置返回页面需要的数据 modelAndView.addObject("itemList", itemsList); // 2. 逻辑视图名称的设置(就是视图文件的文件名) modelAndView.setViewName("itemList"); return modelAndView; } |
画面显示了【系统繁忙,请稍候再试】,而不是丑陋的500异常信息,就是因为有了整个系统的统一异常处理。
如果去掉这个统一的异常处理,比如讲SpringMVC.xml中的配置去掉,然后在请求这个页面就会出现丑陋的500:
各层都throws Exception,最后由DispatcherServlet交给HandlerExceptionResolver的实现类来处理的好处:
异常信息统一处理,更易于维护。
避免将500、404这样的错误信息返回给用户。
可以判断自定义异常,用异常机制控制业务违规的限制。
我们还可以自定义异常类,那自定义异常类究竟有什么作用呢?——自定义异常只是希望利用java异常机制做一些特殊业务的限制,这样的业务限制不是程序bug。比如秒杀活动中的限购提示或者取钱时余额不足时中断处理并提示余额不足等。这些并不是程序的bug,都是业务范畴的限制。我们就可以利用java的异常机制,自定义一种异常,一旦业务出现违规就抛出这个特殊的异常,当系统捕获到这个特殊异常时就做对应的业务违规提示。
自定义异常【CustomException.java】
package cn.itcast.exception;
/** * 自定义异常类 * @author Derek Sun * */ public class CustomException extends Exception {
private String message;
/** * @return the message */ public String getMessage() { return message; }
/** * @param message the message to set */ public void setMessage(String message) { this.message = message; } }
|
在程序中造一个业务业务违规。由于是业务违规都是先进行判断,并在判断条件为true的逻辑中设置业务违规的具体信息,然后再抛出自定义异常。
【ItemsController.java】:做一个假的业务违规逻辑
@RequestMapping("/list") public ModelAndView itemsList() throws Exception { // 自定义异常 if (true) { CustomException exc = new CustomException(); exc.setMessage("请不要太贪心,您已经购买了一台!"); throw exc; }
List // 1. 设置返回页面需要的数据 2. 指定返回页面的地址 ModelAndView modelAndView = new ModelAndView(); // 1. 设置返回页面需要的数据 modelAndView.addObject("itemList", itemsList); // 2. 逻辑视图名称的设置(就是视图文件的文件名) modelAndView.setViewName("itemList"); return modelAndView; } |
异常抛出后最终还是会由自定义的异常处理解析器捕获,因此需要在异常处理解析器中增加自定义异常处理的逻辑判断:【CustomExceptionResolver.java】
package cn.itcast.exception;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView;
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override public ModelAndView resolveException(HttpServletRequest arg0, HttpServletResponse arg1, Objectarg2, Exception exc) { // 异常信息 String msg = ""; // 判断传入的异常种类 // 如果是自定义异常直接抛出对应的业务违规信息 // 如果是程序异常就提示:系统繁忙,请稍后再试 if (exc instanceof CustomException) { // 自定义异常 msg = exc.getMessage(); } else { // 运行时异常 msg = "系统繁忙,请稍候再试"; }
ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("msg", msg); modelAndView.setViewName("error"); return modelAndView; } } |
再次运行tomcat测试,结果显示【请不要太贪心,您已经购买了一台!】
SpringMVC的异常处理思想其实就是架构级别的异常处理思想,是从JavaEE架构整体的角度去统一异常处理。这是一个系统架构处理异常的最重要思想。
1. JSON数据格式:键值对的形式承载数据,即{k1:v1,k2:v2,...}
2. JSON的起源:源于JavaScript,JS对象的字符串表示形式,这种用字符串表示对象的方式叫做序列化。序列化的好处是便于对象的传输交互。
3. JSON的本质:就是一个字符串。
因此,JSON在JS代码中必须是一个字符串的形式:
(其中key名、字符串类型的value值都要用双引号括起来,包括大括号在内整体要包在一对单引号中)
例如:'{"name":"测试商品", "price":99.9}'
除此之外,大家经常在js中写的:{"name":"测试商品", "price":99.9},这种不是JSON,而是普通js对象。
大家为什么会误解普通js对象就是一个json?
之所以会误解普通js对象就是一个json的原因是浏览器惹的祸,因为在js代码以外的地方我们看到的json都是这种:{"name":"测试商品", "price":99.9},但注意这是在js代码以外的地方,才表示成这样的,在它的老家里它必须要表明它的本质——字符串。而我们有时候看json最多的地方就是浏览器的后台监视里面,而在浏览器的监视窗口中json确实就是这个样子:{"name":"测试商品", "price":99.9},因此许多人此时产生了在js代码中{"name":"测试商品", "price":99.9}就是json的错误的理解。
比xml更小、更高效,构上结和pojo类似,可以借助工具进行相互转换。
在SpringMVC中要想使用json必须导入一下jar包:
因为注解驱动<mvc:annotation-driven />会自动加载解析json的转换器:
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter,而这个转换器就需要依赖这三个jar包,因此直接导入jar包后不需要任何配置。
接收和返回json需要两个注解:@RequestBody和@ResponseBody:
@RequestBody:接收json转化成java对象。要求:json的key名==java对象的属性名
@ResponseBody:将返回值处理成字符串返回,如果返回结果是String类型就直接返回,如果返回值是java对象就转化成json返回(因为json本质也是一个字符串)。
两个注解是通过SpringMVC提供的接口:
org.springframework.http.converter.HttpMessageConverter
【代码示例】
1. 随便在itemList.jsp页面上添加一个button,然后在jsp中用jquery定义一个js函数里面定义一个ajax作为客户端,点击添加的button进行ajax提交。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.4.4.min.js">script> <script type="text/javascript"> function sendJson() { $.ajax({ type:"post", url:"${pageContext.request.contextPath }/items/sendJson.action", contentType:"application/json;charset=utf-8", // 指定从页面传给Controller的数据格式是什么样的 //dataType:"", // 从Controller返回给页面的数据格式是什么样的,一般可以不写,它可以自动jquery可以自动匹配 data:'{"name":"测试商品","price":99.9}', success:function(data){ alert(data); } }); } script> <title>查询商品列表title> head> <body> <input type="button" value="sendJson" onclick="sendJson()"> <form action="${pageContext.request.contextPath }/items/search.action" method="post"> 查询条件: <table width="100%" border=1> 。。。。。。。。 table> form> body> html> |
2. 在后台Controller中定义一个新方法来响应这个ajax提交:
【ItemsController.java】形式一:@ResponseBody放在了方法定义上面
/** * json数据交互 */ @RequestMapping("/sendJson") @ResponseBody public Items sendJson(@RequestBody Items items) throws Exception { System.out.println(items); items.setDetail("aaaa"); return items; } |
【ItemsController.java】形式二:@ResponseBody放在了方法返回类型前面
/** * json数据交互 */ @RequestMapping("/sendJson") public @ResponseBody Items sendJson(@RequestBody Items items) throws Exception { System.out.println(items); items.setDetail("aaaa"); return items; } |
1. 如果方法参数上配置了@RequestBody,必须保证前台提交过来的必须是json字符串,即【data:'{"name":"测试商品","price":99.9}'】和【contentType:"application/json;charset=utf-8"】必须有。没有就不会被看作是一个json字符串。
2. @RequestBody要求HTTP请求的类型不能是GET,那如果是在传统web请求中除了GET就只剩POST了。但在RESTful中还可以是PUT和DELETE。
如果不满足上面的两个要求,提交会报错。
<beanclass="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">bean> list> property> bean> |
如果不使用默认的转换器,就需要像上面这样配置。
@ResponseBody注解对应的处理类会把返回的字符串写入Response对象的body区,并返回给浏览器,HTTP头信息中的状态会变成200(OK,即成功),ajax会监听这个HTTP头的状态,如果是200,就会调用success中定义的function函数,这样就激活了回调函数,然后js把Response对象body区中的字符串取出来,如果是普通字符串就直接传给回调函数的参数,如果是json字符串就转化成普通js对象然后传给回调函数的参数。由于采用了ajax异步提交并回调的方式,因此此时SpringMVC方法返回后是不会走视图解析器的处理流程的,直接回到了前台浏览器。
传统的URL没有严格的要求,但为了表明URL操作的目的一般都会加入一些动词,比如要取得一个商品就getXXX:http://shop.com/laptop/getlenovo.action。比如:添加一个商品就addXXX或insertXXX:http://shop.com/laptop/addlenovo.action。
RESTful风格URL的一个重要特征:
URL中只能包含名词。
URL只能包含名词意味着什么?
如果只有名词,那URL只能作为网络中某个文件、某些数据、某段视频、某个图片等“东西”的定位地址了。
那么,对URL地址定位的“东西”进行操作的目的该如何表达呢?
这就是RESTful风格URL的另一个重要特征:用HTTP请求的动词类型来区分操作的目的,即GET:查询、POST:新增、PUT:更新、DELETE:删除。
网络上的这些“东西”我们统称为资源。
根据上面两个RESTful特征,传统的URL就变成了下面的样子:
取得商品:HTTP GET---http://shop.com/laptop/lenovo.action。
添加商品:HTTP POST---http://shop.com/laptop/lenovo.action。
删除商品:HTTP DELETE---http://shop.com/laptop/lenovo.action。
修改商品:HTTP PUT---http://shop.com/laptop/lenovo.action。
RESTful风格URL这样改的目的就是要让URL变得简单,因此RESTful风格URL的第三个重要特征:
URL不能有后缀名。
URL进一步变成了下面的样子:没有了后缀名,URL就变短了。
取得商品:HTTP GET---http://shop.com/laptop/lenovo。
添加商品:HTTP POST---http://shop.com/laptop/lenovo。
删除商品:HTTP DELETE---http://shop.com/laptop/lenovo。
修改商品:HTTP PUT---http://shop.com/laptop/lenovo。
GET请求URL后面的参数在RESTful风格URL中如何写?
参数跟在URL后面用斜杠/分隔,不需要问号?不需要参数名。
比如根据商品类别取得商品:HTTP GET---http://shop.com/laptop/lenovo/E470。
如果传递多个参数就按照事先设计好的参数顺序排在URL的后面:
HTTP GET---http://shop.com/laptop/lenovo/E470/black/8G/500G。
这样URL变得更短小、简洁,因为没有参数名称,参数的业务含义就不好推测了,因此也会更安全。
1. 用名词组成的URL定位资源,用HTTP动词(GET:查询、POST:新增、PUT:更新、DELETE:删除)描述操作。
2. 请求的URL,除了静态资源文件URL以外,不允许有后缀名。
3. GET请求URL后面附带的参数必须在URL后面用斜杠/分隔。
URL改成RESTful风格后变得更加简洁了。
RESTful风格URL特点并不是强制要求,只是一些改进的建议,愿意遵守的就是RESTful风格,不愿意遵守就不是RESTful风格。
SpringMVC对RESTful的URL支持还是比较好的,只需两步配置就可以支持RESTful风格的URL了。
想要SpringMVC支持RESTful,需要在web.xml中修改可以接收的URL:.action改成/,这样的配置会让DispatcherServlet拦截有所的url,只放行.jsp的URL。
<servlet-mapping> <servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern> servlet-mapping> |
因为除了.jsp的URL以外,其余的URL都会被拦截,那js、css等资源文件的URL怎么办?
【SpringMVC.xml】
<mvc:resources location="/js/" mapping="/js/**"/> |
此处可以试一试:
启动tomcat后,直接访问一个js文件,应该是可以访问到的,但是如果把这个配置注视掉,再启动tomcat后,就访问不到了。
到此SpringMVC的RESTful配置完成。剩下要做的就是把我们的系统从传统URL改造成RESTful。
在我们现在的代码示例中,传统的URL:
http://localhost:8080/ssm-2/items/list.action(查询,GET)
http://localhost:8080/ssm-2/items/itemEdit.action?id=1(查询,GET)
http://localhost:8080/ssm-2/items/itemUpdate.action(更新,POST)
http://localhost:8080/ssm-2/items/sendJson.action(模拟删除,POST)
把上面url变成RESTful样式如下:
http://localhost:8080/ssm-2/items/list(查询,GET)
http://localhost:8080/ssm-2/items/detail/1(查询,GET)
http://localhost:8080/ssm-2/items/detail(更新,PUT)
http://localhost:8080/ssm-2/items/detail(模拟删除,DELETE)
RESTful系统中@RequestMapping注解的作用非常大:
@RequestMapping(value="url", method=RequestMethod.POST/GET/DELETE/PUT)
【itemList.jsp】:ajax支持四种HTTP动词,可以直接写:
<script type="text/javascript"> function sendJson() { $.ajax({ type:'delete', url:'${pageContext.request.contextPath }/items/detail', contentType:'application/json;charset=utf-8', data:'{"name":"测试商品", "price":99.9}', success:function(data) { alert(data.name + '---' + data.price); } }); } script> |
<tr> <td>${item.name }td> <td>${item.price }td> <td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>td> <td>${item.detail }td> <td><a href="${pageContext.request.contextPath }/items/detail/${item.id}">修改a>td> tr> |
JSP表单提交只支持GET和POST,不支持DELETE和PUT,这是java web的要求。所以想要能DELETE或PUT提交就必须将POST转换成PUT或者DELETE。
如何把POST改成PUT和DELETE?
1) 需要在【web.xml】中配置一个过滤器:这个配置用时拷贝即可。
<filter> <filter-name>HiddenHttpMethodFilterfilter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class> filter> <filter-mapping> <filter-name>HiddenHttpMethodFilterfilter-name> <url-pattern>/*url-pattern> filter-mapping> |
2) 在editItem.jsp中添加一个名为_method的hidden变量:
<body>
<form id="itemForm" action="${pageContext.request.contextPath }/items/detail" method="post"enctype="multipart/form-data"> <input type="hidden" name="_method" value="PUT" /> 。。。。。。 form> |
注意:多媒体表单只能使用POST提交,所以这里将图片上传的功能暂时取消掉,恢复成正常的form。
RESTful的URL中用PUT表示更新,但是如果是多媒体表单提交即使你做了PUT的相关设置也是无效的,只要是多媒体表单,提交就只认POST类型。
【ItemsController.java】
在@RequestMapping的URL中如果有接收的参数,就用{}把参数名括起来,名称根据业务需求自定义;
在方法的形参前用@PathVariable注解限定形参与URL中参数的映射关系;
@RequestMapping(value="/detail/{itemsId}", method=RequestMethod.GET) public String getItemsById(@PathVariable("itemsId") Integer id, HttpServletRequest request, Model model) throws Exception { // Integer id = Integer.valueOf(request.getParameter("id")); Items items = itemsService.getItemsById(id); model.addAttribute("item", items); return "items/editItem"; } 或者:如果url中参数名==形参名,@PathVariable中的名字可以省略 @RequestMapping(value="/detail/{id}", method=RequestMethod.GET) public String getItemsById(@PathVariable Integer id, HttpServletRequest request, Model model) throws Exception { // Integer id = Integer.valueOf(request.getParameter("id")); Items items = itemsService.getItemsById(id); model.addAttribute("item", items); return "items/editItem"; } 如果想加多个参数:【http://localhost:8080/ssm-2/items/detail/1/123.1】,对应注解可以这样写:@RequestMapping(value="/detail/{itemId}/{itemPrice}", method=RequestMethod.GET) 然后用注解@PathVariable("url中参数名")限定URL参数与形参的映射,对号入座取来使用。
|
@RequestMapping(value="/detail", method=RequestMethod.PUT) public String updateItemsById2(Items items) throws Exception { itemsService.updateItemsById(items); // model.addAttribute("id", items.getId()); return "redirect:/items/detail/" + items.getId(); } 注意: 在RESTful的url下请求转发和重定向的url要用绝对路径,否则路径可能会出现混乱,此时的id必须拼在URL上面,不能用model对象传递了,否则与上面方法的URL定义即不匹配了。
|
@RequestMapping(value="/detail", method=RequestMethod.DELETE) @ResponseBody public Items sendJsonTest(@RequestBody Items items) throws Exception { items.setDetail("json test"); return items; } |
这跟我们软件系统的演变有关系:C/S单机结构 -> B/S网络结构 -> C/S互联网结构
C/S互联网结构:
一个后台系统服务多种客户端,甚至还出现了一些面向大众的公共服务平台,比如Facebook platform,微博开放平台,微信公共平台等,它们不需要有显式的前端,只有一套提供服务的接口,用户可以利用这些平台进行基于平台的应用开发。
这些新的互联网的演化,要求我们的服务端架构设计要进行调整,以适应各种不同的C(客户)。于是一哥们在他的毕业论文中提出了REST概念,即以网络资源(数据、文件、图片、视频、音频)为核心的一种思想。
Roy Fielding的毕业论文。这哥们参与设计HTTP协议,也是Apache Web Server项目的co-founder。PhD的毕业学校是 UC Irvine,Irvine在加州,有着充裕的阳光和美丽的海滩,是著名的富人区。Oculus VR 的总部就坐落于此(虚拟现实眼镜,被FB收购,CTO为Quake和Doom的作者 John Carmack)。
1. 全称:
Resource Representational State Transfer(资源表现的状态转移),通俗讲就是资源在网络中以某种表现形式进行状态转移。它认为网络中的核心是资源。
2. 解释:
Resource:资源,即数据,比如商品信息、用户信息、一个图片、一个视频等、一个pdf文件等。互联网中的一切都是资源。
Representational:某种表现形式,比如用JSON、XML、JPEG、PDF等;
State Transfer:状态变化。通过HTTP动词(GET、POST、PUT、DELETE等)实现。即通过CRUD的动作对数据产生的变化。比如:苹果从青到红到烂,就是苹果的状态变化,是细菌和氧气对苹果的产生的动作作用的结果。同理通过HTTP协议中的动作对网络资源进行CRUD的操作,使得资源发生变化,即为状态变化。
3. 怎样理解:
小的方面:就是围绕着网络资源的状态变化,通过某种表现形式表现出来。
大的方面:就是为了达到网络资源为核心的目的,并能更好的为各种客户端提供服务,需要对web系统架构进行重组,基于此大牛架构师先行者们提出了一些建议,使得REST成为一种如何组织web服务架构的建议的代名词,它不是强制性的标准,更不是一种新的技术,只是一种建议或者叫做风格。
从小的方面入手就是用URL定位资源,用HTTP动词(GET、POST、PUT、DELETE等)描述操作。
从大的方面入手就是形容web系统符合了REST风格就称为RESTful。
大的方面需要多年的开发积累和自己的对系统架构的不断研究学习才能有所体会的。因此我们从小的方面讲RESTful,即解决如何使我们的url变得RESTful
先来看一个RESTful风格URL的例子:知乎的某问题的url:
http://www.zhihu.com/question/28557115。
根据用URL定位资源,用HTTP动词描述操作原则,组合如下:
创建:POST http://www.zhihu.com/question/28557115
删除:DELETE http://www.zhihu.com/question/28557115 (可以用POST代替)
更新:PUT http://www.zhihu.com/question/28557115 (可以用POST代替)
取得:GET http://www.zhihu.com/question/28557115
由上面的叙述可知:URL中只需要描述你需要访问的资源在哪,即:
http://www.jd.com/drinks/beers/local/list
http://www.jd.com/drinks/beers/qingdao/1903/1
如何使我们的URL变得RESTful?(两点)
RESTful的URL中使用名词而不是动词,且推荐用复数,不要有参数。
RESTful中的资源要分类分层次(什么分类下什么层次下的什么资源名中的具体哪个资源对象)
注意:
不要有参数即不要有Get请求中那样的参数:http://www.a.com/goods/list.action?id=aaa&name=bbb
RESTful中的参数全被视为资源定位的名词描述
URL示例:
Bad:
http://www.jd.com/beer/getqingdao/1903/1
http://www.a.com/toys/cars/list.action?name=bmw&color=red
Good:
http://www.jd.com/beers/qingdao/1903/1
http://www.a.com/toys/cars/list/bmw/red
1. 使用客户/服务器模型:
客户和服务器之间通过一个统一的接口来互相通讯。
2. 层次化的系统:
在一个REST系统中,客户端并不会固定地与一个服务器打交道。
3. 无状态:
在一个REST系统中,服务端并不会保存有关客户的任何状态。也就是说,客户端自身负责用户状态的维持,并在每次发送请求时都需要提供足够的信息。
4. 可缓存:
REST系统需要能够恰当地缓存请求,以尽量减少服务端和客户端之间的信息传输,以提高性能。
5. 统一的接口:
一个REST系统需要使用一套统一的接口来完成子系统之间以及服务与用户之间的交互。这使得REST系统中的各个子系统可以独自完成演化。
参考网页:
https://www.zhihu.com/question/28557115
http://www.cnblogs.com/loveis715/p/4669091.html
http://www.cnblogs.com/rainy-shurun/p/5412162.html
拦截请求,类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理。一般在权限验证的时候使用较多。
SpringMVC第一天学习的转换器仅仅是处理参数的,拦截器的功能更加强大。
自定义拦截器都要实现org.springframework.web.servlet.HandlerInterceptor接口:
在工程中创建连接器:
【Interceptor1.java】
package interceptor;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;
public class Interceptor1 implements HandlerInterceptor {
/** * 执行时机:页面渲染完毕后调用此方法。 * 应用场景:可以用来清除某些资源(类似java的finally)。 */ @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { System.out.println("======Interceptor1=============afterCompletion======"); }
/** * 执行时机:在调用Controller方法结束后、页面渲染之前调用此方法。 * 应用场景:把一些公共信息加入到Model中,返回给页面。
*/ @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2,ModelAndView arg3) throws Exception { System.out.println("======Interceptor1=============postHandle======"); // Items items = (Items)arg3.getModel().get("item"); // System.out.println(items.getName()); }
/** * 执行时机:在调用Controller方法前会调用此方法。 * 返回布尔类型的结果,返回true放行,返回false拦截后续所有的处理。 * 应用场景:检查用户是否登录。 */ @Override public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throwsException { System.out.println("======Interceptor1=============preHandle======"); return true; } }
|
【SpringMVC.xml】
<mvc:interceptors> <mvc:interceptor>
<mvc:mapping path="/**"/> <bean class="interceptor.Interceptor1" /> mvc:interceptor> <mvc:interceptor>
<mvc:mapping path="/**"/> <bean class="interceptor.Interceptor2" /> mvc:interceptor> mvc:interceptors> |
这里了解一下单个拦截器中和多个拦截器并存时三个方法的执行顺序的规律,主要是想让大家把握住拦截器执行的详细顺序,尤其是多个拦截器共同工作的时候,以免使用时由于不清楚顺序而拦截失败或拦截了不该拦截的东西。
1. 单个拦截器的执行顺序:
先定义一个拦截器:Interceptor1.java测试它里面三个方法的拦截顺序
======Interceptor1=============preHandle======
======Interceptor1=============postHandle======
======Interceptor1=============afterCompletion======
2. 多个拦截器的执行顺序:
a) 两个拦截器中preHandle方法都返回true时:在配置文件中配置顺序是先1后2
preHandle:(配置的正序)
======Interceptor1=============preHandle======
======Interceptor2=============preHandle======
postHandle:(配置的反序)
======Interceptor2=============postHandle======
======Interceptor1=============postHandle======
afterCompletion:(配置的反序)
======Interceptor2=============afterCompletion======
======Interceptor1=============afterCompletion======
b) 两个拦截器中preHandle方法都返回true时:在配置文件中配置顺序是先2后1
preHandle:(配置的正序)
======Interceptor2=============preHandle======
======Interceptor1=============preHandle======
postHandle:(配置的反序)
======Interceptor1=============postHandle======
======Interceptor2=============postHandle======
afterCompletion:(配置的反序)
======Interceptor1=============afterCompletion======
======Interceptor2=============afterCompletion======
当都所有拦截器都返回true时,此时总的规律:先开始的后结束。
1. 让Interceptor2的preHandle方法返回false时:(配置顺序中不是第一个的拦截器)
======Interceptor1=============preHandle======
======Interceptor2=============preHandle======
======Interceptor1=============afterCompletion======
说明:
首先拦截器2的preHandle返回false,它自己的后续方法全部中断。
其次拦截器1的preHandle返回true,但是它的postHandle也没有执行,说明postHandle受到所有拦截器的preHandle方法返回值的影响
再次拦截器1的afterCompletion方法却执行了,说明afterCompletion不受其他拦截器的preHandle方法返回值的影响。
结论:
postHandle受所有拦截器的preHandle执行结果的影响,只有全部preHandle都返回true时才执行
afterCompletion只受它自己所属拦截器中preHandle的影响,preHandle返回true时执行。
2. 让Interceptor1的preHandle方法返回false时:(配置顺序中的第一个拦截器)
======Interceptor1=============preHandle======
结论:
配置顺序第一个拦截器的preHandle返回了false,则中断所有后续处理。
1. 有一个登录页面,需要写一个Controller访问登录页面
2. 登录页面有一提交表单的动作。需要在Controller中处理。
a) 判断用户名密码是否正确(在控制台打印)
b) 如果正确,向session中写入用户信息(写入用户名username)
c) 跳转到商品列表
3. 拦截器
a) 访问商品列表画面时,拦截用户请求,判断用户是否登录(登录请求不能拦截)
b) 如果用户已经登录。放行
c) 如果用户未登录,跳转到登录页面。
【login.jsp】
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title heretitle> head> <body> <form action="${pageContext.request.contextPath }/user/login.action"> <label>用户名:label> <br> <input type="text" name="username"> <br> <label>密码:label> <br> <input type="password" name="password"> <br> <input type="submit"> form> body> html> |
【UserController.java】
package cn.itcast.controller;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
@Controller @RequestMapping("/user") public class UserController { /** * 跳转到登录页面 */ @RequestMapping("/toLogin") public String toLogin() { return "items/login"; } /** * 用户登录 */ @RequestMapping("/login") public String login(String username, String password, HttpSession session) { // 校验用户登录 System.out.println(username); System.out.println(password); // 把用户名放到session中 if (username != null && !"".equals(username)) { session.setAttribute(session.getId(), username); } return "redirect:/items/list.action"; } } |
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2)throws Exception { // 从request中获取session HttpSession session = request.getSession(); // 从session中根据Session id取得用户登录信息 Object user = session.getAttribute(session.getId()); // 判断user是否为null if (user == null) { // 如果为空则跳转到登录页面 response.sendRedirect(request.getContextPath() + "/user/toLogin.action"); return false; } // 如果不为空则放行 return true; } |
拦截商品业务中的url
因为ItemController做了url窄化限定,
所以配置文件中如下配置:表明url以/items/开头的均被拦截。
<mvc:interceptor>
<mvc:mapping path="/items/**"/>
<bean class="cn.itcast.interceptor.LoginHandlerInterceptor"/> mvc:interceptor> |
本章所讲的图片上传方法是JavaWeb传统的上传方式,即前台页面提交一个可以包含图片的特殊form,后台处理需要具有处理特殊form的能力,将form中的图片提取出来交给后台程序处理。
上传的图片应该在画面上显示出来,在web页面中访问一个图片是使用一个url的。Tomcat提供一种设置虚拟URL和实际图片保存的磁盘路径的映射关系,这样在web页面访问这个虚拟url就相当于访问实际磁盘的路径,就可以访问到指定的图片。
如何创建一个web虚拟url路径和一个磁盘物理路径的映射关系呢?——在web服务器中可以指定它们之间的映射关系,比如我们的tomcat就可以创建:
点击Modules
将上面指定的实际保存图片的物理路【C:\mydir\03_workspace\image】与这个虚拟url路径【/pic】关联到一起。这样当通过url:http://localhost:8080/pic/xxxx.jpg就可以访问的对应的图片了,并显示到浏览器中。就相当于访问C:\mydir\03_workspace\image\xxxx.jpg。
这里的物理路径:C:\mydir\03_workspace\image
映射后url路径:/pic
可以启动tomcat试一下:
先找一个图片放到C:\mydir\03_workspace\image目录下
然后启动tomcat
在浏览器访问http://localhost:8080/pic/xxx.jpg
注意:这个虚拟url路径是tomcat本身自己的配置,和任何web工程无关,所以任何web工程都可以使用这个虚拟路径。
这样在页面上就可以在回显的img的src中这样写:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>修改商品信息title> head> <body>
<form id="itemForm" action="${pageContext.request.contextPath }/items/it/${item.id }" method="post"enctype="multipart/form-data"> <%-- <%-- --%> 修改商品信息: <table width="100%" border=1> <tr> <td>商品名称td> <td><input type="text" name="name" value="${item.name }" />td> tr> <tr> <td>商品价格td> <td><input type="text" name="price" value="${item.price }" />td> tr> <tr> <td>商品生产日期td> <td><input type="text" name="createtime" value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>"/>td> tr> <tr> <td>商品图片td> <td> <c:if test="${item.pic !=null}"> <img src="/pic/${item.pic}" width=100 height=100/> <br/> c:if> <input type="file" name="pictureFile"/> td> tr> <tr> <td>商品简介td> <td><textarea rows="3" cols="30" name="detail">${item.detail }textarea> td> tr> <tr> <td colspan="2" align="center"><input type="submit" value="提交" /> td> tr> table> form> body> html> |
|
在jsp页面中,form的【enctype="multipart/form-data"】属性,作用是表示该表单可以提交多媒体文件,比如图片
修改【editItem.jsp】,给form添加这个属性,使得它能够处理图片上传。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>修改商品信息title> head> <body>
<form id="itemForm" action="${pageContext.request.contextPath }/items/it/${item.id }"method="post" enctype="multipart/form-data"> <%-- <%-- --%> 修改商品信息: <table width="100%" border=1> <tr> <td>商品名称td> <td><input type="text" name="name" value="${item.name }" />td> tr> <tr> <td>商品价格td> <td><input type="text" name="price" value="${item.price }" />td> tr> <tr> <td>商品生产日期td> <td><input type="text" name="createtime" value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>" />td> tr> <tr> <td>商品图片td> <td> <c:if test="${item.pic !=null}"> <img src="${item.pic}" width=100 height=100/> <br/> c:if> <input type="file" name="pictureFile"/> td> tr> <tr> <td>商品简介td> <td><textarea rows="3" cols="30" name="detail">${item.detail }textarea> td> tr> <tr> <td colspan="2" align="center"><input type="submit" value="提交" /> td> tr> table> form> body> html> |
上传过程只是强调一点:提交表单,前台将图片转换成二进制流并提交。
注意:图片上传必须通过post方式提交多媒体类型的form表单,其他方式,包括get都不允许提交多媒体的form,否则会报500错误(The current request is not a multipart request)
SpringMVC对上传的图片提供后台的解析支持,使用的解析器是:org.springframework.web.multipart.commons.CommonsMultipartResolver,但是解析器需要依赖commons-fileupload和commons-io两个第三方的jar包,因此需要导入它们:
然后SpringMVC需要配置一下这个解析器才能生效:
【SpringMVC.xml】
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize"> <value>5242880value> property> bean> |
这里限制了文件上传的大小,不能太大,否则容易造成服务器的磁盘负担超大。
SpringMVC中配置了多媒体解析器后,Controller方法中就可以使用【MultipartFile】类型定义一个形参接收图片,并调用这个形参对象的方法处理图片。
·传参规范:页面上传控件的name属性值必须等于Controller方法中MultipartFile形参的变量名。
【ItemsController.java】:修改updateItems方法如下:
/** * 演示图片上传的后台处理 */ @RequestMapping("/update") public String updateItems(MultipartFile pictureFile, Items items, Model model) throws Exception { // 1. 获取图片原始的文件名 String fileName = pictureFile.getOriginalFilename(); // 2. 随机生成字符串 + 原文件的扩展名组成新的文件名称 String newFileName = UUID.randomUUID().toString() +fileName.substring(fileName.lastIndexOf(".")); // 3. 将图片保存到磁盘 pictureFile.transferTo(new File("C:\\mydir\\03_workspace\\image\\" + newFileName)); // 4. 将新的图片名称保存到数据库 items.setPic("http://localhost:8080/image/" + newFileName);
itemsService.updateItems(items);
// 在底层仍然是将model中设置的这个属性名和属性值设置到request对象中,所以无论是请求转发还是 // 重定向都可以将需要的数据通过model带到他们对应的request对象中,这样数据就被带到请求转发或 // 者重定向后的方法中去了。 model.addAttribute("id", items.getId());
return "redirect:toEdit.action"; } |
在项目实际中这种传统的上传方式已经不适用了,作为SpringMVC的一个比较重要的插件,这里只是作为一个SpringMVC的知识点把SpringMVC对上传图片的支持介绍给大家,大家作为一个知识了解一下即可。
因为在当今实际项目中,都采用js端的上传插件,图片选择完成后直接上传,后台需要提前编写一个独立的Controller类并定义一个方法来处理上传,直接保存到文件服务器,然后返回对应的url给页面。这时在整个页面完整信息进行提交保存时,form表单中只包含图片的url字符串和其他业务信息,这个form就不需要指定多媒体类型的属性了,没有了多媒体类型的属性的form就可以不局限于只运行post提交了,这就给处理带来了便利。尤其是解决了RESTful的更新表单提交问题(这个在RESTful中再详细说明)。