Spring MVC 笔记
Spring MVC基本概念:
1、DispatcherServlet: 前端控制器
2、Controller: 调用业务逻辑生成model的地方
3、HandlerAdapter:是被dispatcherServlet使用的,dispatcherServlet通过handlerAdapter来调用controller方法,handler其实是dispatcherServlet内部使用的一个类。
4、HandlerInterceptor:拦截器 该接口提供after postHandle preHandle 三个方法,调用controller前后使用
5、HandlerMapping:负责确定DispatcherServelet与controller之间映射的类,告诉DispatcherServelet,在请求到来后,由哪个controller来响应这个请求
6、HandlerExecutionChain: preHandle->Controller method->postHandle->afterCompletion的执行链
7、ModelAndView:model的具体表现
8、viewResolver:视图解析器,决定需要用哪个视图来进行视图的呈现。
9、view:界面
Spring MVC的原理流程
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428103703527-690317125.png)
ModelAndView是springmvc的封装对象,将model和view封装在一起。
一个中心:(不需要开发)
DispatcherServlet前端控制器
三个组件:(不需要开发)
处理器映射器
处理器适配器
视图解析器
Handler:(需要开发,上图的第5步,第6步之间的Handler处理器)
处理器,理解成action
View:
需要开发页面:jsp
springmvc的框架原理
包括哪些组件:
前端控制器(中心)
处理器映射器(三大组件)
作用:根据url找到Handler.
处理器适配器(三大组件)
作用:执行Handler
视图解析器(三大组件)
作用:解析出View,根据逻辑视图名解析出真正的视图
开发springmvc程序的步骤
第一步:配置前端控制器
在web.xml配置DispathcherServlet前端控制器
第二步:配置springmvc的全局配置文件
配置三大组件:
处理器映射器:
根据url查找 Handler
处理器适配器:
执行Handler
视图解析器:
解析出视图View,根据逻辑视图名解析出真正的视图。
第三步:按照处理器适配器规则开发Handler(action)
第四步:将Handler配置在spring容器中。
第五步:编写视图(jsp+jstl)
处理器映射器:
作用:根据url找到Handler.
①形式 BeanNameUrlHandlerMapping
根据url匹配bean的name 处理器映射器实现了HandlerMapping接口
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<!-- 配置action -->
<bean id="hello_controller" name="/helloworld.action" class="cn.itcast.springmvc.Hello" />
②形式 SimpleUrlHandlerMapping
将url进行集中配置
实现了HandlerMapping接口,处理url到bean的映射
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/hello1.action">hello_controller</prop>
<prop key="/hello2.action">hello_controller</prop>
<prop key="/hello3.action">hello_controller3</prop>
</props>
</property>
</bean>
<!-- 配置action -->
<bean id="hello_controller" name="/helloworld.action" class="cn.itcast.springmvc.Hello" />
总结:
多个处理器映射器可以共存
③注解映射器形式
<!--注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
④<mvc:annotation-driven />代替注解映射器
处理器适配器
执行Handler
①形式 SimpleControllerHandlerAdapter
SimpleControllerHandlerAdapter:规则是Handler要实现Controller接口
<bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
public class Hello implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
ModelAndView modelAndView = new ModelAndView();
//向页面显示一行提示信息
//下边的方法就相当于request.setAttribute(arg0, arg1)
modelAndView.addObject("message", "helloworldttt@$@!^&&%9966!!!!");
//指定jsp页面地址
//指定逻辑视图名,真正的视图地址:前缀+逻辑视图名+后缀
modelAndView.setViewName("hello");
return modelAndView;
}
}
②形式 HttpRequestHandlerAdapter
HttpRequestHandlerAdapter:规则是Handler要实现HttpRequestHandler接口。
<!-- 配置HttpRequestHandlerAdapter处理器适配器 -->
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" />
public class Hello1 implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//向页面显示一行提示信息
String message = "hellworld1";
request.setAttribute("message", message);
//指定转向页面,使用request指定页面完整路径
request.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(request, response);
}
}
③ 注解适配器形式
<!--注解适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
④<mvc:annotation-driven />代替注解适配器
mvc的注解驱动
<mvc:annotation-driven />默认注册了注解映射器和注解适配器等bean。
mvc组件扫描
<context:component-scan base-package="springmvc.action" />
扫描@component、@controller、@service、@repository的注解
注意:如果使用组件扫描则controller不需要在springmvc-servlet.xml中配置
视图解析器:
InternalResourceViewResolver
<!-- 视图解析器 解析jsp视图,默认使用jstl,要求classpath下有jstl的jar包 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 视图的前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 视图的后缀 -->
<property name="suffix" value=".jsp" />
</bean>
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428103804977-137494021.png)
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428103812655-1087488921.png)
<mvc:annotation-driven />
是一种简写形式,替换掉适配器、映射器
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428103839512-1394507733.jpg)
<context:component-scan base-package="com.roof" />
自动扫描bean
例子:
<!-- 扫描所有的controller 但是不扫描service -->
<context:component-scan base-package="cn.itcast">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Service" />
</context:component-scan>
org.springframework.context.support.ResourceBundleMessageSource类
注册到bean.xml中,这个类的作用是获取资源文件的内容,注册到IoC的bean.xml文件中是为了自动获得此类的对象
org.springframework.web.multipart.commons.CommonsMultipartResolver
@Entity
标注该类为实体类。
@Table(name = "sys_user")
@Table注释指定了Entity所要映射带的据库表
Controller方法通过形参接收页面传递的参数。
如下:
@RequestMapping("/userlist")
public String userlist(HttpServletRequest request,
HttpServletResponse response,
HttpSession session,
Model model
){
}
默认支持的参数类型
HttpServletRequest
通过request对象获取请求信息
HttpServletResponse
通过response处理响应信息
HttpSession
通过session对象得到session中存放的对象
Model
通过model向页面传递数据,如下:
model.addAttribute("user", new User("李四"));
页面通过${user.XXXX}获取user对象的属性值。
springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。简而言之就是,从把jsp页面的数据,以对象的形式可以在后台获取到。前提是jsp的中的名称和model的名称是一致的。页面上以pojo对象中属性名称命名
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104029934-799053612.png)
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104037069-1085237219.png)
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104043817-84695807.png)
表单对象-pojo
1.1.1 使用pojo属性名传递
页面定义:
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104051098-1862169357.jpg)
Action方法的定义:
在形参使用pojo接收上边的参数。
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104221771-1059460348.jpg)
1.1.2 使用pojo点属性名传递
页面定义:
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104227572-243556087.jpg)
Action方法的定义:
在形参不能直接使用pojo接收上边的参数。
应该使用包装对象接收上边的参数。
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104324095-1928622283.jpg)
Action形参的定义:
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104330919-559372972.jpg)
字符串数组绑定
需求:在学生列表上多选,删除学生信息。
页面定义:
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104432001-2124203219.jpg)
Action方法定义:
使用字符串数组接收。
批量删除学生信息方法,接收页面复选框的值(学生id)
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104437521-648147791.jpg)
List绑定
页面向action传递复杂的批量数据,比如学生的成绩信息(课程名称、成绩)
页面定义:
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104529595-1105064896.png)
Scores:包装对象中list属性的名称
Coursename:包装对象中list中pojo的属性名称。
Score:包装对象中list中pojo的属性名称。
如果上边下标相同的Coursename和Score设置在一个pojo中。
Action方法定义:
使用List<pojo>接收上边的接收,pojo中包括上边课程名称(coursename)和成绩(score)
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104622940-1605789531.jpg)
List通过包装对象接收。
在UserVo包装中定义属性:List<>
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104629146-1057724586.jpg)
Action方法形参使用包装对象接收list
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104634915-244300980.jpg)
@RequestMapping
URL路径映射
@RequestMapping(value="/user")或@RequestMapping("/user")
根路径+子路径
根路径:
@RequestMapping放在类名上边,如下:
@Controller
@RequestMapping("/user")
子路径:
@RequestMapping放在方法名上边,如下:
@RequestMapping("/useradd")
public String useradd(….
URI 模板模式映射
@RequestMapping(value="/useredit/{userId}"):{×××}占位符,请求的URL可以是“/useredit/001”或“/useredit/abc”,通过在方法中使用@PathVariable获取{×××}中的×××变量。
例如:
// 修改学生信息页面
// @RequestMapping指定url
@RequestMapping(value="/editstudent/{id}",method = RequestMethod.GET)
public String editstudent(HttpServletRequest request,Model model,@PathVariable String id) throws Exception {
System.out.println("id="+id);
......
jsp页面上
<td><a href="editstudent/${stu.id }.action">修改</a></td>
实现restFul,所有的url都是一个资源的链接,有利于搜索引擎对网址收录。
多个占位符:
@RequestMapping("/useredit/{groupid}/{userid}")
public String useredit(@PathVariable String groupid,@PathVariable String userid,Model model) throws Exception{
//方法中使用@PathVariable获取useried的值,使用model传回页面
model.addAttribute("groupid", groupid);
model.addAttribute("userid", userid);
return"/user/useredit";
}
请求方法限定
限定GET方法
@RequestMapping(method = RequestMethod.GET)
如果通过Post访问则报错:
HTTP Status 405 - Request method 'POST' not supported
例如:
@RequestMapping(value="/useredit/{userid}",method=RequestMethod.GET)
限定POST方法
@RequestMapping(method = RequestMethod.POST)
如果通过Post访问则报错:
HTTP Status 405 - Request method 'GET' not supported
GET和POST都可以
@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})
@RequestParam绑定单个请求参数
value:参数名字,即入参的请求参数名字,如value=“studentid”表示请求的参数区中的名字为studentid的参数的值将传入;
required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报400错误码;
defaultValue:默认值,表示如果请求中没有同名参数时的默认值
定义如下:
public String userlist( @RequestParam(defaultValue="2",value="group",required=true) String groupid) {=
}
形参名称为groupid,但是这里使用value="group"限定参数名为group,所以页面传递参数的名必须为group。
这里通过required=true限定groupid参数为必需传递,如果不传递则报400错误,由于使用了defaultvalue=”2”默认值即使不传group参数它的值为”2”,所以页面不传递group也不会报错,如果去掉defaultvalue=”2”且定义required=true则如果页面不传递group则会报错。
结果转发
Redirect
//请求重定向
Contrller方法返回结果重定向到一个url地址,如果方式:
return "redirect:/user/userlist.action";
redirect方式相当于“response.sendRedirect()”,转发后浏览器的地址栏变为转发后的地址,因为转发即执行了一个新的request和response。
由于新发起一个request原来的参数在转发时就不能传递到下一个url,如果要传参数可以/user/userlist.action后边加参数,如下:
/user/userlist.action?groupid=2&…..
forward
//页面转发
controller方法执行后继续执行另一个controller方法。
return "forward:/user/userlist.action";
forward方式相当于“request.getRequestDispatcher().forward(request,response)”,转发后浏览器地址栏还是原来的地址。转发并没有执行新的request和response,而是和转发前的请求共用一个request和response。所以转发前请求的参数在转发后仍然可以读取到。
如下例子:
@RequestMapping("/c")
public String c(String groupid,UserVo userVo)throws Exception{
System.out.println("...c...."+groupid+"...user..."+userVo.getUser());
return "forward:/to/d.action";
}
@RequestMapping("/d")
public String d(String groupid,UserVo userVo)throws Exception{
System.out.println("...d...."+groupid+"...user..."+userVo.getUser());
return "success";
}
@RequestBody @ResponseBody实现json数据交互
可以使用@RequestBody将请求的json串转成java对象。
使用@ResponseBody将action方法返回java对象转成json输出。
请求json响应json
![](https://images2018.cnblogs.com/blog/430354/201804/430354-20180428104720268-866256494.jpg)
<!--注解适配器 -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
json转换器
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
</list>
</property>
</bean>
如果使用 了<mvc:annotation-driven />则替换上边定义的处理器映射器和适配器 -->
1 拦截器
拦截器是针对handlerMapping的拦截器,由handlerMapping查找Handler后,将拦截器返回给前端控制器。
1.1 配置拦截器
针对某一个handlerMapping配置拦截器
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
<ref bean="handlerInterceptor2"/>
</list>
</property>
</bean>
<bean id="handlerInterceptor1" class="springmvc.intercapter.HandlerInterceptor1"/>
<bean id="handlerInterceptor2" class="springmvc.intercapter.HandlerInterceptor2"/>
间接配置全局拦截器:让springmvc框架自动向每个handlerMapping中注册拦截器
<!--拦截器 -->
<mvc:interceptors>
<!--多个拦截器,顺序执行 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>//拦截所有请求
<bean class="cn.itcast.springmvc.interceptor.HandlerInterceptor1"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="cn.itcast.springmvc.interceptor.HandlerInterceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
1.2 定义拦截器
实现HandlerInterceptor接口。
public class HandlerInterceptor1 implements HandlerInterceptor {
//handler,springmvc根据url找到Handler(只有一个方法)
//执行时机:进入Handler方法之前执行,如果返回false表示拦截,如果返回true表示放行
//使用场景:用于用户身份校验,用户权限拦截校验
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("HandlerInterceptor1..preHandle");
return false;
}
//执行时机:进入Handler方法之后 ,在返回modelAndView之前
//使用场景:使用modelAndView,向页面传递通用数据,使用统一的view
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("HandlerInterceptor1..postHandle");
}
//执行时机:Handler方法执行完成,(modelAndView已经返回)
//使用场景:统一异常处理,统一记录系统日志,用于action方法执行监控(在preHandle记录一个时间点,在afterCompletion记录执行结束时间点,将结束时间点减去开始执行时间点,得到执行时长)
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("HandlerInterceptor1..afterCompletion");
}
1.3 测试
1.3.1 两个拦截器都放行
HandlerInterceptor1..preHandle
HandlerInterceptor2..preHandle
HandlerInterceptor2..postHandle
HandlerInterceptor1..postHandle
HandlerInterceptor2..afterCompletion
HandlerInterceptor1..afterCompletion
结论:
preHandle是按照拦截器定义顺序执行,
postHandle和afterCompletion是按照拦截器定义逆向执行。
1.3.1 第一个放行第二个不放行
HandlerInterceptor1..preHandle
HandlerInterceptor2..preHandle
HandlerInterceptor1..afterCompletion
结论:
只要有一个拦截器不放行,action方法无法完成。
如果拦截器放行,afterCompletion才会执行。
只要有一个拦截器不放行,postHandle不执行。
1.3.2 两个都不放行
HandlerInterceptor1..preHandle
结论:
只要有一个拦截器不放行,action方法无法完成。
只要有一个拦截器不放行,postHandle不执行。
1.4 拦截器应用
案例:
用户身份认证拦截,用户登陆成功后,系统记录session(用户身份信息),用户去操作url时,拦截器需要校验用户身份是否合法(查看session中是否有用户身份信息,如果没有说明用户身份不合法,不合法重新登陆)
1.4.1 action
@Controller
public class LoginAction {
//登陆页面
@RequestMapping("/login")
public String login(Model model)throws Exception{
return "login";
}
//登陆提交
//userid:用户账号,pwd:密码
@RequestMapping("/loginsubmit")
public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{
//向session记录用户身份信息
session.setAttribute("activeUser", userid);
return "redirect:stu/querystudent.action";
}
//退出
public String logout(HttpSession session)throws Exception{
//session过期
session.invalidate();
return "redirect:stu/querystudent.action";
}
}
1.4.2 页面
@Controller
public class LoginAction {
//登陆页面
@RequestMapping("/login")
public String login(Model model)throws Exception{
return "login";
}
//登陆提交
//userid:用户账号,pwd:密码
@RequestMapping("/loginsubmit")
public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{
//向session记录用户身份信息
session.setAttribute("activeUser", userid);
return "redirect:stu/querystudent.action";
}
//退出
@RequestMapping("/logout")
public String logout(HttpSession session)throws Exception{
//session过期
session.invalidate();
return "redirect:stu/querystudent.action";
}
}
1.4.3 拦截器
拦截所有url(将公开地址排除(无需登陆即可操作的url)),校验用户身份是否合法(查看session中是否有用户身份信息,如果没有说明用户身份不合法,不合法重新登陆)
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//判断请求的url是否公开 地址(无需登陆即可操作url)
//正常开发时,需要将公开地址配置在配置文件中。
//取出请求的url
String url = request.getRequestURI();
if(url.indexOf("loginsubmit.action")>=0){
//说明 公开地址
//放行
return true;
}
//得到session
HttpSession session = request.getSession();
//从 session取出用户身份信息
String userid = (String) session.getAttribute("activeUser");
if(userid!=null){
//说明 用户已登陆(用户身份合法)
//放行
return true;
}
//执行到这里说明 用户身份不合法,拦截,跳转到登陆页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
return false;
}
1.1 拦截器应用
案例:
用户身份认证拦截,用户登陆成功后,系统记录session(用户身份信息),用户去操作url时,拦截器需要校验用户身份是否合法(查看session中是否有用户身份信息,如果没有说明用户身份不合法,不合法重新登陆)
1.1.1 action
@Controller
public class LoginAction {
//登陆页面
@RequestMapping("/login")
public String login(Model model)throws Exception{
return "login";
}
//登陆提交
//userid:用户账号,pwd:密码
@RequestMapping("/loginsubmit")
public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{
//向session记录用户身份信息
session.setAttribute("activeUser", userid);
return "redirect:stu/querystudent.action";
}
//退出
public String logout(HttpSession session)throws Exception{
//session过期
session.invalidate();
return "redirect:stu/querystudent.action";
}
}
1.1.2 页面
@Controller
public class LoginAction {
//登陆页面
@RequestMapping("/login")
public String login(Model model)throws Exception{
return "login";
}
//登陆提交
//userid:用户账号,pwd:密码
@RequestMapping("/loginsubmit")
public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{
//向session记录用户身份信息
session.setAttribute("activeUser", userid);
return "redirect:stu/querystudent.action";
}
//退出
@RequestMapping("/logout")
public String logout(HttpSession session)throws Exception{
//session过期
session.invalidate();
return "redirect:stu/querystudent.action";
}
}
1.1.3 拦截器
拦截所有url(将公开地址排除(无需登陆即可操作的url)),校验用户身份是否合法(查看session中是否有用户身份信息,如果没有说明用户身份不合法,不合法重新登陆)
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//判断请求的url是否公开 地址(无需登陆即可操作url)
//正常开发时,需要将公开地址配置在配置文件中。
//取出请求的url
String url = request.getRequestURI();
if(url.indexOf("loginsubmit.action")>=0){
//说明 公开地址
//放行
return true;
}
//得到session
HttpSession session = request.getSession();
//从 session取出用户身份信息
String userid = (String) session.getAttribute("activeUser");
if(userid!=null){
//说明 用户已登陆(用户身份合法)
//放行
return true;
}
//执行到这里说明 用户身份不合法,拦截,跳转到登陆页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
return false;
}