自己 new 出来的对象一样也可以被 Spring 容器管理!

存储 存储软件
按理说自己 new 出来的对象和容器是没有关系的,但是在 Spring Security 框架中也 new 了很多对象出来,一样也可以被容器管理,那么它是怎么做到的?

按理说自己 new 出来的对象和容器是没有关系的,但是在 Spring Security 框架中也 new 了很多对象出来,一样也可以被容器管理,那么它是怎么做到的?

[[330985]]

今天来和大家聊一个略微冷门的话题,Spring Security 中的 ObjectPostProcessor 到底是干嘛用的?

如果大家研究过松哥的微人事项目,就会发现里边的动态权限配置有这样一行代码:

  1. @Configuration 
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter { 
  3.  
  4.     @Override 
  5.     protected void configure(HttpSecurity http) throws Exception { 
  6.         http.authorizeRequests() 
  7.                 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { 
  8.                     @Override 
  9.                     public <O extends FilterSecurityInterceptor> O postProcess(O object) { 
  10.                         object.setAccessDecisionManager(customUrlDecisionManager); 
  11.                         object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource); 
  12.                         return object; 
  13.                     } 
  14.                 }) 
  15.                 .and() 
  16.                 ... 
  17.     } 

这里的 withObjectPostProcessor 到底该如何理解?

今天松哥就来和大家揭开谜题。

1.ObjectPostProcessor 的作用

