谈谈Spring中Lookup配置的使用与原理

本文介绍了Spring框架中的Lookup方法,通过XML配置和@Lookup注解两种方式实现动态方法注入。演示了如何通过这些方法在运行时指定要使用的Bean,并探讨了Spring内部如何利用Cglib实现这一过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

这篇文章我们就来具体使用下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并且走同样的逻辑了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值