簡體   English   中英

動態注入 JDK 動態代理作為 spring bean - 但前提是沒有其他實現可用

[英]Dynamically injecting a JDK dynamic proxy as spring bean - but only if no other implementation is available

我有一種情況,我想查找帶有給定注釋的接口,然后檢查匹配的實現是否可用。 如果沒有,我想提供一個實際上是接口的 JDK 代理的 bean,本質上是什么:

@ConditionalOnMissingBean 

會做,除非沒有為每一個編寫工廠方法。

我有“有時”工作的代碼 - 它似乎對類加載器結構非常敏感,特別是,類是從 springboot fat jar 加載(有效)還是某些部分是從單獨的類路徑條目加載的(大多數情況下不起作用) .

這就是我正在做的事情:

@Service
@Order(value = Ordered.LOWEST_PRECEDENCE)
public class RemotingImportService implements BeanFactoryPostProcessor {

    private static Log log = LogFactory.getLog(RemotingExportService.class);

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {


         // scan classpath with classgraph, then:
        for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(MyAnnotation.class.getCanonicalName())) {

            Class c = Class.forName(classInfo.getName());

            if(beanFactory.getBeanNamesForType(c).length > 0) {
                implFound = true;
                log.info(c.getName()+" already has an implementation ... skipping");
                continue;
            }

            // create proxy, then:

            GenericBeanDefinition bdService = new GenericBeanDefinition();
            bdService.setBeanClassName(classInfo.getName());
            bdService.setInstanceSupplier(new ProxySupplier(proxy));
            bdService.setLazyInit(true);
            ((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(classInfo.getName(), bdService);              
            log.info(c.getName()+" has NO implementation ... proxy registerd");

        }

在某些情況下,beanfactory 似乎還沒有完成,並且

  beanFactory.getBeanNamesForType() 

返回一個空列表,盡管它稍后會找到該類型的 bean。 我知道搞砸這可能並不理想——但如果能找到一個與 spring 引導配合得很好的解決方案會很好。

關於如何解決這個問題的任何建議? 將 bean 定義標記為“ConditionalOnMissingBean”的方法也很棒。

您應該使用BeanPostProcessor而不是BeanFactoryPostProcessor

BeanPostProcessor由組裝的 bean 操作,而BeanFactoryPostProcessor使用原始 bean 定義。 在這里閱讀更多

public class ConditionalOnMissingProcessor implements BeanPostProcessor, Ordered, ApplicationContextAware
{
private static final Logger LOG = Logger.getLogger(ConditionalOnMissingProcessor .class);

private ApplicationContext applicationContext;

// Ordering to last in chain.
@Override
public int getOrder()
{
    return Ordered.LOWEST_PRECEDENCE;
}

@Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException
{
    this.applicationContext = applicationContext;
}

/**
 * Process each bean and inject proxy objects in fields marked with: {@ConditionalOnMissingBean}
 */
@Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException
{
    LOG.debug("Processing bean: " + beanName);
    final Class clazz = bean.getClass();
    ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback()
    {
        @Override
        public void doWith(final Field field)
        {
            try
            {
                if(field.isAnnotationPresent(ConditionalOnMissingBean.class))
                {
                    ReflectionUtils.makeAccessible(field);
                     final String beanFieldName = field.getName();
                    ...
                    // Find proper implementation in application context
                    // Or create a proxy.
                    // Inject proxy or impl into a bean
                    field.set(bean, <value>));

                }
            }
            catch(IllegalAccessException e)
            {
                LOG.error("Cannot set " + field.getName() + " in " + beanName, e);
            }
        }
    });

    return bean;
}

升級版:

首先,我不得不說一下您的 impl 的缺點(恕我直言):-您正在嘗試掃描類路徑以查找帶有@RemoteEndpoint注釋的所有接口,並且,如果當前應用程序上下文不包含實現此接口的 bean-創建一個代理bean。 但是,如果我說,並非所有標有@RemoteEndpoint的接口都應該考慮在內呢? 開發人員應該明確標記這些接口,然后您可以創建所有需要的 bean(例如,開發人員制作了一個公共服務庫)。 - 在使用@RemotingEndpoint(value=RandomService.class)注釋標記 impl class 時,您可能正在指定冗余信息。 當你實現一個接口時,你已經提到過。

有多種方法可以實現您的想法

  1. 在 bean 字段上使用自定義注釋而不是@Autowired

    優點:

    • 只需檢查所有 bean 字段是否存在您的自定義注釋,並使用BeanPostProcessor注入依賴項(代理或 impl)。

    缺點:

    • 開發人員應使用自定義注釋標記所有 bean 依賴項;
    • 如果開發人員必須在運行時獲取新的依賴項,它將無法工作。
  2. 使用常規@Autowired@Value注釋來注入遠程服務代理在這種情況下,您應該使用BeanFactoryPostProcessor (正如您已經嘗試過的那樣)。 您必須遍歷所有 bean 定義,收集字段名稱的 map 您必須注冊的遠程服務代理的接口(依賴項元信息)。 下一步是使用依賴元信息創建和注冊 bean(僅當上下文中沒有實現 bean 時才創建新的)。 Spring 稍后將自動連接這些字段。 但是,這些依賴項應該是 singleton bean。

    優點:

    • 自定義注釋沒有混亂;
    • 在運行時工作

    缺點:

    • 代理是單例(您需要顯示圖表中的原型)。
  3. 仍然使用常規的@Autowired@Value注釋以及BeanFactoryPostProcessor ,但是您應該為每個接口@RemoteEndpoint注冊一個FactoryBean ,而不是注冊新的 bean。

    優點:

    • 自定義注釋沒有混亂。
    • 在運行時工作
    • 原型范圍的代理

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM