2021SC@SDUSC
1, Code analysis content
This blog introduces the fourth part of the inject module in ActiveJ: module package. The module package required by inject is related to the binding package.
Dependency graph is difficult to create directly, so we use a simple but powerful DSL to provide automatic graph transformation, generation and verification mechanism.
All these preprocessing steps are performed by compiling the module at startup. Each module exports a combined binding. If any key has two or more bindings, use the Multibinder reduce function provided by the user to simplify them into one binding.
Let's take a look at the code used to call the Module interface:
public interface Module { Trie<Scope, Map<Key<?>, Set<Binding<?>>>> getBindings(); Map<Integer, Set<BindingTransformer<?>>> getBindingTransformers(); Map<Class<?>, Set<BindingGenerator<?>>> getBindingGenerators(); Map<Key<?>, Multibinder<?>> getMultibinders(); }
2, module package structure
There are 12 classes in the module package, including 4 interfaces, 1 annotation and 7 classes. This blog focuses on several of them.
3, Code interpretation
1.Module class:
A module is an object that provides a set of bindings, converters, generators, or multiple binders arranged by keys in some data structures.
The meaning of the above explanation can be seen from the following code. Scope, Key and Binding are used.
Trie<Scope, Map<Key<?>, Set<Binding<?>>>> getBindings();
The following code obtains the binding set through key arrangement.
default Trie<Scope, Set<Key<?>>> getExports() { return getBindings().map(map -> map.keySet().stream().filter(key -> !isUnique(key.getQualifier())).collect(toSet())); }
Return an empty module:
static Module empty() { return Modules.EMPTY; }
To create a Module from a given binding diagram trie:
static Module of(Trie<Scope, Map<Key<?>, Set<Binding<?>>>> bindings) { return new SimpleModule(bindings, emptyMap(), emptyMap(), emptyMap()); }
2.Modules class
This class contains a set of utilities for handling Module interface modules. Let me introduce some methods that I think are more useful.
2.1 combine (collection < module > modules) method
Combine multiple modules into one module:
This method combines multiple Module classes into a SimpleModule class. This method can simplify the Module, which is still used.
public static Module combine(Collection<Module> modules) { if (modules.size() == 1) { return modules.iterator().next(); } Trie<Scope, Map<Key<?>, Set<Binding<?>>>> bindings = Trie.merge(bindingMultimapMerger(), new HashMap<>(), modules.stream().map(Module::getBindings)); Map<KeyPattern<?>, Set<BindingGenerator<?>>> bindingGenerators = new HashMap<>(); Map<KeyPattern<?>, Set<BindingTransformer<?>>> bindingTransformers = new HashMap<>(); Map<Key<?>, Multibinder<?>> multibinders = new HashMap<>(); for (Module module : modules) { combineMultimap(bindingTransformers, module.getBindingTransformers()); combineMultimap(bindingGenerators, module.getBindingGenerators()); mergeMultibinders(multibinders, module.getMultibinders()); } return new SimpleModule(bindings, bindingTransformers, bindingGenerators, multibinders); }
2.2override (list < module > modules) method
Each given module is continuously covered with the next module, and the cumulative result is returned.
public static Module override(List<Module> modules) { return modules.stream().reduce(Module.empty(), Modules::override); }
2.3 ignorescopes (module from) method
Create a module, merge all trie nodes into one and place them on the root node. Basically, any scope is ignored. This is useful for some tests.
public static Module ignoreScopes(Module from) { Map<Key<?>, Set<Binding<?>>> bindings = new HashMap<>(); Map<Key<?>, Scope[]> scopes = new HashMap<>(); from.getBindings().dfs(UNSCOPED, (scope, localBindings) -> localBindings.forEach((k, b) -> { bindings.merge(k, b, ($, $2) -> { Scope[] alreadyThere = scopes.get(k); String where = alreadyThere.length == 0 ? "in root" : "in scope " + getScopeDisplayString(alreadyThere); throw new IllegalStateException("Duplicate key " + k + ", already defined " + where + " and in scope " + getScopeDisplayString(scope)); }); scopes.put(k, scope); })); return new SimpleModule(Trie.leaf(bindings), from.getBindingTransformers(), from.getBindingGenerators(), from.getMultibinders()); }
3.AbstractModule class
This class is an abstract module wrapper around the ModuleBuilder class. To facilitate conversion, it provides functions similar to some other DI frameworks.
This class is a class that implements the Module interface.
The construction method is as follows:
public AbstractModule() { StackTraceElement[] trace = Thread.currentThread().getStackTrace(); StackTraceElement found = null; Class<?> cls = getClass(); for (int i = 2; i < trace.length; i++) { StackTraceElement element = trace[i]; try { String className = element.getClassName(); Class<?> traceCls = Class.forName(className); if (!traceCls.isAssignableFrom(cls) && !className.startsWith("sun.reflect") && !className.startsWith("java.lang")) { found = element; break; } } catch (ClassNotFoundException ignored) { break; } } this.location = found; this.builder = new ModuleBuilderImpl<>(getName(), location); }
4.ModuleBuilder class
This interface is used to restrict DSL s. Basically, it does not allow any method in ModuleBuilder0 not listed below to be called without first calling bind (...)} in bind.
4.1 install method
Adds all bindings, converters, generators, and multi binders in a given module to this module. It's like defining all of these directly in this module.
ModuleBuilder install(Collection<Module> modules); default ModuleBuilder install(Module... modules) { return install(Arrays.asList(modules)); }
4.2 scan method
Scan the class hierarchy and install the providers in each class as modules,
This way, the export will not interfere between classes. The Class parameter is used to specify which Class in the hierarchy to start with.
ModuleBuilder scan(@NotNull Class<?> containerClass, @Nullable Object container); //Like scan, the starting class defaults to the class of the object instance. default ModuleBuilder scan(Object container) { return scan(container.getClass(), container); } //It is the same as scan, but only scans static methods, independent of class instances. //The non static annotated method is IllegalStateException prohibited. default ModuleBuilder scan(Class<?> container) { return scan(container, null); }
5.DefaultModule class
This Module provides a set of default generators and implements the Module interface.
The first attempts to generate a binding for any missing keys by searching the Inject constructor.
The second generates an instance of any Key SomeType.
The purpose is to get materialized types from generics in templating providers.
The last two generate appropriate instances for InstanceProvider and InstanceInjector requests.
public static class InstanceProviderImpl<T> implements InstanceProvider<T> { private final Key<T> key; private final CompiledBinding<T> compiledBinding; private final AtomicReferenceArray[] scopedInstances; private final int synchronizedScope; public InstanceProviderImpl(Key<T> key, CompiledBinding<T> compiledBinding, AtomicReferenceArray[] scopedInstances, int synchronizedScope) { this.key = key; this.compiledBinding = compiledBinding; this.scopedInstances = scopedInstances; this.synchronizedScope = synchronizedScope; } @Override public Key<T> key() { return key; } @Override public T get() { return compiledBinding.getInstance(scopedInstances, synchronizedScope); } @Override public String toString() { return "InstanceProvider<" + key.getDisplayString() + ">"; } }
6.SimpleModule class
The SimpleModule class is also the implements Module interface. This class is designed to implement simple form Modules. There is a combine method in the Modules class described above, which is to combine multiple Modules into a SimpleModule. Its properties are consistent with Modules.
The construction method is as follows:
public SimpleModule(Trie<Scope, Map<Key<?>, Set<Binding<?>>>> bindings, Map<KeyPattern<?>, Set<BindingTransformer<?>>> transformers, Map<KeyPattern<?>, Set<BindingGenerator<?>>> generators, Map<Key<?>, Multibinder<?>> multibinders) { this.bindings = bindings; this.transformers = transformers; this.generators = generators; this.multibinders = multibinders; }
This class overrides the four methods of getBindings(), getBindingTransformers(), getBindingGenerators(), getMultibinders(). The code is as follows:
@Override public Trie<Scope, Map<Key<?>, Set<Binding<?>>>> getBindings() { return bindings; } @Override public Map<KeyPattern<?>, Set<BindingTransformer<?>>> getBindingTransformers() { return transformers; } @Override public Map<KeyPattern<?>, Set<BindingGenerator<?>>> getBindingGenerators() { return generators; } @Override public Map<Key<?>, Multibinder<?>> getMultibinders() { return multibinders; }
7.OptionalGeneratorModule class
This class is an extension module. It generates an optional t binding of any type T. If the T binding is not bound or contains T instances, the generated optional binding is empty. OptionalGeneratorModule class inherits AbstractModule class and overrides the configure() method:
protected void configure() { generate(Optional.class, (bindings, scope, key) -> { Binding<?> binding = bindings.get(key.getTypeParameter(0)); return binding != null ? binding.mapInstance(Optional::of) : Binding.toInstance(Optional.empty()); }); }
4, Summary
The code analyzed this time is seven classes in the module package, which are several important classes in the module package. Module in ActiveJ can provide simple and powerful DSL, and provides a mechanism for automatic graph transformation, generation and verification. And it can generate bindings, which is very useful.