#432 Injector improvements

- Separate FieldInjection from default fallback for no-Inject public no-args constructor classes
- Make CommandInitializer a normal, instantiable service
- Add various injections instead of fetching through command service
This commit is contained in:
ljacqu
2016-05-08 11:15:56 +02:00
parent 3e6223dc5a
commit 5e5836f167
50 changed files with 554 additions and 332 deletions
@@ -132,7 +132,8 @@ public class AuthMeServiceInitializer {
* @return the instantiated object
*/
private <T> T instantiate(Class<T> clazz, Set<Class<?>> traversedClasses) {
Injection<T> injection = firstNotNull(ConstructorInjection.provide(clazz), FieldInjection.provide(clazz));
Injection<T> injection = firstNotNull(
ConstructorInjection.provide(clazz), FieldInjection.provide(clazz), InstantiationFallback.provide(clazz));
if (injection == null) {
throw new IllegalStateException("Did not find injection method for " + clazz + ". Make sure you have "
+ "a constructor with @Inject or fields with @Inject. Fields with @Inject require "
@@ -1,14 +0,0 @@
package fr.xephi.authme.initialization;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to denote the collection of AuthMe commands.
*/
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BaseCommands {
}
@@ -74,7 +74,7 @@ class FieldInjection<T> implements Injection<T> {
*
* @param clazz the class to provide field injection for
* @param <T> the class' type
* @return field injection provider for the given class
* @return field injection provider for the given class, or null if not applicable
*/
public static <T> Provider<FieldInjection<T>> provide(final Class<T> clazz) {
return new Provider<FieldInjection<T>>() {
@@ -85,7 +85,7 @@ class FieldInjection<T> implements Injection<T> {
return null;
}
List<Field> fields = getInjectionFields(clazz);
return fields == null ? null : new FieldInjection<>(constructor, fields);
return fields.isEmpty() ? null : new FieldInjection<>(constructor, fields);
}
};
}
@@ -0,0 +1,84 @@
package fr.xephi.authme.initialization;
import javax.inject.Inject;
import javax.inject.Provider;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* Fallback instantiation method for classes with an accessible no-args constructor
* and no no {@link Inject} annotations whatsoever.
*/
public class InstantiationFallback<T> implements Injection<T> {
private final Constructor<T> constructor;
private InstantiationFallback(Constructor<T> constructor) {
this.constructor = constructor;
}
@Override
public Class<?>[] getDependencies() {
return new Class<?>[0];
}
@Override
public Class<?>[] getDependencyAnnotations() {
return new Class<?>[0];
}
@Override
public T instantiateWith(Object... values) {
if (values == null || values.length > 0) {
throw new UnsupportedOperationException("Instantiation fallback cannot have parameters");
}
try {
return constructor.newInstance();
} catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
throw new UnsupportedOperationException(e);
}
}
/**
* Returns an instantiation fallback if the class is applicable.
*
* @param clazz the class
* @param <T> the class' type
* @return instantiation fallback provider for the given class, or null if not applicable
*/
public static <T> Provider<InstantiationFallback<T>> provide(final Class<T> clazz) {
return new Provider<InstantiationFallback<T>>() {
@Override
public InstantiationFallback<T> get() {
Constructor<T> noArgsConstructor = getNoArgsConstructor(clazz);
// Return fallback only if we have no args constructor and no @Inject annotation anywhere
if (noArgsConstructor != null
&& !isInjectAnnotationPresent(clazz.getDeclaredConstructors())
&& !isInjectAnnotationPresent(clazz.getDeclaredFields())
&& !isInjectAnnotationPresent(clazz.getDeclaredMethods())) {
return new InstantiationFallback<>(noArgsConstructor);
}
return null;
}
};
}
private static <T> Constructor<T> getNoArgsConstructor(Class<T> clazz) {
try {
// Note ljacqu 20160504: getConstructor(), unlike getDeclaredConstructor(), only considers public members
return clazz.getConstructor();
} catch (NoSuchMethodException e) {
return null;
}
}
private static <A extends AccessibleObject> boolean isInjectAnnotationPresent(A[] accessibles) {
for (A accessible : accessibles) {
if (accessible.isAnnotationPresent(Inject.class)) {
return true;
}
}
return false;
}
}