001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.filter; 018 019import java.io.IOException; 020 021import javax.servlet.Filter; 022import javax.servlet.FilterChain; 023import javax.servlet.ServletException; 024import javax.servlet.ServletRequest; 025import javax.servlet.ServletResponse; 026 027import org.springframework.context.ConfigurableApplicationContext; 028import org.springframework.lang.Nullable; 029import org.springframework.util.Assert; 030import org.springframework.web.context.WebApplicationContext; 031import org.springframework.web.context.support.WebApplicationContextUtils; 032 033/** 034 * Proxy for a standard Servlet Filter, delegating to a Spring-managed bean that 035 * implements the Filter interface. Supports a "targetBeanName" filter init-param 036 * in {@code web.xml}, specifying the name of the target bean in the Spring 037 * application context. 038 * 039 * <p>{@code web.xml} will usually contain a {@code DelegatingFilterProxy} definition, 040 * with the specified {@code filter-name} corresponding to a bean name in 041 * Spring's root application context. All calls to the filter proxy will then 042 * be delegated to that bean in the Spring context, which is required to implement 043 * the standard Servlet Filter interface. 044 * 045 * <p>This approach is particularly useful for Filter implementation with complex 046 * setup needs, allowing to apply the full Spring bean definition machinery to 047 * Filter instances. Alternatively, consider standard Filter setup in combination 048 * with looking up service beans from the Spring root application context. 049 * 050 * <p><b>NOTE:</b> The lifecycle methods defined by the Servlet Filter interface 051 * will by default <i>not</i> be delegated to the target bean, relying on the 052 * Spring application context to manage the lifecycle of that bean. Specifying 053 * the "targetFilterLifecycle" filter init-param as "true" will enforce invocation 054 * of the {@code Filter.init} and {@code Filter.destroy} lifecycle methods 055 * on the target bean, letting the servlet container manage the filter lifecycle. 056 * 057 * <p>As of Spring 3.1, {@code DelegatingFilterProxy} has been updated to optionally accept 058 * constructor parameters when using Servlet 3.0's instance-based filter registration 059 * methods, usually in conjunction with Spring 3.1's 060 * {@link org.springframework.web.WebApplicationInitializer} SPI. These constructors allow 061 * for providing the delegate Filter bean directly, or providing the application context 062 * and bean name to fetch, avoiding the need to look up the application context from the 063 * ServletContext. 064 * 065 * <p>This class was originally inspired by Spring Security's {@code FilterToBeanProxy} 066 * class, written by Ben Alex. 067 * 068 * @author Juergen Hoeller 069 * @author Sam Brannen 070 * @author Chris Beams 071 * @since 1.2 072 * @see #setTargetBeanName 073 * @see #setTargetFilterLifecycle 074 * @see javax.servlet.Filter#doFilter 075 * @see javax.servlet.Filter#init 076 * @see javax.servlet.Filter#destroy 077 * @see #DelegatingFilterProxy(Filter) 078 * @see #DelegatingFilterProxy(String) 079 * @see #DelegatingFilterProxy(String, WebApplicationContext) 080 * @see javax.servlet.ServletContext#addFilter(String, Filter) 081 * @see org.springframework.web.WebApplicationInitializer 082 */ 083public class DelegatingFilterProxy extends GenericFilterBean { 084 085 @Nullable 086 private String contextAttribute; 087 088 @Nullable 089 private WebApplicationContext webApplicationContext; 090 091 @Nullable 092 private String targetBeanName; 093 094 private boolean targetFilterLifecycle = false; 095 096 @Nullable 097 private volatile Filter delegate; 098 099 private final Object delegateMonitor = new Object(); 100 101 102 /** 103 * Create a new {@code DelegatingFilterProxy}. For traditional (pre-Servlet 3.0) use 104 * in {@code web.xml}. 105 * @see #setTargetBeanName(String) 106 */ 107 public DelegatingFilterProxy() { 108 } 109 110 /** 111 * Create a new {@code DelegatingFilterProxy} with the given {@link Filter} delegate. 112 * Bypasses entirely the need for interacting with a Spring application context, 113 * specifying the {@linkplain #setTargetBeanName target bean name}, etc. 114 * <p>For use in Servlet 3.0+ environments where instance-based registration of 115 * filters is supported. 116 * @param delegate the {@code Filter} instance that this proxy will delegate to and 117 * manage the lifecycle for (must not be {@code null}). 118 * @see #doFilter(ServletRequest, ServletResponse, FilterChain) 119 * @see #invokeDelegate(Filter, ServletRequest, ServletResponse, FilterChain) 120 * @see #destroy() 121 * @see #setEnvironment(org.springframework.core.env.Environment) 122 */ 123 public DelegatingFilterProxy(Filter delegate) { 124 Assert.notNull(delegate, "Delegate Filter must not be null"); 125 this.delegate = delegate; 126 } 127 128 /** 129 * Create a new {@code DelegatingFilterProxy} that will retrieve the named target 130 * bean from the Spring {@code WebApplicationContext} found in the {@code ServletContext} 131 * (either the 'root' application context or the context named by 132 * {@link #setContextAttribute}). 133 * <p>For use in Servlet 3.0+ environments where instance-based registration of 134 * filters is supported. 135 * <p>The target bean must implement the standard Servlet Filter. 136 * @param targetBeanName name of the target filter bean to look up in the Spring 137 * application context (must not be {@code null}). 138 * @see #findWebApplicationContext() 139 * @see #setEnvironment(org.springframework.core.env.Environment) 140 */ 141 public DelegatingFilterProxy(String targetBeanName) { 142 this(targetBeanName, null); 143 } 144 145 /** 146 * Create a new {@code DelegatingFilterProxy} that will retrieve the named target 147 * bean from the given Spring {@code WebApplicationContext}. 148 * <p>For use in Servlet 3.0+ environments where instance-based registration of 149 * filters is supported. 150 * <p>The target bean must implement the standard Servlet Filter interface. 151 * <p>The given {@code WebApplicationContext} may or may not be refreshed when passed 152 * in. If it has not, and if the context implements {@link ConfigurableApplicationContext}, 153 * a {@link ConfigurableApplicationContext#refresh() refresh()} will be attempted before 154 * retrieving the named target bean. 155 * <p>This proxy's {@code Environment} will be inherited from the given 156 * {@code WebApplicationContext}. 157 * @param targetBeanName name of the target filter bean in the Spring application 158 * context (must not be {@code null}). 159 * @param wac the application context from which the target filter will be retrieved; 160 * if {@code null}, an application context will be looked up from {@code ServletContext} 161 * as a fallback. 162 * @see #findWebApplicationContext() 163 * @see #setEnvironment(org.springframework.core.env.Environment) 164 */ 165 public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) { 166 Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty"); 167 this.setTargetBeanName(targetBeanName); 168 this.webApplicationContext = wac; 169 if (wac != null) { 170 this.setEnvironment(wac.getEnvironment()); 171 } 172 } 173 174 /** 175 * Set the name of the ServletContext attribute which should be used to retrieve the 176 * {@link WebApplicationContext} from which to load the delegate {@link Filter} bean. 177 */ 178 public void setContextAttribute(@Nullable String contextAttribute) { 179 this.contextAttribute = contextAttribute; 180 } 181 182 /** 183 * Return the name of the ServletContext attribute which should be used to retrieve the 184 * {@link WebApplicationContext} from which to load the delegate {@link Filter} bean. 185 */ 186 @Nullable 187 public String getContextAttribute() { 188 return this.contextAttribute; 189 } 190 191 /** 192 * Set the name of the target bean in the Spring application context. 193 * The target bean must implement the standard Servlet Filter interface. 194 * <p>By default, the {@code filter-name} as specified for the 195 * DelegatingFilterProxy in {@code web.xml} will be used. 196 */ 197 public void setTargetBeanName(@Nullable String targetBeanName) { 198 this.targetBeanName = targetBeanName; 199 } 200 201 /** 202 * Return the name of the target bean in the Spring application context. 203 */ 204 @Nullable 205 protected String getTargetBeanName() { 206 return this.targetBeanName; 207 } 208 209 /** 210 * Set whether to invoke the {@code Filter.init} and 211 * {@code Filter.destroy} lifecycle methods on the target bean. 212 * <p>Default is "false"; target beans usually rely on the Spring application 213 * context for managing their lifecycle. Setting this flag to "true" means 214 * that the servlet container will control the lifecycle of the target 215 * Filter, with this proxy delegating the corresponding calls. 216 */ 217 public void setTargetFilterLifecycle(boolean targetFilterLifecycle) { 218 this.targetFilterLifecycle = targetFilterLifecycle; 219 } 220 221 /** 222 * Return whether to invoke the {@code Filter.init} and 223 * {@code Filter.destroy} lifecycle methods on the target bean. 224 */ 225 protected boolean isTargetFilterLifecycle() { 226 return this.targetFilterLifecycle; 227 } 228 229 230 @Override 231 protected void initFilterBean() throws ServletException { 232 synchronized (this.delegateMonitor) { 233 if (this.delegate == null) { 234 // If no target bean name specified, use filter name. 235 if (this.targetBeanName == null) { 236 this.targetBeanName = getFilterName(); 237 } 238 // Fetch Spring root application context and initialize the delegate early, 239 // if possible. If the root application context will be started after this 240 // filter proxy, we'll have to resort to lazy initialization. 241 WebApplicationContext wac = findWebApplicationContext(); 242 if (wac != null) { 243 this.delegate = initDelegate(wac); 244 } 245 } 246 } 247 } 248 249 @Override 250 public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 251 throws ServletException, IOException { 252 253 // Lazily initialize the delegate if necessary. 254 Filter delegateToUse = this.delegate; 255 if (delegateToUse == null) { 256 synchronized (this.delegateMonitor) { 257 delegateToUse = this.delegate; 258 if (delegateToUse == null) { 259 WebApplicationContext wac = findWebApplicationContext(); 260 if (wac == null) { 261 throw new IllegalStateException("No WebApplicationContext found: " + 262 "no ContextLoaderListener or DispatcherServlet registered?"); 263 } 264 delegateToUse = initDelegate(wac); 265 } 266 this.delegate = delegateToUse; 267 } 268 } 269 270 // Let the delegate perform the actual doFilter operation. 271 invokeDelegate(delegateToUse, request, response, filterChain); 272 } 273 274 @Override 275 public void destroy() { 276 Filter delegateToUse = this.delegate; 277 if (delegateToUse != null) { 278 destroyDelegate(delegateToUse); 279 } 280 } 281 282 283 /** 284 * Return the {@code WebApplicationContext} passed in at construction time, if available. 285 * Otherwise, attempt to retrieve a {@code WebApplicationContext} from the 286 * {@code ServletContext} attribute with the {@linkplain #setContextAttribute 287 * configured name} if set. Otherwise look up a {@code WebApplicationContext} under 288 * the well-known "root" application context attribute. The 289 * {@code WebApplicationContext} must have already been loaded and stored in the 290 * {@code ServletContext} before this filter gets initialized (or invoked). 291 * <p>Subclasses may override this method to provide a different 292 * {@code WebApplicationContext} retrieval strategy. 293 * @return the {@code WebApplicationContext} for this proxy, or {@code null} if not found 294 * @see #DelegatingFilterProxy(String, WebApplicationContext) 295 * @see #getContextAttribute() 296 * @see WebApplicationContextUtils#getWebApplicationContext(javax.servlet.ServletContext) 297 * @see WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 298 */ 299 @Nullable 300 protected WebApplicationContext findWebApplicationContext() { 301 if (this.webApplicationContext != null) { 302 // The user has injected a context at construction time -> use it... 303 if (this.webApplicationContext instanceof ConfigurableApplicationContext) { 304 ConfigurableApplicationContext cac = (ConfigurableApplicationContext) this.webApplicationContext; 305 if (!cac.isActive()) { 306 // The context has not yet been refreshed -> do so before returning it... 307 cac.refresh(); 308 } 309 } 310 return this.webApplicationContext; 311 } 312 String attrName = getContextAttribute(); 313 if (attrName != null) { 314 return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); 315 } 316 else { 317 return WebApplicationContextUtils.findWebApplicationContext(getServletContext()); 318 } 319 } 320 321 /** 322 * Initialize the Filter delegate, defined as bean the given Spring 323 * application context. 324 * <p>The default implementation fetches the bean from the application context 325 * and calls the standard {@code Filter.init} method on it, passing 326 * in the FilterConfig of this Filter proxy. 327 * @param wac the root application context 328 * @return the initialized delegate Filter 329 * @throws ServletException if thrown by the Filter 330 * @see #getTargetBeanName() 331 * @see #isTargetFilterLifecycle() 332 * @see #getFilterConfig() 333 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) 334 */ 335 protected Filter initDelegate(WebApplicationContext wac) throws ServletException { 336 String targetBeanName = getTargetBeanName(); 337 Assert.state(targetBeanName != null, "No target bean name set"); 338 Filter delegate = wac.getBean(targetBeanName, Filter.class); 339 if (isTargetFilterLifecycle()) { 340 delegate.init(getFilterConfig()); 341 } 342 return delegate; 343 } 344 345 /** 346 * Actually invoke the delegate Filter with the given request and response. 347 * @param delegate the delegate Filter 348 * @param request the current HTTP request 349 * @param response the current HTTP response 350 * @param filterChain the current FilterChain 351 * @throws ServletException if thrown by the Filter 352 * @throws IOException if thrown by the Filter 353 */ 354 protected void invokeDelegate( 355 Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) 356 throws ServletException, IOException { 357 358 delegate.doFilter(request, response, filterChain); 359 } 360 361 /** 362 * Destroy the Filter delegate. 363 * Default implementation simply calls {@code Filter.destroy} on it. 364 * @param delegate the Filter delegate (never {@code null}) 365 * @see #isTargetFilterLifecycle() 366 * @see javax.servlet.Filter#destroy() 367 */ 368 protected void destroyDelegate(Filter delegate) { 369 if (isTargetFilterLifecycle()) { 370 delegate.destroy(); 371 } 372 } 373 374}