Shiro是由Apache开发的一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能,在开发WEB项目时我们常常使用shiro进行用户认证及权限管理。但再将shiro与WEB框架(如Spring MVC)整合时,它是如何实现动态代理的呢?
- 我们通常会在web.xml中定义一个过滤器,然后让Shiro动态代理它。
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 在applicationContext.xml中定义Shiro Bean:
<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/"/>
<property name="unauthorizedUrl" value="/login"/>
<property name="filters">
<util:map>
<entry key="user" value-ref="multipleViewUserFilter"/>
</util:map>
</property>
<property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource"/>
</bean>
那么问题来啦,Shiro
是怎么替代<code>org.springframework.web.filter.DelegatingFilterProxy</code>实现动态代理的呢?
接下来我们就来看看Shiro实现动态代理的原理
我们知道一个Filter关键的方法是 doFilter
,那我们就从<code>DelegatingFilterProxy</code>的doFilter
方法来看:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
this.delegate = initDelegate(wac);
}
delegateToUse = this.delegate;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
我们看到这个方法中,使用了一个关键的对象delegateToUse
(其实它就是ShiroFilter Bean),后面还使用这个对象调用了invokeDelegate(delegateToUse, request, response, filterChain);
方法,
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
在invokeDelegate
方法中实际就是调用了它的doFilter
方法,现在只需要ShiroFilter Bean
使用替换delegateToUse
就是就可以实现动态代理了。
那么我们现在再看看<code>org.springframework.web.filter.DelegatingFilterProxy</code>的init方法,但它的类里没有init方法!别担心它的父类org.springframework.web.filter.GenericFilterBean
中有公开的init方法:
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
}
this.filterConfig = filterConfig;
// Set bean properties from init parameters.
try {
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
String msg = "Failed to set bean properties on filter '" +
filterConfig.getFilterName() + "': " + ex.getMessage();
logger.error(msg, ex);
throw new NestedServletException(msg, ex);
}
// Let subclasses do whatever initialization they like.
initFilterBean();
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
}
}
我们可以看到,这个方法调用了initFilterBean();
,这个方法在DelegatingFilterProxy
中被重写了:
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}
其中它又调用findWebApplicationContext();
获取web容器:
protected WebApplicationContext findWebApplicationContext() {
if (this.webApplicationContext != null) {
// the user has injected a context at construction time -> use it
if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
if (!((ConfigurableApplicationContext)this.webApplicationContext).isActive()) {
// the context has not yet been refreshed -> do so before returning it
((ConfigurableApplicationContext)this.webApplicationContext).refresh();
}
}
return this.webApplicationContext;
}
String attrName = getContextAttribute();
if (attrName != null) {
return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
}
else {
return WebApplicationContextUtils.getWebApplicationContext(getServletContext());
}
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
获取web容器后通过,再调用initDelegate
方法获取Shiro Bean
,并赋值给this.delegate
!
就这样实现了Shiro
的动态代理!