Skip to content

Commit 3872c1a

Browse files
committed
Merge branch '6.2.x'
# Conflicts: # spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java # spring-jms/src/main/java/org/springframework/jms/config/AbstractJmsListenerContainerFactory.java # spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java
2 parents fb423d6 + 84430a8 commit 3872c1a

File tree

6 files changed

+113
-15
lines changed

6 files changed

+113
-15
lines changed

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import org.springframework.core.OrderComparator;
7878
import org.springframework.core.Ordered;
7979
import org.springframework.core.ResolvableType;
80+
import org.springframework.core.SpringProperties;
8081
import org.springframework.core.annotation.MergedAnnotation;
8182
import org.springframework.core.annotation.MergedAnnotations;
8283
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
@@ -128,6 +129,17 @@
128129
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
129130
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
130131

132+
/**
133+
* System property that instructs Spring to enforce string locking during bean creation,
134+
* rather than the mix of strict and lenient locking that 6.2 applies by default. Setting
135+
* this flag to "true" restores 6.1.x style locking in the entire pre-instantiation phase.
136+
* @since 6.2.6
137+
* @see #preInstantiateSingletons()
138+
*/
139+
public static final String STRICT_LOCKING_PROPERTY_NAME = "spring.locking.strict";
140+
141+
private static final boolean lenientLockingAllowed = !SpringProperties.getFlag(STRICT_LOCKING_PROPERTY_NAME);
142+
131143
private static @Nullable Class<?> jakartaInjectProviderClass;
132144

