springMvc---SPI实现零XML启动原理

一、SPI简介

1.概念:

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。SPI的作用就是为这些被扩展的API寻找服务实现。简单来说就将接口与实现相分离,可以通过外部文件来灵活的控制接口实现。这不是本篇主要内容有个概念即可。

2.实际使用场景:

  • 数据库驱动加载接口实现类的加载,JDBC加载不同类型数据库的驱动
  • 日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类
  • Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等

3.使用方法:

要使用Java SPI,需要遵循如下约定:

1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个不带参数的构造方法;

二、springMvc中的具体使用

根据上面使用方法的叙述我们大致已经了解到SPI的使用方法,下面就去mvc源码中看看它到底是不是这么做的:
在这里插入图片描述
内容

org.springframework.web.SpringServletContainerInitializer

可以看到文件名为ServletContainerInitializer的全限定名,其为servlet为我们提供的一个接口,并会在tomcat,jetty等web容器启动时调用
在这里插入图片描述
SpringServletContainerInitializer实现了该接口

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				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);
					}
				}
			}
		}

		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);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

首先我们看到的是一个很陌生的注解,@HandlesTypes(WebApplicationInitializer.class)。其实在ServletContainerInitializer的注释中我们就已经发现有对次注解做简单介绍。其主要作用就是将当前注释中接口的实现类加入到webAppInitializerClasses中,避免我们有很多的实现类需要在启动时调用,重复的配置写很多配置文件。当我们有其它方法或类需要调用时,只需要在当前实现了ServletContainerInitializer规范的类上加一个@HandlesTypes注解里面提供一个自己定义的接口。当tomcat启动时就会把例如WebApplicationInitializer接口所有的实现类扫描到webAppInitializerClasses集合中。

继续往下看spring在当前onStartup方法中做了什么,首先可以看到:

List<WebApplicationInitializer> initializers = new LinkedList<>();

定义了一个list集合来存放我们的WebApplicationInitializer对象,因为方法参数Set中存放的是类。class是只能调用静态方法的,所以这里需要我们类的对象来分别调用各自onStartup方法。下面做的事情也很简单通过遍历利用反射new出每个类的对象存入了initializers 中。

for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}

最后调用每个对象的onStartup方法来进行初始化。

至于调用了那些类的onStartup方法我们可以点击看一下:
在这里插入图片描述
曾经我们手动配置的dispatcherServlet,ContextLoaderListener等XML配置正是在这些实现类中进行了初始化,从而避免了我们手动配置。
就拿AbstractContextLoaderInitializer实现类为例我们看看其onStartup方法做了什么:

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}

	/**
	 * Register a {@link ContextLoaderListener} against the given servlet context. The
	 * {@code ContextLoaderListener} is initialized with the application context returned
	 * from the {@link #createRootApplicationContext()} template method.
	 * @param servletContext the servlet context to register the listener against
	 */
	protected void registerContextLoaderListener(ServletContext servletContext) {
		// Root
		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");
		}
	}

这段javaConfig正对应着我们曾经的下面这段配置

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

其余很多配置就不去一一看了,到这里我们已经基本明白spring0XML配置的全部流程,下面简单总结一下:

1.通过servlet3.0实现的spi机制,指定在tomcat启动时调用ServletContainerInitializer的实现类SpringServletContainerInitializer中的onStartup方法
2.利用@HandlesTypes(WebApplicationInitializer.class)注解,在tomcat启动时将WebApplicationInitializer所有实现类扫描到Servlet ContainerInitializer的set集合中。
3.定义一个list集合存放符合要求的WebApplicationInitializer对象
4.遍历调用每个WebApplicationInitializer对象的onStartup方法来初始化,从而省去繁琐的XML配置。(一切根源都在/META-INF/services下)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值