繁体   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