mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99a366baf6 | |||
| 498ccda8fc | |||
| fd68ea6fcb | |||
| 28caa39020 | |||
| 8ecc553696 | |||
| cd44efaf68 | |||
| 59d2895c82 | |||
| a876bb41af | |||
| 3b6becac01 | |||
| 59ffbd7a59 | |||
| c0a9da65f4 | |||
| 30bee4db0f | |||
| 9a89654a64 |
@@ -11,9 +11,9 @@ dependencies {
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.12"))
|
||||
api(platform("io.netty:netty-bom:4.1.121.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.18"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.19"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.27"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.21"))
|
||||
@@ -121,7 +121,7 @@ dependencies {
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
api("org.freemarker:freemarker:2.3.33")
|
||||
api("org.freemarker:freemarker:2.3.34")
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
@@ -140,7 +140,7 @@ dependencies {
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:3.141.59")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.slf4j:slf4j-api:2.0.16")
|
||||
api("org.slf4j:slf4j-api:2.0.17")
|
||||
api("org.testng:testng:7.9.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.1.20-SNAPSHOT
|
||||
version=6.1.22-SNAPSHOT
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+9
-2
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -139,14 +140,20 @@ class ConfigurationClassEnhancer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given config class relies on package visibility,
|
||||
* either for the class itself or for any of its {@code @Bean} methods.
|
||||
* Checks whether the given config class relies on package visibility, either for
|
||||
* the class and any of its constructors or for any of its {@code @Bean} methods.
|
||||
*/
|
||||
private boolean reliesOnPackageVisibility(Class<?> configSuperClass) {
|
||||
int mod = configSuperClass.getModifiers();
|
||||
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
|
||||
return true;
|
||||
}
|
||||
for (Constructor<?> ctor : configSuperClass.getDeclaredConstructors()) {
|
||||
mod = ctor.getModifiers();
|
||||
if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (Method method : ReflectionUtils.getDeclaredMethods(configSuperClass)) {
|
||||
if (BeanAnnotationHelper.isBeanAnnotated(method)) {
|
||||
mod = method.getModifiers();
|
||||
|
||||
+38
@@ -104,6 +104,31 @@ class ConfigurationClassEnhancerTests {
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNonPublicConstructor() {
|
||||
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
|
||||
|
||||
ClassLoader classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
|
||||
Class<?> enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new OverridingClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new CustomSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
|
||||
classLoader = new BasicSmartClassLoader(getClass().getClassLoader());
|
||||
enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicConstructor.class, classLoader);
|
||||
assertThat(MyConfigWithNonPublicConstructor.class).isAssignableFrom(enhancedClass);
|
||||
assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNonPublicMethod() {
|
||||
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
|
||||
@@ -160,6 +185,19 @@ class ConfigurationClassEnhancerTests {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
public static class MyConfigWithNonPublicConstructor {
|
||||
|
||||
MyConfigWithNonPublicConstructor() {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public String myBean() {
|
||||
return "bean";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
public static class MyConfigWithNonPublicMethod {
|
||||
|
||||
|
||||
+29
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -29,6 +29,7 @@ import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
@@ -72,6 +73,33 @@ class PropertySourcesPlaceholderConfigurerTests {
|
||||
assertThat(ppc.getAppliedPropertySources()).isNotNull();
|
||||
}
|
||||
|
||||
@Test // gh-34936
|
||||
void replacementFromEnvironmentPropertiesWithConversion() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.registerBeanDefinition("testBean",
|
||||
genericBeanDefinition(TestBean.class)
|
||||
.addPropertyValue("name", "${my.name}")
|
||||
.getBeanDefinition());
|
||||
|
||||
record Point(int x, int y) {
|
||||
}
|
||||
|
||||
Converter<Point, String> pointToStringConverter =
|
||||
point -> "(%d,%d)".formatted(point.x, point.y);
|
||||
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
conversionService.addConverter(Point.class, String.class, pointToStringConverter);
|
||||
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setConversionService(conversionService);
|
||||
env.setProperty("my.name", new Point(4,5));
|
||||
|
||||
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
|
||||
ppc.setEnvironment(env);
|
||||
ppc.postProcessBeanFactory(bf);
|
||||
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("(4,5)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void localPropertiesViaResource() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
|
||||
+11
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -27,7 +27,7 @@ import org.springframework.core.testfixture.env.MockPropertySource;
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
* @since 3.2
|
||||
* @see org.springframework.core.testfixture.env.MockPropertySource
|
||||
* @see MockPropertySource
|
||||
*/
|
||||
public class MockEnvironment extends AbstractEnvironment {
|
||||
|
||||
@@ -44,19 +44,21 @@ public class MockEnvironment extends AbstractEnvironment {
|
||||
|
||||
/**
|
||||
* Set a property on the underlying {@link MockPropertySource} for this environment.
|
||||
* @see MockPropertySource#setProperty(String, Object)
|
||||
*/
|
||||
public void setProperty(String key, String value) {
|
||||
this.propertySource.setProperty(key, value);
|
||||
public void setProperty(String name, Object value) {
|
||||
this.propertySource.setProperty(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient synonym for {@link #setProperty} that returns the current instance.
|
||||
* Useful for method chaining and fluent-style use.
|
||||
* Convenient synonym for {@link #setProperty(String, Object)} that returns
|
||||
* the current instance.
|
||||
* <p>Useful for method chaining and fluent-style use.
|
||||
* @return this {@link MockEnvironment} instance
|
||||
* @see MockPropertySource#withProperty
|
||||
* @see MockPropertySource#withProperty(String, Object)
|
||||
*/
|
||||
public MockEnvironment withProperty(String key, String value) {
|
||||
setProperty(key, value);
|
||||
public MockEnvironment withProperty(String name, Object value) {
|
||||
setProperty(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -26,7 +26,7 @@ import org.springframework.core.env.PropertySource;
|
||||
* a user-provided {@link Properties} object, or if omitted during construction,
|
||||
* the implementation will initialize its own.
|
||||
*
|
||||
* The {@link #setProperty} and {@link #withProperty} methods are exposed for
|
||||
* <p>The {@link #setProperty} and {@link #withProperty} methods are exposed for
|
||||
* convenience, for example:
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
@@ -95,7 +95,7 @@ public class MockPropertySource extends PropertiesPropertySource {
|
||||
|
||||
/**
|
||||
* Convenient synonym for {@link #setProperty} that returns the current instance.
|
||||
* Useful for method chaining and fluent-style use.
|
||||
* <p>Useful for method chaining and fluent-style use.
|
||||
* @return this {@link MockPropertySource} instance
|
||||
*/
|
||||
public MockPropertySource withProperty(String name, Object value) {
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -26,7 +26,7 @@ import org.springframework.core.env.PropertySource;
|
||||
* a user-provided {@link Properties} object, or if omitted during construction,
|
||||
* the implementation will initialize its own.
|
||||
*
|
||||
* The {@link #setProperty} and {@link #withProperty} methods are exposed for
|
||||
* <p>The {@link #setProperty} and {@link #withProperty} methods are exposed for
|
||||
* convenience, for example:
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
@@ -36,7 +36,7 @@ import org.springframework.core.env.PropertySource;
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.1
|
||||
* @see org.springframework.mock.env.MockEnvironment
|
||||
* @see MockEnvironment
|
||||
*/
|
||||
public class MockPropertySource extends PropertiesPropertySource {
|
||||
|
||||
@@ -95,7 +95,7 @@ public class MockPropertySource extends PropertiesPropertySource {
|
||||
|
||||
/**
|
||||
* Convenient synonym for {@link #setProperty} that returns the current instance.
|
||||
* Useful for method chaining and fluent-style use.
|
||||
* <p>Useful for method chaining and fluent-style use.
|
||||
* @return this {@link MockPropertySource} instance
|
||||
*/
|
||||
public MockPropertySource withProperty(String name, Object value) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -67,6 +67,7 @@ public final class ContentDisposition {
|
||||
for (int i=33; i<= 126; i++) {
|
||||
PRINTABLE.set(i);
|
||||
}
|
||||
PRINTABLE.set(34, false); // "
|
||||
PRINTABLE.set(61, false); // =
|
||||
PRINTABLE.set(63, false); // ?
|
||||
PRINTABLE.set(95, false); // _
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -107,12 +107,12 @@ public interface ServerWebExchange {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the web session for the current request. Always guaranteed to
|
||||
* return an instance either matching to the session id requested by the
|
||||
* client, or with a new session id either because the client did not
|
||||
* specify one or because the underlying session had expired. Use of this
|
||||
* method does not automatically create a session. See {@link WebSession}
|
||||
* for more details.
|
||||
* Return the web session for the current request.
|
||||
* <p>Always guaranteed to return either an instance matching the session id
|
||||
* requested by the client, or a new session either because the client did not
|
||||
* specify a session id or because the underlying session expired.
|
||||
* <p>Use of this method does not automatically create a session. See
|
||||
* {@link WebSession} for more details.
|
||||
*/
|
||||
Mono<WebSession> getSession();
|
||||
|
||||
|
||||
+16
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -79,10 +79,10 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link Clock} to use to set lastAccessTime on every created
|
||||
* session and to calculate if it is expired.
|
||||
* <p>This may be useful to align to different timezone or to set the clock
|
||||
* back in a test, e.g. {@code Clock.offset(clock, Duration.ofMinutes(-31))}
|
||||
* Configure the {@link Clock} to use to set the {@code lastAccessTime} on
|
||||
* every created session and to calculate if the session has expired.
|
||||
* <p>This may be useful to align to different time zones or to set the clock
|
||||
* back in a test, for example, {@code Clock.offset(clock, Duration.ofMinutes(-31))}
|
||||
* in order to simulate session expiration.
|
||||
* <p>By default this is {@code Clock.system(ZoneId.of("GMT"))}.
|
||||
* @param clock the clock to use
|
||||
@@ -94,16 +94,17 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured clock for session lastAccessTime calculations.
|
||||
* Return the configured clock for session {@code lastAccessTime} calculations.
|
||||
*/
|
||||
public Clock getClock() {
|
||||
return this.clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map of sessions with an {@link Collections#unmodifiableMap
|
||||
* unmodifiable} wrapper. This could be used for management purposes, to
|
||||
* list active sessions, invalidate expired ones, etc.
|
||||
* Return an {@linkplain Collections#unmodifiableMap unmodifiable} copy of the
|
||||
* map of sessions.
|
||||
* <p>This could be used for management purposes, to list active sessions,
|
||||
* to invalidate expired sessions, etc.
|
||||
* @since 5.0.8
|
||||
*/
|
||||
public Map<String, WebSession> getSessions() {
|
||||
@@ -157,10 +158,11 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for expired sessions and remove them. Typically such checks are
|
||||
* kicked off lazily during calls to {@link #createWebSession() create} or
|
||||
* {@link #retrieveSession retrieve}, no less than 60 seconds apart.
|
||||
* This method can be called to force a check at a specific time.
|
||||
* Check for expired sessions and remove them.
|
||||
* <p>Typically such checks are kicked off lazily during calls to
|
||||
* {@link #createWebSession()} or {@link #retrieveSession}, no less than 60
|
||||
* seconds apart.
|
||||
* <p>This method can be called to force a check at a specific time.
|
||||
* @since 5.0.8
|
||||
*/
|
||||
public void removeExpiredSessions() {
|
||||
@@ -278,7 +280,7 @@ public class InMemoryWebSessionStore implements WebSessionStore {
|
||||
private void checkMaxSessionsLimit() {
|
||||
if (sessions.size() >= maxSessions) {
|
||||
expiredSessionChecker.removeExpiredSessions(clock.instant());
|
||||
if (sessions.size() >= maxSessions) {
|
||||
if (sessions.size() >= maxSessions && !sessions.containsKey(this.id.get())) {
|
||||
throw new IllegalStateException("Max sessions limit reached: " + sessions.size());
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -32,10 +32,10 @@ import org.springframework.web.server.WebSession;
|
||||
public interface WebSessionManager {
|
||||
|
||||
/**
|
||||
* Return the {@link WebSession} for the given exchange. Always guaranteed
|
||||
* to return an instance either matching to the session id requested by the
|
||||
* client, or a new session either because the client did not specify one
|
||||
* or because the underlying session expired.
|
||||
* Return the {@link WebSession} for the given exchange.
|
||||
* <p>Always guaranteed to return either an instance matching the session id
|
||||
* requested by the client, or a new session either because the client did not
|
||||
* specify a session id or because the underlying session expired.
|
||||
* @param exchange the current exchange
|
||||
* @return promise for the WebSession
|
||||
*/
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -43,7 +43,7 @@ public interface WebSessionStore {
|
||||
* Return the WebSession for the given id.
|
||||
* <p><strong>Note:</strong> This method should perform an expiration check,
|
||||
* and if it has expired remove the session and return empty. This method
|
||||
* should also update the lastAccessTime of retrieved sessions.
|
||||
* should also update the {@code lastAccessTime} of retrieved sessions.
|
||||
* @param sessionId the session to load
|
||||
* @return the session, or an empty {@code Mono}
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -305,6 +305,13 @@ class ContentDispositionTests {
|
||||
tester.accept("foo.txt\\\\\\", "foo.txt\\\\\\\\\\\\");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWithUtf8FilenameWithQuotes() {
|
||||
String filename = "\"中文.txt";
|
||||
assertThat(ContentDisposition.formData().filename(filename, StandardCharsets.UTF_8).build().toString())
|
||||
.isEqualTo("form-data; filename=\"=?UTF-8?Q?=22=E4=B8=AD=E6=96=87.txt?=\"; filename*=UTF-8''%22%E4%B8%AD%E6%96%87.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWithEncodedFilenameUsingInvalidCharset() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
|
||||
+63
-21
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -19,11 +19,11 @@ package org.springframework.web.server.session;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.web.server.WebSession;
|
||||
@@ -35,10 +35,11 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
* Tests for {@link InMemoryWebSessionStore}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class InMemoryWebSessionStoreTests {
|
||||
|
||||
private InMemoryWebSessionStore store = new InMemoryWebSessionStore();
|
||||
private final InMemoryWebSessionStore store = new InMemoryWebSessionStore();
|
||||
|
||||
|
||||
@Test
|
||||
@@ -53,13 +54,14 @@ class InMemoryWebSessionStoreTests {
|
||||
void startsSessionImplicitly() {
|
||||
WebSession session = this.store.createWebSession().block();
|
||||
assertThat(session).isNotNull();
|
||||
session.start();
|
||||
// We intentionally do not invoke start().
|
||||
// session.start();
|
||||
session.getAttributes().put("foo", "bar");
|
||||
assertThat(session.isStarted()).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-24027, gh-26958
|
||||
public void createSessionDoesNotBlock() {
|
||||
void createSessionDoesNotBlock() {
|
||||
this.store.createWebSession()
|
||||
.doOnNext(session -> assertThat(Schedulers.isInNonBlockingThread()).isTrue())
|
||||
.block();
|
||||
@@ -103,7 +105,7 @@ class InMemoryWebSessionStoreTests {
|
||||
}
|
||||
|
||||
@Test // SPR-17051
|
||||
public void sessionInvalidatedBeforeSave() {
|
||||
void sessionInvalidatedBeforeSave() {
|
||||
// Request 1 creates session
|
||||
WebSession session1 = this.store.createWebSession().block();
|
||||
assertThat(session1).isNotNull();
|
||||
@@ -132,33 +134,69 @@ class InMemoryWebSessionStoreTests {
|
||||
|
||||
@Test
|
||||
void expirationCheckPeriod() {
|
||||
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(this.store);
|
||||
Map<?,?> sessions = (Map<?, ?>) accessor.getPropertyValue("sessions");
|
||||
assertThat(sessions).isNotNull();
|
||||
|
||||
// Create 100 sessions
|
||||
IntStream.range(0, 100).forEach(i -> insertSession());
|
||||
assertThat(sessions).hasSize(100);
|
||||
IntStream.rangeClosed(1, 100).forEach(i -> insertSession());
|
||||
assertNumSessions(100);
|
||||
|
||||
// Force a new clock (31 min later), don't use setter which would clean expired sessions
|
||||
// Force a new clock (31 min later). Don't use setter which would clean expired sessions.
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(this.store);
|
||||
accessor.setPropertyValue("clock", Clock.offset(this.store.getClock(), Duration.ofMinutes(31)));
|
||||
assertThat(sessions).hasSize(100);
|
||||
assertNumSessions(100);
|
||||
|
||||
// Create 1 more which forces a time-based check (clock moved forward)
|
||||
// Create 1 more which forces a time-based check (clock moved forward).
|
||||
insertSession();
|
||||
assertThat(sessions).hasSize(1);
|
||||
assertNumSessions(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxSessions() {
|
||||
this.store.setMaxSessions(10);
|
||||
|
||||
IntStream.range(0, 10000).forEach(i -> insertSession());
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
this::insertSession)
|
||||
.withMessage("Max sessions limit reached: 10000");
|
||||
IntStream.rangeClosed(1, 10).forEach(i -> insertSession());
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(this::insertSession)
|
||||
.withMessage("Max sessions limit reached: 10");
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSession() {
|
||||
WebSession session = insertSession();
|
||||
|
||||
StepVerifier.create(session.save())
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test // gh-35013
|
||||
void updateSessionAfterMaxSessionLimitIsExceeded() {
|
||||
this.store.setMaxSessions(10);
|
||||
|
||||
WebSession session = insertSession();
|
||||
assertNumSessions(1);
|
||||
|
||||
IntStream.rangeClosed(1, 9).forEach(i -> insertSession());
|
||||
assertNumSessions(10);
|
||||
|
||||
// Updating an existing session should succeed.
|
||||
StepVerifier.create(session.save())
|
||||
.expectComplete()
|
||||
.verify();
|
||||
assertNumSessions(10);
|
||||
|
||||
// Saving an additional new session should fail.
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(this::insertSession)
|
||||
.withMessage("Max sessions limit reached: 10");
|
||||
assertNumSessions(10);
|
||||
|
||||
// Updating an existing session again should still succeed.
|
||||
StepVerifier.create(session.save())
|
||||
.expectComplete()
|
||||
.verify();
|
||||
assertNumSessions(10);
|
||||
}
|
||||
|
||||
|
||||
private WebSession insertSession() {
|
||||
WebSession session = this.store.createWebSession().block();
|
||||
assertThat(session).isNotNull();
|
||||
@@ -167,4 +205,8 @@ class InMemoryWebSessionStoreTests {
|
||||
return session;
|
||||
}
|
||||
|
||||
private void assertNumSessions(int numSessions) {
|
||||
assertThat(store.getSessions()).hasSize(numSessions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user