Service injector - implement stricter requirements for PostConstruct methods

- Implement similar restrictions as prescribed by the PostConstruct documentation:
   - Class may have at most one method annotated with PostConstruct
   - PostConstruct method must return void
- Javadoc: replace mentions of injector construction where any injection method was meant
This commit is contained in:
ljacqu 2016-05-19 21:50:48 +02:00
parent 383820cd22
commit f014485789
5 changed files with 86 additions and 38 deletions

View File

@ -144,8 +144,8 @@ public class AuthMeServiceInitializer {
} }
/** /**
* Instantiates the given class by locating an @Inject constructor and retrieving * Instantiates the given class by locating its @Inject elements and retrieving
* or instantiating its parameters. * or instantiating the required instances.
* *
* @param clazz the class to instantiate * @param clazz the class to instantiate
* @param traversedClasses collection of classes already traversed * @param traversedClasses collection of classes already traversed
@ -164,13 +164,13 @@ public class AuthMeServiceInitializer {
validateInjectionHasNoCircularDependencies(injection.getDependencies(), traversedClasses); validateInjectionHasNoCircularDependencies(injection.getDependencies(), traversedClasses);
Object[] dependencies = resolveDependencies(injection, traversedClasses); Object[] dependencies = resolveDependencies(injection, traversedClasses);
T object = injection.instantiateWith(dependencies); T object = injection.instantiateWith(dependencies);
executePostConstructMethods(object); executePostConstructMethod(object);
return object; return object;
} }
/** /**
* Resolves the dependencies for the given constructor, i.e. returns a collection that satisfy * Resolves the dependencies for the given class instantiation, i.e. returns a collection that satisfy
* the constructor's parameter types by retrieving elements or instantiating them where necessary. * the class' dependencies by retrieving elements or instantiating them where necessary.
* *
* @param injection the injection parameters * @param injection the injection parameters
* @param traversedClasses collection of traversed classes * @param traversedClasses collection of traversed classes
@ -247,22 +247,21 @@ public class AuthMeServiceInitializer {
+ "allowed packages. It must be provided explicitly or the package must be passed to the constructor."); + "allowed packages. It must be provided explicitly or the package must be passed to the constructor.");
} }
private static void executePostConstructMethods(Object object) { /**
for (Method method : object.getClass().getDeclaredMethods()) { * Executes an object's method annotated with {@link PostConstruct} if present.
if (method.isAnnotationPresent(PostConstruct.class)) { * Throws an exception if there are multiple such methods, or if the method is static.
if (method.getParameterTypes().length == 0 && !Modifier.isStatic(method.getModifiers())) { *
* @param object the object to execute the post construct method for
*/
private static void executePostConstructMethod(Object object) {
Method postConstructMethod = getAndValidatePostConstructMethod(object.getClass());
if (postConstructMethod != null) {
try { try {
method.setAccessible(true); postConstructMethod.setAccessible(true);
method.invoke(object); postConstructMethod.invoke(object);
} catch (IllegalAccessException | InvocationTargetException e) { } catch (IllegalAccessException | InvocationTargetException e) {
throw new UnsupportedOperationException(e); throw new UnsupportedOperationException(e);
} }
} else {
throw new IllegalStateException(String.format("@PostConstruct methods may not be static or have "
+ " any parameters. Method '%s' of class '%s' is either static or has parameters",
method.getName(), object.getClass().getSimpleName()));
}
}
} }
} }
@ -272,6 +271,31 @@ public class AuthMeServiceInitializer {
} }
} }
/**
* Validate and locate the given class' post construct method. Returns {@code null} if none present.
*
* @param clazz the class to search
* @return post construct method, or null
*/
private static Method getAndValidatePostConstructMethod(Class<?> clazz) {
Method postConstructMethod = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
if (postConstructMethod != null) {
throw new IllegalStateException("Multiple methods with @PostConstruct on " + clazz);
} else if (method.getParameterTypes().length > 0 || Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@PostConstruct method may not be static or have any parameters. "
+ "Invalid method in " + clazz);
} else if (method.getReturnType() != void.class) {
throw new IllegalStateException("@PostConstruct method must be void. Offending class: " + clazz);
} else {
postConstructMethod = method;
}
}
}
return postConstructMethod;
}
@SafeVarargs @SafeVarargs
private static <T> Injection<T> firstNotNull(Provider<? extends Injection<T>>... providers) { private static <T> Injection<T> firstNotNull(Provider<? extends Injection<T>>... providers) {
for (Provider<? extends Injection<T>> provider : providers) { for (Provider<? extends Injection<T>> provider : providers) {

View File

@ -96,11 +96,9 @@ public class PermissionsManager implements PermissionsService {
/** /**
* Setup and hook into the permissions systems. * Setup and hook into the permissions systems.
*
* @return The detected permissions system.
*/ */
@PostConstruct @PostConstruct
public PermissionsSystemType setup() { public void setup() {
// Force-unhook from current hooked permissions systems // Force-unhook from current hooked permissions systems
unhook(); unhook();
@ -177,7 +175,7 @@ public class PermissionsManager implements PermissionsService {
ConsoleLogger.info("Hooked into " + type.getName() + "!"); ConsoleLogger.info("Hooked into " + type.getName() + "!");
// Return the used permissions system type // Return the used permissions system type
return type; return;
} catch (Exception ex) { } catch (Exception ex) {
// An error occurred, show a warning message // An error occurred, show a warning message
@ -187,7 +185,6 @@ public class PermissionsManager implements PermissionsService {
// No recognized permissions system found, show a message and return // No recognized permissions system found, show a message and return
ConsoleLogger.info("No supported permissions system found! Permissions are disabled!"); ConsoleLogger.info("No supported permissions system found! Permissions are disabled!");
return null;
} }
/** /**

View File

@ -166,7 +166,7 @@ public class AuthMeServiceInitializerTest {
PostConstructTestClass testClass = initializer.get(PostConstructTestClass.class); PostConstructTestClass testClass = initializer.get(PostConstructTestClass.class);
// then // then
assertThat(testClass.werePostConstructsCalled(), equalTo(true)); assertThat(testClass.wasPostConstructCalled(), equalTo(true));
assertThat(testClass.getBetaManager(), not(nullValue())); assertThat(testClass.getBetaManager(), not(nullValue()));
} }
@ -188,6 +188,18 @@ public class AuthMeServiceInitializerTest {
initializer.get(InvalidPostConstruct.ThrowsException.class); initializer.get(InvalidPostConstruct.ThrowsException.class);
} }
@Test(expected = RuntimeException.class)
public void shouldThrowForMultiplePostConstructMethods() {
// given / when / then
initializer.get(InvalidPostConstruct.MultiplePostConstructs.class);
}
@Test(expected = RuntimeException.class)
public void shouldThrowForPostConstructNotReturningVoid() {
// given / when / then
initializer.get(InvalidPostConstruct.NotVoidReturnType.class);
}
@Test(expected = RuntimeException.class) @Test(expected = RuntimeException.class)
public void shouldThrowForAbstractNonRegisteredDependency() { public void shouldThrowForAbstractNonRegisteredDependency() {
// given / when / then // given / when / then

View File

@ -9,10 +9,8 @@ import javax.inject.Inject;
public abstract class InvalidPostConstruct { public abstract class InvalidPostConstruct {
public static final class WithParams { public static final class WithParams {
@SuppressWarnings("unused")
@Inject @Inject
private AlphaService alphaService; private AlphaService alphaService;
@SuppressWarnings("unused")
@Inject @Inject
private ProvidedClass providedClass; private ProvidedClass providedClass;
@ -41,4 +39,28 @@ public abstract class InvalidPostConstruct {
throw new IllegalStateException("Exception in post construct"); throw new IllegalStateException("Exception in post construct");
} }
} }
public static final class NotVoidReturnType {
@Inject
private ProvidedClass providedClass;
@PostConstruct
public int returnsInt() {
return 42;
}
}
public static final class MultiplePostConstructs {
@Inject
private ProvidedClass providedClass;
@PostConstruct
public void postConstruct1() {
// --
}
@PostConstruct
public void postConstruct2() {
// --
}
}
} }

View File

@ -17,22 +17,15 @@ public class PostConstructTestClass implements SettingsDependent {
@Inject @Inject
private BetaManager betaManager; private BetaManager betaManager;
private boolean wasPostConstructCalled = false; private boolean wasPostConstructCalled = false;
private boolean wasSecondPostConstructCalled = false;
private boolean wasReloaded = false; private boolean wasReloaded = false;
@PostConstruct @PostConstruct
protected void setFieldToTrue() { public void postConstructMethod() {
wasPostConstructCalled = true; wasPostConstructCalled = true;
} }
@PostConstruct public boolean wasPostConstructCalled() {
public int otherPostConstructMethod() { return wasPostConstructCalled;
wasSecondPostConstructCalled = true;
return 42;
}
public boolean werePostConstructsCalled() {
return wasPostConstructCalled && wasSecondPostConstructCalled;
} }
public BetaManager getBetaManager() { public BetaManager getBetaManager() {