Peu de gens parlent de dépendances circulaires au printemps, apprenons ensemble aujourd'hui!

Il y a trop de blogs sur les dépendances cycliques de Spring sur Internet. Beaucoup d'entre eux sont analysés en profondeur et écrits très soigneusement. Ils ont même dessiné des diagrammes de séquence et des organigrammes pour aider les lecteurs à comprendre. Après l'avoir lu, j'ai senti que j'avais compris, mais j'ai fermé Quand je pose les yeux dessus, j'ai toujours l'impression de ne pas l'avoir bien compris, et j'ai toujours l'impression qu'il y a encore un ou deux obstacles à franchir.C'est vraiment difficile pour quelqu'un comme moi qui est un peu stupide. À ce moment-là, je pensais, si un jour, je comprends les dépendances cycliques du printemps, je dois écrire un blog à ma manière pour aider tout le monde à mieux comprendre.Après avoir compris, j'ai réfléchi à comment écrire et comment écrire. C'est plus facile à comprendre. Il y a quelques jours à peine, je l'ai compris. Ça devrait être plus facile à comprendre. Avant d'écrire ce blog, j'ai lu beaucoup de blogs sur les dépendances circulaires Spring. Il ne devrait pas y avoir de telles explications sur Internet, alors commençons maintenant.

Qu'est-ce que la dépendance circulaire

En un mot: les deux dépendent l'un de l'autre.

En développement, cette situation peut souvent se produire, mais nous ne remarquons généralement pas que les deux classes ou même les classes multiples que nous avons écrites dépendent l'une de l'autre, pourquoi ne pas remarquer? Bien sûr, c'est parce qu'il n'y a pas d'erreur et qu'il n'y a aucun problème. Si une erreur est signalée ou un problème survient, ne le remarquerons-nous toujours pas? Tout cela est le mérite de Spring, qui résout silencieusement le problème de la dépendance circulaire pour nous plus tard.

Comme suit:

@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);
    }
}

résultat de l'opération:

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

Vous pouvez voir que AuthorService est requis dans BookService, et BookService est requis dans AuthorService. Similaire à cela s'appelle la dépendance circulaire, mais la magie est qu'il n'y a aucun problème.

Bien sûr, certains amis peuvent ne pas en comprendre la magie, quant à la magie, nous en reparlerons plus tard.

Spring peut-il résoudre des dépendances circulaires?

En aucune façon.

S'il s'agit d'une dépendance circulaire du bean prototype, Spring ne peut pas le résoudre:

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

Après le démarrage, la police rouge effrayante est apparue sur la console:

image.png

S'il s'agit d'une dépendance circulaire injectée par des paramètres de construction, Spring ne peut pas résoudre:

@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;
    }
}

Police rouge toujours ennuyeuse:

image.png

La dépendance circulaire peut-elle être fermée? Oui, Spring fournit cette fonctionnalité, nous devons écrire:

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

Exécutez-le à nouveau et une erreur est signalée:

image.png

Il est à noter que nous ne pouvons pas écrire:

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

Si vous écrivez de cette façon, le programme exécute la première ligne de code et tout le conteneur Spring a été initialisé. Si vous n'autorisez pas les dépendances circulaires, cela n'aidera pas.

Quelle est la magie de la dépendance circulaire

De nombreux petits partenaires ne pensent peut-être pas à quel point il est étonnant de pouvoir compter circulairement. C'est parce qu'ils ne savent pas où sont les contradictions. Parlons ensuite de ce problème: lorsque beanA et beanB sont circulairement dépendants:

Créez beanA et trouvez une dépendance sur beanB; créez beanB et trouvez une dépendance sur beanA; créez beanA et trouvez une dépendance sur beanB; créez beanB et trouvez une dépendance sur beanA. ... D'accord, c'est une boucle sans fin. La contradiction de la dépendance circulaire est que pour créer beanA, il faut beanB, et créer beanB nécessite beanA, et aucun bean ne peut être créé.

Comment résoudre facilement les dépendances circulaires

Si vous avez déjà lu le blog de Spring sur la résolution des dépendances circulaires, sachez qu'il existe plusieurs cartes. Une carte contient les objets les plus complets appelés singletonObjects et une carte contient des objets exposés à l'avance, appelés earlySingletonObjects.

