Помочь вам лучше понять циклические зависимости Spring

Java

В Интернете слишком много блогов о циклических зависимостях Spring. Многие из них глубоко проанализированы и написаны с большой осторожностью. Они даже рисуют диаграммы последовательности и блок-схемы, чтобы помочь читателям понять. После прочтения я чувствую, что понимаю это, но оно закрыто Когда я открывал глаза, я всегда чувствовал, что не до конца понимаю его, и я всегда чувствовал, что есть еще одно или два препятствия, которые нужно преодолеть.Это было действительно трудно для такого глупого человека, как я. В то время я думал, что если однажды я пойму круговую зависимость Spring, я должен написать блог по-своему, чтобы помочь всем лучше понять. может Так проще понять.Буквально на днях разобрался.Должно быть проще понять. Перед тем, как написать этот блог, я прочитал много блогов о Spring Circular Dependency, таких объяснений, как у меня, в Интернете быть не должно, давайте начнем прямо сейчас.

что такое циклическая зависимость

В двух словах: они зависят друг от друга.

В разработке такая ситуация может часто возникать, но мы обычно не замечаем, что два или даже несколько классов, которые мы написали, зависят друг от друга, почему мы этого не замечаем? Конечно, потому что ошибки нет, и проблемы вообще нет. Все это благодаря Spring, который молча решает за нас проблему циклических зависимостей.

Следующим образом:

@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class AuthorService {
    @Autowired
    BookService bookService;
}
@Service
public class BookService {
    @Autowired
    AuthorService authorService;
}
public class Main {
    public static void main(String[] args) {
        ApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

        BookService bookService = (BookService) annotationConfigApplicationContext.getBean("bookService");
        System.out.println(bookService.authorService);

        AuthorService authorService = (AuthorService) annotationConfigApplicationContext.getBean("authorService");
        System.out.println(authorService.bookService);
    }
}

результат операции:

com.codebear.springcycle.AuthorService@63376bed
com.codebear.springcycle.BookService@4145bad8

Видно, что AuthorService требуется в BookService, а BookService требуется в AuthorService.Подобно этому, это называется циклической зависимостью, но магия в том, что проблемы нет вообще.

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

Может ли Spring разрешать любые циклические зависимости?

нет.

Spring не может разрешить циклическую зависимость прототипа bean-компонента:

@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class BookService {
    @Autowired
    AuthorService authorService;
}
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AuthorService {
    @Autowired
    BookService bookService;
}

После загрузки в консоли появился страшный красный шрифт:

image.png

Если это циклическая зависимость, введенная параметрами конструктора, Spring не может разрешить:

@Service
public class AuthorService {
    BookService bookService;

    public AuthorService(BookService bookService) {
        this.bookService = bookService;
    }
}
@Service
public class BookService {

    AuthorService authorService;

    public BookService(AuthorService authorService) {
        this.authorService = authorService;
    }
}

Или противный красный шрифт:

image.png

Можно ли закрыть циклические зависимости?

Да, Spring предоставляет эту функцию, нам нужно написать:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.setAllowCircularReferences(false);
        applicationContext.register(AppConfig.class);
        applicationContext.refresh();
    }
}

Когда я запускаю его снова, я получаю сообщение об ошибке:

image.png

Обратите внимание, что мы не можем написать:

        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        applicationContext.setAllowCircularReferences(false);

Если вы напишете так, программа выполнит первую строку кода, и весь Spring-контейнер будет инициализирован, если вы установите, чтобы он не допускал циклических зависимостей, это не поможет.

Магия циклических зависимостей

Есть много мелких партнеров, которые могут не думать о том, как замечательно иметь циклические зависимости. Это потому, что они не знают, в чем заключается противоречие. Давайте поговорим об этой проблеме: Когда beanA, beanB циклически зависим:

  1. Создавайте bean-компоненты и обнаруживайте зависимые bean-компоненты;
  2. Создайте beanB и найдите зависимый beanA;
  3. Создавайте bean-компоненты и обнаруживайте зависимые bean-компоненты;
  4. Создайте beanB и найдите зависимость от beanA.

