Spring容器及实例化

spring容器

容器是什么?

我们先看官网中的一句话:

The org.springframework.context.ApplicationContext interface represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the beans.

翻译下来大概就是:

  1. Spring IOC容器就是一个org.springframework.context.ApplicationContext的实例化对象
  2. 容器负责了实例化,配置以及装配一个bean
    那么我们可以说:
    从代码层次来看:Spring容器就是一个实现了ApplicationContext接口的对象,
    从功能上来看: Spring 容器是 Spring 框架的核心,是用来管理对象的。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。

    容器如何工作?
    我们直接看官网上的一张图片,如下:
    在这里插入图片描述
    Spring容器通过我们提交的pojo类以及配置元数据产生一个充分配置的可以使用的系统
    这里说的配置元数据,实际上我们就是我们提供的XML配置文件,或者通过注解方式提供的一些配置信息

Spring Bean

如何实例化一个Bean?
从官网上来看,主要有以下三种方法
在这里插入图片描述
1.构造方法
2.通过静态工厂方法
3.通过实例工厂方法
这三种例子,官网都有具体的演示,这里就不再贴了,我们通过自己查阅部分源码,来验证我们在官网得到的结论,然后通过debug等方式进行验证。

们再从代码的角度进行一波分析,这里我们直接定位到

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

这个方法中,具体定位步骤不再演示了,大家可以通过形如下面这段代码:

ClassPathXmlApplicationContext cc =
    // 这里我们通过xml配置实例化一个容器
    new ClassPathXmlApplicationContext("classpath:application.xml");
MyServiceImpl myServiceImpl= (MyServiceImpl) cc.getBean("myServiceImpl");

直接main方法运行,然后在

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

这个方法的入口打一个断点,如图:
在这里插入图片描述
接下来我们对这个方法进行分析,其实这个方法在springbean的生命周期中算是比较重要的一个方法了,这个方法完成了springbean的生命周期中调用第二次bean的后置处理器中的推断构造方法的方法。而且spring中这个方法会返回一个BeanWrapper对象,在外面可以通过调用BeanWrapper的getWrappedInstance()方法然后可以返回包装的Bean对象.这个BeanWrapper其实就是对spring中bean对象的一个封装,spring中对对象的封装使用的是淋漓尽致,其中就包括BeanWrapper和后面会看到一个Holder对象,这些都是后话了,还是回到这个方法上来,首先看看这个方法的参数,首先传了一个String类型的beanName,这个参数肯定是不用说的,因为在spring中如何将这么多个bean存储起来呢,肯定就是通过名字,而且spring中还有一个专门用来存储beanName的一个集合

private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

然后传了一个RootBeanDefinition的一个参数,这个参数如果要说的话可以说一篇文章,所以这里我就先不说了,等后面说到这里的时候再说,这里先提一下这个参数的大概用途,在spring中我们一般都是用BeanDefinition来描述一个bean,就相当于java中用一个Class对象来描述一个类一样,都是对一个对象的一个建模.而BeanDefinition则有很多实现,每个实现都有自己不可或缺的功能,其中这个RootBeanDefinition可以用来做bean的模板(这里要利用到setAbstract这个方法),然后还可以作为一个真是的bean存在,这里主要是可以用作父bd出现,但是不能做子bd,因为在org.springframework.beans.factory.support.RootBeanDefinition#setParentName方法里直接抛出了异常,说明不能设置父bd.关于RootBeanDefinition先说到这里,后面再详细介绍.

