Injector - don't use instantiation fallback if PostConstruct method is present

- Do not instantiate classes with instantiation fallback if they have a PostConstruct method - thanks @sgdc3 for the hint
- Change Injector test to check exception messages also
This commit is contained in:
ljacqu 2016-05-20 17:57:14 +02:00
parent a355c325c5
commit 244e1a2b7d
7 changed files with 85 additions and 31 deletions

View File

@ -260,7 +260,7 @@ public class AuthMeServiceInitializer {
postConstructMethod.setAccessible(true); postConstructMethod.setAccessible(true);
postConstructMethod.invoke(object); postConstructMethod.invoke(object);
} catch (IllegalAccessException | InvocationTargetException e) { } catch (IllegalAccessException | InvocationTargetException e) {
throw new UnsupportedOperationException(e); throw new UnsupportedOperationException("Error executing @PostConstruct method", e);
} }
} }
} }
@ -287,7 +287,8 @@ public class AuthMeServiceInitializer {
throw new IllegalStateException("@PostConstruct method may not be static or have any parameters. " throw new IllegalStateException("@PostConstruct method may not be static or have any parameters. "
+ "Invalid method in " + clazz); + "Invalid method in " + clazz);
} else if (method.getReturnType() != void.class) { } else if (method.getReturnType() != void.class) {
throw new IllegalStateException("@PostConstruct method must be void. Offending class: " + clazz); throw new IllegalStateException("@PostConstruct method must have return type void. "
+ "Offending class: " + clazz);
} else { } else {
postConstructMethod = method; postConstructMethod = method;
} }

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.initialization; package fr.xephi.authme.initialization;
import javax.annotation.PostConstruct;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider; import javax.inject.Provider;
import java.lang.reflect.AccessibleObject; import java.lang.reflect.AccessibleObject;
@ -8,7 +9,7 @@ import java.lang.reflect.InvocationTargetException;
/** /**
* Fallback instantiation method for classes with an accessible no-args constructor * Fallback instantiation method for classes with an accessible no-args constructor
* and no no {@link Inject} annotations whatsoever. * and no elements whatsoever annotated with {@link Inject} or {@link PostConstruct}.
*/ */
public class InstantiationFallback<T> implements Injection<T> { public class InstantiationFallback<T> implements Injection<T> {
@ -54,9 +55,9 @@ public class InstantiationFallback<T> implements Injection<T> {
Constructor<T> noArgsConstructor = getNoArgsConstructor(clazz); Constructor<T> noArgsConstructor = getNoArgsConstructor(clazz);
// Return fallback only if we have no args constructor and no @Inject annotation anywhere // Return fallback only if we have no args constructor and no @Inject annotation anywhere
if (noArgsConstructor != null if (noArgsConstructor != null
&& !isInjectAnnotationPresent(clazz.getDeclaredConstructors()) && !isInjectionAnnotationPresent(clazz.getDeclaredConstructors())
&& !isInjectAnnotationPresent(clazz.getDeclaredFields()) && !isInjectionAnnotationPresent(clazz.getDeclaredFields())
&& !isInjectAnnotationPresent(clazz.getDeclaredMethods())) { && !isInjectionAnnotationPresent(clazz.getDeclaredMethods())) {
return new InstantiationFallback<>(noArgsConstructor); return new InstantiationFallback<>(noArgsConstructor);
} }
return null; return null;
@ -73,9 +74,9 @@ public class InstantiationFallback<T> implements Injection<T> {
} }
} }
private static <A extends AccessibleObject> boolean isInjectAnnotationPresent(A[] accessibles) { private static <A extends AccessibleObject> boolean isInjectionAnnotationPresent(A[] accessibles) {
for (A accessible : accessibles) { for (A accessible : accessibles) {
if (accessible.isAnnotationPresent(Inject.class)) { if (accessible.isAnnotationPresent(Inject.class) || accessible.isAnnotationPresent(PostConstruct.class)) {
return true; return true;
} }
} }

View File

@ -18,8 +18,11 @@ import fr.xephi.authme.initialization.samples.ProvidedClass;
import fr.xephi.authme.initialization.samples.Size; import fr.xephi.authme.initialization.samples.Size;
import fr.xephi.authme.settings.NewSetting; import fr.xephi.authme.settings.NewSetting;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
@ -36,6 +39,11 @@ public class AuthMeServiceInitializerTest {
private AuthMeServiceInitializer initializer; private AuthMeServiceInitializer initializer;
// As we test many cases that throw exceptions, we use JUnit's ExpectedException Rule
// to make sure that we receive the exception we expect
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Before @Before
public void setInitializer() { public void setInitializer() {
initializer = new AuthMeServiceInitializer(ALLOWED_PACKAGE); initializer = new AuthMeServiceInitializer(ALLOWED_PACKAGE);
@ -54,15 +62,17 @@ public class AuthMeServiceInitializerTest {
} }
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForInvalidPackage() { public void shouldThrowForInvalidPackage() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("outside of the allowed packages");
initializer.get(InvalidClass.class); initializer.get(InvalidClass.class);
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForUnregisteredPrimitiveType() { public void shouldThrowForUnregisteredPrimitiveType() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("Primitive types must be provided");
initializer.get(int.class); initializer.get(int.class);
} }
@ -85,24 +95,27 @@ public class AuthMeServiceInitializerTest {
assertThat(object.getGammaService(), equalTo(initializer.get(BetaManager.class).getDependencies()[1])); assertThat(object.getGammaService(), equalTo(initializer.get(BetaManager.class).getDependencies()[1]));
} }
@Test(expected = RuntimeException.class) @Test
public void shouldRecognizeCircularReferences() { public void shouldRecognizeCircularReferences() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("Found cyclic dependency");
initializer.get(CircularClasses.Circular3.class); initializer.get(CircularClasses.Circular3.class);
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForUnregisteredAnnotation() { public void shouldThrowForUnregisteredAnnotation() {
// given // given
initializer.provide(Size.class, 4523); initializer.provide(Size.class, 4523);
// when / then // when / then
expectRuntimeExceptionWith("must be registered beforehand");
initializer.get(ClassWithAnnotations.class); initializer.get(ClassWithAnnotations.class);
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForFieldInjectionWithNoDefaultConstructor() { public void shouldThrowForFieldInjectionWithoutNoArgsConstructor() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("Did not find injection method");
initializer.get(BadFieldInjection.class); initializer.get(BadFieldInjection.class);
} }
@ -124,36 +137,41 @@ public class AuthMeServiceInitializerTest {
equalTo(result.getBetaManager().getDependencies()[1])); equalTo(result.getBetaManager().getDependencies()[1]));
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForAnnotationAsKey() { public void shouldThrowForAnnotationAsKey() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("Cannot retrieve annotated elements in this way");
initializer.get(Size.class); initializer.get(Size.class);
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForSecondRegistration() { public void shouldThrowForSecondRegistration() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("There is already an object present");
initializer.register(ProvidedClass.class, new ProvidedClass("")); initializer.register(ProvidedClass.class, new ProvidedClass(""));
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForSecondAnnotationRegistration() { public void shouldThrowForSecondAnnotationRegistration() {
// given // given
initializer.provide(Size.class, 12); initializer.provide(Size.class, 12);
// when / then // when / then
expectRuntimeExceptionWith("already registered");
initializer.provide(Size.class, -8); initializer.provide(Size.class, -8);
} }
@Test(expected = NullPointerException.class) @Test
public void shouldThrowForNullValueAssociatedToAnnotation() { public void shouldThrowForNullValueAssociatedToAnnotation() {
// given / when / then // given / when / then
expectedException.expect(NullPointerException.class);
initializer.provide(Duration.class, null); initializer.provide(Duration.class, null);
} }
@Test(expected = NullPointerException.class) @Test
public void shouldThrowForRegisterWithNull() { public void shouldThrowForRegisterWithNull() {
// given / when / then // given / when / then
expectedException.expect(NullPointerException.class);
initializer.register(String.class, null); initializer.register(String.class, null);
} }
@ -170,39 +188,45 @@ public class AuthMeServiceInitializerTest {
assertThat(testClass.getBetaManager(), not(nullValue())); assertThat(testClass.getBetaManager(), not(nullValue()));
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForInvalidPostConstructMethod() { public void shouldThrowForInvalidPostConstructMethod() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("@PostConstruct method may not be static or have any parameters");
initializer.get(InvalidPostConstruct.WithParams.class); initializer.get(InvalidPostConstruct.WithParams.class);
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForStaticPostConstructMethod() { public void shouldThrowForStaticPostConstructMethod() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("@PostConstruct method may not be static or have any parameters");
initializer.get(InvalidPostConstruct.Static.class); initializer.get(InvalidPostConstruct.Static.class);
} }
@Test(expected = RuntimeException.class) @Test
public void shouldForwardExceptionFromPostConstruct() { public void shouldForwardExceptionFromPostConstruct() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("Error executing @PostConstruct method");
initializer.get(InvalidPostConstruct.ThrowsException.class); initializer.get(InvalidPostConstruct.ThrowsException.class);
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForMultiplePostConstructMethods() { public void shouldThrowForMultiplePostConstructMethods() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("Multiple methods with @PostConstruct");
initializer.get(InvalidPostConstruct.MultiplePostConstructs.class); initializer.get(InvalidPostConstruct.MultiplePostConstructs.class);
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForPostConstructNotReturningVoid() { public void shouldThrowForPostConstructNotReturningVoid() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("@PostConstruct method must have return type void");
initializer.get(InvalidPostConstruct.NotVoidReturnType.class); initializer.get(InvalidPostConstruct.NotVoidReturnType.class);
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForAbstractNonRegisteredDependency() { public void shouldThrowForAbstractNonRegisteredDependency() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("cannot be instantiated");
initializer.get(ClassWithAbstractDependency.class); initializer.get(ClassWithAbstractDependency.class);
} }
@ -220,12 +244,13 @@ public class AuthMeServiceInitializerTest {
assertThat(cwad.getAlphaService(), not(nullValue())); assertThat(cwad.getAlphaService(), not(nullValue()));
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForAlreadyRegisteredClass() { public void shouldThrowForAlreadyRegisteredClass() {
// given // given
initializer.register(BetaManager.class, new BetaManager()); initializer.register(BetaManager.class, new BetaManager());
// when / then // when / then
expectRuntimeExceptionWith("There is already an object present");
initializer.register(BetaManager.class, new BetaManager()); initializer.register(BetaManager.class, new BetaManager());
} }
@ -241,9 +266,10 @@ public class AuthMeServiceInitializerTest {
assertThat(singletonScoped, not(sameInstance(requestScoped))); assertThat(singletonScoped, not(sameInstance(requestScoped)));
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForStaticFieldInjection() { public void shouldThrowForStaticFieldInjection() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("is static but annotated with @Inject");
initializer.newInstance(InvalidStaticFieldInjection.class); initializer.newInstance(InvalidStaticFieldInjection.class);
} }
@ -283,10 +309,16 @@ public class AuthMeServiceInitializerTest {
assertThat(providedClass.getWasReloaded(), equalTo(true)); assertThat(providedClass.getWasReloaded(), equalTo(true));
} }
@Test(expected = RuntimeException.class) @Test
public void shouldThrowForNullSetting() { public void shouldThrowForNullSetting() {
// given / when / then // given / when / then
expectRuntimeExceptionWith("Settings instance is null");
initializer.performReloadOnServices(); initializer.performReloadOnServices();
} }
private void expectRuntimeExceptionWith(String message) {
expectedException.expect(RuntimeException.class);
expectedException.expectMessage(containsString(message));
}
} }

View File

@ -115,11 +115,9 @@ public class FieldInjectionTest {
} }
private static class ThrowingConstructor { private static class ThrowingConstructor {
@SuppressWarnings("unused")
@Inject @Inject
private ProvidedClass providedClass; private ProvidedClass providedClass;
@SuppressWarnings("unused")
public ThrowingConstructor() { public ThrowingConstructor() {
throw new UnsupportedOperationException("Exception in constructor"); throw new UnsupportedOperationException("Exception in constructor");
} }

View File

@ -65,4 +65,14 @@ public class InstantiationFallbackTest {
assertThat(instantiation, nullValue()); assertThat(instantiation, nullValue());
} }
@Test
public void shouldReturnNullForClassWithPostConstruct() {
// given / when
Injection<InstantiationFallbackClasses.ClassWithPostConstruct> instantiation =
InstantiationFallback.provide(InstantiationFallbackClasses.ClassWithPostConstruct.class).get();
// then
assertThat(instantiation, nullValue());
}
} }

View File

@ -1,9 +1,10 @@
package fr.xephi.authme.initialization.samples; package fr.xephi.authme.initialization.samples;
import javax.annotation.PostConstruct;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
* Sample class - triggers instantiation fallback. * Sample class - tests various situations for the instantiation fallback.
*/ */
public abstract class InstantiationFallbackClasses { public abstract class InstantiationFallbackClasses {
@ -42,4 +43,12 @@ public abstract class InstantiationFallbackClasses {
} }
} }
// Class with @PostConstruct method should never be instantiated by instantiation fallback
public static final class ClassWithPostConstruct {
@PostConstruct
public void postConstructMethod() {
// --
}
}
} }

View File

@ -34,6 +34,9 @@ public abstract class InvalidPostConstruct {
} }
public static final class ThrowsException { public static final class ThrowsException {
@Inject
private ProvidedClass providedClass;
@PostConstruct @PostConstruct
public void throwingPostConstruct() { public void throwingPostConstruct() {
throw new IllegalStateException("Exception in post construct"); throw new IllegalStateException("Exception in post construct");