Ici, il faut d'abord expliquer ces deux choses:

singletonObjects: pool Singleton, qui stocke les beans ayant subi le cycle de vie complet de Spring, et les dépendances des beans ont été remplies. earlySingletonObjects: une carte d'objets exposés à l'avance, qui stocke les objets qui viennent d'être créés, les beans qui n'ont pas connu le cycle de vie complet de Spring et les dépendances des beans n'ont pas encore été remplies. Nous pouvons le faire:

Lorsque nous avons fini de créer beanA, nous nous plaçons dans earlySingletonObjects, trouvons que nous avons besoin de beanB, puis créons beanB; lorsque nous créons beanB, nous nous mettons dans earlySingletonObjects, et trouvons que nous avons besoin de beanA, puis nous allons péter Créez beanA; avant de créer beanA, allez dans earlySingletonObjects et découvrez que vous avez été créé, et renvoyez-vous; beanB obtient beanA, beanB est créé, mettez-vous dans singletonObjects; beanA peut aller à singletonObjects pour l'obtenir BeanB, beanA est également créé, mettez-vous dans singletonObjects. L'ensemble du processus est terminé. Implémentons cette fonction ci-dessous: Tout d'abord, définissons une annotation personnalisée. Si l'annotation est marquée sur le champ, cela signifie qu'elle doit être câblée automatiquement:

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

Créez deux autres classes circulairement dépendantes:

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

Ensuite, il y a le noyau, la création d'objets, le remplissage des propriétés et la résolution du problème des dépendances circulaires 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) {
                }
            }
        }
    }
}

Appel de préchargement:

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);
    }
}

résultat de l'opération:

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

Appel de chargement paresseux:

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);
    }
}

résultat de l'opération:

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

Pourquoi ne peut pas résoudre la dépendance circulaire injectée par le prototype et la méthode de construction

Dans ce qui précède, nous avons écrit le code pour résoudre nous-mêmes la dépendance circulaire. Nous pouvons voir que le cœur est d'utiliser une carte pour résoudre ce problème. Cette carte est équivalente à un cache.

Pourquoi pouvons-nous faire cela, car notre bean est singleton, et l'injection de champ (injection de setter), singleton signifie que l'objet n'a besoin d'être créé qu'une seule fois, ce qui peut être retiré du cache plus tard, l'injection de champ signifie que nous n'avons pas besoin Appelez le constructeur pour injecter.

S'il s'agit d'un bean prototype, cela signifie que vous devez créer un objet à chaque fois et que vous ne pouvez pas utiliser le cache; s'il s'agit d'une injection de constructeur, cela signifie que vous devez appeler l'injection de constructeur et que vous ne pouvez pas utiliser le cache.

Et si j'ai besoin d'AOP?

Notre schéma ci-dessus semble très bon, mais il y a toujours un problème, si notre haricot est créé, nous devons faire un traitement, que devons-nous faire? Peut-être que vous n'avez pas compris la signification de cette phrase, disons-le plus clairement, si beanA et [l'objet proxy de beanB] sont circulairement dépendants, ou [l'objet proxy de beanA] et beanB sont circulairement dépendants, ou [l'objet proxy de beanA] et [ Objet proxy de la dépendance cyclique beanB], que faire?

La création d'objets proxy mentionnés ici n'est qu'une possibilité de «traitement».

Dans ce cas, nous ne pouvons pas jeter l'objet créé directement dans le cache? Si nous faisons cela, si [l'objet proxy de beanA] et [l'objet proxy de beanB] sont cycliquement dépendants, le beanB dans beanA que nous obtenons finalement est toujours beanB, pas l'objet proxy de beanB.

Intelligent, vous devez penser, n'est-ce pas simple: après avoir créé un objet, nous jugeons si l'objet a besoin d'un proxy, si nous avons besoin d'un proxy, créons un objet proxy, puis placez l'objet proxy dans earlySingletonObjects, n'est-ce pas OJ8K? comme ça:

Objet privé 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;

}

