栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【spring】spring mvc与spring的整合源码分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【spring】spring mvc与spring的整合源码分析

spring mvc与spring的整合简单介绍

本文源码基于spring-framework-5.3.10。mvc是spring源码中的一个子模块!本文重点是无xml方式的整合springmvc。Spring整合SpringMVC唯一的体现就是父子容器。 spring mvc的父子容器

父容器(Spring)管理Service、Dao层的Bean, 子容器(SpringMVC)管理Controller的Bean。子容器可以访问父容器的Bean, 父容器无法访问子容器的Bean。 spring整合spring mvc的思路(xml方式)

一般会在web.xml中配置下面的代码。核心是一个ContextLoaderListener(加载spring容器)、一个DispatcherServlet(加载spring mvc容器)。


    org.springframework.web.context.ContextLoaderListener



    contextConfigLocation
    classpath:spring-core.xml



    dispatcherServlet
    org.springframework.web.servlet.DispatcherServlet
    
    
    contextConfigLocation
    classpath:spring-mvc.xml

    
    1


    dispatcherServlet
    /

零配置SpringMVC实现方式的基础:SPI

SPI:我们叫他服务接口扩展,(Service Provider Interface) 直译服务提供商接口。其实就是根据Servlet厂商(服务提供商)提供要求的一个接口,在固定的目录(meta-INF/services)放上以接口全类名为命名的文件, 文件中放入接口的实现的全类名,该类由我们自己实现,按照这种约定的方式(即SPI规范),服务提供商会调用文件中实现类的方法,从而完成扩展。在往简单说:定义一个借口,把他的实现的全限定类名配置到一个配置文件,然后通过JDK8提供的方法进行调用。

public class Test {
    public static void main(String[] args) {
	// 得到你配置的所有实现了IUserDao的接口
        ServiceLoader daos = ServiceLoader.load(IUserDao.class);
	// 循环调用每个接口
        for (IUserDao dao : daos) {
            dao.save();
        }
    }
}

spring mvc与spi的关系:在Serlvet3-1的规范手册中:就提供了一种更加易于扩展可用于共享库可插拔的一种方式,参见8.2.4:

简单说就是放一个SPI规范:你在应用meta-INF/services 路径下放一个 javax.servlet.ServletContainerInitailizer,里面配置具体的实现的全限定类名!容器(Tomcat)就会去调用

零配置SpringMVC实现方式的基础:HandlesTypes

通过HandlesTypes可以将感兴趣的一些类注入到ServletContainerInitializerde的onStartup方法作为参数传入。在SpringServletContainerInitializer的类上配置了@HandlesTypes(WebApplicationInitializer.class)tomcat会自动找到程序中所有的实现了WebApplicationInitializer接口的实现类。然后把找到的所有的WebApplicationInitializer实现类传入onStartUp方法的webAppInitializerClasses参数上! SPI的方式SpringMVC启动原理

外置Tomcat启动的时候通过SPI的方式找到我们应用中的/meta-INF/service/javax.servlet.ServletContainerInitializer。里面会配置org.springframework.web.SpringServletContainerInitializer。所以会调用SpringServletContainerInitializer.onStartUp()方法。调用onStartUp()前会先找到@HandlesTypes(WebApplicationInitializer.class) 所有实现了WebApplicationInitializer的类,传入到OnStartup的webAppInitializerClasses参数中,并传入Servlet上下文对象。

public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext)
		throws ServletException {

	// 得到一个空的数组,里面存放实例化好的WebApplicationInitializer对象
	List initializers = Collections.emptyList();

	// 找到有WebApplicationInitializer的实现类
	if (webAppInitializerClasses != null) {
		// 创建一个有长度的数组,长度和找到的WebApplicationInitializer实现类数量一致
		initializers = new ArrayList<>(webAppInitializerClasses.size());
		for (Class waiClass : webAppInitializerClasses) {
			// 接口和抽象类servlet容器也会给我们,但是我们不要
			// 排除接口和容器
			if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
					WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
				try {
					// 实例化,然后添加到集合中
					initializers.add((WebApplicationInitializer)
							ReflectionUtils.accessibleConstructor(waiClass).newInstance());
				}
				catch (Throwable ex) {
					throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
				}
			}
		}
	}

	// 没有实例化的WebApplicationInitializer对象,直接返回。。
	if (initializers.isEmpty()) {
		servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
		return;
	}

	servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
	AnnotationAwareOrderComparator.sort(initializers);
	// 调用initializer.onStartup  进行扩展
	for (WebApplicationInitializer initializer : initializers) {
		initializer.onStartup(servletContext);
	}
}
spring整合spring mvc的思路(无xml方式)

直接看默认是没有一个类(不是接口,不是抽象类的)去实现了WebApplicationInitializer接口,所以需要我们自己去实现

