在camel应用程序中使用自定义注册表

时间:2015-07-24 07:35:07

标签: apache-camel

我正在实施通用消息路由器。我的消息路由器应该能够使用在运行时配置的不同JMS和数据库资源。

我对JMS没有任何问题,因为我可以在运行时动态创建和添加JmsComponent个实例。

我的问题是添加数据源。我能够从java代码创建它们,用他们实际获得的事务管理器测试来装饰它们等等。但是我坚持将它们添加到注册表中,以便jdbc:myDataSource端点能够正确解析。 / p>

有点想法,谷歌把我带走,我试图实现一个接受Registry方法的自定义registerBean(String key, Object bean)。为了最大限度地减少我的工作,我将骆驼从CompositeRegistry框中扩展出来,如下所示。

/**
 * Custom implementation of a camel registry to facilitate
 * adding beans at run time..
 */
public class MessageRouterRegistry implements Registry {
    @Autowired
    private ApplicationContext messageRouterContext;

    private Registry springContextRegistry;
    private final SimpleRegistry simpleRegistry;

    public MessageRouterRegistry() {
        simpleRegistry = new SimpleRegistry();
    }

    @PostConstruct
    public void initSpringRegistry() {
        springContextRegistry = new ApplicationContextRegistry(messageRouterContext);
    }

    public void registerBean(String key, Object bean) {
        simpleRegistry.put(key, bean);
    }

    @Override
    public Object lookupByName(String name) {
        return  new CompositeRegistry(asList(simpleRegistry, springContextRegistry)).lookupByName(name);
    }

    @Override
    public <T> T lookupByNameAndType(String name, Class<T> type) {
        return new CompositeRegistry(asList(simpleRegistry, springContextRegistry)).lookupByNameAndType(name, type);
    }

    // The other methods follow the same pattern
}

我还有一个单元测试(Test-NG)除了连接所有spring应用程序上下文并检查bean是否都可访问之外什么都不做。

@ContextConfiguration(locations = {"classpath:META-INF/spring/camel-context.xml"})
@TestExecutionListeners(CustomTestExecutionListener.class)
public class CamelContextStartupTest extends AbstractTestNGSpringContextTests {

    @Test
    public void shouldBeAbleToLoadCamelContext() throws Exception {
        assertThat(applicationContext.getBean("message-router"), instanceOf(CamelContext.class));
    }
}

此测试运行正常,我可以在日志中看到正确创建的所有路由。请注意,自定义测试侦听器除了在其他任何事情发生之前设置一些预期的系统属性之外没有做任何其他事情。

然而,当我运行正确加载相同的弹簧上下文文件的东西时,我在下面得到了这个例外:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'message-router': Invocation of init method failed; nested exception is java.lang.IllegalStateException: org.springframework.context.support.GenericApplicationContext@cecf639 has not been refreshed yet
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:293)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
    at au.com.nab.message.router.running.RouterRunner.createApplicationContext(RouterRunner.java:25)
    at au.com.nab.message.router.running.RouterRunner.start(RouterRunner.java:38)
    at au.com.nab.message.router.running.RouterRunner.main(RouterRunner.java:18)
Caused by: java.lang.IllegalStateException: org.springframework.context.support.GenericApplicationContext@cecf639 has not been refreshed yet

我的直觉是,显然我做的不对,但我无法发现什么。异常消息很安静;我正在尝试从上下文中访问bean,因为它们还没有准备好使用但是我找不到解决这个问题的方法,也许因为我的方法错误而无法修复。

所以这是我的问题:

  1. 在运行时以任何方式将bean添加到注册表中?

  2. 自定义注册表是如何工作的,或者在开始使用之前有更好的方法来获取spring应用程序上下文的引用?

  3. CompositeRegistry类是为某种类型的骆驼内部用法而创建的,还是专为公共用途而设计的。我知道它是公开的,但它最有趣的方法getRegistryList是包范围。

  4. 提前感谢您的意见。

1 个答案:

答案 0 :(得分:2)

这就是我设法在我的camel应用程序中使用自定义注册表并使其能够在运行时绑定bean的方法。这个解决方案有点复杂,我相信如果camel框架本身将registerBean(String key, Object bean)方法添加到他们的Registry接口,那将会很棒。

