SpringMVC源码解析(一):web容器启动流程

SpringMVC源码系列文章

SpringMVC源码解析(一):web容器启动流程


目录

  • 一、SpringMVC全注解配置
    • 1、pom文件
    • 2、web容器初始化类(代替web.xml)
    • 3、SpringMVC配置类(代替springmvc.xml)
    • 4、测试Controller
  • 二、SpringServletContainerInitializer
    • 1、web容器初始化入口
    • 2、SpringServletContainerInitializer的作用解析
  • 三、自定义配置类的加载
    • 1、AbstractDispatcherServletInitializer注册前端控制器
      • 1.1、创建web注解容器
      • 1.2、创建DispatcherServlet对象
      • 1.3、注册过滤器
    • 2、DispatcherServlet初始化
  • 四、@EnableWebMvc解析
    • 1、WebMvcConfigurer接口
    • 2、DelegatingWebMvcConfiguration
    • 3、WebMvcConfigurationSupport
      • 3.1、处理器映射器和处理器适配器
      • 3.2、RequestMappingHandlerMapping映射器
        • 3.2.1、实例化
          • 3.2.1.1、获取所有拦截器
          • 3.2.1.2、获取所有跨域配置
        • 3.2.2、初始化
          • 3.2.2.1、获取候选bean
          • 3.2.2.2、获取HandlerMethod并统计数量
      • 3.3、RequestMappingHandlerAdapter适配器
        • 3.3.1、实例化
          • 3.3.1.1、获取消息转换器
  • 总结

一、SpringMVC全注解配置

1、pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         	http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xc.mvc</groupId>
    <artifactId>springmvc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>war</packaging>

    <dependencies>
        <!-- SpringMVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.27</version>
        </dependency>

        <!-- ServletAPI -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <!--配置Maven中Java的编译版本 -->
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2、web容器初始化类(代替web.xml)

/**
 * web工程的初始化类,用来代替web.xml
 * 以下配置的都是以前在web.xml中配置的
 */
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * Spring的配置,目前不涉及Spring,这里设置为空
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[0];
    }

    /**
     * SpringMVC的配置
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{SpringMVCConfig.class};
    }

    /**
     * 用于配置DispatcherServlet的映射路径
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 注册过滤器
     */
    @Override
    protected Filter[] getServletFilters() {
        // 字符编码过滤器
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        // HiddenHttpMethodFilter 用于支持 PUT、DELETE 等 HTTP 请求
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
    }
}

3、SpringMVC配置类(代替springmvc.xml)

// 将当前类标识为一个配置类
@Configuration
// 仅仅扫描@Controller、@RestController
@ComponentScan(
        value = "com.xc",
        includeFilters = {@ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                classes = {Controller.class, RestController.class}
        )},
        // 默认扫描 @Component @Repository, @Service, or @Controller
        useDefaultFilters = false 
)
// mvc注解驱动
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer {
    // 拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        MyInterceptor myInterceptor = new MyInterceptor();
        registry.addInterceptor(myInterceptor).addPathPatterns("/**");
    }
}

拦截器

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("处理器方法前调用");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("处理器方法后调用");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("渲染完成后调用");
    }
}

4、测试Controller

// 接受User对象修改并返回
@PostMapping("/test")
@ResponseBody
public User test(@RequestBody User user) {
    // 修改名字为李四然后返回给前台
    user.setName("李四");
    System.out.println(user);
    return user;
}

启动tomcat发送请求结果

在这里插入图片描述

二、SpringServletContainerInitializer

1、web容器初始化入口

  • 在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等
    • servlet规范中通过ServletContainerInitializer接口实现此功能
  • 需要在对应的jar包的META-INF/services目录创建一个名为javax.servlet.ServletContainerInitializer的文件
    • 文件内容指定具体的ServletContainerInitializer实现类

在这里插入图片描述

ps:JDK会自动加载META-INF/services目录下的类(深入理解SPI机制)
容器在启动应用的时候,会扫描当前应用每一个jar包里面META-INF/services指定的实现类(tomcat默认读取)

2、SpringServletContainerInitializer的作用解析

  • @HandlesTypes注解作用
    • 获取到所有的实现了WebApplicationInitializer接口的类,然后赋值给onStartup方法的webAppInitializerClasses参数
    • 官方话术为,获取当前类(SpringServletContainerInitializer)感兴趣的类(WebApplicationInitializer)信息
  • 获取到WebApplicationInitializer实现类的Class集合,反射创建对象,遍历调用对象的onStartup方法
