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());

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring MVC 是 Spring 框架的一个重要模块,用于开发 Web 应用程序。它采用MVC(Model-View-Controller)设计模式,将应用程序分成三个部分:模型、视图和控制器,以便进行分层开发。 Spring MVC 的源码主要包括以下几个部分: 1. DispatcherServlet DispatcherServlet 是 Spring MVC 的核心控制器,它负责接收用户请求并将请求分发给相应的处理器。它的源码主要包括以下几个方法: - init():初始化 DispatcherServlet。 - doDispatch():分发请求给处理器。 - processRequest():处理 HTTP 请求。 - render():渲染视图。 2. HandlerMapping HandlerMapping 是一个接口,它负责将请求映射到相应的处理器。Spring MVC 提供了多种 HandlerMapping 实现,如 BeanNameUrlHandlerMapping、RequestMappingHandlerMapping 等。它的源码主要包括以下几个方法: - getHandler():根据请求获取处理器。 - registerHandler():注册处理器。 - getHandlerExecutionChain():获取处理器执行链。 3. HandlerAdapter HandlerAdapter 是一个接口,它负责执行处理器。Spring MVC 提供了多种 HandlerAdapter 实现,如 SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter 等。它的源码主要包括以下几个方法: - supports():判断是否支持该处理器。 - handle():执行处理器。 4. ViewResolver ViewResolver 是一个接口,它负责将逻辑视图名映射到实际视图。Spring MVC 提供了多种 ViewResolver 实现,如 InternalResourceViewResolver、JsonViewResolver 等。它的源码主要包括以下几个方法: - resolveViewName():根据逻辑视图名获取实际视图。 - setViewClass():设置视图类。 5. HandlerInterceptor HandlerInterceptor 是一个接口,它负责在处理器执行前后拦截请求。Spring MVC 提供了多种 HandlerInterceptor 实现,如 LocaleChangeInterceptor、ThemeChangeInterceptor 等。它的源码主要包括以下几个方法: - preHandle():处理器执行前拦截请求。 - postHandle():处理器执行后拦截请求。 - afterCompletion():处理器完成后拦截请求。 这些是 Spring MVC 的主要组件,它们共同构成了 Spring MVC 的框架。理解 Spring MVC 的源码不仅需要对 Java Web 开发有深入的了解,还需要对 Spring Framework 有一定的了解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值