有多少人用过Spring的@Lookup注解?

63 篇文章 0 订阅
60 篇文章 0 订阅

一、简介

Lookup方法注入能够根据@Lookup注解的value属性值或被注解该方法的返回值,从容器中查找bean作为方法的返回值对象使用。Spring容器会通过CGLIB生成当前类的代理,然后重写被@Lookup注解的方法。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lookup {

  /**
   * 根据你设置的value值,从容器中查找对应的bean,如果没有指定value值
   * 那么会根据被注解方法的返回值类型从容器中查找相应的bean
   */
  String value() default "";

}

二、应用案例

准备基础类

static interface HttpSecurity {
  void http() ;
}
static class HtmlHttpSecurity implements HttpSecurity {
  @Override
  public void http() {
    System.out.println("Html HttpSecurity...") ;
  }
}

定义一个抽象类
该类中有一个抽象方法被@Lookup注解标注

static abstract class SecurityManager {
  public void execute() {
    HttpSecurity httpSecurity = httpSecurity() ;
    System.out.println(httpSecurity.getClass()) ;
    httpSecurity.http() ; 
  }
  @Lookup("html")
  protected abstract HttpSecurity httpSecurity() ;
}

注册Bean
将上面的类注册到容器中

public static void main(String[] args) {
  try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
    context.registerBean("html", HtmlHttpSecurity.class) ;
    context.register(SecurityManager.class) ;
    context.refresh() ;
    SecurityManager sm = context.getBean(SecurityManager.class);
    sm.execute() ;
    System.out.println(sm.getClass()) ;
  }
}

执行结果

class com.pack.main.lookup.MethodLookupInjectMain2$HtmlHttpSecurity
Html HttpSecurity...
class com.pack.main.lookup.MethodLookupInjectMain2$SecurityManager$$EnhancerBySpringCGLIB$$ae697832

SecurityManager通过CGLIB被创建为了代理类。同时execute方法中HttpSecurity对象就是HtmlHttpSecurity类,也就是容器通过查找注入的。

去掉@Lookup注解的value属性

@Lookup
protected abstract HttpSecurity httpSecurity() ;

继续执行上面的测试代码,程序也能正常的输出。
在添加一个HttpSecurity的实现

static class CssHttpSecurity implements HttpSecurity {
  @Override
  public void http() {
    System.out.println("Css HttpSecurity...") ;
  }
}

将上面的类也注册到容器中,如果这时候你的@Lookup注解没有value属性将会报错

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.pack.main.lookup.MethodLookupInjectMain2$HttpSecurity' available: expected single matching bean but found 2: html,css
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1273)
expected single matching bean but found 2: html,css;

错误提示你有2个bean,期望的是一个,所以这里我们必须添加value属性,指明要查找的bean对象。

继续测试,被@Lookup注解的方法是不是必须是抽象的?

修改代码如下:

@Lookup("html")
protected HttpSecurity httpSecurity() {
  return null ;
};

方法返回null,执行结果

class com.pack.main.lookup.MethodLookupInjectMain2$HtmlHttpSecurity
Html HttpSecurity...
class com.pack.main.lookup.MethodLookupInjectMain2$SecurityManager$$EnhancerBySpringCGLIB$$ae697832

程序正常,没有问题。这也说明了,这里和具体的返回值是没有关系的。

三、实现原理

容器在创建一个Bean对象时会执行如下步骤

public abstract class AbstractAutowireCapableBeanFactory {
  protected Object doCreateBean() {
    BeanWrapper instanceWrapper = null;
    // ...
    if (instanceWrapper == null) {
      // 创建实例
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    // ...
  }
  protected BeanWrapper createBeanInstance() {
    // 查找构造方法
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    // 实例化bean对象;上面一步对@Lookup注解的方法进行了初始化查找
    return instantiateBean(beanName, mbd);
  }
  protected Constructor<?>[] determineConstructorsFromBeanPostProcessors(@Nullable Class<?> beanClass, String beanName)
      throws BeansException {

    if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) {
      // 调用BeanPostProcessor
      for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
        // 这里通过了AutowiredAnnotationBeanPostProcessor处理器
        // 这里其实最终返回了null,关键就是执行了下面的动作,查找了@Lookup注解的方法进行解析
        // 保存到BeanDefinition中
        Constructor<?>[] ctors = bp.determineCandidateConstructors(beanClass, beanName);
        if (ctors != null) {
          return ctors;
        }
      }
    }
    return null;
  }
}

