赞
踩
这两天,学习了一下springmvc的源码,主要是学习了启动和调用的流程,主要分以下两部分来记录笔记
1.启动流程
2.调用流程
springmvc源码,先概括的说一下
1、初始化handlerMapping对象、初始化handlerAdapter对象;在初始化handlerMapping对象的时候,会解析所有的bean,将controller和对应的URL存入到对应的map集合中
2、在调用的时候,会调用到org.springframework.web.servlet.DispatcherServlet#doDispatch方法
3、getHandler()获取到处理当前请求的handlerMapping,就是根据请求中的URL去map中找对应的method
4、getAdapter()获取到一个合适的handlerAdapter对象,这里之所以说是合适的,是因为不同的controller方式有不同的处理逻辑
5、执行目标方法
这里有一个细节点:一种是通过反射方式来完成方法调用;一种是通过调用接口实现类中的方法来完成调用
6、进行判断:是需要跳转到视图,还是直接通过流将数据写到浏览器;也就是@ResponseBody和ModelAndView的区分
有三种方式,可以声明一个controller
后面两种原理基本上是一样的,spring默认的handlerMapping有两种:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping;对于@Controller注解的controller,都是由前者来处理的,实现controller接口或者httpRequestHandler接口,是由后者来处理的
handlerAdapter也是类似的
后面两种我们可以认为是一类,都是通过beanName来作为请求的url的;在实际调用方法的时候,这两类方式是有区别的:
@Controller这种方式的方法,是通过反射的方式来完成调用的
后面两种,是通过调用接口实现类中的方法来完成方法调用的
上图是在网上随便扒的一个截图,大致就是springmvc的流程
对于启动流程而言,我们这篇博客,只需要关注
RequestMappingHandlerMapping
BeanNameUrlHandlerMapping
这两个bean的初始化即可,因为这两个bean的初始化是我们这篇博客的重点:url和method是如何对应起来的
我们可以看到,该类间接的实现了InitializingBean接口,所以,在初始化该类的时候,会调用到
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods
这是初始化方法的调用链,这里只把调用链贴出来了,中间的代码不重要,都是几行代码;关键的代码,就是最后面的这个方法
/** * 在初始化时,会调到这里,然后会获取到beanDefinitionMap中的bean,判断当前bean是否是@Controller或者@RequestMapping修饰的类 * 如果是,就调用detectHandlerMethods方法,解析方法的@RequestMapping注解对应的path,然后存入到map集合中 */ protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } /** * 这里涉及到父子容器 * spring容器和springmvc容器 */ String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); /** * 我们姑且可以理解为:这里获取到spring容器中所有的对象 */ for (String beanName : beanNames) { /** * 如果是以"scopedTarget."开头,就跳过,不做处理 */ if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isDebugEnabled()) { logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex); } } /** * 判断当前bean是否是@Controller或者@RequestMapping修饰的bean * 如果当前类是这两个注解修饰的,就在下面的方法中,会解析@RequestMapping对应的URL */ if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } } } handlerMethodsInitialized(getHandlerMethods()); } @Override protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); }
这个方法是来处理所有被@Controller或者@RequestMapping修饰的bean,然后获取到bean对应的method进行解析
/** * @param handler * 在这里其实是根据bean,获取到bean中所有添加了@RequestMapping注解的method,然后把method和url进行映射,并把映射关系存到map中 */ protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { //userType是当前的类名 Class<?> userType = ClassUtils.getUserClass(handlerType); /** * 根据类名获取到所有的方法,这里的key是method,value是@RequestMapping对应的path * key: public void com.springmvc.TestController.test() * value: {[/test]} */ Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { /** * 根据method,获取到当前method上添加的@RequestMapping注解的path属性信息 */ return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } }); if (logger.isDebugEnabled()) { logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods); } /** * 遍历依次解析bean所有的method以及对应的url */ methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); } }
这是解析method对应的url的方法和存入到map的代码;
其中getMappingForMethod(method, userType);是根据method解析method的@RequestMapping信息的代码,这里就不贴出来了,里面的逻辑比较简单
registerHandlerMethod(handler, invocableMethod, mapping);方法会依次遍历method,然后将method和url存入到map集合中
对于该类而言,这是通过bean的后置处理器来完成url和method的映射的
可以看到,该类间接的实现了ApplicationContextAware接口,所以在
org.springframework.context.support.ApplicationContextAwareProcessor#postProcessBeforeInitialization
BeanNameUrlHandlerMapping这个bean初始化的时候,调用到该后置处理器的postProcessBeforeInitialization方法时,会调用到org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#determineUrlsForHandler
这里debug看一下就可以了,中间的跳转逻辑不做过多解释
/** * BeanNameUrlHandlerMapping该bean在初始化的时候,会调用到该方法,和该接口实现了ApplicationContextAware接口有关系 * @param beanName the name of the candidate bean * @return */ @Override protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<>(); /** * 只处理以/开头的beanName * 这个bean处理的是实现了Controller接口或者HttpRequestHandler接口的controller,这两种方式 * 都是需要在bean上添加@Component注解,并制定beanName,beanName就是url,所以,beanName要以/开头 */ if (beanName.startsWith("/")) { urls.add(beanName); } //处理别名 String[] aliases = obtainApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); }
这是核心的代码,其他的就不贴了,大致也是一样的逻辑,将解析得到的urls中的beanName和method存入到一个map集合中
在调用controller的时候,入口我们直接从org.springframework.web.servlet.DispatcherServlet#doDispatch
开始看起
/** * 找到对应的handlerMapping,并将interceptor封装到HandlerExecutionChain * 如果handlerMapping为null,就表示没有找到对应的映射器,返回404 notFound */ mappedHandler = getHandler(processedRequest); if (mappedHandler == 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.对modelAndView的处理 /** * 实际的处理器处理请求,返回结果视图对象 * 如果是@Controller注解的这种方式,是通过反射实现的 * 如果是实现了Controller接口或者实现了HttpRequestHandler接口这种方式,是通过调用实现类的方法来完成的 */ mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
我只贴出来一部分代码
这里是调用的流程,放到下篇博客说吧;内容太多,容易乱
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。