前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >神奇!自己 new 出来的对象一样也可以被 Spring 容器管理!

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

作者头像
江南一点雨
发布2020-06-24 16:25:54
1.4K0
发布2020-06-24 16:25:54
举报
文章被收录于专栏:玩转JavaEE玩转JavaEE

松哥原创的 Spring Boot 视频教程已经杀青,感兴趣的小伙伴戳这里-->Spring Boot+Vue+微人事视频教程

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

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

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

代码语言:javascript
复制
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        return object;
                    }
                })
                .and()
                ...
    }
}

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

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

1.ObjectPostProcessor 的作用

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

代码语言:javascript
复制
package org.springframework.security.config.annotation;
public interface ObjectPostProcessor<T> {
 <O extends T> O postProcess(O object);
}

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

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

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

代码语言:javascript
复制
final class AutowireBeanFactoryObjectPostProcessor
  implements ObjectPostProcessor<Object>, DisposableBean, SmartInitializingSingleton {
 AutowireBeanFactoryObjectPostProcessor(
   AutowireCapableBeanFactory autowireBeanFactory) {
  this.autowireBeanFactory = autowireBeanFactory;
 }
 @SuppressWarnings("unchecked")
 public <T> T postProcess(T object) {
  if (object == null) {
   return null;
  }
  T result = null;
  try {
   result = (T) this.autowireBeanFactory.initializeBean(object,
     object.toString());
  }
  catch (RuntimeException e) {
   Class<?> type = object.getClass();
   throw new RuntimeException(
     "Could not postProcess " + object + " of type " + type, e);
  }
  this.autowireBeanFactory.autowireBean(object);
  if (result instanceof DisposableBean) {
   this.disposableBeans.add((DisposableBean) result);
  }
  if (result instanceof SmartInitializingSingleton) {
   this.smartSingletons.add((SmartInitializingSingleton) result);
  }
  return result;
 }
}

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):

代码语言:javascript
复制
protected final HttpSecurity getHttp() throws Exception {
 if (http != null) {
  return http;
 }
    ...
    ...
 http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
   sharedObjects);
 ...
    ...
}

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

代码语言:javascript
复制
public void setFilterChainProxySecurityConfigurer(
  ObjectPostProcessor<Object> objectPostProcessor,
  @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
  throws Exception {
 webSecurity = objectPostProcessor
   .postProcess(new WebSecurity(objectPostProcessor));
    ...
    ...
}

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

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

代码语言:javascript
复制
public void configure(B http) throws Exception {
 ...
    ...
 F filter = postProcess(authFilter);
 http.addFilter(filter);
}

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

3.为什么这样

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

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

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

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

代码语言:javascript
复制
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        return object;
                    }
                })
                .and()
                ...
    }
}

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

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

代码语言:javascript
复制
abstract class AbstractInterceptUrlConfigurer<C extends AbstractInterceptUrlConfigurer<C, H>, H extends HttpSecurityBuilder<H>>
  extends AbstractHttpConfigurer<C, H> {
 @Override
 public void configure(H http) throws Exception {
  FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
  if (metadataSource == null) {
   return;
  }
  FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(
    http, metadataSource, http.getSharedObject(AuthenticationManager.class));
  if (filterSecurityInterceptorOncePerRequest != null) {
   securityInterceptor
     .setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
  }
  securityInterceptor = postProcess(securityInterceptor);
  http.addFilter(securityInterceptor);
  http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
 }
}

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

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

代码语言:javascript
复制
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                ...
                .and()
                .formLogin()
                .withObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() {
                    @Override
                    public <O extends UsernamePasswordAuthenticationFilter> O postProcess(O object) {
                        object.setUsernameParameter("name");
                        return object;
                    }
                })
                ...
    }
}

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

4.小结

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 江南一点雨 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.ObjectPostProcessor 的作用
  • 2.框架举例
  • 3.为什么这样
  • 4.小结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档