Merge branch '6.0.x'

This commit is contained in:
Juergen Hoeller 2023-05-10 17:19:39 +02:00
commit 841124af75
6 changed files with 258 additions and 69 deletions

View File

@ -237,33 +237,28 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
private AutowiredArguments resolveArguments(RegisteredBean registeredBean, Executable executable) {
Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class, registeredBean.getBeanFactory());
String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass();
AbstractAutowireCapableBeanFactory beanFactory =
(AbstractAutowireCapableBeanFactory) registeredBean.getBeanFactory();
RootBeanDefinition mergedBeanDefinition = registeredBean.getMergedBeanDefinition();
int startIndex = (executable instanceof Constructor<?> constructor &&
ClassUtils.isInnerClass(constructor.getDeclaringClass())) ? 1 : 0;
int parameterCount = executable.getParameterCount();
Object[] resolved = new Object[parameterCount - startIndex];
Assert.isTrue(this.shortcuts == null || this.shortcuts.length == resolved.length,
() -> "'shortcuts' must contain " + resolved.length + " elements");
ConstructorArgumentValues argumentValues = resolveArgumentValues(registeredBean);
Set<String> autowiredBeans = new LinkedHashSet<>(resolved.length);
ConstructorArgumentValues argumentValues = resolveArgumentValues(beanFactory,
beanName, mergedBeanDefinition);
for (int i = startIndex; i < parameterCount; i++) {
MethodParameter parameter = getMethodParameter(executable, i);
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(parameter, true);
String shortcut = (this.shortcuts != null) ? this.shortcuts[i - startIndex] : null;
DependencyDescriptor descriptor = new DependencyDescriptor(parameter, true);
String shortcut = (this.shortcuts != null ? this.shortcuts[i - startIndex] : null);
if (shortcut != null) {
dependencyDescriptor = new ShortcutDependencyDescriptor(
dependencyDescriptor, shortcut, beanClass);
descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut, registeredBean.getBeanClass());
}
ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null);
resolved[i - startIndex] = resolveArgument(registeredBean,autowiredBeans,
dependencyDescriptor, argumentValue);
resolved[i - startIndex] = resolveArgument(registeredBean, descriptor, argumentValue, autowiredBeans);
}
registerDependentBeans(beanFactory, beanName, autowiredBeans);
registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeans);
return AutowiredArguments.of(resolved);
}
@ -277,15 +272,14 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
throw new IllegalStateException("Unsupported executable: " + executable.getClass().getName());
}
private ConstructorArgumentValues resolveArgumentValues(
AbstractAutowireCapableBeanFactory beanFactory, String beanName,
RootBeanDefinition mergedBeanDefinition) {
private ConstructorArgumentValues resolveArgumentValues(RegisteredBean registeredBean) {
ConstructorArgumentValues resolved = new ConstructorArgumentValues();
if (mergedBeanDefinition.hasConstructorArgumentValues()) {
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
if (beanDefinition.hasConstructorArgumentValues() &&
registeredBean.getBeanFactory() instanceof AbstractAutowireCapableBeanFactory beanFactory) {
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(
beanFactory, beanName, mergedBeanDefinition, beanFactory.getTypeConverter());
ConstructorArgumentValues values = mergedBeanDefinition.getConstructorArgumentValues();
beanFactory, registeredBean.getBeanName(), beanDefinition, beanFactory.getTypeConverter());
ConstructorArgumentValues values = beanDefinition.getConstructorArgumentValues();
values.getIndexedArgumentValues().forEach((index, valueHolder) -> {
ValueHolder resolvedValue = resolveArgumentValue(valueResolver, valueHolder);
resolved.addIndexedArgumentValue(index, resolvedValue);
@ -298,30 +292,27 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
if (valueHolder.isConverted()) {
return valueHolder;
}
Object resolvedValue = resolver.resolveValueIfNecessary("constructor argument",
valueHolder.getValue());
ValueHolder resolvedValueHolder = new ValueHolder(resolvedValue,
valueHolder.getType(), valueHolder.getName());
resolvedValueHolder.setSource(valueHolder);
return resolvedValueHolder;
Object value = resolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue());
ValueHolder resolvedHolder = new ValueHolder(value, valueHolder.getType(), valueHolder.getName());
resolvedHolder.setSource(valueHolder);
return resolvedHolder;
}
@Nullable
private Object resolveArgument(RegisteredBean registeredBean, Set<String> autowiredBeans,
DependencyDescriptor dependencyDescriptor, @Nullable ValueHolder argumentValue) {
private Object resolveArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor,
@Nullable ValueHolder argumentValue, Set<String> autowiredBeans) {
TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter();
Class<?> parameterType = dependencyDescriptor.getMethodParameter().getParameterType();
if (argumentValue != null) {
return (!argumentValue.isConverted()) ?
typeConverter.convertIfNecessary(argumentValue.getValue(), parameterType) :
argumentValue.getConvertedValue();
return (argumentValue.isConverted() ? argumentValue.getConvertedValue() :
typeConverter.convertIfNecessary(argumentValue.getValue(),
descriptor.getDependencyType(), descriptor.getMethodParameter()));
}
try {
return registeredBean.resolveAutowiredArgument(dependencyDescriptor, typeConverter, autowiredBeans);
return registeredBean.resolveAutowiredArgument(descriptor, typeConverter, autowiredBeans);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, registeredBean.getBeanName(), dependencyDescriptor, ex);
throw new UnsatisfiedDependencyException(null, registeredBean.getBeanName(), descriptor, ex);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -41,19 +41,6 @@ public class PayloadApplicationEvent<T> extends ApplicationEvent implements Reso
private final ResolvableType payloadType;
/**
* Create a new PayloadApplicationEvent.
* @param source the object on which the event initially occurred (never {@code null})
* @param payload the payload object (never {@code null})
* @param payloadType the type object of payload object (can be {@code null})
* @since 6.0
*/
public PayloadApplicationEvent(Object source, T payload, @Nullable ResolvableType payloadType) {
super(source);
Assert.notNull(payload, "Payload must not be null");
this.payload = payload;
this.payloadType = (payloadType != null) ? payloadType : ResolvableType.forInstance(payload);
}
/**
* Create a new PayloadApplicationEvent, using the instance to infer its type.
@ -64,6 +51,22 @@ public class PayloadApplicationEvent<T> extends ApplicationEvent implements Reso
this(source, payload, null);
}
/**
* Create a new PayloadApplicationEvent based on the provided payload type.
* @param source the object on which the event initially occurred (never {@code null})
* @param payload the payload object (never {@code null})
* @param payloadType the type object of payload object (can be {@code null}).
* Note that this is meant to indicate the payload type (e.g. {@code String}),
* not the full event type (such as {@code PayloadApplicationEvent<&lt;String&gt;}).
* @since 6.0
*/
public PayloadApplicationEvent(Object source, T payload, @Nullable ResolvableType payloadType) {
super(source);
Assert.notNull(payload, "Payload must not be null");
this.payload = payload;
this.payloadType = (payloadType != null ? payloadType : ResolvableType.forInstance(payload));
}
@Override
public ResolvableType getResolvableType() {

View File

@ -128,12 +128,12 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
multicastEvent(event, null);
}
@Override
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
@ -145,10 +145,6 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
}
}
private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
return ResolvableType.forInstance(event);
}
/**
* Invoke the given listener with the given event.
* @param listener the ApplicationListener to invoke

View File

@ -385,23 +385,47 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/**
* Publish the given event to all listeners.
* <p>This is the internal delegate that all other {@code publishEvent}
* methods refer to. It is not meant to be called directly but rather serves
* as a propagation mechanism between application contexts in a hierarchy,
* potentially overridden in subclasses for a custom propagation arrangement.
* @param event the event to publish (may be an {@link ApplicationEvent}
* or a payload object to be turned into a {@link PayloadApplicationEvent})
* @param eventType the resolved event type, if known
* @param typeHint the resolved event type, if known.
* The implementation of this method also tolerates a payload type hint for
* a payload object to be turned into a {@link PayloadApplicationEvent}.
* However, the recommended way is to construct an actual event object via
* {@link PayloadApplicationEvent#PayloadApplicationEvent(Object, Object, ResolvableType)}
* instead for such scenarios.
* @since 4.2
* @see ApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)
*/
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
protected void publishEvent(Object event, @Nullable ResolvableType typeHint) {
Assert.notNull(event, "Event must not be null");
ResolvableType eventType = null;
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent applEvent) {
applicationEvent = applEvent;
eventType = typeHint;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event, eventType);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
ResolvableType payloadType = null;
if (typeHint != null && ApplicationEvent.class.isAssignableFrom(typeHint.toClass())) {
eventType = typeHint;
}
else {
payloadType = typeHint;
}
applicationEvent = new PayloadApplicationEvent<>(this, event, payloadType);
}
// Determine event type only once (for multicast and parent publish)
if (eventType == null) {
eventType = ResolvableType.forInstance(applicationEvent);
if (typeHint == null) {
typeHint = eventType;
}
}
@ -416,7 +440,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext abstractApplicationContext) {
abstractApplicationContext.publishEvent(event, eventType);
abstractApplicationContext.publishEvent(event, typeHint);
}
else {
this.parent.publishEvent(event);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,6 +21,7 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.PayloadApplicationEvent;
@ -68,16 +69,97 @@ class PayloadApplicationEventTests {
});
}
@Test
@SuppressWarnings("resource")
void testEventClassWithPayloadType() {
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(NumberHolderListener.class);
PayloadApplicationEvent<NumberHolder<Integer>> event = new PayloadApplicationEvent<>(this,
new NumberHolder<>(42), ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
ac.publishEvent(event);
assertThat(ac.getBean(NumberHolderListener.class).events.contains(event.getPayload())).isTrue();
ac.close();
}
@Test
@SuppressWarnings("resource")
void testEventClassWithPayloadTypeOnParentContext() {
ConfigurableApplicationContext parent = new AnnotationConfigApplicationContext(NumberHolderListener.class);
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
ac.refresh();
PayloadApplicationEvent<NumberHolder<Integer>> event = new PayloadApplicationEvent<>(this,
new NumberHolder<>(42), ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
ac.publishEvent(event);
assertThat(parent.getBean(NumberHolderListener.class).events.contains(event.getPayload())).isTrue();
ac.close();
parent.close();
}
@Test
@SuppressWarnings("resource")
void testPayloadObjectWithPayloadType() {
final Object payload = new NumberHolder<>(42);
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(NumberHolderListener.class) {
@Override
protected void finishRefresh() throws BeansException {
super.finishRefresh();
// This is not recommended: use publishEvent(new PayloadApplicationEvent(...)) instead
publishEvent(payload, ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
}
};
assertThat(ac.getBean(NumberHolderListener.class).events.contains(payload)).isTrue();
ac.close();
}
@Test
@SuppressWarnings("resource")
void testPayloadObjectWithPayloadTypeOnParentContext() {
final Object payload = new NumberHolder<>(42);
ConfigurableApplicationContext parent = new AnnotationConfigApplicationContext(NumberHolderListener.class);
ConfigurableApplicationContext ac = new GenericApplicationContext(parent) {
@Override
protected void finishRefresh() throws BeansException {
super.finishRefresh();
// This is not recommended: use publishEvent(new PayloadApplicationEvent(...)) instead
publishEvent(payload, ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
}
};
ac.refresh();
assertThat(parent.getBean(NumberHolderListener.class).events.contains(payload)).isTrue();
ac.close();
parent.close();
}
@Test
@SuppressWarnings("resource")
void testEventClassWithInterface() {
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(AuditableListener.class);
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
ac.publishEvent(event);
assertThat(ac.getBean(AuditableListener.class).events.contains(event)).isTrue();
ac.close();
}
@Test
@SuppressWarnings("resource")
void testEventClassWithInterfaceOnParentContext() {
ConfigurableApplicationContext parent = new AnnotationConfigApplicationContext(AuditableListener.class);
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
ac.refresh();
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
ac.publishEvent(event);
assertThat(parent.getBean(AuditableListener.class).events.contains(event)).isTrue();
ac.close();
parent.close();
}
@Test
@SuppressWarnings("resource")
void testProgrammaticEventListener() {
@ -96,6 +178,27 @@ class PayloadApplicationEventTests {
ac.close();
}
@Test
@SuppressWarnings("resource")
void testProgrammaticEventListenerOnParentContext() {
List<Auditable> events = new ArrayList<>();
ApplicationListener<AuditablePayloadEvent<String>> listener = events::add;
ApplicationListener<AuditablePayloadEvent<Integer>> mismatch = (event -> event.getPayload());
ConfigurableApplicationContext parent = new GenericApplicationContext();
parent.addApplicationListener(listener);
parent.addApplicationListener(mismatch);
parent.refresh();
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
ac.refresh();
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
ac.publishEvent(event);
assertThat(events.contains(event)).isTrue();
ac.close();
parent.close();
}
@Test
@SuppressWarnings("resource")
void testProgrammaticPayloadListener() {
@ -108,12 +211,77 @@ class PayloadApplicationEventTests {
ac.addApplicationListener(mismatch);
ac.refresh();
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
ac.publishEvent(event);
assertThat(events.contains(event.getPayload())).isTrue();
String payload = "xyz";
ac.publishEvent(payload);
assertThat(events.contains(payload)).isTrue();
ac.close();
}
@Test
@SuppressWarnings("resource")
void testProgrammaticPayloadListenerOnParentContext() {
List<String> events = new ArrayList<>();
ApplicationListener<PayloadApplicationEvent<String>> listener = ApplicationListener.forPayload(events::add);
ApplicationListener<PayloadApplicationEvent<Integer>> mismatch = ApplicationListener.forPayload(Integer::intValue);
ConfigurableApplicationContext parent = new GenericApplicationContext();
parent.addApplicationListener(listener);
parent.addApplicationListener(mismatch);
parent.refresh();
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
ac.refresh();
String payload = "xyz";
ac.publishEvent(payload);
assertThat(events.contains(payload)).isTrue();
ac.close();
parent.close();
}
@Test
@SuppressWarnings("resource")
void testPlainPayloadListener() {
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(PlainPayloadListener.class);
String payload = "xyz";
ac.publishEvent(payload);
assertThat(ac.getBean(PlainPayloadListener.class).events.contains(payload)).isTrue();
ac.close();
}
@Test
@SuppressWarnings("resource")
void testPlainPayloadListenerOnParentContext() {
ConfigurableApplicationContext parent = new AnnotationConfigApplicationContext(PlainPayloadListener.class);
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
ac.refresh();
String payload = "xyz";
ac.publishEvent(payload);
assertThat(parent.getBean(PlainPayloadListener.class).events.contains(payload)).isTrue();
ac.close();
parent.close();
}
static class NumberHolder<T extends Number> {
public NumberHolder(T number) {
}
}
@Component
public static class NumberHolderListener {
public final List<NumberHolder<Integer>> events = new ArrayList<>();
@EventListener
public void onEvent(NumberHolder<Integer> event) {
events.add(event);
}
}
public interface Auditable {
}
@ -139,11 +307,16 @@ class PayloadApplicationEventTests {
}
}
static class NumberHolder<T extends Number> {
public NumberHolder(T number) {
@Component
public static class PlainPayloadListener {
public final List<String> events = new ArrayList<>();
@EventListener
public void onEvent(String event) {
events.add(event);
}
}
}

View File

@ -57,8 +57,9 @@ public abstract class AbstractApplicationContextTests extends AbstractListableBe
protected TestApplicationListener parentListener = new TestApplicationListener();
@BeforeEach
public void setUp() throws Exception {
public void setup() throws Exception {
this.applicationContext = createContext();
}
@ -79,6 +80,7 @@ public abstract class AbstractApplicationContextTests extends AbstractListableBe
*/
protected abstract ConfigurableApplicationContext createContext() throws Exception;
@Test
public void contextAwareSingletonWasCalledBack() throws Exception {
ACATester aca = (ACATester) applicationContext.getBean("aca");