简体   繁体   English

动态注入 JDK 动态代理作为 spring bean - 但前提是没有其他实现可用

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

I have a situation where I want to look for interfaces annotated with a given annotation, then check if a matching implementation is available.我有一种情况,我想查找带有给定注释的接口,然后检查匹配的实现是否可用。 If not, I want to make a bean available that is actually a JDK proxy of the interface, essentially exactly what:如果没有,我想提供一个实际上是接口的 JDK 代理的 bean,本质上是什么:

@ConditionalOnMissingBean 

would do, except without writing a factory method for each of those.会做,除非没有为每一个编写工厂方法。

I have code that is working "sometimes" - it seems extremely sensitive to the classloader structure, specifically, wether classes are loaded from a springboot fat jar (works) or wether some part is loaded from a separate classpath entry (doesnt work, mostly).我有“有时”工作的代码 - 它似乎对类加载器结构非常敏感,特别是,类是从 springboot fat jar 加载(有效)还是某些部分是从单独的类路径条目加载的(大多数情况下不起作用) .

here is what I am doing:这就是我正在做的事情:

@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");

        }

in some cases, it seems that the beanfactory isn't finished, and在某些情况下,beanfactory 似乎还没有完成,并且

  beanFactory.getBeanNamesForType() 

returns an empty list, although it does find beans for that type later.返回一个空列表,尽管它稍后会找到该类型的 bean。 i am aware that messing with this is probably not ideal - but it would be nice to find a solution that plays nice with spring boot.我知道搞砸这可能并不理想——但如果能找到一个与 spring 引导配合得很好的解决方案会很好。

any suggestions on how to solve this?关于如何解决这个问题的任何建议? a way to mark a bean definition as "ConditionalOnMissingBean" would also be great.将 bean 定义标记为“ConditionalOnMissingBean”的方法也很棒。

You should use BeanPostProcessor instead of BeanFactoryPostProcessor您应该使用BeanPostProcessor而不是BeanFactoryPostProcessor

BeanPostProcessor is operating by assembled beans, while BeanFactoryPostProcessor uses raw bean definitions. BeanPostProcessor由组装的 bean 操作,而BeanFactoryPostProcessor使用原始 bean 定义。 Read more here 在这里阅读更多

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;
}

UPD:升级版:

First of all, I have to say about disadvantages of your impl (IMHO): - you are trying to scan classpath to find all interfaces with @RemoteEndpoint annotation, and, if current application context doesn't contain a bean that implemented this interface - create a proxy bean.首先,我不得不说一下您的 impl 的缺点(恕我直言):-您正在尝试扫描类路径以查找带有@RemoteEndpoint注释的所有接口,并且,如果当前应用程序上下文不包含实现此接口的 bean-创建一个代理bean。 But what if I say, that not all interfaces marked with @RemoteEndpoint should be taken into account?但是,如果我说,并非所有标有@RemoteEndpoint的接口都应该考虑在内呢? Developer should explicitly mark those interfaces, and then you can create all needed beans (for example, developer makes a common-services library).开发人员应该明确标记这些接口,然后您可以创建所有需要的 bean(例如,开发人员制作了一个公共服务库)。 - probably you are specifying redudnant information, when marking impl class with @RemotingEndpoint(value=RandomService.class) annotation. - 在使用@RemotingEndpoint(value=RandomService.class)注释标记 impl class 时,您可能正在指定冗余信息。 You are already mentioned that when you implemented an interface.当你实现一个接口时,你已经提到过。

There are multiple ways for implementing your idea有多种方法可以实现您的想法

  1. Using custom annotation on bean fields instead of @Autowired .在 bean 字段上使用自定义注释而不是@Autowired

    pros:优点:

    • simply check all bean fields for presence of your custom annotation, and inject dependency (proxy or impl), using BeanPostProcessor .只需检查所有 bean 字段是否存在您的自定义注释,并使用BeanPostProcessor注入依赖项(代理或 impl)。

    cons:缺点:

    • developer should mark all bean dependencies with custom annotation;开发人员应使用自定义注释标记所有 bean 依赖项;
    • it won't work if developer would have to obtain new dependencies at runtime.如果开发人员必须在运行时获取新的依赖项,它将无法工作。
  2. Using regular @Autowired and @Value annotations for injecting remote service proxy In this case you should use BeanFactoryPostProcessor (as you already tried).使用常规@Autowired@Value注释来注入远程服务代理在这种情况下,您应该使用BeanFactoryPostProcessor (正如您已经尝试过的那样)。 You'll have to iterate over all bean definitions, collect a map of field names and interfaces of remote service proxies you'll have to register (dependencies meta info).您必须遍历所有 bean 定义,收集字段名称的 map 您必须注册的远程服务代理的接口(依赖项元信息)。 And next step is creating and registering beans using dependencies meta info (creating new ones only if there is no implementation bean in context).下一步是使用依赖元信息创建和注册 bean(仅当上下文中没有实现 bean 时才创建新的)。 Spring will autowire those fields later. Spring 稍后将自动连接这些字段。 But, those dependencies should be singleton beans.但是,这些依赖项应该是 singleton bean。

    pros:优点:

    • no mess with custom annotations;自定义注释没有混乱;
    • works at runtime在运行时工作

    cons:缺点:

    • proxies are singletons (you need prototypes as diagram displayed).代理是单例(您需要显示图表中的原型)。
  3. Still using regular @Autowired and @Value annotations and BeanFactoryPostProcessor , but instead of registering new beans, you should register a FactoryBean for each interface @RemoteEndpoint .仍然使用常规的@Autowired@Value注释以及BeanFactoryPostProcessor ,但是您应该为每个接口@RemoteEndpoint注册一个FactoryBean ,而不是注册新的 bean。

    pros:优点:

    • no mess with custom annotations.自定义注释没有混乱。
    • works at runtime在运行时工作
    • prototype-scoped proxies原型范围的代理

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM