https://img2024.cnblogs.com/blog/3305226/202503/3305226-20250331155133325-143341361.jpg

SpringMvc源码分析学习

SpringMvc源码分析学习

Spring MVC是基于Servlet API构建的原始Web框架,从一开始就包含在对应为spring-webmvc包。它遵循Model-View-Controller (MVC) 设计模式

DispatcherServlet初始化

前端控制器,负责接受客户端请求,并将请求分给合适的处理程序。作为请求处理流程的入口,其实现了Servlet接口。其实就是一个Servlet

image-20250622183509056

所以servlet初始化时,会调用init方法,一直向上父类找,初始化时会调用HttpServletBean的init方法,进行一些bean参数配置然后调用initServletBean()方法

image-20250622184257132

这里实例化了一个Spring容器 ,FrameWorkServlet(DispatcherServlet的父类)的属性WebApplicationContext。也就相当于在DispatcherServlet内部有一个Spring容器,这个Spring容器管理了一些bean以供DispatcherServlet使用。

调试,Tomcat启动时会来调用init方法调用initServletBean()创建spring容器,初始化servlet

image-20250622184734768

image-20250624192738707

我调试使用的springboot环境

容器先收集当前 Servlet 在 web.xml(或注解 )中配置的初始化参数(如 <init-param> ),这里springboot自己用实现类配置了后面会说,封装成 ServletConfig 对象,springboot配置了默认Dispathcherservlet

image-20250624192601052

获取了注解实现类的spring容器AnnotationConfigServletWebServerApplicationContext,包含bean工厂,包含自己写的bean。

外部访问,DispatcherServlet即可调用这里bean

image-20250624192952264

如果使用的不是注解类型,则创建spring容器的xml实现类XmlWebApplicationContext

image-20250624193531690

private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class
//可通过xml或者注解指定默认值,更换spring容器实现类

容器创建后会调用onRefresh

image-20250624200046476

而onRefresh调用了initStrategies,初始化HandlerMappings或HandlerAdapters等

image-20250624200127153

initHandlerMappings,在spring容器中找HandlerMapping,多个则排序,然后如果没有handlerMapping,则会去取一些默认的handlerMapping

	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<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.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.isTraceEnabled()) {
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}

		for (HandlerMapping mapping : this.handlerMappings) {
			if (mapping.usesPathPatterns()) {
				this.parseRequestPath = true;
				break;
			}
		}
	}

getDefaultStratrgies这里会通过查找资源文件然后反射加载默认的配置

image-20250624201048213

image-20250624200957039

调用createDefaultStrategy工厂的方式创建bean

image-20250624201651508

	protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
		return context.getAutowireCapableBeanFactory().createBean(clazz);
	}

RequestMappingHandlerMapping继承于InitializingBean,通过工厂创建bean会调用初始化操作。接着调用父类afterPropertiesSet()

image-20250624201845446

	@Override
	public void afterPropertiesSet() {
		initHandlerMethods();
	}

初始化HandlerMethods,拿到所有的bean调用processCandidateBean

	protected void initHandlerMethods() {
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

其中会判断是否为Controller或RequestMapping

image-20250624202538210

如果是,则进入函数detectHandlerMethods,其中会再判断这个控制里所有方法里面带RequestMapping的方法,封装为RequestMappingInfo包括参数,方法名等等

image-20250624203225789

再注册在mappingRegistry中,这就是spring,controller内存马的原理,在写恶意controller时就是将写好的恶意controller放入mappingRegistry中

image-20250624203549733

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	this.mappingRegistry.register(mapping, handler, method);
}

RequestMappingHandlerAdapter的初始化也是一样,这个类主要是处理请求是一个加了@RequestMapping的方法

其他的适配器如HttpRequestHandlerAdapter则是处理继承了HttpRequestHandler(也能作为控制器)的bean

image-20250624205249975

supports判断是否支持

image-20250624205422666

initControllerAdviceCache主要找到@ControllerAdvice注解存放,这个注解常用于异常处理器

image-20250624205610524

