骚操作,实现DependsOnByType

399 阅读6分钟

背景

最近在做需求的时候遇到一个小问题,我需要让A的所有实现类bean去依赖B接口的所有实现类bean,且考虑到日后的实现类bean还会增加,需要有一劳永逸的方法,而不是每次在新增后需要再去进行维护。


思路

核心在于控制bean的创建顺序,我需要控制A接口的所有实现类beanB接口的所有实现类bean之前进行创建。


调研


@Order注解 or Ordered接口


场景使用场景

第一个想到的就是@Order注解

比较经典的是: 我们在结合Spring框架编码策略模式的时候,通过@Resource or@Autowired注入List<策略接口>,就能拿到所有策略

让后对于具体策略使用@Order注解,或者让类实现Ordered接口控制顺序,这样在注入的时候List中的策略就会根据具体value进行升序

以下是一个比较简单的案例

public interface TestInterface {
}

public abstract class AbstractTest implements TestInterface {
}

@Order(2)
@Service
public class Test1 extends AbstractTest {
}

@Order(3)
@Service
public class Test2 extends AbstractTest {
}

@Order(1)
@Service
public class Test3 extends AbstractTest {
}

启动项目,Debug可以看到确实是按照我们@Order注解定义的顺序升序了~

image-20230527135245207


真的不行

上面只是举个常见例子,下面实测下是不是不行~

public interface TestA {
}

@Order(2)
@Service
public class TestA_1 implements TestA{
	public TestA_1() {
		System.out.println("TestA_1");
	}
}

public interface TestB {
}

@Order(1)
@Service
public class TestB_1 implements TestB {
    public TestB_1() {
            System.out.println("TestB_1");
    }
}

默认情况下(不使用@Order注解),根据文件是从上到下加载,所以默认TestA_1TestB_1先加载,但是我们在使用@Order注解发现结果还是一样,依然是**TestA_1TestB_1先加载**

image-20230527135925539


为什么不行呢

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

因为实例化bean时,是根据beanDefinitionNames的顺序来一个一个实例化的,并没有根据我们的@Order注解或者Ordered接口定义的优先级来进行排序,所以@Order注解没用!


/** List of bean definition names, in registration order. */
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

public void preInstantiateSingletons() throws BeansException {
  if (logger.isTraceEnabled()) {
    logger.trace("Pre-instantiating singletons in " + this);
  }

  List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

  // 遍历创建
  for (String beanName : beanNames) {
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
      if (isFactoryBean(beanName)) {
        Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
        if (bean instanceof FactoryBean) {
          FactoryBean<?> factory = (FactoryBean<?>) bean;
          boolean isEagerInit;
          if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
            isEagerInit = AccessController.doPrivileged(
              (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
              getAccessControlContext());
          }
          else {
            isEagerInit = (factory instanceof SmartFactoryBean &&
                           ((SmartFactoryBean<?>) factory).isEagerInit());
          }
          if (isEagerInit) {
            getBean(beanName);
          }
        }
      }
      else {
        getBean(beanName);
      }
    }
  }
}

@DependsOn注解扩展性太差

第二个想到的就是@DependsOn注解,这个肯定没问题。

public interface TestA {
}

@DependsOn("testB_1")
@Service
public class TestA_1 implements TestA{
  public TestA_1() {
    System.out.println("TestA_1");
  }
}

public interface TestB {
}

@Service
public class TestB_1 implements TestB {
  public TestB_1() {
    System.out.println("TestB_1");
  }
}

启动项目,确实发现,TestB_1TestA_1之前被创建了

image-20230527140825740

但是啊,@DependsOn注解只支持传**beanName的数组**,那如果A接口、B接口的实现类bean越来越多,那岂不是每一个A接口实现类bean都要加上@DependsOn注解,且value配置一大堆B接口的实现类beanName数组,这样配置起来就太太太麻烦了!

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {

  String[] value() default {};

}

发现骚操作

回到思路,核心在于控制bean的创建顺序,我需要控制A接口的所有实现类beanB接口的所有实现类bean之前进行创建。

仔细想想,Spring给我们留下来那么多扩展接口,难道就没有一个可以利用的吗?

答案肯定不是,不然我这篇文章也出不来

image-20230527141430515

代码实现

这不,就被我发现一个扩展接口InstantiationAwareBeanPostProcessorAdapter

@Component
public class ExtendDependsOnByType extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {

	private ListableBeanFactory listableBeanFactory;

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		if (beanFactory instanceof ListableBeanFactory) {
			this.listableBeanFactory = (ListableBeanFactory) beanFactory;
		}
	}

	@Override
	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		if (TestA.class.isAssignableFrom(beanClass)) {
			listableBeanFactory.getBeansOfType(TestB.class);
		}
		return null;
	}

}
public interface TestA {
}

@Service
public class TestA_1 implements TestA{
  public TestA_1() {
    System.out.println("TestA_1");
  }
}

@Service
public class TestA_2 implements TestA{
  public TestA_2() {
    System.out.println("TestA_2");
  }
}

public interface TestB {
}

@Service
public class TestB_1 implements TestB {
  public TestB_1() {
    System.out.println("TestB_1");
  }
}

@Service
public class TestB_2 implements TestB {
  public TestB_2() {
    System.out.println("TestB_2");
  }
}