我们先来看下 ObjectPostProcessor 到底有啥作用,先来看一下这个接口的定义:

  1. package org.springframework.security.config.annotation; 
  2. public interface ObjectPostProcessor<T> { 
  3.  <O extends T> O postProcess(O object); 

接口中就只有一个 postProcess 方法。

我们再来看看 ObjectPostProcessor 的继承关系:

两个比较重要的实现类,其中 AutowireBeanFactoryObjectPostProcessor 值得一说,来看下 AutowireBeanFactoryObjectPostProcessor 的定义:

  1. final class AutowireBeanFactoryObjectPostProcessor 
  2.   implements ObjectPostProcessor<Object>, DisposableBean, SmartInitializingSingleton { 
  3.  AutowireBeanFactoryObjectPostProcessor( 
  4.    AutowireCapableBeanFactory autowireBeanFactory) { 
  5.   this.autowireBeanFactory = autowireBeanFactory; 
  6.  } 
  7.  @SuppressWarnings("unchecked"
  8.  public <T> T postProcess(T object) { 
  9.   if (object == null) { 
  10.    return null
  11.   } 
  12.   T result = null
  13.   try { 
  14.    result = (T) this.autowireBeanFactory.initializeBean(object, 
  15.      object.toString()); 
  16.   } 
  17.   catch (RuntimeException e) { 
  18.    Class<?> type = object.getClass(); 
  19.    throw new RuntimeException( 
  20.      "Could not postProcess " + object + " of type " + type, e); 
  21.   } 
  22.   this.autowireBeanFactory.autowireBean(object); 
  23.   if (result instanceof DisposableBean) { 
  24.    this.disposableBeans.add((DisposableBean) result); 
  25.   } 
  26.   if (result instanceof SmartInitializingSingleton) { 
  27.    this.smartSingletons.add((SmartInitializingSingleton) result); 
  28.   } 
  29.   return result; 
  30.  } 

AutowireBeanFactoryObjectPostProcessor 的源码很好理解:

  1. 首先在构建 AutowireBeanFactoryObjectPostProcessor 对象时,传入了一个 AutowireCapableBeanFactory 对象,看过 Spring 源码的小伙伴就知道,AutowireCapableBeanFactory 可以帮助我们手动的将一个实例注册到 Spring 容器中。
  2. 在 postProcess 方法中,就是具体的注册逻辑了,都很简单,我就不再赘述。

由此可见,ObjectPostProcessor 的主要作用就是手动注册实例到 Spring 容器中去(并且让实例走一遍 Bean 的生命周期)。

正常来说,我们项目中的 Bean 都是通过自动扫描注入到 Spring 容器中去的,然而在 Spring Security 框架中,有大量的代码不是通过自动扫描注入到 Spring 容器中去的,而是直接 new 出来的,这样做的本意是为了简化项目配置。

这些直接 new 出来的代码,如果想被 Spring 容器管理该怎么办呢?那就得 ObjectPostProcessor 出场了。

2.框架举例

接下来我随便举几个框架中对象 new 的例子,大家看一下 ObjectPostProcessor 的作用:

HttpSecurity 初始化代码(WebSecurityConfigurerAdapter#getHttp):

  1. protected final HttpSecurity getHttp() throws Exception { 
  2.  if (http != null) { 
  3.   return http; 
  4.  } 
  5.     ... 
  6.     ... 
  7.  http = new HttpSecurity(objectPostProcessor, authenticationBuilder, 
  8.    sharedObjects); 
  9.  ... 
  10.     ... 

WebSecurity 初始化代码(WebSecurityConfiguration#setFilterChainProxySecurityConfigurer):

  1. public void setFilterChainProxySecurityConfigurer( 
  2.   ObjectPostProcessor<Object> objectPostProcessor, 
  3.   @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) 
  4.   throws Exception { 
  5.  webSecurity = objectPostProcessor 
  6.    .postProcess(new WebSecurity(objectPostProcessor)); 
  7.     ... 
  8.     ... 

Spring Security 框架源码中,随处可见手动装配。Spring Security 中,过滤器链中的所有过滤器都是通过对应的 xxxConfigure 来进行配置的,而所有的 xxxConfigure 都是继承自 SecurityConfigurerAdapter,如下:

而在这些 xxxConfigure 的 configure 方法中,无一例外的都会让他们各自配置的管理器去 Spring 容器中走一圈,例如 AbstractAuthenticationFilterConfigurer#configure 方法:

  1. public void configure(B http) throws Exception { 
  2.  ... 
  3.     ... 
  4.  F filter = postProcess(authFilter); 
  5.  http.addFilter(filter); 

其他的 xxxConfigurer#configure 方法也都有类似的实现,小伙伴们可以自行查看,我就不再赘述了。

3.为什么这样

直接将 Bean 通过自动扫描注册到 Spring 容器不好吗?为什么要搞成这个样子?

在 Spring Security 中,由于框架本身大量采用了 Java 配置,并且没有将对象的各个属性都暴露出来,这样做的本意是为了简化配置。然而这样带来的一个问题就是需要我们手动将 Bean 注册到 Spring 容器中去,ObjectPostProcessor 就是为了解决该问题。

一旦将 Bean 注册到 Spring 容器中了,我们就有办法去增强一个 Bean 的功能,或者需修改一个 Bean 的属性。

例如一开始提到的动态权限配置代码:

  1. @Configuration 
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter { 
  3.  
  4.     @Override 
  5.     protected void configure(HttpSecurity http) throws Exception { 
  6.         http.authorizeRequests() 
  7.                 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { 
  8.                     @Override 
  9.                     public <O extends FilterSecurityInterceptor> O postProcess(O object) { 
  10.                         object.setAccessDecisionManager(customUrlDecisionManager); 
  11.                         object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource); 
  12.                         return object; 
  13.                     } 
  14.                 }) 
  15.                 .and() 
  16.                 ... 
  17.     } 

权限管理本身是由 FilterSecurityInterceptor 控制的,系统默认的 FilterSecurityInterceptor 已经创建好了,而且我也没办法修改它的属性,那么怎么办呢?我们可以利用 withObjectPostProcessor 方法,去修改 FilterSecurityInterceptor 中的相关属性。

上面这个配置生效的原因之一是因为 FilterSecurityInterceptor 在创建成功后,会重走一遍 postProcess 方法,这里通过重写 postProcess 方法就能实现属性修改,我们可以看下配置 FilterSecurityInterceptor 的方法(AbstractInterceptUrlConfigurer#configure):

  1. abstract class AbstractInterceptUrlConfigurer<C extends AbstractInterceptUrlConfigurer<C, H>, H extends HttpSecurityBuilder<H>> 
  2.   extends AbstractHttpConfigurer<C, H> { 
  3.  @Override 
  4.  public void configure(H http) throws Exception { 
  5.   FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http); 
  6.   if (metadataSource == null) { 
  7.    return
  8.   } 
  9.   FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor( 
  10.     http, metadataSource, http.getSharedObject(AuthenticationManager.class)); 
  11.   if (filterSecurityInterceptorOncePerRequest != null) { 
  12.    securityInterceptor 
  13.      .setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest); 
  14.   } 
  15.   securityInterceptor = postProcess(securityInterceptor); 
  16.   http.addFilter(securityInterceptor); 
  17.   http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor); 
  18.  } 

可以看到,securityInterceptor 对象创建成功后,还是会去 postProcess 方法中走一遭。

看懂了上面的代码,接下来我再举一个例子小伙伴们应该一下就能明白:

  1. @Configuration 
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter { 
  3.  
  4.     @Override 
  5.     protected void configure(HttpSecurity http) throws Exception { 
  6.         http.authorizeRequests() 
  7.                 .antMatchers("/admin/**").hasRole("admin"
  8.                 ... 
  9.                 .and() 
  10.                 .formLogin() 
  11.                 .withObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() { 
  12.                     @Override 
  13.                     public <O extends UsernamePasswordAuthenticationFilter> O postProcess(O object) { 
  14.                         object.setUsernameParameter("name"); 
  15.                         return object; 
  16.                     } 
  17.                 }) 
  18.                 ... 
  19.     } 

在这里,我把配置好的 UsernamePasswordAuthenticationFilter 过滤器再拎出来,修改一下用户名的 key(正常来说,修改用户名的 key 不用这么麻烦,这里主要是给大家演示 ObjectPostProcessor 的效果),修改完成后,以后用户登录时,用户名就不是 username 而是 name 了。

4.小结

好了,只要小伙伴们掌握了上面的用法,以后在 Spring Security 中,如果想修改某一个对象的属性,但是却不知道从哪里下手,那么不妨试试 withObjectPostProcessor!

本文转载自微信公众号「江南一点雨」,可以通过以下二维码关注。转载本文请联系江南一点雨公众号。

 

责任编辑:武晓燕 来源: 江南一点雨
相关推荐

2020-09-10 13:55:53

VScodego开发工具

2011-03-14 16:51:24

2022-11-29 12:53:36

机器人物理MIT

2022-04-14 19:39:39

Java线程安全

2019-09-05 09:29:00

CAP理论分布式系统

2011-10-27 09:42:19

ASP.NET

2011-02-28 10:38:13

Windows 8

2012-12-20 10:17:32

IT运维

2012-03-07 17:24:10

戴尔咨询

2021-08-27 06:41:34

Docker ContainerdRun&Exec

2021-02-01 06:10:02

springaop机制开发

2009-06-12 15:26:02

2021-08-12 06:08:15

CSS 技巧组件状态

2015-08-25 09:52:36

云计算云计算产业云计算政策

2021-05-20 08:37:32

multiprocesPython线程

2013-01-11 18:10:56

软件

2020-01-09 17:03:29

人工智能技术算法

2020-11-12 08:30:38

Java微服务Go

2021-06-01 05:50:03

Spring@PostConstrLifecycle

2021-06-13 08:55:25

Spring NatiWebFluxJVM
点赞
收藏

51CTO技术栈公众号