- ContextLoaderListener与/WEB-INF/applicationContext.xml
- ContextLoaderListener之外的选择
- ServletContext是啥(Tomcat知识点巩固)
- Dispathcher与xxx-servlet.xml
- 小结
- 小案例
MVC起源
Servlet独行天下的时代
Servlet是java平台第一个用于Web开发的技术,但是在初期使用Servlet的时候,很多开发人员,都将Servlet写的臃肿难以维护,为什么呢?
因为开发人员为了偷懒,将所有逻辑都混杂与一个Servlet之中,从而形成了神奇的Servlet.
代码语言:javascript复制@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String name = request.getParameter("name");
String pwd = request.getParameter("pwd");
try {
Connection connection = getConn();
Statement statement = connection.createStatement();
//数据库访问操作
} catch (SQLException e) {
e.printStackTrace();
}
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>" name "</h1>");
out.println("</body></html>");
}
...
}
代码可维护性差先不提,单单是数据访问逻辑,业务处理逻辑,视图渲染逻辑混杂在一起,就是严重的耦合错误。
因此,如果想要对上面的代码进行解耦合,那么首先就是将三者逻辑剥离开来,
- 业务处理逻辑和数据访问逻辑剥离出来很简单
通过Service层完成业务处理逻辑,业务处理过程中通过Dao层完成数据访问逻辑,然后Service层返回的数据交给Servlet进行渲染。
看上去好像非常美好,但是视图渲染逻辑依然和Servlet纠缠在了一起,这个怎么解耦呢?
- 如果每个Servlet类中都存放着一堆HTML代码,你觉得合适吗?
繁盛一时的JSP时代
为了能够将视图渲染逻辑从Servlet中剥离出来,就有了JSP的诞生,虽然JSP本质也是通过视图解释器最终还是会翻译成一个Servlet,然后通过一堆out.printfln输出,但是这个过程被模板化了,开发人员不需要关心。
- Servlet向相关域中放入属性
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
request.setAttribute("name","DHY");
request.setAttribute("age","18");
request.getRequestDispatcher("index.jsp").forward(request,response);
}
}
- JSP中取出相关域中的属性,然后展示 (具体是模板解释器在解释的时候,会去查询相关域中保存的属性,然后进行解释替换)
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<body>
<H1>我的名字叫做: ${name}</H1>
</body>
</html>
因为JSP中可以内嵌JAVA代码,因此在JSP繁盛时期,大量开发人员将本该属于Service或者Dao层的逻辑,全部一股脑塞入了JSP中,因此原本为了剥离视图渲染逻辑的JSP,由变得耦合起来。
出现上面的情况,不是因为JSP的解决思路出现了错误,而是当时开发者没有严格地界定JSP的基本使命,这才有了后来的MVC时代。
还有问题就是因为大部分开发人员都是采用一个web请求对应一个servlet的方式,所以在项目请求数非常多的情况下,就需要超级多的Servlet类被创建,类越多,越难维护,因此如何解决这个问题,也十分棘手。
Servlet与JSP的联盟
为了区分各个组件的职责,避免乱堆乱放,后来推出了JSP Model标准的诞生。
JSP Model引入了JavaBean,通过JavaBean对相关业务逻辑进行封装,完成初步关注点的分离。
并且由于一开始没有严格限制JSP的职责,导致JSP还是会被乱用,因此后来又规定JSP只能用于视图逻辑渲染,不能在其中增加一堆业务逻辑代码。
此时的JSP Model架构图如下:
在改进后的JSP Model架构中,由Servlet负责管理流程控制,由JSP负责视图渲染,由JavaBean封装业务逻辑并负责与数据层进行交互。
可以看出,此时的JSP Model模型已经初步具备了MVC模式的样子,但是与MVC又有些许不同,下面我们先来回顾一下MVC。
MVC(Model-View-Controller 模型–视图–控制器) 在当今JAVA界尤其是Web开发领域,已经非常成熟了。
MVC中有以下几个组件:
- 控制器负责接收视图发送的请求并进行处理,它会根据请求条件通知模型进行应用程序状态的更新,之后选择合适的视图展示给用户。
- 模型通常封装了应用的逻辑以及数据状态,当控制器通知模型进行状态更新的时候,模型封装的相应逻辑将被调用。执行完成后,模型通常会通过事件机制通知状态更新完毕,从而视图可以显示最新数据状态。
- 视图是面向用户的接口,当用户通过视图发起某种请求的时候,视图将这些请求转发给控制器进行处理,处理流程经控制器和模型之后,最终视图将接收到模型的状态更新通知,然后视图将结合模型数据,更新自身的显示。
但是,最初意义上的MVC模式,在视图与模型间的数据同步工作是采用从模型PUSH到视图的形式完成的。而对于Web应用来说,局限于所用的协议和使用场景,无法实现从模型PUSH数据到视图这样的功能。
所以,我们只能对MVC中的组件的最初作用定义做出调整,由控制器与模型进行交互,在原来的通知模型更新应用程序状态的基础上,还要获取模型更新的结果数据,然后将更新的模型数据一并转发给视图。
也就是说,我们现在改由控制器从模型中PULL数据给视图,这种意义上的MVC称为Web MVC,也就是现在大多说WEB开发框架所用的架构模式。
虽然JSP Model改进后的模型已经非常接近与Web MVC框架了,但是还是存在问题。
从JSP Model的架构图上可以看到,Servlet是作为控制器角色存在的,但是,该架构并没有说明,具体应用程序是只需要一个控制器,还是需要多个控制器,这就造成了如下两种情况:
- Web应用程序中使用多个Servlet控制器,即一个Servlet对应一个Web请求的处理,这是最初开发人员使用最多的模式。但是这个模式存在很大的问题,因为随着Web请求的增多,我们需要在Web.xml将每一个Servlet映射的URL记录下来,这会导致Web.xml非常臃肿。并且由于Web请求的处理流程将各自分散管理,没有一种集中管理方式,因此不利于整个系统规定开发和维护。
- Web应用程序中使用单一的Servle作为集中控制器。现在,所有的Web处理请求全部由单一的Servlet控制器处理,但是这个时候还是存在下面几个问题。
- 因为所有的Web请求都映射到了单一的Servlet控制器中处理,所有,我们需要自己在这个控制器中,对每个请求的URL进行分析,然后判断处理流程的流向。显然,这部分逻辑如果写死了,那么就不方便调整,如果不写死,就需要在此基础上写一套请求映射管理和请求派发逻辑。
正如我们所看到的那样,制约JSP Model2发展的,就是将流程控制等通用相关逻辑进行硬编码的实践方式,这直接导致了JSP Model2架构的不可重用性。 每次启动新的Web应用程序开发,又需要从头编写Servlet控制器的URL分析,以及流程控制等Web层通用逻辑。
这自然就促使我们去除架构中控制逻辑的硬编码,并尽可能的复用Web应用程序开发过程中的一些通用逻辑。
数英雄人物,还看今朝
Web框架存在的意义在于,他们为Web应用程序的开发提供了一套可重复利用的基础设施,这样开发人员只需要关注特定与每个应用程序的逻辑开发工作,而不需要每次都重复哪些可以统一处理的通用逻辑。
当前Web开发框架有如下两种类型:
- 请求驱动的Web框架,又称为request/response框架,顾名思义,这种框架是基于Serlvet的请求/响应处理模型构建而成的。这种类型的开发框架大都以Web MVC模式为指导,在JSP Model架构基础上进化而来。比如: struts框架,spring mvc框架等。
- 事件驱动的Web框架,又被称为基于组件的Web开发框架,这种框架采用与Swing等GUI开发框架类似的思想,将视图组件化,由视图中的相应组件触发事件,进而驱动整个处理流程。
这里重点讲解请求驱动框架
对于请求驱动的Web框架来说,他们是基于JSP Model演化而来,那么他们是如何解决JSP Model在实践过程中的问题呢?
首先,对于大多数请求驱动的Web框架而言,他们更倾向于使用单一Servlet作为控制器的实践方式,并且这些框架通常会结合Front Controller和Page Controller模式,对单一Servlet控制器做进一步的改进,对原先过于耦合的各种控制器逻辑进行逐步分离。
具体来说就是原来的单一Servlet作为整个应用程序的Front Controller,该Servlet接收到具体的Web处理请求之后,会参照映射信息,将待处理的Web请求转发给次一级的控制器来处理。
在控制器Servlet接收到Web请求后,他会对Web请求的URL进行分析,然后根据分析结果,并通过相关配置信息,将当前Web请求转发给次一级的控制器类进行处理。
现在,作为Front Controller的Servlet和次级控制器类共同组成了整个应用程序的控制器。
原先单一的控制器Servlet通过将流程控制信息外部化,并分离具体的Web请求处理逻辑给次级控制器类进行处理的方式。
瘦身为灵活而可复用的担当Front Controller的Servlet,有了这样的关注点分离之后,我们就可以提高整个Web应用中控制器逻辑的可复用性。
其实Spring MVC就是上面思想一步步演化而来,如果稍微研究过的小伙伴,很快就可以对应上Spring mvc中相关组件的作用
Spring MVC初探
Spring MVC也是通过Front Controller和Page Controller的概念来分离流程控制逻辑与具体Web请求处理逻辑。
org.springframework.web.servlet.DispatcherServlet就是Spring MVC框架中的Front Controller,它负责接收并处理所有的Web请求,只不过针对具体的处理逻辑,它会委派给它的下一级控制器去实现,即org.springframework.web.servlet.mvc.Controller,也对应着Page Controller的角色定义。
再来探究DispathcherServlet之前,不妨先思考一下Servlet通常都会做什么工作。
- 获取请求信息,比如请求的路径,各种参数值等
- 根据请求信息,调用具体的服务对象处理具体的Web请求
- 处理完后,将要在视图中显示的模型数据通过request进行传递,最后通过RequestDispathcer选择具体的JSP视图并显示。
DispathcerServlet所作的工作和上面说的没有什么不同,唯一的改变就是DispathcerServlet将各项工作细化并分离给了较为独立的角色来完成。
DispathcerServlet的处理流程可以简单概括如下:
- HandlerMapping—Web请求的处理协调人
既然DispathcerServlet是整个框架的FrontController,当将它注册到Web.xml时,就注定了它要服务于规定的一组Web请求的命运,而不是一个单独的Web请求,如果要将DispathcerServlet注册到web.xml中,通常配置形式如下:
代码语言:javascript复制 <servlet>
<servlet-name>dispathcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispathcerServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispathcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
因为DispathcerServlet会拦截所有的请求,而自己负责处理Web请求和具体的处理类之间的映射匹配关系。
具体的处理方式或者说策略可能多种多样,例如:
- 例如: "掐头去尾"的处理方式,将Web请求的URL路径去除前面的上下文路径(context path)和最后的扩展名,去最终剩下的路径信息,作为匹配的结果。比如,如下代码将最终以resource作为匹配结果。
http://www.hhh.com/app/resouce.html
- 以Web请求的URL中存在的某个参数的值作为匹配的标准。比如,当发现请求的路径中存在controller这个参数的话,其值就会被作为匹配结果用来调用具体的处理类,对于如下所示的URL:
http://www.hhh.com/app/resouce.html?controller=hhController
匹配的结果就是hhController
- 以cookie或者session中的某些信息作为匹配标准,比如,针对某个客户的Web请求,全部转发给一个处理类进行处理。
或者结合Ruby On Rails的理念,我们在开发中规定一些惯例或者说约定,然后以这些惯例或者约定来解析Web请求的URL路径信息,以获取具体的处理类匹配。
可见,如果把映射匹配的逻辑写死在DispatcherServlet中,是无效有效扩展的,而且匹配的方式也可能随着需求而变化。所以,Spring MVC为了能够灵活地处理映射的匹配,引入了HandlerMapping专门管理Web请求到具体的处理类之间的映射关系。
在Web请求叨叨DispathcerServlet之后,Dispatcher将寻找具体的HandlerMapping实例,以获取对应当前Web请求的具体处理类,即Controller.
- Controller (Web请求具体处理者)
Controller是对应DispatcherServlet的次级控制器,它本身实现了对应某个Web请求的处理逻辑。在我们所使用的HandlerMapping查找到当前Web请求对应哪个Controller的具体实例之后,DispatcherServlet即可获得HandlerMapping返回的结果,并调用Controller的处理方法来处理当前的Web请求。
Controller的处理方法执行完后,将返回一个ModelAndView实例,该对象包含如下两部分信息:
- 视图的逻辑名称(或者具体的视图实例),DispatcherServlet将根据该视图的逻辑名称来决定为用户显示哪个视图。
- 模型数据。视图渲染过程中需要将这些模型数据并入视图的显示中。
有了ModelAndView所包含的视图与模型二者信息后,DispatcherServlet就可以进行视图渲染的工作了。
- ViewResolver和View(视图独立战争的领导者)
对于一个Web框架而言,视图渲染技术一般存在多个,例如Velocity,Freemarker等通用的模板引擎,并且他们不依赖于request对象来传递模型数据,甚至我们也不需要依赖DispatcherServlet来输出最终的视图。
鉴于视图技术存在多种选择,Spring提出了一套基于ViewResolver和View接口的Web视图处理抽象层,来屏蔽Web框在使用不同的Web技术时的差异性。
那么,大家思考一个问题: Spring MVC是如何以统一的方式,将相同的模型数据纳入不同的视图形式并显示的呢?
原生的Servlet自身就提供了两种基本的视图输出方式,即HTML文本输出和二进制内容输出,即
代码语言:javascript复制PrintWriter writer=response.getWriter();
ServletOutputStream out= response.getOutputStream();
在HttpServletResponse可以同时支持文本形式和二进制形式的视图输出的前提下,我们只要在最终将视图数据通过HttpServletResponse输出之前,借助于不同的视图技术API,并结合模型数据和相应的模板文件,就能生成最终的视图结果,如下所示:
- 获取模型(Model)数据
- 获取视图模板文件(比如: *.JSP, *.VM , *.FM , *.XLS等)
- 结合视图模板和模型数据,使用相应的视图技术API生成最终视图结果
- 完成
这样,不管最终生成的视图如何,我们都可以用同样的方式输出他们,但唯一的问题在于,我们不可能将每个视图的生成代码都纳入DispatcherServlet的职权范围。
大家思考一下,如果同时存在多个实现子类时,通常会怎么解决这个问题
- Interface ----> 统一上层接口,底层无论使用什么视图技术,对我们而言都是透明的
Spring MVC通过引入View接口定义,来统一抽象视图的生产策略。
之后,DispatcherServlet只需要根据Spring Controller处理完毕后通过ModelAndView返回的逻辑视图名称查找到具体的View实现,然后委派该具体的View实现类来根据模型数据,输出具体的视图内容即可。
Spring的View抽象策略如下图所示:
现在,视图模板与模型数据的合并逻辑,以及合并后的视图结果的输出逻辑,全部封装到了相应的View实现类中。
DispatcherServlet只需要根据ModelAndView返回的信息,选择具体的View实现类做最终的具体工作即可。
不过,与HandlerMapping帮助DispatcherServlet查找具体的Controller处理Web请求类似,DispatcherServlet通过ViewResolver来处理逻辑视图名与具体的View实例之间的映射关系。
ViewResolver将根据ModelAndView中的逻辑视图名查找相应的View实现类,然后将查找的结果返回DispatcherServlet。
DispatcherServlet最终会将ModelAndView中的模型数据交给返回的View来处理最终的视图渲染工作。
最后简单来看一下DispatcherServlet的工作流程:
实践出真知
上面讲的都是理论,下面来看一下实际使用时,我们需要做什么
一个基于Spring MVC框架的Web应用,依然是一个遵循Servlet规范的Web应用程序,自然,它也就拥有一个遵循Servlet规范的Web应用程序应有的目录结构和相应的部署描述符文件。
不过,Spring MVC框架会额外增加两个配置文件。
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>dhy</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>controller</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>controller</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
loadOnStartUp>0时,表示对应Servlet在tomcat启动时,就进行初始化,而不是等到第一次访问时,采去创建
web.xml属于tomcat的配置文件,它是整个Web应用程序的部署描述符文件,我们可以在该文件中配置监听器,过滤器,servelt等。
我们可以看到,对于Spring MVC来说,其在web.xml中注册了一个ContextLoaderListener监听器和DispatcherServelt全局单一控制器。
ContextLoaderListener与/WEB-INF/applicationContext.xml
上面我们在Web.xml中注册了一个ContextLoaderListener,它负责为整个Web应用程序加载顶层的WebApplicationContext(ROOT WebApplicationContext).
该顶层WebApplicationContext主要用于提供应用所使用的中间层服务。我们使用的数据源(DataSource),数据访问对象(DAO),服务器对象(Service)等,都在WebApplicationContext中注册。
可以将其看做ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext,只不过,WebApplicationContext专门用于Web环境下,在这种容器中,我们可以注册scope类型为request或者session的bean。
ContextLoaderListener加载的WebApplicationContext的默认配置文件路径为/WEB-INF/application.xml,这也就是为什么下图中/WEB-INF/目录下存在一个application.xml配置文件的原因。
该文件符合通用的Spring IOC容器配置文件格式。但是,实际开发中,不管是出于团队并行开发效率的考虑,还是处于管理的因素,很少使用单一配置文件的方式。一般会按照应用程序的层次进行配置文件的分割,要么会按照系统功能模块进行配置文件的切割。
当存在多个分割后的配置文件的时候,ContextLoaderListener的默认加载行为将成为我们的制约。这时,我们可以通过在web.xml中指定名称为contextConfigLocation的配置参数来打破默认行为的制约。
代码语言:javascript复制 <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/application.xml,/WEB-INF/applicationContext-module.xml</param-value>
</context-param>
< param-value >中通过逗号或者空格来分割多个配置文件路径,甚至使用ANT类型的路径表达式。
ContextLoaderListener之外的选择
如果你不得不继续使用Servlet 2.2的Web容器,或者那些并不支持ServletContextListener特性的Servlet 2.3容器,那么不得不放弃ContextLoaderListener,转而使用ContextLoaderServlet加载顶层的WebApplicationContext.
ContextLoaderServlet完成与ContextLoaderListener相同的工作,需要注意的是,我们需要调整load-on-startup的值,让它在当前Web应用程序中使用的其他Servlet之前启动。
我们可以通过下面的配置方式让ContextLoaderServlet工作:
代码语言:javascript复制 <servlet>
<servlet-name>contextLoader</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
load-on-startup数字越小,优先级越大
ContextLoaderServlet和ContextLoaderListener加载相应路径下的容器配置文件,并在构建完成相应的顶层WebApplicationContext之后,将该顶层WebApplicationContext绑定到ServletContext,如果想要获取绑定到ServletContext的WebApplicationContext,我们可以通过WebApplicationContextUtils类,这样就不需要知道顶层WebApplicationContext绑定到ServletContext的时候使用的key是什么。
例如:
代码语言:javascript复制//getServletContext()方法来自父类GenericServlet中ServletConfig属性,ServletConfig属性再父类GenericServlet的init方法中被赋值
WebApplicationContext wac=WebApplicationContextUtils.getWebAplicationContext(getServletContext());
//或者--严格模式,如果获取不到wac,就抛出异常
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
wac.getBean("serviceBeanName");
其实是因为StandardWrapper继承了ServletConfig接口,所以在wrapper初始化每个servlet,调用其init方法时,会将当前wrapper的外观对象传入(为了隐藏部分接口)
从零开始手写Tomcat的教程11节----StandardWrapper
通过ContextLoaderListener或者ContextLoaderServlet将wac绑定到ServletContext,任何类型的Web应用程序,只要能够获取ServletContext的引用,就能获取并使用该WebApplicationContext。
ServletContext是啥(Tomcat知识点巩固)
- 先看下面这两张图片,回顾一下Tomcat的大体架构
没读过tomcat源码也没关系,大概弄清楚是怎么肥事就可以了
一个Host对应一个域名,一般我们访问的Http URL都是 www.dhy.com/dhy/xpy/queryOne , 域名后面跟着的就是资源名。
Host下面管理的子容器集合是Context,一般一个Web应用程序只有一个Context,一个Context会映射到一个资源目录,如下所示:
代码语言:javascript复制Host也可以映射到一个目录,如果严格按照层级关系的话,context对应的目录,应该是Host目录下的一个子目录,当然tomcat并没有限制死
<Host name="www.dhy.com" appBase="dhy"
unpackWARs="true" autoDeploy="true">
<context path="/xpy" docBase="xpy" ></context>
</Host>
context标签中的path其实就是对应我们平常设置的context-path,即表示要访问当前context对应的Web应用程序,域名后面紧跟着的第一级资源路径必须为/xpy, 如果访问www.dhy.com/xpy/user.html, 再没有特别指定user.html位于那个具体路径下的时候,默认会去xpy目录下面寻找。
一个Context下面可以有很多个Servlet,其实上面的ServletContext就可以理解为是站在Context层面的一个资源集合,那么该资源集合会被该Context下面所有的servlet所共享。
口说无凭,下面以tomcat 4源码为例,我们大概看看ServletContext的模样:
- ServletContext
//ServletContext 是servlet规范的一个接口,tomcat的具体实现类是ApplicationContext
public class ApplicationContext implements ServletContext {
/**
* 可以向ServletContext中存放属性,给该context下面所有的servlets共享
*/
private HashMap attributes = new HashMap();
/**
*和上面不同之处在于,该集合中的属性,是只读的
*/
private HashMap readOnlyAttributes = new HashMap();
/**
* 当前servletContext关联的Context对象
*/
private StandardContext context = null;
/**
* 为了向程序员隐藏ApplicationContext类中某些接口,这里使用了外观模式---我们程序员在开发中拿到的其实是外观对象
*/
private ServletContext facade = new org.apache.catalina.core.ApplicationContextFacade(this);
/**
* 存放初始化参数
*/
private HashMap parameters = null;
//ApplicationContext的构造函数也可以说明,一个ApplicationContext必须与一个Context对象相关联才行
public ApplicationContext(String basePath, StandardContext context) {
super();
this.context = context;
this.basePath = basePath;
}
...
}
- StanardContext类中也有获取ServletContext 的方法
/**
* Return the servlet context for which this Context is a facade.
*/
public ServletContext getServletContext() {
if (context == null)
context = new org.apache.catalina.core.ApplicationContext(getBasePath(), this);
return (context);
}
Dispathcher与xxx-servlet.xml
Dispathcer使用了一个外部化的配置文件,来配置Spring MVC框架在处理Web请求过程中涉及到的各个组件,包括HandlerMapping定义,Controller定义,ViewResolver定义等。
该外部化的配置文件存在的默认路径也是/WEB-INF/,名称需要参照web.xml中定义的DispathcerServlet的< servlet-name >来决定。
例如: 我们当前定义的DispathcerServlet对应的< servlet-name>为controller,那么,默认的配置文件即对应/WEB-INF/controller-servlet.xml,也就是说,DispathcerServlet对应的默认配置文件名称,将在< servlet-name >的值的基础加上后缀-servlet.xml。
< servlet-name > -servlet.xml与/WEB-INF/applicationContext.xml相同,也符合Spring IOC容器配置文件格式。
只不过,相对于/WEB-INF/applicationContext.xml来说,< servlet-name >-servlet.xml有自己的职责。其主要负责配置基于Spring MVC框架的Web应用程序使用的各种Web组件。
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean name="/infoList.do" class="...InfoListController"/>
</beans>
DispathcerServlet启动之后将加载该配置文件,并构建相应的WebApplicationContext,该WebApplicationContext将之前通过ContextLoaderListener加载的顶层WebApplicationContext(ROOT WebApplicationContext)作为父容器。
这样,如果需要< servlet-name > -servlet.xml中注册的各种面向Web层的组件,也可以注入来自顶层WebApplicationContext的依赖了。
即二者是父子容器的关系。
- 顶层ROOT的wac和特定于DispathcerServlet的wac之间的逻辑依赖关系。
因为随着Web应用程序的开发,单一的< servlet-name >-servlet.xml配置文件会变得越来越臃肿,因此DispathcerServlet的contextConfigLocation初始化参数可以帮助我们切割配置文件。
代码语言:javascript复制 <servlet>
<servlet-name>controller</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/contriller-servlet.xml,/WEB-INF/module-servlet.xml,...</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
多个配置文件,用逗号或者空格分割即可。
小结
ContextLoaderListener负责加载顶层的WebApplicationContext,该顶层的WebApplicationContext将/WEB-INF/applicationContext.xml作为其默认配置文件的路径。
顶层的WebApplicationContext提供的IOC容器主要负责Dao层,Service层,一些domain对象的bean管理。
而对于DispatcherServlet来说,其会去加载< servlet-name > -servlet.xml配置文件并构建对应的WebApplicationContext,并将顶层WebApplicationContext作为其父容器。
由DispatcherServlet构建出的子wac提供的IOC主要负责对controller层相关bean进行管理。
因为子容器可以注入父容器中的bean,因此controller层可以注入service层的bean,而在父子容器的限制下,service层无法注入controller层的bean
TIPS: Springboot提供的是一个IOC容器,不存在父子容器之说,不要搞混了哦!
小案例
就实现一个点击a标签,发送请求,经过处理后,跳转到指定页面的功能吧。
从浏览器中点击某个a标签后,Web请求将被发送到DispathcerServlet进行处理。DispathcerServlet将寻求相应的HandlerMapping对Web请求进行分析,然后调用匹配结果对应的Controller实现,具体到当前场景就是我们要实现的HelloController。
HelloController处理完毕将视图名称hello和模型数据一并返回,然后DispathcerServlet借助于相应的ViewResolver,根据返回的视图名称选择相应的视图并显示,这就是整个流程。
下面开始实现:
- 配置基础装备,在web.xml中配置DispathcerServlet和ContextLoaderListener。还可以额外添加相应的Filter和ServletContextListener以及处理字符编码和Log4j初始化等配置内容。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>dhy</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/application.xml,/WEB-INF/applicationContext-module.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<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>
<servlet-name>controller</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>controller</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>controller</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
- 添加applicationContext.xml配置文件和servletName-servlet.xml配置文件
- 开发独立的业务逻辑。对于一个设计良好的Web应用程序来说,Web层依赖于业务层对象,但业务层却不应该对Web层有任何依赖。Web层只应该看做是公开业务逻辑的一种视角或者交互方式,这样实现的话,业务层完全可以独立设计并实现,而不需要关心最终通过什么手段将服务公开给用户。
public class HelloService {
public String hello(){
return "hello";
}
}
整个Web应用的中间层服务支持,默认都是通过/WEB-INF/applicationContext.xml注册(即ROOT wac)。所以,我们要将HelloService的实现增加到Web应用程序顶层容器的配置文件中。
代码语言:javascript复制 <bean id="helloService" class="com.example.service.HelloService"/>
真实开发中,我们还需要添加数据源和一些其他bean
- 添加HandlerMapping,DispathcerServlet在接收到Web请求后,将寻求相应HandlerMapping进行Web请求到具体的Controller实现的匹配。所以,我们需要提供一个HandlerMapping的实现,Spring MVC默认提供了多个HandlerMapping实现,我们暂且使用BeanNameUrlHandlerMapping,它将根据URL与Controller的bean定义的名称进行匹配。
代码语言:javascript复制HandlerMapping要添加到servletName-servlet.xml配置文件中去
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
Spring MVC在没有配置任何HandlerMapping的情况下,默认使用BeanNameUrlHandlerMapping。
BeanNameUrlHandlerMapping的匹配规则如下: http://host:port/hhh/hhh.do -----> 去当前容器内勋在名为/hhh.do的Controller定义。
- 实现对应的Controller并添加到配置文件,在Spring mvc起初使用的时候,是通过扩展AbstractController 的方式来实现具体的Controller的。
@Data
public class HelloController extends AbstractController {
private HelloService helloService;
private String viewName;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
String hello = helloService.hello();
ModelAndView modelAndView = new ModelAndView(viewName);
modelAndView.addObject("hello",hello);
return modelAndView;
}
}
将controller注册到servletname-servlet.xml配置文件中去
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<bean name="/hhh.do" class="com.example.controller.HelloController">
<property name="helloService" ref="helloService"/>
<property name="viewName" value="hello"/>
</bean>
</beans>
helloService的依赖注入来自父容器。
BeanNameUrlHandlerMapping要求Controller的bean定义名称以/开头。
- 添加ViewResolver. 因为上面controller返回的视图名hello,DispatcherServlet并不知道这个逻辑视图名对应的视图实现,因此需要一个视图名解析器。
Spring mvc为ViewResolver提供了多种实现,我们使用JST/JSTL作为视图技术:
代码语言:javascript复制 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
prefix和suffix属性规定了拼接的前后缀,即我们返回的视图名为hello,会拼接成/WEB-INF/jsp/hello.jsp,即会去拼接后的目录下面寻找对应的文件。
- 实现hello.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<body>
<h1>${hello}</h1>
</body>
</html>
- 测试