什么是循环依赖?Spring如何解决循环依赖?

1. Spring创建代理原理

什么是循环依赖?Spring如何解决循环依赖?_第1张图片
在这里插入图片描述

什么是循环依赖?Spring如何解决循环依赖?_第2张图片

1.1 ProxyFactory类

第一步:创建一个基础SpringBoot项目


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
dependency>

第二步:创建一个测试类

package com.lvpt.circular;

/**
 * 创建代理原理
 * @author jgy
 * @date 2022-09-14
 **/
public class App01 {

    public static void main(String[] args) {
        //aspect(切面) = advice(通知) + pointcut(切点),切面类就是可用抽离出来的公共代码
        //advisor = 更细粒度的切面,包含一个通知和切点
    }

    interface I1{
        void foo();
        void bar();
    }

    static class Target1 implements I1{

        @Override
       public void foo(){
           System.out.println("target1 foo");
       }

       @Override
        public void bar() {
            System.out.println("target1 bar");
        }
    }
}

第三步:写一个测试方法,使用ProxyFactory实现增强

/**
 * 代理工厂 proxyFactory测试1
 **/
public void proxyFactoryTest1(){
    //1、创建代理工厂
    ProxyFactory proxyFactory = new ProxyFactory();
    //2、指定代理对象
    proxyFactory.setTarget(new Target1());
    //3、添加通知: MethodInterceptor: 方法拦截器,属于环绕通知; 底层还是调的addAdvisor(),
    //用了Pointcut.TRUE: 即匹配所有方法: DefaultPointcutAdvisor(Pointcut.TRUE, new MethodInterceptor)
    proxyFactory.addAdvice((MethodInterceptor) invocation -> {
        try {
            System.out.println("前置增强....");
            //调用目标
            return invocation.proceed();
        }finally {
            System.out.println("后置增强....");
        }
    });
    //我们也可用改变代理方式,设置代理方式为JDK代理: 默认CGLib
    proxyFactory.addInterface(I1.class);
    //这是就需要写成接口类型
    I1 proxy = (I1)proxyFactory.getProxy();
    //强制指定用CGLib: true:CGLib; false:JDK
    //proxyFactory.setProxyTargetClass(true);
    //4、拿到代理对象: CGLib代理方式(默认)
    //Target1 proxy =(Target1) proxyFactory.getProxy();
    //5、调用增强后的方法
    proxy.foo();
    proxy.bar();
    //打印类名,可用知道用的是CGLib代理
    System.out.println("代理方式: " + proxy.getClass().getName());
}

测试结果:都得到增强

前置增强....
target1 foo
后置增强....
前置增强....
target1 bar
后置增强....
com.lvpt.circular.$Proxy0

第四步:再写一个测试方法,添加AspectJExpressionPointcut:切点表达式,只增强指定方法

/**
 * 代理工厂: proxyFactory测试2
 **/
public void proxyFactoryTest2(){
    //1、创建代理工厂
    ProxyFactory proxyFactory = new ProxyFactory();
    //2、指定代理对象
    proxyFactory.setTarget(new Target1());

    //3、获取AspectJ切点表达式
    AspectJExpressionPointcut expressionPointcut = new AspectJExpressionPointcut();
    //4、定义切点表达式
    expressionPointcut.setExpression("execution(* foo())");
    //5、创建通知: 使用默认通知DefaultPointcutAdvisor,
    //参数为: 切点表达式 + 通知(这就是advisor,只有一个切面,也就是这两个参数)
    proxyFactory.addAdvisor(new DefaultPointcutAdvisor(expressionPointcut, (MethodInterceptor) invocation -> {
        try {
            System.out.println("前置增强....");
            //调用目标
            return invocation.proceed();
        }finally {
            System.out.println("后置增强....");
        }
    }));
    //再次添加切面Advisor(可用多个)
    proxyFactory.addAdvisor(new DefaultPointcutAdvisor(expressionPointcut, (MethodInterceptor) invocation -> {
        try {
            System.out.println("前置增强22....");
            //调用目标
            return invocation.proceed();
        }finally {
            System.out.println("后置增强22....");
        }
    }));
    //6、拿到代理对象
    Target1 target1 =(Target1) proxyFactory.getProxy();
    //7、调用增强后的方法: 只有foo得到增强
    target1.foo();
    target1.bar();
}

