場景引入
如何實現IOC的效果,我們可以來想想,無非就是一個隱式實現,而想要做到,總不能什麼都沒有,來個巧婦難為無米之炊的境地吧,所以說,米必須要有滴,在Spring中就是一個bean,也就是說,容器裡得有米,再官話點就是上下文中得存在所需要的bean。同樣模組化中兩個互相隔離的模組想要達到這種效果,也要先往jvm裡扔個物件進去的,然後who use ,who get 就可以了。
請看例子(可以認為是我們平常寫的SpringMVC專案中的service->serviceImpl->controller):
service介面化模組
package com.example.api;
public interface CodecFactory {
Encoder getEncoder(String encodingName);
Decoder getDecoder(String encodingName);
}複製程式碼
上面這個介面所在的模組定義:
module migo.codec.api {
exports com.example.api;
}複製程式碼
serviceImpl化模組
接著,我們定義一個實現模組:
module migo.codec.service {
requires com.example.api;
provides com.example.api.CodecFactory with com.example.service.codec.CodecFactoryImpl;
}複製程式碼
具體實現就省略了。
controller化模組
最後我們在最上層的模組內使用:
module migo.codec.controller {
requires migo.codec.api;
uses com.example.api.CodecFactory;
}複製程式碼
具體的controller模組內使用的程式碼如下:
ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
for (CodecFactory factory : loader) {
Encoder enc = factory.getEncoder("PNG");
if (enc != null)
... use enc to encode a PNG file
break;
}複製程式碼
或者:
public static void main(String... args) {
CodecFactory cf =
ServiceLoader.load(CodecFactory.class)
.findFirst()
.orElse(getFallBack());
if(cf == null) {
System.out.println("Using a fallback");
} else {
System.out.println("Found a service");
}
}
private static CodecFactory getFallBack() {
return null;
}複製程式碼
亦或者假如有很多服務實現的提供者,而某個提供服務實現的provider(也就是serviceImpl)上面有新增註解@PNG
,而我們想使用帶有這個註解的例項,可以使用以下程式碼:
ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
Set<CodecFactory> pngFactories = loader
.stream()
.filter(p -> p.type().isAnnotationPresent(PNG.class))
.map(Provider::get)
.collect(Collectors.toSet());複製程式碼
內部工作機制原理
具體思路:
通過在模組定義裡面的provides aaa with aaaImpl 這個功能,可以很容易的想到key value
組合
當我們碰到這對關鍵字的時候,我們就會解析並將aaa
做為key
,aaaImpl
新增到一個list
中並將這個list
作為value
,並新增到一個Map<String,list>
中
在我們碰到uses
關鍵字(原始碼裡面acc
會去確定這個許可權),並通過ServiceLoader.load(key)
來找到這個key所對應的一個包含了實現類具體地址的list,可能有多個,那麼,擴充功能,我們使用一個裝飾模式,也就是繼承了Iterable
這個介面,可以達到遍歷並生成具體例項來達到要求。
原始碼解析
確定米粒的路徑
那麼按照這個思路,我們反著來找下,這裡只列關鍵程式碼:
從上面的Demo中,我們可以看到,通過類的class位元組碼來載入:
之前有說,巧婦難為無米之炊,所以這個上下文很重要,我們的類載入器也是要講究上下文的
/**
* Creates a new service loader for the given service type, using the
* current thread's {@linkplain java.lang.Thread#getContextClassLoader
* context class loader}.
*
* <p> An invocation of this convenience method of the form
* <pre>{@code
* ServiceLoader.load(service)
* }</pre>
*
* is equivalent to
*
* <pre>{@code
* ServiceLoader.load(service, Thread.currentThread().getContextClassLoader())
* }</pre>
*
* @apiNote Service loader objects obtained with this method should not be
* cached VM-wide. For example, different applications in the same VM may
* have different thread context class loaders. A lookup by one application
* may locate a service provider that is only visible via its thread
* context class loader and so is not suitable to be located by the other
* application. Memory leaks can also arise. A thread local may be suited
* to some applications.
*
* @param <S> the class of the service type
*
* @param service
* The interface or abstract class representing the service
*
* @return A new service loader
*
* @throws ServiceConfigurationError
* if the service type is not accessible to the caller or the
* caller is in an explicit module and its module descriptor does
* not declare that it uses {@code service}
*
* @revised 9
* @spec JPMS
*/
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}複製程式碼
我們進去這個ServiceLoader
,其實無非就是一個構造器而已了,關鍵程式碼我截下:
this.service = svc;
this.serviceName = svc.getName();
this.layer = null;
this.loader = cl;
this.acc = (System.getSecurityManager() != null)
? AccessController.getContext()
: null;複製程式碼
有了這個載入器之後,其實我們就拿到了上下文和訪問許可權的一些東西,我們再來看看這個類的欄位:
public final class ServiceLoader<S>
implements Iterable<S>
{
// The class or interface representing the service being loaded
private final Class<S> service;
// The class of the service type
private final String serviceName;
// The module layer used to locate providers; null when locating
// providers using a class loader
private final ModuleLayer layer;
// The class loader used to locate, load, and instantiate providers;
// null when locating provider using a module layer
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// The lazy-lookup iterator for iterator operations
private Iterator<Provider<S>> lookupIterator1;
private final List<S> instantiatedProviders = new ArrayList<>();
// The lazy-lookup iterator for stream operations
private Iterator<Provider<S>> lookupIterator2;
private final List<Provider<S>> loadedProviders = new ArrayList<>();
private boolean loadedAllProviders; // true when all providers loaded
// Incremented when reload is called
private int reloadCount;
private static JavaLangAccess LANG_ACCESS;複製程式碼
可以看到,它實現了按照我們分析的Iterable
介面,這樣我們就可以多了很多操作,而且我們也看到了下面這幾個東西,這樣我們就可以做事情了:
private Iterator<Provider<S>> lookupIterator2;
private final List<Provider<S>> loadedProviders = new ArrayList<>();
private boolean loadedAllProviders; // true when all providers loaded複製程式碼
我們走進findFirst
這個方法來看看:
public Optional<S> findFirst() {
Iterator<S> iterator = iterator();
if (iterator.hasNext()) {
return Optional.of(iterator.next());
} else {
return Optional.empty();
}
}複製程式碼
我們看到了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;
...
}複製程式碼
現在newLookupIterator()
進入到我們的視野中,沒有條件建立條件,剛開始我們可沒有拿到米,現在去找米去:
/**
* Returns a new lookup iterator.
*/
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();
}
}
};
}
}複製程式碼
這裡拋開其他我們來看ModuleServicesLookupIterator()
這個建構函式 :
ModuleServicesLookupIterator() {
this.currentLoader = loader;
this.iterator = iteratorFor(loader);
}複製程式碼
映入眼簾的是iteratorFor(ClassLoader loader)
這個方法:
/**
* Returns an iterator to iterate over the implementations of {@code
* service} in modules defined to the given class loader or in custom
* layers with a module defined to this class loader.
*/
private Iterator<ServiceProvider> iteratorFor(ClassLoader loader) {
// modules defined to the class loader
ServicesCatalog catalog;
if (loader == null) {
catalog = BootLoader.getServicesCatalog();
} else {
catalog = ServicesCatalog.getServicesCatalogOrNull(loader);
}
//此處往下到我中文標記結束就是我們的正主了
List<ServiceProvider> providers;
if (catalog == null) {
providers = List.of();
} else {
providers = catalog.findServices(serviceName);
}
//結束
// modules in layers that define modules to the class loader
ClassLoader platformClassLoader = ClassLoaders.platformClassLoader();
if (loader == null || loader == platformClassLoader) {
return providers.iterator();
} else {
List<ServiceProvider> allProviders = new ArrayList<>(providers);
Iterator<ModuleLayer> iterator = LANG_ACCESS.layers(loader).iterator();
while (iterator.hasNext()) {
ModuleLayer layer = iterator.next();
for (ServiceProvider sp : providers(layer)) {
ClassLoader l = loaderFor(sp.module());
if (l != null && l != platformClassLoader) {
allProviders.add(sp);
}
}
}
return allProviders.iterator();
}
}複製程式碼
這裡終於找到了findServices(String service)
這個方法:
/**
* Returns the (possibly empty) list of service providers that implement
* the given service type.
*/
public List<ServiceProvider> findServices(String service) {
return map.getOrDefault(service, Collections.emptyList());
}複製程式碼
結合getOrDefault
的原始碼可知:
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}複製程式碼
是不是和我們的具體思路接上軌了
拿到我們想要的大米
而我們的provider
例項從何而來,請容我娓娓道來咯:
我們從jdk.internal.module.Modules
這個模組定義類中可以找到addProvides
這個方法,也就是說在我們載入這個模組的時候,這個動作就已經要幹活了:
/**
* Updates module m to provide a service
*/
public static void addProvides(Module m, Class<?> service, Class<?> impl) {
ModuleLayer layer = m.getLayer();
PrivilegedAction<ClassLoader> pa = m::getClassLoader;
ClassLoader loader = AccessController.doPrivileged(pa);
ClassLoader platformClassLoader = ClassLoaders.platformClassLoader();
if (layer == null || loader == null || loader == platformClassLoader) {
// update ClassLoader catalog
ServicesCatalog catalog;
if (loader == null) {
catalog = BootLoader.getServicesCatalog();
} else {
catalog = ServicesCatalog.getServicesCatalog(loader);
}
catalog.addProvider(m, service, impl);
}
if (layer != null) {
// update Layer catalog
JLA.getServicesCatalog(layer).addProvider(m, service, impl);
}
}複製程式碼
然後我們可以從sun.instrument.InstrumentationImpl
這個類來看到其工作方式(通過其註釋就可以看到這個類和JVM相關):
在載入模組的時候就執行了下面的程式碼,看下面update provides
這個註釋的程式碼可以知道其呼叫了上面的addProvides
這個方法,而最後也是呼叫了addProvider(m, service, impl)
/**
* The Java side of the JPLIS implementation. Works in concert with a native JVMTI agent
* to implement the JPLIS API set. Provides both the Java API implementation of
* the Instrumentation interface and utility Java routines to support the native code.
* Keeps a pointer to the native data structure in a scalar field to allow native
* processing behind native methods.
*/
public class InstrumentationImpl implements Instrumentation {
...
@Override
public void redefineModule(Module module,
Set<Module> extraReads,
Map<String, Set<Module>> extraExports,
Map<String, Set<Module>> extraOpens,
Set<Class<?>> extraUses,
Map<Class<?>, List<Class<?>>> extraProvides)
{
if (!module.isNamed())
return;
if (!isModifiableModule(module))
throw new UnmodifiableModuleException(module.getName());
// copy and check reads
extraReads = new HashSet<>(extraReads);
if (extraReads.contains(null))
throw new NullPointerException("'extraReads' contains null");
// copy and check exports and opens
extraExports = cloneAndCheckMap(module, extraExports);
extraOpens = cloneAndCheckMap(module, extraOpens);
// copy and check uses
extraUses = new HashSet<>(extraUses);
if (extraUses.contains(null))
throw new NullPointerException("'extraUses' contains null");
// copy and check provides
Map<Class<?>, List<Class<?>>> tmpProvides = new HashMap<>();
for (Map.Entry<Class<?>, List<Class<?>>> e : extraProvides.entrySet()) {
Class<?> service = e.getKey();
if (service == null)
throw new NullPointerException("'extraProvides' contains null");
List<Class<?>> providers = new ArrayList<>(e.getValue());
if (providers.isEmpty())
throw new IllegalArgumentException("list of providers is empty");
providers.forEach(p -> {
if (p.getModule() != module)
throw new IllegalArgumentException(p + " not in " + module);
if (!service.isAssignableFrom(p))
throw new IllegalArgumentException(p + " is not a " + service);
});
tmpProvides.put(service, providers);
}
extraProvides = tmpProvides;
// update reads
extraReads.forEach(m -> Modules.addReads(module, m));
// update exports
for (Map.Entry<String, Set<Module>> e : extraExports.entrySet()) {
String pkg = e.getKey();
Set<Module> targets = e.getValue();
targets.forEach(m -> Modules.addExports(module, pkg, m));
}
// update opens
for (Map.Entry<String, Set<Module>> e : extraOpens.entrySet()) {
String pkg = e.getKey();
Set<Module> targets = e.getValue();
targets.forEach(m -> Modules.addOpens(module, pkg, m));
}
// update uses
extraUses.forEach(service -> Modules.addUses(module, service));
// update provides
for (Map.Entry<Class<?>, List<Class<?>>> e : extraProvides.entrySet()) {
Class<?> service = e.getKey();
List<Class<?>> providers = e.getValue();
providers.forEach(p -> Modules.addProvides(module, service, p));
}
}
...
}複製程式碼
Instrumentation
介面有一段很重要的註釋,大家自己看吧,就不多說了:
/**
* This class provides services needed to instrument Java
* programming language code.
* Instrumentation is the addition of byte-codes to methods for the
* purpose of gathering data to be utilized by tools.
* Since the changes are purely additive, these tools do not modify
* application state or behavior.
* Examples of such benign tools include monitoring agents, profilers,
* coverage analyzers, and event loggers.
*
* <P>
* There are two ways to obtain an instance of the
* <code>Instrumentation</code> interface:
*
* <ol>
* <li><p> When a JVM is launched in a way that indicates an agent
* class. In that case an <code>Instrumentation</code> instance
* is passed to the <code>premain</code> method of the agent class.
* </p></li>
* <li><p> When a JVM provides a mechanism to start agents sometime
* after the JVM is launched. In that case an <code>Instrumentation</code>
* instance is passed to the <code>agentmain</code> method of the
* agent code. </p> </li>
* </ol>
* <p>
* These mechanisms are described in the
* {@linkplain java.lang.instrument package specification}.
* <p>
* Once an agent acquires an <code>Instrumentation</code> instance,
* the agent may call methods on the instance at any time.
*
* @since 1.5
*/
public interface Instrumentation {
}複製程式碼
那麼,我們最後,走入addProvider(m, service, impl)
這個方法中:
/**
* Add a provider in the given module to this services catalog
*
* @apiNote This method is for use by java.lang.instrument
*/
public void addProvider(Module module, Class<?> service, Class<?> impl) {
List<ServiceProvider> list = providers(service.getName());
list.add(new ServiceProvider(module, impl.getName()));
}
...
public final class ServiceProvider {
private final Module module;
private final String providerName;
public ServiceProvider(Module module, String providerName) {
this.module = module;
this.providerName = providerName;
}
...
}複製程式碼
再經過了這麼曲曲折折的過程,終於拿到了ServiceProvider
,裡面包括了我們所要呼叫實現類的地址資訊
於是,看下ServiceLoader這個類定義的Provider
靜態內部介面:
/**
* Represents a service provider located by {@code ServiceLoader}.
*
* <p> When using a loader's {@link ServiceLoader#stream() stream()} method
* then the elements are of type {@code Provider}. This allows processing
* to select or filter on the provider class without instantiating the
* provider. </p>
*
* @param <S> The service type
* @since 9
* @spec JPMS
*/
public static interface Provider<S> extends Supplier<S> {
/**
* Returns the provider type. There is no guarantee that this type is
* accessible or that it has a public no-args constructor. The {@link
* #get() get()} method should be used to obtain the provider instance.
*
* <p> When a module declares that the provider class is created by a
* provider factory then this method returns the return type of its
* public static "{@code provider()}" method.
*
* @return The provider type
*/
Class<? extends S> type();
/**
* Returns an instance of the provider.
*
* @return An instance of the provider.
*
* @throws ServiceConfigurationError
* If the service provider cannot be instantiated, or in the
* case of a provider factory, the public static
* "{@code provider()}" method returns {@code null} or throws
* an error or exception. The {@code ServiceConfigurationError}
* will carry an appropriate cause where possible.
*/
@Override S get();
}複製程式碼
然後我們回到之前追到的iteratorFor
方法,知道其返回的是 Iterator<ServiceProvider>
型別
/**
* Returns an iterator to iterate over the implementations of {@code
* service} in modules defined to the given class loader or in custom
* layers with a module defined to this class loader.
*/
private Iterator<ServiceProvider> iteratorFor(ClassLoader loader) {
// modules defined to the class loader
ServicesCatalog catalog;
if (loader == null) {
catalog = BootLoader.getServicesCatalog();
} else {
catalog = ServicesCatalog.getServicesCatalogOrNull(loader);
}
List<ServiceProvider> providers;
if (catalog == null) {
providers = List.of();
} else {
providers = catalog.findServices(serviceName);
}
...
}複製程式碼
然後回到ModuleServicesLookupIterator()
這個建構函式,直接看這個內部類,也就是呼叫這個
/**
* Implements lazy service provider lookup of service providers that
* are provided by modules defined to a class loader or to modules in
* layers with a module defined to the class loader.
*/
private final class ModuleServicesLookupIterator<T>
implements Iterator<Provider<T>>
{
ClassLoader currentLoader;
Iterator<ServiceProvider> iterator;
Provider<T> nextProvider;
ServiceConfigurationError nextError;
ModuleServicesLookupIterator() {
this.currentLoader = loader;
this.iterator = iteratorFor(loader);
}
...
}複製程式碼
在newLookupIterator
這個方法中得到ModuleServicesLookupIterator
的例項first
,並呼叫其hasNext
方法
/**
* Returns a new lookup iterator.
*/
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();
}
}
};
}複製程式碼
我們來進入這個hasNext
方法,也就是在這裡,呼叫了loadProvider
生成了一個bean
@Override
public boolean hasNext() {
while (nextProvider == null && nextError == null) {
// get next provider to load
while (!iterator.hasNext()) {
if (currentLoader == null) {
return false;
} else {
currentLoader = currentLoader.getParent();
iterator = iteratorFor(currentLoader);
}
}
// attempt to load provider
ServiceProvider provider = iterator.next();
try {
@SuppressWarnings("unchecked")
Provider<T> next = (Provider<T>) loadProvider(provider);
nextProvider = next;
} catch (ServiceConfigurationError e) {
nextError = e;
}
}
return true;
}
@Override
public Provider<T> next() {
if (!hasNext())
throw new NoSuchElementException();
Provider<T> provider = nextProvider;
if (provider != null) {
nextProvider = null;
return provider;
} else {
ServiceConfigurationError e = nextError;
assert e != null;
nextError = null;
throw e;
}
}
}複製程式碼
走進這個loadProvider
方法,拋開前面所有,我們只看最後返回為:new ProviderImpl<S>(service, type, ctor, acc)
/**
* Loads a service provider in a module.
*
* Returns {@code null} if the service provider's module doesn't read
* the module with the service type.
*
* @throws ServiceConfigurationError if the class cannot be loaded or
* isn't the expected sub-type (or doesn't define a provider
* factory method that returns the expected type)
*/
private Provider<S> loadProvider(ServiceProvider provider) {
Module module = provider.module();
if (!module.canRead(service.getModule())) {
// module does not read the module with the service type
return null;
}
String cn = provider.providerName();
Class<?> clazz = null;
if (acc == null) {
try {
clazz = Class.forName(module, cn);
} catch (LinkageError e) {
fail(service, "Unable to load " + cn, e);
}
} else {
PrivilegedExceptionAction<Class<?>> pa = () -> Class.forName(module, cn);
try {
clazz = AccessController.doPrivileged(pa);
} catch (PrivilegedActionException pae) {
Throwable x = pae.getCause();
fail(service, "Unable to load " + cn, x);
return null;
}
}
if (clazz == null) {
fail(service, "Provider " + cn + " not found");
}
int mods = clazz.getModifiers();
if (!Modifier.isPublic(mods)) {
fail(service, clazz + " is not public");
}
// if provider in explicit module then check for static factory method
if (inExplicitModule(clazz)) {
Method factoryMethod = findStaticProviderMethod(clazz);
if (factoryMethod != null) {
Class<?> returnType = factoryMethod.getReturnType();
if (!service.isAssignableFrom(returnType)) {
fail(service, factoryMethod + " return type not a subtype");
}
@SuppressWarnings("unchecked")
Class<? extends S> type = (Class<? extends S>) returnType;
return new ProviderImpl<S>(service, type, factoryMethod, acc);
}
}
// no factory method so must be a subtype
if (!service.isAssignableFrom(clazz)) {
fail(service, clazz.getName() + " not a subtype");
}
@SuppressWarnings("unchecked")
Class<? extends S> type = (Class<? extends S>) clazz;
@SuppressWarnings("unchecked")
Constructor<? extends S> ctor = (Constructor<? extends S> ) getConstructor(clazz);
return new ProviderImpl<S>(service, type, ctor, acc);
}複製程式碼
最後,我們通過檢視這個ProviderImpl
類終於得到了我們想要得到的結果。
/**
* A Provider implementation that supports invoking, with reduced
* permissions, the static factory to obtain the provider or the
* provider's no-arg constructor.
*/
private static class ProviderImpl<S> implements Provider<S> {
final Class<S> service;
final Class<? extends S> type;
final Method factoryMethod; // factory method or null
final Constructor<? extends S> ctor; // public no-args constructor or null
final AccessControlContext acc;
ProviderImpl(Class<S> service,
Class<? extends S> type,
Method factoryMethod,
AccessControlContext acc) {
this.service = service;
this.type = type;
this.factoryMethod = factoryMethod;
this.ctor = null;
this.acc = acc;
}
ProviderImpl(Class<S> service,
Class<? extends S> type,
Constructor<? extends S> ctor,
AccessControlContext acc) {
this.service = service;
this.type = type;
this.factoryMethod = null;
this.ctor = ctor;
this.acc = acc;
}
@Override
public Class<? extends S> type() {
return type;
}
@Override
public S get() {
if (factoryMethod != null) {
return invokeFactoryMethod();
} else {
return newInstance();
}
}複製程式碼
IOC和模組化所提供的類似效果的最大的區別就是,前者是提供了例項化的bean(即便是通過AOP實現的,這點很重要,Java9模組化在使用Spring的時候會有特別的設定),而且是基於Spring容器的單例的存在(多例注入的問題請參考我這方面的Spring原始碼解析),後者是提供了class位元組碼所在的路徑,用的時候內部會自行生成例項,所以是多例的。
其實整個過程,Java的模組化檔案系統起了很大的作用(這塊看情況假如篇幅比較長久不放在我的書裡了),然後自己追原始碼的思路也在這裡給大家展現了一番,希望可以對大家有所幫助,看原始碼不要上來就瞎找的。另外,最重要的一點就是,不要因為原始碼很多,很複雜就輕言放棄,看的多了,看的久了,自然就有一套屬於自己的方法論了。