AutowiredAnnotationBeanPostProcessor处理器

public class AutowiredAnnotationBeanPostProcessor {
  public Constructor<?>[] determineCandidateConstructors() {
    do {
      // 遍历当前类的所有方法
      ReflectionUtils.doWithLocalMethods(targetClass, method -> {
        // 获取方法是的@Lookup注解
        Lookup lookup = method.getAnnotation(Lookup.class);
        if (lookup != null) {
          // 如果存在则构造LookupOverride对象,将当前的Method及注解的value值传入
          LookupOverride override = new LookupOverride(method, lookup.value());
          try {
            RootBeanDefinition mbd = (RootBeanDefinition)this.beanFactory.getMergedBeanDefinition(beanName);
            // 最后将其保存到BeanDefinition中
            mbd.getMethodOverrides().addOverride(override);
          }
        }
      });
      targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);
  }
}

继续上面执行到instantiateBean方法

public abstract class AbstractAutowireCapableBeanFactory {
  protected BeanWrapper instantiateBean() {
    // getInstancetiationStrategy方法返回了CglibSubclassingInstantiationStrategy
    beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
  }
}
进入SimpleInstantiationStrategy#instanceiate方法

public class SimpleInstantiationStrategy implements InstantiationStrategy {
  public Object instantiate(RootBeanDefinition bd) {
    // 判断当前的BeanDefinition中是否有LookupOverride
    // 上面查找到后已经将其保存到了BeanDefinition中。
    if (!bd.hasMethodOverrides()) {
      // ...
    }
    else {
      // 存在则通过CGLIB进行创建代理
      // 进入CglibSubclassingInstantiationStrategy
      return instantiateWithMethodInjection(bd, beanName, owner);
    }
  }
}

CglibSubclassingInstantiationStrategy

public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {
  protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
    return instantiateWithMethodInjection(bd, beanName, owner, null);
  }
  protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
      @Nullable Constructor<?> ctor, Object... args)
    return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
  }
}

CglibSubclassCreator

private static class CglibSubclassCreator {
  private static final Class<?>[] CALLBACK_TYPES = new Class<?>[]
        {NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class};
  public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
    // 创建代理,当前类的子类
    Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
    // ...
  }
  private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(beanDefinition.getBeanClass());
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    if (this.owner instanceof ConfigurableBeanFactory) {
      ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader();
      enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl));
    }
    
    enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition));
// 我们主要关注这里,这些都是CGLIB相关的知识了。
// 关注LookupOverrideMethodInterceptor拦截器
enhancer.setCallbackTypes(CALLBACK_TYPES);
return enhancer.createClass();

}
}

LookupOverrideMethodInterceptor拦截器

private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don’t insist on args at all
// 判断是否设置了@Lookup value值
if (StringUtils.hasText(lo.getBeanName())) {
// 根据value指定的值查找bean对象
Object bean = (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) :
this.owner.getBean(lo.getBeanName()));
return (bean.equals(null) ? null : bean);
}
// 如果没有设置value,那么会根据方法的返回值类型在容器中查找bean
else {
ResolvableType genericReturnType = ResolvableType.forMethodReturnType(method);
return (argsToUse != null ? this.owner.getBeanProvider(genericReturnType).getObject(argsToUse) :
this.owner.getBeanProvider(genericReturnType).getObject());
}
}
}

以上就是@Lookup注解的原理

总结:Spring的@Lookup注解提供了一种灵活的机制,用于在运行时动态地创建和初始化beans。通过@Lookup,开发者可以在配置中指定一个方法,该方法会在运行时被调用以获取相应的bean实例。这使得在某些特定条件下或在运行时配置变更时,能够动态地选择和创建不同的bean实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值