Skip to content

Commit 003268f

Browse files
committed
Add support for @webfilter, @weblistener, @WebServlet
This commit adds a new annotation, @ServletComponentScan, that can be used to enable scanning for @webfilter, @weblistener, and @WebServlet annotated classes. Registration beans will be automatically created for any classes that are found, with the configuration derived from the annotation.
1 parent 07ec9bb commit 003268f

18 files changed

+1441
-16
lines changed

spring-boot-docs/src/main/asciidoc/howto.adoc

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -348,15 +348,23 @@ Spring Boot. The definitive list comes from searching the source code for
348348

349349

350350

351-
[[howto-add-a-servlet-filter-or-servletcontextlistener]]
352-
=== Add a Servlet, Filter or ServletContextListener to an application
353-
`Servlet`, `Filter`, `ServletContextListener` and the other listeners supported by the
354-
Servlet spec can be added to your application as `@Bean` definitions. Be very careful that
355-
they don't cause eager initialization of too many other beans because they have to be
356-
installed in the container very early in the application lifecycle (e.g. it's not a good
357-
idea to have them depend on your `DataSource` or JPA configuration). You can work around
358-
restrictions like that by initializing them lazily when first used instead of on
359-
initialization.
351+
[[howto-add-a-servlet-filter-or-listener]]
352+
=== Add a Servlet, Filter or Listener to an application
353+
There are two ways to add `Servlet`, `Filter`, `ServletContextListener` and the other
354+
listeners supported by the Servlet spec to your application. You can either provide
355+
Spring beans for them, or enable scanning for Servlet components.
356+
357+
358+
359+
[[howto-add-a-servlet-filter-or-listener-as-spring-bean]]
360+
==== Add a Servlet, Filter or Listener using a Spring bean
361+
To add a `Servlet`, `Filter`, or Servlet `*Listener` provide a `@Bean` definition for it.
362+
This can be very useful when you want to inject configuration or dependencies. However,
363+
you must be very careful that they don't cause eager initialization of too many other
364+
beans because they have to be installed in the container very early in the application
365+
lifecycle (e.g. it's not a good idea to have them depend on your `DataSource` or JPA
366+
configuration). You can work around restrictions like that by initializing them lazily
367+
when first used instead of on initialization.
360368

361369
In the case of `Filters` and `Servlets` you can also add mappings and init parameters by
362370
adding a `FilterRegistrationBean` or `ServletRegistrationBean` instead of or as well as
@@ -365,7 +373,7 @@ the underlying component.
365373

366374

367375
[[howto-disable-registration-of-a-servlet-or-filter]]
368-
=== Disable registration of a Servlet or Filter
376+
===== Disable registration of a Servlet or Filter
369377
As <<howto-add-a-servlet-filter-or-servletcontextlistener,described above>> any `Servlet`
370378
or `Filter` beans will be registered with the servlet container automatically. To disable
371379
registration of a particular `Filter` or `Servlet` bean create a registration bean for it
@@ -382,6 +390,15 @@ and mark it as disabled. For example:
382390
----
383391

384392

393+
[[howto-add-a-servlet-filter-or-listener-using-scanning]]
394+
==== Add Servlets, Filters, and Listeners using classpath scanning
395+
`@WebServlet`, `@WebFilter`, and `@WebListener` annotated classes can be automatically
396+
registered with an embedded servlet container by annotating a `@Configuration` class
397+
with `@ServletComponentScan` and specifying the package(s) containing the components
398+
that you want to register. By default, `@ServletComponentScan` will scan from the package
399+
of the annotated class.
400+
401+
385402

386403
[[howto-change-the-http-port]]
387404
=== Change the HTTP port
@@ -1215,7 +1232,7 @@ using the "logging.config" property.
12151232
[[howto-configure-logback-for-loggin]]
12161233
=== Configure Logback for logging
12171234
If you put a `logback.xml` in the root of your classpath it will be picked up from
1218-
there
1235+
there
12191236
(or `logback-spring.xml` to take advantage of the templating features provided by Boot). Spring Boot provides a default base configuration that you can include if you just
12201237
want to set levels, e.g.
12211238

spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,12 +1585,18 @@ instance. By default the embedded server will listen for HTTP requests on port `
15851585

15861586

15871587

1588-
[[boot-features-embedded-container-servlets-and-filters]]
1589-
==== Servlets and Filters
1588+
[[boot-features-embedded-container-servlets-filters-listeners]]
1589+
==== Servlets, Filters, and listeners
15901590
When using an embedded servlet container you can register Servlets, Filters and all the
1591-
listeners from the Servlet spec (e.g. `HttpSessionListener`) directly as
1592-
Spring beans. This can be particularly convenient if you want to refer to a value from
1593-
your `application.properties` during configuration.
1591+
listeners from the Servlet spec (e.g. `HttpSessionListener`) either by using Spring beans
1592+
or by scanning for Servlet components.
1593+
1594+
1595+
[[boot-features-embedded-container-servlets-filters-listeners-beans]]
1596+
===== Registering Servlets, Filters, and listeners as Spring beans
1597+
Any `Servlet`, `Filter` or Servlet `*Listener` instance that is a Spring bean will be
1598+
registered with the embedded container. This can be particularly convenient if you want to
1599+
refer to a value from your `application.properties` during configuration.
15941600

15951601
By default, if the context contains only a single Servlet it will be mapped to `/`. In the
15961602
case of multiple Servlet beans the bean name will be used as a path prefix. Filters will
@@ -1603,6 +1609,16 @@ the `ServletContextInitializer` interface.
16031609

16041610

16051611

1612+
[[boot-features-embedded-container-servlets-filters-listeners-scanning]]
1613+
===== Scanning for Servlets, Filters, and listeners
1614+
When using an embedded container, automatic registration of `@WebServlet`, `@WebFilter`,
1615+
and `@WebListener` annotated classes can be enabled using `@ServletComponentScan`.
1616+
1617+
TIP: `@ServletComponentScan` will have no effect in a standalone container, where the
1618+
container's built-in discovery mechanisms will be used instead.
1619+
1620+
1621+
16061622
[[boot-features-embedded-container-application-context]]
16071623
==== The EmbeddedWebApplicationContext
16081624
Under the hood Spring Boot uses a new type of `ApplicationContext` for embedded servlet
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.servlet;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
import org.springframework.beans.factory.config.BeanDefinition;
24+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
25+
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
26+
import org.springframework.core.annotation.AnnotationAttributes;
27+
import org.springframework.core.type.filter.AnnotationTypeFilter;
28+
import org.springframework.core.type.filter.TypeFilter;
29+
30+
/**
31+
* Abstract base class for handlers of Servlet components discovered via classpath
32+
* scanning.
33+
*
34+
* @author Andy Wilkinson
35+
*/
36+
abstract class ServletComponentHandler {
37+
38+
private final Class<? extends Annotation> annotationType;
39+
40+
private final TypeFilter typeFilter;
41+
42+
protected ServletComponentHandler(Class<? extends Annotation> annotationType) {
43+
this.typeFilter = new AnnotationTypeFilter(annotationType);
44+
this.annotationType = annotationType;
45+
}
46+
47+
TypeFilter getTypeFilter() {
48+
return this.typeFilter;
49+
}
50+
51+
protected String[] extractUrlPatterns(String attribute, Map<String, Object> attributes) {
52+
String[] urlPatterns = (String[]) attributes.get("urlPatterns");
53+
if (urlPatterns.length > 0) {
54+
if (((String[]) attributes.get("value")).length > 0) {
55+
throw new IllegalStateException("The urlPatterns and value attributes "
56+
+ "are mututally exclusive");
57+
}
58+
return urlPatterns;
59+
}
60+
return (String[]) attributes.get("value");
61+
}
62+
63+
protected final Map<String, String> extractInitParameters(
64+
Map<String, Object> attributes) {
65+
Map<String, String> initParameters = new HashMap<String, String>();
66+
for (AnnotationAttributes initParam : (AnnotationAttributes[]) attributes
67+
.get("initParams")) {
68+
String name = (String) initParam.get("name");
69+
String value = (String) initParam.get("value");
70+
initParameters.put(name, value);
71+
}
72+
return initParameters;
73+
}
74+
75+
void handle(ScannedGenericBeanDefinition beanDefinition,
76+
BeanDefinitionRegistry registry) {
77+
Map<String, Object> attributes = beanDefinition.getMetadata()
78+
.getAnnotationAttributes(this.annotationType.getName());
79+
if (attributes != null) {
80+
doHandle(attributes, beanDefinition, registry);
81+
}
82+
}
83+
84+
protected abstract void doHandle(Map<String, Object> attributes,
85+
BeanDefinition beanDefinition, BeanDefinitionRegistry registry);
86+
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.servlet;
18+
19+
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.List;
22+
import java.util.Set;
23+
24+
import org.springframework.beans.BeansException;
25+
import org.springframework.beans.factory.config.BeanDefinition;
26+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
27+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
28+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
29+
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
30+
import org.springframework.context.ApplicationContext;
31+
import org.springframework.context.ApplicationContextAware;
32+
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
33+
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
34+
35+
/**
36+
* {@link BeanFactoryPostProcessor} that registers beans for Servlet components found via
37+
* package scanning.
38+
*
39+
* @see ServletComponentScan
40+
* @see ServletComponentScanRegistrar
41+
* @author Andy Wilkinson
42+
*/
43+
class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor,
44+
ApplicationContextAware {
45+
46+
private final List<ServletComponentHandler> handlers = Arrays.asList(
47+
new WebServletHandler(), new WebFilterHandler(), new WebListenerHandler());
48+
49+
private final Set<String> packagesToScan;
50+
51+
private ApplicationContext applicationContext;
52+
53+
public ServletComponentRegisteringPostProcessor(Set<String> packagesToScan) {
54+
this.packagesToScan = packagesToScan;
55+
}
56+
57+
@Override
58+
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
59+
throws BeansException {
60+
if (isRunningInEmbeddedContainer()) {
61+
ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
62+
for (String packageToScan : this.packagesToScan) {
63+
for (BeanDefinition candidate : componentProvider
64+
.findCandidateComponents(packageToScan)) {
65+
if (candidate instanceof ScannedGenericBeanDefinition) {
66+
for (ServletComponentHandler handler : this.handlers) {
67+
handler.handle(((ScannedGenericBeanDefinition) candidate),
68+
(BeanDefinitionRegistry) this.applicationContext);
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}
75+
76+
private boolean isRunningInEmbeddedContainer() {
77+
return this.applicationContext instanceof EmbeddedWebApplicationContext
78+
&& ((EmbeddedWebApplicationContext) this.applicationContext)
79+
.getServletContext() == null;
80+
}
81+
82+
private ClassPathScanningCandidateComponentProvider createComponentProvider() {
83+
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
84+
false);
85+
for (ServletComponentHandler handler : this.handlers) {
86+
componentProvider.addIncludeFilter(handler.getTypeFilter());
87+
}
88+
return componentProvider;
89+
}
90+
91+
Set<String> getPackagesToScan() {
92+
return Collections.unmodifiableSet(this.packagesToScan);
93+
}
94+
95+
@Override
96+
public void setApplicationContext(ApplicationContext applicationContext)
97+
throws BeansException {
98+
this.applicationContext = applicationContext;
99+
}
100+
101+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.servlet;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import javax.servlet.annotation.WebFilter;
26+
import javax.servlet.annotation.WebListener;
27+
import javax.servlet.annotation.WebServlet;
28+
29+
import org.springframework.context.annotation.Import;
30+
31+
/**
32+
* Enables scanning for Servlet components ({@link WebFilter filters}, {@link WebServlet
33+
* servlets}, and {@link WebListener listeners}). Scanning is only performed when using an
34+
* embedded Servlet container.
35+
* <p>
36+
* Typically, one of {@code value}, {@code basePackages}, or {@code basePackageClasses}
37+
* should be specified to control the packages to be scanned for components. In their
38+
* absence, scanning will be performed from the package of the class with the annotation.
39+
*
40+
* @author Andy Wilkinson
41+
* @since 1.3.0
42+
* @see WebServlet
43+
* @see WebFilter
44+
* @see WebListener
45+
*/
46+
@Target(ElementType.TYPE)
47+
@Retention(RetentionPolicy.RUNTIME)
48+
@Documented
49+
@Import(ServletComponentScanRegistrar.class)
50+
public @interface ServletComponentScan {
51+
52+
/**
53+
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
54+
* declarations e.g.: {@code @ServletComponentScan("org.my.pkg")} instead of
55+
* {@code @ServletComponentScan(basePackages="org.my.pkg")}.
56+
*
57+
* @return the base packages to scan
58+
*/
59+
String[] value() default {};
60+
61+
/**
62+
* Base packages to scan for annotated servlet components. {@link #value()} is an
63+
* alias for (and mutually exclusive with) this attribute.
64+
* <p>
65+
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
66+
* package names.
67+
*
68+
* @return the base packages to scan
69+
*/
70+
String[] basePackages() default {};
71+
72+
/**
73+
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
74+
* scan for annotated servlet components. The package of each class specified will be
75+
* scanned.
76+
*
77+
* @return classes from the base packages to scan
78+
*/
79+
Class<?>[] basePackageClasses() default {};
80+
}

0 commit comments

Comments
 (0)