输出结果:发现,只增强了foo()方法

前置增强....
前置增强22....
target1 foo
后置增强22....
后置增强....
target1 bar

切面信息存储才代理对象的advisors属性中;当然,我们也也是基于注解的方式AOP编程;
标注@Aspect标识一个切面类,@Aroubd()、@Before(execution(…))、@After()等等表示通知,
注解里写切点表达式,定位切点;

2. 循环依赖问题

2.1 问题描述

什么是循环依赖?Spring如何解决循环依赖?_第3张图片

2.2 一级缓存(单例模式)

保证对象只存在一份,即单例

解决不了循环依赖;

2.3 二级缓存

流程图:
什么是循环依赖?Spring如何解决循环依赖?_第4张图片

整理流程表述:

2.4 三级缓存

二级缓存中有Bug,就是创建A代理对象比较晚,导致拿了未增强的A对象去创建B,应该拿增强后的A去创建B;添加了一个工厂类,工厂类判断A需不需要增强,不需要就没啥影响,如果需要,就需要用到三级缓存,利用工厂创建代理后的Bean;

2.5 set方式注入循环依赖案例

案例1:

创建一个类,内部又内部类A和B,两个类中都都依赖了彼此,然后创建A、B两个类的Bean;

package com.lvpt.circular;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;

import javax.annotation.PostConstruct;

/**
 * set方法出现循环依赖(不具有代理)
 * @author jgy
 * @date 2022-09-14
 * */
public class App02 {

    /**
     * A类
     * */
    static class A{
        private static final Logger log = LoggerFactory.getLogger("A");
        private B b;

        public A() {
            log.info("执行A的构造方法");
        }

        /** 注解@Autowired用在set方法上,和直接加再变量上一样可用实现注入 **/
        @Autowired
        public void setB(B b){
            log.info("赋值B: setB({})",b);
            this.b = b;
        }

        @PostConstruct
        public void init(){
            log.info("A初始化: init()");
        }
    }

    /**
     * B类
     * */
    static class B{

        public static final Logger log  = LoggerFactory.getLogger("B");
        private A a;

        /**
         * 构造方法
         * */
        public B(){
            log.info("执行B的构造方法");
        }

        @Autowired
        public void setA(A a){
            log.info("赋值A: setA({})",a);
            this.a = a;
        }

        @PostConstruct
        public void init(){
            log.info("B初始化: init()");
        }

    }


    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        //注册Bean
        context.registerBean("a" , A.class);
        context.registerBean("b" , B.class);
        //添加后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        //refresh过程中就会创建A和B的Bean
        context.refresh();
    }

}

运行结果:

18:07:23.015 [main] INFO A - 执行A的构造方法
18:07:23.061 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b'
18:07:23.062 [main] INFO B - 执行B的构造方法
18:07:23.066 [main] INFO B - 赋值A: setA(com.lvpt.circular.App02$A@64485a47)
18:07:23.067 [main] INFO B - B初始化: init()
18:07:23.067 [main] INFO A - 赋值B: setB(com.lvpt.circular.App02$B@7276c8cd)
18:07:23.067 [main] INFO A - A初始化: init()

创建A对象=>发现需要B对象=>创建B对象=>从三级缓存中获取半成品A=>完成B对象创建=>
回到A中继续为B赋值=>完成A对象的创建

运行结果来看,并没有报错,因为Spring为我们解决了循环依赖问题;

断点调试掌握原理

第一步:选取断点

第一个断点:AbstractBeanFactory类中的doGetBean()方法中:
创建流程的开始:别名处理、缓存中找对象等
什么是循环依赖?Spring如何解决循环依赖?_第5张图片
然后为断点添加条件:右键断点
什么是循环依赖?Spring如何解决循环依赖?_第6张图片

