سؤال

Let's say I have an interface for language change event in my application (it's based on Vaadin):

public interface ILanguageChangeListener{
    @Subscribe onLanguageChange(LanguageChangeEvent event);
}

And I have many beans that implements this interface annotated with @Component, thus they are available in Spring IoC. I have also an EventBus bean:

<bean id="languageSwitcher" class="com.google.common.eventbus" scope="session" />

Now, after getting an instance of any bean from IoC I have to get also an instance of the languageSwitcher and register the newely created bean in it with:

languageSwitcher.register(myNewBean);

in order to receive this events. Is it possible to somehow tell the IoC that I want to call the register method of the languageSwitcher bean on every new bean that implements the ILanguageChangeListener?

هل كانت مفيدة؟

المحلول

OK, using a BeanPostProcessor, register every bean of your interface:

public class EventBusRegisterBeanPostProcessor implements BeanPostProcessor,
        ApplicationContextAware {

    private ApplicationContext context;

    @Autowired
    private EventBus eventBus; // The only event bus i assume...

    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {

        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {

        if (bean instanceof ILanguageChangeListener) {
            registerToEventBus(bean);
        }

        return bean;
    }

    private void registerToEventBus(Object bean) {
        this.eventBus.register(bean);
    }

    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.context = applicationContext;
    }

}

Note that if you have many EventBus beans, you should use the ApplicationContext.getBean(String) to get the EventBus you need.

I quote from the javadoc:

In case of a FactoryBean, this callback will be invoked for both the FactoryBean instance and the objects created by the FactoryBean (as of Spring 2.0). The post-processor can decide whether to apply to either the FactoryBean or created objects or both through corresponding bean instanceof FactoryBean checks.

نصائح أخرى

IMO, it is even better (less coupling), instead of implementing a marker interface, to use a class level annotation to mark beans that should be registered. Here is the modified bean post processor code :

public class EventBusListenersRegistererBeanPostProcessor implements BeanPostProcessor{

    Logger log = LoggerFactory.getLogger(this.getClass());

    @Inject
    private EventBus bus;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean.getClass().isAnnotationPresent(RegisterWithEventBus.class)){
            log.info("Event Bus is registering bean named \"{}\" of class {}.", beanName, bean.getClass().getCanonicalName());
            bus.register(bean);
        }

        return bean;
    }
}

And the annotation :

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited // important when working with dynamically generated proxies i.e. CGLib
public @interface RegisterWithEventBus {}

Note that the annotation interface has the @Inherited meta-annotation. This is necessary in a Spring app that uyses CGLIB proxies, since the annotation will not be on the actual (dynamic) class of the object, but on the parent class.

Use a factory bean for your event bus and inject a list of all the ILanguageChangeListener beans in your context.

public class EventBusFactoryBean implements FactoryBean<EventBus> {

    @Autowired
    private List<ILanguageChangeListener> languageChangeListeners;

    private EventBus instance;

    @PostConstruct
    public void init() {

        this.instance = new EventBus();

        for (ILanguageChangeListener listener : this.languageChangeListeners) {
            this.instance.register(listener);
        }
    }

    public EventBusFactoryBean() {

    }

    public EventBus getObject() throws Exception {
        return this.instance;
    }

    public Class<?> getObjectType() {
        return EventBus.class;
    }

    public boolean isSingleton() {
        return true;
    }

    public List<ILanguageChangeListener> getLanguageChangeListeners() {
        return languageChangeListeners;
    }

    public void setLanguageChangeListeners(
            List<ILanguageChangeListener> languageChangeListeners) {
        this.languageChangeListeners = languageChangeListeners;
    }

}

And then define your bean in the Spring Bean Definition file or annotate it with @Component

One way would be using the interface InitializingBean and inject the language switcher in your bean. Something like this:

public class NotVeryUsefulLanguageListener implements ILanguageChangeListener,
        InitializingBean {

    @Autowired
    private EventBus languageSwitcher;

    public void afterPropertiesSet() throws Exception {

        this.languageSwitcher.register(this);
    }

    //... getters, setters, etc

}

If NotVeryUsefulLanguageLisetener is singleton, it will happen only once... if prototype then every time you get an instance from the Spring

If you are using XML then you can use something like this:

<bean id="languageListenerA" init-method="afterPropertiesSet" class="org.company.whatever.NotVeryUsefulLanguageListener">
        <!-- stuff -->
    </bean>
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top