springmvc学习一初始化源码

这两天,学习了一下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

  1. 在类上添加@Controller注解,在方法中通过@RequestMapping注解指定url
  2. 实现Controller接口,这种方式,需要在类名增加@Component("/映射地址")
  3. 实现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());

我只贴出来一部分代码
这里是调用的流程,放到下篇博客说吧;内容太多,容易乱

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/108962732