【SSM框架】SpringMVC笔记 --- 汇总
1、什么是 SpringMVC?
SpringMVC 是基于 MVC 开发模式的框架,用来优化控制器。它是 Spring 家族的一员,它也具备 IOC 和 AOP。
- 什么是MVC?
MVC 是一种开发模式,它是模型视图控制器的简称,所有的 web 应用都是基于 MVC 开发。
1)M:模型层,包含实体类,业务逻辑层,数据访问层
2)V:视图层,html,javaScript,vue 等都是视图层,用来显现数据
3)C:控制器,它是用来接收客户端的请求,并返回响应到客户端的组件,Servlet就是组件
2、SpringMVC 框架的优点?
1)轻量级,基于 MVC 的框架
2)易于上手,容易理解,功能强大
3)它具备 IOC 和 AOP
4)完全基于注解开发
3、Springmvc 的执行流程:
执行流程说明:
1)向服务器发送HTTP请求,请求被前端控制器 DispatcherServlet 捕获。
2)DispatcherServlet 根据<servlet-name>中的配置对请求的URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用 HandlerMapping 获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以 HandlerExecutionChain 对象的形式返回。
3)DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter。
4)提取 Request 中的模型数据,填充 Handler 入参,开始执行 Handler(Controller)。在填充 Handler的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作:
HttpMessageConveter:将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息。
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等。
数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等。
数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中。
5)Handler(Controller)执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象。
6)根据返回的ModelAndView,选择一个适合的 ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet。
7)ViewResolver 结合Model和View,来渲染视图。
8)视图负责将渲染结果返回给客户端
4、基于注解的 SpringMVC 框架开发的步骤:
1)新建项目,选择 webapp 模板
2)修改目录,添加缺失的 test 目录,java目录,resources目录(两套),并修改目录属性
3)修改 pom.xml 文件,添加 SpringMVC 的依赖,添加 Servlet 的依赖
<!--添加springmvc的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--添加servlet的依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
4)添加 springmvc.xml 配置文件,指定包扫描,添加视图解析器.
SpringMVC 框架为了避免请求资源路径与扩展名上的冗余,在视图解析器 InternalResouceViewResolver 中引入了请求的前辍与后辍。而 action 中只需给出要跳转页面的文件名即可,对于具体的文件路径与文件扩展名,视图解析器会自动完成拼接;
<context:component-scan>:用来进行包扫描,这里用于指定@Controller注解所在的包路径。
<!--添加包扫描-->
<context:component-scan base-package="com.bjpowernode.controller"></context:component-scan>
<!--添加视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀-->
<property name="prefix" value="/admin/"></property>
<!--配置后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
5)删除 web.xml 文件,新建 web.xml
6)在 web.xml 文件中注册 springMVC 框架(所有的 web 请求都是基于 servlet 的)
<!--注册SpringMVC框架-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
指定拦截什么样的请求
<a href="${pageContext.request.contextPath}/demo.action">访问服务器</a>
-->
<!--指定拦截以.action结尾的请求,交给核心处理器DispatcherServlet处理。-->
<url-pattern>*.action</url-pattern>
</servlet-mapping>
7)在 webapp 目录下新建 admin 目录,在 admin 目录下新建 main.jsp 页面,删除 index.jsp 页面,并新建
8)开发控制器(Servlet),这是一个普通的类,不用继承和实现接口。类中的每个方法就是一个具体的action控制器。
@Controller //表示当前类为处理器,交给Spring去创建对象
public class DemoAction {
/**
* 以前的Servlet的规范
* protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}
* action中所有的功能实现都是由方法来完成的
* action方法的规范
* 1)访问权限是public
* 2)方法的返回值任意
* 3)方法名称任意
* 4)方法可以没有参数,如果有可是任意类型
* 5)要使用@RequestMapping注解来声明一个访问的路径(名称)
*
*/
@RequestMapping("/demo")
public String demo(){
System.out.println("服务器被访问到了.......");
return "main"; //可以直接跳到/admin/main.jsp页面上
}
}
9)添加 tomcat 进行测试功能
5、分析 web 请求:
web请求执行的流程:
index.jsp<--------------->DispatcherServlet<------------------->SpringMVC的处理器(加了@Controller的类中的方法)
one.jsp <--------------->DispatcherServlet<------------------->SpringMVC的处理器(加了@Controller的类中的方法)
- DispatcherServlet 要在 web.xml 文件中注册才可用
6、@RequestMapping 注解详解:
- 此注解就是来映射服务器访问的路径。@RequestMapping 的 value 属性用于定义所匹配请求的 URI。
1)此注解可加在方法上,是为此方法注册一个可以访问的名称(路径)。
@RequestMapping("/demo")
public String demo(){
System.out.println("服务器被访问到了.......");
return "main"; //可以直接跳到/admin/main.jsp页面上
}
//<a href="${pageContext.request.contextPath}/demo.action">访问服务器</a>
2)此注解可以加在类上,相当于是包名(虚拟路径),区分不同类中相同的 action 的名称。URI 的请求是相对于 Web 的根目录。换个角度说,要访问处理器的指定方法,必须要在方法指定 URI 之前加上处理器类前定义的模块名称。
@RequestMapping("/user")
public class DemoAction1 {..}
//<a href="${pageContext.request.contextPath}/user/demo.action">访问服务器</a>
3)此注解可区分 get 请求和 post 请求:
- 对于@RequestMapping,其有一个属性 method,用于对被注解方法所处理请求的提交方式进行限制,即只有满足该 method 属性指定的提交方式的请求,才会执行该被注解方法。
@Controller
public class ReqAction {
@RequestMapping(value = "/req",method = RequestMethod.GET)
public String req(){
System.out.println("我是处理get请求的........");
return "main";
}
@RequestMapping(value = "/req" ,method = RequestMethod.POST)
public String req1(){
System.out.println("我是处理post请求的........");
return "main";
}
}
7、五种数据提交方式:
- 前四种数据注入的方式,会自动进行类型转换。但无法自动转换日期类型。
1)单个提交数据
- 在方法中声明一个和表单提交的参数名称相同的参数,由框架按照名称直接注入。
栗子:
/*
<form action="${pageContext.request.contextPath}/one.action">
姓名:<input name="myname"><br>
年龄:<input name="age"><br>
<input type="submit" value="提交">
</form>
*/
@RequestMapping("/one")
public String one(String myname,int age){ ===>自动注入,并且类型转换
System.out.println("myname="+myname+",age="+(age+100));
return "main";
}
2)对象封装提交数据:
-
声明一个自定义的实体类参数,框架调用实体类中相应的setter方法注入属性值,只要保证实体类中成员变量的名称与提交请求的name属性值一致即可。
实体类:
public class Users {
private String name;
private int age;
//构造方法,setter方法等省略。。。
}
页面:
<form action="${pageContext.request.contextPath}/two.action" method="post">
姓名:<input name="name"><br>
年龄:<input name="age"><br>
<input type="submit" value="提交">
</form>
action:
@RequestMapping("/two")
public String two(Users u){
System.out.println(u);
return "main";
}
3)动态占位符提交
- 仅限于超链接或地址拦提交数据,它是一杠一值,一杠一大括号,使用注解 @PathVariable 来解析。
//<a href="${pageContext.request.contextPath}/three/张三/22.action">动态提交</a>
@RequestMapping("/three/{uname}/{uage}")
public String three(@PathVariable("uname") //用来解析路径中的请求参数
String name,
@PathVariable("uage")
int age){
System.out.println("name="+name+",age="+(age+100));
return "main";
}
4)映射名称不一致
- 提交请求参数与action方法的形参的名称不一致,使用注解@RequestParam来解析
/**
* 姓名:<input name="name"><br>
* 年龄:<input name="age"><br>
*/
@RequestMapping("/four")
public String four(@RequestParam("name") //专门用来解决名称不一致的问题
String uname,
@RequestParam("age")
int uage){
System.out.println("uname="+uname+",uage="+(uage+100));
return "main";
}
5)使用HttpServletRequest对象提取;
/**
* 姓名:<input name="name"><br>
* 年龄:<input name="age"><br>
*/
@RequestMapping("/five")
public String five(HttpServletRequest request){
String name = request.getParameter("name");
int age = Integer.parseInt(request.getParameter("age"));
System.out.println("name="+name+",age="+(age+100));
return "main";
}
8、中文乱码解决方案:
- 对于前面所接收的请求参数,若含有中文,则会出现中文乱码问题。Spring 对于请求参数中的中文乱码问题,给出了专门的字符集过滤器:
- spring-web-5.2.5.RELEASE.jar 的 org.springframework.web.filter 包下的 CharacterEncodingFilter 类。
解决方案:
- 在 web.xml 中注册字符集过滤器,即可解决 Spring 的请求参数的中文乱码问题。不过,最好将该过滤器注册在其它过滤器之前。因为过滤器的执行是按照其注册顺序进行的。
<filter>
<filter-name>encode</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--
配置参数
private String encoding;
private boolean forceRequestEncoding;
private boolean forceResponseEncoding;
-->
<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>encode</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
9、action方法的返回值:
1)String:
处理器方法返回的字符串可以指定逻辑视图名,通过视图解析器解析可以将其转换为物理视图地址;
可以自动拼接前缀和后缀,可以指定返回的路径;
当然,也可以直接返回资源的物理视图名。不过,此时就不需要再在视图解析器中再配置前辍与后辍了。
2)Object:
返回 json 格式的对象,自动将对象或集合转为 json,使用的 jackson 工具进行转换,必须要添加 jackson 依赖,一般用于 ajax 请求;
处理器方法也可以返回 Object 对象,这个 Object 可以是 Integer,自定义对象,Map,List 等。但返回的对象不是作为逻辑视图出现的,而是作为直接在页面显示的数据出现的。返回对象,需要使用@ResponseBody 注解,将转换后的 JSON 数据放入到响应体中。Ajax请求多用于Object返回值类型。
3)void:
无返回值,一般用于ajax请求;
若处理器对请求处理后,无需跳转到其它任何资源,此时可以让处理器方法返回 void。
4)基本数据类型,用于 ajax 请求。
5)ModelAndView:返回数据和视图对象,现在用的很少。
10、完成 ajax 请求访问服务器,返回学生集合:
1)添加jackson依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
2)在 webapp 目录下新建 js目录,添加 jQuery 函数库
3)在 index.jsp 页面上导入函数库:
4)在 action 上添加注解@ResponseBody,用来处理ajax请求
@Controller
public class AjaxAction {
//处理ajax请求,一定要加@ResponseBody
@ResponseBody
@RequestMapping("/ajax")
public List<Student> ajax(){
Student stu1 = new Student("张三",22);
Student stu2 = new Student("李四",24);
Student stu3 = new Student("王五",23);
List<Student> list = new ArrayList<>();
list.add(stu1);
list.add(stu2);
list.add(stu3);
//调用json转换工具ObjectMapper进行转换
return list; //===>springmvc负责转换成json
}
}
5)在 springmvc.xml 文件中添加注解驱动 <mvc:annotationdriven/>,它用来解析 @ResponseBody 注解
<mvc:annotation-driven></mvc:annotation-driven>
11、SpringMVC 的四种跳转方式:
- 本质就是两种跳转:请求转发和重定向,衍生出四种是请求转发页面,转发action,重定向页面,重定向action;
- 默认的跳转是请求转发,直接跳转到 jsp 页面展示;
可以使用框架提供的关键字 redirect: ,进行一个重定向操作,包括重定向页面和重定向action;
可以使用框架提供的关键字forward:,进行服务器内部转发操作,包括转发页面和转发action;
- 当使用 redirect: 和 forward: 关键字时,视图解析器中前缀后缀的拼接就无效了。
栗子:
@RequestMapping("/one")
public String one(){
System.out.println("这是请求转发页面跳转.........");
return "main"; //默认是请求转发,使用视图解析器拼接前缀后缀进行页面跳转
}
@RequestMapping("/two")
public String two(){
System.out.println("这是请求转发action跳转.........");
// /admin/ /other.action .jsp
//forward: 这组字符串可以屏蔽前缀和后缀的拼接.实现请求转发跳转
return "forward:/other.action"; //默认是请求转发,使用视图解析器拼接前缀后缀进行页面跳转
}
@RequestMapping("/three")
public String three(){
System.out.println("这是重定向页面.......");
//redirect: 这组字符串可以屏蔽前缀和后缀的拼接.实现重定向跳转
return "redirect:/admin/main.jsp";
}
@RequestMapping("/four")
public String four(){
System.out.println("这是重定向action.......");
//redirect: 这组字符串可以屏蔽前缀和后缀的拼接.实现重定向跳转
return "redirect:/other.action";
}
@RequestMapping("/five")
public String five(){
System.out.println("这是随便跳.......");
return "forward:/fore/login.jsp";
}
12、SpringMVC 默认的参数类型:
不需要去创建,直接拿来使用即可。
1)HttpServletRequest
2)HttpServletResponse
3)HttpSession
4)Model
5)Map
6)ModelMap
栗子:
//做一个数据,传到main.jsp页面上
Users u = new Users("张三",22);
//传递数据
request.setAttribute("requestUsers",u);
session.setAttribute("sessionUsers",u);
model.addAttribute("modelUsers",u);
map.put("mapUsers",u);
modelMap.addAttribute("modelMapUsers",u);
注意:Map,Model,ModelMap 和 request 一样,都使用请求作用域进行数据传递,所以服务器端的跳转必须是请求转发,页面才可以得到值。
13.日期处理:
1)日期的提交处理
A、单个日期处理
- 要使用注解@DateTimeFormat,此注解必须搭配 springmvc.xml 文件中的 <mvc:annotationdriven标签>
在方法的参数上使用@DateTimeFormat注解;
在类的成员setXXX()方法上使用@DateTimeFormat注解
- 但这种解决方案要在每个使用日期类型的地方都去添加使用@DateTimeFormat注解,比较麻烦,我们可以使用@InitBinder注解来进行类中统一日期类型的处理。
B.类中全局日期处理
注册一个注解,用来解析本类中所有的日期类型,自动转换
@InitBinder;
public void initBinder(WebDataBinder dataBinder){
dataBinder.registerCustomEditor(Date.class,new CustomDateEditor(sf,true));
}
2)日期的显示处理:
- 在页面上显示好看的日期,必须使用 JSTL
步骤:
A)添加依赖 jstl
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
B)在页面上导入标签库 :
- 如果是单个日期对象,直接转为好看的格式化的字符串进行显示
如果是 list 中的实体类对象的成员变量是日期类型,则必须使用 jstl 进行显示。
<%--导入jstl核心标签库--%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--导入jstl格式化标签库--%>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
C)使用标签显示数据:
<table width="800px" border="1">
<tr>
<th>姓名</th>
<th>生日</th>
</tr>
<c:forEach items="${list}" var="stu">
<tr>
<td>${stu.name}</td>
<td>${stu.birthday}------ <fmt:formatDate value="${stu.birthday}" pattern="yyyy-MM-dd"></fmt:formatDate></td>
</tr>
</c:forEach>
</table>
14、SpringMVC 的拦截器:
- 针对请求和响应进行的额外的处理,在请求和响应的过程中添加预处理,后处理和最终处理
拦截器执行的时机:
1)preHandle():在请求被处理之前进行操作(预处理)
2)postHandle():在请求被处理之后,但结果还没有渲染前进行操作,可以改变响应结果(后处理)
3)afterCompletion:所有的请求响应结束后执行善后工作,清理对象,关闭资源(最终处理)
拦截器实现的两种方式:
1)继承 HandlerInterceptorAdapter 的父类
2)实现 HandlerInterceptor 接口
拦截器实现的步骤:
1)创建登录方法,在 session 中存储用户信息,用于进行权限验证
@RequestMapping("/login")
public String login(String name, String pwd, HttpServletRequest request){
if("zar".equalsIgnoreCase(name) && "123".equalsIgnoreCase(pwd)){
//在session中存储用户信息,用于进行权限验证
request.getSession().setAttribute("users",name);
return "main";
}else{
request.setAttribute("msg","用户名或密码不正确!");
return "login";
}
}
2)开发拦截器的功能,实现 HandlerInterceptor 接口,重写 preHandle() 方法:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//是否登录过的判断
if(request.getSession().getAttribute("users") == null){
//此时就是没有登录,打回到登录页面,并给出提示
request.setAttribute("msg","您还没有登录,请先去登录!");
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
return false;
}
return true;//放行请求
}
}
3)在 springmvc.xml 文件中注册拦截器:
<mvc:interceptors>
<mvc:interceptor>
<!--映射要拦截的请求-->
<mvc:mapping path="/**"/>
<!--设置放行的请求-->
<mvc:exclude-mapping path="/showLogin"></mvc:exclude-mapping>
<mvc:exclude-mapping path="/login"></mvc:exclude-mapping>
<!--配置具体的拦截器实现功能的类-->
<bean class="com.bjpowernode.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
资源在WEB-INF目录下:
很多企业会将动态资源放在WEB-INF目录下,这样可以保证资源的安全性,因为在WEB-INF目录下的动态资源不可以直接访问,必须要通过请求转发的方式进行访问,这样避免了通过地址栏直接对资源的访问,重定向也无法访问动态资源。
栗子:
@Controller
public class WebInfAction {
@RequestMapping("/showMain")
public String showMain(){//通过请求转发的方式访问webapp目录下的WEB-INF目录下的main.jsp
System.out.println("访问main.jsp");
return "main";
}
@RequestMapping("/showLogin")
public String showLogin(){////通过请求转发的方式访问webapp目录下的WEB-INF目录下的login.jsp
System.out.println("访问login.jsp");
return "login";
}
}