C’est en effet possible, mais cela viole l’intention initiale de Spring. L’intention initiale de Spring est d’aller au sommet au cours des dernières étapes du cycle de vie du haricot. Si vous procédez comme indiqué ci-dessus, cela signifie qu’une fois l’objet créé, Spring ira au bout. aop, qui viole l'intention initiale de Spring, donc Spring ne l'a pas fait.

Cependant, s'il y a une dépendance circulaire sur aop bean, il n'y a rien à faire. Vous pouvez seulement aller d'abord à aop, mais s'il n'y a pas de dépendance circulaire, Spring ne veut pas effectuer aop ici, alors Spring introduit Map <String, ObjectFactory <? >>, ObjectFactory est une interface fonctionnelle, qui peut être comprise comme une méthode de fabrique. Lorsque l'objet est créé, placez la [méthode de fabrique pour obtenir cet objet] dans cette carte, et lorsque la dépendance circulaire se produit, exécutez ceci [get this Object factory method], récupère l'objet traité.

Le code suivant est publié directement:

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) {
                }
            }
        }
    }
}

Méthode d'appel:

 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);
    }

résultat de l'opération:

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

Le cache de deuxième niveau peut-il résoudre la dépendance circulaire et à quoi sert le cycle de troisième niveau?

Mes points de vue peuvent être très différents des points de vue traditionnels sur Internet. Quant à savoir si mes points de vue sont bons ou faux, jugez par vous-même.

Le cache de deuxième niveau peut résoudre la dépendance circulaire, même si le bean aop est cycliquement dépendant, comme nous l'avons mentionné ci-dessus, nous pouvons créer l'objet directement, créer l'objet proxy directement et placer l'objet proxy dans le cache de deuxième niveau, de sorte que ce que nous obtenons du cache de deuxième niveau doit être haricot aop, pas le haricot lui-même.

À quoi sert le cache à trois niveaux? L'opinion dominante sur Internet est de résoudre la dépendance circulaire, et aussi pour l'efficacité. Afin de résoudre la dépendance circulaire, nous en avons discuté ci-dessus. Mon opinion est que le cache secondaire peut déjà résoudre la dépendance circulaire. Pensons-y. L'efficacité est-elle importante?

Mon point de vue est que cela n’a pas d’importance. La raison en est la suivante: nous avons mis la [Méthode d’obtention d’objet en usine] dans la carte

  • S'il n'y a pas de dépendance circulaire, cette carte n'est pas du tout utilisée et n'a rien à voir avec l'efficacité;
  • S'il s'agit d'une dépendance cyclique de bean ordinaire, le cache à trois niveaux renvoie directement le bean, ce qui n'a rien à voir avec l'efficacité;
  • S'il s'agit d'une dépendance cyclique d'un beanop, s'il n'y a pas de cache de troisième niveau, créez directement un objet proxy et placez-le dans le cache de deuxième niveau. S'il y a un cache de troisième niveau, vous devez toujours créer un objet proxy, mais le timing des deux est différent, et cela n'a rien à voir avec l'efficacité.
  • Avec la fondation de ce blog, lorsque vous lisez d'autres blogs sur les dépendances circulaires Spring, cela devrait être plus facile, car après tout, nous avons résolu les dépendances circulaires par nous-mêmes. Les dépendances circulaires Spring ne sont qu'une encapsulation supplémentaire et Améliorer.

Enfin

Répondez aux données par message privé pour recevoir un résumé des questions d'entretien Java d'un grand fabricant + manuel Alibaba Taishan + un guide d'apprentissage des points de connaissances + un résumé des points de connaissances de base Java dans un document pdf de 300 pages!

Le contenu de ces documents sont tous les points de connaissances que l'intervieweur doit poser pendant l'entretien. Le chapitre comprend de nombreux points de connaissances, y compris les connaissances de base, les collections Java, JVM, la concurrence multithread, les principes de printemps, les microservices, Netty et RPC, Kafka , Agenda, modèle de conception, algorithme Java, base de données, gardien de zoo, cache distribué, structure de données, etc.

Auteur: CodeBear Original: https://0x9.me/EL7No

fichier

Je suppose que tu aimes

Origine blog.csdn.net/weixin_46577306/article/details/107450755
conseillé
Classement