ActiveJ learning experience - inject

Keywords: Java

2021SC@SDUSC

1, Code analysis content

This code analysis is still to analyze the inject part of ActiveJ source code. The following example is an example of using inject:

class Main extends Launcher {
  @Inject
  String message;
  
  @Provides
  String message() {
    return "Hello, world";
  }
  
  @Override
  protected void run() {
    System.out.println(message);
  }
  
  public static void main(String[] args) throws Exception {
    Launcher launcher = new Main();
    launcher.launch(args);
  }
}

In the last blog, I introduced the functions of eight separate classes: Inject, InstanceInjector, InstanceProvider, Key, KeyPattern, Qualifiers, ResourceLocator and Scope. This blog analyzes the functions of the binding package in the core Inject package.

2, Binding overview

Binding is one of the main components of ActiveJ Inject.
It can be reduced to "introspection function", because it only describes the function of creating T instances from the object array and its Dependency array.
It also includes a set of io.activej.inject.module.AbstractModule binding classes DSL, static factory methods, and some function transformations to facilitate the creation of immutable binding modifications.
Application components may need some dependencies to be created, and dependency injection is responsible for providing these required objects to application components. To do this, we need to specify what it needs to provide and how to use the provided objects.
Therefore, the binding has two corresponding properties.
A Set of dependency relationships (POJO and Key) to be created.
BindingCompiler will compile the required instances

 public final class Binding<T> {
     final Set<Dependency> dependencies;
     final BindingCompiler<T> compiler;
}

Binding is like a "recipe" that explains how to create an instance of a component.
Dependency -- displays what ingredients should be used.
Compiler -- know how to put them together.

3, binding package structure

There are 8 classes and 3 interfaces in the binding package. The binding class is the premise of several other classes. The binding class defines some properties and methods that need to be used in other classes and interfaces.

4, Code interpretation

1.Binding class
Binding is one of the main components of ActiveJ Inject.
It can be reduced to "introspection function", because it only describes the function of creating T instances from the object array and its Dependency array.
The objects defined in the Binding class are as follows:

	private final Set<Dependency> dependencies;
	private BindingType type;

	private @Nullable LocationInfo location;

The construction method is as follows:

	protected Binding(@NotNull Set<Dependency> dependencies) {
		this(dependencies, BindingType.REGULAR, null);
	}

	protected Binding(@NotNull Set<Dependency> dependencies, BindingType type, @Nullable LocationInfo location) {
		this.dependencies = dependencies;
		this.type = type;
		this.location = location;
	}

2.BindingGenerator interface
This is a function that attempts to generate missing dependency bindings when the Injector compiles the final binding diagram trie.
An example of such a function can be io.activej.inject.util.ReflectionUtils#generateImplicitBinding the injection DSL.

@FunctionalInterface
public interface BindingGenerator<T> {
	@Nullable Binding<T> generate(BindingLocator bindings, Scope[] scope, Key<T> key);
}

3.BindingGenerators class
BindingGenerators is the default generator that never generates anything.
The module exports multiple mappings of the binding generator with the original class key.
This generator aggregates such mappings into a large generator for use by the Injector#compile method.
For each requested key, a set of generators is obtained according to its original class.
Then all generators in this collection are called, and if one of them and only one of them generates a binding, the binding is returned.
When no binding is generated, the composition generator does not generate a binding. When multiple bindings are generated, this is considered an error.
When the original class of the request key is an interface, it is matched by equality. When it is a class, the generator of its nearest superclass will be called.
Note this when creating generators for specific classes: they are usually created for interfaces or final classes.

public static BindingGenerator<?> combinedGenerator(Map<KeyPattern<?>, Set<BindingGenerator<?>>> generators) {
		LinkedHashMap<KeyPattern<?>, Set<BindingGenerator<?>>> sorted = sortPatternsMap(generators);
		return (bindings, scope, key) -> {
			for (Map.Entry<KeyPattern<?>, Set<BindingGenerator<?>>> entry : sorted.entrySet()) {
				if (entry.getKey().match(key)) {
					for (BindingGenerator<?> generator : entry.getValue()) {
						@Nullable Binding<Object> generated = ((BindingGenerator<Object>) generator).generate(bindings, scope, key);
						if (generated != null) return generated;
					}
				}
			}
			return null;
		};
	}

4, BindingToKey class
The BindingToKey class inherits the Binding class, which is a class bound to the key.

BindingToKey(Key<? extends T> key) {
		super(Collections.singleton(Dependency.toKey(key)));
		this.key = key;
	}

	public Key<? extends T> getKey() {
		return key;
	}

	@Override
	public CompiledBinding<T> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
		//noinspection unchecked
		return (CompiledBinding<T>) compiledBindings.get(key);
	}

5, BindingTransformer interface
This is a conversion function that the Injector applies once for each binding.

@FunctionalInterface
public interface BindingTransformer<T> {
	@NotNull Binding<T> transform(BindingLocator bindings, Scope[] scope, Key<T> key, Binding<T> binding);
}

6, BindingTransformers class
This converter aggregates such mappings into a large generator for use by the Injector#compile method. The mapping is converted to a sorted list of collections. Then, for each of these collections, similar to the bindingGenerator#combinedGenerator generator generator, only zero or one converter in the collection is allowed to return anything other than the binding it gives (as an identity converter).
Therefore, if the priorities of the two converters are different, they can be applied in priority order.

public static BindingTransformer<?> combinedTransformer(Map<KeyPattern<?>, Set<BindingTransformer<?>>> transformers) {
		LinkedHashMap<KeyPattern<?>, Set<BindingTransformer<?>>> sorted = sortPatternsMap(transformers);
		return (bindings, scope, key, binding) -> {
			Binding<Object> result = binding;
			for (Map.Entry<KeyPattern<?>, Set<BindingTransformer<?>>> entry : sorted.entrySet()) {
				if (entry.getKey().match(key)) {
					for (BindingTransformer<?> transformer : entry.getValue()) {
						result = ((BindingTransformer<Object>) transformer).transform(bindings, scope, key, result);
					}
				}
			}
			return result;
		};
	}

6, Dependency class
A simple POJO that combines a Key with a Boolean value indicating where a Key is required and whether it is implicit.
The following are the attributes and construction methods defined by Dependency:

private final Key<?> key;
	private final boolean required;
	private final boolean implicit;

	public Dependency(Key<?> key, boolean required, boolean implicit) {
		this.key = key;
		this.required = required;
		this.implicit = implicit;
	}

Implicit dependencies do not cause loop check errors and are grayed out in debug graphical viz output. Such dependencies should not be instantiated because they may lead to various cycle related errors,
Such infinite recursion.
They are used to describe some logical dependencies, which may or may not be circular.

	public static Dependency implicit(Key<?> key, boolean required) {
		return new Dependency(key, required, true);
	}

	public Key<?> getKey() {
		return key;
	}

	public boolean isRequired() {
		return required;
	}

	public boolean isImplicit() {
		return implicit;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}

		Dependency that = (Dependency) o;

		return required == that.required && Objects.equals(key, that.key);
	}

4, Summary

The codes interpreted this time are classes or interfaces in the binding package. Some are dependencies of other classes, and some are interfaces that need to be implemented in other classes, which are more important.

Posted by groovey on Thu, 14 Oct 2021 15:59:24 -0700