SpringMVC、MVC的基础知识
说明:关于该知识的项目可以在ubuntu--eclipse--SpringMVC文件夹中查看
mvc的概念
[基于异步请求的MVVM 模式](#基于异步请求的MVVM 模式)
环境搭建
[SpringMVC 核心组件和三大组件](#SpringMVC 核心组件和三大组件)
我们开发的MVC业务代码
[SpringMVC 其他组件 ](#SpringMVC 组件 )
注解
请求相关
请求参数的绑定
解决POST表单中文乱码
自定义类型转换
常用注解
响应相关
@RequestMapping方法返回值分类:
Json相关
Springmvc实现文件上传
异常处理
springMVC的异常处理
springmvc拦截器
SSM整合
其他细节
AJAX的处理
@RequestMapping方法对应的url
[RESTful 风格知识](#RESTful 风格)
EL表达式
WEB-INF目录及重定向
错误收集
SpringMVC、MVC的基础知识
讲的很好的课程:黑马张阳老师
MVC的概念
说明:MVC是一种模型概念。在java中,SpringMVC是表现层框架:
- Model:模型,指的就是 javaBean
- View:视图,一般指返回的静态或动态页面。如将结果封装到 jsp中显示。
- Controller:控制器,通常指Servlet(可访问)或Filter(不可访问),还有我们编写的实现了
@RequestMapping的类和方法。
Model1:早期开发的设计模型图示:(只有JSP和JavaBean)

说明:
由于 JSP 的可重用性差,不同项目的共享代码,每次都需要再在 JSP 中写一遍,而且 JSP 还要翻译成.java再编译成.class,现在几乎没有用这种方式开发web应用的了。
优点:结构简单。当用于小型项目时,效率高。
缺点:
①职责:JSP 的职责兼顾:[ 展示数据 ] + [ 处理数据 ]。(也就是干了视图、控制器的事)
②开发时:开发效率低。因为所有逻辑代码都写在 JSP 中,导致代码重用性很低。
③运行维护:维护性差。因为展示数据的代码和部分的业务代码交织在了一起,维护非常不方便。
结论:此种模型已被淘汰,无人使用。
Model2:针对上述的缺点进行改进得到的模型
针对每个缺点:
①:让JSP的职责为仅展示数据,而不处理数据。(仅作为View)处理数据交由控制器(Servlet)来实现。
②:抽取原 JSP 中重复代码,提取并封装,让其成为可重用代码。
③:如①所述,其可以带来维护的便利。
图示:

延续拓展:
根据Model2模型,由许多开发人员共同努力,提出MVC的理念。
MVC模型的优势
其优势也就是解决了上述Model1的缺点,概括如下:
①职责清晰:清晰而又单一的职责划分。
②组件功能独立:每个组件提供不同的功能,有利于代码的重用。
③后期维护方便。
④任何项目都适用,包括中小项目的表现层也可以采用该模型来开发,并且后期易于拓展(二次开发)
MVC 模型的弊端
任何模型都有两面性,MVC也并非全方面优秀的设计模型,弊端如下:
①运行速度慢:展示数据响应速度慢(主要是因为用 JSP显示数据。JSP 如上述要经过翻译、编译成.class文件,然后才能展示。
②开发人员要求高:需要对组件进行合理设计,构造严谨的架构。
③异步交互不方便。(因为AJAX处理数据时,如服务器响应后进行更新的操作,其逻辑中包含了视图和处理数据,交织在了一起,又形成了职责模糊。
针对这些弊端,如今又提出了基于异步请求的设计模式:
基于异步请求的MVVM 模式
Model-View-ViewModel,模型-视图-视图模型
改善:功能分离。其主要将异步操作中,脚本里的服务器响应后视图更新的View提取出来放到html页面中,让脚本只专注于逻辑,而写html页面只专注于页面的布局、结构、样式等等。
如下,将${"#mydiv"}.html(data);这个用于服务器异步响应之后的更新动作,放到了页面布局中:

实现MVVM的应用框架:VUE
springmvc和struts2的区别
共同点:
- 他们都是表现层框架,且都是基于MVC模型。(从轮廓)
- 他们的底层都是ServletAPI。(从底层所用接口)
- 他们处理请求的机制都是一个核心控制器。(从工作机制)
区别:
SpringMVC的入口是Servlet;而Struts2是Filter。
SpringMVC是基于方法设计的(单例模式);而Struts2是基于类,其每次执行都会创建一个动作类。(前者会稍微比后者快)
-
SpringMVC使用更简单,同时支持 JSR303,处理ajax的请求更方便(异步)
(JSR303 是一套JavaBean 参数校验的标准,他定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean 的属性上面,就可以在需要校验时进行校验)
Struts2的OGNL表达式 使页面开发效率相比 SpringMVC更高些,但执行效率并没有比JSTI提升,尤其是Struts2 的表单标签,远没有html执行效率高。
环境搭建
小技巧:
- maven配置pom.xml文件中,可以使用
<properties>映射字符串:如版本<properties><spring.version>5.2.5.RELEASE<...,后面的version即可用${spring.version}代替。
maven项目:ubuntu--eclipse--springmvc01
①建立maven项目,archetype选择webapp。
②补全java目录:
因为main下面没有自动生成java、test、resources目录,需手动添加。
②-1:java下新建一个Hello类,并将其作为spring Bean,且创建一个方法,用于返回一个字符串如“success”。
这个字符串将会被解析成要返回的 success.jsp。
③配置pom.xml:
导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
</dependencies>
④配置web.xml:
要将springMVC的Servlet类添加到过滤所有请求
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 当tomcat启动时,自动读取xml文件。 -->
<init-param>
<param-name>ContextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 启动服务器时便创建该servlet(默认单例),而非调用 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 注意,映射后某些.jsp、.html、js脚本文件需要映射配置:[webapp下的各种信息映射]-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
⑤resources下新建springMVC.xml,并进行配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="hello"></context:component-scan>
<!-- 其可以自动加载springmvc的处理器映射器(HandlerMapping)、处理器适配器(handlerAdaptor) -->
<mvc:annotation-driven/>
<!-- 配置视图解析器,解析hello返回的值"success",并打开success.jsp -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀为success.jsp所在目录 -->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- 后缀名:.jsp -->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置index.jsp中索引js库的路径:配置js的映射 -->
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/web/**" location="/web/"/>
</beans>
注意:视图解析器中,value的值要在WEB-INF前加'/',如果不加,则该方法返回的字符串解析后的路径是: /springmvc01/user/WEB-INF/pages/success.jsp,是错误的页面。
⑥配置服务器,并开始。
流程图
说明:DispatcherServlet是核心,其对每个request请求都会进行拦截,并处理。
概括流程图:
- ①拦截“hello”( 点了入门程序),并将其与标注有@RequestMapping的方法进行匹配
- ②匹配到时,该DispatcherServlet就会调用sayHello方法,并返回一个值。
- ③这个值,又被DispatcherServlet拦截,并发送给视图解析器对象,由其生成返回的seccess.jsp。
- ④DispatcherServlet将该jsp发送给服务器,服务器发送给浏览器。


具体流程图:


MVC的分析:

由于前面的大图中,其他组件是我们没使用mvc之前就有的,所以后四个就是我们的mvc。
-
DispathcerServlet:其是控制器C,但helloControl也是C,不同之处在于它处理的是公共数据(用封装的可重用代码),如将参数进行转换,传给helloControl的方法中。 -
helloController:其本身可以看成C,DispatcherController是spring提供的通用控制器,而实际处理程序业务的控制器是我们自己写的这个helloController -
helloController内部我们自己写的方法:其里面封装的我们自己写的组件,可以看成JavaBean,即M。 -
success.jsp:V,用于展示数据。
说明:spring不仅提供了 MVC,还对C:Controller进行了更细致的划分。
如在我们@RequestMApping定义的方法中,传入的参数,其类型并不是我们自己转换的,而是DispatcherServlet帮我们转换的。我们自己写的控制器helloController类,只用着重业务层面的逻辑,而不需要考虑转换请求参数的问题,springmvc都帮我们封装好了。
SpringMVC将控制器独立出来,分成了两部分。
好处:各司其职、清晰高效、维护拓展性强。DispatcherController处理公共部分,而我们自己写的helloController只用写纯的业务逻辑。
springmvc流程图:
说明:springmvc框架基于组件的方式执行流程:

SpringMVC 核心组件和三大组件
DispatcherServlet
说明:继承关系:DispatcherServlet <--- 派生 --- FrameworkServlet < -------- HttpServletBean <------
HttpServlet(原始Servlet)
所以DispatcherServlet也是一个Servlet,不过由springmvc精心设计之后,其提供了强大的功能。
源码分析思路:
- 查看 initStrategies 方法:主要是为了查看三大组件的初始化过程。
- 查看 doService 方法:主要是为了查看执行流程。
内部执行过程:
①doService():原始HttpServlet中的Service方法,其会选择调用doGet、doPost等,而doService()会调用doDispatch.
②doDispatch():其随后调用了如下类:
-
RequsetMappingHandlerAdapter:调用了该类的handle()方法,实际方法是:handleInternal() 随后,经过一系列赋值、判断,调用了
doInvoke()中的this.getBridgeMethod().innvoke()方法,其反射创建了一个proxy对象,即我们自己定义的@RequestMapping()方法所在类。 最后,进入到我们自己编写的
@RequestMapping()代码中。
在incovableHandlerMethod类中,其的一个方法,反射调用并执行了我们自己编写的业务逻辑代码,即helloController里面的注释了@RequestMapping的方法。
调用结束并得到返回值(ModelAndView,其包含了请求域和view名),然后在该类中,再执行处理返回值,即调用视图解析器处理 view名 来得到实际的 jsp地址,并返回给浏览器。
拦截后的映射:webapp下的各种信息映射
三大组件
HandlerMapping(处理器映射器)
职责:如其名,职责是负责映射。其中一个职责是:将@RequestMapping注释的方法进行映射,如:"/hello" = com.controller.Hello.myHello()。还包含了很多映射。
说明:我是在DispatcherServlet的initStrategies()中,找到的initHandlerMappings(),并跟进去看了。
其有一个Map<String, HandlerMapping> matchingBeans哈希表,里面存放了各种映射。包含上面提到的注释方法的映射。
HandlerAdapter(处理器适配器)
职责:其是一个接口,职责仅仅是提供一个统一的适配器接口,供DispatcherServlet使用。(主要是三种Controller创建方法)
其实现的接口包括三种自定义控制器的适配器,用于在DispatcherServlet的doDispatch中,三种实现方法适配器的接口整合,doDispatch中可以用接口就处理三种适配器,清晰简单。
InternalResourceViewResolver(视图解析器)
说在前面:其实现在视图解析器比较尴尬了,在以前传统开发中其是很有作用的,但现在的互联网开发中,不使用 .jsp 来显示页面了,而是使用 html 配合AJAX异步请求,浏览器可以直接解析,速度快很多。
传统开发项目:某个企业的办公自动化、某个电商库存。需要用户、密码才能访问,面向群体小。
互联网开发:如现在的淘宝,其不需要用户密码也能访问,而且面向的是所有互联网用户,基量很大。
springmvc 视图:


不需要视图解析器的场景:
不需要页面跳转,即ajax的异步请求,用json数据交互时,即可不配置任何视图解析器。
我们开发的MVC业务代码
说明:springmvc帮我们实现了很多可重用代码的封装,极大程度低提高了我们的开发效率,那我们到底需要编写哪些东西呢?
M
位置:一般位于.../model/下。
职责:关于model模型的设计,其应该在controller之后,其提取出业务代码中的可重用代码,去重并封装成可重用组件JavaBeann,供controller使用。
V
位置:如果是eclipse中,一般位于webapp/ 、webapp/WEB-INF/pages/这两个位置。
职责:提供最终返回并显示的html页面。
C
位置:一般位于.../controller/下。
职责:是url映射的方法入口,当请求一个url,就会到Controller,然后其会调用M、和一些独立代码,来处理请求,并最终返回一个ModelAndView(可能是直接返回、或者是字符串由springmvc封装后返回)。
三种实现方式:
说明:后两种方法都需要在springmvc.xml中配置。
如:
<!-- 为其他两种自定义Controller类进行配置,而非使用@RequestMapping生成。 -->
<!-- implements HttpRequestHandler -->
<bean name="/implHttpRequestHandler" class="com.controller.implHttpRequestHandler"/>
<bean id="httpRequestHandlerApater" class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/>
<!-- implements Controller -->
<bean name="/implController" class="com.controller.implController"></bean>
<bean id="simpleControllerHandlerAdapter" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
-
@RequestMapping注解:
其是最简单的,不需要配置springmvc.xml,直接在我们写的Controller类、方法,进行注解即可。不需要继承或者实现其他接口。
适配器:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
-
implements Controller:
其是实现了
org.springframework.web.servlet.mvc.Controller接口的,我们自定义的Controller类。我们的自定义代码写在一个必须实现的方法:ModelAndView handleRequest(HttpServletRequest request,HttpServletResponse response)中。 但是因为其没有对url和方法的映射,我们需要在springmvc.xml 中配置映射。
适配器:org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
-
implements HttpRequestHandler:
其是实现了
org.springframework.web.HttpRequestHandler接口的,我们自定义类。我们的自定义代码也是要写在一个void handleRequest(HttpServletRequest request,HttpServletResponse response)方法中。 同第二种方式,也需要配置映射。
适配器:org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
SpringMVC 组件
说明:springmvc提供了许多组件,用来支持表现层框架,如上图中的各个组件。HandlerMapping、HandlerApator、ViewResolver称为SpringMVC三大组件。
配置:HandlerMapping、HandlerApator应该是已经被springMVC框架作为组件进行注解注入了,所以在springmvc.xml中,<mvc:annotation-drivern/>可以加载这两个适配器。
注解
@RequestMapping:
作用:建立起浏览器请求url和后端服务器方法的联系。通过@RequestMapping(path="value"),确定该方法用来执行对应path(项目名+包名+类名+value:之类,就是一个url)
当方法所在类也被该注解作用时,索引该方法时:<a href="类path的value/value">..</a>。(<a>在同一项目的html中)
所在类没注解时,索引该方法:<a href="value">..</a>。
源码:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping{...}
属性:
-
path:指定调用该方法的url。 我猜想其实现的效果是将方法变成了一个拟Servlet。
-
value:功能和path一样,是别名。 所以只有一个值时,
path是可以省略的。如:@RequestMapping("hello") -
method:用于规定请求方式。 value为:RequestMethod的枚举类型数组。当被值指定时,只能符合请求方式,如
method="RequestMethod.POST",则只有POST请求才能调用该方法。 -
params:用于规定必要参数 value为:String[]。用于指定该请求的必要参数,当没有传入这些参数时,不能调用该方法。
如某页面中:
<a href="hello">想要索引某方法,该方法的@RequestMapping(value="hello",params={"username","password=123"}),则必须要为
<a href="hello?username=任意&password=123">才可以成功调用执行该方法。 -
header:用于规定包含请求头 value为:String[]。当客户端http请求没有包含该请求头时,则不可以调用。
请求相关
请求参数的绑定
说明:也可以说请求参数的获取。如servlet中getParameter()得到的参数。
1-方法参数对应请求参数:
说明:当某方法是注解了@RequestMapping后,当其有参数时,且与请求的参数同名,调用该方法时,会将请求参数的value,作为实参传入同名参数。
@RequestMapping("/testParams")
public String testParams(String username,String password) {...}
2-对象属性对应请求参数:
说明:首先,该对象应作为实现请求映射注解的方法参数;然后,请求参数应该与该对象的属性名(成员变量名)相同。(如果该对象包含一个对象obj引用,则对应请求参数名应该是obj.paramName)
索引jsp中:
<h3>参数对象绑定测试</h3>
<form action="params/obj2Params" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
其他:<input type="text" name="other"/><br/>
用户名字:<input type="text" name="user.name"/><br/>
用户年龄:<input type="text" name="user.age"/><br/>
提交:<input type="submit"/><br/>
</form>
请求映射类中:(Account包含User引用,其有两个参数name、age)
@RequestMapping("/obj2Params")
public String testParams (Account act) {
System.out.println("testParams调用了");
System.out.println(act);
return "success";
}
2-2对象含有集合变量:
说明:请求映射方法如上;有集合变量也蛮简单,就是将参数按索引得到对象,然后传入参数。
索引 jsp中:
<h3>参数--对象含集合:绑定测试</h3>
<form action="params/obj2Params" method="post">
用户名:<input type="text" name="username"/>
密码:<input type="text" name="password"/>
其他:<input type="text" name="ohter"/>
<p>集合信息:</p>
名字:<input type="text" name="list[0].name"/>
年龄:<input type="text" name="list[0].age"/><br/>
名字:<input type="text" name="list[0].name"/>
年龄:<input type="text" name="list[0].age"/><br/>
名字:<input type="text" name="map['first'].name"/>
年龄:<input type="text" name="map['first'].age"/><br/>
提交:<input type="submit"/>
</form>
解决POST表单中文乱码:
说明:使用Springmvc框架提供的一个过滤器组件:characterEncodingFilter
步骤:
在web.xml中,添加一个全局过滤器,然后初始化参数,设置encoding为utf-8.
<!-- 配置过滤器,处理POST请求中文乱码 -->
<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>
自定义类型转换
说明:spring框架为我们提供了很多的类型转换(http参数--请求映射方法参数),当我们需要拓展进行自定义时,可以使用自定义类型转换。
步骤:
①创建一个类,其实现了 org.springframework.lang.Nullable.Converter接口
②在该类中,实现convert方法,并进行自定义类型转换。源值作为参数source,对其进行处理并将结果值进行返回。
③在springmvc.xml配置文件中,添加转换服务工厂组件,并将我们的自定义类作为其参数传入。
<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="hello.util.String2Date"/>
</set>
</property>
</bean>
④将该工厂组件启用:
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"/>
得到原生的Request、Response
说明:也是使用了springmvc提供的请求参数绑定的方式,在请求映射方法的参数中输入即可,如:
@RequestMapping("originalServlet")
public void originalServlet(HttpServletRequest requset,HttpServletRespond respond){
...
}
常用注解
@RequestParam
作用:用于将http请求参数的name与@RequestMapping方法参数的名字进行映射,如下。
@RequestMapping("annoParam")
public String annoParam(@RequestParam("name") String username) {..}
注意:该注解有一个参数为required,默认为true。意味着当调用该方法,http参数名不匹配 或 缺失 时,会抛异常。
@RequestBody
作用:用于获取请求体内容。
类型:String;
值:name=value&name=value... 结构的数据。所以中文的的话会乱码,因为其是模拟url。多个参数时时,顺序可能会有错乱。
注意:不适用于GET,因为GET请求的参数直接封装在了url中,并没有封装请求体。
@RequestMapping("requestBody")
public String requestBody(@RequestBody String body) {
System.out.println(body);
return "success";
}
@ResponseBody
作用:将Controller返回值:对象、基础数据类型等,封装成JSON数据格式,传递给html,通常用于script、AJAX
注意:使用该注解后,不会使用视图解析器,即不会跳转到.jsp页面,其返回的值就是浏览器页面显示的值。所以只适用于异步更新等请求。
@PathVariable
作用:用于RESTfule编程风格的代码。 解析路径的变量,并赋值给请求映射方法的参数。
如:
@RequestMapping("/restful/{id}")
public String restfulProgramming(@PathVariable("id") int id) {
System.out.println(id);
return "success";
}
@RequestMapping("/restful")
public String restfulProgramming(){
System.out.println("没有参数的同路径restful运行了。");
return "success";
}
@RequestMapping("/restful/{id}/{age}")
public String restfulProgramming(@PathVariable("id") int id,int age) {
System.out.println(id);
return "success";
}
→ [RESTful 风格](#RESTful 风格)
说明:浏览器如果没有插件,是无法实现put、delete等请求方式的,如果需要模拟,则需要浏览器插件、或者自己用spring提供的组件:HiddenHttpMethodFilter类来进行处理。并且表单中,应该要有一个<input type=“hidden" name="_method" value="any">的表项。
@RequestHeader
作用:用于获取请求头的值,并将某个请求头映射到方法参数,如:
@RequestMapping("/requestHeader")
public String requestHeader(@RequestHeader("Accept") String header){
System.out.println("requestHeader运行了。");
System.out.println(header);
return "success";
}
@CookieValue
作用:用于获取Cookie的值,将指定cookie名的值映射到方法参数,如:
@RequestMapping("/getCookie")
public String getCookie(@CookieValue("JSESSIONID") String cookie){
System.out.println("getCookie运行了。");
System.out.println(cookie);
return "success";
}
@ModelAttribute
作用:用于方法或类注解,另其首先执行。若是方法时,则调用该方法所在类的其他方法前,会先执行该方法。
其一个用处是:表单中元素不全时(如封装到一个User对象),则该方法将对User的其他值进行定义。
两种完善方法:
-
方法有返回值:
@ModelAttribute public User modelAttribute(String name,int age) { System.out.println("modelAttribute运行了。"); //模拟数据库进行提取 User user = new User(); user.setAge(age); user.setName(name); user.setDate(new Date()); return user; } -
方法无返回值:使用Map
@ModelAttribute public void modelAttribute(String name,int age,Map<String, User> map) { System.out.println("modelAttribute运行了。"); //模拟数据库进行提取 User user = new User(); user.setAge(age); user.setName(name); user.setDate(new Date()); map.put("姓孙", user); } -----------同时在实际调用方法中-------------------- @RequestMapping("testModelAttribute") public String testModelAttribute(@ModelAttribute("姓孙") User user) {...}
@SessionAttributes
作用:将请求域扩展为会话域,这样同一个浏览器页面以后进行访问时,可以通过${sessionScope}得到该属性的值。因为请求域只是本次请求。
只能是类的注解,其值为:String[]类型。用于将requestScope中的属性,添加到sessionScope中。
如:
@Controller
@RequestMapping("/annoTest")
@SessionAttributes(value= {"keyWords"})
public class AnnoTest {
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Model model) {
System.out.println("testSessionAttribute运行了hh。");
model.addAttribute("keyWords", "i m a good boy");
return "success";
}
@RestController
作用:相当于配置了@RequestBody和@ResponseBody。写在类前,方法可以直接返回字符串。
响应相关
项目:springmvc/springmvcResponse
大致行为:
①当@RequestMapping方法被调用时,给方法添加参数(Model model),用该model对象的addAttribute(key,value)方法加入响应页面的数据(如数据库中的数据)。
②该方法返回到字符串可以指定逻辑视图(view)名,通过视图解析器将其解析为实际返回页面。
③在实际显示页面如respond.jsp中,用EL表达式方法可以获得之前输入model中的数据,并进行封装显示。
@RequestMapping方法返回值分类:
1-字符串:(String)
使用试图解析器,设置其prefix、suffix属性,其分别对应:返回页面路径、返回页面的后缀 (如.jsp)。
2-void
3种方法实现响应:
-
用请求调度器进行转发:
HttpServletRequest.getRequestDispatcher("/WEB_INF/pages/success.jsp").forward(request,response);
路径为:实际路径(视图解析器将不会进行解析)。若不想继续执行该方法后文,则return。
-
用重定向(需要浏览器两次请求):
HttpServletResponse.sendRedirect(request.getContextPath()+"/WEB_INF/pages/xxx.jsp"); -
用respond在方法内直接输出页面:
response.getWriter().print("..."),详细可以看Servlet
@RequestMapping("/voidTest")
public void voidTest(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
System.out.println("voidTest执行了.");
/*这是第一种,传入request、response。并用request的调度器进行转发。*/
//request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
/*第二种方式:用response的sendRedirect进行重定向*/
//response.sendRedirect("/springmvcResponse/success.jsp");
/*第三种方式:直接用response返回页面,输出的内容会被封装到body里面*/
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("<h2>成功</h2>");
return;
}
3-ModelAndView对象:
其和String类似,只是会返回一个ModelAndView的对象。在方法中创建该对象,并且设置View,返回后会由视图解析器进行解析得到实际返回页面的路径。
@RequestMapping("/mvTest")
public ModelAndView mvTest() {
ModelAndView mv= new ModelAndView();
System.out.println("mvTest执行了.");
User user=new User();
user.setName("老孙");
user.setAge(24);
mv.addObject("user1",user);
mv.setViewName("success");
return mv;
}
4-String+关键字:
/**
* 用关键字实现转发,返回的String前置固定的关键字.
* 转发:"forward:..."
* 重定向:"redirect:" (底层会进行项目名前缀封装,可以不用写,但自己写的重定向要写)
* @return
*/
@RequestMapping("/keywordsTest")
public String keywordsTest() {
System.out.println("keywordsTest执行了.");
//关键字实现转发:forward
//return "forward:/WEB-INF/pages/success.jsp";
//关键字实现重定向:redirect
return "redirect:/indexSuccess.jsp";
}
Json相关
需要知识:javaScript
过滤静态资源:
-
Controller中:
使用@RequestBody注解方法参数,如:[异步请求参数为Json 的处理](#异步请求参数为Json 的处理)
使用@ResponseBody注解返回值。
-
View中:(或者说在索引页,调用我们编写的Controller中的JS里)
脚本中使用jQuery,并且用ajax进行异步处理。
添加依赖:
jackson,功能如下图:

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
说明:
当未用`jackson`依赖时,我们需要自己得到一个response.write(),然后将从数据库得到的对象User,手动输出。使用jackson后,就可以让直接返回User对象,传入时也是。只需要在返回值前加注解@ResponseBody
@RequestMapping("handler3")
public @ResponseBody User handler(@RequestBody User user) throws IOException {
System.out.println("handler3执行了.");
user.setName("小孙");
System.out.println(user);
return user;
}
详细:AJAX的处理
Springmvc实现文件上传
传统文件上传
前提**:

依赖:

始--终
始:文件表单编写。
页面的点击,即VIew中的索引页,其应该提供一个文件上传表单,并配置enctype(encoded type):multipart/form-data。
<!-- 文件上传测试 -->
<form action="fileHandler/fileUpload" method="POST" enctype="multipart/form-data">
文件上传:<input type="file" name="fileUpload"/>
<input type="submit" value="上传文件"/>
</form>
终:将上传的文件,存储在磁盘上。
首先是导入文件上传依赖包,commons-fileupload和commons-io。然后在Controller中,请求映射方法的参数为HttpServletRequest。
编写逻辑:从request中得到项目目录、新建一个磁盘文件、将文件放到该文件中。(可以先给文件名加入前缀或后缀以去重。)
@RequestMapping("/fileUpload")
public String fileUpload(HttpServletRequest request) {
System.out.println("fileUpload执行了.");
// 创建一个磁盘文件,用于存放
/* 下面getRealPath默认是项目的目录,其位于 /home/ubuntu/soft/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc01/uploads */
String path = request.getSession().getServletContext().getRealPath("/uploads/");
File uploadDir = new File(path);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 得到请求中的文件表单
// 使用了依赖。
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
//解析request
List<FileItem> items;
try {
items = upload.parseRequest(request);
for(FileItem i:items) {
//判断是否是Item项
if(i.isFormField()){
continue;
}
else {
// 处理文件名、避免重复上传出现同名文件
String fileName =i.getName()+ Math.random();
// 将文件放在磁盘中
i.write(new File(path, fileName));
// 删除临时文件
i.delete();
}
}
} catch (FileUploadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("上传文件解析失败!");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("文件写入磁盘失败!");
}
return "success";
}
Springmvc实现的文件上传
原理图:

Springmvc文件解析器:multipartResolver
职责:可以解析request中的文件,并与@requestMapping方法参数进行绑定。
注意:
- 参数名称与表单中文件的名称必须相同。
- 配置文件中,文件解析器的id,必须是:
multipartResolver
**
* 处理springmvc文件上传操作,依赖了两个jar包。commons-fileupload
* 和commons-io,还需要配置springmvc.xml的视图解析器。
*
* @throws IOException
*/
@RequestMapping("/fileUpload2")
public void fileUpload2(HttpServletRequest request, HttpServletResponse response, MultipartFile upload)
throws IOException {
System.out.println("fileUpload执行了.");
// 创建一个磁盘文件,用于存放
/*
* 下面getRealPath默认是项目的目录,其位于
* /home/ubuntu/soft/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server
* .core/ tmp0/wtpwebapps/springmvc01/uploads
*/
String path = request.getSession().getServletContext().getRealPath("/uploads/");
File uploadDir = new File(path);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 使用MultipartFile得到请求中的文件表单
// 处理文件名、避免重复上传出现同名文件
String fileName = Math.random()+upload.getOriginalFilename() ;
// 将文件放在磁盘中
upload.transferTo(new File(path, fileName));
response.setContentType("text/plain;charset=utf-8");
response.getWriter().write("成功了");
return;
}
跨服务器的文件上传
为何需要跨服务器上传:


始--终
起始:
想测试跨服务器文件上传,首先需要两个服务器。
-
服务器配置:(eclipse)
①window--preference--servers里面,新建一个tomcat即可,但http:port 和 jxm:port要修改。
②新建一个项目,用于启动该服务器。项目不用任何配置,只需要在webapp下新建一个uploads文件夹即可。还可以弄个index.jsp,确认创建成功。
③在项目浏览器中的servers里,找到文件服务器,在其web.xml中添加下述到servlet
<init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param>④启动该文件服务器对应项目。
-
jar依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>1.18.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>1.18.1</version> </dependency> -
Controller
@RequestMapping("/fileUpload3") public void fileUpload3(HttpServletResponse response, MultipartFile upload) throws IOException { System.out.println("跨服务器文件上传执行了."); // 使用MultipartFile得到请求中的文件表单 // 处理文件名、避免重复上传出现同名文件 String fileName = Math.random()+upload.getOriginalFilename() ; //文件服务器路径,记得添加项目名。 String path = "http://localhost:8081/fileServer/uploads/"; //建立一个文件服务器客户端,连接服务器 Client client = Client.create(); //传输文件 WebResource webResource = client.resource(path + fileName); webResource.put(upload.getBytes()); response.setContentType("text/plain;charset=utf-8"); response.getWriter().write("成功了"); return; }
springMVC的异常处理
处理原因:
当出现异常时,如很多网站公司,都会出现一个自定义的比较和谐的报错页面,而不是纯错误信息栈的不友好页面。
如下图是springMVC的异常处理。而当前端控制器:DispatcherServlet不使用异常处理器组件,而是直接抛出异常到浏览器时,浏览器只会打印如同控制台的错误信息。
我们可以通过配置异常处理器组件,来自定义错误返回页面。

流程:
-
首先创建一个自定义的异常类,其继承自Exception,内部至少应该要有一个String msg,用于存储信息。
public class CustomException extends Exception { String msg; public CustomException(String msg) { super(); this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } -
然后创建一个异常处理类,其继承自HandlerExceptionResolver,实现其方法,如下:
public class CustomExceptionResolver implements HandlerExceptionResolver { public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //始:传入一个异常参数ex。 —— 终:最终跳转到一个我们定义的页面。 //判断ex是否为我们自定义的异常类 CustomException custExce =null; if(ex instanceof CustomException ) { //如果是,则强转ex为我们自定义异常类 custExce = (CustomException)ex; } else { //如果不是,则创建我们自定义异常类,并将ex的错误信息传入。 custExce = new CustomException("系统维护~"); } //设置ModelAndView,将错误信息传入请求域,以让提示页面jsp中,得到该信息。 ModelAndView mv = new ModelAndView(); mv.addObject("errorMsg",custExce.getMsg()); //设置返回的view名 mv.setViewName("errorPrompt"); return mv; } } springmvc.xml中配置异常处理类。(注释注入不知为何出错了)
springmvc拦截器
拦截器说明:其类似于servlet中的过滤器Filter,但其不能拦截所有资源,仅拦截我们编写的Controller,而不会去拦截View的请求,如静态html、jsp等资源。

其是AOP思想的具体应用。
多个拦截器
说明:按照配置文件中的前后顺序,确定其拦截顺序。
运行流程示意图:

拦截器创建
- 自定义一个类,其实现了handlerInterceptor.( 该接口没有必须实现的方法)
- 按需重载HandlerInterceptor的方法。(preHandler、postHandler、afterCompletion)
- 配置spingmvc.xml:
- 将拦截器加入容器
- 配置拦截器:
<mvc:interceptor>
/**
* 自定义拦截器,实现了HandlerInterceptor。
* 如下重载方法,其返回值为boolean,代表是否放行
* true为放行,可能会传递给下一个拦截器,或直接到Controller。
* false为不放行,可以用request、response来进行返回页面跳转。
* @author ubuntu
*
*/
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("myHandlerInterceptor拦截器执行了。");
return false;
}
}
springmvc.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="myHandlerInterceptor"
class="com.interceptor.MyHandlerInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="myHandlerInterceptor2"
class="com.interceptor.MyHandlerInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
三个重载函数
boolean preHandler(...)
职责:此方法执行在控制器方法执行之前,所以我们可以使用其对请求部分进行增强,同时因结果视图没创建,所以我们可以指定响应的视图。如访问页面,如果没有登录,则跳转到登录页面。
执行时机:
Controller执行前。
void postHandler(...)
职责:此方法因在视图结果创建生成之前执行,所以通常使用该方法对响应部分进行增强。如压缩视图等待方法。如:将service对象转成JSON数据格式等。
执行时机:
Controller执行后,success.jsp执行前。
void afterCompletion(...)
职责:该方法在结果视图创建省城后,展示到浏览器之前执行。我们可以利用其进行清理操作,释放一些资源。
执行时机:
success.jsp执行后,展示到浏览器之前。
console控制台输出:
说明:在配置文件中,拦截器1在拦截器2之前编写。
myHandlerInterceptor拦截器执行了。。。前111
myHandlerInterceptor拦截器执行了。。。前222
myHello调用了
myHandlerInterceptor拦截器执行了。。。后222
myHandlerInterceptor拦截器执行了。。。后111
success执行了.
myHandlerInterceptor拦截器执行了。。。完成222
myHandlerInterceptor拦截器执行了。。。完成111
DispatcherServlet中的AOP
说明:以ha.handle作为切点,在其前后加入通知。
拦截器数组是在initStrategies()的时候加入的。

SSM整合
环境搭建
项目:ubuntu--eclipse---ssm
思路:Spring框架来整合表现层(springmvc)和持久层(mybatis)
ssm中,每个都应该单独尝试是否成功,再两两进行整合,如spring和springmvc都测试成功,再进行整合,如此当发生错误时,容易及时定位解决。
spring
先搭建好Spring的环境及相关类(Service类和DAO类):spring.md
-
在tomcat的配置文件web.xml中,设置启动时就创建spring的核心容器,原因在于核心容器只需要有一份。
-
原理:
根据ServletContext这个最大的域对象,其生命周期与服务器相同,仅会创建一次,我们建立一个监听它的监听器,然后在监听到其创建,即服务器启动时,就导入spring的配置文件。如下的对象是spring-web为我们提供的API。
-
ContextLoaderListener
通过该spring提供的ServletContext监听器,我们可以很简单的在服务器启动时导入spring配置文件。
-
SpringMVC
- 搭建好环境、springmvc.xml、web.xml
- 测试无误后,进行与spring的整合。所谓的整合,即Spring由服务器启动时,自动创建核心容器,然后可以在Controller中,注入Service类。
mybatis
-
搭建好环境:SqlMapConfig.xml,及dao接口的配置映射文件。(可以是注解、可以是同路径xml配置):
mybatis
-
测试无误后,进行与spring整合。整合即是将dao接口代理类,添加到核心容器中作为bean,并在Service中注入该dao代理对象。
方法:
-
applicationContext.xml中:
<!--配置连接池--> <!--SqlSessionFactory工厂对象--> <!--配置接口所在包-->
-
AJAX的处理
jQuery
始-终:
-
浏览器(准确说是索引文件):在脚本中,设置AJAX,如:
... <scrpit src="..."> $(function(){ $("#btn").click(function(){ $.ajax({ url:"demo_test.txt", data:null, datatype:"json", success:function(result){ alert("成功接收"); }}); }); }); </script> <button id="btn"> ... -
Controller:要有一个控制器专门来处理,其不应该有返回值。
在方法中,应该传入参数response,用以直接返回json格式的数据,以供浏览器中 JS AJAX处理,用来刷新数据。
@Controller @RequestMapping("/ajax0") public class AjaxHandler { @RequestMapping("/handler") public void handler(HttpServletResponse response) throws IOException { System.out.println("handler执行了."); response.setContentType("text/html;charset=utf-8"); response.getWriter().write("{\"username\"=\"老孙\",\"age\"=18}"); return; } }
配置:
如果是用我们放在/js/下面的 JS库,此时应该要配置springmvc.xml,即js的路径映射。
如:
<mvc:resources mapping="/js/**" location="/js/"/>
异步请求参数为Json 的处理
索引文件中:
$.ajax(){}中,配置:contentType:dapplication/json"Controller中:配置注解@RequestBody
springmvc.xml配置文件中:若jQuery库存放在本地,则需要配置映射。webapp下的各种信息映射
@RequestMapping方法对应的url
说明:如下代码,该方法的url为:localhost:8080/项目名/annoTest/annoParam
若方法的注解为("annoParam"),则其路径为localhost:8080/项目名/annoTestannoParam
@RequestMapping("/annoTest")
public class AnnoTest {
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Model model) {
System.out.println("testSessionAttribute运行了hh。");
model.addAttribute("keyWords", "i m a good boy");
return "success";
}
}
------显示页面.jsp的头中,应该取消EL表达式忽略------------
<%@ page language="java" contentType="text/html; charset=UTF-8" isELIgnored="false"
pageEncoding="UTF-8"%>
<a>中的索引url
说明:如下代码,其索引的url是:localhost:8080/项目名/annoTest/annoParam
若加上'/':href="/annoTest/annoParam",则索引变为:localhost:8080/annoTest/annoParam
<a href="annoTest/annoParam">RequestParam</a>
RESTful 风格
全称:Representational State Tranfer
编程风格:
说明:如下图,restfule的前三个方法都可以使用同一个url路径,但他们的请求方式是不同的,按照请求方式就可以调用指定方法。
findall和findById可以通过是否传入参数(格式即user后+占位符:user/{id})来判断调用哪个方法。
优势:restful风格的代码只需要一份缓存,节约空间,而且利于管理。

requestScope
说明:是EL表达式定义的隐式对象,使用方法都是和${}一起。如:${requestScope};值为requestScope(请求域)中的所有属性的键值对。(属性:Attribute指对象,可以是基本对数据对象、自定义类对象)
设已经存储了一个属性:addAttribute("keyWords","i m a handsome boy!");则:
${requesScope.keyWords}可以在客户端浏览器页面输出"i m a handsome boy"
EL表达式
------显示页面.jsp的头中,应该取消EL表达式忽略------------
<%@ page language="java" contentType="text/html; charset=UTF-8" isELIgnored="false"
pageEncoding="UTF-8"%>
结构:${...}
如:${requestScope},输出request域中的所有属性及其值。
${requestScope.user},输出request域中user属性的值。
四种变量作用域:(都使用EL表达式)
下列的对应,都指的是jsp内置对象。其作用就是为了读取值(getAttribute()),读取的是该对象的setAttribute()方法,已设定的属性(对象)的数值。
-
pageScope:表示变量只能在本页面使用。对应:
pageContext -
requestScope:表示变量能在本次请求中使用。对应:
request -
sessionScope:表示变量能在本次会话中使用。
对应:
session -
applicationScope:表示变量能在整个应用程序中使用。
对应:
application
隐式对象:

WEB-INF目录及重定向
大概说明:
WEB-INF里面的内容只能由服务器级别访问,客户端并不能访问。所以当重定向时,不能直接重定向到/WEB-INF/..之类的页面。可以使用:
request.getRequestDispatcher("/WEB-INF/.../").forward(request,respond);进行跳转
两种方式:
一: 转发模式
借助于request范围(请求-->request-->转发-->request),------------>forward()
二: 重定向模式
借助于session范围(请求-->request-->重定向-->新request)------>sendRedirect()
webapp下的各种信息映射
说明:由于我们配置了所有路径的拦截器DispatcherServlet,所以其会进行拦截处理,我们定义的索引.html、js文件夹等都需要需要如下处理:
<!-- 针对ajax异步请求处理,不需要试图解析器,但是要配置index.jsp中索引js库的路径:配置js的映射 -->
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/web/**" location="/web/"/>
错误收集
No mapping for GET
说明:
检查web.xml文件中的
<url-pattern>/</url-pattern>检查springmvc.xml文件中的
<context:component-scan base-package="".."是否正确,检查java文件下的目录。如果不行,关掉eclipse重启试一下!我这样试就好了