第二个断点:AbstractAutowireCapableBeanFactory类中的docreateBean()方法中,这里涉及到了Bean的实例创建、依赖注入和初始化等,getSingleton()方法处H
什么是循环依赖?Spring如何解决循环依赖?_第7张图片

第二步:调试

案例2

A1类具有代理增强,需要用到三级缓存

package com.lvpt.circular;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;

import javax.annotation.PostConstruct;

/**
 * set方法出现循环依赖(具有代理)
 * @author jgy
 * @date 2022-09-14
 * */
public class App03 {

    /**
     * A类
     * */
    static class A1{
        private static final Logger log = LoggerFactory.getLogger("A1");
        private B1 b1;

        public A1() {
            log.info("执行A的构造方法");
        }

        /** 注解@Autowired用在set方法上,和直接加再变量上一样可用实现注入 **/
        @Autowired
        public void setB(B1 b1){
            log.info("赋值B: setB1({})",b1);
            this.b1 = b1;
        }

        @PostConstruct
        public void init(){
            log.info("A1初始化: init()");
        }

        public void foo(){
            System.out.println("foo");
        }
    }

    /**
     * B1类
     * */
    static class B1{

        public static final Logger log  = LoggerFactory.getLogger("B1");
        private A1 a1;

        /**
         * 构造方法
         * */
        public B1(){
            log.info("执行B的构造方法");
        }

        @Autowired
        public void setA1(A1 a1){
            log.info("赋值A1: setA1({})",a1.getClass());
            this.a1 = a1;
        }

        @PostConstruct
        public void init(){
            log.info("B1初始化: init()");
        }
    }

    /**
     * 注解类: 表示这是一个切面类
     * */
    @Aspect
    static class MyAspect{
        @Before("execution(* foo())")
        public void before(){
            System.out.println("前置增强...");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        //注册Bean
        context.registerBean("a1" , A1.class);
        context.registerBean("b1" , B1.class);
        context.registerBean(MyAspect.class);
        //自动代理处理器
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
        //添加后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        //refresh过程中就会创建A和B的Bean
        context.refresh();
        //增强后foo方法
        context.getBean(A1.class).foo();
    }
}

运行结果

INFO A1 - 执行A的构造方法
20:38:30.042 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b1'
20:38:30.042 [main] INFO B1 - 执行B的构造方法
20:38:30.198 [main] INFO B1 - 赋值A1: setA1(class com.lvpt.circular.App03$A1$$EnhancerBySpringCGLIB$$66940c28)
20:38:30.199 [main] INFO B1 - B1初始化: init()
20:38:30.202 [main] INFO A1 - 赋值B: setB1(com.lvpt.circular.App03$B1@6f03482)
20:38:30.202 [main] INFO A1 - A1初始化: init()
20:38:30.202 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.lvpt.circular.App03$MyAspect'
前置增强...
foo

我们发现,A不是原始的对象,而是增强后的代理对象:
class com.lvpt.circular.App03$A1$$EnhancerBySpringCGLIB$$66940c28

断点调试:

2.7 解决构造方法循环依赖

Spring利用三级缓存解决set方式的注入Bean的循环依赖,如果在构造方法中出现了循环依赖,三级缓存就不适用了;最好的就是尽量避免构造方法方式的注入,从根源上解决问题;

解决思路:
1、创建A时,给A一个假的B对象,即B的代理对象;推辞真实B的获取,这样A就能正常创建了;那么B也能正常创建;

2、利用ObjectFactpry工厂,延迟B的创建;
什么是循环依赖?Spring如何解决循环依赖?_第8张图片

构造一个构造方法的循环依赖

A创建依赖B,B创建依赖A;

package com.lvpt.circular;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;

import javax.annotation.PostConstruct;

/**
 * 构造方法出现循环依赖
 * @author jgy
 * */
public class App04 {

    static class A2{
        public static final Logger log = LoggerFactory.getLogger("A2");
        private B2 b2;
        public A2(B2 b2){
            log.info("A2(B2 b2){}",b2.getClass());
            this.b2 = b2;
        }
        @PostConstruct
        public void init(){
            log.info("A初始化");
        }
    }