...

Ну и бесконечный цикл.

Противоречие круговой зависимости состоит в том, что для создания bean-компонента A требуется bean-компонент B, а для создания bean-компонента B требуется bean-компонент A, и тогда оба компонента не могут быть созданы.

Как легко разрешать циклические зависимости

Если вы читали блог Spring о решении циклических зависимостей, то должны знать, что в нем есть несколько карт: одна карта содержит наиболее полный объект, называемый singletonObjects, а другая карта содержит объекты, которые выставляются заранее, называемые EarlySingletonObjects.

Здесь давайте сначала объясним эти две вещи:

  • singletonObjects: пул синглетонов, в котором хранятся bean-компоненты, прошедшие полный жизненный цикл Spring, где были заполнены зависимости bean-компонентов.
  • EarlySingletonObjects: Карта выставленных заранее объектов, в которой хранятся только что созданные объекты, bean-компоненты, не прошедшие полный жизненный цикл Spring, и зависимости bean-компонентов, которые еще не были заполнены.

Мы можем это сделать:

  1. Когда мы закончили создание beanA, мы поместили себя в EarlySingletonObjects, обнаружили, что нам нужен beanB, а затем перешли к Pidianpidian для создания beanB;
  2. Когда мы создали beanB, мы поместили себя в EarlySingletonObjects, обнаружили, что нам нужен beanA, а затем обратились к Pidianpidian, чтобы создать beanA;
  3. Прежде чем создавать beanA, сначала перейдите к EarlySingletonObjects, обнаружите, что вы были созданы, и вернитесь;
  4. beanB получает beanA, beanB создается и помещается в singletonObjects;
  5. beanA может перейти к singletonObjects, чтобы получить beanB, beanA также создается и помещается в singletonObjects. Весь процесс окончен.

Давайте реализуем эту функцию: Сначала настройте аннотацию, и поле с этой аннотацией указывает на то, что она должна быть Autowired:

@Retention(RetentionPolicy.RUNTIME)
public @interface CodeBearAutowired {
}

Создайте еще два циклически зависимых класса:

public class OrderService {
    @CodeBearAutowired
    public UserService userService;
}
public class UserService {
    @CodeBearAutowired
    public OrderService orderService;
}

Затем есть ядро, создание объектов, заполнение свойств и разрешение циклических зависимостей Spring:

public class Cycle {
    // 单例池,里面放的是完整的bean,已完成填充属性
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 存放的是提前暴露出来的bean,没有经历过spring完整的生命周期,没有填充属性
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();

    // 在Spring中,这个map存放的是beanNam和beanDefinition的映射关系
    static Map<String, Class<?>> map = new HashMap<>();
    static {
        map.put("orderService", OrderService.class);
        map.put("userService", UserService.class);
    }
    // 如果先调用init方法,就是预加载,如果直接调用getBean就是懒加载,两者的循环依赖问题都解决了
    public void init() {
        for (Map.Entry<String, Class<?>> stringClassEntry : map.entrySet()) {
            createBean(stringClassEntry.getKey());
        }
    }

    public Object getBean(String beanName) {
        // 尝试从singletonObjects中取,
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }

        // 尝试从earlySingletonObjects取
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }

        return createBean(beanName);
    }

    private Object createBean(String beanName) {
        Object singletonObject;

        try {
            // 创建对象
            singletonObject = map.get(beanName).getConstructor().newInstance();

            // 把没有完成填充属性的半成品 bean 放入earlySingletonObjects
            earlySingletonObjects.put(beanName, singletonObject);

            // 填充属性
            populateBean(singletonObject);

            // bean创建成功,放入singletonObjects
            this.singletonObjects.put(beanName, singletonObject);

            return singletonObject;
        } catch (Exception ignore) {
        }
        return null;
    }

    private void populateBean(Object object) {
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(CodeBearAutowired.class) != null) {
                Object value = getBean(field.getName());
                try {
                    field.setAccessible(true);
                    field.set(object, value);
                } catch (IllegalAccessException ignored) {
                }
            }
        }
    }
}

