Переопределение установщика Autowire с помощью Java Config

Рассмотрим следующий класс:

public class MyBean {
    private A a;

    @Autowired(required=true)
    public void setA(A a) {
        this.a = a;
    }

    public A getA() {
        return a;
    }
}

Бывают случаи, когда нужно переопределить автоматическую инъекцию, например, когда Spring не может найти ни одного кандидата на инъекцию. В XML у меня может быть следующий пример:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="first" class="my.pkg.AImpl"/>
    <bean id="second" class="my.pkg.AImpl"/>
    <bean id="myBeanFirst" class="my.pkg.MyBean">
        <property name="a" ref="first"/>
    </bean>
    <bean id="myBeanSecond" class="my.pkg.MyBean">
        <property name="a" ref="second"/>
    </bean>

</beans>

Есть ли способ сделать то же самое с Java Config? Следующее не работает (и я понимаю, почему), потому что Spring пытается автоматически связать свойство после возврата из метода myBean, и оно завершается с ошибкой NoUniqueBeanDefinitionException:

@Configuration
public class MyConfig {
    @Bean
    public A first() {
        return new AImpl();
    }

    @Bean
    public A second() {
        return new AImpl();
    }

    @Bean
    public MyBean myBeanFirst(A first) {
        MyBean myBean = new MyBean();
        myBean.setA(first);
        return myBean;
    }

    @Bean
    public MyBean myBeanSecond(A second) {
        MyBean myBean = new MyBean();
        myBean.setA(first);
        return myBean;
    }
}

Модифицировать класс MyBean не всегда возможно, например, потому что он получен из внешней библиотеки. Это тот случай, когда я должен использовать XML-конфигурацию?

Спасибо, Андреа Польчи.

Обновление Спасибо за два решения (внедрение по имени и использование @Primary), но они не решают мой вариант использования, поэтому, я думаю, мне нужно быть более конкретным.

В моем случае класс MyBean исходит из внешней библиотеки, поэтому какие-либо изменения в нем невозможны. Мне также нужно иметь более одного экземпляра MyBean, каждый из которых внедряет разные экземпляры интерфейса A. Я обновил приведенный выше код, чтобы отразить это (как xml, так и java).

Есть ли какое-либо решение с использованием конфигурации Java? Можно ли избежать автопривязки зависимости от MyBean? (Только для bean-компонентов этого класса, не отключая автопроводку полностью для каждого bean-компонента в контексте)


person Andrea Polci    schedule 14.03.2014    source источник
comment
Может ли тот, кто проголосовал за этот вопрос, объяснить, почему, пожалуйста?   -  person Andrea Polci    schedule 17.03.2014


Ответы (3)


Хорошо, вот оно, я считаю, что этот ответ удовлетворит ваши потребности.

Нам нужна реализация MergedBeanDefinitionPostProcessor, которая установит правильное значение свойства a класса MyBean. Это можно сделать с помощью следующего класса

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.stereotype.Component;


@Component
public class MyBeanPostProcessor implements MergedBeanDefinitionPostProcessor {


    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if(beanName.equals("myBeanFirst")) {
            beanDefinition.getPropertyValues().add("a", getMyBeanFirstAImpl());
        }
        else if(beanName.equals("myBeanSecond")) {
            beanDefinition.getPropertyValues().add("a", getMyBeanSecondAImpl());
        }
    }

    private Object getMyBeanFirstAImpl() {
        return new AImpl();
    }

    private Object getMyBeanSecondAImpl() {
        return new AImpl();
    }

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

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

Как вы можете видеть, имена bean-компонентов здесь жестко закодированы, но они могут быть установлены в статической финальной строке, которая также будет использоваться аннотациями @Bean в следующем коде.

@Configuration
public class Configuration {



    @Bean
    public MyBean myBeanFirst() {
        return new MyBean();
    }

    @Bean
    public MyBean myBeanSecond() {
        return new MyBean();
    }

}

