Весна - отбор кандидатов для инъекций в соответствии с назначенной средой - PullRequest
0 голосов
/ 22 сентября 2011

Редактировать: Возможно, более краткий способ задать этот вопрос: Предоставляет ли Spring способ для меня разрешить неоднозначные кандидаты во время внедрения, предоставляя мою собственную логику слушателя / фабрики / решения?


На самом деле, возможно, что квалификатор @Environmental в поле члена ниже не нужен: если @ Inject-ion неоднозначен ... позвольте мне помочь?Фактически, @ResolveWith (EnvironmentalResolver.class) тоже будет в порядке ..


Когда Spring пытается внедрить зависимость (используя аннотации), я понимаю, что мне нужно @Qualifier в точке @Inject, если яЯ должен иметь несколько компонентов, которые реализуют этот интерфейс.

Я хотел бы сделать что-то вроде этого:

</p> <pre><code>class MyFoo implements Foo { @Inject @Environmental private Bar bar; } @Environmental(Environment.Production) class ProductionBar implements Bar { } @Environmental({Environment.Dev, Environment.Test}) class DevAndTestBar implements Bar { }

Я бы ожидал, что мне нужно будет создать какой-нибудь определитель неоднозначности, который бы выглядел (смутно) примерно так:

</p> <pre><code>class EnvironmentalBeanAmbiguityResolver { // set from configuration, read as a system environment variable, etc. private Environment currentEnvironment; public boolean canResolve(Object beanDefinition) { // true if definition has the @Environmental annotation on it } public Object resolve(Collection<Object> beans) { for (Object bean : beans) { // return bean if bean @Environmental.values[] contains currentEnvironment } throw new RuntimeException(...); } }

Oneпример того, где это было бы полезно, у нас есть сервис, который связывается с конечными пользователями.Прямо сейчас у меня есть только взломанный AOP-аспект, который перед вызовом метода для «MailSender» проверяет флаг среды «Production» и, если он не установлен, отправляет нам электронное письмо вместо электронного письма пользователя.Я хотел бы вместо того, чтобы обернуть это в аспект AOP, специфичный для отправки почты, вместо этого иметь возможность дифференцировать сервисы на основе текущей среды. Иногда это просто вопрос «производства» или «не производства», как я продемонстрировал выше,но определение для каждой среды тоже работает.

Я думаю, что это можно повторно использовать и для региона ... например, @Regional и @Regional (Region.UnitedStates) и т. д. и т. д.

Я бы подумал, что @Environmental на самом деле был бы @Qualifier таким образом, если бы вы хотели напрямую зависеть от того, что вы могли бы сделать (бин @Environmental (Production), вероятно, напрямую зависел бы от соавтора @Environmental (Production) - так что никакой двусмысленностидля предметов более низкого уровня --- то же самое для @Regional (US) будет зависеть от других @Regional (US) товары явно и обойдут мой пока непонятный BeanAmbiguityResolver)

Спасибо.

1 Ответ

0 голосов
/ 23 сентября 2011

Я думаю, что решил это!

Обратите внимание на следующее:

public interface Ambiguity {
    public boolean isSatisfiedBy(BeanDefinitionHolder holder);
}

@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
public @interface Ambiguous {
    Class<? extends Ambiguity> value();
}

@Target(TYPE)
@Retention(RUNTIME)
public @interface Environmental {
    public static enum Environment {
        Development, Testing, Production
    };
    Environment[] value() default {};
}

@Named
public class EnvironmentalAmbiguity implements Ambiguity {

    /* This can be set via a property in applicationContext.xml, which Spring
       can use place holder, environment variable, etc. */
    Environment env = Environment.Development;

    @Override
    public boolean isSatisfiedBy(BeanDefinitionHolder holder) {
        BeanDefinition bd = holder.getBeanDefinition();
        RootBeanDefinition rbd = (RootBeanDefinition) bd;

        Class<?> bc = rbd.getBeanClass();

        Environmental env = bc.getAnnotation(Environmental.class);

        return (env == null) ? false : hasCorrectValue(env);
    }

    private boolean hasCorrectValue(Environmental e) {
        for (Environment env : e.value()) {
            if (env.equals(this.env)) {
                return true;
            }
        }
        return false;
    }

}

@Named
public class MySuperDuperBeanFactoryPostProcessor implements
        BeanFactoryPostProcessor, AutowireCandidateResolver {

    private DefaultListableBeanFactory beanFactory;
    private AutowireCandidateResolver defaultResolver;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory arg)
            throws BeansException {
        if (arg instanceof DefaultListableBeanFactory) {
            beanFactory = (DefaultListableBeanFactory) arg;

            defaultResolver = beanFactory.getAutowireCandidateResolver();
            beanFactory.setAutowireCandidateResolver(this);

            return;
        }

        throw new FatalBeanException(
                "BeanFactory was not a DefaultListableBeanFactory");
    }

    @Override
    public Object getSuggestedValue(DependencyDescriptor descriptor) {
        return defaultResolver.getSuggestedValue(descriptor);
    }

    @Override
    public boolean isAutowireCandidate(BeanDefinitionHolder holder,
            DependencyDescriptor descriptor) {
        Ambiguity ambiguity = getAmbiguity(descriptor);

        if (ambiguity == null) {
            return defaultResolver.isAutowireCandidate(holder, descriptor);
        }
        return ambiguity.isSatisfiedBy(holder);
    }

    private Ambiguity getAmbiguity(DependencyDescriptor descriptor) {
        Ambiguous ambiguous = getAmbiguousAnnotation(descriptor);

        if (ambiguous == null) {
            return null;
        }

        Class<? extends Ambiguity> ambiguityClass = ambiguous.value();
        return beanFactory.getBean(ambiguityClass);
    }

    private Ambiguous getAmbiguousAnnotation(DependencyDescriptor descriptor) {
        Field field = descriptor.getField();

        if (field == null) {
            MethodParameter methodParameter = descriptor.getMethodParameter();

            if (methodParameter == null) {
                return null;
            }
            return methodParameter.getParameterAnnotation(Ambiguous.class);
        }
        return field.getAnnotation(Ambiguous.class);
    }

}

Теперь, если у меня есть интерфейс MyInterface и два класса, которые его реализуют, MyFooInterface и MyBarInterface примерно так:

public interface MyInterface {
    public String getMessage();
}

@Named
@Environmental({ Environment.Testing, Environment.Production })
public class MyTestProdInterface implements MyInterface {
    @Override
    public String getMessage() {
        return "I don't always test my code, but when I do, I do it in production!";
    }
}

@Named
@Environmental(Environment.Development)
public class DevelopmentMyInterface implements MyInterface {
    @Override
    public String getMessage() {
        return "Developers, developers, developers, developers!";
    }
}

Если я захочу @Inject MyInterface, я получу ту же ошибку определения множества бинов, что и следовало ожидать. Но я могу добавить @Ambiguous (EnvironmentalAmbiguity.class), и тогда EnvironmentalAmbiguity скажет, какому определению бина он удовлетворен.

Другой подход состоял бы в том, чтобы использовать List и просматривать их все, проверяя, удовлетворены ли они определенным определением bean-компонента, это означало бы, что зависимости не потребуется аннотация @Ambiguous. Это могло бы быть больше "IoC-ишем", но я также думал, что это могло бы работать плохо. Я не проверял это.

...