多加点类试试,哈哈哈哈

image-20230527142100325

image-20230527142647303


骚操作原理

再提一遍,核心在于控制bean的创建顺序,我需要控制A接口的所有实现类beanB接口的所有实现类bean之前进行创建。


A接口bean实例化时机点

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
  throws BeanCreationException {
  
  // ...... 省略

  try {
    // bean实例化之前
    Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
    if (bean != null) {
      return bean;
    }
  }
  catch (Throwable ex) {
    throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                                    "BeanPostProcessor before instantiation of bean failed", ex);
  }

  try {
    // 真正去创建bean
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    if (logger.isTraceEnabled()) {
      logger.trace("Finished creating instance of bean '" + beanName + "'");
    }
    return beanInstance;
  }
  catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
    // A previously detected exception with proper bean creation context already,
    // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
    throw ex;
  }
  catch (Throwable ex) {
    throw new BeanCreationException(
      mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
  }
}

翻阅源码,在createBean方法中,会去调用doCreateBean真正去创建bean

但在此之前会调用resolveBeforeInstantiation,即实例化之前会做一些操作

protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
  Object bean = null;
  if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      Class<?> targetType = determineTargetType(beanName, mbd);
      if (targetType != null) {
        bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
        if (bean != null) {
          bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
        }
      }
    }
    mbd.beforeInstantiationResolved = (bean != null);
  }
  return bean;
}

protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
  for (BeanPostProcessor bp : getBeanPostProcessors()) {
    if (bp instanceof InstantiationAwareBeanPostProcessor) {
      InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
      Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
      if (result != null) {
        return result;
      }
    }
  }
  return null;
}

通过applyBeanPostProcessorsBeforeInstantiation方法,会调用InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation

这个时机就是我们要的A接口所有实现类bean实例化的时机点

则可以先写出如下代码

@Component
public class ExtendDependsOnByType extends InstantiationAwareBeanPostProcessorAdapter {

  @Override
  public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
    // 如果是TestA接口的实现类进行实例化
    if (TestA.class.isAssignableFrom(beanClass)) {

    }
  }

}

先创建B接口所有bean

接下来就是要先创建B接口的所有bean

稍微了解Spring框架的朋友们应该是知道如下源码的

// org.springframework.beans.factory.support.AbstractBeanFactory

@Override
public Object getBean(String name) throws BeansException {
  return doGetBean(name, null, null, false);
}

@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
  return doGetBean(name, requiredType, null, false);
}

@Override
public Object getBean(String name, Object... args) throws BeansException {
  return doGetBean(name, null, args, false);
}


没错,从BeanFactory获取bean,其核心代码最终如下,正是createBean

if (mbd.isSingleton()) {
  sharedInstance = getSingleton(beanName, () -> {
    try {
      return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
      // Explicitly remove instance from singleton cache: It might have been put there
      // eagerly by the creation process, to allow for circular reference resolution.
      // Also remove any beans that received a temporary reference to the bean.
      destroySingleton(beanName);
      throw ex;
    }
  });
  bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

所以思路这不就来了,我们可以在A接口实现类bean实例化时,进行B接口getBean,当然getBean只是返回单个,我们需要All

这样就可以先去创建B接口的所有bean了,那么怎么getAllBean呢?

ApplicationContextUtil.context.getBeansOfType()

上面那行代码相信不用我多说吧,根据Type获取所有bean,源码一看是ListableBeanFactory提供的方法

image-20230527152417235

再瞅一瞅,是否满足期望,可以看到源码中是获取到Type的所有beanName,然后遍历进行getBean

public <T> Map<String, T> getBeansOfType(
  @Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException {

  // 获取目标Type的所有beanName
  String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
  Map<String, T> result = new LinkedHashMap<>(beanNames.length);
  for (String beanName : beanNames) {
    try {
      // 遍历进行getBean
      Object beanInstance = getBean(beanName);
      if (!(beanInstance instanceof NullBean)) {
        result.put(beanName, (T) beanInstance);
      }
    }
    catch (BeanCreationException ex) {
      // ......省略
    }
  }
  return result;
}

@Override
public Object getBean(String name) throws BeansException {
  return doGetBean(name, null, null, false);
}

所以写出最终代码

@Component
public class ExtendDependsOnByType extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {

  private ListableBeanFactory listableBeanFactory;

  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    if (beanFactory instanceof ListableBeanFactory) {
      this.listableBeanFactory = (ListableBeanFactory) beanFactory;
    }
  }

  @Override
  public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
    if (TestA.class.isAssignableFrom(beanClass)) {
      listableBeanFactory.getBeansOfType(TestB.class);
    }
    return null;
  }

}

结尾

最终写出满足目标期望的代码也是也就十几行,也不是什么高超的设计,无非就是利用Spring预留的扩展接口来完成‘骚操作’

比较满意的是我并没有妥协使用@DependsOn来完成目标期望,为了以后的扩展付出了一定的时间与精力,不过很好,最终完美达到了我的期望~

最后,我是 Code皮皮虾 ,会在以后的日子里跟大家一起学习,一起进步!

觉得文章不错的话,求一个点赞~,更多精彩内容尽在->JavaCodes