Вызов предварительной загрузки:

public class Main {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        cycle.init();
        UserService userService = (UserService) cycle.getBean("userService");
        OrderService orderService = (OrderService) cycle.getBean("orderService");
        System.out.println(userService.orderService);
        System.out.println(orderService.userService);
    }
}

результат операции:

com.codebear.cycleeasy.OrderService@61baa894
com.codebear.cycleeasy.UserService@b065c63

Ленивая загрузка вызова:

public class Main {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        UserService userService = (UserService) cycle.getBean("userService");
        OrderService orderService = (OrderService) cycle.getBean("orderService");
        System.out.println(userService.orderService);
        System.out.println(orderService.userService);
    }
}

результат операции:

com.codebear.cycleeasy.OrderService@61baa894
com.codebear.cycleeasy.UserService@b065c63

Почему не удается решить круговую зависимость, введенную прототипом и конструктором

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

Почему мы можем это сделать, потому что наш bean-компонент является синглтоном, и он внедряется в поле (внедрение сеттера). Синглтон означает, что нам нужно создать объект только один раз, а затем мы можем получить его из кеша позже. Внедрение поля означает, что нам не нужно вызывать конструктор для инъекции.

  • Если это bean-прототип, это означает, что объект должен создаваться каждый раз, а кеш нельзя использовать;
  • Если это внедрение конструктора, это означает, что необходимо вызвать внедрение конструктора, а кеш нельзя использовать.

Что делать, если нужен аоп?

Наша вышеприведенная схема выглядит очень хорошо, но есть проблема: если наш бин создан, нам нужно сделать небольшую обработку, что нам делать? Возможно, вы не понимаете смысл этого предложения, давайте проясним, если beanA и [прокси-объект beanB] циклически зависимы, или [прокси-объект beanA] и beanB циклически зависимы, или [прокси-объект beanA] и [ прокси-объект beanB] циклическая зависимость, что мне делать?

Упомянутое здесь создание прокси-объектов — лишь одна из возможностей «обработки».

В таком случае мы не можем кинуть созданный объект сразу в кеш? Если мы сделаем это, если [прокси-объект beanA] и [прокси-объект beanB] циклически зависимы, beanB в beanA, который мы в конечном итоге получим, по-прежнему будет beanB, а не прокси-объектом beanB.

Вы умны, вы, должно быть, думаете, разве это не просто: После того, как мы создадим объект, определите, нужен ли объекту прокси.Если ему нужен прокси, создайте прокси-объект, а затем поместите прокси-объект в EarlySingletonObjects, не так ли? нравится:

    private Object createBean(String beanName) {
        Object singletonObject;

        try {
            // 创建对象
            singletonObject = map.get(beanName).getConstructor().newInstance();

            // 创建bean的代理对象
            /**
             * if( 需要代理){
             *     singletonObject=创建代理对象;
             *
             * }
             */

            // 把没有完成填充属性的半成品 bean 放入earlySingletonObjects
            earlySingletonObjects.put(beanName, singletonObject);

            // 填充属性
            populateBean(singletonObject);

            // bean创建成功,放入singletonObjects
            this.singletonObjects.put(beanName, singletonObject);

            return singletonObject;
        } catch (Exception ignore) {
        }
        return null;
    }

Это действительно возможно, но это нарушает исходное намерение Spring. Исходное намерение Spring состоит в том, чтобы перейти к aop только на последних нескольких шагах жизненного цикла компонента. Если это делается, как указано выше, это означает, что после создания объекта Spring пойдет на аоп, что нарушает первоначальный замысел Spring, поэтому Spring этого не делал.

Но если есть циклическая зависимость aop-бина, другого пути нет.Вы можете только сначала перейти к aop.Однако, если нет циклической зависимости, Spring не хочет выполнять здесь aop, поэтому Spring вводит Map>, ObjectFactory — это функциональный интерфейс, под которым можно понимать фабричный метод, при создании объекта поместить [фабричный метод получения этого объекта] в эту карту, а когда действительно возникнет циклическая зависимость, выполнить this [получить этот фабричный метод объекта], чтобы получить обработанный объект.

