Весна. Решить круговую зависимость с java-конфигурацией и без @Autowired

У меня есть круговая зависимость и java-конфиг. Хотя решение с помощью xml-конфигурации очень просто, я не могу разрешить его с помощью java-конфигурации без @Autowired. Фасоль:

public class A {
    private B b;

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

Я пробовал это (я прочитал, что с аннотацией @Bean Spring не будет вызывать метод каждый раз, когда bean ссылается, но в этом случае он фактически вызывается все время):

@Configuration
public class Config {
    @Bean
    public A a() {
        A a = new A();
        a.setB(b());
        return a;
    }

    @Bean
    public B b() {
        B b = new B();
        b.setA(a());
        return b;
    }
}

И это, с @Autowired из полей класса Configuration:

@Configuration
public class Config {
    @Autowired
    A a;
    @Autowired
    B b;

    @Bean
    public A a() {
        A a = new A();
        a.setB(b);
        return a;
    }

    @Bean
    public B b() {
        B b = new B();
        b.setA(a);
        return b;
    }
}

Также я пробовал все выше с аннотацией @Lazy. Не помогает. Но работает отлично, если я комментирую сеттеры A и B с @Autowired. Но это не то, что я хочу сейчас. Что я делаю неправильно и есть ли способ разрешить зависимость Circular в java-конфиге без использования @Autowired?

Ответы

Ответ 1

Поведение, которое вы хотите получить, следующее

A a = new A();
B b = new B();
a.setB(b);
b.setA(a);

Методы @Bean не дают вам этого. Они заканчиваются, чтобы предоставить экземпляр компонента.

Вам в основном нужно частично создать один из экземпляров, а затем завершить его инициализацию, когда вы создали другую.

@Configuration
class Config {
    @Bean
    public A a() {
        A a = new A();
        return a;
    }

    @Bean
    public B b() {
        B b = new B();
        A a = a();
        b.setA(a);
        a.setB(b);
        return b;
    }
}

или

@Bean
public B b(A a) {
    B b = new B();
    b.setA(a);
    a.setB(b);
    return b;
}

Ответ 2

Другой подход, использующий @Autowired и @Component - использовать этот шаблон:

@Component
class A {
    private B b;

    public B getB() {
        return b;
    }

    public void setB(final B b) {
        this.b = b;
    }
}


@Component
class B {
    private final A a;

    @Autowired
    public B(final A a) {
        this.a = a;
        a.setB(this);
    }

    public A getA() {
        return a;
    }
}

Это устраняет необходимость в отдельной @Configuration -class. Кроме того, setB -method может быть защищен от пакетов, если классы существуют в одном пакете, чтобы максимально минимизировать область охвата.

Ответ 3

Я хочу добавить еще одно возможное решение для вашего кода. Вместо установки круговых зависимостей прямо в config:

@Configuration
public class Config {
    @Bean
    public A a() {
        A a = new A();
        a.setB(b());
        return a;
    }

    @Bean
    public B b() {
        B b = new B();
        b.setA(a());
        return b;
    }
}

Вы также можете позволить весне выполнять работу с помощью @Autowired аннотации.

@Configuration
public class Config {
    @Bean
    public A a() {
        A a = new A();
        return a;
    }

    @Bean
    public B b() {
        B b = new B();
        return b;
    }
}

public class A {
    private B b;

    @Autowired
    public setB(B b) { this.b = b; }
}

public class B {
    private A a;

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

Конечно, это нетривиально из "чистой/удобочитаемой/понятной" точки зрения, потому что теперь ваша конфигурация смешивается в @Configuration и самом классе. Но поскольку круговые зависимости встречаются довольно редко, мы можем позволить себе взломать.