SpringMVC
1、简介
1.1、什么是SpringMVC
-
MVC框架,它解决WEB开发中常见的问题(参数接收、文件上传、表单验证、国际化、等等),而且使用简单,与Spring无缝集成。
-
Spring3.0 后全面超越 Struts2,成为最优秀的 MVC 框架 (更安全,性能更好,更简单)。
-
支持 RESTful风格的 URL 请求 。
-
采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。
2、前端控制器介绍
Front Controller模式要求在WEB应用系统的前端(Front)设置一个入口控制器(Controller),所有的request请求都被发往该控制器统一处理,处理所有请求共同的操作。
学习MVC框架: 都得先配置前端控制器.
3、入门程序HelloWord
3.1、所需依赖
(1) Spring的核心包(core,beans,context,test)
(2) SringMVC框架的依赖(web,webmvc)
(3) servlet-api
3.2、配置前段控制器
<!--配置前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--
1、配置spring配置文件
2、如果不进行配置路径,默认情况下会寻找WEB-INF下面
名为的dispatcherServlet-servlet.xml的配置文件
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<!--
在启动Toncat的时候初始化前端控制器(Servlet)
如果不适用该标签,表示在第一次访问Servlet的时候初始化前端控制器
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3.3、开发配置控制器
public class HelloController implements Controller{
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("Hello SpringMVC!");
return null;
}
}
3.4、前端控制器关联SpringMVC
<!--mvc.xml-->
<!--相当于设置访问路径-->
<bean name="/hello" class="com.ujiuye.springmvc._01_hello.HelloController"></bean>
4、SpringMVC执行流程
4.1、流程图
4.2、流程介绍
-
用户发送出请求到前端控制器DispatcherServlet。
-
DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。
-
HandlerMapping找到具体的控制器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
-
DispatcherServlet调用HandlerAdapter(处理器适配器)。
-
HandlerAdapter经过适配调用具体的控制器(Handler/Controller)。
-
Controller执行完成返回ModelAndView对象。
-
DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。
-
ViewReslover解析后返回具体View(视图)。
-
DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
-
DispatcherServlet响应用户。
4.3、涉及组件分析
1、前端控制器DispatcherServlet(不需要程序员开发),由框架提供,在web.xml中配置。
作用:接收请求,响应结果,相当于转发器,中央处理器。
2、处理器映射器HandlerMapping(不需要程序员开发),由框架提供。
作用:根据请求的url查找Handler(处理器/Controller),可以通过XML和注解方式来映射。
3、处理器适配器HandlerAdapter(不需要程序员开发),由框架提供。
作用:按照特定规则(HandlerAdapter要求的规则)去执行****Controller。
4、处理器Handler(也称之为Controller,需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler。
作用:接受用户请求信息,调用业务方法处理请求,也称之为后端控制器。
5、视图解析器ViewResolver(不需要程序员开发),由框架提供
作用:进行视图解析,把逻辑视图名解析成真正的物理视图。
SpringMVC框架支持多种View视图技术,包括:jstlView、freemarkerView、pdfView等。
6、视图View(需要工程师开发)
作用:把数据展现给用户的页面
View是一个接口,实现类支持不同的View技术(jsp、freemarker、pdf等)
5、静态资源的访问
5.1、引入原因
在web.xml中最后使用"/“拦截所有路径会导致若在webapp下面的静态资源文件无法被找到,因为在Tomcat的web.xml文件中也配置了一个defaultServlet用于拦截静态资源文件,也是使用”/"进行拦截,所以此处被dispatcherServlet覆盖而使用dispatcherServlet方式进行解析,所以找不到。
5.2、具体说明
5.2.1、我们这样的配置有这样一个问题
在Web根路径添加index.html,然后不能访问,原因是什么呢?为什么此时在配置前端控制器的URL模式()写成 / 就不行呢?
5.2.2、原因
Tomcat中处理静态资源访问的servlet(default)的映射路径为****/.
在启动项目的时候,在Tomcat中的web.xml是先加载的,项目的web.xml是后加载的,如果配置了相同的映射路径,后面的会覆盖前者.
也就是说,SpringMVC中的DispatcherServlet的映射路径覆盖了Tomcat默认对静态资源的处理的路径。
如果SpringMVC要配置为/,那么就得设置Dispatcherservlet对静态资源进行支持。
5.3.3、解决方案
- 方法一
需要在SpringMVC的配置文件中添加对静态资源的访问
<mvc:default-servlet-handler/>
mvc:default-servlet-handler/将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 Tomcat默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理
- 方法二
可以把映射路径配置成*.do,表示只有以.do结尾的请求路径,才会被前端控制前处理
5.3.4、“/”和“/*”的区别:
-
"/"会匹配url请求/index等 ,也会匹配静态资源*.js,.html等, 但不会匹配.jsp文件。
-
"/* "会匹配url请求/index等 ,也会匹配静态资源*.js,.html等, 会匹配.jsp文件。如果使用JSP,直接响应JSP代码,没有渲染。
-
所以需要配置成"/"因为他的级别比较低,不会解析JSP
6、注解方式开发
6.1、注意事项
在以后的开发中SpringMVC注解驱动经常需要使用,所以每次使用注解方式开发的时候,最好都加上SpringMVC注解驱动:
<mvc:annotation-driven/>
6.2、开发步骤
-
创建普通的控制器类,并且使用@Controller注解标注,并且需要添加@Controller的注解解析器
-
添加MVC的注解解析器
<mvc:annotation-driven/>
-
在需要访问的访问的方法中标注@RequestMapping注解,并且设置注解的value属性的值.表示可以通多value属性的值,就能访问到被注解标注的方法.
-
虽然@RequestMapping注解还能设置请求方式,但是浏览器只能送get请求,若想要使用Post请求必须通过提交表单的方式
@RequestMapping(value="/list",method=RequestMethod.POST)
- @RequestMapping注解注意事项
- 设置Controller方法的访问URL,不能重名.
- URL路径映射
- 窄化请求映射
- 请求方法限定
7、响应传值方式
7.1、注意
浏览器自动发送第二次请求不能访问到WEB-INF下面的文件,所以不能使用重定向,需要使用转发。
7.2、传值方式
7.2.1、方式一:返回值为void
方式一相当于javaWeb中使用HttpServletRequest和HttpServletResponse两个参数设置值,并使用转发方式到jsp等页面进行展示。
@Controller
public class ResponsetController {
/*
了解:
使用传统的HttpServletRequest和HttpServletResponse对象
重点:直接把HttpServletRequest或者HttpServletResponse对象作为方法的形参,
springmvc就会给对象自动注入值
*/
@RequestMapping("/test1")
public void test1(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws Exception{
request.setAttribute("msg","你好世界!!!&&&&");
//重定向
//response.sendRedirect("/hello.jsp");
//转发
request.getRequestDispatcher("/WEB-INF/views/resp/list.jsp")
.forward(request,response);
System.out.println("test1...");
}
}
7.2.2、方式二:返回值为ModelAndView对象
@Controller
public class ResponsetController {
/*
方式二:返回ModelAndView对象
通过ModelAndView对象.添加共享的数据,设置试图的名称
*/
@RequestMapping("/test2")
public ModelAndView test2(){
//实现页面跳转和共享数据
ModelAndView mv = new ModelAndView();
//共享数据
mv.addObject("msg","你好,师姐!!");
mv.addObject("哈哈哈!!"); //只传递属性的value是,默认的key为属性类型首字母小写
mv.addObject(20); //只传递属性的value是,默认的key为属性类型首字母小写
mv.addObject(new Date()); //只传递属性的value是,默认的key为属性类型首字母小写
mv.addObject("嘿嘿嘿"); //key相同时,后一个value会覆盖前一个value的值
//实现页面跳转
mv.setViewName("/WEB-INF/views/resp/list.jsp"); //转发方式跳转到视图
return mv;
}
}
7.2.3、方式三:返回值字符串类型
@Controller
public class ResponsetController {
/*
方式三:返回值字符串类型
返回的字符串,就是需要拼接前缀和后缀的 视图名称
共享数据是,需要在方法形参中注入一个model对象
*/
@RequestMapping("/test3")
public String test3(Model model) throws Exception{
//共享数据
model.addAttribute("msg","你好,师妹!!");
model.addAttribute("呵呵呵!1");
model.addAttribute(new Date(0));
model.addAttribute(100);
return "resp/list";
}
}
7.2.4、方式四:返回一个对象
@Controller
public class ResponsetController {
/*
方式四:返回一个对象
该对象会直接共享到前台
跳转的视图是 前缀 + 请求的url地址 +后缀
*/
@RequestMapping("/test5")
public User test5(){
User user = new User("rose",18);
return user;
}
/*
forward和redirect:
返回的字符串中遇到两个字符串的任何一个,那么该返回的字符串不再去拼接视图解析器的前缀和后缀
forward:以转发的方式,跳转到对应的视图
redirect:以重定向的方式跳转到对应的视图
*/
@RequestMapping("/test4")
public String test4(Model model) throws Exception{
//共享数据
model.addAttribute("msg","不符合视图解析器的配置");
return "redirect:/hello.jsp";
}
}
7.2.5、配置视图解析器
因为在转发过程中,视图的前缀和后缀是一样的,每次写都会增加代码冗余度,所以可以使用视图解析器进行抽取出来。
<!--
配置视图解析器
抽取共同名称的前缀和后缀
-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
7.2.6、配置视图解析器后,请求路径不符合前缀后缀情况
/*
forward和redirect:
返回的字符串中遇到两个字符串的任何一个,那么该返回的字符串不再去拼接视图解析器的前缀和后缀
forward:以转发的方式,跳转到对应的视图
redirect:以重定向的方式跳转到对应的视图
*/
@RequestMapping("/test4")
public String test4(Model model) throws Exception{
//共享数据
model.addAttribute("msg","不符合视图解析器的配置");
return "redirect:/hello.jsp";
}
7.2.7、返回JSON格式字符串
@RequestMapping("/test6")
public void test6(HttpServletRequest request,HttpServletResponse response) throws Exception{
PrintWriter writer = response.getWriter();
String str = "{\"name\":\"rose\",\"age\":\"18\"}";
writer.write(str);
}
简化版
//导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
@RequestMapping("/test6")
public void test6(HttpServletRequest request,HttpServletResponse response) throws Exception{
PrintWriter writer = response.getWriter();
//注意使用此种方式,对象必须提供Get,Set方法
User user = new User("rose",18);
String str = JSON.toJSONString(user);
writer.write(str);
}
7.2.8※、SpringMVC对JSON的使用
将对象转换为json的方法有很多,可以使用工具类处理
有两种,一种是springMVC底层使用的jackson
另外一个中为阿里出品,fastjson
需要倒入jackson的包,因为Springmvc是使用jackson的方式吧对象转换为json格式的字符串
@ResponseBody可以把方法返回的对象,转换为JSON格式的字符串,响应给方法的访问者
//导入依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.6</version>
</dependency>
@RequestMapping("/test8")
@ResponseBody
public User test8() throws Exception{
return new User("rose",20);
}
8、请求传参的方式
8.1、传值方式
8.1.1、方式一:传统方式
/*
获取前台的请求参数:方式一:
传统的方式,使用request对象
*/
@RequestMapping("/test1")
public ModelAndView test1(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username);
System.out.println(password);
return null;
}
<fieldset>
<legend>请求方式一</legend>
<form action="/test1" method="post">
姓名:<input type="text" name="username"/>
密码:<input type="text" name="password"/>
<input type="submit" value="提交">
</form>
</fieldset>
8.1.2、方式二:简单类型参数和RequestParam注解
/*
对于个数比较少的请求参数,可以直接使用方法的形参接收
默认请求下,要求方法的形参名等于请求参数的参数名
假如请求参数参数名和形参不同时,
可以通过一个@RequestParam(value = "xx")注解制定形参获取那个请求参数的值
被@RequestParam指定的请求参数,在前台进行请求时,必须要有对应的请求参数
*/
@RequestMapping("/test2")
public ModelAndView test2(@RequestParam(value = "username",defaultValue="tom") String username,String password){
System.out.println("==="+username);
System.out.println("==="+password);
return null;
}
<fieldset>
<legend>请求方式二</legend>
<form action="/test2" method="post">
姓名:<input type="text" name="username"/>
密码:<input type="text" name="password"/>
<input type="submit" value="提交">
</form>
</fieldset>
8.1.3、方式三:复合类型
/*
当前台传递的请求参数,大于等于5个时,最好使用引用数据类型接收
SpringMVC会把请求参数的值,自动注入给对象中对应(同名匹配)的属性
作为形参的对象,会自动的放入请求作用域,共享给前台页面
默认的key值为对象类型首字母小写
可以使用@ModelAttribute注解修改默认的key值
*/
@RequestMapping("/test3")
public ModelAndView test3(@ModelAttribute("emp") Employee employee){
System.out.println(employee);
ModelAndView mv = new ModelAndView();
//设置试图页面
mv.setViewName("forward:/hello.jsp");
return mv;
}
<fieldset>
<legend>请求方式三</legend>
<form action="/test3" method="post">
姓名:<input type="text" name="username"/>
密码:<input type="text" name="password"/>
年龄:<input type="text" name="age"/>
<input type="submit" value="提交">
</form>
</fieldset>
8.1.4、方式四:数组和List结合类型参数
/*
当请求参数,出现多个请求参数名相同的时候
使用数组作为方法的形参接收,形参的参数为为请求参数的参数名
还可以使用对象的一个list属性来接收
*/
@RequestMapping("/test4")
public ModelAndView test4(Integer[] id){
for (Integer i : id) {
System.out.println(i);
}
return null;
}
/*
还可以通过将该数据类型封装到类中,然后通过类属性获取
*/
@RequestMapping("/test5")
public ModelAndView test5(Employee employee){
for (Integer integer : employee.getId()) {
System.out.println(integer);
}
return null;
}
//javaBean
@Setter
@ToString
@Getter
public class Employee {
private String username;
private String password;
private Integer age;
private List<Integer> id;
}
<fieldset>
<legend>请求方式五</legend>
<form action="/test5" method="post">
<input type="checkbox" name="id" value="1">
<input type="checkbox" name="id" value="2">
<input type="checkbox" name="id" value="3">
<input type="checkbox" name="id" value="4">
<input type="checkbox" name="id" value="5">
<input type="submit" value="提交">
</form>
</fieldset>
9、RESTful风格参数
9.1、核心代码演示
/*
对于restful风格的请求的支持
实现在路径中,使用{},定义一个变量来接收请求参数,相当于sring id = 10
通过PathVariable注解,获取路径中的变量的值
并且把获取到的值赋值给形式参数
*/
@RequestMapping(value = "/emp/{id}/dept/{deptId}",method = RequestMethod.DELETE)
public ModelAndView delete(@PathVariable("id") Integer id,@PathVariable("deptId") Integer deptId){
//获取到路径当中的参数值
System.out.println(id);
System.out.println(deptId);
return null;
}
10、配置请求编码过滤器(中文乱码问题)
<!--web.xml-->
<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>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping
11、 日期类型的处理
11.1、简述问题原因
SpringMVC能将前端传过来的数据(一般是String类型)自动转换成你要封装的数据类型,若不能转换成功会报类型转换异常,对于请求传参来说,一般作用于请求参数,所以会报400状态码
11.2、解决办法
使用注解@DataTimeFormat(pattern = “yyyy-MM-dd”)
11.2.1、简单数据类型
<fieldset>
<legend>请求方式六</legend>
<form action="/test6" method="post">
<input type="text" name="birthday">
<input type="submit" value="提交">
</form>
</fieldset>
@RequestMapping("/test6")
public ModelAndView test6(@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday){
System.out.println(birthday);
return null;
}
11.2.2、复合数据类型
//javaBean
@Setter
@ToString
@Getter
public class Employee {
private String username;
private String password;
private Integer age;
private List<Integer> id;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
}
@RequestMapping("/test7")
public ModelAndView test7(Employee employee){
System.out.println(employee);
return null;
}
<fieldset>
<legend>请求方式七</legend>
<form action="/test7" method="post">
<input type="text" name="birthday">
<input type="submit" value="提交">
</form>
</fieldset>
12、文件上传
12.1、所需依赖
<!--需要导入fileupload包和io的包-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
12.2、form表单
<!--
文件上传
form:请求方式必须是post
enctype:把form表单的数据已什么形式进行编码
-->
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="pic">
<input type="submit" value="upload">
</form>
12.3、核心方法
@Controller
public class UploadController {
@RequestMapping("/upload")
public ModelAndView upload(MultipartFile pic) throws Exception{
File file = new File("D:/img/"+pic.getOriginalFilename());
pic.transferTo(file);
return null;
}
}
12.4、配置文件上传解析器
<!--
配置文件上传解析器
注意id必须为:multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
13、文件下载
13.1、下载超链接
<a href="/download?name=z.jpg">下载</a>
13.2、依赖
<!--因为java.nio.file.Files在jdk1.6之后才能使用,所以需要改变编译环境-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
13.3、核心代码
@Controller
public class UploadController {
@RequestMapping("/download")
public ModelAndView download(String name, HttpServletResponse response) throws IOException {
response.setContentType("application/x-msdownload");
response.setHeader("content-Disposition","attachment;filename=" + name);
Files.copy(Paths.get("D:/img",name),response.getOutputStream());
return null;
}
}
14、拦截器
14.1、配置两个拦截器类
//实现HandlerInterceptor接口方式
public class MyInterceptor1 implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor1.preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor1.postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor1.afterCompletion");
}
}
//继承HandlerInterceptorAdapter抽象方法形式
public class MyInterceptor2 extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor2.preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor2.postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor2.afterCompletion");
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor2.afterConcurrentHandlingStarted");
}
}
14.2、mvc.xml配置文件
<!--配置拦截器-->
<mvc:interceptors>
<!--配置一个拦截器-->
<mvc:interceptor>
<!--
配置拦截路径
/**:表示拦截所以路径
/*:表示只拦截一级路径
-->
<mvc:mapping path="/**"/>
<!--设置拦截器1不拦截update方法-->
<mvc:exclude-mapping path="/*/update"/>
<!--配置拦截器类-->
<bean class="com.ujiuye.springmvc._06_MyInterceptor.MyInterceptor1"/>
</mvc:interceptor>
<!--
配置一个拦截器
当配置多个拦截器的时候执行顺序是先进后执行,后进先执行
-->
<mvc:interceptor>
<!--配置拦截路径-->
<mvc:mapping path="/*"/>
<!--配置拦截器类-->
<bean class="com.ujiuye.springmvc._06_MyInterceptor.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
onse response, Object handler) throws Exception {
System.out.println(“MyInterceptor2.afterConcurrentHandlingStarted”);
}
}
## 14.2、mvc.xml配置文件
```xml
<!--配置拦截器-->
<mvc:interceptors>
<!--配置一个拦截器-->
<mvc:interceptor>
<!--
配置拦截路径
/**:表示拦截所以路径
/*:表示只拦截一级路径
-->
<mvc:mapping path="/**"/>
<!--设置拦截器1不拦截update方法-->
<mvc:exclude-mapping path="/*/update"/>
<!--配置拦截器类-->
<bean class="com.ujiuye.springmvc._06_MyInterceptor.MyInterceptor1"/>
</mvc:interceptor>
<!--
配置一个拦截器
当配置多个拦截器的时候执行顺序是先进后执行,后进先执行
-->
<mvc:interceptor>
<!--配置拦截路径-->
<mvc:mapping path="/*"/>
<!--配置拦截器类-->
<bean class="com.ujiuye.springmvc._06_MyInterceptor.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>