1. 简介
SpringMVC 是一种基于 Java 的实现 MVC 设计模式的轻量级 Web 框架,属于SpringFrameWork 的 后续产品,已经融合在 Spring Web Flow 中。 SpringMVC 已经成为目前最主流的MVC框架之一,并且随着Spring3.0 的发布,全面超越 Struts2, 成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持 RESTful 编程风格的请求。
SpringMVC的框架就是封装了原来Servlet中的共有行为;例如:参数封装,视图转发等。
2. 入门案例
1、导包
<!--springMVC坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
2、配置servlet
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
理解:
DispatcherServlet
类是SpringMVC提供的一个Servlet。其内部会加载Springmvc配置并构建容器。设置它匹配所有请求,拿到请求后它会在容器中找对应的实例来处理。如此一来我们就不需要编写servlet了,只需要编写bean然后指定用于处理那个请求就可以了。
contextConfigLocation
初始化参数则是指定springmvc配置文件的位置。
servlet相关知识
<load-on-startup>
当其为负数或者不指定时,servlet在首次被访问时加载,当其为0或者正整数时,Tomcat启动时servlet就被加载。其值越小越先被加载。
<url-pattern>
当值为 / 时,表示匹配除 \.jsp之外的所有请求(目前只知道jsp是个例外),而 /\ 则表示匹配所有请求。设置为/\* 时的问题:controller转发到jsp或其他页面时又会被次controller拦截,就会出现404。 ==设置为 / 时:==jsp页面可以正常访问,但HTML等资源的访问依然会被拦截。
3、编写bean作为处理器
@Controller
public class UserController {
@RequestMapping("/quick")
public String quick() {
System.out.println("quick running.....");
return "/WEB-INF/pages/success.jsp";
}
}
@RequestMapping
注解指定匹配请求的路径。
4、编写spring核心配置文件
SpringMVC中配置文件一般命名为spring-mvc.xml
,在其中开启注解扫描即可。
2.1 Tomcat执行流程
2.2 springmvc的执行流程
即DispatcherServlet的执行流程:
1. 用户发送请求至前端控制器DispatcherServlet。
2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4. DispatcherServlet调用HandlerAdapter处理器适配器。
5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6. Controller执行完成返回ModelAndView。
7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9. ViewReslover解析后返回具体View。
10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11. DispatcherServlet将渲染后的视图响应响应用户。
2.3 springMVC 组件
1. 前端控制器:DispatcherServlet
用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。
2. 处理器映射器:HandlerMapping
HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3. 处理器适配器:HandlerAdapter
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4. 处理器:Handler【**开发者编写**】
它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到Handler。由Handler 对具体的用户请求进行处理。
5. 视图解析器:ViewResolver
View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。
6. 视图:View 【**开发者编写**】
SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的视图就是 jsp。一般情况下需要通过
三大组件:处理器映射器、处理器适配器、视图解析器
在spring配置文件中配置 组件
<!--处理器映射器和处理器适配器功能增强-->
<mvc:annotation-driven/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
不配置会使用默认的组件,<mvc:annotation-driven/>
同时配置映射器和适配器,直观用途为增加了对JSON的支持
new一个InternalResourceViewResolver实例放到容器中,就会使用其作为视图适配器,主要用用途是可以通过prefix和suffix设置前后缀,这样在控制器只需返回逻辑视图名即可。注意:prefix
要以 / 开头,否则会以访问的路径的父路径为当前路径进行拼接。
2.4 springmvc配置文件 & @RequestMapping
与spring整合时,springmvc的容器只是作为子容器,注意这不是同一个容器,所以一般springmvc只会管理Controller层的Bean,而由spring管理Service层DAO层的Bean。
@RequestMapping使用
1、此注解可以使用在类上或者方法上,使用在类上为一级访问目录,方法上的为二级访问路径,匹配时两者会结合。
老师说路径都要以 / 开头,但经测试好像 加不加 / 效果一样,但还是都以 / 开头吧,一来保险,二来也顺眼。
2、路径的配置使用的是此注解的value属性[path属性也是一样的],此注解另外还常用method属性和params属性,前者指定访问方式,后者则是指定访问时必须携带的参数。
@RequestMapping(value = "/hello",method = RequestMethod.GET,params = {"aa"})
此注解修饰的方法只能通过get方式访问,访问时必须携带参数 aa
3. 请求参数封装
3.1 对于普通表单数据
springmvc对请求参数的获取进行了分装,不必自行调用getParameter进行获取。
1、将请求参数作为Controllor方法的参数即可,springmvc会根据变量名与参数名进行匹配,并且进行数据类型转化。【参数接受】
2、也可以以实体类为参数,springmvc会将参数按照名称对应装入实体的属性中。【实体类接受】
3、对于复选框这样的情况,spring可以将参数值分装进数组,所以只要指定方法参数时设为数组即可。【数组接受 复选框】
3.2 对于复杂数据类型
<form action="${pageContext.request.contextPath}/user/queryParam" method="post">
搜索关键字:
<input type="text" name="keyword"> <br>
user对象:
<input type="text" name="user.id" placeholder="编号">
<input type="text" name="user.username" placeholder="姓名"><br>
list集合<br>
第一个元素:
<input type="text" name="userList[0].id" placeholder="编号">
<input type="text" name="userList[0].username" placeholder="姓名"><br>
第二个元素:
<input type="text" name="userList[1].id" placeholder="编号">
<input type="text" name="userList[1].username" placeholder="姓名"><br>
map集合<br>
第一个元素:
<input type="text" name="userMap['u1'].id" placeholder="编号">
<input type="text" name="userMap['u1'].username" placeholder="姓名"><br>
第二个元素:
<input type="text" name="userMap['u2'].id" placeholder="编号">
<input type="text" name="userMap['u2'].username" placeholder="姓名"><br>
<input type="submit" value="复杂类型">
</form>
public class QueryVo {
private String keyword;
private User user;
private List<User> userList;
private Map<String, User> userMap;
}
@RequestMapping("/queryParam")
public String queryParam(QueryVo queryVo) {
System.out.println(queryVo);
return "success";
}
注意,其他对象,List Map 这些复杂数据类型,只能以实体类属性的方式进行分装,不能直接出现在Controller参数中。
3.3 解决Post中文乱码
<filter>
<filter-name>encodingFilter</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>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
记住过滤器类名CharacterEncodingFilter
,以及注意指定初始化参数encoding即可。
3.4 自定义类型转换
import org.springframework.core.convert.converter.Converter;
public class DateConverter implements Converter<String,Date> {
@Override
public Date convert(String s) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
return sdf.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
配置:
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="mc.converters.DateConverter"/>
</set>
</property>
</bean>
记忆:
1、实现Converter接口进行自定义转换器,注意接口所属的包,属于springframework并且包含多个convert
2、Convert的两个泛型是指 将什么类型转化为什么类型
3、配置转换器是,是new一个ConversionServiceFactoryBean的实例,并将 自定义类的实例 放到 其属性converters【set集合】中。
4、最后不要忘了要将new除来的ConversionServiceFactoryBean实例指定给annotation-driven才行。
3.5 参数封装相关注解
@RequestParam注解
作用位置:Controller方法的形参上。
注解属性:
name : 指定对应的请求参数,用于解决请求参数名必须与方法参数名同名的缺陷。
required : 是否必须传此参数,默认为true,当指定了defaultValue时默认为false。
defaultValue :若请求没有传此参数时的默认值。
@RequestHeader注解
上述参数封装只能封装请求体中携带的参数。此注解的作用在于将请求头中的数据封装进方法参数中。
作用位置:Controller方法的形参上。
public String doform2(@RequestHeader("Content-Type") String type) { . . . }
@CookieValue注解
作用:获取分装Cookie中的数据
public String doform2(@CookieValue("JSESSIONID") String id) { . . . }
3.6 使用servlet原生对象
如何在Controller方法中使用Servlet中常用的对象呢?如HttpServletRequest等。
直接定义这些类型的参数即可。
public String func(HttpServletRequest request, HttpServletResponse response, HttpSession session){ . . . }
4. 请求响应
4.1 响应分类
页面跳转
- 返回字符串逻辑视图
- void原始ServletAPI
- ModelAndView
返回数据
- 直接返回字符串数据
- 将对象或集合转为json返回
4.2 springMVC方式实现 重定向和转发
重定向和转发
使用原始API可以实现重定向和转发,但是需要植入request和response对象,还要调用方法,较为麻烦;返回逻辑视图也是一种请求转发,但是其受前后缀限制。
通过return 字符串中添加forward关键字进行转发
return "forward:/WEB-INF/pages/success.jsp";
通过return 字符串中添加redirect关键字进行转发
return "redirect:/index.jsp";
==注意:==这里以 / 开头表示应用程序根目录。原生重定向时,/ 表示网站根路径
Model参数
在控制器方法中添加Model类型参数,其作用与request的作用域相似,可通过addAttribute存入参数,在转发后获取。
若是重定向,model中添加的参数会成为二次请求的请求参数。
注意:
- 添加参数的方法是 add 不同于Request
- 好像转发后写的controller无法取出model中的值,但是model中的值在转发时会被写入request作用域中。可以使用request对象获取。
4.3 ModelAndView对象
使用ModelAndView也可以实现转发,可以自己直接new一个ModelAndView的对象,也可以作为Controller方法的参数给出。
调用其addObject(String key,Object o)
方法可以将数据存入model。
调用其setViewName(String name)
方法可以指定逻辑视图名。
然后,return此ModelAndView对象即可。
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("pp","13931154861");
modelAndView.setViewName("success");
return modelAndView;
也可以直接在new对象的时候直接指定视图名。
4.4 @SessionAttributes注解
@SessionAttributes({"name"})
必须标注在Controller类上
作用:可以理解为将model中的指定属性保存到Session作用域,而一般的model中的数据则是被保存进Request作用域。
5. 开启静态资源访问
由于前端控制器的匹配路径设置为了 / ,这样可以对jsp放行,但是html的等静态资源依然会被拦截,如何使这些资源能够正常访问呢?
方式一:
<!--在springmvc配置文件中指定放行资源-->
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/img/**" location="/img/"/>
使用标签mvc:resource
属性名: mapping location
以 / 开头,当前应用为根目录,\\ 表示其下所有内容,包括子目录。
方式二:
<!--在springmvc配置文件中开启DefaultServlet处理静态资源-->
<mvc:default-servlet-handler/>
记忆标签名:mvc:default-servlet-handler
原始servlet开发中,默认就是使用DefaultServlet管理静态资源的。
比较: 方式一配置较为麻烦,但是可以精准控制开放那些资源,方式二配置简单,但是会开放所有资源。
1. Ajax异步交互
Ajax请求一般返回的是JSON数据,springMVC如何处理JSON数据呢?
通过@RequestBody注解将json数据封装进一个对象或者集合中,通过@ResponseBody可以将控制器返回值转换为JSON字符串格式并返回给浏览器。
举例:
@RequestMapping("/ajaxtest")
@ResponseBody
public List<User> ajaxx(@RequestBody List<User> list){ ... }
另外还需要引入JSON解析所需的依赖:
<!--Jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
2. Restfull编程风格
1、简介
Restfull是一种编程风格,这种风格更适用于前后端分离的项目,REST是一种后端API的定义规范,使用该规范可以设计出更加简洁有层次感的API。
2、规范
Restful风格的请求是使用“url+请求方式”表示一次请求目的的。
如:GET方式表示读取 POST方式表示新建 PUT表示更新 DELETE表示删除
如上,相同的url却可以表示不同的功能,
而 /{1} 这样的占位符则允许我们通过url路径传递参数
springmvc对restfull的支持
1、 @GetMapping
@PostMapping
@PutMapping
@DeleteMapping
这些注解相当于@RequestMapping
添加了methen声明。可以对应的匹配各种请求,使用起来更加方便。
2、@PathVariable
此注解用于获取占位符的参数值
3、@RestController
由于一般Rest风格的程序为前后端分离项目,请求的返回值类型一般为JSON。@RestController
注解用于替换@Controller
注解,相当于@Controller和@ResponseBody的结合,是由之后Controller中的所有方法,都会将返回值转化为JSON字符串返回客户端。
4、@CrossOrigin
解决跨域问题,前后端分离项目一般都不会是相同的域。
举例:
@RestController
@CrossOrigin
public class myControllor {
@GetMapping(value = "/rest/{name}",produces = {"text/html;charset=utf-8"})
public String restTest(@PathVariable String name, HttpServletResponse response){
System.out.println("路径参数为:"+name);
return "请求的name为:"+name;
}
}
注意:
Rest只是一种编程风格,而如上这些注解时springmvc提供的功能,这些功能肯定是可用不,不论是否循循rest风格要求。
3. 文件上传
3.1 文件上传三要素
- 表单项 type="file"
- 表单的提交方式 method="POST"
- 表单的enctype属性是多部分表单形式 enctype=“multipart/form-data"
3.2 springmvc中的文件上传
1、springmvc中的文件上传机制依赖于 FileUpload 和 Common-IO工具类。所以要先导包:
<!-- fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.1</version>
</dependency>
<!-- commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.4</version>
</dependency>
2、在容器中放一个CommonsMultipartResolver
对象,并制定一些上传约束
<!--文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设定文件上传的最大值为5MB,5*1024*1024 -->
<property name="maxUploadSize" value="5242880"></property>
<!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 -->
<property name="maxInMemorySize" value="40960"></property>
</bean>
记住类名:CommonsMultipartResolver
3、控制器中接受上传文件
使用MultipartFile类型的参数可以接受上传的文件对象。同样参数名与file表单的name保持一致。若上传的文件有多个,则使用MultipartFilep[]数组即可。
@RestController
public class myControllor {
@PostMapping(value = "/fileupload",produces = {"text/html;charset=utf-8"})
public String restTest(String name, MultipartFile[] files) throws IOException {
System.out.println("名字:"+name);
for (MultipartFile file : files) {
String fileName = file.getOriginalFilename();
file.transferTo(new File("C:\\Users\\默尘\\Desktop\\新建文件夹",fileName));
System.out.println("文件: "+fileName+" 上传成功!");
}
return "上传成功!!";
}
}
记住使用MultipartFile对象接受就行,
getOriginalFilename() 是获取文件名
getName() 是获取表单项name的值,没什么用。
transferTo(File f) 可以拷贝文件
getContentType() 获取文件类型
getSize() 获取文件大小
getInputStream()
getBytes()
方法见名知意。
4. 异常处理机制
4.1 java异常处理
springmvc的异常处理机制要求我们,不必就地处理异常,向上抛出即可,抛出到前端控制器,前端控制器会调用异常处理器进行异常处理。
springmvc提供了默认的异常处理器,但是我们也可以通过实现HandlerExceptionResolver接口自定义异常处理器
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception e) {
ModelAndView mav = new ModelAndView("error");
mav.addObject("err",e.getMessage());
return mav;
}
}
创建好异常处理器后将其放到容器中即可生效。
4.2 错误码跳转页面
将404等错误代码转发到错误显示页面。
<!--处理500异常-->
<error-page>
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>
<!--处理404异常-->
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
注意:配置文件是web.xml,这是属于servlet的知识点。
location必须以 / 开头,表示应用程序根目录,可以配置WEB-INF中的页面,这里是请求转发。但是配置html也可以。
5. 拦截器 interceptor
5.1 简介
Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。 将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(InterceptorChain)。在访问被拦截 的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实 现。
拦截器和过滤器区别:
5.2 编写拦截器
1、编写拦截器
实现HandlerInterceptor
接口即可。
public class interceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("到大处理器前,do something ...");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("处理器执行完,转发到视图前....");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("都执行完了,。。。。");
}
}
2、配置拦截器
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--对哪些资源执行拦截操作-->
<mvc:mapping path="/**"/>
<bean class="com.lagou.interceptor.MyInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
/\\ 按老师将的理解是拦截所有Controller中方法的请求,但是访问JSP时,如果JSP中使用了el表达式,也会被拦截到。目前并不清楚其拦截规则到底是什么?
先记忆吧,interceptor需要xml配置,标签以mvc开头
3、拦截器链
拦截器的执行顺序与配置顺序相关。
当某一个拦截器prehandle返回false时,就会跳转到前一个拦截器的afterCompletion执行。
如2号拦截器的prehandle返回了false : preHandle_1 preHandle_2
afterCompletion_1
如3号拦截器的prehandle返回了false : preHandle_1 preHandle_2
preHandle_3
afterCompletion_2
afterCompletion_1
1. spring整合Mybatis
**第一步:**导入整合JAR包
<!--mybatis整合spring坐标-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--JDBCTemplate-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
理解:
要实现Mybatis与Spring整合就是要做到可以通过注入获取到mapper实例对象。单纯使用Mybatis时我们是通过SqlSession的getMapper手动获取Mapper实例的。现在要实现自动注入,那首先是要把mapper实例放进容器中,mybatis-spring帮助我们扫描mapper包,为其中的每个接口构建实例存入容器。
疑问:
1、为什么要依赖于springJDBC?
**第二步:**编写配置
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="mc.study.entity"/>
<property name="environment" value="sqlMapConfig.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mc.study.mapper"/>
</bean>
记忆:
整合过程就是放置两个对象到容器中,SqlSessionFactoryBean中可以配置MyBatis的相关信息,相当于替换了原来Mybatis的核心配置文件,MapperScannerConfigurer中则是指明要在哪个包下描mapper接口。
注意:
- dataSource 必须要配置。
疑问:
1、为什么DataSource是必须的?纯Mybatis可以使用其他数据源吗?
2、放两个对象进去有什么用?整合后Mapper代理实例的创建是在什么时候?是单例的吗?SQLSession是单例的吗?
2. Spring整合SpringMVC
第一步:导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
第二步:配置监听器
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:ApplicationContext.xml</param-value>
</context-param>
作用:在监听到Tomcat启动时,启动Spring容器。这样就可以在SpringMVC子容器中获取Spring容器中的实例了。
注意:注入对象时,会首先在springmvc容器中找,找不到再去父容器中找,所以配置注解扫描时要注意。