Spring源码-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】

实验环境:spring-framework-5.0.2、jdk8、gradle4.3.1

 

上文 Spring源码-Spring是如何解决Bean循环依赖的【6】 讲到,Spring是通过一级缓存(singletonObjects)、二级缓存(earlySingletonObjects)、三级缓存(singletonFactories)构成了三层缓存的模式。

如果去掉二级缓存(earlySingletonObjects),只使用一级缓存(singletonObjects)和三级缓(singletonFactories)的话,对于普通的bean没有影响,但对于AOP代理的bean会导致重复创建bean实例,违法了单例原则。

下面我将用一个实际例子来证明一下:去掉二级缓存(earlySingletonObjects) ,AOP代理的bean违反了单例原则。

1、定义一个注解

package beans;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAopAnnotation {
    String value();
}

 

2、定义AOP,把切面切到@MyAopAnnotation上,这样的话@MyAopAnnotation标在谁上面,谁就是代理对象,比较灵活

package beans;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@Aspect
@Component
@EnableAspectJAutoProxy
public class MyAop {

    @Pointcut("@annotation(beans.MyAopAnnotation)")
    public void pointCat() {
    }

    @Before("pointCat()")
    public void before(JoinPoint joinPoint) {
        System.out.println("执行AOP before方法");
    }
}

 

3、定义bean,我把@MyAopAnnotation配置在TestBean方面上,也就是说TestBean需要创建AOP代理。另外TestBean、User、User2之间相互注入

TestBean
package beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TestBean {

    @Autowired
    private User user;

    @Autowired
    private User2 user2;

    @Autowired
    private TestBean testBean;

    public User getUser() {
        return user;
    }

    public User2 getUser2() {
        return user2;
    }

    public TestBean getTestBean() {
        return testBean;
    }

    @MyAopAnnotation("")
    public void hello() {
        System.out.println("TestBean 执行 hello 方法 ");
    }
}
User
package beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class User {

    @Autowired
    private User2 user2;

    @Autowired
    private TestBean testBean;

    public TestBean getTestBean() {
        return testBean;
    }

    public User2 getUser2() {
        return user2;
    }


}
User2
package beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

@Component
@DependsOn({"user"})
public class User2 {

    @Autowired
    private User user;

    @Autowired
    private TestBean testBean;

    public User getUser() {
        return user;
    }

    public TestBean getTestBean() {
        return testBean;
    }

}

 

4、运行代码

public class Main {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.scan("beans");
		context.refresh();

		TestBean testBean = (TestBean) context.getBean("testBean");
		testBean.hello();

		User user = context.getBean(User.class);
		User2 user2 = context.getBean(User2.class);
		System.out.println("user == user2.getUser() : " + (user == user2.getUser()));

		System.out.println("testBean == user.getTestBean() : " + (testBean == user.getTestBean()));
		System.out.println("testBean == user2.getTestBean() : " + (testBean == user2.getTestBean()));
		System.out.println("user.getTestBean() == user2.getTestBean() : " + (user.getTestBean() == user2.getTestBean()));

		context.close();
	}

 

运行结果如下:可以看到AOP生效了,另外各个bean注入的属性是同一个bean,由此证明无论是代理bean(TestBean)还是普通bean(User、User2)都是单例的。

 

那么,我修改一下getSingleton方法,把earlySingletonObjects屏蔽掉,也就是说bean在没有成为完整对象之前会一直从singletonFactory.getObject()获取早期bean实例。上文已经说过了,singletonFactory.getObject()是个工厂方法,每调用一次就会执行getEarlyBeanReference方法,而getEarlyBeanReference方法针对AOP代理的bean会创建一个新的代理对象,普通的bean直接返回。

修改后的getSingleton
@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {

		// 1.【从一级缓存里面获取】从单例对象缓存中获取beanName对应的单例对象
		Object singletonObject = this.singletonObjects.get(beanName);

		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 2.加锁进行操作
			synchronized (this.singletonObjects) {

				// 3.【从二级缓存里面获取】从早期单例对象缓存中获取单例对象
				// 之所称成为早期单例对象,是因为earlySingletonObjects里的对象的都是通过提前曝光的ObjectFactory创建出来的,还未进行属性填充等操作
				// TODO 屏蔽掉earlySingletonObjects
				//singletonObject = this.earlySingletonObjects.get(beanName);

				// 4.如果在早期单例对象缓存中也没有,并且允许创建早期单例对象引用
				if (singletonObject == null && allowEarlyReference) {

					// 5.【从三级缓存里面获取】从单例工厂缓存中获取beanName的单例工厂
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {

						// 6.如果存在单例对象工厂,则通过工厂创建一个单例对象
						singletonObject = singletonFactory.getObject();

						// TODO 屏蔽掉earlySingletonObjects
						// 7.将通过单例对象工厂创建的单例对象,放到早期单例对象缓存中
						//this.earlySingletonObjects.put(beanName, singletonObject);
						// 8.移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放到earlySingletonObjects缓存了,
						// 因此,后续获取beanName的单例对象,可以通过earlySingletonObjects缓存拿到,不需要在用到该单例工厂
						//this.singletonFactories.remove(beanName);
					}
				}
			}
		}

		return singletonObject;
	}

 

我们再运行一下,发现user和user2.getUser()是同一个对象,因为user只是一个普通的bean,无论获取多少次都是它自己。而testBean呢,发现各自不同了,就是因为它是代理对象,没有了二级缓存earlySingletonObjects,直接从三级缓存singletonFactories里面拿,每拿一次就返回一个新的代理对象,所以此时testBean已经不是单例了。

 

posted on 2022-02-06 15:34  wzyy  阅读(385)  评论(0编辑  收藏  举报