談談Spring中Lookup配置的使用與原理 前言 正文

前言

這篇文章我們就來具體使用下Spring提供給我們的Lookup方法。

正文

Xml配置lookup-method
首先我們需要定義一個Java的抽象類命名爲Fruit:

public abstract class Fruit {
   // 抽象方法獲取水果
   protected abstract Fruit getFruit(String fruitName);
}

再定義兩個公共類繼承自上面的Fruit:

public class Apple extends Fruit {
   public Apple() {
      System.out.println("我得到了一個蘋果" );
   }
}
public class Orange extends Fruit {
   public Orange() {
      System.out.println("我得到了一個橙子" );
   }
}

隨後我們通過Xml配置下這兩個對象爲Spring Bean:

<!-- 這是2個非單例模式的bean -->
<bean id="apple" class="com.john.xmlconfig.Apple" scope="prototype"/>
<bean id="orange" class="com.john.xmlconfig.Orange " scope="prototype"/>

最後我們來引入最重要一個Spring Bean,它是帶有lookup-method子標籤的:

<bean id="myFruit" class="com.john.xmlconfig.Fruit">
   <lookup-method name="getFruit" bean="apple"/>
</bean>

我們注意到lookup-method的name屬性我們賦爲了Fruit類的getFruit抽象方法,而bean屬性則賦值爲apple這個Spring bean,我們姑且先可以猜測通過Spring容器運作後我們獲取myFruit後再調用它的getFruit方法,不出意外我們會得到apple這個bean。

接下來我們通過代碼啓動下Spring容器,來看看我們的猜想到底是否會生效?

public static void main(String[] args) {

   ApplicationContext app = new ClassPathXmlApplicationContext("classpath:spring-config-lookup.xml");
   Fruit myFruit= (Fruit)app.getBean("myFruit");
   myFruit.getFruit();
}

控制檯順利地打印出了 我得到了一個蘋果 ,說明我們的猜測是正確的。同理,我們通過配置@Lookup註解來驗證下。

@Lookup註解
我們把上面的所有Bean加上@Component註解,最重要的是要在Fruit類的getFruit方法上加上@Lookup註解:

public abstract class Fruit {
   // 抽象方法獲取水果
   @Lookup
   protected abstract Fruit getFruit(String fruitName);
}

之後我們通過 AnnotationConfigApplicationContext 類來啓動Spring容器驗證下,不出意外也會打印出上面的一行中文 我得到了一個蘋果 。那麼Spring是如何成功獲取到這個抽象類並把抽象類的方法替換掉的呢?答案就是應用cglib提供的強大功能。

SimpleInstantiationStrategy
在第一種Xml配置的方式下Spring首先解析Xml配置後會把lookup-method放入對應beanDefinition的 methodOverrides 成員變量中,隨後在實例化這個beanDefinition的時候,它會判斷是否有methodOverrides ,如果有,那麼它就會調用 instantiateWithMethodInjection 方法,我們拿源碼作證:

//在SimpleInstantiationStrategy類下
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
   // Don't override the class with CGLIB if no overrides.
   if (!bd.hasMethodOverrides()) {
      //省略部分代碼
   }
   else {
      //todo 像lookup replace等 需要用cglib 代理來實例化
      // Must generate CGLIB subclass.
      return instantiateWithMethodInjection(bd, beanName, owner);
   }
}

接下來就會通過CglibSubclassCreator來創建抽象類的子類了:

public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
            //todo 根據beanDefinition類來創建子類 2021-1-14
            Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
            Object instance;
            if (ctor == null) {
                instance = BeanUtils.instantiateClass(subclass);
            }
            else {
                try {
                    Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
                    instance = enhancedSubclassConstructor.newInstance(args);
                }
                catch (Exception ex) {
                    throw new BeanInstantiationException(this.beanDefinition.getBeanClass(),
                            "Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex);
                }
            }
            // SPR-10785: set callbacks directly on the instance instead of in the
            // enhanced class (via the Enhancer) in order to avoid memory leaks.
            Factory factory = (Factory) instance;
            factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
                    new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
                    new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
            return instance;
        }

我們只要看裏面最重要的一句代碼就是:

factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
                    new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
                    new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});

它會加入 LookupOverrideMethodInterceptor 這個攔截器,攔截器必然會有最重要的一個攔截方法:

//todo 調用方法的時候纔會進入這個intercept 2020-1-11
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
            // Cast is safe, as CallbackFilter filters are used selectively.
            LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
            Assert.state(lo != null, "LookupOverride not found");
            Object[] argsToUse = (args.length > 0 ? args : null);  // if no-arg, don't insist on args at all
            if (StringUtils.hasText(lo.getBeanName())) {
                return (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) :
                        this.owner.getBean(lo.getBeanName()));
            }
            else {
                //如果沒有beanName 那就根據方法返回類型來查找Bean
                return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) :
                        this.owner.getBean(method.getReturnType()));
            }
        }

到這裏我們應該會豁然開朗,Spring在我們調用getFruit方法的時候實際上會攔截,隨後就進入這個方法中執行相應的邏輯,最終就是通過beanFactory的getBean方法返回我們在Xml配置的bean。

AutowiredAnnotationBeanPostProcessor
那麼@Lookup註解是什麼時候把方法放入beanDefinition的methodOverrides中的呢?答案就是AutowiredAnnotationBeanPostProcessor 的 推斷候選的構造函數階段 。

Spring在第一次通過構造函數創建Bean實例的時候會去推斷是否有合適的構造函數,如下代碼所示:

@Override
@Nullable
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
      throws BeanCreationException {

   // Let's check for lookup methods here..
   if (!this.lookupMethodsChecked.contains(beanName)) {
      try {
         ReflectionUtils.doWithMethods(beanClass, method -> {
            Lookup lookup = method.getAnnotation(Lookup.class);
            if (lookup != null) {
               Assert.state(this.beanFactory != null, "No BeanFactory available");
               LookupOverride override = new LookupOverride(method, lookup.value());
               try {
                  ///todo 把找到的方法加入 MethodOverrides
                  RootBeanDefinition mbd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName);
                  mbd.getMethodOverrides().addOverride(override);
               }
               catch (NoSuchBeanDefinitionException ex) {
                  throw new BeanCreationException(beanName,
                        "Cannot apply @Lookup to beans without corresponding bean definition");
               }
            }
         });
      }
      catch (IllegalStateException ex) {
         throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);
      }
      this.lookupMethodsChecked.add(beanName);
   }
  //省略部分代碼
}

在這裏它會獲取方法上的Lookup註解,隨後就把獲取到的註解值,也就是bean名稱,還有當前方法放入methodOverrides。之後就會跟Xml配置方式一樣會通過 getInstantiationStrategy().instantiate(mbd, beanName, parent) 方法實例化Bean並且走同樣的邏輯了。

來源:https://www.tuicool.com/articles/q63ueaI

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章