// SpringServletContainerInitializer类
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = Collections.emptyList();
		
		// 如果找不到WebApplicationInitializer的实现类,webAppInitializerClasses就为null
		if (webAppInitializerClasses != null) {
			initializers = new ArrayList<>(webAppInitializerClasses.size());
			for (Class<?> waiClass : webAppInitializerClasses) {
				// 判断实现类不是接口抽象类,即正常的接口实现类
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						// 反射创建对象,并添加到集合中,后面统一遍历调用onStartup方法
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}
		
		// 集合为空,证明没找到实现类,直接返回
		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}
		
		// 排序,证明WebApplicationInitializer的实现类有先后顺序
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			// 调用WebApplicationInitializer接口实现类的onStartup方法
			initializer.onStartup(servletContext);
		}
	}
}

// WebApplicationInitializer接口
public interface WebApplicationInitializer {
	/**
	 * 初始化此Web应用程序所需的任何Servlet、过滤器、侦听器上下文参数和属性配置给定的ServletContext
	 */
	void onStartup(ServletContext servletContext) throws ServletException;
}

WebApplicationInitializer接口与自定义配置类WebAppInitalizer(代替web.xml)关系

  • WebApplicationInitializer初始化核心接口,onStartup方法初始化Servelt、过滤器等
  • WebAppInitalizer即为WebApplicationInitializer的实现类,也就是SpringServletContainerInitializer要找的感兴趣的类,获取到多个放到集合initializer中,然后排序,最后遍历调用onStartup方法

在这里插入图片描述

  总结SpringServletContainerInitializer作用:加载自定义的WebApplicationInitializer初始化核心接口的实现类WebAppInitializer,调用onStartup方法来实现web容器初始化。

在这里插入图片描述

三、自定义配置类的加载

自定义配置类WebAppInitializer(代替web.xml)的类图如下:

在这里插入图片描述
  由上一节可知,web容器初始化工作会调用自定义配置类的onStartup方法,那就是根据类图从下往上找onStartup方法调用,WebAppInitializer和AbstractAnnotationConfigDispatcherServletInitializer中都没有onStartup方法,那么首先进入AbstractDispatcherServletInitializer重写的onStartup方法,核心内容注册前端控制器

在这里插入图片描述

1、AbstractDispatcherServletInitializer注册前端控制器

  • getServletMappings():调用自定义配置类配置DispatcherServlet的映射路径的方法
  • getServletFilters():调用自定义配置类注册过滤器的方法
// AbstractDispatcherServletInitializer类的方法
protected void registerDispatcherServlet(ServletContext servletContext) {
	// 获取servlet名称,常量“dispatcher”
	String servletName = getServletName();
	// 创建一个web应用程序子容器
	WebApplicationContext servletAppContext = createServletApplicationContext();
	// 创建DispatcherServlet对象,将上下文设置到dispatcherServlet中
	FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
	// 设置servlet容器初始化参数(这里不设置一般默认为null)
 	dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
	// 把servlet添加到Tomcat容器中
	ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
	if (registration == null) {
		throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
				"Check if there is another servlet registered under the same name.");
	}
	// 将前端控制器初始化提前到服务器启动时,否则调用时才会初始化
	registration.setLoadOnStartup(1);
	// 添加servlet映射,拦截请求
	// 调用自定义配置类重写的getServletMappings方法
	registration.addMapping(getServletMappings());
	// 设置是否支持异步,默认true
	registration.setAsyncSupported(isAsyncSupported());
	
	// 获取所有的过滤器getServletFilters方法
	// 调用自定义配置类重写的getServletMappings方法
	Filter[] filters = getServletFilters();
	if (!ObjectUtils.isEmpty(filters)) {
		for (Filter filter : filters) {
			registerServletFilter(servletContext, filter);
		}
	}
	// 空方法,可以再对dispatcherServlet进行处理
	customizeRegistration(registration);
}

1.1、创建web注解容器

  • getServletConfigClasses():调用自定义配置类配置SpringMVC配置的方法
// AbstractAnnotationConfigDispatcherServletInitializer类方法
protected WebApplicationContext createServletApplicationContext() {
	AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
	// 调用自定义配置类的设置子容器配置文件的方法
	Class<?>[] configClasses = getServletConfigClasses();
	if (!ObjectUtils.isEmpty(configClasses)) {
		context.register(configClasses);
	}
	return context;
}

1.2、创建DispatcherServlet对象

  • 创建DispatcherServlet对象,传入web容器
