一、Spring MVC 的由来
(1)传统方式
在早期 Java Web 的开发中,统一把显示层、控制层、数据层的操作全部交给 JSP 或者 JavaBean 来进行处理。
出现的弊端:
正因为上面的种种弊端,所以很快这种方式就被 Servlet + JSP + Java Bean 所替代了,早期的 MVC 模型就像下图这样:
首先用户的请求会到达 Servlet,然后根据请求调用相应的 Java Bean,并把所有的显示结果交给 JSP 去完成,这样的模式我们就称为 MVC 模式。
(2)Spring MVC
为解决持久层中一直未处理好的数据库事务的编程,又为了迎合 NoSQL 的强势崛起,Spring MVC 给出了方案:
传统的模型层被拆分为了业务层(Service)和数据访问层(DAO,Data Access Object)。 在 Service 下可以通过 Spring 的声明式事务操作数据访问层,而在业务层上还允许我们访问 NoSQL ,这样就能够满足异军突起的 NoSQL 的使用了,它可以大大提高互联网系统的性能。
特点:
二、Spring MVC 概述
1、什么是 Spring MVC
Spring MVC 就是一个 Spring 内置的 MVC 框架。
SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。
spring 是一个一站式的框架,提供了表现层(springmvc)到业务层(spring)再到数据层(springdata)的全套解决方案;spring 的两大核心 IOC (控制反转)和 AOP (面向切面编程)更是给我们的程序解耦和代码的简介提供了支持。
Spring框架图:
从Spring的结构图可以看出,springMVC 位于 spring web 端的一个框架,是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦。附:基于请求驱动指的就是使用请求-响应模型。
spring和SpringMVC是一种父子关系,SpringMVC是spring扩展出的一个应用于 web 端的框架。
spring 主要的作用是黏合其他模块组件,进行统一管理,springmvc 则主要是负责 web 端。我们在应用spring的时候,可以使用注入。这个时候,如果我们的 web 端是用的 SpringMVC,这个时候 controller 理论上是通过 SpringMVC 去注入,但是,使用 spring 注入,同样是可行的。同理,service等层,使用SpringMVC配置的统一扫描装配也是可以的。所以,如果说只是为了使用 spring 的依赖注入,大可不必将 springMVC 和 spring 同时使用的。他们完全可以分开!
尽管 SpringMVC 和 spring 都可以进行自动装配扫描,但是 spring(父容器)并不能直接访问 SpringMVC(子容器)所注入的对象,SpringMVC 却可以访问到 spring 装载的对象
。所以,在配置自动装配的时候,应该注意到这一点。
2、Spring MVC 原理
在没有使用 Spring MVC 之前我们都是使用 Servlet 在做 Web 开发。但是使用Servlet开发在接收请求参数,数据共享,页面跳转等操作相对比较复杂。servlet是java进行web开发的标准,既然springMVC是对servlet的封装,那么很显然SpringMVC底层就是Servlet,SpringMVC就是对Servlet进行深层次的封装。
Spring MVC 框架是以请求为驱动,围绕 Servlet 设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是 DispatcherServlet,它是一个Servlet,顶层是实现的 Servlet 接口。
(1)DispatcherServlet 简介
在整个 Spring MVC 框架中,DispatcherServlet 处于核心位置,它负责协调和组织不同组件完成请求处理并返回响应工作。
DispatcherServlet 是 SpringMVC 统一的入口,所有的请求都通过它。DispatcherServlet 是前端控制器,配置在 web.xml 文件中,Servlet 依自已定义的具体规则拦截匹配的请求,分发到目标 Controller 来处理
。 初始化 DispatcherServlet 时,该框架在 web 应用程序 WEB-INF 目录中寻找一个名为 [servlet-名称]-servlet.xml 的文件,并在那里定义相关的 Beans,重写在全局中定义的任何 Beans。
DispatcherServlet 继承自 HttpServlet,它遵循 Servlet 里的“init-service-destroy”三个阶段:
1)init 阶段:
● DispatcherServlet 的 init() 在其父类 HttpServletBean 中实现的,它覆盖了 GenericServlet 的 init(),主要作用是
加载在 WEB-INF 目录下的 web.xml 中 DispatcherServlet 的 配置,并调用子类的初始化
。@Override public final void init() throws ServletException { try { // ServletConfigPropertyValues 是静态内部类,使用 ServletConfig 获取 web.xml 中配置的参数 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); // 使用 BeanWrapper 来构造 DispatcherServlet BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) {} // 让子类实现的方法,这种在父类定义在子类实现的方式叫做模版方法模式 initServletBean(); }
● 在 HttpServletBean 的 init() 中调用了 initServletBean(),它是在 FrameworkServlet 类中实现的,主要作用是
建立 WebApplicationContext 容器(有时也称上下文),并加载 SpringMVC 配置文件中定义的 Bean 到该容器中,如果在全局上下文中存在相同名字的Bean,则它们将被新定义的同名Bean覆盖,最后将该容器添加到 ServletContext 中
。@Override protected final void initServletBean() throws ServletException { try { // 初始化 WebApplicationContext (即SpringMVC的IOC容器) this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException ex) { } catch (RuntimeException ex) { } }
每个 DispatcherServlet 都持有一个自己的上下文对象 WebApplicationContext,WebApplicationContext 继承自 ApplicationContext,
从容器中可以获取当前应用程序环境信息
;它又继承了根(root)WebApplicationContext对象中已经定义的所有Bean,也是 SpringMVC 的 IOC 容器
。WebApplicationContext 被绑定在 ServletContext 中。
● 建立好 WebApplicationContext(上下文) 后,通过 onRefresh(ApplicationContext context) 回调,进入 DispatcherServlet 类中。onRefresh() 提供 SpringMVC 各个组件的初始化
。@Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); // 从 SpringMVC 的容器及 Spring 的容器中查找所有的 HandlerMapping 实例,并把它们放入到 handlerMappings 这个 list 中。 // 这个方法并不是对 HandlerMapping 实例的创建,HandlerMapping 实例是在上面 WebApplicationContext 容器初始化,即 SpringMVC 容器初始化的时候创建的。 initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
2)处理请求
HttpServlet 提供了 doGet()、doPost() 等方法,DispatcherServlet 中这些方法是在其父类 FrameworkServlet 中实现的,代码如下:
@Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); }
这些方法又都调用了 processRequest(),代码如下:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; // 返回与当前线程相关联的 LocaleContext LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); // 根据请求构建 LocaleContext,公开请求的语言环境为当前语言环境 LocaleContext localeContext = buildLocaleContext(request); // 返回当前绑定到线程的 RequestAttributes RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); // 根据请求构建ServletRequestAttributes ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); // 获取当前请求的 WebAsyncManager,如果没有找到则创建 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); // 使 LocaleContext 和 requestAttributes 关联 initContextHolders(request, localeContext, requestAttributes); try { // 由 DispatcherServlet 实现 doService(request, response); } catch (ServletException ex) { } catch (IOException ex) { } catch (Throwable ex) { } finally { // 重置 LocaleContext 和 requestAttributes,解除关联 resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); }// 发布 ServletRequestHandlerEvent 事件 publishRequestHandledEvent(request, startTime, failureCause); } }
DispatcherServlet 的 doService() 主要是设置一些 request 属性,并调用 doDispatch() 进行请求分发处理,doDispatch() 的主要过程是通过 HandlerMapping 获取 Handler,再找到用于执行它的 HandlerAdapter,执行 Handler 后得到 ModelAndView ,ModelAndView 是连接“业务逻辑层”与“视图展示层”的桥梁,接下来就要通过 ModelAndView 获得 View,再通过它的 Model 对 View 进行渲染
。doDispatch() 如下:protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; // 获取当前请求的WebAsyncManager,如果没找到则创建并与请求关联 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { // 检查是否有 Multipart,有则将请求转换为 Multipart 请求 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 遍历所有的 HandlerMapping 找到与请求对应的 Handler,并将其与一堆拦截器封装到 HandlerExecution 对象中。 mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // 遍历所有的 HandlerAdapter,找到可以处理该 Handler 的 HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 处理 last-modified 请求头 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 遍历拦截器,执行它们的 preHandle() if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // 执行实际的处理程序 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv); // 遍历拦截器,执行它们的 postHandle() mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } // 处理执行结果,是一个 ModelAndView 或 Exception,然后进行渲染 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { } catch (Error err) { } finally { if (asyncManager.isConcurrentHandlingStarted()) { // 遍历拦截器,执行它们的 afterCompletion() mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
(2)DispatcherServlet 的处理流程
配置好DispatcherServlet以后,开始有请求会经过这个DispatcherServlet。此时,DispatcherServlet会依照以下的次序对请求进行处理:
3、SpringMVC 的执行流程
(1)springmvc 的流程
●
用户发送出请求被前端控制器 DispatcherServlet 拦截进行处理
。
● DispatcherServlet 收到请求调用 HandlerMapping(处理器映射器)。
●HandlerMapping 找到具体的处理器(查找xml配置或注解配置),生成处理器对象及处理器拦截器
(如果有),再一起返回给DispatcherServlet。
● DispatcherServlet 调用 HandlerAdapter(处理器适配器)。
●HandlerAdapter 经过适配调用具体的处理器(Handler/Controller)
。
● Controller 执行完成返回 ModelAndView 对象。
●HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet
。
● DispatcherServlet 将 ModelAndView 传给 ViewReslover(视图解析器)。
●ViewReslover 解析 ModelAndView 后返回具体 View(视图)给 DispatcherServlet
。
●DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)
。
● DispatcherServlet 响应用户返回视图。
(2)组件
1)前端控制器 DispatcherServlet(不需要开发者开发,由框架提供)
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性
。(2)处理器映射器 HandlerMapping(不需要开发者开发,由框架提供)
作用:根据请求的 url 查找 Handler
HandlerMapping 负责根据用户请求找到 Handler 即处理器
,springmvc 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。(3)处理器适配器 HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行 Handler
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行
。(4)处理器 Handler(需要开发者开发)
注意:编写 Handler 时按照 HandlerAdapter 的要求去做,这样适配器才可以去正确执行 Handler
Handler 是继 DispatcherServlet 前端控制器的后端控制器,在 DispatcherServlet 的控制下 Handler 对具体的用户请求进行处理
。
由于 Handler 涉及到具体的用户业务请求,所以一般情况需要开发者根据业务需求开发Handler。(5)视图解析器View resolver (不需要开发者开发,由框架提供)
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户
。
springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由开发者根据业务需求开发具体的页面。(6)视图 View(需要开发者开发)
View 是一个接口,实现类支持不同的 View 类型(jsp、freemarker、pdf等)
根据以上分析,SpringMVC需要程序员完成的工作有三个:
三、Spring MVC 开发
1、添加依赖
(1)添加 spring-webmvc 依赖
webmvc 依赖包含 beans、context、core、expression、commons-logging、aop、web、webmvc。导入一个 webmvc 依赖,就间接导入了启动 mvc 框架的所有依赖。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
(2)添加 servlet 依赖
因为 springmvc 底层还是 servlet,所以还必须添加 serlvet 依赖。
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
2、web.xml 配置前端控制器 DispatcherServlet
在 web.xml 文件中配置 spring mvc 前端控制器,并加载 springmvc 配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!--配置 DispatcherServlet-->
<servlet>
<servlet-name>springMvc</servlet-name>
<!--总控本质上还是一个Servlet,因为SpringMVC底层就是使用Servlet编写的-->
<!--配置前端控制器-->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 创建前端控制器的时候读取springmvc配置文件启动ioc容器 -->
<!-- 此处可以加载多个配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 默认启动路径,Tomcat启动就创建此对象 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置DispatcherServlet 拦截路径url -->
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<!-- /* 代表所有的请求都会进入到前端控制器中,包含jsp
/ 代表所有的请求都会进入到前端控制器中,不包含jsp
*.itcsdn 代表以 .itcsdn结尾的请求都会进入到前端控制器中-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置文件详解:
(1) <display-name></display-name>
<display-name>test-hwp-web-application</display-name> 定义了web应用的名称.
(2)<context-param></context-param>
<!--****************************上下文初始化参数***************************--> <context-param> <param-name>webAppRootKey</param-name> <param-value>business.root</param-value> </context-param> <!-- spring config --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-configuration/*.xml</param-value> </context-param>
<context-param></context-param> 用来声明应用范围(整个WEB项目)内的上下文初始化参数,通常用来放置配置文件。 <context-param>元素含有一对参数名和参数值,
为 Servlet 上下文初始化参数,参数名在整个Web应用中必须是惟一的,在web应用的整个生命周期中上下文初始化参数都存在,任意的Servlet和jsp都可以随时随地访问它
。<param-name>子元素包含有参数名,而<param-value>子元素包含的是参数值。代码中使用 context-param 的值:
(3)<listener>
<!--****************************监听器配置*********************************--> <!-- Spring的log4j监听器 --> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 与CAS Single Sign Out Filter配合,注销登录信息 --> <listener> <listener-class>com.yonyou.mcloud.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener>
<listener> 为 web 应用程序定义监听器,监听器用来
监听各种事件
,比如:application和session事件,所有的监听器按照相同的方式定义,功能取决去它们各自实现的接口。常用的Web事件接口有如下几个:
<listener> 主要用于监听Web应用事件,其中有两个比较重要的Web应用事件:Application的启动和停止(starting up or shutting down)和Session的创建和失效(created or destroyed)。Application启动事件发生在应用第一次被Servlet容器装载和启动的时候;停止事件发生在Web应用停止的时候。Session创建事件发生在每次一个新的Session创建的时候,类似地Session失效事件发生在每次一个Session失效的时候。为了使用这些Web应用事件做些有用的事情,我们必须创建和使用一些特殊的“监听类”。它们是实现了以下两个接口中任何一个接口的简单java类:javax.servlet.ServletContextListener或javax.servlet.http.HttpSessionListener,如果想让你的类监听Application的启动和停止事件,你就得实现ServletContextListener接口;想让你的类去监听Session的创建和失效事件,那你就得实现HttpSessionListener接口。配置 listener 方式:
配置 Listener 只要向 Web 应用注册 Listener 实现类即可,无需配置参数之类的东西
,因为Listener获取的是Web应用ServletContext(Application)的配置参数。为Web应用配置Listener的两种方式:
我们选择web.xml这种配置方式,该方式有两种方法:ContextLoaderListener 和 ContextLoaderServlet。若使用 ContextLoaderServlet 则当 <filter> 需要用到 bean 时,加载顺序是先加载 <filter> 后加载 <servlet>,则 <filter> 中初始化操作中的 bean 为 null。所以使用 ContextLoaderListener 方式
。<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
在 J2EE 工程中
web 服务器启动的时候最先调用 web.xml,上面这段配置的意思是加载 spring 的监听器,其中 ContextLoaderListener 的作用就是启动 Web 容器时,自动装配 applicationContext.xml 的配置信息,执行它所实现的方法。(执行顺序:监听器 > 过滤器 > 拦截器)
(4)Spring 配置文件
配置 Spring 必须需要<listener>,而<context-param>可有可无。
如果在 web.xml 中不写 <context-param>配置信息,默认的路径是 /WEB-INF/applicationontext.xml,在 WEB-INF 目录下创建的 xml 文件的名称必须是 applicationContext.xml
。
如果是要自定义文件名可以在 web.xml 里加入contextConfigLocation 这个context参数:在 <param-value></param-value> 里指定相应的 xml 文件名,如果有多个 xml 文件,可以写在一起并以“,”号分隔
。<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:applicationContext*.xml,</param-value> </context-param>
部署在同一容器中的多个 Web 项目,要配置不同的 webAppRootKey
,web.xml 文件中最好定义 webAppRootKey 参数,如果不定义,将会缺省为“webapp.root”,如下:<!-- 应用路径 --> <context-param> <param-name>webAppRootKey</param-name> <param-value>webapp.root</param-value> </context-param>
当然也不能重复,否则会报错误。
多个项目要对 webAppRootKey 进行配置。比如我们工程主要是让log4j能将日志写到对应项目根目录下,我们的项目的 webAppRootKey 为<!--business-client应用路径 --> <context-param> <param-name>webAppRootKey</param-name> <param-value>business.root</param-value> </context-param> <!--public-base应用路径 --> <context-param> <param-name>webAppRootKey</param-name> <param-value>pubbase.root</param-value> </context-param>
这样就不会出现冲突了。就可以在运行时动态地找到项目路径,在log4j.properties配置文件中可以按下面的方式使用${webapp.root}:log4j.appender.file.File = ${webapp.root}/WEB-INF/logs/sample.log,就可以在运行时动态地找出项目的路径。
(5)多个配置文件引用
如果web.xml中有contextConfigLocation参数指定的Spring配置文件,则会去加载相应的配置文件,而不会去加载/WEB-INF/下>>的applicationContext.xml。但是如果没有指定的话,默认会去/WEB-INF/下加载applicationContext.xml。
在一个团队使用Spring的实际项目中,应该需要多个Spring的配置文件,如何使用和交叉引用的问题,多个配置文件可以在>>web.xml里用逗号分隔写入,<context-param> <param-name>contextConfigLocation</param-name> <param-value>applicationContext-database.xml,applicationContext.xml</param-value> </context-param>
(6)<session-config></session-config>
<session-config> 用于
设置容器的session参数
,比如:<session-timeout> 用于指定http session的失效时间。<session-timeout>用来指定默认的会话超时时间间隔,以分钟为单位。该元素值必须为整数。如果 session-timeout 元素的值为零或负数,则表示会话将永远不会超时。(7) <filter></filter>
过滤器配置
(8)<welcome-file-list></welcome-file-list>
<welcome-file-list>包含一个子元素<welcome-file>,
\<welcome-file>用来指定首页文件名称
。
<welcome-file-list>元素可以包含一个或多个<welcome-file>子元素。如果在第一个<welcome-file>元素中没有找到指定的文件,Web容器就会尝试显示第二个,以此类推。<welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list>
web.xml 加载过程:
●
启动WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml,读两个节点::<listener></listener> 和 <context-param></context-param>;
●
容器创建一个 ServletContext(上下文),这个WEB项目所有部分都将共享这个上下文
;●
容器将<context-param></context-param>转化为键值对,并交给ServletContext
;● 容器创建<listener></listener>中的类实例,即创建监听;
● 在监听中会有contextInitialized(ServletContextEvent args)初始化方法,在这个方法中获得:
ServletContext = ServletContextEvent.getServletContext(); context-param的值 = ServletContext.getInitParameter("context-param的键");
● 得到这个context-param的值之后,你就可以做一些操作了。注意,这个时候你的WEB项目还没有完全启动完成,这个动作会比所有的Servlet都要早,这个时候你对<context-param>中的键值做的操作将在你的WEB项目完全启动之前被执行。
从上图中可以看出:●
ContextLoaderListener 初始化的上下文加载的 Bean 是对于整个应用程序共享的
,不管是使用什么表现层技术,一般如 DAO 层、Service 层 Bean;
●DispatcherServlet 初始化的上下文加载的 Bean 是只对 Spring Web MVC 有效的 Bean,如 Controller、HandlerMapping、HandlerAdapter 等等
,该初始化上下文应该只加载 Web 相关组件。由上面的初始化过程可知
容器对于 web.xml 的加载过程是: context-param > listener > fileter > servlet
。
3、springmvc.xml 配置
在 springmvc.xml 中配置包扫描、注解支持和视图解析器
等。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--包扫描-->
<context:component-scan base-package="work"/>
<!--<!-- 配置MVC注解扫描支持 -->-->
<mvc:annotation-driven/>
<!-- 配置视图解析器(可写可不写)-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/jsps/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"/>
<property name="maxUploadSize" value="#{3171028}"/>
</bean>
</beans>
4、spring MVC 常用注解
5、Spring MVC 拦截器
Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(InterceptorChain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用
。拦截器也是AOP思想的具体实现
。
在实际项目中会经常使用到拦截器,例如在购物网站中通过拦截器可以拦截未登录的用户,禁止其购买商品,或者使用它来验证已登录用户是否有相应的操作权限(即权限验证),记录请求信息的日志等应用。
所谓拦截器,就是能够
在进行某个操作之前拦截请求,如果请求符合条件就允许在往下执行
。比如说,海关就是一个拦截器,他拦截进出口的货物,如果货物满足进出口条件,则放行,否则就拦截,退回处理。
(1)拦截器和过滤器区别
(2)拦截器的定义
在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,定义一个拦截器可以通过两种方式:一种是通过
实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类
来定义;另一种是通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类
来定义。
以实现 HandleInterceptor 接口为例
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle方法在控制器的处理请求方法调用之后,解析视图之前执行");
return false;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle方法在控制器的处理请求方法调用之后,解析视图之前执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("afterCompletion方法在控制器的处理请求方法执行完成后执行,即视图渲染结束之后执行");
}
}
从以上可以看出,自定义的拦截器类实现了 HandlerInterceptor 接口,并且实现了接口中的三个方法。对这三个方法解释如下:
● preHandle()方法:预处理回调方法,实现处理器的预处理(如登录检查)。
返回值为 true 表示继续流程(如调用下一个拦截器或处理器),false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过 response 来产生响应
;
● postHandle() 方法:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过此方法对对请求域中的模型数据或视图进行处理
,modelAndView 也可能为 null。
● afterCompletion() 方法:整个请求处理完毕回调方法,即在视图渲染完毕时回调。可以通过此方法进行一些资源清理、记录日志信息等工作
。注意当仅 preHandle 返回 true 的拦截器才会执行 afterCompletion。
(3)添加 Controller
@Controller
@RequestMapping("/index")
public class LoginControl {
@RequestMapping(value = "/login")
public String login(){
System.out.println("LoginControl->login");
return "login";
}
@RequestMapping(value = "/test")
@ResponseBody
public String test(){
System.out.println("LoginControl->test");
return "test";
}
}
(4)拦截器的配置
要使自定义的拦截器类生效,还需要在 springmvc 的配置文件中进行配置。配置如下所示:
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 第一种方式:配置一个全局拦截器,拦截该包下的所有请求 -->
<bean class="springmvc.interceptor.TestInterceptor"/>
<!-- 第二种方式,自定义拦截器,要求在拦截器类上加@Component -->
<!-- 拦截器1 -->
<mvc:interceptor>
<!-- 配置拦截器作用的路径,/**表示拦截所有路径 -->
<mvc:mapping path="/**"/>
<!-- 配置不需要拦截器作用的路径 /admin表示放行所有以/admin结尾的路径 -->
<mvc:exclude-mapping path="/admin"/>
<!-- 定义在<mvc:interceptor>下面的Interceptor,表示对匹配路径的请求才进行拦截 -->
<bean class="springmvc.interceptor.AdminInterceptor"/>
</mvc:interceptor>
<!-- 拦截器2 -->
<mvc:interceptor>
<mvc:mapping path="/index/**"/>
<bean class="springmvc.interceptor.IndexInterceptor"/>
</mvc:interceptor>
<!-- 后面可以配置多个拦截器 -->
</mvc:interceptors>
<mvc:interceptors> 元素用于配置一组拦截器,其子元素 :
● <bean> 定义的是全局拦截器,即拦截该包下所有的请求。
● <mvc:interceptor> 元素中定义的是指定路径的拦截器,其子元素:
● <mvc:mapping> :用于配置拦截器作用的路径,该路径在其属性 path 中定义。path 的属性值“/**”表示拦截所有路径,“/gotoTest”表示拦截所有以“/gotoTest”结尾的路径。
● <mvc:exclude-mapping> :如果在请求路径中包含不需要拦截的内容,可以通过 <mvc:exclude-mapping> 子元素进行配置。需要注意的是,
<mvc:interceptor> 元素的子元素必须按照 <mvc:mapping.../>、<mvc:exclude-mapping.../>、<bean.../> 的顺序配置
。
(4)拦截器执行流程
在运行程序时,拦截器的执行是有一定的顺序的,该顺序与配置文件中所定义的拦截器的顺序是相关的。拦截器执行顺序有两种情况,即单个拦截器和多个拦截器的情况,单个拦截器和多个拦截器的执行顺序是不一样的,略有差别。
1)单个拦截器执行流程
当只定义了一个拦截器时,它的执行流程如下图所示:
对单个拦截器:程序首先会执行拦截器类中的preHandle()方法,如果该方法返回true,则程序会继续向下执行处理器中的方法,否则程序将不再往下继续执行。在业务处理器(即控制器Controller类)处理完请求后,会执行postHandle()方法,然后会通过DispatcherServlet向客户端返回响应,在DispatcherServlet处理完请求后,才会执行afterCompletion()方法。
2)多个拦截器执行流程
在大型的企业级项目中,通常会配置多个拦截器来实现不同的功能。例如在 springmvc 配置文件中配置了两个拦截器 Interceptor1 和 Interceptor2。配置顺序 Interceptor1 在 Interceptor2 的前面。
则它们的执行流程如下图所示:
当有多个拦截器同时工作时,它们的 preHandle() 方法会按照配置文件中拦截器的配置顺序执行,而它们的 postHandle() 和 afterCompletion() 方法则会按照配置顺序的反序执行。
因此当多个拦截器工作时,其执行流程:preHandle1——preHandle2——Handle——postHandle2——postHandle1——afterCompletion2——afterCompletion1··········