再分别找其中对应注解的方法,分别存放供以后使用。如ModelAttribute.class,InitBinder.class

Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
	public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
			(!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&
					AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
	public static final MethodFilter INIT_BINDER_METHODS = method ->
			AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);

DispatcherServlet如何创建的

这里从正序的角度描述,一步步断点倒着调试过来即可

Tomcat是如何找到Spring并加载?

spring继承Tomcat的ServletContainerInitializer,Tomcat通过SPI机制加载到spring的 SpringServletContainerInitializer,这就把tomcat和spring联系起来了。

SPI机制 我之前SnakeYaml文章中的一条链子介绍过

SPI(Service Provider Interface), JDK内置的一种服务提供发现机制。它的利用方式是通过在ClassPath路径下的META-INF/services文件夹下查找文件,自动加载文件中所定义的类

image-20250624214002908

通过上图注释@HandlesTypes(WebApplicationInitializer.class),找到WebApplicationInitializer的所有实现类并调用onStartup方法

image-20250624214125145

所以我们可以自己写WebApplicationInitializer的实现类配置DispatcherServlet,而springboot已实现SpringBootServletInitializer,因此不用配置springmvc

image-20250624214145858

可以通过继承SpringBootServletInitializer配置configure简化配置

public class CustomServletInitializer extends SpringBootServletInitializer {
    @Override 
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(MyApplication.class) 
                     .properties("spring.mvc.throw-exception-if-no-handler-found=true"); 
    }
}

DispatcherServlet处理请求

Springboot默认配置的DispatcherServlet映射路径是/,可以通过配置server.servlet.context-path配置

image-20250625191539587

TestController

@Controller
public class TestController {

    @GetMapping("/a")
    @ResponseBody
    public String test(String name){
        return "hello "+name;
    }
}

当访问到DispatcherServlet对应的路径(/)后,tomcat便会调用对应Service方法,调用父类FrameworkServlet#service方法,解析方法请求后继续调用父类service方法

image-20250625192159676

调用HttpServlet(Tomcat规范的Servlet方法),调用doGet方法

image-20250625192324154

FrameworkServlet#doGet方法

image-20250625192537636

核心就是调用了doService方法

image-20250625192639766

而doService方法中的核心方法doDispatch处理分发。

image-20250625193824323

首先调用getHandler获取请求对应的handler

image-20250625194325465

getHandler,遍历每一个HandlerMapping,这里主要是用的RequestMappingHandlerMapping处理,所以这里只说其里面做了什么重要处理。其他HandlerMapping如BeanNameUrlHandlerMapping负责的是继承了Controller类且以("/")开头的bean名称

使用getPathWithinApplication获取访问路径,分别获取DispatcherServlet的映射和对uri做处理,这里能联想到由于spring和shiro对路径处理的不同导致权限绕过

image-20250625195457624

image-20250625195442526

lookupHandlerMethod

首先通过路径直接拿RequestMappinginfo,如果不是再将所有mapping遍历匹配,如有多个再选择排序一个最合适的。这里访问/a存在此路径,直接拿到了

image-20250625201121754

通过getMattchingMappings将信息封装在RequestMappinginfo,通过mappingRegistry中的找到对应mapping的handler方法

image-20250625200817401

image-20250625201345415

在获取到handler后会创建一个执行链,包括拦截器和handler

image-20250625210311917

接下来通过HandlerMethod获取对应的HandlerAdapter适配器

image-20250625205049010

同样循环调用遍历HandlerAdapter调用supports方法找对应的adapter,adapter便会通过handlerMethod的类型去调用它适配的方法

image-20250625205244018

调用applyPreHandler

image-20250625210450250

执行所有拦截器的preHandle方法,如果其一个返回false则中断

image-20250625210533806

接下来调用适配器的handler方法,其中会调用真正的方法返回ModelAndView对象

进入handlerInternal,调用invokeHandlerMethod

image-20250630211257232

getDataBinderFactory 找出所有的@InitBinder注解(参数绑定转换)以及生成ModelFactroy,以生成Model对象(包括@ModelAtrribute等注解对Model的处理),然后设置参数解析器,返回值解析器。创建并初始化ModelAndView容器