// AbstractDispatcherServletInitializer类方法
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
	return new DispatcherServlet(servletAppContext);
}

1.3、注册过滤器

  • 将过滤器添加到Tomcat容器的过滤器集合中
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
	String filterName = Conventions.getVariableName(filter);
	Dynamic registration = servletContext.addFilter(filterName, filter);

	...

	registration.setAsyncSupported(isAsyncSupported());
	registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
	return registration;
}
  • 对于注册过滤器是Tomcat的内容,之前文章Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper中有介绍

在这里插入图片描述

小结一下

  • 目前为止代替web.xml的配置类中内容加载完成
    • 创建web注解容器,此时只是创建出来,还没有初始化
    • 创建DispatcherServlet并设置映射路径
    • 注册过滤器
  • 接下来的入口在DispatcherServlet这里,因为其本质是Servlet,那么就会涉及到Tomcat初始化Servelt

2、DispatcherServlet初始化

DispatcherServlet类图如下:

在这里插入图片描述

  Tomcat启动容器加载Servelt(这里是DispatcherServlet)并初始化,就会调用到这里。之前文章Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper中有介绍。

在这里插入图片描述

  • initWebApplicationContext方法内核心内容就是调用configureAndRefreshWebApplicationContext这个方法
  • 看这方法名字中文直译:配置和刷新web容器
  • 核心内容就是最后一句,web容器的刷新

在这里插入图片描述

  • 容器刷新抽象类AbstractApplicationContextrefresh方法,看过spring源码的应该很熟悉
  • web容器spring容器都间接继承了AbstractApplicationContext,容器刷新都调用如下方法
  • 关于spring的源码Spring源码解析(三):bean容器的刷新之前介绍

在这里插入图片描述

  容器初始化时候有个很重要的bean工厂后置处理器ConfigurationClassPostProcessor,作用就是解析@Configuration,@Import,@ComponentScan,@Bean等注解给bean容器添加bean定义,之前文章Spring源码解析(六):bean工厂后置处理器ConfigurationClassPostProcessor有介绍。

  接下来的入口就在自定义g配置类SpringMVCConfi这里,因为它的配置类注解@Configuration(也是@Component),@ComponentScan(扫描@Controller注解)@EnableWebMvc(导入DelegatingWebMvcConfiguration.class)注解都会被扫描解析到。

在这里插入图片描述

四、@EnableWebMvc解析

@EnableWebMvc

在这里插入图片描述

1、WebMvcConfigurer接口

  在讲DelegatingWebMvcConfiguration之前先说下WebMvcConfigurer接口,因为下面内容都是围绕着WebMvcConfigurer接口展开的。

  WebMvcConfigurer是一个接口,它提供了一种扩展SpringMVC配置的方式。通过实现WebMvcConfigurer接口,可以定制化SpringMVC的配置例如添加拦截器、资源处理、视图解析器等。

public interface WebMvcConfigurer {
	...
	
	// 配置拦截器
	default void addInterceptors(InterceptorRegistry registry) {
	}

	// 配置跨域
	default void addCorsMappings(CorsRegistry registry) {
	}

	// 配置视图解析器
	default void configureViewResolvers(ViewResolverRegistry registry) {
	}

	// 配置消息转换器(此时可能还没有转换器)
	default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
	}
	
	// 扩展消息转换器(至少已存在默认转换器)
	default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
	}

	// 配置异常处理器
	default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
	}
	
	...
}

2、DelegatingWebMvcConfiguration

DelegatingWebMvcConfiguration的类图如下:
在这里插入图片描述

  • 如果开发者或者第三方想要配置拦截器、消息转换器的等配置,只要实现WebMvcConfigurer接口重写对应方法即可
  • 通过setConfigurers方法讲所有WebMvcConfigurer接口实现类注入进来,放入configurers的List<WebMvcConfigurer> delegates属性中
  • 下面会讲到什么时候触发调用DelegatingWebMvcConfiguration中重写的MVC配置方法
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
	// WebMvcConfigurerComposite实现WebMvcConfigurer,内部有个WebMvcConfigurer集合
	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	// 注入一组WebMvcConfigurer,这些WebMvcConfigurer由开发人员提供,或者框架其他部分提供
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
	
	...
	
	// 如下方法都是重写父类WebMvcConfigurationSupport
	// 与WebMvcConfigurer接口中的方法一样,配置拦截器、跨域配置等
	@Override
	protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
		this.configurers.configureDefaultServletHandling(configurer);
	}

	@Override
	protected void addInterceptors(InterceptorRegistry registry) {
		this.configurers.addInterceptors(registry);
	}

	@Override
	protected void addCorsMappings(CorsRegistry registry) {
		this.configurers.addCorsMappings(registry);
	}

	@Override
	protected void configureViewResolvers(ViewResolverRegistry registry) {
		this.configurers.configureViewResolvers(registry);
	}

	@Override
	protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		this.configurers.configureMessageConverters(converters);
	}

	@Override
	protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
	}

	...
}

  调用DelegatingWebMvcConfiguration重写的MVC配置方法实际就是对应的配置添加到对应的注册器中。如所有的拦截器都会被添加到InterceptorRegistry(拦截器注册器)、所有跨域配置则会被添加到CorsRegistry(跨域注册器),不用说对应的注册器中肯定维护着对应的配置集合。

