Acegi源码解读——FilterToBeanProxy

  这个类最早是在基于Spring的安全框架Acegi中提出来的,一个非常不错的创意性的代理类:将Servlet Filter代理成Spring容器中的Bean,这样可以让Filter也能享受到Spring容器的依赖注入。

需求分析

  我们通常在配置过滤器(比如编码设置,比如XSS过滤等)时,都是在 web.xml 中通过<filter>节点来完成,这个是基于Tomcat与J2EE规范的,并不在Spring容器的管理范围内。如果我想要在Filter的执行过程中利用到Spring容器里面依赖管理的话该怎么处理呢?比如,我们想要在Filter中获取某个SpringBean该怎么办呢?其方法之一就是通过WebApplicationContextUtils的工具方法来获取到ApplicationContext,再通过ApplicationContext来获取相应的SpringBean,如下所示:

1
2
ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(filterConfig.getServletContext());
MyBean myBean = applicationContext.getBean("myBean", MyBean.class);

  这个过程虽然能达到我们获取容器组件的目标,但如果我们所依赖的组件较多,同时还想要其它有关Spring容器的功能时会比较繁琐。

使用方法

   FilterToBeanProxyAcegi(现在已经成为SpringSecurity框架的一部分)专门为了解决这类问题,它在内部维护了一个delegate对象,并将Filter的操作都代理到delegate上,而这个对象delegateSpring容器中的组件,这样一来,既能够满足我们Filter的功能,也能利用到Spring容器带来的增强。

  它的使用方法跟普通的Filter一样,是在web.xml配置FilterToBeanProxy,只不过它的filter-name是有特殊含义的,如下所示:

1
2
3
4
5
6
7
8
<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>

  它的特殊性就在于,这个Filter最终所有的操作都是代理到FilterChainProxy类型的SpringBean上面去的。

初始化过程

  我们来看看它的源码(省略非关注点代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
public class FilterToBeanProxy implements Filter {

private Filter delegate;
private FilterConfig filterConfig;
private boolean initialized = false;
private boolean servletContainerManaged = false;

public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;

// 判定初始化策略,如果是 lazy 则是在第一次请求过滤的时候执行初始化,而非容器启动
String strategy = filterConfig.getInitParameter("init");

if ((strategy != null) && strategy.toLowerCase().equals("lazy")) {
return;
}

doInit();
}

private synchronized void doInit() throws ServletException {
if (initialized) {
// already initialized, so don't re-initialize
return;
}

// 查找 targetBean 属性,如果配置过了的话
String targetBean = filterConfig.getInitParameter("targetBean");

if ("".equals(targetBean)) {
targetBean = null;
}

// 确定生命周期,这个是为后面决定是否需要初始化做准备的,如果是由 servlet 容器来管理的话,才需要执行 init
String lifecycle = filterConfig.getInitParameter("lifecycle");

if ("servlet-container-managed".equals(lifecycle)) {
servletContainerManaged = true;
}

ApplicationContext ctx = this.getContext(filterConfig);

String beanName = null;

// 如果设置了 targetBean 而且 Spring 容器中也存在这个名称的 bean 的话,就直接用这个名称
if ((targetBean != null) && ctx.containsBean(targetBean)) {
beanName = targetBean;
} else if (targetBean != null) {
throw new ServletException("targetBean '" + targetBean + "' not found in context");
} else {
// 否则的话获取目标的 class,并根据 class 从容器中获取 SpringBean 定义,如果存在的话就获取第一个该类型的 Bean Name
String targetClassString = filterConfig.getInitParameter("targetClass");

if ((targetClassString == null) || "".equals(targetClassString)) {
throw new ServletException("targetClass or targetBean must be specified");
}

Class targetClass;

try {
targetClass = Thread.currentThread().getContextClassLoader().loadClass(targetClassString);
} catch (ClassNotFoundException ex) {
throw new ServletException("Class of type " + targetClassString + " not found in classloader");
}

Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, targetClass, true, true);

if (beans.size() == 0) {
throw new ServletException("Bean context must contain at least one bean of type " + targetClassString);
}

beanName = (String) beans.keySet().iterator().next();
}

// 根据 beanName 从容器中获取组件
Object object = ctx.getBean(beanName);

// 这个组件一定要同时是一个常规的 Filter ,否则抛出异常
if (!(object instanceof Filter)) {
throw new ServletException("Bean '" + beanName + "' does not implement javax.servlet.Filter");
}

// 将找到的组件赋值给 delegate
delegate = (Filter) object;

if (servletContainerManaged) {
delegate.init(filterConfig);
}

// Set initialized to true at the end of the synchronized method, so
// that invocations of doFilter() before this method has completed will not
// cause NullPointerException
initialized = true;
}

/**
* Allows test cases to override where application context obtained from.
*
* @param filterConfig which can be used to find the <code>ServletContext</code>
*
* @return the Spring application context
*/
protected ApplicationContext getContext(FilterConfig filterConfig) {
return WebApplicationContextUtils.getRequiredWebApplicationContext(filterConfig.getServletContext());
}
}

  可以看到,在 doInit的时候就是根据配置查找对应的Spring容器组件的过程,步骤如下:

  1. 判定初始化策略,如果是 lazy 则是在第一次请求过滤的时候执行初始化,而非容器启动时;
  2. 查找targetBean属性,如果存在,则 beanName就是该配置值;
  3. 检查lifecycle配置,如果是servlet-container-managed则标记该组件是一个Servlet管控的组件。
  4. 如果targetBean忏悔存在,但是Spring容器中没有这个bean,则直接抛出异常。
  5. 查找targetClass属性,如果不存在则报错(该属性和 targetBean必须选配一个),如果存在则在Spring容器中查找该类型的组件。
    • 如果找到了,则beanName为该类型第一个组件的beanName
    • 如果没找到,则抛出异常;
  6. 判定找到的组件是否是一个Servlet Filter,如果不是则抛出异常;
  7. 如果是Servlet管控的组件,则调用组件的init方法。

执行过程

  执行过程就比较简单了,就是将doFilter 方法代理给Spring组件,如下所示:

1
2
3
4
5
6
7
8
9
10
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 判定初始化策略,如果是 lazy 则是在第一次请求过滤的时候执行初始化
if (!initialized) {
doInit();
}

// 代理到 spring 组件上
delegate.doFilter(request, response, chain);
}