Injector - disallow static PostConstruct methods, add more tests

This commit is contained in:
ljacqu 2016-04-29 23:49:03 +02:00
parent ee08eb9efb
commit 2c491803d3
6 changed files with 148 additions and 38 deletions

View File

@ -121,17 +121,19 @@ public class AuthMeServiceInitializer {
if (Annotation.class.isAssignableFrom(clazz)) {
throw new UnsupportedOperationException("Cannot retrieve annotated elements in this way!");
} else if (objects.containsKey(clazz)) {
return getObject(clazz);
return clazz.cast(objects.get(clazz));
}
// First time we come across clazz, need to instantiate it. Add the clazz to the list of traversed
// classes in a new list, so each path we need to take has its own Set.
// First time we come across clazz, need to instantiate it. Validate that we can do so
validatePackage(clazz);
validateInstantiable(clazz);
// Add the clazz to the list of traversed classes in a new Set, so each path we take has its own Set.
traversedClasses = new HashSet<>(traversedClasses);
traversedClasses.add(clazz);
return instantiate(clazz, traversedClasses);
T object = instantiate(clazz, traversedClasses);
storeObject(object);
return object;
}
/**
@ -154,7 +156,6 @@ public class AuthMeServiceInitializer {
validateInjectionHasNoCircularDependencies(injection.getDependencies(), traversedClasses);
Object[] dependencies = resolveDependencies(injection, traversedClasses);
T object = injection.instantiateWith(dependencies);
storeObject(object);
executePostConstructMethods(object);
return object;
}
@ -186,27 +187,6 @@ public class AuthMeServiceInitializer {
return values;
}
/**
* Internal method to retrieve an object from the objects map for <b>non-annotation classes</b>.
* In such cases, the type of the entry always corresponds to the key, i.e. the entry of key
* {@code Class<T>} is guaranteed to be of type {@code T}.
* <p>
* To retrieve values identified with an annotation, use {@code objects.get(clazz)} directly.
* We do not know or control the type of the value of keys of annotation classes.
*
* @param clazz the class to retrieve the implementation of
* @param <T> the type
* @return the implementation
*/
private <T> T getObject(Class<T> clazz) {
Object o = objects.get(clazz);
if (o == null) {
throw new NullPointerException("No instance of " + clazz + " available");
}
return clazz.cast(o);
}
/**
* Stores the given object with its class as key. Throws an exception if the key already has
* a value associated to it.
@ -262,7 +242,7 @@ public class AuthMeServiceInitializer {
private static void executePostConstructMethods(Object object) {
for (Method method : object.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
if (method.getParameterTypes().length == 0) {
if (method.getParameterTypes().length == 0 && !Modifier.isStatic(method.getModifiers())) {
try {
method.setAccessible(true);
method.invoke(object);
@ -270,8 +250,9 @@ public class AuthMeServiceInitializer {
throw new UnsupportedOperationException(e);
}
} else {
throw new IllegalStateException("@PostConstruct methods must have an empty parameter list. " +
"Found parameters in " + method + " belonging to " + object.getClass());
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()));
}
}
}

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.initialization;
import fr.xephi.authme.initialization.samples.AlphaService;
import fr.xephi.authme.initialization.samples.BadFieldInjection;
import fr.xephi.authme.initialization.samples.BetaManager;
import fr.xephi.authme.initialization.samples.CircularClasses;
@ -18,6 +19,7 @@ import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
/**
@ -47,12 +49,18 @@ public class AuthMeServiceInitializerTest {
}
}
@Test(expected = IllegalStateException.class)
@Test(expected = RuntimeException.class)
public void shouldThrowForInvalidPackage() {
// given / when / then
initializer.get(InvalidClass.class);
}
@Test(expected = RuntimeException.class)
public void shouldThrowForUnregisteredPrimitiveType() {
// given / when / then
initializer.get(int.class);
}
@Test
public void shouldPassValueByAnnotation() {
// given
@ -169,7 +177,19 @@ public class AuthMeServiceInitializerTest {
@Test(expected = RuntimeException.class)
public void shouldThrowForInvalidPostConstructMethod() {
// given / when / then
initializer.get(InvalidPostConstruct.class);
initializer.get(InvalidPostConstruct.WithParams.class);
}
@Test(expected = RuntimeException.class)
public void shouldThrowForStaticPostConstructMethod() {
// given / when / then
initializer.get(InvalidPostConstruct.Static.class);
}
@Test(expected = RuntimeException.class)
public void shouldForwardExceptionFromPostConstruct() {
// given / when / then
initializer.get(InvalidPostConstruct.ThrowsException.class);
}
@Test(expected = RuntimeException.class)
@ -191,4 +211,26 @@ public class AuthMeServiceInitializerTest {
assertThat(cwad.getAbstractDependency() == concrete, equalTo(true));
assertThat(cwad.getAlphaService(), not(nullValue()));
}
@Test(expected = RuntimeException.class)
public void shouldThrowForAlreadyRegisteredClass() {
// given
initializer.register(new BetaManager());
// when / then
initializer.register(BetaManager.class, new BetaManager());
}
@Test
public void shouldCreateNewUntrackedInstance() {
// given / when
AlphaService singletonScoped = initializer.get(AlphaService.class);
AlphaService requestScoped = initializer.newInstance(AlphaService.class);
// then
assertThat(singletonScoped.getProvidedClass(), not(nullValue()));
assertThat(singletonScoped.getProvidedClass(), equalTo(requestScoped.getProvidedClass()));
assertThat(singletonScoped, not(sameInstance(requestScoped)));
}
}

View File

@ -0,0 +1,54 @@
package fr.xephi.authme.initialization;
import fr.xephi.authme.initialization.samples.AlphaService;
import fr.xephi.authme.initialization.samples.BetaManager;
import fr.xephi.authme.initialization.samples.ClassWithAnnotations;
import fr.xephi.authme.initialization.samples.Duration;
import fr.xephi.authme.initialization.samples.FieldInjectionWithAnnotations;
import fr.xephi.authme.initialization.samples.GammaService;
import fr.xephi.authme.initialization.samples.ProvidedClass;
import fr.xephi.authme.initialization.samples.Size;
import org.junit.Test;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Test for {@link FieldInjection}.
*/
public class FieldInjectionTest {
@Test
public void shouldReturnDependencies() {
// given
FieldInjection<FieldInjectionWithAnnotations> injection =
FieldInjection.provide(FieldInjectionWithAnnotations.class).get();
// when
Class<?>[] dependencies = injection.getDependencies();
Class<?>[] annotations = injection.getDependencyAnnotations();
// then
assertThat(dependencies, arrayContaining(BetaManager.class, int.class, long.class, ClassWithAnnotations.class));
assertThat(annotations, arrayContaining((Class<?>) null, Size.class, Duration.class, null));
}
@Test
public void shouldInstantiateClass() {
// given
FieldInjection<BetaManager> injection = FieldInjection.provide(BetaManager.class).get();
ProvidedClass providedClass = new ProvidedClass("");
AlphaService alphaService = AlphaService.newInstance(providedClass);
GammaService gammaService = new GammaService(alphaService);
// when
BetaManager betaManager = injection.instantiateWith(providedClass, gammaService, alphaService);
// then
assertThat(betaManager, not(nullValue()));
assertThat(betaManager.getDependencies(), arrayContaining(providedClass, gammaService, alphaService));
}
}