// WebMvcConfigurerComposite类
class WebMvcConfigurerComposite implements WebMvcConfigurer {
	private final List<WebMvcConfigurer> delegates = new ArrayList<>();
	
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.addInterceptors(registry);
		}
	}
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.addCorsMappings(registry);
		}
	}
	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.configureViewResolvers(registry);
		}
	}
	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.configureMessageConverters(converters);
		}
	}
}

3、WebMvcConfigurationSupport

  在上面说到WebMvcConfigurationSupport类中定义了与WebMvcConfigurer接口一样的配置方法,都是空实现,由子类DelegatingWebMvcConfiguration重写,遍历所有WebMvcConfigurer的实现类,将对应配置添加到对应注册器中。

  另外一方面在WebMvcConfigurationSupport类中有很多@Bean方法,即bean定义,返回值即为创建的bean对象。其中有两个很重要,映射器RequestMappingHandlerMapping适配器RequestMappingHandlerAdapter

3.1、处理器映射器和处理器适配器

映射器HandlerMapping

  • HandlerMapping映射器作用:主要是根据request请求匹配/映射上能够处理当前request的Handler
  • 定义Handler的方式有很多
    • 最常用的@Controller,结合@RequestMapping("/test")
    • 实现Controller接口,实现handleRequest方法,结合@Component(“/test”)
    • 实现HttpRequestHandler接口,实现handleRequest方法,结合@Component(“/test”)
  • 以上三种方式都需要生成请求和Handler的映射
    • 所以抽象出一个接口:HandlerMapping,有很多实现类
    • @RequestMapping注解的Handler使用RequestMappingHandlerMapping处理
    • 其他方式会使用BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping

适配器HandlerAdapter

  • 上一步中不同映射器通过请求映射到的Handler不确定类型
    • @RequestMapping注解方式获取到的是具体的Method
    • 其他实现接口方式获取到的是具体的Class
  • 此时拿到Handler去执行具体的方法时候,方式是不统一
  • 那么适配器就出现了
  • RequestMappingHandlerAdapter就是处理@RequestMapping注解的Handler

3.2、RequestMappingHandlerMapping映射器

  • 一句话解释它:解析@RequestMapping注解
3.2.1、实例化
// WebMvcConfigurationSupport类方法
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
		@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
	// 创建RequestMappingHandlerMapping对象
	RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
	mapping.setOrder(0);
	// 设置所有的拦截器,并排序
	mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
	mapping.setContentNegotiationManager(contentNegotiationManager);
	// 设置所有的跨域配置
	mapping.setCorsConfigurations(getCorsConfigurations());

	// 省略各种匹配url的属性,如url正则匹配等等,我们这次只考虑url正常匹配
	...

	return mapping;
}
3.2.1.1、获取所有拦截器

  这里先创建拦截器注册器InterceptorRegistry,然后调用DelegatingWebMvcConfiguration重写WebMvcConfigurationSupport的添加拦截器方法addInterceptors,这样所有拦截器就都会被添加到InterceptorRegistry registry中。

// WebMvcConfigurationSupport
protected final Object[] getInterceptors(
		FormattingConversionService mvcConversionService,
		ResourceUrlProvider mvcResourceUrlProvider) {

	if (this.interceptors == null) {
		InterceptorRegistry registry = new InterceptorRegistry();
		// 这里就是调用DelegatingWebMvcConfiguration重写的方法
		addInterceptors(registry);
		registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
		registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
		this.interceptors = registry.getInterceptors();
	}
	return this.interceptors.toArray();
}
3.2.1.2、获取所有跨域配置

  也是先创建跨域注册器CorsRegistry,然后调用然后调用DelegatingWebMvcConfiguration重写WebMvcConfigurationSupport的添加跨域方法addCorsMappings,也是添加到CorsRegistry registry中。

