1. Spring 集成 Web 环境
弊端: 应用上下文对象是通过 new ClasspathXmlApplicationContext
方式获取的,但是每次从容器中获得 Bean 时都要编写 ,这样的弊端是配置文件加载多次,应用上下文对象创建多次
解决: 在 Web 项目中,可以使用 ServletContextListener 监听 Web 应用的启动,我们可以在 Web 应用启动时,就加载 Spring 的配置文件,创建应用上下文对象 ApplicationContext ,在将其存储到最大的域 ServletContext 域中,这样就可以在任意位置从域中获得应用上下文 ApplicationContext 对象了
故我们需要获取 IOC 容器中的 Bean 时,仅需进行:
- 配置 ContextLoaderListener 监听器
- 使用 WebApplicationContextUtils 获得应用上下文
2. SpringMVC 组件解析
2.1 执行流程
- 用户发送请求至前端控制器
DispatcherServlet
-
DispatcherServlet
收到请求调用HandlerMapping
处理器映射器 - 处理器映射器找到具体的处理器(可以根据 Xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给
DispatcherServlet
-
DispatcherServlet
调用HandlerAdapter
处理器适配器 -
HandlerAdapter
经过适配调用具体的处理器(后端控制器 Controller) -
Controller
执行完成返回ModelAndView
-
HandlerAdapter
将Controller
执行结果ModelAndView
返回给DispatcherServlet
-
DispatcherServlet
将ModelAndView
传给ViewReslover
视图解析器 -
ViewReslover
解析后返回具体View
-
DispatcherServlet
根据View
进行渲染视图(即将模型数据填充至视图中),DispatcherServlet
响应用户
2.2 组件解析
- 前端控制器:
DispatcherServlet
- 用户请求到达前端控制器,它就相当于 MVC 模式中的 C,
DispatcherServlet
是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet
的存在降低了组件之间的耦合性
- 用户请求到达前端控制器,它就相当于 MVC 模式中的 C,
- 处理器映射器:
HandlerMapping
- 根据用户请求找到
Handler
即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等
- 根据用户请求找到
- 处理器适配器:
HandlerAdapter
-
HandlerAdapter
的作用是根据映射器找到的处理器Handler
信息,按照特定的规则去执行相关的处理器Handler
-
- 处理器:
Handler
- 它就是我们开发中要编写的具体业务控制器,由 DispatcherServlet 把用户请求转发到 Handler,由 Handler 对具体的用户请求进行处理
- 视图解析器:
ViewResolver
-
ViewResolver
将处理结果生成View
视图,ViewResolver
首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成View
视图对象,最后对View
进行渲染将处理结果通过页面展示给用户
-
2.3 注解解析
@RequestMapping
作用: 用于建立请求 URL 和处理请求方法之间的对应关系
位置:
- 类上,请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录
- 方法上,请求 URL 的第二级访问目录,与类上的使用
@ReqquestMapping
标注的一级目录一起组成访问虚拟路径
参数:
- value:用于指定请求的 URL
- method:用于指定请求的方式
-
params:用于指定限制请求参数的条件
-
params={"username"}
表示请求参数必须有 username -
params={"age!18"}
表示请求参数 age 不能为 18
-
使用 SpringMVC 注解配置需要引入 mvc 命名空间,同时由于 SpringMVC 基于Spring容器,所以在进行 SpringMVC 操作时,需要将 Controller 存储到 Spring 容器中(Spring 容器与 SpringMVC 容器为父子容器关系,SpringMVC 能访问 Spring 容器内资源,反之不行)
<context:component-scan base-package=“com.example.controller"/>
2.4 XML配置解析
视图解析器:修改前后缀
- prefix:前缀
- suffix:后缀
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
3. SpringMVC 数据响应
3.1 数据响应方式
- 页面跳转
- 直接返回字符串
- 通过 ModelAndView 对象返回
- 回写数据
- 直接返回字符串
- 返回对象或集合
3.2 页面跳转
直接返回字符串: 此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转
返回 ModelAndView 对象:
@RequestMapping("/test")
public ModelAndView testMethod(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:index.jsp");
return modelAndView;
}
@RequestMapping("/test")
public ModelAndView testMethod(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("forward:/WEB-INF/views/index.jsp");
return modelAndView;
}
除了 new ModelAndView
还可以在参数种添加 ModelAndView 让 SpringMVC 自动注入
向 request 域存储数据:
-
通过 SpringMVC 框架注入的 request 对象
setAttribute()
方法设置@RequestMapping("/test") public String testMethod(HttpServletRequest request){ request.setAttribute("name","zhangsan"); return "index"; }
-
通过 ModelAndView 的
addObject()
方法设置@RequestMapping("/test") public ModelAndView testMethod(){ ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("forward:/WEB-INF/views/index.jsp"); modelAndView.addObject("name","lisi"); return modelAndView; }
3.3 回写数据
使用 Response 对象:
通过 SpringMVC 框架注入的 response 对象,使用 response.getWriter().print(“hello world”)
回写数据,此时不需要视图跳转,业务方法返回值为 void
@RequestMapping("/test")
public void testMethod(HttpServletResponse response) throws IOException {
response.getWriter().print("Hello World");
}
直接返回字符串:
将需要回写的字符串直接返回,但此时需要通过 @ResponseBody
注解告知 SpringMVC 框架,方法返回的字符串不是跳转是直接在 http 响应体中返回
@RequestMapping("/test")
@ResponseBody
public String testMethod() throws IOException {
return "Hello SpringMVC";
}
返回对象或集合:
如果我们需要返回一个POJO,我们一般习惯于字符串返回 Json 格式的字符串,此时我们需要使用 jackson 工具进行转换
通过 SpringMVC 帮助我们对对象或集合进行 json字符串的转换并回写,为处理器适配器配置消息转换参数,指定使用 jackson 进行对象或集合的转换,因此需要在 spring-mvc.xml 中进行如下配置:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</list>
</property>
</bean>
配置之后我们只需要这样就可以返回 json 字符串了:
@RequestMapping("/test")
@ResponseBody public User testMethod() throws IOException {
User user = new User();
user.setUsername("思思不羡仙");
user.setAge(18);
return user;
}
在方法上添加 @ResponseBody
就可以返回 json 格式的字符串,但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用 mvc 的注解驱动代替上述配置:
<mvc:annotation-driven/>
4. SpringMVC 获取请求数据
客户端请求参数的格式是:name=value&name=value&name=value
服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC 可以接收如下类型的参数:
- 基本类型参数
- POJO类型参数
- 数组类型参数
- 集合类型参数
4.1 获取基本类型参数
Controller 中的业务方法的参数名称要与请求参数的 name 一致,参数值会自动映射匹配
@RequestMapping("/test")
@ResponseBody
public void testMethod(String username,int age) throws IOException {
System.out.println(username);
System.out.println(age);
}
4.2 获取POJO类型参数
Controller 中的业务方法的 POJO 参数的属性名与请求参数的 name 一致,参数值会自动映射匹配
// 请求地址:http://localhost:8080/test?username=apple&age=18
@Data
public class User {
private String username;
private int age;
}
@RequestMapping("/test")
@ResponseBody
public void testMethod(User user) throws IOException {
System.out.println(user);
}
4.3 获取数组类型参数
Controller 中的业务方法数组名称与请求参数的 name 一致,参数值会自动映射匹配
// 请求地址:http://localhost:8080/test?strs=111&strs=222&strs=333
@RequestMapping("/test")
@ResponseBody
public void testMethod(String[] strs) throws IOException {
System.out.println(Arrays.asList(strs));
}
4.4 获取集合类型参数
获得集合参数时,需要包装到一个 POJO 中才可以,即创建一个 VO 对象,内部有 List<User> userList
成员变量,表单可如下:
<form action="${pageContext.request.contextPath}/test" method="post">
<input type="text" name="userList[0].username"><br>
<input type="text" name="userList[0].age"><br>
<input type="text" name="userList[1].username"><br>
<input type="text" name="userList[1].age"><br>
<input type="submit" value="提交"><br>
</form>
当使用 ajax 提交时,可以指定 contentType 为 json 形式,那么在方法参数位置使用 @RequestBody
可以直接接收集合数据而无需使用 POJO 进行包装
@RequestMapping("/test")
@ResponseBody
public void testMethod(@RequestBody List<User> userList) throws IOException {
System.out.println(userList);
}
释放指定资源:
没有加载到 jquery 文件,原因是 SpringMVC 的前端控制器 DispatcherServlet 的 url-pattern 配置的是 / ,代表对所有的资源都进行过滤操作,我们可以通过以下两种方式指定放行静态资源:
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:default-servlet-handler/>
上述第一种方式需要注意:
-
/
不会拦截页面,只会拦截路径 -
/*
是拦截所有的文件夹的页面,不包含子文件夹 -
/**
是拦截所有的文件夹及里面的子文件夹的页面
4.5 请求数据乱码问题
当 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>
4.6 参数绑定注解
当请求的参数名称与 Controller 的业务方法参数名称不一致时,就需要通过 @RequestParam
注解显示的绑定
<form action="${pageContext.request.contextPath}/test" method="post">
<input type="text" name="name"><br>
<input type="submit" value="提交"><br>
</form>
@RequestMapping("/test")
@ResponseBody
public void testMethod(@RequestParam("name") String username) throws IOException {
System.out.println(username);
}
@RequestParam
的参数:
- value:与请求参数名称
- required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错
- defaultValue:当没有指定请求参数时,则使用指定的默认值赋值
4.7 获取 Restful 风格的参数
Restful 是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等
Restful 风格的请求是使用 [url + 请求方式] 表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:
- GET:用于获取资源
- POST:用于新建资源
- PUT:用于更新资源
- DELETE:用于删除资源
上述 url 地址 /user/hello 中的 hello 就是要获得的请求参数,在 SpringMVC 中可以使用占位符进行参数绑定,地址 /user/hello 可以写成 /user/{id} ,占位符 {id} 对应的就是 hello 的值,在业务方法中我们可以使用 @PathVariable
注解进行占位符的匹配获取工作
// 请求地址:http://localhost:8080/test/zhangsan
@RequestMapping("/test/{name}")
@ResponseBody
public void testMethod(@PathVariable(value = "name",required = true) String name){
System.out.println(name);
}
4.8 自定义类型转换器
SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成 int 型进行参数设置,但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器
自定义类型转换器的开发步骤:
-
定义转换器类实现 Converter 接口
public class DateConverter implements Converter<String,Date>{ @Override public Date convert(String source) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); try { Date date = format.parse(source); return date; } catch (ParseException e) { e.printStackTrace(); } return null; } }
-
在配置文件中声明转换器
<bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.example.converter.DateConverter"/> </list> </property> </bean>
-
在
<annotation-driven>
中引用转换器<mvc:annotation-driven conversion-service="converterService"/>
4.9 获取Servlet相关API
SpringMVC 支持使用原始 ServletAPI 对象作为控制器方法的参数进行注入,常用的对象如下:
- HttpServletRequest
- HttpServletResponse
- HttpSession
4.10 获取请求头
1. @RequestHeader
注解
相当于 request.getHeader(name)
方法,该注解有以下属性:
- value:请求头的名称
- required:是否必须携带此请求头
@RequestMapping("/test")
@ResponseBody
public void testMethod(@RequestHeader(value = "User-Agent",required = false) String headerValue){
System.out.println(headerValue);
}
2. @CookieValue
注解
使用该注解可以获得指定 Cookie 的值,该注解有以下属性:
- value:指定 Cookie 的名称
- required:是否必须携带此 Cookie
@RequestMapping("/test")
@ResponseBody
public void testMethod(@CookieValue(value = "JSESSIONID",required = false) String jsessionid){
System.out.println(jsessionid);
}
4.11 文件上传
文件上传三要素
-
表单项 type = “ file ”
-
表单的提交方式是 post
-
表单的 enctype 属性是多部分表单形式,及 enctype = “ multipart/form-data ”
文件上传原理
-
当 form 表单修改为多部分表单时,
request.getParameter()
将失效 -
enctype = “ application/x-www-form-urlencoded ” 时,form表单的正文内容格式是:key=value&key=value&key=value
-
当 form 表单的 enctype 取值为 mutilpart/form-data 时,请求正文内容就变成多部分形式:
单文件上传步骤
-
导入 commons-fileupload 和 commons-io 坐标
-
配置文件上传解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--上传文件总大小--> <property name="maxUploadSize" value="5242800"/> <!--上传单个文件的大小--> <property name="maxUploadSizePerFile" value="5242800"/> <!--上传文件的编码类型--> <property name="defaultEncoding" value="UTF-8"/> </bean>
-
编写文件上传代码
@RequestMapping("/test") @ResponseBody public void testMethod(String name,MultipartFile uploadFile) throws IOException { //获得文件名称 String originalFilename = uploadFile.getOriginalFilename(); //保存文件 uploadFile.transferTo(new File("C:\\upload\\"+originalFilename)); }
多文件上传步骤
多文件上传,只需要将页面修改为多个文件上传项,将方法参数 MultipartFile 类型修改为 MultipartFile[] 即可
@RequestMapping("/test")
@ResponseBody
public void testMethod(String name,MultipartFile[] uploadFiles) throws IOException {
for (MultipartFile uploadFile : uploadFiles){
String originalFilename = uploadFile.getOriginalFilename();
uploadFile.transferTo(new File("C:\\upload\\"+originalFilename));
}
}