View File

@ -17,4 +17,14 @@ public class AlphaService {
public ProvidedClass getProvidedClass() {
return providedClass;
}
/**
* Creates a new instance (for instantiations in tests).
*
* @param providedClass .
* @return created instance
*/
public static AlphaService newInstance(ProvidedClass providedClass) {
return new AlphaService(providedClass);
}
}

View File

@ -10,7 +10,7 @@ public class GammaService {
private AlphaService alphaService;
@Inject
GammaService(AlphaService alphaService) {
public GammaService(AlphaService alphaService) {
this.alphaService = alphaService;
}

View File

@ -8,12 +8,35 @@ import javax.inject.Inject;
*/
public class InvalidPostConstruct {
@Inject
private AlphaService alphaService;
@Inject
private ProvidedClass providedClass;
public static final class WithParams {
@Inject
private AlphaService alphaService;
@Inject
private ProvidedClass providedClass;
@PostConstruct
public void invalidPostConstr(BetaManager betaManager) {
WithParams() { }
@PostConstruct
public void invalidPostConstr(BetaManager betaManager) {
}
}
public static final class Static {
@Inject
Static(BetaManager betaManager) {
// --
}
@PostConstruct
public static void invalidMethod() {
// --
}
}
public static final class ThrowsException {
@PostConstruct
public void throwingPostConstruct() {
throw new IllegalStateException("Exception in post construct");
}
}
}