public class ZhangweiStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	
	@Override
	protected Class[] getRootConfigClasses() {
		return new Class[]{RootConfig.class};
	}

	
	@Override
	protected Class[] getServletConfigClasses() {
		return new Class[]{WebAppConfig.class};
	}

	
	@Override
	protected String[] getServletMappings() {
		return new String[]{"/"};
	}
}


@Configuration
@ComponentScan(basePackages = "com.zhangwei",excludeFilters = {
		@ComponentScan.Filter(type = FilterType.ANNOTATION,value={Controller.class}),
		@ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ),
})
public class RootConfig {}



@Configuration
@ComponentScan(basePackages = {"com.zhangwei"},includeFilters = {
      @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class})
},useDefaultFilters =false)
@EnableWebMvc   // ≈
public class WebAppConfig implements WebMvcConfigurer{

}
WebApplicationInitializer接口的实现类执行原理:onStartup源码调用逻辑
public void onStartup(ServletContext servletContext) throws ServletException {
	//注册registerContextLoaderListener
	super.onStartup(servletContext);
	//注册registerDispatcherServlet
	registerDispatcherServlet(servletContext);
}


public void onStartup(ServletContext servletContext) throws ServletException {
	// 父容器的注册
	registerContextLoaderListener(servletContext);
}


protected void registerContextLoaderListener(ServletContext servletContext) {
	// 创建父容器 ,
	WebApplicationContext rootAppContext = createRootApplicationContext();
	if (rootAppContext != null) {
		ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
		// 设置初始化器
		listener.setContextInitializers(getRootApplicationContextInitializers());
		servletContext.addListener(listener);
	}
	else {
		logger.debug("No ContextLoaderListener registered, as " +
				"createRootApplicationContext() did not return an application context");
	}
}


protected WebApplicationContext createRootApplicationContext() {
	// 这里得到的是我们自己的配置类
	Class[] configClasses = getRootConfigClasses();
	if (!ObjectUtils.isEmpty(configClasses)) {
		// 创建spring容器!
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		// 注册当前的配置类
		context.register(configClasses);
		return context;
	}
	else {
		return null;
	}
}


protected void registerDispatcherServlet(ServletContext servletContext) {
	String servletName = getServletName();
	Assert.hasLength(servletName, "getServletName() must not return null or empty");
	// 创建子容器
	WebApplicationContext servletAppContext = createServletApplicationContext();
	Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
	// 创建DispatcherServlet
	frameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
	Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
	// 初始化容器
	dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

	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);
	// 添加映射
	registration.addMapping(getServletMappings());
	// 是否异步支持
	registration.setAsyncSupported(isAsyncSupported());
	// 设置DispatcherServlet的过滤器
	Filter[] filters = getServletFilters();
	if (!ObjectUtils.isEmpty(filters)) {
		for (Filter filter : filters) {
			registerServletFilter(servletContext, filter);
		}
	}
	// 空方法, 可以再对DispatcherServlet进行定制
	customizeRegistration(registration);
}


protected WebApplicationContext createServletApplicationContext() {
	// 创建一个spring容器
	AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
	// 调用我们重写的配置类:WebAppConfig
	Class[] configClasses = getServletConfigClasses();
	if (!ObjectUtils.isEmpty(configClasses)) {
		// 给容器注册配置类
		context.register(configClasses);
	}
	return context;
}
监听器的初始化源码分析:核心是调用父容器的refresh方法
public void contextInitialized(ServletContextEvent event) {
	initWebApplicationContext(event.getServletContext());
}


public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
		throw new IllegalStateException(
				"Cannot initialize context because there is already a root application context present - " +
				"check whether you have multiple ContextLoader* definitions in your web.xml!");
	}

	servletContext.log("Initializing Spring root WebApplicationContext");
	Log logger = LogFactory.getLog(ContextLoader.class);
	if (logger.isInfoEnabled()) {
		logger.info("Root WebApplicationContext: initialization started");
	}
	long startTime = System.currentTimeMillis();

	try {
		// xml会在这里创建
		if (this.context == null) {
			this.context = createWebApplicationContext(servletContext);
		}
		// 我们配置的注解会生成AnnotationConfigWebApplicationContext对象,属于ConfigurableWebApplicationContext
		if (this.context instanceof ConfigurableWebApplicationContext) {
			// 得到AnnotationConfigWebApplicationContext对象
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				// 这里在spring5以上,都是null。可以自己扩展!
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent ->
					// determine parent for root web application context, if any.
					// 默认返回null
					ApplicationContext parent = loadParentContext(servletContext);
					// 设置父容器为null
					cwac.setParent(parent);
				}
				// 配置刷新容器
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
		// 绑定父子容器!在servlet域中设置根容器(在子容器就可以直接拿到了)
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);



		// 获取线程上下文类加载器,默认为WebAppClassLoader
		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		// 如果spring的jar包放在每个webapp自己的目录中
		// 此时线程上下文类加载器会与本类的类加载器(加载spring的)相同,都是
		if (ccl == ContextLoader.class.getClassLoader()) {
			currentContext = this.context;
		}
		// 如果不同,也就是上面说的那个问题的情况,那么用一个map把刚才创建的
		else if (ccl != null) {
			// 一个webapp对应一个记录,后续调用时直接根据WebAppClassLoader来取出
			currentContextPerThread.put(ccl, this.context);
		}

		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
		}

		return this.context;
	}
	catch (RuntimeException | Error ex) {
		logger.error("Context initialization failed", ex);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
}


protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {

		// 设置id
		String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
		if (idParam != null) {
			wac.setId(idParam);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(sc.getContextPath()));
		}
	}
	// 设置ServletContext到spring上下文
	wac.setServletContext(sc);
	// 获得servlet容器中的全局参数contextConfigLocation  (xml)
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}

	// The wac environment's #initPropertySources will be called in any case when the context
	// is refreshed; do it eagerly here to ensure servlet property sources are in place for
	// use in any post-processing or initialization that occurs below prior to #refresh
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}
	// 在容器加载前 可以通过设置初始化参数contextInitializerClasses、globalInitializerClasses 进行扩展
	customizeContext(sc, wac);
	// 刷新容器
	wac.refresh();
}
初始化DispatcherServlet:核心是调用父容器的refresh方法
public final void init() throws ServletException {

	// 解析 init-param 并封装只 pvs 中(xml)
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	if (!pvs.isEmpty()) {
		try {
			// 将当前的这个 Servlet 类转化为一个 BeanWrapper,从而能够以 Spring 的方法来对 init-param 的值进行注入
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			// 属性注入
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			if (logger.isErrorEnabled()) {
				logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			}
			throw ex;
		}
	}

	// Let subclasses do whatever initialization they like.
	// 初始化Servlet的Bean
	initServletBean();
}


protected final void initServletBean() throws ServletException {
	getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
	if (logger.isInfoEnabled()) {
		logger.info("Initializing Servlet '" + getServletName() + "'");
	}
	long startTime = System.currentTimeMillis();

	try {
		// 初始化容器!
		this.webApplicationContext = initWebApplicationContext();
		// 无实现
		initframeworkServlet();
	}
	catch (ServletException | RuntimeException ex) {
		logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (logger.isDebugEnabled()) {
		String value = this.enableLoggingRequestDetails ?
				"shown which may lead to unsafe logging of potentially sensitive data" :
				"masked to prevent unsafe logging of potentially sensitive data";
		logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
				"': request parameters and headers will be " + value);
	}

	if (logger.isInfoEnabled()) {
		logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
	}
}


protected WebApplicationContext initWebApplicationContext() {
	// 获得ContextLoaderListener存的父容器
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;

	if (this.webApplicationContext != null) {
		// 获得子容器
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				// 如果没有设置父容器   spring的doGetBean使用
				if (cwac.getParent() == null) {
					// 设置父容器
					cwac.setParent(rootContext);
				}
				// 配置并且加载子容器
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
		// 从servlet上下文根据名字从域里面获取
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		// xml会在这里创建
		wac = createWebApplicationContext(rootContext);
	}

	//refreshEventReceived 它会在容器加载完设置为true (通过事件onApplicationEvent)
	// springboot在这初始化组件
	if (!this.refreshEventReceived) {
		synchronized (this.onRefreshMonitor) {
			onRefresh(wac);
		}
	}

	if (this.publishContext) {
		// 将当前容器放到servlet域中, 可以再创建子容器
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}

	return wac;
}


protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// 设置id
		if (this.contextId != null) {
			wac.setId(this.contextId);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
		}
	}
	// 设置servlet上下文
	wac.setServletContext(getServletContext());
	wac.setServletConfig(getServletConfig());
	wac.setNamespace(getNamespace());
	// 监听器(这里注册了很多的springmvc的组件)  委托设计模式
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

	// 将init-param设置到Environment中
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}
	// 空方法可扩展
	postProcessWebApplicationContext(wac);
	// 容器启动前初始化
	applyInitializers(wac);
	wac.refresh();
}
结束语

获取更多本文的前置知识文章,以及新的有价值的文章,让我们一起成为架构师!目前已经完成了并发编程、MySQL、spring源码、Mybatis的源码。可以在公众号下方菜单点击查看之前的文章!接下来的目标是深入分析JVM、tomcat、redis这个公众号,无广告!!!每日更新!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/786428.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号