Код выпущен непосредственно ниже:

public class Cycle {
    // 单例池,里面放的是完整的bean,已完成填充属性
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 存放的是 加工bean的工厂方法
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

    // 存放的是提前暴露出来的bean,没有经历过spring完整的生命周期,没有填充属性
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();

    private final Set<String> singletonsCurrentlyInCreation = new HashSet<>();

    static Map<String, Class<?>> map = new HashMap<>();

    static {
        map.put("orderService", OrderService.class);
        map.put("userService", UserService.class);
    }

    public void init() {
        for (Map.Entry<String, Class<?>> stringClassEntry : map.entrySet()) {
            createBean(stringClassEntry.getKey());
        }
    }

    private Object createBean(String beanName) {
        Object instance = null;
        try {
            instance = map.get(beanName).getConstructor().newInstance();
        } catch (Exception ex) {
        }


        Object finalInstance = instance;
        this.singletonFactories.put(beanName, () -> {
            // 创建代理对象
            return finalInstance;
        });

        populateBean(instance);

        this.singletonObjects.put(beanName, instance);
        return instance;
    }

    public Object getBean(String beanName) {
        // 尝试从singletonObjects中取,
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }

        // 尝试从earlySingletonObjects取
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }

        // 尝试从singletonFactories取出工厂方法
        ObjectFactory<?> objectFactory = this.singletonFactories.get(beanName);
        if (objectFactory != null) {
            singletonObject = objectFactory.getObject();
            this.earlySingletonObjects.put(beanName, singletonObject);
            return singletonObject;
        }

        return createBean(beanName);
    }

    private void populateBean(Object object) {
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(CodeBearAutowired.class) != null) {
                Object value = getBean(field.getName());
                try {
                    field.setAccessible(true);
                    field.set(object, value);
                } catch (IllegalAccessException ignored) {
                }
            }
        }
    }
}

Метод вызова:

 public static void main(String[] args) {
        Cycle cycle = new Cycle();
        cycle.init();
        System.out.println(((UserService) cycle.getBean("userService")).orderService);
        System.out.println(((OrderService) cycle.getBean("orderService")).userService);
    }

результат операции:

com.codebear.cycles.OrderService@49e4cb85
com.codebear.cycles.UserService@2133c8f8

Может ли кеш второго уровня решать циклические зависимости, и какая польза от цикла третьего уровня?

Мои взгляды могут сильно отличаться от общепринятых в Интернете, а правильные или неправильные мои взгляды, судите сами.

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

Какая польза от кэша L3? Основная точка зрения в Интернете заключается в том, чтобы решить круговую зависимость, а также для эффективности.Чтобы решить круговую зависимость, мы обсуждали это выше.Моя точка зрения состоит в том, что кеш второго уровня уже может решить круговую зависимость. , Давайте подумаем об этом, и имеет ли значение эффективность?

Мое мнение, что это не имеет значения по следующим причинам: Ставим на карту [заводской метод получения объекта]

  • Если круговой зависимости нет, то эта карта вообще не используется, и к эффективности это отношения не имеет;
  • Если это обычная циклическая зависимость компонента, кэш третьего уровня напрямую возвращает компонент, что не имеет ничего общего с эффективностью;
  • Если это циклическая зависимость aop bean, если нет кеша третьего уровня, напрямую создайте прокси-объект и поместите его в кеш второго уровня.Если есть кеш третьего уровня, вам все равно нужно создать прокси-объект, но время у них разное, и это не имеет ничего общего с эффективностью.

С основанием этого блога, когда вы смотрите другие блоги о циклических зависимостях Spring, это должно быть намного проще, потому что, в конце концов, мы сами решили циклические зависимости, а циклические зависимости Spring просто дополнительно инкапсулированы и улучшены.