133145
static {
@@ -1015,7 +1027,8 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName
10151027

10161028
@Override
10171029
protected @Nullable Boolean isCurrentThreadAllowedToHoldSingletonLock() {
1018-
return (this.preInstantiationPhase ? this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null);
1030+
return (lenientLockingAllowed && this.preInstantiationPhase ?
1031+
this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null);
10191032
}
10201033

10211034
@Override

Diff for: spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java

+41
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@
2020
import org.junit.jupiter.api.Timeout;
2121

2222
import org.springframework.beans.factory.ObjectProvider;
23+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
24+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2325
import org.springframework.beans.testfixture.beans.TestBean;
2426
import org.springframework.context.ConfigurableApplicationContext;
27+
import org.springframework.core.SpringProperties;
2528
import org.springframework.core.testfixture.EnabledForTestGroups;
2629
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
2730

31+
import static org.assertj.core.api.Assertions.assertThat;
2832
import static org.springframework.context.annotation.Bean.Bootstrap.BACKGROUND;
2933
import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
3034

@@ -56,6 +60,21 @@ void bootstrapWithUnmanagedThreads() {
5660
ctx.close();
5761
}
5862

63+
@Test
64+
@Timeout(5)
65+
@EnabledForTestGroups(LONG_RUNNING)
66+
void bootstrapWithStrictLockingThread() {
67+
SpringProperties.setFlag(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME);
68+
try {
69+
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(StrictLockingBeanConfig.class);
70+
assertThat(ctx.getBean("testBean2", TestBean.class).getSpouse()).isSameAs(ctx.getBean("testBean1"));
71+
ctx.close();
72+
}
73+
finally {
74+
SpringProperties.setProperty(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME, null);
75+
}
76+
}
77+
5978
@Test
6079
@Timeout(5)
6180
@EnabledForTestGroups(LONG_RUNNING)
@@ -148,6 +167,28 @@ public TestBean testBean4() {
148167
}
149168

150169

170+
@Configuration(proxyBeanMethods = false)
171+
static class StrictLockingBeanConfig {
172+
173+
@Bean
174+
public TestBean testBean1(ObjectProvider<TestBean> testBean2) {
175+
new Thread(testBean2::getObject).start();
176+
try {
177+
Thread.sleep(1000);
178+
}
179+
catch (InterruptedException ex) {
180+
throw new RuntimeException(ex);
181+
}
182+
return new TestBean();
183+
}
184+
185+
@Bean
186+
public TestBean testBean2(ConfigurableListableBeanFactory beanFactory) {
187+
return new TestBean((TestBean) beanFactory.getSingleton("testBean1"));
188+
}
189+
}
190+
191+
151192
@Configuration(proxyBeanMethods = false)
152193
static class CircularReferenceBeanConfig {
153194

Diff for: spring-core/src/main/java/org/springframework/core/SpringProperties.java

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* @author Juergen Hoeller
4040
* @since 3.2.7
4141
* @see org.springframework.beans.StandardBeanInfoFactory#IGNORE_BEANINFO_PROPERTY_NAME
42+
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#STRICT_LOCKING_PROPERTY_NAME
4243
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
4344
* @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
4445
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME

Diff for: spring-core/src/main/java/org/springframework/objenesis/SpringObjenesis.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -69,7 +69,7 @@ public SpringObjenesis(InstantiatorStrategy strategy) {
6969
this.strategy = (strategy != null ? strategy : new StdInstantiatorStrategy());
7070

7171
// Evaluate the "spring.objenesis.ignore" property upfront...
72-
if (SpringProperties.getFlag(SpringObjenesis.IGNORE_OBJENESIS_PROPERTY_NAME)) {
72+
if (SpringProperties.getFlag(IGNORE_OBJENESIS_PROPERTY_NAME)) {
7373
this.worthTrying = Boolean.FALSE;
7474
}
7575
}

Diff for: spring-jms/src/main/java/org/springframework/jms/config/AbstractJmsListenerContainerFactory.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -56,6 +56,8 @@ public abstract class AbstractJmsListenerContainerFactory<C extends AbstractMess
5656

5757
private @Nullable Integer sessionAcknowledgeMode;
5858

59+
private @Nullable Boolean acknowledgeAfterListener;
60+
5961
private @Nullable Boolean pubSubDomain;
6062

6163
private @Nullable Boolean replyPubSubDomain;
@@ -125,6 +127,14 @@ public void setSessionAcknowledgeMode(Integer sessionAcknowledgeMode) {
125127
this.sessionAcknowledgeMode = sessionAcknowledgeMode;
126128
}
127129

130+
/**
131+
* @since 6.2.6
132+
* @see AbstractMessageListenerContainer#setAcknowledgeAfterListener(boolean)
133+
*/
134+
public void setAcknowledgeAfterListener(Boolean acknowledgeAfterListener) {
135+
this.acknowledgeAfterListener = acknowledgeAfterListener;
136+
}
137+
128138
/**
129139
* @see AbstractMessageListenerContainer#setPubSubDomain(boolean)
130140
*/
@@ -193,6 +203,7 @@ public void setObservationRegistry(ObservationRegistry observationRegistry) {
193203
this.observationRegistry = observationRegistry;
194204
}
195205

206+
196207
@Override
197208
public C createListenerContainer(JmsListenerEndpoint endpoint) {
198209
C instance = createContainerInstance();
@@ -218,6 +229,9 @@ public C createListenerContainer(JmsListenerEndpoint endpoint) {
218229
if (this.sessionAcknowledgeMode != null) {
219230
instance.setSessionAcknowledgeMode(this.sessionAcknowledgeMode);
220231
}
232+
if (this.acknowledgeAfterListener != null) {
233+
instance.setAcknowledgeAfterListener(this.acknowledgeAfterListener);
234+
}
221235
if (this.pubSubDomain != null) {
222236
instance.setPubSubDomain(this.pubSubDomain);
223237
}

Diff for: spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java

+40-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -152,7 +152,8 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
152152
implements MessageListenerContainer {
153153

154154
private static final boolean micrometerJakartaPresent = ClassUtils.isPresent(
155-
"io.micrometer.jakarta9.instrument.jms.JmsInstrumentation", AbstractMessageListenerContainer.class.getClassLoader());
155+
"io.micrometer.jakarta9.instrument.jms.JmsInstrumentation",
156+
AbstractMessageListenerContainer.class.getClassLoader());
156157

157158
private volatile @Nullable Object destination;
158159

@@ -166,12 +167,12 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
166167

167168
private @Nullable String subscriptionName;
168169

170+
private boolean pubSubNoLocal = false;
171+
169172
private @Nullable Boolean replyPubSubDomain;
170173

171174
private @Nullable QosSettings replyQosSettings;
172175

173-
private boolean pubSubNoLocal = false;
174-
175176
private @Nullable MessageConverter messageConverter;
176177

177178
private @Nullable ExceptionListener exceptionListener;
@@ -180,6 +181,8 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
180181

181182
private @Nullable ObservationRegistry observationRegistry;
182183

184+
private boolean acknowledgeAfterListener = true;
185+
183186
private boolean exposeListenerSession = true;
184187

185188
private boolean acceptMessagesWhileStopping = false;
@@ -484,12 +487,7 @@ public void setReplyPubSubDomain(boolean replyPubSubDomain) {
484487
*/
485488
@Override
486489
public boolean isReplyPubSubDomain() {
487-
if (this.replyPubSubDomain != null) {
488-
return this.replyPubSubDomain;
489-
}
490-
else {
491-
return isPubSubDomain();
492-
}
490+
return (this.replyPubSubDomain != null ? this.replyPubSubDomain : isPubSubDomain());
493491
}
494492

495493
/**
@@ -575,6 +573,37 @@ public void setObservationRegistry(@Nullable ObservationRegistry observationRegi
575573
return this.observationRegistry;
576574
}
577575

576+
/**
577+
* Specify whether the listener container should automatically acknowledge
578+
* each JMS Message after the message listener returned. This applies in
579+
* case of client acknowledge modes, including vendor-specific modes but
580+
* not in case of auto-acknowledge or a transacted JMS Session.
581+
* <p>As of 6.2, the default is {@code true}: The listener container will
582+
* acknowledge each JMS Message even in case of a vendor-specific mode,
583+
* assuming client-acknowledge style processing for custom vendor modes.
584+
* <p>If the provided listener prefers to manually acknowledge each message in
585+
* the listener itself, in combination with an "individual acknowledge" mode,
586+
* switch this flag to {code false} along with the vendor-specific mode.
587+
* @since 6.2.6
588+
* @see #setSessionAcknowledgeMode
589+
* @see #setMessageListener
590+
* @see Message#acknowledge()
591+
*/
592+
public void setAcknowledgeAfterListener(boolean acknowledgeAfterListener) {
593+
this.acknowledgeAfterListener = acknowledgeAfterListener;
594+
}
595+
596+
/**
597+
* Determine whether the listener container should automatically acknowledge
598+
* each JMS Message after the message listener returned.
599+
* @since 6.2.6
600+
* @see #setAcknowledgeAfterListener
601+
* @see #isClientAcknowledge(Session)
602+
*/
603+
public boolean isAcknowledgeAfterListener() {
604+
return this.acknowledgeAfterListener;
605+
}
606+
578607
/**
579608
* Set whether to expose the listener JMS Session to a registered
580609
* {@link SessionAwareMessageListener} as well as to
@@ -812,7 +841,7 @@ protected void commitIfNecessary(Session session, @Nullable Message message) thr
812841
JmsUtils.commitIfNecessary(session);
813842
}
814843
}
815-
else if (message != null && isClientAcknowledge(session)) {
844+
else if (message != null && isAcknowledgeAfterListener() && isClientAcknowledge(session)) {
816845
message.acknowledge();
817846
}
818847
}

0 commit comments

Comments
 (0)