JAVA SPI机制及SPI机制在Tomcat中的应用和在springboot中的应用

50 篇文章 11 订阅
11 篇文章 0 订阅

SPI 是 JAVA 提供的一种服务提供发现接口,其实就是一种面向接口的编程,为接口去匹配具体服务实现的机制,这一点上与 IOC 的思想类似,都是把装配的控制权放到了程序之外,下面具体看看什么是 SPI。

一、什么是 SPI

SPI 全称为 Service Provider Interface,即服务提供发现接口,这里的服务指的不是我们经常听到的微服务服务发现,这里的一个服务 Service 指的是一个接口或抽象类,服务提供方则是对这个接口或抽象类的实现。SPI 是 ”基于接口的编程 + 策略模式 + 配置文件“ 组合实现的动态加载机制

二、为什么使用 SPI

模块化设计中,模块之间基于接口编程,把装配的控制权放到程序之外,实现系统的解耦

  1. 使用场景

适用于调用方根据实际需求启用、扩展、替换服务的策略实现。许多开源框架中都使用了 Java 的 SPI 机制,如 JDBC 的 SPI 加载模式、日志框架 SLF4J 加载不同提供商的日志实现、Spring 中也大量适用了 SPI、Dubbo 的扩张机制、ServiceComb Java Chassis (CSE) 的 Filter、异常处理等扩展机制

三、SPI 的实现

  1. SPI 的实现步骤
  • 在类路径下的 META-INF/services 目录下,创建以服务接口的”全限定名“命名的文件,文件的内容为接口实现类的全限定名
  • 实现类必须在当前程序的 classpath 下
  • 使用 bash java.util.ServiceLoader 动态加载实现,会扫描 META-INF/services 下的配置文件加载实现类
  1. 先定义一个基接口
public interface BaseDriver {
    void url();
}

并使用maven打包,并安装在本地maven仓库

  1. 新建maven项目,导入含有BaseDriver的maven坐标,编写一个接口实现类
public class MysqlDriver implements BaseDriver
{
    @Override
    public void url() {
        System.out.println("this is a mysql url");
    }
}

以下这一步很重要:
在这里插入图片描述

再次使用maven打包,并安装在本地maven仓库

  • 可是多写一个实现类
public class OracleDriver implements BaseDriver {
    public void url() {
        System.out.println("this is an oracle url");
    }
}

也要再次打包

在这里插入图片描述

  1. 新建maven项目使用
    pom.xml
        <dependency>
            <groupId>com.spi</groupId>
            <artifactId>MYSQL_SPI</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.spi</groupId>
            <artifactId>ORACLE_SPI</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
  1. 测试
    在这里插入图片描述

四、SPI机制在Tomcat中的应用

  • 我们知道:Servlet 2.5 实现 webApp 加载的方式是 web.xml,获取其中配置的ServletContextListener 的实现类,通过实现类的 contextInitialized 方法来加载 webapp。
   <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

具体可参考我另一篇文章

  • Servlet 3.0 就无需配置 web.xml, 直接通过实现接口 ServletContainerInitializer 就可以实现 webApp 的加载,就如类名一样,这个类的作用就是在 servlet 容器初始化过程中加入自定义操作,因为是自定义的,所以可扩展性就非常强,能干很多事情。

  • 两者执行位置的区别
    在这里插入图片描述
    两个方法的执行位置都是在 Tomcat 启动过程中,Context 容器启动时。具体方法是 StandardContext 类的 startInternal() 方法,一个 context 容器就代表了一个 webapp。

  • ServletContainerInitializer源码

public interface ServletContainerInitializer {
    public void onStartup(Set<Class<?>> c, ServletContext ctx)
        throws ServletException; 
}
  • 那么servlet3.0规范下,是如何不通过web.xml的方式,使用纯java代码的方式加载spring应用的呢

直接看到spring-web:5.3.13的源码:
在这里插入图片描述
没错,tomcat启动后,可以根据servlet3.0的规范,通过SPI机制,将实现了ServletContainerInitializer 接口的类全部进行加载,并排序后,依次调用onstartup方法
在看看javax.servlet.ServletContainerInitializer的内容:

org.springframework.web.SpringServletContainerInitializer

于是,我们直接找到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();
		if (webAppInitializerClasses != null) {
			initializers = new ArrayList<>(webAppInitializerClasses.size());
			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);
		}
	}
}

这里要先知道@HandleTypes这个注解的含义,该注解也是Servlet规范所定义的,作用就是:将注解指定的Class对象(包括其实现类及子类)作为参数传递到onStartup(也就是@HandleTypes只能作用在实现了ServletContainerInitializer 的类上)

//含义就是,将WebApplicationInitializer.class的子类获取实现类,
//作为一个Set<Class<?>>参数传入道到SpringServletContainerInitializer.onstartup方法中
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {}

然后,我们就可以在onstartup方法中,进行我们的开发,例如初始化web容器,初始化DispatcherServlet等操作

  • AbstractContextLoaderInitializer.onstartup:
  public void onStartup(ServletContext servletContext) throws ServletException {
        this.registerContextLoaderListener(servletContext);
    }
  • AbstractDispatcherServletInitializer.onstartup:
 public void onStartup(ServletContext servletContext) throws ServletException {
        //调用父类
        super.onStartup(servletContext);
        this.registerDispatcherServlet(servletContext);
    }

在这里插入图片描述

参考文章
参考文章

五、在SpringBoot中的应用

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
参考视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值