image-20250630212717127

调用invokeAndHandle,其中会获取参数,解析然后调用真实方法。然后使用返回值处理器解析返回值

image-20250630221219228

invokeAndHandle 中调用getArgumentResolver,其中会遍历解析参数解析器,找到支持的解析器

如一些带注解的解析器

image-20250701171040141

然后调用解析器解析参数

image-20250701172605391

这一步就是获得真实的参数名

image-20250701173140672

调用resolveName去拿值,如果为空则寻找默认值。然后如果设置了类型转换器,如string转date,即会调用转换,以及一些注解的解析。调用doInvoke反射执行原始方法。

image-20250701173406861

接下来就是返回值处理逻辑

直接返回给浏览器或者返回页面

returnValueHandlers初始化了一些返回值处理器,调用handleReturnValue

image-20250701204517340

handleReturnValue调用selectHanler选择返回值解析器,

image-20250701204724996

如存在注解ResponseBody便会使此解析器

image-20250701205012211

RequestResponseBodyyMethodProcessor调用handleReturnValue处理返回,调用writeWithMessageConverters

image-20250701205720936

首先判断是否为String类型 设置值类型以及参数类型

接下来会调用循环找消息转换器也是同样的方式,这里处理String使用的是StringHttpMessageConverter。然后调用getAdvice().beforeBodyWrite.这里就是找有没有advice,在写入Body前会先运行Advice中的beforeBodyWriteimage-20250701211807439

image-20250701213722659

这个转换器支持返回纯文本以及任意类型,在上述的canWrite中,valueType以及这个选择的MediaType(浏览器同意返回的格式)都是支持的

image-20250701212100437

最后直接写入即可

image-20250701213852523

还有很多其他的返回值处理器,如ViewNameMethodReturnValueHandler,确认需要重定向,以及存入mavContainer,后面跳转等

image-20250701220154133

DispatcherServlet页面渲染

在调用invokeAndHandle处理返回值后会调用getModelAndView获取mav

image-20250702014508240

首先会调用updateModel,将带有@SessionAttributes中的值存入model中,设置视图名。创建mav返回

image-20250702015416698

如带有@ResponseBody会在处理返回值时就设置RequestHandled为true,不再返回视图。

image-20250702014226482

接下调用processDispatchResult进行渲染

image-20250702193232919

其中调用render,用初始化的视图解析器解析视图名

image-20250702193308586

后面这一点调试器坏了,只能直接看代码了

demo中核心的处理是这里,创建视图,前面是重定向和转发的处理,然后进入父类createView

image-20250702200316089

一直进入到UrlBasedViewResolver#buildView,创建view,设置url。这里前缀和后缀都没有设置所以view的url就是index

image-20250702200517855

view.rend寻找渲染文件,重点是这个方法

image-20250702201037370

exposeModelAsRequestAttributes,遍历model设置在request中,跳转页面后即可使用request中的值

image-20250702201123863

再调用这个函数找路径,即为之前设置的index

image-20250702201246400

使用此路径生成RequestDispatcher(Tomcat的后端转发器)此时转发的也就是Dispatcher的根路径加上index --> http://..../app/index,转发此请求 但是显然是找不到此路径的

image-20250702201547243

能找到index.jsp的方法,或者通过forward:/index.jsp

springmvc通过添加前后缀找到对应的jsp文件

@EnableWebMvc
public class AppConfig implements WebMvcConfigurer{
    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}

把整个springmvc的流程简单梳理了一遍,里面的细节实在太多,只能粗略的看一遍,有些看了一遍但是没有记录,篇幅太长了,以后遇到需要深究的地方会再仔细看的,对框架的理解又稍微多了点。

学习视频:

【目前B站讲的最好的SpringMVC源码解析课(2023最新版)】https://www.bilibili.com/video/BV1wg41147yL?p=29&vd_source=2884b80d333f3bfc8048b360e6195550

posted @ 2025-07-02 21:13  kudo4869  阅读(53)  评论(0)    收藏  举报