还是说回到这个方法,这个方法一上来就判断了类的访问权限,因为spring默认情况下对于非public的类是允许访问的,后面提到的FactoryMethod也是很重要的,后面再说.

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
		// 检测一个类的访问权限, spring默认情况下对于非public的类是允许访问的
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}

		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}

		/**
		 * factoryMethod, 如果工厂方法不为空, 则通过工厂方法构建bean对象
		 * 这种构建bean的方式可以自己写个demo去试试, 源码就不做深入分析了
		 *
		 * 如果有FactoryMethod, 就调用FactoryMethod创建对象
		 * 需要用xml测试, 不知道注解中是否可以用factoryMethod
		 * <bean id="xxx" class="xxx.xxx.xxx" factory-method="test()"></>
		 * 其实是bd被改变了, 把bd里的beanClass属性给改成了factoryMethod指定的
		 */
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// Shortcut when re-creating the same bean...
		/**
		 * 从spring的原始注释可以知道这个是一个Shortcut, 什么意思呢?
		 * 当多次构建同一个bean时, 可以使用这个Shortcut, 也就是说不再需要每次推断应该使用哪种方式构造bean
		 * 比如在多次构建同一个prototype类型的bean时, 就可以走此处的Shortcut
		 *
		 * 这里的resolved和mbd.constructorArgumentsResolved将会在bean第一次实例化的过程中被设置,后面来证明
		 *
		 *
		 * shortCut: 快捷方式, 当我们重新创建同一个bean时, 可以利用这个快捷方式; 就是spring在构建一个对象的时候, 他要知道这个对象如何来构建, 利用构造方法还是默认的构造方法, 通过这个值来记录, 当new一个原型对象的时候, resolved这个值就起到作用了, 下边就不用再判断了
		 */
		boolean resolved = false;
		boolean autowireNecessary = false;		// 是不是必须要自动装配
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				// 如果缓存的已解析的构造函数或工厂方法不为空
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					// constructorArgumentsResolved字段是构造函数参数标记为已解析, 即如果已经解析了构造方法的参数, 则下面必须要通过一个带参构造方法来实例
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		if (resolved) {
			if (autowireNecessary) {
				// 通过构造方法自动装配的方式构造bean对象
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				// 通过默认的无参构造方法进行初始化
				return instantiateBean(beanName, mbd);
			}
		}

		// Candidate constructors for autowiring?
		// 如果(args不为空)或(args为空 且 缓存的已解析的构造函数或工厂方法也为空), 才会走到这里
		/**
		 * 第二次执行后置处理器
		 * SmartInstantiationAwareBeanPostProcessor.determineCandidateConstructors
		 * 推断构造方法
		 */
		// Candidate constructors for autowiring?
		// 查找是否存在候选的自动注入构造器, 由后置处理器决定返回哪些构造方法
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			// 通过构造方法自动装配的方式(注入的方式)调用构造器实例化bean对象
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		// No special handling: simply use no-arg constructor.
		// 使用默认的无参构造方法进行初始化(利用反射newInstance)
		return instantiateBean(beanName, mbd);
	}

我们主要关注进行实例化的几个方法:

  1. 通过BeanDefinition中的instanceSupplier直接获取一个实例化的对象。这个instanceSupplier属性我本身不是特别理解,在xml中的标签以及注解的方式都没有找到方式配置这个属性。后来在org.springframework.context.support.GenericApplicationContext这个类中找到了以下两个方法

经过断点测试,发现这种情况下,在实例化对象时会进入上面的supplier方法。下面是测试代码:

public static void main(String[] args) {
    // AnnotationConfigApplicationContext是GenericApplicationContext的一个子类
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
		ac.registerBean("service", Service.class,Service::new);
		ac.refresh();
		System.out.println(ac.getBean("service"));
	}

可以发现进入了这个方法进行实例化
这个方法一般不常用,平常我们也使用不到,就不做过多探究,笔者认为,这应该是Spring提供的一种方便外部扩展的手段,让开发者能够更加灵活的实例化一个bean。
2.接下来我们通过不同的创建bean的手段,来分别验证对象的实例化方法
通过@compent,@Service等注解的方式
测试代码:

public class Main {
	public static void main(String[] args) {
        // 通过配置类扫描
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
		System.out.println(ac.getBean(Service.class));
	}
}

@Component
public class Service {

}

可以发现,代码执行到最后一行,同时我们看代码上面的注释可以知道,当没有进行特殊的处理的时候,默认会使用无参构造函数进行对象的实例化

• 通过普通XML的方式(同@compent注解,这里就不赘诉了)
• 通过@Configuration注解的方式

测试代码:

public class Main {
	public static void main(String[] args) {
        // 通过配置类扫描
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
        // 这里将测试对象换为config即可,同时记得将条件断点更改为beanName.equlas("config")
		System.out.println(ac.getBean(config.class));
	}
}

这里断点进入的方法跟上一种情况是一样的,都是走的默认无参的构造方法去实例化对象的,我就不截图进行验证了。

•通过@Bean的方式
测试代码:

@Configuration
@ComponentScan("com.dmz.official")
public class Config {
    @Bean
    public Service service(){
        return new Service();
    }
}

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(Config.class);
        System.out.println(ac.getBean("service"));
    }
}