/**
 * Custom implementation of a camel registry to facilitate
 * adding beans at run time. Nothing else then a wrapper
 * around a camel SimpleRegistry instance.
 */
public class MessageRouterRegistry implements Registry {

    private final SimpleRegistry simpleRegistry;

    public MessageRouterRegistry() {
        simpleRegistry = new SimpleRegistry();
        REGISTRY.setRegistry(this);
    }

    public void registerBean(String key, Object bean) {
        simpleRegistry.put(key, bean);
    }

    @Override
    public Object lookupByName(String beanName) {
        return  simpleRegistry.lookupByName(beanName);
    }
    // All the other methods follow the same delefdate pattern
    ...
}

在上面的Registry实现中唯一需要注意的是,在完成构建之后,注册表将自己注册为REGISTRY单例。正如您将在稍后看到的那样,在完全创建camel上下文之前需要掌握此Registry实例。单例实现只是一个简单的枚举。

/**
 * Message Router Registry holder. 
 */
public enum RegistryHolderSingleton {
    REGISTRY;

    private MessageRouterRegistry registry;

    public void setRegistry(MessageRouterRegistry registry) {
        this.registry = registry;
    }

    public void registerBean(String beanName, Object bean) {
        registry.registerBean(beanName, bean);
    }
}

为了在camel-context.xml中使用自定义注册表,我添加了以下条目。注意两个PostProcessor bean实际上会做的伎俩。

<camel:camelContext id="message-router">
    <camel:propertyPlaceholder location="router.properties" id="router.properties"/>
    <camel:routeBuilder ref="routesBuilder"/>
</camel:camelContext>

<bean id="routerRegistry" class="au.com.nab.message.router.registry.MessageRouterRegistry"/>
<bean class="au.com.nab.message.router.registry.RouterBeanFactoryPostProcessor"/>
<bean class="au.com.nab.message.router.registry.RouterBeanPostProcessor"/>

RouterBeanFactoryPostProcessor用于破解spring应用程序上下文,并确保我的自定义MessageRouterRegistry是第一个通过将所有bean定义更改为依赖于此实例化来实例化的bean。我本来可以调用beanFactory.getBean(ROUTER_REGISTRY)但是这反对spring建议不在bean工厂后处理器中实例化bean,所以这就是我最终得到这个相当不寻常的实现的方式。但是,调用getBean(...)对我有用,没有明显的副作用。

/**
 * Custom bean factory post processor to facilitate instantiating
 * router registry bean before any other beans.
 */
public class RouterBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    private static final String ROUTER_REGISTRY = "routerRegistry";

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException {
        // For most of the beans (see rules below) alter their definition
        // so they will be instantiated after the "routerRegistry" bean 
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            if (okToSetAsDependant(beanDefinition)) {
                beanDefinition.setDependsOn(ROUTER_REGISTRY);
            }
        }
    }

    private boolean okToSetAsDependant(BeanDefinition beanDefinition) {
         // Do not set the bean dependency if:
         // 1. An internal Spring used bean
         // 2. Is an instance of this post processor class as this is already created
         // 3. Is our custom message router registry as this is the one we want to create first
         // 4. Has dependencies defined already as these ones will not be created first anyway
         return !((beanDefinition instanceof RootBeanDefinition)
                 || beanDefinition.getBeanClassName().equals(getClass().getName())
                 || beanDefinition.getBeanClassName().equals(MessageRouterRegistry.class.getName())
                 || beanDefinition.getDependsOn() != null);
    }
}

最后,RouterBeanPostProcessor将使用自定义Registry

注册每个spring bean
/**
 * Custom bean post processor to put all spring beans into the
 * custom camel registry.
 */
public class RouterBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (!(bean instanceof MessageRouterRegistry)) {
            REGISTRY.registerBean(beanName, bean);
        }
        return bean;
    }
}

完成此操作后,我可以在运行时在自定义Registry中添加bean。还有一点需要注意的是,camel还会将自定义注册表包装在PropertyPlaceholderDelegateRegistry实例中,以便访问您的自定义注册表,您将在代码中进行不太好的转换或从单例实例中获取它。

在发布此代码时,此答案中的代码在我的所有单元测试中都能正常工作,并且到目前为止在预生产测试环境中尚未完全测试并且已经准备好生产。

希望你会发现这个有用的谷歌搜索如何解决方案让我接近无处。