// WebMvcConfigurationSupport
protected final Map<String, CorsConfiguration> getCorsConfigurations() {
	if (this.corsConfigurations == null) {
		CorsRegistry registry = new CorsRegistry();
		addCorsMappings(registry);
		this.corsConfigurations = registry.getCorsConfigurations();
	}
	return this.corsConfigurations;
}
3.2.2、初始化

RequestMappingHandlerMapping的复杂类图看一下(有删减)

  @RequestMapping注解肯定是在容器启动时候解析的,那么这个工作就放在RequestMappingHandlerMapping这个bean对象的初始化阶段来完成。之前文章Spring源码解析(四):单例bean的创建流程有介绍过,bean对象创建后会调用各种初始化方法,其实就包括调用InitializingBean接口afterPropertiesSet方法来实现初始化

在这里插入图片描述

  • RequestMappingHandlerMapping实现了afterPropertiesSet方法如下
// RequestMappingHandlerMapping类方法
@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
	// 创建RequestMappingInfo对象
	this.config = new RequestMappingInfo.BuilderConfiguration();
	this.config.setTrailingSlashMatch(useTrailingSlashMatch());
	this.config.setContentNegotiationManager(getContentNegotiationManager());

	if (getPatternParser() != null) {
		this.config.setPatternParser(getPatternParser());
		Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
				"Suffix pattern matching not supported with PathPatternParser.");
	}
	else {
		this.config.setSuffixPatternMatch(useSuffixPatternMatch());
		this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
		this.config.setPathMatcher(getPathMatcher());
	}
	// 调用父类实现的afterPropertiesSet方法
	super.afterPropertiesSet();
}
  • 父类AbstractHandlerMethodMapping实现了afterPropertiesSet方法如下
// AbstractHandlerMethodMapping类方法

/**
 * 在初始化时检测处理程序方法
 */
@Override
public void afterPropertiesSet() {
	initHandlerMethods();
}

/**
 * 扫描 ApplicationContext 中的 Bean,检测并注册处理程序方法
 */
protected void initHandlerMethods() {
	// 获取所有的bean对象并遍历
	for (String beanName : getCandidateBeanNames()) {
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
			// 筛选候选的bean
			processCandidateBean(beanName);
		}
	}
	// getHandlerMethods()获取请求路径与具体Controller方法的映射关系
	handlerMethodsInitialized(getHandlerMethods());
}
3.2.2.1、获取候选bean
// AbstractHandlerMethodMapping类方法
protected void processCandidateBean(String beanName) {
	Class<?> beanType = null;
	try {
		// 获取bean对象的Class类型
		beanType = obtainApplicationContext().getType(beanName);
	}
	catch (Throwable ex) {
		...
	}
	// 判断是否处理程序
	if (beanType != null && isHandler(beanType)) {
		// 查找处理程序的方法
		detectHandlerMethods(beanName);
	}
}
  • 根据类上@Controller@RequestMapping注解判断是否为处理程序
