这两天,学习了一下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的区分
springmvc应用
实现controller的方式
有三种方式,可以声明一个controller
- 在类上添加@Controller注解,在方法中通过@RequestMapping注解指定url
- 实现Controller接口,这种方式,需要在类名增加@Component(“/映射地址”)
- 实现HttpRequestHandler接口,在类上加@Component(“/映射地址”)
后面两种原理基本上是一样的,spring默认的handlerMapping有两种:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping;对于@Controller注解的controller,都是由前者来处理的,实现controller接口或者httpRequestHandler接口,是由后者来处理的
handlerAdapter也是类似的
后面两种我们可以认为是一类,都是通过beanName来作为请求的url的;在实际调用方法的时候,这两类方式是有区别的:
@Controller这种方式的方法,是通过反射的方式来完成调用的
后面两种,是通过调用接口实现类中的方法来完成方法调用的
上图是在网上随便扒的一个截图,大致就是springmvc的流程
启动流程
对于启动流程而言,我们这篇博客,只需要关注
RequestMappingHandlerMapping
BeanNameUrlHandlerMapping
这两个bean的初始化即可,因为这两个bean的初始化是我们这篇博客的重点:url和method是如何对应起来的
RequestMappingHandlerMapping
我们可以看到,该类间接的实现了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集合中
BeanNameUrlHandlerMapping
对于该类而言,这是通过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());
我只贴出来一部分代码
这里是调用的流程,放到下篇博客说吧;内容太多,容易乱