Refresh your Java skills--聊聊Java9 中模块化设计是如何实现类似IOC依赖注入效果及与其区别

场景引入

如何实现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做为keyaaaImpl添加到一个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的模块化文件系统起了很大的作用(这块看情况假如篇幅比较长久不放在我的书里了),然后自己追源码的思路也在这里给大家展现了一番,希望可以对大家有所帮助,看源码不要上来就瞎找的。另外,最重要的一点就是,不要因为源码很多,很复杂就轻言放弃,看的多了,看的久了,自然就有一套属于自己的方法论了。

原文:
Refresh your Java skills–聊聊Java9 中模块化设计是如何实现类似IOC依赖注入效果及与其区别

    原文作者:知秋z
    原文地址: https://zhuanlan.zhihu.com/p/30860041
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