// RequestMappingHandlerMapping
@Override
protected boolean isHandler(Class<?> beanType) {
	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
  • 查询处理程序的方法
    1. 此时获取到的handler已经确定有@Controller或者@RequestMapping
    2. 遍历handler类下所有的Method
      • 判断Method方法上是否有@RequestMapping注解
      • 有注解则将注解内的属性包装成一个类:RequestMappingInfo
    3. 返回一个map集合methods
      • key为有@RequestMapping注解的Method对象
      • value为RequestMappingInfo对象
// AbstractHandlerMethodMapping类方法
protected void detectHandlerMethods(Object handler) {
	Class<?> handlerType = (handler instanceof String ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());

	if (handlerType != null) {
		// 类如果被代理,获取真正的类型
		Class<?> userType = ClassUtils.getUserClass(handlerType);
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					try {
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						...
					}
				});
		...		
		
		// 这里有做了一些列的包装处理
		methods.forEach((method, mapping) -> {
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

我简述下获取到methods以后的的包装处理,就是各种包装对象,就不放代码了,没啥意思

  • 首先创建一个MappingRegistry(映射注册器),以后的mapping映射什么都放这里
  • handler(也就是Controller类对象)和有@RequestMapping的Method组成对象HandlerMethod
  • 获取到HandlerMethod后,这时候会校验RequestMappingInfo(@RequestMapping属性对象)是否存在
    • 存在的话这里就会抛出异常There is already 'xxx' bean method(这个大家应该很熟悉)
  • 最后一步往MappingRegistryMap<T, MappingRegistration<T>> registry属性中注册内容
    • key为RequestMappingInfo对象(@RequestMapping注解属性,包括映射路径)
    • value为由RequestMappingInfo和HandlerMethod组成的新对象MappingRegistration
  • 注意:以上步骤都是在readWriteLock锁内完成的,以防多个线程注册映射对象重复
3.2.2.2、获取HandlerMethod并统计数量
  • 从mappingRegistry中获取map集合,key为RequestMappingInfo,value为HandlerMethod
// AbstractHandlerMethodMapping类方法
public Map<T, HandlerMethod> getHandlerMethods() {
	this.mappingRegistry.acquireReadLock();
	try {
		return Collections.unmodifiableMap(
				this.mappingRegistry.getRegistrations().entrySet()
				.stream().collect(Collectors.toMap(
					Map.Entry::getKey, 
					entry -> entry.getValue().handlerMethod)
				));
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}
  • 获取到上面拿到的handlerMethods,打印数量
protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {
	// 获取到上面拿到的值,打印数量
	int total = handlerMethods.size();
	if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) {
		logger.debug(total + " mappings in " + formatMappingName());
	}
}

3.3、RequestMappingHandlerAdapter适配器

  • 一句话解释它:拿到RequestMappingHandlerMapping解析出的HandlerMethod去调用具体的Method
3.3.1、实例化
// WebMvcConfigurationSupport类方法

private static final boolean jackson2Present;
// 加载对应的类,能加载成功方true,不能加载成功,表示没有这个类,没有导入包,返回false
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
				ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);


@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
		@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcValidator") Validator validator) {
	// 创建RequestMappingHandlerAdapter对象
	RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
	adapter.setContentNegotiationManager(contentNegotiationManager);
	// 设置消息转换器
	adapter.setMessageConverters(getMessageConverters());
	adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
	adapter.setCustomArgumentResolvers(getArgumentResolvers());
	adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
	
	// 请求响应数据与json的转化
	// 如果导入Jackson包,添加
	if (jackson2Present) {
		adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
		adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
	}

	// 省略异步配置,以后再研究
	...

	return adapter;
}
3.3.1.1、获取消息转换器

  先调用DelegatingWebMvcConfiguration重写的方法,也就是遍历所有WebMvcConfigurer实现类,调用他们的configureMessageConverters方法,新增的消息转换器都会添加到messageConverters集合中。如果开发者和第三方都没有添加,那么设置默认的消息转换器,设置完以后,再调用扩展方法,也就是遍历所有WebMvcConfigurer实现类,调用他们的extendMessageConverters方法,对消息转换器做最后修改

// WebMvcConfigurationSupport
protected final List<HttpMessageConverter<?>> getMessageConverters() {
	if (this.messageConverters == null) {
		this.messageConverters = new ArrayList<>();
		// 这里就是调用DelegatingWebMvcConfiguration重写的方法,配置消息转换器
		configureMessageConverters(this.messageConverters);
		if (this.messageConverters.isEmpty()) {
			// 如果上面没有添加消息转换器,这里添加默认的消息转换器
			addDefaultHttpMessageConverters(this.messageConverters);
		}
		// 这里就是调用DelegatingWebMvcConfiguration重写的方法,扩展消息转化器
		extendMessageConverters(this.messageConverters);
	}
	return this.messageConverters;
}

  加载jackson里的类,能加载成功jackson2Present返回true,不能加载成功,表示没有这个类,没有导入包,返回jackson2Present返回false,然后去判断是否导入Google、JDK自带的处理JSON类。一般我们会导入Jackson,那么这里会添加MappingJackson2HttpMessageConverter消息转换器。

// WebMvcConfigurationSupport类方法
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
	messageConverters.add(new ByteArrayHttpMessageConverter());
	messageConverters.add(new StringHttpMessageConverter());
	messageConverters.add(new ResourceHttpMessageConverter());
	messageConverters.add(new ResourceRegionHttpMessageConverter());

	...
	
	// jackson
	if (jackson2Present) {
		Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
		if (this.applicationContext != null) {
			builder.applicationContext(this.applicationContext);
		}
		messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
	}
	// Google Gson 库中的一个核心类,Java对象与JSON 格式字符串进行相互转换
	else if (gsonPresent) {
		messageConverters.add(new GsonHttpMessageConverter());
	}
	// JDK 类库JSON类
	else if (jsonbPresent) {
		messageConverters.add(new JsonbHttpMessageConverter());
	}

	...
}

总结

  • 加载继承AbstractAnnotationConfigDispatcherServletInitializer的MVC配置类(开发者创建,代替web.xml)
  • 既然代替web.xml那么这个配置类可以设置DispatcherServlet的映射路径注册过滤器
  • 父类AbstractAnnotationConfigDispatcherServletInitializer里面会创建web注解容器创建DispatcherServlet对象添加过滤器到Tomcat容器的过滤器集合中
    • DispatcherServlet初始化触发了web容器的刷新,加载所有@Controller注解的bean
  • 如果开发者或者第三方想要配置拦截器消息转换器的等配置,只要实现WebMvcConfigurer接口重写对应方法即可
  • 解析@RequestMapping注解
    • 遍历所有的bean,筛选类上是否有@Controller@RequestMapping注解
    • 如果有,再遍历所有的方法,筛选方法上是否有@RequestMapping注解
    • 最后包装成map存起来
      • key为@RequestMapping注解属性组成的RequestMappingInfo
      • value为Controller里存在@RequestMapping注解的Method组成的HandlerMethod

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/782437.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RNN 交叉熵

RNN善于处理时序 序列数据 简单RNN 展开就是 LSTM 遗忘门f_t决定上期记忆保留多少 隐藏层 在神经网络中&#xff0c;隐藏层指的是除了输入层和输出层之外的层&#xff0c;它们的输出不会直接用于网络的最终输出&#xff0c;而是作为中间步骤用于提取和转换数据。因此&#x…

LabVIEW光谱测试系统

在现代光通信系统中&#xff0c;光谱分析是不可或缺的工具。开发了一种基于LabVIEW的高分辨率光谱测试系统&#xff0c;通过对可调谐激光器、可编程光滤波器和数据采集系统的控制&#xff0c;实现了高效、高精度的光谱测量。 项目背景 随着光通信技术的迅速发展&#xff0c;对…

Filter和Listener

1. Filter 过滤器 1 过滤器简介 Filter过滤器是JavaWeb的三大组件&#xff08;Servlet程序、Listener监听器、Filter过滤器&#xff09;之一 Filter作用&#xff1a;拦截请求、过滤响应 是javaee的规范也是接口 拦截请求常见的应用有 权限检查日记操作事务管理 2 Filter …

刷题之多数元素(leetcode)

多数元素 哈希表解法&#xff1a; class Solution { public:/*int majorityElement(vector<int>& nums) {//map记录元素出现的次数&#xff0c;遍历map&#xff0c;求出出现次数最多的元素unordered_map<int,int>map;for(int i0;i<nums.size();i){map[nu…

详解Java垃圾回收(GC)机制

一、为什么需要垃圾回收 如果不进行垃圾回收&#xff0c;内存迟早都会被消耗空&#xff0c;因为我们在不断的分配内存空间而不进行回收。除非内存无限大&#xff0c;我们可以任性的分配而不回收&#xff0c;但是事实并非如此。所以&#xff0c;垃圾回收是必须的。 二、哪些内…

flutter环境安装(Mac+vscode)

以前据说flutter跨平台开发app很牛逼&#xff0c;最近突然想到这个东西&#xff0c;于是想体验一下flutter的开发流程&#xff0c;看看能否适合做独立开发。 我用的是mac&#xff0c;手机也是ios&#xff0c;就开始着手部署mac下的开发环境了。 开发后台的时候&#xff0c;一…

Vine: 一种全新定义 Vue 函数式组件的解决方案

7月6日的 vue confg 大会上 ShenQingchuan 大佬介绍了他的 Vue Vine 项目&#xff0c; 一种全新定义 Vue 函数式组件的解决方案。 和 React 的函数式组件有异曲同工之妙&#xff0c;写起来直接起飞了。 让我们来快速体验一下 vine&#xff0c; 看看到底给我们带来了哪些惊喜吧…

AI周报(6.30-7.6)

AI应用-AI控制F16战机与人类飞行员狗斗 2024年美国国防部领导下的国防部高级研究计划局&#xff08;DARPA&#xff09;宣布&#xff0c;世界上首次人工智能&#xff08;AI&#xff09;驾驶的战斗机与人类驾驶的战斗机之间的空战&#xff0c;于去年秋季在加利福尼亚州爱德华兹空…

