做过web项目的都知道页面连接后端程序中间是需要一个连接器来进行连接控制的。拿最常用的web容器tomcat来说,我们用tomcat搭建一个简单的web应用,就是配置好tomcat的web.xml文件,然后将后端应用打成一个war放到tomcatwebapp下就能完成对应web项目的部署。
传统的定义servlet的方式稍显有些繁琐,而且容易出错,随着web应用越来越庞大,web.xml里的内容也越来越多。这时候就有了springMVC的问世,当然struts也属于这一类框架。但是不管是从应用的广泛性还是实用性来说,目前springMVC是主流,其完美对接spring应用,以及使用spring框架集成各类中间件与其他框架也是简单易用。
SpringMVC的启动流程
Tomcat的启动以及加载Spring配置文件整个过程绕来绕去,对有些初学者来说其实不算太友好,这里就对整个SpringMVC的启动流程做了简单的阐述。
大致可以分为如下几步,其中包含了从Tomcat调用到SpringMVC,然后又从SpringMVC回调到Tomcat中Servlet的相关初始化(虽然也是SpringMVC的相关内容)。
Tomcat通过Servlet监听启动springioc容器
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-ioc.xml</param-value>
</context-param>
<!-- 配置监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- MVC Dispatcher -->
<servlet>
<servlet-name>MVC Dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MVC Dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
首先上springMVC简单的web.xml配置。基本上这几行配置就能完成一个简单的web应用的搭建。
指定Servlet的监听器,Tomcat在启动之后会执行器ServletContextListener监听器里的contextInitialized方法。
springMVC默认使用的是spring自己实现的servlet监听器ServletContextListener,然后通过公共参数配置指定加载的spring容器的配置文件,来完成对spring-ioc容器的加载初始化。
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
Tomcat初始化Servlet来完成SpringMVC容器的启动
我们都知道Servlet的生命周期,是在Tomcat启动时完成相关初始化的,而整个SpringMVC中就注册了一个Servlet,那就是DispatcherServlet,这个也是整个SpringMVC中最重要的一个类。
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
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) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
//调用具体的子类实现初始化方法,模板方法,具体有子类实现
initServletBean();
}
调用到子类FrameworkServlet中的initServletBean来完成对springMVC容器的刷新。
但是在容器刷新之前,会先注册一个spring容器的监听事件,在完成容器刷新后对其进行回调。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
//为当前spring上下文添加一个监听事件,保证后面SpringMVC九大组件的初始化
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
SpringMVC九大组件的初始化
在之前就说过了,当springMVC容器完成刷新后,会通过监听事件来回调DispatcherServlet的initStrategies方法:
protected void initStrategies(ApplicationContext context) {
//初始化文件上传解析器,没有默认的处理器,
// 如果需要涉及到文件上传,需要收到注入对应的实现类
initMultipartResolver(context);
//初始化本地解析器,主要用来解决国际化的
initLocaleResolver(context);
//初始化主题解析器
initThemeResolver(context);
//初始化处理器映射器,主要解决根据请求uri来定位到具体的Controller的业务方法
initHandlerMappings(context);
//初始化处理器适配器,
//因为事先控制器(Controller)的方法有三种,
//1.注解@Controller;2.实现Controller接口;3.实现HttpRequestHandler接口
// 所以当要调用到具体的业务Controller方法需要不同的适配器来处理
initHandlerAdapters(context);
//初始化异常处理器,常用的有统一异常处理器,如报文封装转意成错误码
initHandlerExceptionResolvers(context);
//初始化请求视图转化器()
initRequestToViewNameTranslator(context);
//初始化视图解析器
initViewResolvers(context);
//初始快照Map管理器,主要用来做重定向请求的参数传递
initFlashMapManager(context);
}
在SpringMVC框架中,基本为这九大组件设置相对应的默认处理器,可以查看springMvc报下的DispatcherServlet.properties,除了MultipartResolver文件上传的解析器,其他都有默认配置。
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
而其中组件加载大致可以分为两大类:
- 单个对象:FlashMapManager、RequestToViewNameTranslator、ThemeResolver、MultipartResolver、LocaleResolver。
单个对象的初始化,都是先从spring容器(这里包含Spring-ioc父容器与Spring-MVC自容器)中进行查找匹配。如果容器中不存在符合条件的对应解析器,则采用默认策略,即从上面配置文件中,配置的对应解析器,如果配置了多个,则取第一个。
private void initLocaleResolver(ApplicationContext context) {
try {
//根据指定的beanName和对应实例类型匹配
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.localeResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
//从DispatchServlet.properties里获取对应的默认解析器 AcceptHeaderLocaleResolver
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
}
}
}
从配置文件读取到对应的权限定类名,然后利用spring容器直接创建Bean对象,如有需要自定义一些初始化工作,则利用Spring容器创建Bean的扩展性完成。如实现ApplicationContextAware接口,或者InitializingBean接口,其中ApplicationContextAware可以加拿到当前容器的上下文,做的事情就多了去了。
- 集合对象:HandlerMapping、HandlerAdapter、HandlerExceptionResolver、ViewResolver。这四个也是整个SpringMVC九大组件中最重要的三个组件,也是运用最广泛的。准确的说HandlerMapping、HandlerAdapter、HandlerExceptionResolver这三个,因为目前都是前后端分离的项目居多,视图解析器使用就没那么多了。
若是集合类型的处理器则初始化的操作有些不同,首先是根据标志位判断是否从整个容器中查找再来匹配,或者从根据具体的BeanName和类型定向查找;
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//判断是否需要从整个spring容器中扫描获取HandlerMapping的实现类实例。默认为true
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> 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 {
//如果不允许从容器中获取HandlerMapping,则从容器中对应对应实例名为handlerMapping的bean
try {
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.
//如果从容器中没有找到一个HandlerMapoing,则采用默认策略
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");
}
}
//遍历所有的HandlerMapping,是否有一个支持parseRequestPath,若有则设置当前DispatcherServlet的parseRequestPath属性为true
for (HandlerMapping mapping : this.handlerMappings) {
if (mapping.usesPathPatterns()) {
this.parseRequestPath = true;
break;
}
}
}
这三个初始化策略基本都这样,默认都是根据接口名直接从容器中查找,若找不到则采用默认策略,取配置文件里的权限定类名然后完成Bean对象的创建很初始化。
到这里SpringMVC基本启动完成,可以正常对外提供服务了。