前言:
我们使用Spring主要是它的控制反转和依赖注入(当然也有其它特性,这是最基本的也是必用的),我们需要把业务中复杂的对象之间的依赖关系以及对象的重复创建交给Spring帮我们管理。
Spring中Bean的管理是靠Ioc容器来管理的,Spring Ioc容器已经是一个产品实现,对于不同的使用场景有不同的Ioc容器来适配。
基础知识:
Spring中对于Bean的管理是通过一个BeanDefinition来管理的,这个类里包含类的相关信息,比如:在容器中的名字,是否单例,是否为抽象等等。Spring中对bean的操作都是通过这个类对应的一个BeanDefinition来完成的。
比如我们在application.xml中定义的bean标签,Spring会为我们定义一个BeanDefinition来管理,创建之后,所有对的PeoPle的操作都是操作BeanDefinition来完成的。
<bean id="people" class="com.my.ioc.pojo.People"/>
看这个类的描述可知:一个BeanDefinition描述一个Bean实例,有属性值,构造函数参数值,更多的属性信息由具体的实现类来提供。比如后面会用到的:RootBeanDefinition,GenericBeanDefinition等等
Ioc的宏观流程:
1:资源定位(定义Bean的资源,比如我们的application.xml)一般通过Resource接口的实现类来完成。
2:创建一个BeanDefinitionRegistry注册器,用于把资源文件注册为BeaderDefinition.
3:创建BeanDefinitionReader,传入上面的注册器,然后读取Bean的资源,这样就可以得到管理的BeanDefinition了。
我们来实现一个手动创建SpringIoc容器的过程:
public static void main(String[] args) {
// ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath:spring-ioc.xml");
// 资源加载器,用来加载外部资源比如spring-ioc.xml
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
Resource resource = defaultResourceLoader.getResource("classpath:spring-ioc.xml");
// 创建一个beanDefiniton的注册器
SimpleBeanDefinitionRegistry beanDefinitionRegistry = new SimpleBeanDefinitionRegistry();
// 得到资源之后,创建一个读取资源的reader并把注册器传进去
BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanDefinitionRegistry);
// 从一个资源里面加载 BeanDefinition
int i = beanDefinitionReader.loadBeanDefinitions(resource);
// 从注册器里得到注册的 BeanDefinition的name,这个name就是xml中我们定义的bean标签的id属性
String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();
for (int j = 0; j <beanDefinitionNames.length ; j++) {
System.out.println(beanDefinitionNames[j]);
}
}
上面这个过程就是Ioc启动的总流程,包括BeanDefinition的资源定位,载入和注册三个基本过程。只是在Spring中把这三个过程分开了,用不同的模块实现而且都提供的扩展点。
当然实际Spring Ioc容器的初始化过程很复杂,上面的过程只是站在总览的角度看到的样子。
一:Spring Ioc容器设计预览
在Spring中我们可以看到两个主要的容器系列,一个是实现BeanFactory接口的简单容器系列,它作为Spring容器的顶级接口,这系列容器只实现了容器的基本功能;另一个是ApplicationContext应用上下文,它作为容器的高级形态而存在。应用上下文在简单容器的基础上增加了许多面向框架的特性,同时对应用环境做了适配。
我们后面主要使用注解的方式来演示Ioc容器启动的,配合@ImportResource注解也可以引用xml文件。所以我们使用的 AnnotationConfigApplicationContext 这个容器。
部分容器列表如下,继承类图大家可以在idea中,选择BeanFactory按Ctrl+H看到。
二:注入Bean的几种方式
具体看源码之前,先清楚Bean注入容器的几种方式,注入的原理都是什么,这是Ioc容器启动过程中的一部分,我们分步来看。
1:FacoryBean
提到FactoryBean肯定都知道它和BeanFactory的区别,因为它俩经常拿来比较。FactoryBean是一个Bean,而且它还是单独生成某类对象的工厂,而BeanFactory是Spring容器的顶级接口,提供容器最基本的功能。
说到这里我们可以先看下BeanFactory中几个方法:
public interface BeanFactory {
//如果一个类是FactoryBean,又想得到这个类本身而不是调用它的getObject方法,那么就在beanname之前加上&
String FACTORY_BEAN_PREFIX = "&";
//根据beanname得到一个bean,这个Ioc容器最基本的功能
Object getBean(String name) throws BeansException;
//是否包含某个bean
boolean containsBean(String name);
//是否为单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
//是否为原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
}
对于FactoryBean的用法,我们举个例子:
package com.my.ioc.pojo.factorybean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;
@Component
public class MyFactoryBean implements FactoryBean<Bird> {
@Override
public Bird getObject() throws Exception {
return new Bird();
}
@Override
public Class<?> getObjectType() {
return Bird.class;
}
}
Bird只是一个普通的类,没有单独放入Spring容器中。
public class Bird {
String name;
boolean fly;
}
package com.my.ioc.pojo;
import java.util.Random;
public class TestTask implements Runnable {
private volatile boolean flag=false;
}
我们就只用一个BeanConfig来作为容器资源的入口
//@Configuration
@ComponentScan("com.my.ioc.pojo.factorybean")
@ImportResource("spring-ioctest.xml")
public class BeanConfig {
@Bean
public People getPeople(){
return new People();
}
}
spring-ioctest.xml内容如下:
<?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="task" class="com.my.ioc.pojo.TestTask"/>
</beans>
启动类如下:
public static void main(String []args){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
System.out.println(applicationContext.getBeanDefinition("myFactoryBean"));
System.out.println(applicationContext.getBean("myFactoryBean"));
System.out.println(applicationContext.getBean("myFactoryBean"));
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
//开始从单例对象池中获取内容
System.out.println("start get beanDefinitionName from SpringIoc");
for (String name:beanDefinitionNames
) {
System.out.println(name);
}
}
输出的结果如下:
> Task :spring-myselftest:MyTest.main()
Generic bean: class [com.my.ioc.pojo.factorybean.MyFactoryBean]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\SourceCode\spring-framework-5.2.x\spring-myselftest\build\classes\java\main\com\my\ioc\pojo\factorybean\MyFactoryBean.class]
com.my.ioc.pojo.factorybean.Bird@5dd1c9f2
com.my.ioc.pojo.factorybean.Bird@5dd1c9f2
start get beanDefinitionName from SpringIoc
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
beanConfig
myFactoryBean
getPeople
task
1) 首先我们看到输出的两次:System.out.println(applicationContext.getBean("myFactoryBean"));
结果都是com.my.ioc.pojo.factorybean.Bird@5dd1c9f2,这就是FactoryBean的作用,getBean的时候会得到FactoryBean中getObject返回的对象,而且还是单例的,这个和FactoryBean中的一个方法有关,这个方法在5.X中在接口已经有了默认实现。 对象是否为单例,默认为true,如果要多例,可以在实现类中覆盖该方法。
如果想得到FactoryBean本身的这个Bean可以通过这种方式得到:System.out.println(applicationContext.getBean("&myFactoryBean"));
com.my.ioc.pojo.factorybean.MyFactoryBean@59b38691
2)我们从applicationContext.getBeanDefinitionNames();得到的遍历结果中发现除了前几个Spring内部的类之外,就只有四个我们定义的对象
其中并没有Bird对象,因为我们的Bird并没有在SpringIoc的单例对象池中,单例对象池中就只有输出的那几个类。
在SpringIoc中的单例对象池就是一个Map,正常情况下我们交给Spring管理的单例Bean都会在这个Map,key就是我们的Bean的名字。比如bean标签的id,@Bean对象的方法名等。
之所以Bird没有在单例对象池中是因为Bird实例是FactoryBean创建的,FactoryBean是交由单例对象池管理的,而Brid是放在下面这个Map中。并没有放入单例对象池中。
具体的过程后面有时间会描述。
2:@Component+@ComponentScan
@Component默认扫描如下四种注解 @Component @Controller @Repository @Service。
@ComponentScan注解的扩展用法:
排除用法excludeFilters
:第一种是排除某个注解,第二种是排除某个类,都可以填写多个。
@ComponentScan(basePackages = "com.my.ioc.pojo.factorybean",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value={Service.class}),
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,value= {Bird.class})
})
包括用法:
@ComponentScan(basePackages = "com.my.ioc.pojo.factorybean",
includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,value = CustomerFilter.class )}
)
包含是个自定义的一个Filter的。
public class CustomerFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
ClassMetadata classMetadata = metadataReader.getClassMetadata();
if (classMetadata.getClassName().contains("myselfBeanName"))
return true;
return false;
}
}
3:@Configuration+@Bean
@Configuration标志这个类是个配置类,@Bean定义需要被Spring管理的类,上面我使用的BeanConfig中没有使用@Configuration,但是还是可以被Spring扫描到进行管理,这里有个需要注意的点,加上@Configuration注解之后BeanConfig这个类就会被Spring进行代理,这样在BeanConfig中有类互相调用的时候就会得到单例来使用,如果不加@Configuration注解,BeanConfig下面的类相互引用的时候得到的就是不同的实例了。具体的过程后面会描述
4:@Import
我们上面使用过@ImportResource("spring-ioctest.xml")导入xml的bean定义,@Import是用来导入@Configuration标识的类,或ImportSelector/ImportBeanDefinitionRegistrar接口实现类
使用ImportSelector的例子:
public class MyImport implements ImportSelector {
//
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 类的全路径名,返回的类会被Spring管理,前提是MyImport被Spring管理
return new String[]{Bird.class.getName()};
}
}
@Configuration
@Import(MyImport.class)
public class BeanConfig {
}
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
System.out.println(applicationContext.getBean(Bird.class));
Bird类已被Spring管理,beanName是类的全限定名。
5:ImportBeanDefinitionRegistrar接口
是个很重要的接口,很多扩展组件都用它和Spring整合。因为它可以得到BeanDefinitionRegistry注册器。
我上面讲述SpringIoc整体流程的时候,说过只要得到注册器就可以向Spring容器一个beanDefinitonMap中注入BeanDefinition,Spring容器就可以管理类了。这也是写中间件中常用的扩展点,用它来把我们的组件整合进Spring。
定义自己的注册接口:
public class Myregister implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 自定义一个BeanDifinition
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Bird.class);
// 利用BeanDefinition给对象赋值
rootBeanDefinition.getPropertyValues().add("name","myFridBird");
// 使用BeanDefinitionRegistry注册器向Spring中的 beanDefinitonMap注入bean
registry.registerBeanDefinition("myBird",rootBeanDefinition);
}
}
交给Spring管理:
@Configuration
@Import({MyImport.class, Myregister.class})
public class BeanConfig {
}
结果:
6:@Conditional条件装配
满足某个条件之后才会装配某个Bean,举个例子。
自定义的条件匹配规则:
public class MyConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 设置什么时候条件装配生效,当BeanFactory中有beanName为myCat的时候才会生效
if (context.getBeanFactory().containsBean("myCat")){
return true;
};
return false;
}
}
使用:
@Configuration
@Import({ Myregister.class})
public class BeanConfig {
@Bean
@Conditional(MyConditional.class)
public People people(){
return new People();
}
}
public static void main(String []args){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
//开始从单例对象池中获取内容
System.out.println("start get beanDefinitionName from SpringIoc");
for (String name:beanDefinitionNames
) {
System.out.println(name);
}
}
输入结果:
里面是没有people的,我们增加一个BeanName为myCat的类试下:
再次执行上面的main方法:people的条件装配成功了。这里需要注意条件装配的Bean的注入的顺序要在myCat之后。
在 Condition的方法中不仅可以得到BeanFactory还可以得到其它的一些核心类来对Spring容器进行操作以达到扩展的目的。
7:手动装配和自动装配
@Autowired 默认的autowireMode=0 关于autowireMode可以参考AutowireCapableBeanFactory,中的四种装配模式。默认是安装类型注入的,
可以放在属性上和构造函数上。
下一步开始探讨SpringIoc的启动过程。