MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分
M:Model,模型层,指工程中的JavaBean,作用是处理数据
JavaBean分为两类:
- 一类称为实体类Bean:专门存储业务数据的,如 Student、User 等
- 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。
V:View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据
C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器
Spring MVC基于Spring的web MVC模块发展而来;底层基于Servlet。有以下三个优点:
- 支持RestFul风格
- spring家族原生产品,与IOC等基础设施无缝对接。
- 支持各种视图技术、支持各种请求资源映射策略。
HandlerMapping、HandlerAdapter、Handler Controller有多种定义方式:
- @Controller
- 实现Controller接口
- 实现Servlet接口定义的Controller
- ...
springmvc提供了多种不同的HandlerMapping实现类,每个实现类都可以根据请求参数(url)查找对应的Handler。这些实现类实例化对象后都放到DispatcherServlet的属性handlerMappings中,以供后续遍历时查找访问。
//入参context是web环境下的ApplicationContext对象,
//默认实现类是AnnotationConfigServletWebServerApplicationContext
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//detectAllHandlerMappings默认是true,
//表示是自动检测,还是只从容器中查找名字为“handlerMapping”的对象
if (this.detectAllHandlerMappings) {
//下面开始自动查找HandlerMapping对象
//下面这一行代码用于从容器中查找类型为HandlerMapping的对象,
//包括父容器也会一并查找,scope=prototype类型的也会搜索到
Map matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);//排序
}
}
else {
try {
//下面是从容器中查找名字为“handlerMapping”且类型为HandlerMapping的对象
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
//如果上述过程没有找到HandlerMapping,那么读取文件DispatcherServlet.properties,
//从该文件中加载HandlerMapping对象
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
从上面代码可以看到springmvc直接从容器中获取HandlerMapping对象添加到属性handlerMappings中。那么容器中的对象从哪来?答案是WebMvcConfigurationSupport。WebMvcConfigurationSupport提供了requestMappingHandlerMapping()、viewControllerHandlerMapping()等创建HandlerMapping的方法。该类一共创建四个,分别是:
- RequestMappingHandlerMapping
- BeanNameUrlHandlerMapping
- RouterFunctionMapping
- resourceHandlerMapping
除了上面四个,还有一个是在EnableWebMvcConfiguration中创建的:
- WelcomePageHandlerMapping
有的HandlerMapping对象,指定了order属性,这个属性用于排序使用,可以看到initHandlerMappings()里面也对这些对象做了排序,排序结果如下:
DispatcherServlet遍历HandlerMapping便是以上图的顺序进行,当从其中一个HandlerMapping中找到合适的Handler,那么后面的便不再遍历,下面是遍历的代码,DispatcherServlet便是调用下面这个方法找到合适的Handler:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;//找到后直接返回
}
}
}
return null;
}
最终HandlerMapping会返回一个handler{ map;其中key是url,value是对应handler(我们的Controller层中的方法)}的执行链HanlerExcutionChain对象。
上面第一个handler是 HanlerExcutionChain对象,第二个handler是我们对应的Controller中方法。
HandlerAdapter :根据Controller层中方法的实现方式找到对应适配器不同的HandlerAdapter处理不同的的Controller实现方式,通过HandlerAdapter去适配当前Controller对应的handler。
Spring MVC拦截器
请求拦截:
- 对congtroller拦截:Spring MVC拦截。
- 对Controller之外Bean的拦截:Spring Aop
- 对静态资源的拦截:Filter
基于注解的拦截器实现
- 实现HandlerIntercepter接口,重写三个方法:
-
preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法。
-
postHandle:控制器方法执行之后执行postHandle()。
-
afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()
-
- 注册拦截器:定义配置类,实现WebMvcConfigurer接口,在接口的addInterceptors方法中注册拦截器、设置拦截路径、设置放行路径。
基于配置的拦截器实现
3、多个拦截器的执行顺序
a>若每个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行
b>若某个拦截器的preHandle()返回了false
preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行
多个拦截器执行顺序
-
若每个拦截器的preHandle()都返回true;此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行。
-
若某个拦截器的preHandle()返回了false;preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行。
转发和重定向 转发(服务端行为) 转发的特点是:
- 地址栏不会改变
- 转发只能转发到当前Web应用内的资源
- 在转发过程中,可以将数据保存到request域对象当中去
- 转发只有一次请求
- 转发是服务器端行为
- 客户端浏览器发送http
- web浏览器接收请求
- 调用内部的一个方法在容器内部完成请求处理和转发工作
需注意的是:转发的路径必须是同一个web容器下的url。在客户端浏览器路径栏显示的仍然是第一次访问的路径。转发行为是浏览器只做了一次访问请求。
重定向(客户端行为) 重定向的特点:- .重定向地址栏会改变
- 重定向可以跳转到当前web应用,甚至是外部域名的网站
- 不能在重定向的过程中,将数据保存到request域对象中
- 客户端浏览器发送http请求
- web服务器接收后,发送302状态码响应,并且发送location给客户端服务器
- .客户端服务器发现是302响应后,则自动发送一个http请求,请求url为重定向的地址,响应的服务器根据此请求寻找资源并发送给客户
- 请求转发是一次请求一次响应,而重定向是两次请求两次响应
- 请求转发地址栏不会改变,而重定向地址栏会显示第二次请求的地址
- 请求转发只能转发给本项目的其他资源,而重定向不仅可以重定向到本项目的其他资源, 还可以重定向到其他项目
- 请求转发是服务器端的行为,转发时只需要给出转发的资源路径即可,如Servlet的访问路径;而重定向需要给出全路径,即路径要包含项目名
- 请求转发比重定向的效率高,因为请求转发是一个请求。在以后的开发中,如果需要地址栏的地址发生改变,就选择重定向;如果需要在Servlet之间通过request域进行数据通信,就选择请求转发
问题:当用转发提交表单后,点击刷新页面,页面会提示表单重复提交。
原因:Servlet处理请求以后,直接转发到目标页面,整个业务只发生一次请求,点击刷新会一直刷新之前的请求。
解决:使用重定向。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)