1. 程式人生 > 程式設計 >說說Spring中為何要引入Lookup註解

說說Spring中為何要引入Lookup註解

前言

我們先探一探官方文件關於Method Injection的章節是怎麼說的:

In most application scenarios,most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean or a non-singleton bean needs to collaborate with another non-singleton bean,you typically handle the dependency by defining one bean as a property of the other. A problem arises when the bean lifecycles are different. Suppose singleton bean A needs to use non-singleton (prototype) bean B,perhaps on each method invocation on A. The container creates the singleton bean A only once,and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed.

用一句話概括就是 一個單例Bean A每次獲取另外一個Bean B的時候怎麼保證這個Bean B是一個新的例項?

正文

ApplicationContextAware介面

官方文件首先也提到了一個解決方案就是把A弄成容器的Aware( make bean A aware of the container),也就是實現ApplicationContextAware介面。

A solution is to forego some inversion of control. You can make bean A aware of the container by implementing the ApplicationContextAware interface,and by making a getBean("B") call to the container ask for (a typically new) bean B instance every time bean A needs it. The following example shows this approach

文件裡隨後就提供了一個示例來說明這個解決方案如何做。

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {
 private ApplicationContext applicationContext;
 public Object process(Map commandState) {
  // grab a new instance of the appropriate Command
  Command command = createCommand();
  // set the state on the (hopefully brand new) Command instance
  command.setState(commandState);
  return command.execute();
 }
 protected Command createCommand() {
  // notice the Spring API dependency!
  return this.applicationContext.getBean("command",Command.class);
 }
  public void setApplicationContext(
   ApplicationContext applicationContext) throws BeansException {
  this.applicationContext = applicationContext;
 }
 }

雖然解決了一開始提出的問題,但是Spring隨後就說到:

The preceding is not desirable,because the business code is aware of and coupled to the Spring Framework. Method Injection,a somewhat advanced feature of the Spring IoC container,lets you handle this use case cleanly.

也就是說 前面的方法是不可取的,因為業務程式碼知道並耦合到Spring框架。那怎麼降低這個耦合度呢?後半句就給出了答案:方法注入是Spring IOC容器的一個稍微高階的特性,它允許您乾淨地處理這個用例。

Lookup Method方法注入

首先再次引入官方文件中的闡述:

Lookup method injection is the ability of the container to override methods on container-managed beans and return the lookup result for another named bean in the container. The lookup typically involves a prototype bean,as in the scenario described in the preceding section. The Spring Framework implements this method injection by using bytecode generation from the CGLIB library to dynamically generate a subclass that overrides the method.

簡要概括下這段話有三個意思:

  • Lookup Method注入可以讓容器重寫容器中bean上的方法並返回容器中另一個bean的查詢結果。
  • Lookup通常會是一個原型bean,如文章開頭所說的。
  • Spring框架通過使用CGLIB庫中的位元組碼生成來動態生成覆蓋該方法的子類,從而實現這種方法注入。

使用Lookup方式需要注意以下幾點:

  • For this dynamic subclassing to work,the class that the Spring bean container subclasses cannot be final,and the method to be overridden cannot be final,either.
  • Unit-testing a class that has an abstract method requires you to subclass the class yourself and to supply a stub implementation of the abstract method.
  • Concrete methods are also necessary for component scanning,which requires concrete classes to pick up.
  • A further key limitation is that lookup methods do not work with factory methods and in particular not with @Bean methods in configuration classes,since,in that case,the container is not in charge of creating the instance and therefore cannot create a runtime-generated subclass on the fly.

這段話也可以概括為以下幾點:

  1. 使這個動態子類可以用,這個類不能是final,要重寫的方法也不能是final。
  2. 單元測試具有抽象方法的類需要您自己對該類進行子類化,並提供抽象方法的存根實現。
  3. 具體的方法對於元件掃描也是必要的,這需要具體的類來獲取。
  4. 一個關鍵限制是Lookup方法不適用於工廠方法,尤其是配置類中的@Bean方法,因為在這種情況下,容器不負責建立例項,因此不能動態建立執行時生成的子類。

接下來Spring就拿上面本來基於ApplicationContextAware的方法來說,就可以用Lookup來替換了。對於前面程式碼段中的CommandManager類,Spring容器動態重寫createCommand()方法的實現。CommandManager類沒有任何Spring依賴項,如修改後的示例所示

In the case of the CommandManager class in the previous code snippet,the Spring container dynamically overrides the implementation of the createCommand() method. The CommandManager class does not have any Spring dependencies,as the reworked example shows.

Xml配置lookup-method

 public abstract class CommandManager { 
  public Object process(Object commandState) {
   // grab a new instance of the appropriate Command interface
   Command command = createCommand();
   // set the state on the (hopefully brand new) Command instance
   command.setState(commandState);
   return command.execute();

 }
 // okay... but where is the implementation of this method?
  protected abstract Command createCommand();
 }

在包含要注入的方法的CommandManager類中,要注入的方法需要以下形式的簽名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果方法是抽象的,則動態生成的子類將實現該方法。否則,動態生成的子類將重寫在原始類中定義的具體方法。

我們來看下Bean的配置:

當需要myCommand這個Bean的新例項時,被標識為commandManager的bean就會呼叫自己的createCommand()方法。如果需要的話,必須小心地將myCommand 這個bean部署為原型。如果是單例,則每次都返回相同的myCommand bean例項.

@Lookup註解

在基於註解的元件模型中,可以通過@lookup註釋宣告查詢方法,如下例所示

 public abstract class CommandManager {
  public Object process(Object commandState) {
   Command command = createCommand();
   command.setState(commandState);
   return command.execute();
  }
  @Lookup("myCommand")
  protected abstract Command createCommand();
 }

或者,你也可以依靠目標bean根據lookup方法的宣告返回型別進行解析

public abstract class CommandManager {
  public Object process(Object commandState) {
   MyCommand command = createCommand();
   command.setState(commandState);
   return command.execute();
  }
  @Lookup
  protected abstract MyCommand createCommand();
 }

需要注意的是你通常應該用一個具體的存根實現宣告這種帶註解的查詢方法,以便它們與Spring的元件掃描規則相容,預設情況下抽象類會被忽略。此限制不適用於顯式註冊或顯式匯入的bean類。也就是說如果用元件掃描Bean的話因為抽象類預設是被忽略的,但是你加上這個Lookup註解後就不會唄忽略。

Spring在最後也提供了其他兩種解決思路:

Another way of accessing differently scoped target beans is an ObjectFactory/ Provider injection point. See Scoped Beans as Dependencies。You may also find the ServiceLocatorFactoryBean (in the org.springframework.beans.factory.config package) to be useful.

  1. 通過ObjectFactory或者ObjectProvider.
  2. 通過ServiceLocatorFactoryBean.

這兩個方案我們之後會單獨寫文章來探討,下篇文章我打算來具體的使用下這個Lookup 方法注入並且從原始碼角度來看下Spring如何巧妙地實現它的。

總結

到此這篇關於Spring中為何要引入Lookup註解的文章就介紹到這了,更多相關Spring為何引入Lookup內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!