Получить родительский компонент в прототипе, который вводится - PullRequest
1 голос
/ 21 октября 2010

Мне бы хотелось, чтобы Бин и SubBean были такими:

@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class SubBean implements ApplicationContextAware{
  private Object parent;
  public void setApplicationContext(ApplicationContext ctx){
    this.parent = doSomeMagicToGetMyParent(ctx);
  }
  public Object getParent(){ 
    return parent; 
  }
}

@Component
public class SomeBean implements InitializingBean{
  @Resource
  private SubBean sub;

  public void afterPropertiesSet(){
    Assert.isTrue(this == sub.getParent());
  }
}

Трюк, которого я хочу достичь, заключается в том, что SubBean автоматически получает ссылку на Бин, в который он был введен.Поскольку область действия дочернего объекта является прототипом, он будет вставлен как новый экземпляр в каждый родительский объект, который хочет, чтобы его внедрили.

Моя большая идея состоит в том, чтобы использовать этот шаблон для написания LoggerBean, который можно вставить внормальные бобы.Подсолнечник должен работать точно так же, как SLF4J Logger.

Так кто-нибудь знает магию, чтобы заставить эту работу?:)


РЕДАКТИРОВАТЬ: Я нашел решение сделать это с помощью пользовательского BeanPostProcessor:

@Component
public class DependencyInjectionAwareBeanPostProcessor implements BeanPostProcessor {
  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) {
    return bean;
  }
  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) {
    for (Field f : bean.getClass().getFields()) {
        if (f.getType().isInstance(IDependencyInjectionAware.class)) {
            ReflectionUtils.makeAccessible(f);
            try {
                IDependencyInjectionAware diAware = (IDependencyInjectionAware) f.get(bean);
                diAware.injectedInto(bean);
            } catch (IllegalArgumentException e) {
                ReflectionUtils.handleReflectionException(e);
            } catch (IllegalAccessException e) {
                ReflectionUtils.handleReflectionException(e);
            }
        }
    }
    return bean;
  }
}

Вот интерфейс:

public interface IDependencyInjectionAware {
  void injectedInto(Object parent);
}

А вот Бин, использующий его:

@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class SomeAwareBean implements IDependencyInjectionAware {
  private Object parent;
  public void injectedInto(Object parent){
    this.parent = parent;
  }
  public Object getParent(){
    return parent;
  }
}

Здесь тест с обычным Бином, который отлично работает:

@Component 
public class UsingBean implements InitializingBean {
  @Resource
  private SomeAwareBean b;
  public void afterPropertiesSet(){
    Assert.notNull(b); //works
    Assert.isTrue(b.getParent() == this); //works
  }
}

Хотя, при использовании того же сВ обычном классе, который получает зависимости, введенные с помощью @Configurable, тест не пройден:

@Configurable
public class UsingPlainClass implements InitializingBean {
  @Resource
  private SomeAwareBean b;
  public void afterPropertiesSet(){
    Assert.notNull(b); //works
    Assert.isTrue(b.getParent() == this); //fails because null is returned
  }
}

Так что, похоже, у меня возник другой вопрос: почему мой пользовательский BeanPostProcessor не работает на классах @Configurable?Может быть, мне придется прибегнуть к AspectJ в конце концов ...


РЕДАКТИРОВАТЬ: Просто чтобы обновить статус.Я не реализовал это в конце концов, потому что это - сверхинжиниринг ...

Ответы [ 2 ]

0 голосов
/ 05 ноября 2013

Исправлено несколько ошибок с решением, данным оригинальным постером:

import java.lang.reflect.Field;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;

public interface DependencyInjectionAware {

void injectedInto(final Object bean, final String beanName);

public static class DependencyInjectionAwareBeanPostProcessor implements
        BeanPostProcessor {

    private static final Logger logger =   Logger.getLogger(DependencyInjectionAwareBeanPostProcessor.class);

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

    @Override
    public Object postProcessAfterInitialization(final Object bean,
            final String beanName) {
        for (final Field f : bean.getClass().getDeclaredFields()) {
            logger.info("scanning field " + f.getName() + " of bean " + beanName + " (class= " + bean.getClass() + ")");

            if (DependencyInjectionAware.class.isAssignableFrom(f.getType())) {
                ReflectionUtils.makeAccessible(f);
                try {
                    final DependencyInjectionAware diAware = (DependencyInjectionAware) f.get(bean);
                    diAware.injectedInto(bean, beanName);
                } catch (final IllegalArgumentException e) {
                    ReflectionUtils.handleReflectionException(e);
                } catch (final IllegalAccessException e) {
                    ReflectionUtils.handleReflectionException(e);
                }                   
            }
        }
        return bean;
    }
}

}

0 голосов
/ 25 ноября 2010

Я считаю это проще:

@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class SubBean implements ApplicationContextAware{
  private Object parent;
  public void setApplicationContext(ApplicationContext ctx){
    ...
  }
  public Object getParent(){ 
    return parent; 
  }
  //ADDED CODE
  public void setParent(Object parent) {
    this.parent = parent;
  }
  //END ADDED CODE
}

@Component
public class SomeBean implements InitializingBean{

  private SubBean sub;
  //ADDED CODE
  @Resource
  public void setSub(SubBean sub) {
      this.sub = sub;
      sub.setParent(this);
  }
  //END ADDED CODE

  public void afterPropertiesSet(){
    Assert.isTrue(this == sub.getParent());
  }
}
...