第三节:SpringMVC
3.1 概述
三层架构
我们的开发架构一般都是基于两种形式:一种是C/S架构,另一种是B/S架构。在Java EE开发中,几乎全都是基于B/S架构的开发。那么在B/S架构中,系统标准的三层架构包括:表现层、业务层、持久层。三层架构在我们的实际开发中使用的非常多。
三层架构中,每一层都各司其职,接下来我们就说说每层都负责哪些方面:
表现层
也就是我们常说的web层,它负责接收客户端的请求,向客户端响应结果,通常客户端使用http协议请求web层,web需要接收http请求,完成http响应
表现层包括:展示层、控制层。展示层负责结果的展示;控制层负责接收请求
表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端
表现层的设计一般都使用MVC模型。(MVC是表现层的设计模型,和其他层没有关系)
业务层
也就是我们常说的service层,它负责业务逻辑处理,和我们开发项目的需求息息相关。web层依赖业务层,但是业务层不依赖web层
业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保持事务一致性。(也就是我们说的,事务应该放到业务层来控制)
持久层
也就是我们常说的dao层,负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。通俗的讲,持久层就是和数据库交互,对数据库进行增删改查的
MVC模型
MVC(Model View Controller):是模型(model)-视图(view)-控制器(controller)的缩写, 是一种用于设计创建 Web 应用程序表现层的模式。MVC 中每个部分各司其职:
Model(模型)
通常指的就是我们的数据模型。作用一般情况下用于封装数据。
View(视图)
通常指的就是我们的 jsp 或者 html。作用一般就是展示数据的。
通常视图是依据模型数据创建的
Controller(控制器)
是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。
它相对于前两个不是很好理解,这里举个例子:
例如: 我们要保存一个用户的信息,该用户信息中包含了姓名,性别,年龄等等。 这时候表单输入要求年龄必须是 1~100 之间的整数。姓名和性别不能为空。并且把数据填充 到模型之中。 此时除了 js 的校验之外,服务器端也应该有数据准确性的校验,那么校验就是控制器的该做 的。 当校验失败后,由控制器负责把错误页面展示给使用者。 如果校验成功,也是控制器负责把数据填充到模型,并且调用业务层实现完整的业务需求。
SpringMVC是什么
SpringMVC是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级web框架,属于Spring FrameWork的后续产品,已经融合在Spring Web Flow里面。Spring框架提供了构建web应用程序的全功能MVC模块。使用Spring可插入的MVC架构,从而在使用Spring进行web开发时,可以选择使用Spring MVC框架或集成其它MVC开发框架,如Struts1(现在一般不用),struts2等
SpringMVC已经成为目前最主流的MVC框架之一,并且随着Spring3.0的发布,全面超越struts2,成为最优秀的MVC框架
它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求
SpringMVC的优势
-
清晰角色划分
前端控制器(DispatcherServlet)
请求到处理器映射(HandlerMapping)
处理器适配器(HandlerAdapter)
视图解析器(ViewResolver)
处理器或页面控制器(Controller)
验证器(Validator)
命令对象(Command 请求参数绑定到的对象就叫命令对象)
表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)
-
分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要
-
由于命令对象就是一个POJO,无需继承框架特定API,可以使用命令对象直接作为业务对象
-
和spring其它框架无缝集成,是其他web框架所不具备的
-
可适配,通过HandlerAdapter可以支持任意的类作为处理器
-
可定制性,HandlerMapping、ViewResolver等能够非常简单地定制
-
功能强大的数据验证、格式化、绑定机制
-
利用spring提供的Mock对象能够非常简单地进行web层单元测试
-
本地化、主题的解析支持,使我们更容易进行国际化和主题的切换
-
强大的JSP标签库,使JSP编写更容易
…还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配 置支持等等。
SpringMVC 和 Struts2 的优略分析
共同点:
- 它们都是表现层框架,都是基于 MVC 模型编写的。
- 它们的底层都离不开原始 ServletAPI。
- 它们处理请求的机制都是一个核心控制器。
区别:
-
Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
-
Spring MVC 是基于方法设计的,而 Struts2 是基于类,Struts2 每次执行都会创建一个动作类。所 以 Spring MVC 会稍微比 Struts2 快些。
-
Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便
(JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注 解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了。)
-
Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提 升,尤其是 struts2 的表单标签,远没有 html 执行效率高。
组件详细概述
DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,由它调用到其他组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性
HandlerMapping:处理器映射器
HandlerMapping负责根据用户请求找到Handler即处理器,SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等
Handler:处理器
它就是我们开发中要编写的具体业务控制器。由DispatcherServlet把用户请求转发到Handler。由Handler对具体的用户请求进行处理
HandlerAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行
View Resolver:视图解析器
View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名 即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户
View:视图
SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView 等。我们最常用的视图就是 jsp。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开 发具体的页面。
<mvc:annotation-driven>
说明
在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。
使 用 自动加载 RequestMappingHandlerMapping (处理映射器) 和 RequestMappingHandlerAdapter ( 处 理 适 配 器 ) , 可 用 在 SpringMVC.xml 配 置 文 件 中 使 用 替代注解处理器和适配器的配置。
3.2 常用注解
@RequestMaapping
**作用:**用于建立请求URL和处理请求方法之间的对应关系
出现位置:
-
类上:
请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录。写的话需要以/开头。 它出现的目的是为了使我们的 URL 可以按照模块化管理:
例如:
账户模块:
/account/add
/account/update
/account/delete
…
订单模块:
order/add
order/update
order/delete
“/”前的部分就是把RequestMapping写在类上,使我们的URL更加精细。
-
方法上:
请求URL的第二级访问目录
属性:
-
value
:用于指定请求的URL。它和path属性的作用是一样的 -
method
:用于指定请求的方式 -
params
:用于指定限制请求参数的条件。它支持简单的表达式,要求请求参数的key和value必须和配置的一模一样例如:
params={"accountName"}
,表示请求参数必须有accountName
params={"money!100"}
,表示请求参数中money不能是100 -
headers
:用于指定限制请求消息头的条件,即发送的请求中必须包含的请求头
**注意:**以上四个属性只要出现2个或以上时,它们的关系是与的关系
@RequestParam
作用:
把请求中指定名称的参数给控制器中的形参赋值
属性:
value
:请求参数中的名称required
:请求参数中是否必须提供此参数。默认值:true,表示必须提供,如果不提供将报错
示例:
@Controller
@RequestMapping("/anno")
public class AnnoContorler {
@RequestMapping("/testRequestServlet")
public String testRequestParam(@RequestParam(name = "name",required = true) String username){
System.out.println(username);
return "success";
}
}
@RequestBody
作用:
用于获取请求体内容。直接使用得到的是key=value&key=value...
结构的数据
get方法不适用
属性:
required
:是否必须有请求体。默认值是:true。当取值为true时,get请求方式会报错。如果取值为false,get请求得到的是null
示例:
//获取到请求体的内容
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body){
System.out.println(body);
return "success";
}
@PathVariable
作用:
拥有绑定URL中的占位符。例如:URL中有/delete/{id}
,{id}
就是占位符
属性:
value
:指定url中的占位符名称
Restful风格的URL
- 请求路径一样,可以根据不同的请求方式去执行后台的不同方法
- restful风格的url优点:
- 结构清晰
- 符合标准
- 易于理解
- 扩展方便
什么是 rest:
REST(英文:Representational State Transfer,简称 REST)描述了一个架构样式的网络系统, 比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之 一。在目前主流的三种 Web 服务交互方案中,REST 相比于 SOAP(Simple Object Access protocol,简单 对象访问协议)以及 XML-RPC 更加简单明了,无论是对 URL 的处理还是对 Payload 的编码,REST 都倾向于用更 加简单轻量的方法设计和实现。值得注意的是 REST 并没有一个明确的标准,而更像是一种设计的风格。
它本身并没有什么实用性,其核心价值在于如何设计出符合 REST 风格的网络接口。
restful 的优点
它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
restful 的特性:
资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。 它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个 URI(统一 资源定位符)指向它,每种资源对应一个特定的 URI 。
要获取这个资源,访问它的 URI 就可以,因此 URI 即为每一个资源的独一无二的识别符。
表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层 (Representation)。 比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二 进制格式。
状态转化(State Transfer):每 发出一个请求,就代表了客户端和服务器的一次交互过程。
HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器, 必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以 就是 “表现层状态转化”。具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET 、POST 、PUT、 DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来 删除资源。
restful 的示例:
/account/1
HTTP GET : 得到 id = 1 的 account
/account/1
HTTP DELETE: 删除 id = 1 的 account
/account/1
HTTP PUT: 更新 id = 1 的 account
/account
HTTP POST: 新增 account
示例:
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
System.out.println("执行了");
System.out.println(request);
System.out.println(request.getSession());
System.out.println(request.getSession().getServletContext());
return "success";
}
<a href="anno/testPathVariable/10">testPathVariable</a>
基于HiddentHttpMethodFilter
的示例
作用:
由于浏览器form表单只支持get和post请求,而delete、put等method并不支持,spring3.0添加了一个过滤器,可以将浏览器请求改为指定的请求方式,发送给我们的控制器方法,使得支持get、post、put与delete请求
使用方法:
- 在web.xml中配置该过滤器
- 请求方式必须使用post请求
- 按照要求提供
_method
参数,该参数的取值就是我们需要的请求方式
@RequestHeader
作用:
用于获取请求消息头
属性:
value
:提供消息头名称required
:是否必须有此消息头
**注:**在实际开发中不怎么用
示例:
//获取请求头的值
@RequestMapping("/testRequestHeader")
public String testRequestHeader(@RequestHeader(value = "Accept") String header){
System.out.println(header);
return "success";
}
@CookieValue
作用:
用于指定cookie名称的值传入控制器方法参数
属性:
value
:指定cookie的名称required
:是否必须有此cookie
示例:
//获取Cookie的值
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue(value = "JSESSIONID")String cookieValue){
System.out.println(cookieValue);
return "success";
}
@ModelAttribute
作用:
该注解是SpringMVC4.3版本以后新加入的。它可以用于修饰方法和参数
出现在方法上,表示当前方法会在控制器的方法执行之前先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法
出现在参数上,获取指定的数据给参数赋值
属性:
value
:用于获取数据的key。key可以是POJO的属性名称,也可以是map结构的key
应用场景:
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据
示例:
有返回值
//testModelAttribute
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user){
System.out.println("testModelAttribute执行了");
System.out.println(user);
return "success";
}
//该方法会先执行
@ModelAttribute
public User showUser(String username){
System.out.println("showUser执行了");
//通过用户名查询数据库
//模拟
User user=new User();
user.setUsername(username);
user.setAge(20);
user.setDate(new Date());
return user;
}
没有返回值
//testModelAttribute
@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute("abc") User user){
System.out.println("testModelAttribute执行了");
System.out.println(user);
return "success";
}
//该方法会先执行
@ModelAttribute
public void showUser(String username, Map<String,User> map){
System.out.println("showUser执行了");
//通过用户名查询数据库
//模拟
User user=new User();
user.setUsername(username);
user.setAge(20);
user.setDate(new Date());
map.put("abc",user);
}
@SessionAttributes
作用:
用于多次执行控制器方法间的参数共享
属性:
value
:用于指定存入的属性名称type
:用于指定存入的数据类型
@Controller
@RequestMapping("/anno")
@SessionAttributes(value ={"msg"})//把mag=美美存入到session域对象中
public class AnnoContorler {
//SessionAttributes的注解
@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(Model model){
System.out.println("testSessionAttributes执行了");
//底层会存储到request域对象中
model.addAttribute("msg","美美");
return "success";
}
}
//从session域中获取值
@RequestMapping("/getSessionAttributes")
public String getSessionAttributes(ModelMap modelMap){
System.out.println("getSessionAttributes执行了");
String msg=(String) modelMap.get("msg");
System.out.println(msg);
return "success";
}
//从session域中删除值
@RequestMapping("/deleteSessionAttributes")
public String deleteSessionAttributes(SessionStatus sessionStatus){
System.out.println("getSessionAttributes执行了");
sessionStatus.setComplete();
return "success";
}
3.3 请求参数的绑定
请求参数的绑定说明
- 绑定机制
- 表单提交的数据都是k=v格式的
username=haha&&paaword=123
- SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
- 要求:提交表单的name和参数的名称是相同的
- 表单提交的数据都是k=v格式的
- 支持的数据类型
- 基本数据类型和字符串类型
- 实体类型(JavaBean)
- 集合数据类型(List、map集合等)
基本数据类型和字符串类型
- 提交表单的name和参数的名称是否是相同的
- 区分大小写
实体类型(JavaBean)
- 提交表单的name和JavaBean中的属性名称需要一致
- 如果一个JavaBean类中包含其它的引用类型,那么表单的name属性需要编写成:对象.属性。例如:
address.name
给集合属性数据封装
jsp页面编写方式:list[0].属性
请求参数中中文乱码的解决
-
在web.xml中配置Spring提供的过滤类
<!--配置过滤器,解决中文乱码的过滤器--> <filter> <filter-name>characterEncodingFilter</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> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
自定义类型转换器
-
表单提交的任何数据类型全部都是字符串类型,但是后台定义Integer类型,数据也可以封装上,说明 Spring框架内部会默认进行数据类型转换。
-
如果想自定义数据类型转换,可以实现
Converter
的接口-
自定义类型转换器
//字符串转换为日期 public class StringToDateConverter implements Converter<String, Date> { //参数-s:传入进来的字符串 @Override public Date convert(String s) { if (s==null) { throw new RuntimeException("请您传入数据"); } DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd"); try { //把字符串转换为日期 return dateFormat.parse(s); } catch (ParseException e) { throw new RuntimeException("数据类型转换出现错误"); } } }
-
. 注册自定义类型转换器,在
SpringMVC.xml
配置文件中编写配置<!--注册自定义类型转换器--> <bean id="conversionServices" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="fuswx.utils.StringToDateConverter"/> </set> </property> </bean> <!--开启springgmvc框架注解和类型转换器的支持--> <mvc:annotation-driven conversion-service="conversionServices"/>
-
在控制器中使用原生的ServletAPI对象
只需要在控制器的方法参数定义HttpServletRequest和HttpServletResponse对象
//原生的API获取
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
System.out.println("执行了");
System.out.println(request);
System.out.println(request.getSession());
System.out.println(request.getSession().getServletContext());
return "success";
}
3.4 SpringMVC返回值类型及响应数据类型
返回值分类
1、返回字符串
-
Controller
方法返回字符串可以指定逻辑视图的名称,根据视图解析器为物理视图的地址。@RequestMapping("/testString") public String testString(Model model){ System.out.println("testString执行了"); //模拟从数据库中查询出User对象 User user=new User("美美","123",20); //model对象 model.addAttribute("user",user); return "success"; }
-
具体的应用场景
@Controller @RequestMapping("/user") public class UserController { /** * 请求参数的绑定 */ @RequestMapping(value="/initUpdate") public String initUpdate(Model model) { // 模拟从数据库中查询的数据 User user = new User(); user.setUsername("张三"); user.setPassword("123"); user.setMoney(100d); user.setBirthday(new Date()); model.addAttribute("user", user); return "update"; } }
<h3>修改用户</h3> ${ requestScope } <form action="user/update" method="post"> 姓名:<input type="text" name="username" value="${ user.username }"><br> 密码:<input type="text" name="password" value="${ user.password }"><br> 金额:<input type="text" name="money" value="${ user.money }"><br> <input type="submit" value="提交"> </form>
2、返回值是void
-
如果控制器的方法返回值编写成void,执行程序报404的异常,默认查找JSP页面没有找到。
默认会跳转到
@RequestMapping(value="/initUpdate") initUpdate
的页面。 -
可以使用请求转发或者重定向跳转到指定的页面
//返回值类型是void //请求转发是一次请求,不用编写项目的名称 @RequestMapping("/testVoid") public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("testVoid执行了"); //编写请求转发的程序 request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);//不会使用视图解析器 //重定向 //response.sendRedirect(request.getContextPath()+"index.jsp"); //直接会进行响应 //设置中文乱码 response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); response.getWriter().println("hello"); return;//不想让后面代码执行 }
3、返回值是ModelAndView对象
-
ModelAndView对象是Spring提供的一个对象,可以用来调整具体的jsp视图
-
具体代码:
//返回值类型是void //请求转发是一次请求,不用编写项目的名称 @RequestMapping("/testModelAndView") public ModelAndView testModelAndView() { //创建ModelAndView对象 ModelAndView modelAndView=new ModelAndView(); System.out.println("testModelAndView执行了"); //模拟从数据库中查询出User对象 User user = new User("美美", "123", 20); //把user对象存储到modelAndView对象中,也会把user对象存入到request对象中 modelAndView.addObject("user",user); //跳转到哪个页面 modelAndView.setViewName("success"); return modelAndView;//不想让后面代码执行 }
转发和重定向
//使用关键字的方式进行转发和重定向
@RequestMapping("/testForwardOrRedirect")
public String testForwardOrRedirect(Model model) {
System.out.println("testForwardOrRedirect执行了");
//请求的转发
//return "forward:/WEB-INF/pages/success.jsp";
//请求重定向
return "redirect:/index.jsp";
}
3.5 ResponseBody响应json数据
-
DispatcherServlet会拦截到所有的资源,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而 不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置
<mvc:resources>
标签配置不过滤- location元素表示webapp目录下的包下的所有文件
- mapping元素表示以/static开头的所有请求路径,如/static/a 或者/static/a/b
<!-- 设置静态资源不过滤 --> <mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 --> <mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 --> <mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
-
使用@RequestBody获取请求体数据
//模拟异步请求响应 @RequestMapping("/testAjax") public void testAjax(@RequestBody String body) { System.out.println("testAjax执行了"); System.out.println(body); }
$(function (){ $("#btn").click(function (){ //alert("hello btn"); //发送ajax请求 $.ajax({ //编写json格式,设置属性和值 url:"user/testAjax", contentType:"application/json;charset=utf-8", data:'{"username":"美美","password":"123","age":30}', dataType:"json", type:"post", success:function (data){ //data:服务器端相应的json数据,进行解析 } }) }) })
-
使用
@RequestBody
注解把json的字符串转换成JavaBean的对象json字符串和JavaBean对象互相转换的过程中,需要使用jackson的jar包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.12.4</version>
</dependency>
要求方法需要返回JavaBean的对象
//模拟异步请求响应
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user) {
System.out.println("testAjax执行了");
//客户端发送ajax的请求,传的是json字符串,后端把json字符串封装到user对象中
System.out.println(user);
//作响应,模拟查询数据库
user.setUsername("haha");
user.setAge(40);
user.setPassword("123");
//作响应
return user;
}
$(function (){
$("#btn").click(function (){
//alert("hello btn");
//发送ajax请求
$.ajax({
//编写json格式,设置属性和值
url:"user/testAjax",
contentType:"application/json;charset=utf-8",
data:'{"username":"美美","password":"123","age":30}',
dataType:"json",
type:"post",
success:function (data){
//data:服务器端相应的json数据,进行解析
alert(data);
alert(data.username);
alert(data.password);
alert(data.age);
}
})
})
})
3.6 SpringMVC实现文件上传
文件上传的回顾
文件上传的必要前提
- form表单的
enctype
取值必须是:multipart/form-data
(默认值是:application/x-www/form-urlencoded
);enctype
:是表单请求正文的类型 method
属性取值必须是post
- 提供一个文件选择域
<input type="file"/>
文件上传的原理分析
当 form 表单的 enctype 取值不是默认值后,request.getParameter()
将失效。
enctype=”application/x-www-form-urlencoded”
时,form 表单的正文内容是: key=value&key=value&key=value
当 form 表单的 enctype 取值为 Mutilpart/form-data 时,请求正文内容就变成: 每一部分都是 MIME 类型描述的正文
-----------------------------7de1a433602ac 分界符
Content-Disposition: form-data; name=“userName” 协议头
aaa 协议的正文
-----------------------------7de1a433602ac
Content-Disposition: form-data; name=“file”; filename=“C:\Users\zhy\Desktop\fileupload_demofile\b.txt”
Content-Type: text/plain 协议的类型(MIME 类型)
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -----------------------------7de1a433602ac–
借助第三方组件实现文件上传
使用 Commons-fileupload 组件实现文件上传,需要导入该组件相应的支撑 jar 包:Commons-fileupload
和 commons-io
。commons-io 不属于文件上传组件的开发 jar 文件,但Commons-fileupload 组件从 1.1 版本开始,它工作时需要 commons-io 包的支持。
原始方式上传图片代码
@Controller
@RequestMapping("/user")
public class FilerUploadController {
//文件上传
@RequestMapping("/fileUpload1")
public String fileUpload(HttpServletRequest request){
System.out.println("文件上传");
//使用Fileupload组件完成上传
//上传的位置
String path=request.getSession().getServletContext().getRealPath("/uploads/");
//判断该路径是否存在
File file=new File(path);
if (!file.exists()){
//创建文件夹
file.mkdir();
}
//解析request对象,获取上传文件项
DiskFileItemFactory factory=new DiskFileItemFactory();
ServletFileUpload upload=new ServletFileUpload(factory);
//解析request
try {
List<FileItem> fileItems=upload.parseRequest(request);
//遍历
for (FileItem item : fileItems) {
//进行判断,当前item对象是否是上传文件项
if (item.isFormField()){
//说明是普通表单项
}else {
//上传文件项
///获取到上传文件的名称
String filename=item.getName();
//把文件名称设置为唯一值,uuid
String uuid=UUID.randomUUID().toString().replace("-","");
filename=uuid+"_"+filename;
//完成文件上传
item.write(new File(path,filename));
//删除临时文件
item.delete();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
}
<form action="user/fileUpload1" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"><br>
<input type="submit" value="上传">
</form>
SpringMVC框架文件上传
SpringMVC框架文件上传的原理分析
//文件上传
@RequestMapping("/fileUpload2")
public String fileUploadByMVC(HttpServletRequest request, MultipartFile upload){
System.out.println("文件上传");
//使用Fileupload组件完成上传
//上传的位置
String path=request.getSession().getServletContext().getRealPath("/uploads/");
//判断该路径是否存在
File file=new File(path);
if (!file.exists()){
//创建文件夹
file.mkdir();
}
//上传文件项
///获取到上传文件的名称
String filename=upload.getOriginalFilename();
//把文件名称设置为唯一值,uuid
String uuid=UUID.randomUUID().toString().replace("-","");
filename=uuid+"_"+filename;
//完成文件上传
try {
upload.transferTo(new File(path,filename));
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
<h3>SpringMVC文件上传</h3>
<form action="user/fileUpload2" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"><br>
<input type="submit" value="上传">
</form>
配置文件解析器对象
<!--配置文件解析器对象-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1024000"/>
</bean>
SpringMVC跨服务器方式的文件上传
分服务器的目的
在实际开发中,我们会有很多处理不同功能的服务器。例如:
应用服务器:负责部署我们的应用
数据库服务器:运行我们的数据库
缓存和消息服务器:负责处理大并发访问的缓存和消息
文件服务器:负责存储用户上传文件的服务器。
(注意:此处说的不是服务器集群)
分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。
步骤:
-
搭建图片服务器
- 根据文档配置tomcat9的服务器,现在是2个服务器
- 导入资料中day02_springmvc5_02image项目,作为图片服务器使用
-
实现SpringMVC跨服务器方式文件上传
-
导入开发需要的jar包
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> <version>1.19.4</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.19.4</version> </dependency>
-
编写文件上传的JSP页面
<h3>跨服务器的文件上传</h3> <form action="user/fileupload3" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="upload"/><br/> <input type="submit" value="上传文件"/> </form>
-
编写控制器
//文件上传 @RequestMapping("/fileUpload3") public String fileUploadByMVC2(MultipartFile upload){ System.out.println("文件上传"); //定义上传服务器的路径 String path="http://localhost:9090/uploads/"; //上传文件项 ///获取到上传文件的名称 String filename=upload.getOriginalFilename(); //把文件名称设置为唯一值,uuid String uuid=UUID.randomUUID().toString().replace("-",""); filename=uuid+"_"+filename; //完成文件上传 try { upload.transferTo(new File(path,filename)); //完成文件上传,跨服务器上传 //创建客户端对象 Client client=Client.create(); //和图片服务器进行连接 WebResource webResource=client.resource(path+filename); //上传文件 webResource.put(upload.getBytes()); } catch (IOException e) { e.printStackTrace(); } return "success"; }
-
3.7 SpringMVC中异常处理
异常处理思路
Controller
调用service,service调用dao,异常都是向上抛出的,最终有DispatcherServlet
找出异常处理器进行异常的处理
SpringMVC的异常处理
-
自定义异常类
public class SysExecption extends Exception{ //存储提示信息的 private String message; public SysExecption(String message){ this.message=message; } @Override public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
-
自定义异常处理器
//异常处理器 public class SysExecptionResolver implements HandlerExceptionResolver { //处理异常的逻辑 @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { //获取到异常对象 SysExecption sysExecption=null; if (sysExecption instanceof SysExecption){ sysExecption=(SysExecption) sysExecption; }else { sysExecption=new SysExecption("系统正在维护..."); } //创建ModelAndView对象 ModelAndView modelAndView=new ModelAndView(); modelAndView.addObject("errorMsg",sysExecption.getMessage()); modelAndView.setViewName("error"); return modelAndView; } }
-
配置异常处理器
<!--配置异常处理器--> <bean class="fuswx.execption.SysExecptionResolver" id="execptionResolver"/>
-
编写异常代码
<a href="user/testExecption">异常处理</a>
3.8 SpringMVC拦截器
拦截器的作用
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
用户可以自己定义一些拦截器来实现特定的功能。
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺 序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但 是也有区别,接下来我们就来说说他们的区别:
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦 截的。
它也是 AOP 思想的具体应用。
我们要想自定义拦截器, 要求必须实现:HandlerInterceptor
接口。
拦截器概述
- SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术。
- 可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链 中的拦截器会按着定义的顺序执行
- 拦截器和过滤器的功能比较类似,有区别
- 过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术。
- 拦截器是SpringMVC框架独有的
- 过滤器配置了/*,可以拦截任何资源。
- 拦截器只会对控制器中的方法进行拦截
- 拦截器也是AOP思想的一种实现方式
- 想要自定义拦截器,需要实现
HandlerInterceptor
接口。
自定义拦截器步骤
-
创建类,实现
HandlerInterceptor
接口,重写需要的方法//自定义拦截器 public class MyInterceptor implements HandlerInterceptor { //预处理,Controller方法执行之前 //return true,放行,执行下一个拦截器,如果没有,就执行controller中的方法 //return false不放行 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor1放行"); return true; } }
-
在
SpringMVC.xml
中配置拦截器类<!--配置拦截器--> <mvc:interceptors> <mvc:interceptor> <!--要拦截的具体方法--> <mvc:mapping path="/user/*"/> <!--不要拦截的具体方法 <mvc:exclude-mapping path=""/> --> <!--配置拦截器对象--> <bean class="fuswx.interceptor.MyInterceptor" id="interceptor"/> </mvc:interceptor> </mvc:interceptors>
HandlerInterceptor
接口中的方法
-
preHandle
方法是controller
方法执行前拦截的方法- 可以使用request或response跳转到指定的页面
return true
放行,执行下一个拦截器,如果没有拦截器,执行controller中的方法return false
bu放行,不会执行controller中的方法
-
postHandle
是controller
方法执行后执行的方法,在jsp视图执行前- 可以使用request或response跳转到指定的页面
- 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示
-
afterCompletion
方法是在jsp执行后执行request或respones不能再跳转页面了
//自定义拦截器
public class MyInterceptor implements HandlerInterceptor {
//预处理,Controller方法执行之前
//return true,放行,执行下一个拦截器,如果没有,就执行controller中的方法
//return false不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor放行");
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}
//后处理方法:controller方法执行后,success.jsp执行之前
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
System.out.println("MyInterceptor执行了...之后");
}
//success.jsp页面执行后,该方法会执行
//跳不了别的页面了
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor执行了...最后");
}
}
配置多个拦截器
-
再编写一个拦截器类
public class MyInterceptor2 implements HandlerInterceptor { //预处理,Controller方法执行之前 //return true,放行,执行下一个拦截器,如果没有,就执行controller中的方法 //return false不放行 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor222...放行"); //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response); return true; } //后处理方法:controller方法执行后,success.jsp执行之前 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response); System.out.println("MyInterceptor222执行了...之后"); } //success.jsp页面执行后,该方法会执行 //跳不了别的页面了 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor222执行了...最后"); } }
-
在
SpringMVC.xml
中配置2个拦截器<!--配置拦截器--> <mvc:interceptors> <mvc:interceptor> <!--要拦截的具体方法--> <mvc:mapping path="/user/*"/> <!--不要拦截的具体方法 <mvc:exclude-mapping path=""/> --> <!--配置拦截器对象--> <bean class="fuswx.interceptor.MyInterceptor" id="interceptor"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <bean id="interceptor2" class="fuswx.interceptor.MyInterceptor2"/> </mvc:interceptor> </mvc:interceptors>
多个拦截器的执行顺序
多个拦截器是按照配置的顺序决定的