В следующем коде вы заметите, что setA не вызывается в методах создания MyBean, потому что независимо от того, какое значение мы установили (или в данном случае не установили), оно будет переопределено, когда Spring запустит постпроцессор компонента.

Если вам нужно значение по умолчанию для A (например, если вы внедряете его в другие bean-компоненты), перейдите к определению @Bean в предыдущей конфигурации.

person geoand    schedule 15.03.2014
comment
Если это более простой способ сделать это с помощью конфигурации Java, я думаю, что в этом случае реальным ответом должно быть использование конфигурации xml. Спасибо то же. - person Andrea Polci; 16.03.2014
comment
Я не знаю, самый простой это или нет, могут быть лучшие ответы! Однако я знаю, что конфигурация xml работает, потому что конфигурация xml переопределяет любую предыдущую конфигурацию. - person geoand; 16.03.2014
comment
@geoand - голосование за ваш ответ, жизнеспособный подход, если спрашивающий знал, что делает. - person SergeyB; 18.03.2014
comment
@ike_love Я никогда не говорил, что это нежизнеспособно. Я имею в виду, что если мне нужно определить пользовательский постпроцессор для установки зависимости, это признак того, что я пытаюсь сделать что-то, для чего фреймворк не предназначен, и мне нужно изменить свой подход (например, использовать конфигурацию xml в таком случае). Не думайте, что я тот, кто проголосовал за этот ответ, это не так. - person Andrea Polci; 18.03.2014
comment
@geoand проголосовал и принял этот ответ, поскольку он единственный, который применим к моему варианту использования, но я изменил свой подход и отказался от попыток использовать конфигурацию Java в этом случае. - person Andrea Polci; 18.03.2014
comment
@AndreaPolci Спасибо! Этот случай, вероятно, является хорошим случаем, когда JavaConfig не помогает разработчику. - person geoand; 18.03.2014

Если у вас есть несколько кандидатов для внедрения, вы можете указать вариант по умолчанию, используя @Primary для bean-компонента, который вы хотите использовать в качестве AImpl по умолчанию. Таким образом, никаких изменений в MyBean не требуется.

person geoand    schedule 15.03.2014
comment
Также это не всегда решает проблему. Что, если мне нужно иметь два экземпляра MyBean с разными экземплярами A, вставленными в них? Что мне нужно, если он существует, так это способ воспроизвести в Java Config поведение конфигурации xml, которое я опубликовал в вопросе. - person Andrea Polci; 15.03.2014

Если вы хотите, чтобы myBeanFirst использовался первым, просто вызовите first(). То же самое для myBeanSecond, хотите, чтобы он использовал второй, а затем просто вызовите second() при установке A;

@Configuration
public class MyConfig {
    @Bean
    public A first() {
        return new AImpl();
    }

    @Bean
    public A second() {
        return new AImpl();
    }

    @Bean
    public MyBean myBeanFirst() {
        MyBean myBean = new MyBean();
        myBean.setA(first());
        return myBean;
    }

    @Bean
    public MyBean myBeanSecond() {
        MyBean myBean = new MyBean();
        myBean.setA(second());
        return myBean;
    }
}
person SergeyB    schedule 17.03.2014
comment
Это не работает, поскольку после того, как bean-компонент был создан с помощью myBeanFirst(), Spring пытается внедрить аннотированное поле @Autowire (он не знает, что оно уже установлено). Как уже было сказано, я не могу изменить MyBean, чтобы удалить аннотацию, поскольку она исходит из библиотеки externa. - person Andrea Polci; 17.03.2014
comment
Я проголосовал за этот вариант только потому, что он не работает. Я не тот, кто проголосовал за другой ответ от вас. Я очень ценю помощь, извините, если я произвел другое впечатление. Кроме того, я не жаловался на отрицательное голосование, мне просто было любопытно, в чем причина, и могу ли я что-то с этим сделать. - person Andrea Polci; 18.03.2014