Spring的控制反转和面向切面,主要表达了代码之间解耦合的编程思想。
而现在说的SpringMVC则是一种web框架,它跟Struts2类似,从web.xml来看,struts2是通过Filter配置,spring是通过Servlet配置。
不管是struts.xml还是spring-mvc.xml,他们都通过xml的配置方式使用了反射,并指定相关类,将其加载到不同框架的容器里。
然后每当浏览器访问时,根据不同url对应的不同处理类(映射),从而达到控制用户访问的目的。
先来说SpringMVC在web.xml中是如何使用的。
Spring的配置和Struts2的配置稍有不同,并未使用Filter,使用的是Servlet.
下面上web.xml的配置
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" 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"> <display-name>Eryi</display-name> <servlet> <!-- load-on-startup:表示启动容器时初始化该Servlet; --> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- url-pattern:表示哪些请求交给Spring Web MVC处理, “/” 是用来定义默认servlet映射的。 --> <!-- 也可以如“*.html”表示拦截所有以html为扩展名的请求。 --> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 自此请求已交给Spring Web MVC框架处理,因此我们需要配置Spring的配置文件, --> <!-- 默认DispatcherServlet会加载WEB-INF/[DispatcherServlet的Servlet名字,也就是上面的spring]-servlet.xml配置文件。 --> <!-- 即spring-servlet.xml --> <welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list> </web-app>
双击web.xml中得Servlet配置,查看该实现类DispatcherServlet,查看里面的方法,按照经验,一般先查找一下里面是否有Init开头的方法,查看struts2框架式也可以使用该方法。
找到如下方法,这句话的意思是初始化servlet使用的策略对象:
/** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
先说一下形参ApplicationContext,如果我们创建了applicationContext.xml文件,甭切在在web.xml中指定了该文件的路径,那么spring框架启动后,就会去读取该文件,将该文件里面的bean配置归于spring管理,如果是通过注解的方式配置,那么spring会自动扫描指定包下面的类,将其注册成bean,然后归属spring管理,不管怎么配置,这些都属于applicationContext。
spring-mvc读取applicationContext.xml和struts2读取struts.xml的方式都是一样的。
看下面的代码,我们可以回顾一下我们通过xml获取到的ApplicationContext对象。
ApplicationContext.xml配置文件实际上就是指导Spring工厂进行Bean生产、依赖关系注入(装配)及Bean实例分发的“图纸”。
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");//读取beans.xml中的内容 Cat p = ctx.getBean("cat",Cat.class);//创建bean的引用对象 p.Cry(); }回到之前的init方法,我么不看一下spring初始化操作时,都调用了那些方法,
initMultipartResolver(context); 初始化文件上传解析,如果在BeanFactory中为此命名空间定义了指定名称的bean,则不会提供多部分处理。
initLocaleResolver(context);初始化本地解析,和上述内容相同,那么我们将默认为AcceptHeaderLocaleResolver。
initThemeResolver(context);初始化主题解析,和上述内容相同,如果没有的话将默认使用FixedThemeResolver
initHandlerMappings(ApplicationContext context);初始化映射服务,将请求映射到处理器(处理类),和上述内容相同,如果没有的话将默认使用BeanNameUrlHandlerMapping
initHandlerAdapters(ApplicationContext context);初始化适配器,用来支持多种类型的处理器(处理类),和上述内容相同,如果没有的话将默认使用SimpleControllerHandlerAdapter
initHandlerExceptionResolvers(ApplicationContext context);初始化异常解析器,运行过程中异常则交给这里处理,和上述内容相同,如果没有的话将默认为Exception
initRequestToViewNameTranslator(ApplicationContext context);初始化servlet使用的”URL请求名“转换器,将url请求解析到到视图名,未配置则使用DefaultRequestToViewNameTranslator
initViewResolvers(ApplicationContext context);初始化视图解析器,通过ViewResolver解析逻辑视图名到具体视图实现,如果在BeanFactory中为此命名空间定义了指定名称的bean ,则使用InternalResourceViewResolver
initFlashMapManager(ApplicationContext context);初始化Flash数据管理,若未配置,则默认为web.servlet.support.DefaultFlashMapManager
说一下initHandlerMappings方法,该方法是将url映射到处理类:
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; 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<HandlerMapping>(matchingBeans.values()); // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { //ApplicationContext 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. if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }
既然是映射服务,那么这个方法肯定是为url映射到class控制类上进行服务,因此知道,这里的映射服务要不通过xml配置,要不通过注解配置。
这时候我们要知道,在web容器中,bean的创建肯定在映射创建之前。
handlerMappings的创建肯定要在servlet之前,因为url映射都没有的话,容器怎么去找servlet呢?
我们想一下Servlet的3个方法,我们就知道,handlerMappings的创建可能或者应该在servlet的初始化方法Init()中。
下面的绿色文字是对上述代码进行描述(个人见解,若有错误请指正):
handlerMappings是个List,代码为:private List<HandlerMapping> handlerMappings
代码中,首先检测映射服务this.detectAllHandlerMappings是否为true。( 这里判断是否默认添加所有的HandlerMappings,初始值是默认添加的)
若为true,则获取Map<String, HandlerMapping> matchingBeans,即从ApplicationContext中找到matchingBeans,若其不为null,则将其赋值给handlerMappings,且使用注解的方式进行排序。
如果默认值this.detectAllHandlerMappings为false,则通过ApplicationContex找到指定的bean获取HandlerMapping并排序
上述的代码说来说去,就是为了获取或创建HandlerMapping,若能获取到则跳过,若获取不到则创建,并采用默认的方式创建。
该链接对handlerMappings进行了详细解释:http://www.cnblogs.com/dragonfei/p/6148625.html,我没提取,等有时间再提取。。。
说完这个,没有说调用映射服务的方法,那么接下来再说一下DispatcherServlet类的doDispatch方法:
这个方法跟上面的一样重要,只是这个方法封装了请求的分发过程,代码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
拿上面比较重要一些代码说一下,抽取一部分为下,当浏览器发送一个url请求时,springmvc将得到的是一个HandlerExecutionChain对象,而这个对象就属于上面所说的handlerMappings,他包含了mvc模块的拦截器,即handlerInterceptor和真正处理请求的handler。
而下面这个方法最终调用的是getHandler()这个方法,这个方法就是隶属于handlerMappings对象的。
//根据请求找到对应的handler mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ...... 调用拦截器等 ...... // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
由此,我们看到,我们通过遍历handlerMappings,获取每一个对象,然后从每个对象中获取HandlerExecutionChain,然后就返回给了上面的对象mv,mv即ModelAndView。mv根据映射结果,返回给浏览器。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
对此我们进行一个简单的整合:
当浏览器请求一个url时,假如web应用刚启动,则springMVC首先通过xml或者注解的方式将各个bean加载到applicationContext中。
然后会去创建映射服务,即handlerMappings对,该对象的作用是将url与指定的控制类进行一一对应。在此之前,servlet未被创建。
然后根据url去创建servlet。如果该url未找到对应的控制类则报错。找到则根据对应控制类的相应,返回给浏览器页面。
下面是个简单的流程图(这图是盗的,无意侵犯。。。):
spring的详细流程图在这里(也是盗的。。。):
大概的意思说到了,希望各位能和我一样,能有个大概的了解,毕竟别人写的东西看起来还是很吃力的,其次是希望这文章能让大家对spring底层稍微有点理解。
参考博客:http://www.cnblogs.com/baiduligang/p/4247164.html
参考博客:http://blog.csdn.net/hongxingxiaonan/article/details/47910911
参考博客:http://blog.csdn.net/sunxing007/article/details/4584748