java的spi就是一种服务提供发现机制,在一方制定好接口规范后,通过spi的机制可以它的子实现类进行服务发现,以及加载它的子实现类,通过这种机制,让我们在引入第三方库时,不用讲第三方库中的类硬编码到我们的代码中,而是通过java spi的机制来动态加载这些类
load()
方法,传入对应的class
对象进行加载,但是这里不会真正的加载,只是讲我们的文件中的类全限定名,存放到providerNames
集合中next()
方法时,会调用provides的next()
方法迭代providerNames
集合对象,获取对象内的每个全限定名,然后调用get
方法获取对应的实例对象,这一步才会创建对象,而且使用 ServiceLoader
迭代的时候,会将所有的实现类都加载并实例化,以便在迭代过程中提供这些实现的实例。
AppClassLoader
和 SystemClassLoader
在层级上是一样的。Bootstrap ClassLoader
负责从 rt.jar 加载标准 JDK 类文件,它是 Java 中所有类加载器的父类。 Bootstrap 类加载器没有任何父类。Extension ClassLoader
将类加载请求委托(delegate)给其父 Bootstrap,如果不成功,则从 jre/lib/ext
目录或 java.ext.dirs 系统属性指向的任何其他目录加载类系统或应用程序类加载器
,它负责从 CLASSPATH 环境变量、-classpath 或 -cp 命令行选项、JAR 内 list 文件的类路径属性加载应用程序特定类。
Extension ClassLoader
的子类,由 sun.misc.Launcher$AppClassLoader
类实现。Bootstrap 类加载器
,它主要是用C 语言实现的,所有Java 类加载器都是使用java.lang.ClassLoader
实现的。public class SpiSolution {
public static void main(String[] args) {
ServiceLoader<Object> load = ServiceLoader.load(Object.class);
Iterator<Object> iterator = load.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
}
}
}
默认去找上下文类加载器:
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
没有就去获取系统类加载器:
new ServiceLoader<>(Reflection.getCallerClass(), service, cl):
private ServiceLoader(Class> caller, Class svc, ClassLoader cl) {
Objects.requireNonNull(svc);
if (VM.isBooted()) {
checkCaller(caller, svc);
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
} else {
// if we get here then it means that ServiceLoader is being used
// before the VM initialization has completed. At this point then
// only code in the java.base should be executing.
Module callerModule = caller.getModule();
Module base = Object.class.getModule();
Module svcModule = svc.getModule();
if (callerModule != base || svcModule != base) {
fail(svc, "not accessible to " + callerModule + " during VM init");
}
// restricted to boot loader during startup
cl = null;
}
this.service = svc;
this.serviceName = svc.getName();
this.layer = null;
this.loader = cl;
this.acc = (System.getSecurityManager() != null)
? AccessController.getContext()
: null;
}
会创建一个newLookupIterator()
和一个迭代器对象new Iterator
()
public Iterator<S> iterator() {
// create lookup iterator if needed
if (lookupIterator1 == null) {
lookupIterator1 = newLookupIterator();
}
return new Iterator<S>() {
// record reload count
final int expectedReloadCount = ServiceLoader.this.reloadCount;
// index into the cached providers list
int index;
/**
* Throws ConcurrentModificationException if the list of cached
* providers has been cleared by reload.
*/
private void checkReloadCount() {
if (ServiceLoader.this.reloadCount != expectedReloadCount)
throw new ConcurrentModificationException();
}
@Override
public boolean hasNext() {
checkReloadCount();
if (index < instantiatedProviders.size())
return true;
return lookupIterator1.hasNext();
}
@Override
public S next() {
checkReloadCount();
S next;
if (index < instantiatedProviders.size()) {
next = instantiatedProviders.get(index);
} else {
next = lookupIterator1.next().get();
instantiatedProviders.add(next);
}
index++;
return next;
}
};
LookupIterator:
Iterator> first = new ModuleServicesLookupIterator<>();
Iterator> second = new LazyClassPathLookupIterator<>();
private Iterator<Provider<S>> newLookupIterator() {
assert layer == null || loader == null;
if (layer != null) {
return new LayerLookupIterator<>();
} else {
Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
return new Iterator<Provider<S>>() {
@Override
public boolean hasNext() {
return (first.hasNext() || second.hasNext());
}
@Override
public Provider<S> next() {
if (first.hasNext()) {
return first.next();
} else if (second.hasNext()) {
return second.next();
} else {
throw new NoSuchElementException();
}
}
};
}
}
LazyClassPathLookupIterator:去读取
迭代过程中才会加载:
next()
,实际获取lookupIterator1.next().get()
去创建实例对象:public Iterator<S> iterator() {
// create lookup iterator if needed
if (lookupIterator1 == null) {
lookupIterator1 = newLookupIterator();
}
return new Iterator<S>() {
// record reload count
final int expectedReloadCount = ServiceLoader.this.reloadCount;
// index into the cached providers list
int index;
/**
* Throws ConcurrentModificationException if the list of cached
* providers has been cleared by reload.
*/
private void checkReloadCount() {
if (ServiceLoader.this.reloadCount != expectedReloadCount)
throw new ConcurrentModificationException();
}
@Override
public boolean hasNext() {
checkReloadCount();
if (index < instantiatedProviders.size())
return true;
return lookupIterator1.hasNext();
}
@Override
public S next() {
checkReloadCount();
S next;
if (index < instantiatedProviders.size()) {
next = instantiatedProviders.get(index);
} else {
next = lookupIterator1.next().get();
instantiatedProviders.add(next);
}
index++;
return next;
}
};
}
jdbc去动态拓展,要去使用其它厂商的服务,如oracle、mysql,它只需要制定一个接口的规范,由其它厂商去遵循它的规范,就可以实现动态可插拔。
在 Spring MVC 中,Servlet 3.0 的 SPI(Service Provider Interface)机制可以帮助您实现零 XML 配置文件的方式,通过注解和接口的实现来自动初始化 Spring MVC 相关的配置,从而实现无需显式的 XML 配置文件。以下是在 Spring MVC 中通过 Servlet 3.0 的 SPI 实现零 XML 配置的详细步骤:
META-INF/services/javax.servlet.ServletContainerInitiaLizer
,内容是实现类org.springframework.web.SpringServletContainerInitializer
,其为servlet为我们提供的一个接口,并会在tomcat,jetty
等web容器启动时调用onStartUp()方法,使用@HandlesTypes(WebApplicationInitializer.class)
,将所有的实现类扫描到webAppInitializerClasses
集合中作为参数onStartup()
方法中。new ArrayList()
集合,将所有符合条件的类,比如实现了WebApplicationInitializer.class
接口的类,然后通过反射创建对象,添加到集合中,然后再统一迭代ArrayList集合中的类,调用每个对象的onStartup()
进行初始化@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
Iterator var4;
if (webAppInitializerClasses != null) {
initializers = new ArrayList(webAppInitializerClasses.size());
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
((List)initializers).add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (((List)initializers).isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort((List)initializers);
var4 = ((List)initializers).iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
1. 创建扩展接口和实现类:
首先,定义一个扩展接口,例如 WebApplicationInitializer
:
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
然后,创建实现了 WebApplicationInitializer
接口的类,这些类将负责初始化 Spring MVC 配置:
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 在这里初始化 Spring MVC 配置
}
}
2. 使用 @HandlesTypes 注解:
在 MyWebAppInitializer
类上使用 @HandlesTypes
注解,以便在应用启动时将所有实现了 WebApplicationInitializer
接口的类传递给容器:
@HandlesTypes(WebApplicationInitializer.class)
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 在这里初始化 Spring MVC 配置
}
}
3. 初始化 Spring MVC 配置:
在 onStartup
方法内,您可以使用 Spring 的注解来初始化 Spring MVC 配置,例如注册 DispatcherServlet
、扫描控制器、视图解析器等:
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
4. 部署到 Servlet 3.0 容器:
将您的应用部署到支持 Servlet 3.0 的容器,例如 Tomcat 7+。
5. 实现零 XML 配置:
通过上述步骤,您的 Spring MVC 应用现在可以实现零 XML 配置。MyWebAppInitializer
类的实现会在应用启动时被容器检测到并执行,从而初始化 Spring MVC 配置,而无需显式的 XML 配置文件。
通过使用 Servlet 3.0 的 SPI 机制和 WebApplicationInitializer
接口,您可以在 Spring MVC 中实现零 XML 配置,更加便捷地进行应用初始化和配置。
Springboot starter的自动装配
Spring SPI与 JDK SPI 类似, 相对于 Java 的 SPI 的主要在于:
Spring SPI 指定配置文件为 classpath 下的 META-INF/spring.factories
,所有的拓展点配置放到一个文件中。
配置文件内容为 key-value
类型,key 为接口的全限定名, value 为 实现类的全限定名
,可以为多个。
Spring Boot通过@EnableAutoConfiguration
注解来开启自动配置功能。这个注解实际上包含了两个注解:@Configuration
和@Import
。
@Configuration
注解表示该类是一个配置类,用于定义Bean的实例化和装配规则。@Import
注解用于导入其他配置类,从而将它们的配置规则合并到当前配置类中。自动装配其实是通过条件化装配、自动配置类、配置属性绑定来实现的
条件化装配:Spring Boot 使用条件化注解(@Conditional
)来实现自动装配。这些注解基于运行时环境的条件来决定是否需要装配某个组件。
自动配置类:Spring Boot 通过在 classpath 下的 META-INF/spring.factories
文件中定义自动配置类,这些自动配置类使用了条件化注解,根据条件来装配相应的组件。
配置属性绑定:自动配置类使用了配置属性(@ConfigurationProperties
)来绑定应用程序的配置到相应的组件中。配置属性可以从 application.properties 或 application.yml 文件中读取。
所以如果我们想要引入一些第三方包,就需要按照下面几步来操作:
源码分析:
@SpringBootApplication
是一个复合注解,包含多个注解的元注解,相当于同时添加了三个注解的效
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制
@Configuration
:允许在上下文中注册额外的 bean 或导入其他配置类
@ComponentScan
:扫描被@Component
(@Service
,@Controller
)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilter
和AutoConfigurationExcludeFilter
。
@EnableAutoConfiguration
是一个复合注解
@Import
注解导入了一个 AutoConfigurationImportSelector
类,这个AutoConfigurationImportSelector
类实现了 DeferredImportSelector
接口,重写了selectImports
方法,它会先判断自动配置这个注解是否开启,只有开启了就会调用getAutoConfigurationEntry
方法,具体方法内会带调用getCandidateConfigurations
方法,在这个方法内就回去调用一个SpringFactoriesLoader
的loadFactoryNames
方法去读取META-INF/spring.factories
内的数据然后,获取所有符合条件的类的全限定类名,存储到List
集合中返回,然后再根据一些条件化配置对集合进行筛选,比如@ConditionalOnClass
:当类路径下有指定类的条件下,@ConditionalOnProperty
:yml配置文件中是否进行了属性配置,去除一些不符合条件的全限定名,满足条件这些类就会被加载到 IoC 容器中。总结:
META-INF/spring.factories
文件,获取所有自动配置类的全限定名@ConditionalOnXXX
注解来进行条件装配,通过判断特定条件是否满足来确定是否进行自动装配dubbo spi和java spi不是同一种实现方式,因为他有一个很大的改进,java spi迭代的时候,会将所有的实现类都加载并实例化,不能制定就获取其中一个,而dubbo是按需加载,它只是一开始读取到了配置文件后,把这些配置文件内的类进行存储,这个过程叫做一个IOC和aop,当你使用的使用才会进行加载,
实战中,我自己也写了个rpc,对所有模块进行了一个spi的统一模块管理,也是参考了dubbo,分为两个,一个是系统的spi就读取程序本身所要使用的bean,然进行加载,除此之外还有一个用户的spi,用户spi,就是说用户可以遵循我的规范来玩的化,就可以对我们程序中的一些模块的增加或者增强,进行一些拓展的行为。
好处: