一.小結 1.程式模塊化和可重用性是軟體工程的中心目標之一。java提供了很多有助於完成這一目標的有效結構。方法就是一個這樣的結構。 2.方法指定方法的修飾符,返回值類型,方法名和參數。比如靜態修飾符static。 3.方法可以返回一個值。返回值類型returnValueType是方法要返回的值數據 ...
有時需要使用AuthenticationManager(以下簡稱Manager)對象,可是這個對象不是Bean,沒有直接保存在Spring的Bean庫中。那麼如何獲取Spring Security中的這個對象呢?
在Spring Security 5.7.0-M2之前,通常會擴展WebSecurityConfigurerAdapter(以下簡稱Adapter)類來自定義網路安全配置。Adapter類中有一個方法authenticationManager()可以提供Manager對象。但是從Spring Security 5.7.0-M2開始,Adapter類就被棄用,再用此類中的authenticationManager()方法獲取Manager對象就不合適了。
以下是Adapter#authenticationManager()方法的源碼。
protected AuthenticationManager authenticationManager() throws Exception { if (!this.authenticationManagerInitialized) { configure(this.localConfigureAuthenticationBldr); if (this.disableLocalConfigureAuthenticationBldr) { this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager(); } else { this.authenticationManager = this.localConfigureAuthenticationBldr.build(); } this.authenticationManagerInitialized = true; } return this.authenticationManager; }
可以發現在該方法中使用Adapter類的私有欄位authenticationConfiguration的getAuthenticationManager()方法獲取Manager對象。而欄位authenticationConfiguration是使用方法setAthenticationConfiguration()自動註入的,所以Spring的Bean庫中存在Manager對象。由此可得到如下獲取AuthenticationManager對象的方案一。
方案一:從Spring的Bean庫中獲取AuthenticationConfiguration(以下簡稱Configuration)對象,然後使用Configuration對象的getAuthenticationManager()方法獲取Manager對象。(關於如何從Spring中獲取Bean,本文不作介紹,請讀者自行查閱資料瞭解)
繼續查看Configuration#getAuthenticationManager()方法的源碼。
public AuthenticationManager getAuthenticationManager() throws Exception { if (this.authenticationManagerInitialized) { return this.authenticationManager; } AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); if (this.buildingAuthenticationManager.getAndSet(true)) { return new AuthenticationManagerDelegator(authBuilder); } for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) { authBuilder.apply(config); } this.authenticationManager = authBuilder.build(); if (this.authenticationManager == null) { this.authenticationManager = getAuthenticationManagerBean(); } this.authenticationManagerInitialized = true; return this.authenticationManager; }
源碼中展示的流程如下。
① 如果Configuration對象中已保存有Manager對象,則返回該對象。
② 如果Configuration對象中沒有Manager對象,則從Spring的Bean庫中獲取AuthenticationManagerBuilder(以下簡稱Builder)對象。
③ 如果已經用Builder對象構建了Manager對象,則返回一個使用Builder對象初始化的AuthenticationManagerDelegator對象。
④ AuthenticationManagerDelegator是Manager的子類。該類代理了另一個Manager對象,被代理的Manager對象是使用Builder對象的getObject()方法獲得的。Builder#getObject()的代碼表明,在調用該方法前,需要先在Builder對象內構建一個Manager。也就是說可以從Spring的Bean庫中獲取Builder對象,然後用它的getObject()方法獲得Manager對象。
@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (this.delegate != null) { return this.delegate.authenticate(authentication); } synchronized (this.delegateMonitor) { if (this.delegate == null) { this.delegate = this.delegateBuilder.getObject(); this.delegateBuilder = null; } } return this.delegate.authenticate(authentication); }
⑤ 如果尚未使用Builder對象構建Manager對象,則先把Configuration的私有欄位globalAuthConfigurers的數據應用在Builder對象上(私有欄位globalAuthConfigurers是通過方法setGlobalAuthConfigurers()自動註入的)。
⑥ 然後使用Builder對象的build()方法構建Manager對象。
⑦ 在Builder#build()方法中,第一次調用該方法會使用doBuild()方法生成Manager對象(不分析doBuild()方法),並將這個對象緩存下來。以後再調用build()方法將會拋出AlreadyBuiltException異常。也就是說可以從Spring的Bean庫中獲取Builder對象,然後用它的build()方法獲得Manager對象。
@Override public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { this.object = doBuild(); return this.object; } throw new AlreadyBuiltException("This object has already been built"); }
⑧ 如果Builder對象未能成功構建Manager對象,則使用Configuration的私有方法lazyBean()方法獲取Manager對象(不分析lazyBean()方法)。
上述流程表明,可以從Spring的Bean庫中獲取AuthenticationManagerBuilder對象,然後使用該對象的build()方法或getObject()方法獲取AuthenticationManager對象。獲取AuthenticationManager對象的方案二如下。
方案二:從Spring的Bean庫中獲取AuthenticationManagerBuilder對象,首先調用該對象的build()方法獲取Manager對象,並對方法調用捕獲AlreadyBuiltException異常。若捕獲到異常,則在異常處理代碼中調用Builder對象的getObject()方法獲取Manager對象。
方案二可能有缺陷。在Configuration#getAuthenticationManager()方法的源碼中可以看到步驟⑤對Builder對象對象做了處理,而方案二並沒有做這種處理,推薦使用方案一。