Servlet是java平台第一个用于Web开发的技术,但是在初期使用Servlet的时候,很多开发人员,都将Servlet写的臃肿难以维护,为什么呢?
因为开发人员为了偷懒,将所有逻辑都混杂与一个Servlet之中,从而形成了神奇的Servlet.
@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中剥离出来,就有了JSP的诞生,虽然JSP本质也是通过视图解释器最终还是会翻译成一个Servlet,然后通过一堆out.printfln输出,但是这个过程被模板化了,开发人员不需要关心。
@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);
}
}
<%@ 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类被创建,类越多,越难维护,因此如何解决这个问题,也十分棘手。
为了区分各个组件的职责,避免乱堆乱放,后来推出了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是作为控制器角色存在的,但是,该架构并没有说明,具体应用程序是只需要一个控制器,还是需要多个控制器,这就造成了如下两种情况:
正如我们所看到的那样,制约JSP Model2发展的,就是将流程控制等通用相关逻辑进行硬编码的实践方式,这直接导致了JSP Model2架构的不可重用性。 每次启动新的Web应用程序开发,又需要从头编写Servlet控制器的URL分析,以及流程控制等Web层通用逻辑。
这自然就促使我们去除架构中控制逻辑的硬编码,并尽可能的复用Web应用程序开发过程中的一些通用逻辑。
Web框架存在的意义在于,他们为Web应用程序的开发提供了一套可重复利用的基础设施,这样开发人员只需要关注特定与每个应用程序的逻辑开发工作,而不需要每次都重复哪些可以统一处理的通用逻辑。
当前Web开发框架有如下两种类型:
这里重点讲解请求驱动框架
对于请求驱动的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也是通过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通常都会做什么工作。
DispathcerServlet所作的工作和上面说的没有什么不同,唯一的改变就是DispathcerServlet将各项工作细化并分离给了较为独立的角色来完成。
DispathcerServlet的处理流程可以简单概括如下:
既然DispathcerServlet是整个框架的FrontController,当将它注册到Web.xml时,就注定了它要服务于规定的一组Web请求的命运,而不是一个单独的Web请求,如果要将DispathcerServlet注册到web.xml中,通常配置形式如下:
<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请求和具体的处理类之间的映射匹配关系。
具体的处理方式或者说策略可能多种多样,例如:
http://www.hhh.com/app/resouce.html
http://www.hhh.com/app/resouce.html?controller=hhController
匹配的结果就是hhController
或者结合Ruby On Rails的理念,我们在开发中规定一些惯例或者说约定,然后以这些惯例或者约定来解析Web请求的URL路径信息,以获取具体的处理类匹配。
可见,如果把映射匹配的逻辑写死在DispatcherServlet中,是无效有效扩展的,而且匹配的方式也可能随着需求而变化。所以,Spring MVC为了能够灵活地处理映射的匹配,引入了HandlerMapping专门管理Web请求到具体的处理类之间的映射关系。
在Web请求叨叨DispathcerServlet之后,Dispatcher将寻找具体的HandlerMapping实例,以获取对应当前Web请求的具体处理类,即Controller.
Controller是对应DispatcherServlet的次级控制器,它本身实现了对应某个Web请求的处理逻辑。在我们所使用的HandlerMapping查找到当前Web请求对应哪个Controller的具体实例之后,DispatcherServlet即可获得HandlerMapping返回的结果,并调用Controller的处理方法来处理当前的Web请求。
Controller的处理方法执行完后,将返回一个ModelAndView实例,该对象包含如下两部分信息:
有了ModelAndView所包含的视图与模型二者信息后,DispatcherServlet就可以进行视图渲染的工作了。
对于一个Web框架而言,视图渲染技术一般存在多个,例如Velocity,Freemarker等通用的模板引擎,并且他们不依赖于request对象来传递模型数据,甚至我们也不需要依赖DispatcherServlet来输出最终的视图。
鉴于视图技术存在多种选择,Spring提出了一套基于ViewResolver和View接口的Web视图处理抽象层,来屏蔽Web框在使用不同的Web技术时的差异性。
那么,大家思考一个问题: Spring MVC是如何以统一的方式,将相同的模型数据纳入不同的视图形式并显示的呢?
原生的Servlet自身就提供了两种基本的视图输出方式,即HTML文本输出和二进制内容输出,即
PrintWriter writer=response.getWriter();
ServletOutputStream out= response.getOutputStream();
在HttpServletResponse可以同时支持文本形式和二进制形式的视图输出的前提下,我们只要在最终将视图数据通过HttpServletResponse输出之前,借助于不同的视图技术API,并结合模型数据和相应的模板文件,就能生成最终的视图结果,如下所示:
这样,不管最终生成的视图如何,我们都可以用同样的方式输出他们,但唯一的问题在于,我们不可能将每个视图的生成代码都纳入DispatcherServlet的职权范围。
大家思考一下,如果同时存在多个实现子类时,通常会怎么解决这个问题
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框架会额外增加两个配置文件。
<?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全局单一控制器。
上面我们在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的配置参数来打破默认行为的制约。
<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类型的路径表达式。
如果你不得不继续使用Servlet 2.2的Web容器,或者那些并不支持ServletContextListener特性的Servlet 2.3容器,那么不得不放弃ContextLoaderListener,转而使用ContextLoaderServlet加载顶层的WebApplicationContext.
ContextLoaderServlet完成与ContextLoaderListener相同的工作,需要注意的是,我们需要调整load-on-startup的值,让它在当前Web应用程序中使用的其他Servlet之前启动。
我们可以通过下面的配置方式让ContextLoaderServlet工作:
<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是什么。
例如:
//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。
没读过tomcat源码也没关系,大概弄清楚是怎么肥事就可以了
一个Host对应一个域名,一般我们访问的Http URL都是 www.dhy.com/dhy/xpy/queryOne , 域名后面跟着的就是资源名。
Host下面管理的子容器集合是Context,一般一个Web应用程序只有一个Context,一个Context会映射到一个资源目录,如下所示:
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 是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;
}
...
}
/**
* 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);
}
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组件。
<?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的依赖了。
即二者是父子容器的关系。
因为随着Web应用程序的开发,单一的< servlet-name >-servlet.xml配置文件会变得越来越臃肿,因此DispathcerServlet的contextConfigLocation初始化参数可以帮助我们切割配置文件。
<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,根据返回的视图名称选择相应的视图并显示,这就是整个流程。
下面开始实现:
<?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>
public class HelloService {
public String hello(){
return "hello";
}
}
整个Web应用的中间层服务支持,默认都是通过/WEB-INF/applicationContext.xml注册(即ROOT wac)。所以,我们要将HelloService的实现增加到Web应用程序顶层容器的配置文件中。
<bean id="helloService" class="com.example.service.HelloService"/>
真实开发中,我们还需要添加数据源和一些其他bean
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定义。
@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配置文件中去
<?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定义名称以/开头。
Spring mvc为ViewResolver提供了多种实现,我们使用JST/JSTL作为视图技术:
<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,即会去拼接后的目录下面寻找对应的文件。
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<body>
<h1>${hello}</h1>
</body>
</html>