    static class B2{
        public static final Logger log = LoggerFactory.getLogger("B2");
        private A2 a2;
        public B2(A2 a2){
            log.info("B2(A2 a2){}",a2.getClass());
            this.a2 = a2;
        }
        @PostConstruct
        public void init(){
            log.info("B初始化");
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("a2" , A2.class);
        context.registerBean("b2" , B2.class);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.refresh();
    }
}

运行结果:直接报异常:

UnsatisfiedDependencyException异常: 
 Is there an unresolvable circular reference?:循环依赖

使用@Lazy方式解决

/**
 * 加@lazy注解懒加载,解决构造方法依赖循环
 * */
public A2(@Lazy B2 b2){
    log.info("A2(B2 b2){}",b2.getClass());
    this.b2 = b2;
}

运行结果:

21:53:42.608 [main] INFO A2 - A2(B2 b2)class com.lvpt.circular.App04$B2$$EnhancerBySpringCGLIB$$31b73ab2
21:53:42.617 [main] INFO A2 - A初始化
21:53:42.617 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b2'
21:53:42.623 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'b2' via constructor to bean named 'a2'
21:53:42.623 [main] INFO B2 - B2(A2 a2)class com.lvpt.circular.App04$A2
21:53:42.624 [main] INFO B2 - B初始化

创建A时并不是用到真实的B对象,而是B的代理对象:
class com.lvpt.circular.App04$B2$$EnhancerBySpringCGLIB$$31b73ab2

@Lazy注解原理:

加了该注解,在DefaultListableBeanFactory中的resolveDependency()方法中就会创建一个代理对象;

ObjectFactory工厂方式(推荐)

  static class A2{
        public static final Logger log = LoggerFactory.getLogger("A2");
        //用B2的工厂对象取代B2对象
        private ObjectFactory<B2> b2;
        public A2(ObjectFactory<B2> b2){
            log.info("A2(B2 b2){}",b2.getClass());
            this.b2 = b2;
        }
        @PostConstruct
        public void init(){
            log.info("A初始化");
        }
    }

//要用真正的B2对象时: 调用工厂的getObject()方法即可
System.out.println(context.getBean(A2.class).b2.getObject());

原理:利用工厂,延迟对象的创建,要用正真的对象时,就调用工厂的getObject方法即可;

Prodiver

这个是Java官方提供的解决方法,需引入依赖:

<dependency>
   <groupId>javax.injectgroupId>
   <artifactId>javax.injectartifactId>
   <version>1version>
dependency>
static class A2{
     public static final Logger log = LoggerFactory.getLogger("A2");

     //方案3: inject: 注入; Provider: 提供者
     private Provider<B2> b2;
     public A2(Provider<B2> b2){
         log.info("A2(B2 b2){}",b2.getClass());
         this.b2 = b2;
     }
     @PostConstruct
     public void init(){
         log.info("A初始化");
     }
}

原理还是和工厂一样,延迟对象的获取;

@Scope注解

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * 解决循环依赖: @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
 * @author jgy
 */
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class A3 {

    public static final Logger log = LoggerFactory.getLogger(A3.class);
    private B3 b3;

    public A3(B3 b3){
        this.b3 = b3;
    }

    @PostConstruct
    public void init(){
        log.debug("A初始化方法init()");
    }
}

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class B3 {

    public static final Logger log = LoggerFactory.getLogger(B3.class);
    private A3 a3;

    public B3(A3 a3){
        this.a3 = a3;
    }

    @PostConstruct
    public void init(){
        log.debug("B初始化方法init()");
    }
}

测试:

 public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        //注册Bean
        context.registerBean("a1" , App03.A1.class);
        context.registerBean("b1" , App03.B1.class);
        //自动代理处理器
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
        //添加后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        //refresh过程中就会创建A和B的Bean
        context.refresh();
    }

你可能感兴趣的:(2,框架,spring,java,后端)