基于CentOS Stream 9平台搭建RabbitMQ3.13.4以及开机自启

1. erlang与RabbitMQ对应版本参考&#xff1a;https://www.rabbitmq.com/which-erlang.html 2. 安装erlang 官网&#xff1a;https://www.erlang.org/downloads GitHub: https://github.com/rabbitmq/erlang-rpm/releases 2.1 安装依赖&#xff1a; yum -y install gcc glib…

【LeetCode】螺旋矩阵

目录 一、题目二、解法完整代码 一、题目 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5] 示例 2&…

zdppy + vue3 + antd 实现一个表格编辑行,批量删除功能

编辑单元格和多选的功能 首先是编辑单元格的功能&#xff0c;点击编辑按钮&#xff0c;可以直接在表格中队内容进行编辑&#xff0c;点击保存以后能够同步到数据库。 其次是多选的功能&#xff0c;点击每行前面的多选框按钮&#xff0c;我们可以选中多行。 完整后端代码&am…

axios和Mybatis

除了get和post方法还有其他的方法&#xff1a; 发送 PUT 请求 发送 PUT 请求通常用于更新服务器上的资源。 const updateData {title: foo updated,body: bar updated,userId: 1 };axios.put(https://jsonplaceholder.typicode.com/posts/1, updateData).then(function (res…

Camera Raw:编辑 - 配置文件

Camera Raw “编辑”模块中的配置文件 Profile面板为照片编辑提供了一个坚实的基础&#xff0c;能够显著改善照片的初始外观&#xff0c;使编辑过程更加高效和灵活。 使用配置文件&#xff0c;可以控制如何呈现照片中的颜色和色调。配置文件旨在作为照片编辑的起点和基础。 ◆ …

数列结构(3.9)——队列应用

树的层次遍历 树的层次遍历&#xff0c;也称为树的广度优先遍历&#xff0c;是一种按照树的层次顺序&#xff0c;从上到下、从左到右遍历树中所有节点的算法。在二叉树中&#xff0c;这种遍历方式通常使用队列来实现。下面是层次遍历的基本步骤&#xff1a; 创建一个空队列&a…

qemu模拟orangepi

前言 由于qemu目前只支持orange pipc单板&#xff0c;也就是H3型号&#xff0c;故我们就拿这个型号做测试 环境搭建 linux主机环境 我这里采用win10 WSL&#xff0c;且环境用的是openeuler的&#xff0c;在选择服务器类型可以按照自己喜好选择&#xff0c;也就是包安装方式…

【ARMv8/v9 GIC 系列 1.7 -- GIC PPI | SPI | SGI | LPI 中断使能配置介绍】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 GIC 各种中断使能配置PPIs(每个处理器私有中断)SPIs(共享外设中断)SGIs(软件生成的中断)LPIs(局部中断)GIC 各种中断使能配置 在ARM GICv3和GICv4架构中,不同类型的中断(如PPIs、SPIs、SGIs和LPIs)可以通过不同的方式进…

SUSAN

1995年英国牛津大学的S.M.Smith提出了一种新的图像边缘检测算法SUSAN算法,不同于以前经典边缘检测算法,SUSAN算法基于灰度相似性比较,采用圆形模板,对图像进行灰度差统计,无需计算方向导数,而且具备积分特性,它简单而且有效,适用于图像中边缘和角点的检测,可以去除图像…

【Unity】简单举例UI合批,优化draw call

1. UGUI-Editor 首先引入UGUI-Editor插件 链接: https://pan.baidu.com/s/1PpxStvgRCDi9xjUr6j6nCQ?pwdm5ju 提取码: m5ju 或者直接去Github搜索UGUI-Editor 2. 没有UI时 3. 放几个UI看看效果 4. 选中Canvas&#xff0c;右键优化Batch 发现减少了3个&#xff0c;这是因为&…

玉石风能否接棒黏土风?一探AI绘画新风尚

在数字艺术的浪潮中,AI绘画平台以其独特的创造力和便捷性,正在逐步改变我们对艺术的传统认知。从黏土风的温暖质感到琉璃玉石的细腻光泽,每一次风格的转变都引领着新的潮流。今天,我们将聚焦玉石风,探讨它是否能成为下一个流行的艺术滤镜,并提供一种在线体验的方式,让你…

昇思25天学习打卡营第13天 | ShuffleNet图像分类

ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有限的计算资源来达到最好的模型精度。ShuffleNetV1的设计核心是引入了两种操作&#xff1a;Pointw…