SpringMVC请求流程图:
SpringMVC工作流程概述:
1、客户端向web服务器(如tomcat)发送一个http请求,web服务器对http请求进行解析,解析后的URL地址如果匹配了DispatcherServlet的映射路径(通过web.xml中的servlet-mapping配置),web容器就将请求交给DispatcherServlet处理。
2、DispatcherServlet接收到这个请求后,再对URL进行解析,得到请求资源标识符(URI)。然后调用相应方法得到的HandlerMapping对象,再根据URI,调用这个对象的相应方法获得Handler对象以及它对应的拦截器。(在这里只是获得了Handler对象,并不会操作它,在SpringMVC中,是通过HandlerAdapter对Handler进行调用、控制的)
3、DispatcherServlet根据得到的Handler对象,选择一个合适的HandlerAdapter,创建其实例对象,执行拦截器中的preHandler()方法。
4、在拦截器方法中,提取请求中的数据模型,填充Handler入参,所以所有准备工作都已做好,开始执行Handler(我们写的controller代码并不是能被直接执行,需要有刚才那些操作,才能转变为Handler被执行)。
5、Handler执行完毕后返回一个ModelAndView对象给DispatcherServlet。
6、这个ModleAndView只是一个逻辑视图,并不是真正的视图,DispatcherServlet通过ViewResolver视图解析器将逻辑视图转化为真正的视图(通俗理解为将视图名称补全,如加上路径前缀,加上.jsp后缀,能指向实际的视图)。
7、DispatcherServlet通过Model将ModelAndView中得到的处数据解析后用于渲染视图。将得到的最终视图通过http响应返回客户端。
了解了大概流程,然后就需要看源代码了。
HandlerMapping接口的实现(只举了我认识的几个) :
BeanNameUrlHandlerMapping :通过对比url和bean的name找到对应的对象
SimpleUrlHandlerMapping :也是直接配置url和对应bean,比BeanNameUrlHandlerMapping功能更多
DefaultAnnotationHandlerMapping : 主要是针对注解配置@RequestMapping的,已过时
HandlerAdapter 接口实现:
RequestMappingHandlerAdapter : 和上面的RequestMappingHandlerMapping配对使用,针对@RequestMapping 首先就是SpringMVC的入口类,DispatcherServlet,它实现了Servlet接口,不再详细说DispatcherServlet的细节,不然又是一大堆的内容。每次请求都会调用它的doService->doDispatch,我们关注的重点就在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);
- //这个是重点,第一步由HandlerMapping找到对应的handler
- // 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,然后由它来调度执行handler的方法
- 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;
- }
- try {
- // Actually invoke the handler.
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- }
- finally {
- if (asyncManager.isConcurrentHandlingStarted()) {
- return;
- }
- }
- applyDefaultViewName(request, mv);
- mappedHandler.applyPostHandle(processedRequest, response, mv);
- }
- catch (Exception ex) {
- dispatchException = ex;
- }
- processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
- }
- catch (Exception ex) {
- triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
- }
- catch (Error err) {
- triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
- }
- finally {
- if (asyncManager.isConcurrentHandlingStarted()) {
- // Instead of postHandle and afterCompletion
- mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
- return;
- }
- // Clean up any resources used by a multipart request.
- if (multipartRequestParsed) {
- cleanupMultipart(processedRequest);
- }
- }
- }
第一步详细查看:
- 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;
- }
可以看到就是通过遍历所有已注册的HandlerMapping来找到对应的handler,然后构建出一个HandlerExecutionChain,它包含了handler和HandlerMapping本身的一些拦截器,如下 :
- public class HandlerExecutionChain {
- private final Object handler;
- private HandlerInterceptor[] interceptors;
- private List<HandlerInterceptor> interceptorList;
- //其他代码省略
- }
其中HandlerMapping的getHandler实现:
- public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
- Object handler = getHandlerInternal(request);
- if (handler == null) {
- handler = getDefaultHandler();
- }
- if (handler == null) {
- return null;
- }
- // Bean name or resolved handler?
- if (handler instanceof String) {
- String handlerName = (String) handler;
- handler = getApplicationContext().getBean(handlerName);
- }
- return getHandlerExecutionChain(handler, request);
- }
这里的getHandlerInternal(request)是个抽象方法,由具体的HandlerMapping来实现,获取到的handler如果为空,则获取默认配置的handler,如果handler为String类型,则表示这个则会去Spring容器里面去找这样名字的bean。
再看下BeanNameUrlHandlerMapping的getHandlerInternal(request)的具体实现(通过一系列的接口设计,之后再好好看看这个设计,到BeanNameUrlHandlerMapping这只用实现该方法中的一部分),如下
- public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
- /**
- * Checks name and aliases of the given bean for URLs, starting with "/".
- */
- @Override
- protected String[] determineUrlsForHandler(String beanName) {
- List<String> urls = new ArrayList<String>();
- if (beanName.startsWith("/")) {
- urls.add(beanName);
- }
- String[] aliases = getApplicationContext().getAliases(beanName);
- for (String alias : aliases) {
- if (alias.startsWith("/")) {
- urls.add(alias);
- }
- }
- return StringUtils.toStringArray(urls);
- }
- }
- protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
- for (HandlerAdapter ha : this.handlerAdapters) {
- if (logger.isTraceEnabled()) {
- logger.trace("Testing handler adapter [" + ha + "]");
- }
- if (ha.supports(handler)) {
- return ha;
- }
- }
- throw new ServletException("No adapter for handler [" + handler +
- "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
- }
我们来看下HttpRequestHandlerAdapter的supports(handler)方法:
- public class HttpRequestHandlerAdapter implements HandlerAdapter {
- @Override
- public boolean supports(Object handler) {
- //就是判断handler是否实现了HttpRequestHandler接口
- return (handler instanceof HttpRequestHandler);
- }
- @Override
- public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- //若handler实现了HttpRequestHandler接口,则调用该接口的方法,执行我们在该方法中写的业务逻辑
- ((HttpRequestHandler) handler).handleRequest(request, response);
- return null;
- }
- @Override
- public long getLastModified(HttpServletRequest request, Object handler) {
- if (handler instanceof LastModified) {
- return ((LastModified) handler).getLastModified(request);
- }
- return -1L;
- }
- }
同理SimpleControllerHandlerAdapter也是这样类似的逻辑
- public class SimpleControllerHandlerAdapter implements HandlerAdapter {
- @Override
- public boolean supports(Object handler) {
- return (handler instanceof Controller);
- }
- @Override
- public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- return ((Controller) handler).handleRequest(request, response);
- }
- @Override
- public long getLastModified(HttpServletRequest request, Object handler) {
- if (handler instanceof LastModified) {
- return ((LastModified) handler).getLastModified(request);
- }
- return -1L;
- }
- }
剩余两个AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter就比较复杂,我也没看。
了解过程了之后,然后就是最重要的也是经常配置出问题的地方。DispatcherServlet的handlerMappings和handlerAdapters的来源问题。
DispatcherServlet初始化的时候,会调用一个方法如下:
- protected void initStrategies(ApplicationContext context) {
- initMultipartResolver(context);
- initLocaleResolver(context);
- initThemeResolver(context);
- //初始化一些HandlerMapping
- initHandlerMappings(context);
- //初始化一些HandlerAdapter
- initHandlerAdapters(context);
- initHandlerExceptionResolvers(context);
- initRequestToViewNameTranslator(context);
- initViewResolvers(context);
- initFlashMapManager(context);
- }
这里可以看到,它会初始化一些HandlerMapping和HandlerAdapter,这两个方法非常重要,理解了这两个方法你就会知道,配置不对问题出在哪里,下面具体看下这两个方法:
- 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.
- OrderComparator.sort(this.handlerMappings);
- }
- }
- else {
- 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.
- if (this.handlerMappings == null) {
- this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
- if (logger.isDebugEnabled()) {
- logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
- }
- }
- }
detectAllHandlerMappings是DispatcherServlet的一个属性,你是可以在web.xml中配置的,默认是true,如果为true,则会去从工程SpringMVC.xml文件中去探测所有实现了HandlerMapping的bean,如果有,则加入DispatcherServlet的handlerMappings中。如果detectAllHandlerMappings为false,则直接去容器中找id="handlerMapping"且实现了HandlerMapping的bean.如果以上都没找到,则会去加载默认的HandlerMapping。
- /** Detect all HandlerMappings or just expect "handlerMapping" bean? */
- private boolean detectAllHandlerMappings = true;
若没有配置HandlerMapping,所以它会去加载默认的,下面看看默认的配置是什么
- protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
- String key = strategyInterface.getName();
- //defaultStrategies存储了默认的配置
- String value = defaultStrategies.getProperty(key);
- if (value != null) {
- String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
- List<T> strategies = new ArrayList<T>(classNames.length);
- for (String className : classNames) {
- try {
- Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
- Object strategy = createDefaultStrategy(context, clazz);
- strategies.add((T) strategy);
- }
- catch (ClassNotFoundException ex) {
- throw new BeanInitializationException(
- "Could not find DispatcherServlet's default strategy class [" + className +
- "] for interface [" + key + "]", ex);
- }
- catch (LinkageError err) {
- throw new BeanInitializationException(
- "Error loading DispatcherServlet's default strategy class [" + className +
- "] for interface [" + key + "]: problem with class file or dependent class", err);
- }
- }
- return strategies;
- }
- else {
- return new LinkedList<T>();
- }
- }
继续看看defaultStrategies是如何初始化的:
- private static final Properties defaultStrategies;
- static {
- // Load default strategy implementations from properties file.
- // This is currently strictly internal and not meant to be customized
- // by application developers.
- try {
- //这里的DEFAULT_STRATEGIES_PATH就是DispatcherServlet.properties
- ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
- defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
- }
- catch (IOException ex) {
- throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
- }
- }
Spring MVC 用法
了解完概念,再来介绍一下用法。这里的用法我只介绍用注解的方法,这种比较常用一些。Spring MVC中大部分的代码都已经写好了,自己需要做的就是需要写业务处理。
首先拦截请求,用注解注入一个类,在这个类中编写方法,用@RequestMapping注解,value就是uri,params就是参数,也可以再加个method,判断http的方法是GET还是POST,函数的体的参数和返回类型完全自己定义,没有特殊的格式。(参数,看了源码发现其实这些是用java反射做的,参数是不限制个数的那种函数申明方式,返回类型是object,所以这些返回类型和参数没有固定的)。不过一般可以定义自己需要的一些参数。
返回值的作用
没有@ResponesBody
没有这个注解,这注意返回的东西,是作为资源调用的标识使用,例如你返回一个a的string类型,会默认解析为找一个名字叫a的html或者js之类的资源文件。如果要返回其他类型,就得看看spring默认支不支持解析咯,有可能自己还要写解析的类,这个类就是ViewResolver,专门解析这些返回对象的。当然这里的资源都是静态的,如果需要一些动态的参数,这时候就要用到modelMap了,就是在ViewResolver处理之后再用到modelMap里面的一些值来动态处理。
有@ResponseBody
如果这个注解,就意味着返回的对象不是作为资源标识符来使用,而是作为http返回的包体内容。上面那种是属于你返回个东西,spring帮你解析成view了返回,这是把你返回的东西直接当做内容返回了。顾名思义ResponseBody。这里返回的内容也是需要解析,解析这个的叫converer,把返回的对象解析成可以放进http包体的内容。Spring默认支持了几种返回,例如string,json,还有byteArray(预知详情,自己可以试着看看源码)。所以如果你要返回一个你自己写的对象,那就要自己写个converer才行,不然最后没有返回的。
其实Spring MVC也就是基于Servlet的,所以不用Spring MVC这种办法直接用Servlet也是可以实现网络请求处理的。而且spring通过大量调用判断其实是会牺牲掉一些性能的。但是依旧还是有人使用spring,其实就在于大型项目管理和维护比较比较方便一点这样逻辑会更清楚,因为这里将model,view,controller给分开了,处理逻辑和返回界面在不同的地方,如果用servlet势必会搅在一起。这大概就是spring的一个好处。