-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathInjector.java
192 lines (173 loc) · 7.64 KB
/
Injector.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - info@scireum.de
*/
package sirius.kernel.di;
import sirius.kernel.Classpath;
import sirius.kernel.Sirius;
import sirius.kernel.di.std.RegisterLoadAction;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.Log;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Central class for collecting and injecting dependencies (which are called <b>parts</b>).
* <p>
* Contains the micro kernel which keeps track of all parts and injects them where required. We consider this
* a micro kernel, as it doesn't directly know any annotations. It rather delegates the work to classes which implement
* defined interfaces. Therefore the implementation is rather short but easily extensible.
* <p>
* Parts are commonly added via subclasses of {@link ClassLoadAction}. These scan each class in the classpath
* and instantiate and insert them into the {@link MutableGlobalContext} if required. Most of these
* <tt>ClassLoadAction</tt> implementations trigger on annotations. A prominent example is the
* {@link RegisterLoadAction} which loads all classes wearing the {@link sirius.kernel.di.std
* .Register}
* annotation. Subclasses of <tt>ClassLoadAction</tt> are discovered automatically.
* <p>
* Accessing parts can be done in two ways. First, one can access the current {@link GlobalContext} via
* {@link #context()}. This can be used to retrieve parts by class or by class and name. The second way
* to access parts is to use marker annotations like {@link sirius.kernel.di.std.Part},
* {@link sirius.kernel.di.std.Parts}. Again, these annotations are not processed by the micro kernel itself, but by
* subclasses of {@link FieldAnnotationProcessor}.
* To process all annotations of a given Java object, {@link GlobalContext#wire(Object)} can be used. This will
* be automatically called for each part which is auto-instantiated by a <tt>ClassLoadAction</tt>.
* <p>
* Also, all annotations on static fields are processed on system startup. This is a simple trick to pass a
* part to objects which are frequently created and destroyed.
*/
public class Injector {
/**
* Logger used by the injection framework. This is public so that annotation processors in sub packages can
* log through this logger.
*/
public static final Log LOG = Log.get("di");
private static PartRegistry ctx = new PartRegistry();
private static List<Class<?>> loadedClasses;
private static List<String> packageFilter;
private static Classpath cp;
private static List<ClassLoadAction> actions;
private Injector() {
}
/**
* Initializes the framework. Must be only called once on system startup.
* <p>
* This is automatically invoked by {@link sirius.kernel.Sirius#start(sirius.kernel.Setup)}.
*
* @param classpath the classpath used to enumerate all classes to be scanned
*/
public static void init(@Nonnull final Classpath classpath) {
ctx = new PartRegistry();
// Make the context itself visible for GlobalContext...
ctx.registerPart(ctx, GlobalContext.class);
cp = classpath;
loadedClasses = new ArrayList<>();
actions = new ArrayList<>();
packageFilter = Sirius.getSettings().getStringList("di.packageFilter");
LOG.INFO("Initializing the MicroKernel....");
LOG.INFO("~ Scanning .class files...");
classpath.find(Pattern.compile(".*?\\.class")).forEach(Injector::loadClass);
LOG.INFO("~ Applying %d class load actions on %d classes...", actions.size(), loadedClasses.size());
loadedClasses.parallelStream().forEach(Injector::applyClassLoadActions);
LOG.INFO("~ Initializing static parts-references...");
loadedClasses.parallelStream().forEach(ctx::wireClass);
LOG.INFO("~ Initializing parts...");
ctx.processAnnotations();
}
private static void loadClass(Matcher matcher) {
String relativePath = matcher.group();
String className = relativePath.substring(0, relativePath.length() - 6).replace("/", ".");
if (!shoudLoadClass(className)) {
return;
}
try {
if (LOG.isFINE()) {
LOG.FINE("Found class: " + className);
}
Class<?> clazz = Class.forName(className, true, cp.getLoader());
if (ClassLoadAction.class.isAssignableFrom(clazz) && !clazz.isInterface()) {
createAndCollectClassLoadAction(className, clazz);
}
loadedClasses.add(clazz);
} catch (NoClassDefFoundError error) {
Exceptions.handle()
.error(error)
.to(LOG)
.withSystemErrorMessage("Failed to load dependent class: %s", className)
.handle();
} catch (Exception exception) {
Exceptions.handle()
.error(exception)
.to(LOG)
.withSystemErrorMessage("Failed to load class %s: %s (%s)", className)
.handle();
}
}
private static void createAndCollectClassLoadAction(String className, Class<?> clazz) {
try {
actions.add((ClassLoadAction) clazz.getDeclaredConstructor().newInstance());
} catch (Exception exception) {
Exceptions.handle()
.error(exception)
.to(LOG)
.withSystemErrorMessage("Failed to instantiate ClassLoadAction: %s - %s (%s)", className)
.handle();
}
}
private static void applyClassLoadActions(Class<?> clazz) {
for (ClassLoadAction action : actions) {
if (action.getTrigger() == null || clazz.isAnnotationPresent(action.getTrigger())) {
if (LOG.isFINE()) {
LOG.FINE("Auto-installing class: %s based on %s", clazz.getName(), action.getClass().getName());
}
try {
action.handle(ctx, clazz);
} catch (Exception exception) {
Exceptions.handle()
.error(exception)
.to(LOG)
.withSystemErrorMessage("Failed to auto-load: %s with ClassLoadAction: %s: %s (%s)",
clazz.getName(),
action.getClass().getSimpleName())
.handle();
}
}
}
}
private static boolean shoudLoadClass(String className) {
if (packageFilter.isEmpty()) {
return true;
}
for (String filter : packageFilter) {
if (className.startsWith(filter)) {
return true;
}
}
return false;
}
/**
* Provides access to the global context, containing all parts
* <p>
* This can also be loaded into a class field using the {@link sirius.kernel.di.std.Part} annotation and
* <tt>GlobalContext</tt> as field type.
*
* @return the global context containing all parts known to the system
*/
public static GlobalContext context() {
return ctx;
}
/**
* Returns a list of all loaded classes.
*
* @return a list of all classes detected at system startup
*/
public static List<Class<?>> getAllLoadedClasses() {
return Collections.unmodifiableList(loadedClasses);
}
}