Skip to content

Add missing attributes to ServletRegistration annotation #45006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,53 @@ private void configureFromAnnotation(ServletRegistrationBean<Servlet> 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<? extends ServletRegistrationBean<?>> beanClass : registration.servletRegistrationBeans()) {
ServletRegistrationBean<?> extraBean = this.beanFactory.getBean(beanClass);
bean.getInitParameters().putAll(extraBean.getInitParameters());
}

}

private Map<String, String> parseInitParameters(String[] initParamsArray) {
Map<String, String> 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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -87,4 +89,50 @@
*/
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<? extends ServletRegistrationBean<?>>[] servletRegistrationBeans() default {};

/**
* 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;

/**
* 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we directly use jakarta.servlet.annotation.MultipartConfig here?


/**
* @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;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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<HttpServlet> {

MyExtraServletRegistrationBean() {
super();
}

}
}


}