在这里插入图片描述
通过打断点进去可以看出是在上面的方法进行实例化的,因此可以看出在@Bean这种方法下,spring的底层在这里是采用的一个FactoryMethod的一个方法进行实例化对象的,spring会在我们需要实例化这个对象对应的beanDefinition的时候记录这个bean对应的factoryBeanName(在上面的例子中,factoryBeanName就是config),同时会记录创建这个对象的factoryMethodName是什么,最后通过factoryBeanName获取一个bean然后通过反射调用factoryMethod实例化一个对象。

这里我们需要注意几个概念:
1.这里所说的通过静态工厂方式通过factoryBeanName获取一个Bean,注意,这个Bean,不是一个FactoryBean。也就是说不是一个实现了org.springframework.beans.factory.FactoryBean接口的Bean。至于什么是FactoryBean我们在后面的文章会认真分析
2.提到了一个概念BeanDefinition,它就是Spring对自己所管理的Bean的一个抽象。至于这个BeanDefinition就更加重要了,这个类就相当于是jdk中的Class类一样,只不过Class类是对java中类的抽象,这个Class是用来描述java中的一个类的,而spring中的BeanDefinition则是对spring中的bean的抽象,这个BeanDefinition则是用来描述spring中的bean的,而关于bean和对象的区别这个问题就不用再重复了。上面也说了这个类的重要性。

•通过静态工厂方法的方式
测试代码:

public static void main(String[] args) {
    ClassPathXmlApplicationContext cc =
        new ClassPathXmlApplicationContext("application.xml");
    System.out.println(cc.getBean("service"));
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	
	<bean id="myFactoryBean" class="com.service.MyFactoryBean">
	</bean>

	<!-- 测试实例工厂方法创建对象-->
	<bean id="clientService"
		  factory-bean="myFactoryBean"
		  factory-method="get"/>

	<!--测试静态工厂方法创建对象-->
	<bean id="service"
		  class="com.service.MyFactoryBean"
		  factory-method="staticGet"/>
</beans>

在这里插入图片描述
可以发现,这种情况也进入了instantiateUsingFactoryMethod方法中。通过静态工厂方法这种方式特殊之处在于,包含这个静态方法的类,不需要实例化,不需要被Spring管理。Spring的调用逻辑大概是:
1.通过bean标签中的class属性得到一个Class对象
2.通过这个Class对象获得对应的方法名称的Method对象
3.最后反射调用Method.invoke(null,args),这里是静态方法,所以在执行的时候,不需要一个对象

•通过实例工厂方法的方式
测试代码(配置文件不变):

public static void main(String[] args) {
    ClassPathXmlApplicationContext cc =
        new ClassPathXmlApplicationContext("application.xml");
    System.out.println(cc.getBean("clientService"));
}

在这里插入图片描述
最终还是调用的这个方法,这个方法执行的流程跟@Bean的流程一样,这里就不重复了。

到这里,其实我们把createBeanInstance()这个方法过了一遍,但是只是简单的过了一遍,里面还有很多逻辑需要去深挖,就比如spring是如何推断构造函数的?因为我们上面验证的都是无参的构造函数,并且只提供了一个构造函数,这里spring做的很复杂,这里还和spring中的自动装配和手动装配有关系,如果装配模型不一样,这里又会产生不一样的结果,因此这个留着后面来说。
还有就是spring是如何推断构造方法的 ?不管是静态工厂方法,还是实例工厂方法的方式,我们都只在类中提供了一个跟配置匹配的方法名,假设我们对方法进行了重载呢,结果又是怎么样的呢。这个也是留着后面来说.

实例化总结:
1.对象实例化,只是得到一个对象,还不是一个完全的Spring中的Bean,我们实例化后的这个对象还没有完成依赖注入,没有走完一系列的声明周期,这里需要大家注意。
2. Spring官网上指明了,在Spring中实例化一个对象有三种方式:
• 构造函数
• 实例工厂方法
• 静态工厂方法
3.自己总结如下:Spring通过解析我们的配置元数据,以及我们提供的类对象得到一个Beanfinition对象。通过这个对象可以实例化出一个java bean对象。

具体流程如下:在这里插入图片描述
这里大概说了spring官网的关于实例化bean的一些知识点以及一些扩展,下一节讲关于依赖注入的部分.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值