From fd705c878892195e32f16b3f307664c22e1c63d3 Mon Sep 17 00:00:00 2001 From: dmytrodanilenkov Date: Fri, 4 Apr 2025 18:54:59 +0100 Subject: [PATCH 1/3] Fix for issue #45000 - ServletRegistrationBean has those properties, but @ServletRegistration hasn't: initParameters, servletRegistrationBeans, multipartConfig Signed-off-by: dmytrodanilenkov --- .../ServletContextInitializerBeans.java | 43 +++++++ .../boot/web/servlet/ServletRegistration.java | 33 +++++ .../ServletContextInitializerBeansTests.java | 113 ++++++++++++++++++ 3 files changed, 189 insertions(+) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletContextInitializerBeans.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletContextInitializerBeans.java index b0010729ada6..04de5fce513c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletContextInitializerBeans.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletContextInitializerBeans.java @@ -321,10 +321,53 @@ private void configureFromAnnotation(ServletRegistrationBean bean, Serv if (registration.urlMappings().length > 0) { bean.setUrlMappings(Arrays.asList(registration.urlMappings())); } + + if (registration.initParameters().length > 0) { + bean.setInitParameters(parseInitParameters(registration.initParameters())); + } + + ServletRegistration.MultipartConfigValues multipart = registration.multipartConfig(); + boolean isMultipartConfigUsed = !(multipart.location().isEmpty() + && multipart.maxFileSize() == -1L + && multipart.maxRequestSize() == -1L + && multipart.fileSizeThreshold() == 0); + if (isMultipartConfigUsed) { + bean.setMultipartConfig(new MultipartConfigElement( + multipart.location(), + multipart.maxFileSize(), + multipart.maxRequestSize(), + multipart.fileSizeThreshold() + )); + } + + for (Class> beanClass : registration.servletRegistrationBeans()) { + ServletRegistrationBean extraBean = this.beanFactory.getBean(beanClass); + bean.getInitParameters().putAll(extraBean.getInitParameters()); + } + + } + + private Map parseInitParameters(String[] initParamsArray) { + Map initParams = new LinkedHashMap<>(); + for (String kv : initParamsArray) { + int index = kv.indexOf('='); + if (index != -1) { + String key = kv.substring(0, index).trim(); + String value = kv.substring(index + 1).trim(); + initParams.put(key, value); + } + else { + throw new IllegalArgumentException( + "initParameters must be in 'key=value' format, got: " + kv); + } + } + return initParams; } } + + /** * {@link RegistrationBeanAdapter} for {@link Filter} beans. */ diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java index e515747295fa..f4a4913ab8cd 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java @@ -23,6 +23,8 @@ import java.lang.annotation.Target; import jakarta.servlet.Servlet; +import jakarta.servlet.annotation.MultipartConfig; +import jakarta.servlet.annotation.WebInitParam; import org.springframework.core.Ordered; import org.springframework.core.annotation.AliasFor; @@ -87,4 +89,35 @@ */ int loadOnStartup() default -1; + /** + * Init parameters to set on the servlet, as {@code "key=value"} pairs. + */ + String[] initParameters() default {}; + + /** + * (Optional) Additional servlet-registration beans to apply. + * Usually left empty unless you need custom bean logic. + */ + Class>[] servletRegistrationBeans() default {}; + + /** + * Multi-part configuration. Mirrors {@link jakarta.servlet.annotation.MultipartConfig}. + * If you omit it (no fields changed), it will not set a multipart config. + */ + MultipartConfigValues multipartConfig() default @MultipartConfigValues; + + /** + * Nested annotation that parallels the fields of {@link jakarta.servlet.annotation.MultipartConfig}. + */ + @Target({}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @interface MultipartConfigValues { + + String location() default ""; + long maxFileSize() default -1L; + long maxRequestSize() default -1L; + int fileSizeThreshold() default 0; + + } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletContextInitializerBeansTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletContextInitializerBeansTests.java index 605ce7f0b879..139b4e72fc1f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletContextInitializerBeansTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/ServletContextInitializerBeansTests.java @@ -179,6 +179,73 @@ void shouldApplyOrderFromOrderAttribute() { .isEqualTo(ServletConfigurationWithAnnotationAndOrder.ORDER)); } + @Test + @SuppressWarnings("unchecked") + void shouldApplyExtendedServletRegistrationAnnotation() { + load(ServletConfigurationWithExtendedAttributes.class); + // Grab all initializers in the context, including beans that are adapted from @ServletRegistration + ServletContextInitializerBeans initializerBeans = new ServletContextInitializerBeans( + this.context.getBeanFactory(), TestServletContextInitializer.class); + + // We expect two registrations in this config: 'testServletWithInitParametersAndMultipart' + // and 'testServletWithExtraBean'. So let's filter them individually or pick the one we want to assert. + + // 1) Check the one with initParameters + multipartConfig + ServletRegistrationBean bean = findServletRegistrationBeanByName(initializerBeans, "extended"); + assertThat(bean).as("extended servlet registration bean").isNotNull(); + + // Verify that the standard attributes were applied + assertThat(bean.getServletName()).isEqualTo("extended"); + assertThat(bean.getUrlMappings()).containsExactly("/extended/*"); + + // Verify our new initParameters + assertThat(bean.getInitParameters()).containsEntry("hello", "world") + .containsEntry("flag", "true"); + + // Verify multi-part config + assertThat(bean.getMultipartConfig()).isNotNull(); + assertThat(bean.getMultipartConfig().getLocation()).isEqualTo("/tmp"); + assertThat(bean.getMultipartConfig().getMaxFileSize()).isEqualTo(1024); + assertThat(bean.getMultipartConfig().getMaxRequestSize()).isEqualTo(4096); + assertThat(bean.getMultipartConfig().getFileSizeThreshold()).isEqualTo(128); + } + + @Test + @SuppressWarnings("unchecked") + void shouldApplyServletRegistrationAnnotationWithExtraRegistrationBeans() { + load(ServletConfigurationWithExtendedAttributes.class); + ServletContextInitializerBeans initializerBeans = new ServletContextInitializerBeans( + this.context.getBeanFactory(), TestServletContextInitializer.class); + + // 2) Check the one referencing 'servletRegistrationBeans' + ServletRegistrationBean bean = findServletRegistrationBeanByName(initializerBeans, "extendedWithExtraBeans"); + assertThat(bean).as("extendedWithExtraBeans registration bean").isNotNull(); + + // Confirm standard attributes + assertThat(bean.getServletName()).isEqualTo("extendedWithExtraBeans"); + assertThat(bean.getUrlMappings()).containsExactly("/extra/*"); + + // Confirm that the extra init param from MyExtraServletRegistrationBean was merged + assertThat(bean.getInitParameters()).containsEntry("extra", "fromExtraBean"); + } + + /** + * Simple helper method to locate a specific ServletRegistrationBean by its name + * from the given ServletContextInitializerBeans collection. + */ + @SuppressWarnings("rawtypes") + private ServletRegistrationBean findServletRegistrationBeanByName( + ServletContextInitializerBeans initializerBeans, String servletName) { + + return initializerBeans.stream() + .filter(ServletRegistrationBean.class::isInstance) + .map(ServletRegistrationBean.class::cast) + .filter((registrationBean) -> servletName.equals(registrationBean.getServletName())) + .findFirst() + .orElse(null); + } + + private void load(Class... configuration) { this.context = new AnnotationConfigApplicationContext(configuration); } @@ -385,4 +452,50 @@ public void onStartup(ServletContext servletContext) { } + @Configuration(proxyBeanMethods = false) + static class ServletConfigurationWithExtendedAttributes { + + @Bean + @ServletRegistration( + name = "extended", + urlMappings = "/extended/*", + initParameters = { "hello=world", "flag=true" }, + multipartConfig = @ServletRegistration.MultipartConfigValues( + location = "/tmp", + maxFileSize = 1024, + maxRequestSize = 4096, + fileSizeThreshold = 128 + ) + ) + TestServlet testServletWithInitParametersAndMultipart() { + return new TestServlet(); + } + + @Bean + MyExtraServletRegistrationBean myExtraServletRegistrationBean() { + MyExtraServletRegistrationBean bean = new MyExtraServletRegistrationBean(); + bean.addInitParameter("extra", "fromExtraBean"); + return bean; + } + + @Bean + @ServletRegistration( + name = "extendedWithExtraBeans", + urlMappings = "/extra/*", + servletRegistrationBeans = { MyExtraServletRegistrationBean.class } + ) + TestServlet testServletWithExtraBean() { + return new TestServlet(); + } + + static class MyExtraServletRegistrationBean extends ServletRegistrationBean { + + MyExtraServletRegistrationBean() { + super(); + } + + } + } + + } From ab74295826d5421192443a466c3d8001fc9abd9d Mon Sep 17 00:00:00 2001 From: dmytrodanilenkov Date: Fri, 4 Apr 2025 19:14:54 +0100 Subject: [PATCH 2/3] Fix for issue #45000 - ServletRegistrationBean has those properties, but @ServletRegistration hasn't: initParameters, servletRegistrationBeans, multipartConfig Signed-off-by: Dmytro Danilenkov Signed-off-by: dmytrodanilenkov --- .../springframework/boot/web/servlet/ServletRegistration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java index f4a4913ab8cd..98b23a796549 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java @@ -101,7 +101,7 @@ Class>[] servletRegistrationBeans() default {}; /** - * Multi-part configuration. Mirrors {@link jakarta.servlet.annotation.MultipartConfig}. + * Multipart configuration. Mirrors {@link jakarta.servlet.annotation.MultipartConfig}. * If you omit it (no fields changed), it will not set a multipart config. */ MultipartConfigValues multipartConfig() default @MultipartConfigValues; From 3da51dc1aefe19f89bfd2596c441a674bd73b089 Mon Sep 17 00:00:00 2001 From: Dmytro Danilenkov Date: Fri, 4 Apr 2025 19:19:05 +0100 Subject: [PATCH 3/3] Fix for issue #45000 - ServletRegistrationBean has those properties, but @ServletRegistration hasn't: initParameters, servletRegistrationBeans, multipartConfig Signed-off-by: Dmytro Danilenkov --- .../boot/web/servlet/ServletRegistration.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java index 98b23a796549..4f9ff382fd90 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistration.java @@ -107,16 +107,31 @@ MultipartConfigValues multipartConfig() default @MultipartConfigValues; /** - * Nested annotation that parallels the fields of {@link jakarta.servlet.annotation.MultipartConfig}. + * Nested annotation that parallels the fields of + * {@link jakarta.servlet.annotation.MultipartConfig}. Used within + * {@link ServletRegistration#multipartConfig()}. + * @see jakarta.servlet.annotation.MultipartConfig */ @Target({}) @Retention(RetentionPolicy.RUNTIME) @Documented @interface MultipartConfigValues { + /** + * @see jakarta.servlet.annotation.MultipartConfig#location() + */ String location() default ""; + /** + * @see jakarta.servlet.annotation.MultipartConfig#maxFileSize() + */ long maxFileSize() default -1L; + /** + * @see jakarta.servlet.annotation.MultipartConfig#maxRequestSize() + */ long maxRequestSize() default -1L; + /** + * @see jakarta.servlet.annotation.MultipartConfig#fileSizeThreshold() + */ int fileSizeThreshold() default 0; }