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