mirror of
https://github.com/apache/struts.git
synced 2026-08-05 22:56:59 +00:00
Merge pull request #1191 from apache/feature/WW-5512-optional-inject-s7
WW-5512 Extends the container to support injecting optional parameters into constructor
This commit is contained in:
@@ -242,6 +242,41 @@ class ContainerImpl implements Container {
|
||||
return toArray(parameterInjectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameter injectors with nulls for optional dependencies.
|
||||
*
|
||||
* @param member to which the parameters belong
|
||||
* @param annotations on the parameters
|
||||
* @param parameterTypes parameter types
|
||||
* @return injections
|
||||
*/
|
||||
<M extends AccessibleObject & Member> List<ParameterInjector<?>> getParametersInjectorsWithNulls(
|
||||
M member,
|
||||
Annotation[][] annotations,
|
||||
Class<?>[] parameterTypes,
|
||||
String defaultName
|
||||
) throws MissingDependencyException {
|
||||
final List<ParameterInjector<?>> parameterInjectors = new ArrayList<>();
|
||||
|
||||
final Iterator<Annotation[]> annotationsIterator = Arrays.asList(annotations).iterator();
|
||||
for (Class<?> parameterType : parameterTypes) {
|
||||
Inject annotation = findInject(annotationsIterator.next());
|
||||
String name = annotation == null ? defaultName : annotation.value();
|
||||
Key<?> key = Key.newInstance(parameterType, name);
|
||||
try {
|
||||
parameterInjectors.add(createParameterInjector(key, member));
|
||||
} catch (MissingDependencyException e) {
|
||||
if (annotation != null && annotation.required()) {
|
||||
throw e;
|
||||
} else {
|
||||
parameterInjectors.add(createNullParameterInjector(key, member));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parameterInjectors;
|
||||
}
|
||||
|
||||
<T> ParameterInjector<T> createParameterInjector(Key<T> key, Member member) throws MissingDependencyException {
|
||||
final InternalFactory<? extends T> factory = getFactory(key);
|
||||
if (factory == null) {
|
||||
@@ -252,6 +287,23 @@ class ContainerImpl implements Container {
|
||||
return new ParameterInjector<>(externalContext, factory);
|
||||
}
|
||||
|
||||
<T> ParameterInjector<T> createNullParameterInjector(Key<T> key, Member member) {
|
||||
final InternalFactory<? extends T> factory = new InternalFactory<>() {
|
||||
@Override
|
||||
public T create(InternalContext context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends T> type() {
|
||||
return key.getType();
|
||||
}
|
||||
};
|
||||
|
||||
final ExternalContext<T> externalContext = ExternalContext.newInstance(member, key, this);
|
||||
return new ParameterInjector<>(externalContext, factory);
|
||||
}
|
||||
|
||||
private ParameterInjector<?>[] toArray(List<ParameterInjector<?>> parameterInjections) {
|
||||
return parameterInjections.toArray(new ParameterInjector[0]);
|
||||
}
|
||||
@@ -339,7 +391,7 @@ class ContainerImpl implements Container {
|
||||
|
||||
MissingDependencyException exception = null;
|
||||
Inject inject = null;
|
||||
ParameterInjector<?>[] parameters = null;
|
||||
List<ParameterInjector<?>> parameters = null;
|
||||
|
||||
try {
|
||||
inject = constructor.getAnnotation(Inject.class);
|
||||
@@ -347,7 +399,7 @@ class ContainerImpl implements Container {
|
||||
} catch (MissingDependencyException e) {
|
||||
exception = e;
|
||||
}
|
||||
parameterInjectors = parameters;
|
||||
parameterInjectors = parameters != null ? container.toArray(parameters) : null;
|
||||
|
||||
if (exception != null) {
|
||||
if (inject != null && inject.required()) {
|
||||
@@ -357,11 +409,11 @@ class ContainerImpl implements Container {
|
||||
injectors = container.injectors.get(implementation);
|
||||
}
|
||||
|
||||
ParameterInjector<?>[] constructParameterInjector(
|
||||
List<ParameterInjector<?>> constructParameterInjector(
|
||||
Inject inject, ContainerImpl container, Constructor<T> constructor) throws MissingDependencyException {
|
||||
return constructor.getParameterTypes().length == 0
|
||||
? null // default constructor.
|
||||
: container.getParametersInjectors(
|
||||
: container.getParametersInjectorsWithNulls(
|
||||
constructor,
|
||||
constructor.getParameterAnnotations(),
|
||||
constructor.getParameterTypes(),
|
||||
|
||||
@@ -28,7 +28,9 @@ import java.util.concurrent.Callable;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
@@ -49,6 +51,7 @@ public class ContainerImplTest {
|
||||
ContainerBuilder cb = new ContainerBuilder();
|
||||
cb.constant("methodCheck.name", "Lukasz");
|
||||
cb.constant("fieldCheck.name", "Lukasz");
|
||||
cb.constant("constructorCheck.name", "Lukasz");
|
||||
cb.factory(EarlyInitializable.class, EarlyInitializableBean.class, Scope.SINGLETON);
|
||||
cb.factory(Initializable.class, InitializableBean.class, Scope.SINGLETON);
|
||||
cb.factory(EarlyInitializable.class, "prototypeEarlyInitializable", EarlyInitializableBean.class, Scope.PROTOTYPE);
|
||||
@@ -65,15 +68,43 @@ public class ContainerImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fieldInjector() throws Exception {
|
||||
public void fieldInjector() {
|
||||
FieldCheck fieldCheck = new FieldCheck();
|
||||
c.inject(fieldCheck);
|
||||
assertEquals(fieldCheck.getName(), "Lukasz");
|
||||
assertEquals("Lukasz", fieldCheck.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodInjector() throws Exception {
|
||||
c.inject(new MethodCheck());
|
||||
public void methodInjector() {
|
||||
MethodCheck methodCheck = new MethodCheck();
|
||||
c.inject(methodCheck);
|
||||
assertEquals("Lukasz", methodCheck.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorInjector() {
|
||||
ConstructorCheck constructorCheck = c.inject(ConstructorCheck.class);
|
||||
assertEquals("Lukasz", constructorCheck.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optionalConstructorInjector() {
|
||||
OptionalConstructorCheck constructorCheck = c.inject(OptionalConstructorCheck.class);
|
||||
assertNull(constructorCheck.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requiredOptionalConstructorInjector() {
|
||||
RequiredOptionalConstructorCheck constructorCheck = c.inject(RequiredOptionalConstructorCheck.class);
|
||||
assertNotNull(constructorCheck.getExistingName());
|
||||
assertNull(constructorCheck.getNonExitingName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optionalRequiredConstructorInjector() {
|
||||
OptionalRequiredConstructorCheck constructorCheck = c.inject(OptionalRequiredConstructorCheck.class);
|
||||
assertNull(constructorCheck.getNonExitingName());
|
||||
assertNotNull(constructorCheck.getExistingName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,7 +123,7 @@ public class ContainerImplTest {
|
||||
* Inject values into method under SecurityManager
|
||||
*/
|
||||
@Test
|
||||
public void testMethodInjectorWithSecurityEnabled() throws Exception {
|
||||
public void testMethodInjectorWithSecurityEnabled() {
|
||||
assumeTrue(SystemUtils.isJavaVersionAtMost(JavaVersion.JAVA_20));
|
||||
System.setSecurityManager(new TestSecurityManager());
|
||||
assertThrows(DependencyException.class, () -> c.inject(new MethodCheck()));
|
||||
@@ -101,7 +132,7 @@ public class ContainerImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEarlyInitializable() throws Exception {
|
||||
public void testEarlyInitializable() {
|
||||
assertTrue("should being initialized already", EarlyInitializableBean.initializedEarly);
|
||||
|
||||
EarlyInitializableCheck earlyInitializableCheck = new EarlyInitializableCheck();
|
||||
@@ -148,22 +179,19 @@ public class ContainerImplTest {
|
||||
|
||||
final InitializableCheck initializableCheck3 = new InitializableCheck();
|
||||
final TestScopeStrategy testScopeStrategy = new TestScopeStrategy();
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ContainerBuilder cb2 = new ContainerBuilder();
|
||||
cb2.factory(EarlyInitializable.class, EarlyInitializableBean.class, Scope.SINGLETON);
|
||||
cb2.factory(Initializable.class, InitializableBean.class, Scope.SINGLETON);
|
||||
cb2.factory(EarlyInitializable.class, "prototypeEarlyInitializable", EarlyInitializableBean.class, Scope.PROTOTYPE);
|
||||
cb2.factory(Initializable.class, "prototypeInitializable", InitializableBean.class, Scope.PROTOTYPE);
|
||||
cb2.factory(Initializable.class, "requestInitializable", InitializableBean.class, Scope.REQUEST);
|
||||
cb2.factory(Initializable.class, "sessionInitializable", InitializableBean.class, Scope.SESSION);
|
||||
cb2.factory(Initializable.class, "threadInitializable", InitializableBean.class, Scope.THREAD);
|
||||
cb2.factory(Initializable.class, "wizardInitializable", InitializableBean.class, Scope.WIZARD);
|
||||
Container c2 = cb2.create(false);
|
||||
c2.setScopeStrategy(testScopeStrategy);
|
||||
c2.inject(initializableCheck3);
|
||||
}
|
||||
Thread thread = new Thread(() -> {
|
||||
ContainerBuilder cb2 = new ContainerBuilder();
|
||||
cb2.factory(EarlyInitializable.class, EarlyInitializableBean.class, Scope.SINGLETON);
|
||||
cb2.factory(Initializable.class, InitializableBean.class, Scope.SINGLETON);
|
||||
cb2.factory(EarlyInitializable.class, "prototypeEarlyInitializable", EarlyInitializableBean.class, Scope.PROTOTYPE);
|
||||
cb2.factory(Initializable.class, "prototypeInitializable", InitializableBean.class, Scope.PROTOTYPE);
|
||||
cb2.factory(Initializable.class, "requestInitializable", InitializableBean.class, Scope.REQUEST);
|
||||
cb2.factory(Initializable.class, "sessionInitializable", InitializableBean.class, Scope.SESSION);
|
||||
cb2.factory(Initializable.class, "threadInitializable", InitializableBean.class, Scope.THREAD);
|
||||
cb2.factory(Initializable.class, "wizardInitializable", InitializableBean.class, Scope.WIZARD);
|
||||
Container c2 = cb2.create(false);
|
||||
c2.setScopeStrategy(testScopeStrategy);
|
||||
c2.inject(initializableCheck3);
|
||||
});
|
||||
thread.run();
|
||||
thread.join();
|
||||
@@ -205,6 +233,76 @@ public class ContainerImplTest {
|
||||
|
||||
}
|
||||
|
||||
public static class ConstructorCheck {
|
||||
private String name;
|
||||
|
||||
@Inject("constructorCheck.name")
|
||||
public ConstructorCheck(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class OptionalConstructorCheck {
|
||||
private String name;
|
||||
|
||||
@Inject(value = "nonExistingConstant", required = false)
|
||||
public OptionalConstructorCheck(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequiredOptionalConstructorCheck {
|
||||
private final String existingName;
|
||||
private final String nonExitingName;
|
||||
|
||||
@Inject(required = false)
|
||||
public RequiredOptionalConstructorCheck(
|
||||
@Inject("constructorCheck.name") String existingName,
|
||||
@Inject(value = "nonExistingConstant", required = false) String nonExitingName
|
||||
) {
|
||||
this.existingName = existingName;
|
||||
this.nonExitingName = nonExitingName;
|
||||
}
|
||||
|
||||
public String getExistingName() {
|
||||
return existingName;
|
||||
}
|
||||
|
||||
public String getNonExitingName() {
|
||||
return nonExitingName;
|
||||
}
|
||||
}
|
||||
|
||||
public static class OptionalRequiredConstructorCheck {
|
||||
private final String existingName;
|
||||
private final String nonExitingName;
|
||||
|
||||
@Inject(required = false)
|
||||
public OptionalRequiredConstructorCheck(
|
||||
@Inject(value = "nonExistingConstant", required = false) String nonExitingName,
|
||||
@Inject("constructorCheck.name") String existingName
|
||||
) {
|
||||
this.existingName = existingName;
|
||||
this.nonExitingName = nonExitingName;
|
||||
}
|
||||
|
||||
public String getExistingName() {
|
||||
return existingName;
|
||||
}
|
||||
|
||||
public String getNonExitingName() {
|
||||
return nonExitingName;
|
||||
}
|
||||
}
|
||||
|
||||
class InitializableCheck {
|
||||
|
||||
private Initializable initializable;
|
||||
|
||||
Reference in New Issue
Block a user