个人笔记
在java项目使用maven导完包之后,点进去发现有的包下面有resource下面有META-INF\service,有的包下面有META-INF\spring.factoris文件。这些是干什么的,我自己写项目的时候为什么都没写过这个。
在我的好奇驱动下,我就查询各种资料。
一、首先为什么要写这些文件,有啥用?
1、介绍
META-INF/services是JDK 提供的 SPI(Service Provider Interface)机制的一种实现方式。
在 JDK 的 SPI 机制中,可以通过在 JAR 文件的 `META-INF/services` 目录下创建一个以接口全限定名为命名的文件,文件中列出接口的实现类的全限定名,用于指定接口的具体实现类。当 JDK 加载该接口时,会自动读取这个文件,并根据文件中指定的实现类来创建实例。
而在Spring 的SPI机制的实现方式是通过 META-INF/spring.factories 文件来实现的。在 Spring 中,每个模块或者库都可以通过在 `META-INF/spring.factories` 文件中注册自己的实现类,从而扩展或者替换 Spring 框架的功能。当 Spring 启动时,会自动读取所有 `META-INF/spring.factories` 文件,并加载其中注册的实现类。
上面两个可知: META-INF/spring.factories 和META-INF/services 都是实现SPI的,只是通过不同的方式去实现的。
2、它们的作用:
虽然两种 SPI 实现方式在原理上是类似的,但是它们的应用场景和使用方式有所区别。JDK 的 SPI 适用于 Java 标准库的扩展和插件化,而 Spring 的 SPI 则更适用于扩展和定制 Spring 框架本身的功能。Spring 的 SPI 机制更加灵活,允许开发者以模块化的方式扩展 Spring 的各个功能,从而实现高度可定制的应用程序。
3、怎么用
例子:
假设我们有一个自动配置类 `com.example.MyAutoConfiguration`,它定义了一个名为 `myBean` 的 Bean,用于打印一条简单的欢迎消息。
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyAutoConfiguration {
@Bean
public MyBean myBean() {
return new MyBean();
}
public static class MyBean {
public void sayHello() {
System.out.println("Hello, this is MyAutoConfiguration!");
}
}
}
现在,我们希望将这个自动配置类作为一个插件,供其他应用程序使用。为此,我们可以在 `resources/META-INF/spring.factories` 文件中添加以下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration
把项目打包,在另一个项目中引入。
<!-- pom.xml -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-auto-configuration</artifactId>
<version>1.0.0</version>
</dependency>
在应用程序的代码中,我们可以直接使用 `MyBean` 类,因为它已经被自动配置了。
import com.example.MyAutoConfiguration.MyBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(MyApp.class, args);
MyBean myBean = context.getBean(MyBean.class);
myBean.sayHello();
}
}
当我们运行应用程序时,会看到输出:
Hello, this is MyAutoConfiguration!
这说明 `com.example.MyAutoConfiguration` 这个自动配置类已经被自动加载,并且其中定义的 `myBean` Bean 已经在 Spring 容器中创建了。
这个例子展示了如何使用 `spring.factories` 文件将自动配置类作为插件提供给其他应用程序使用。通过引入相应的依赖,其他应用程序可以自动获得 `MyAutoConfiguration` 中定义的 Bean,并获得相应的功能扩展。
二、深入了解
SPI是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了spi接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔spi实现了动态加载,插件化。
解决的根本问题:
为什么需要SPI机制,这才是问题的源头。SPI是一种跟API相对应的反向设计思想:API由实现方确定标准规范和功能,调用方无权做任何干预; 而SPI是由调用方确定标准规范,也就是接口,然后调用方依赖此接口,第三方实现此接口,这样做就可以方便的进行扩展,类似于插件机制,这是SPI出现的需求背景。然后问题来了:
怎么样才能加载这些SPI接口的实现类呢?
问题的关键不是/META-INF/services。真正的原因是须了解java的类加载机制!SPI接口属于java rt核心包,只能由启动类加载器BootStrap classLoader加载,而第三方jar包是用户classPath路径下,根据类加载器的可见性原则:启动类加载器无法加载这些jar包,也就是没法向下委托,所以spi必须打破这种传统的双亲委派机制,通过自定义的类加载器来加载第三方jar包下的spi接口实现类! 这才是问题的关键!