1 容器内部协作及简介
1.容器在启动时,读取Bean配置元数据信息(eg:实现类,属性,依赖关系以及配置行为(eg实现Bean级生命周期接口))
2.解析读取的信息将Bean构建成BeanDefinition对象并形成Bean注册表
3.容器根据BeanDefinition注册表实例化 & 组装 & 执行Bean相应的配置方法以及生命周期方法并将Bean放在Bean缓存池中
4.应用程序通过getBean(...)从容器的缓存池中获取Bean
2 属性注入
2.1 设值注入
通过setter方法注入Bean的属性或者依赖对象
使用setter注入的前提:1.Bean需要提供无参构造函数 2.Bean需要提供对应的setter方法
<bean id="beanId" class="Bean实现类"> <property name="fieldName" value=""/><!-- 注入属性 file为基础数据类型--> <property name="fieldName" ref="当前容器/父容器中存在的Bean"/><!-- 注入依 赖对象 file为Object类型--> ...... </bean> |
采用setter注入需要主要的是:当以大写字母命名变量时,需要改变量的前两个字母为大写或者小写.
2.2 构造注入
通过构造函数注入Bean对应的属性或者依赖对象,使Bean在实例化后就得到设置,确保Bean实例化后就可以使用.
2.2.1 按类型匹配入参
<bean id="beanId" class="Bean实现类">
<!-- 注入属性 fileType为基础数据类型-->
<constructor-arg name="fieldName" type=”file数据类型”><value><![CDATA[;<>]]></value></constructor-arg>
<!-- 注入依赖对象 fileType为Object类型-->
<constructor-arg name="fieldName" ref="当前容器/父容器中存在的Bean" type=”file数据类型”></constructor-arg>
......
</bean>
当Bean实现类中只有一个构造函数时,<constructor-arg>的声明顺序确定了构造函数的入参顺序,所以不需要添加入参数据类型,但是在某些情况下定义多个具有相同入参的构造函数时,需要按索引匹配入参
2.2.2 按索引匹配入参
需要注意的是索引下标是从0开始
<bean id="beanId" class="Bean实现类">
<!-- 注入属性 fileType为基础数据类型-->
<constructor-arg name="fieldName" index=”fiel索引”><value><![CDATA[;<>]]></value></constructor-arg>
<!-- 注入依赖对象 fileType为Object类型-->
<constructor-arg name="fieldName" ref="当前容器/父容器中存在的Bean" index=”fiel索引”></constructor-arg>
......
</bean>
构造函数是允许被重载的,此时需要联合使用两种类型匹配.
2.2.3 工厂方法注入
2.2.3.1 非静态工厂方法注入
<bean id="factoryBeanId" class="工厂方法实现类"></bean>
<bean id="beanId" factory-bean="factoryBeanId" factory-method="工厂类方法即用来生成对象的方法"></bean>
2.2.3.2 静态工厂方法注入
<bean id="beanId" factory-bean="工厂方法实现类" factory-method="工厂类方法即用来生成对象的方法"></bean>
2.3 方法注入
在单例Bean中注入prototyep Bean.单例Bean注入关联Bean的动作只发生一次.虽然关联Bean被声明为prototype,但是单例Bean保留的依然是第一次注入的Bean的实例
2.3.2 实现BeanFactoryAware接口
public class Boss implements BeanFactoryAware {
private Car car;
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public Car getCar() {
return beanFactory.getBean("car", Car.class);
}
}
<bean id="boss" class="com.lookup.Boss" scope="singleton"></bean>
<bean id="car" class="com.lookup.Car" scope="prototype"></bean>
单实例Bean直接获得BeanFactory实例,采用该实例直接从容器中获得prototype Bean。此中方式Spring接口侵入Bean,造成了对Bean的污染
2.3.2 采用配置
public interface ICar {
public Car getCar();
}
public class Car {
}
public class Boss {
private Car car;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
<bean id="car" class="lookup.Car" scope="prototype"></bean>
<bean id="boss" class="lookup.Boss" scope="singleton">
<lookup-method name="getCar" bean="car"></lookup-method>
</bean>
没有接口的实现,动态创建子类或者实现类。
lookup-method的签名需要符合如下标准:<public | protected> [abstract] <return-type> methodName(arguments);
3 注入方法比较
注入方式 | 优点 | 缺点 |
---|---|---|
构造注入 | 1.避免重要属性没有设置而获得无用的bean 2.减少setter方法 3.更好的封装,变面外界修改重要属性 | 1.重要属性可选时,需要提供null 2.构造函数重载需要考虑匹配问题 3.自类需要应用父类复杂的构造函数 4.参数过多时,构造函数签名复杂 |
设值注入 | 1.灵活 2.解决循环依赖 | 1.重要属性无法在实例化时被设置 2.外界可以通过setter修改重要属性 |
工厂方法注入 | 1.简化配置,工厂方法创建对象是通过构造方法 或者setter对对象进行初始化 | 1.随时间的推移工厂类会增加,导致系统类增多,增加系统复杂性 2.构造函数重载时,需要修改工厂类 |
lookup注入 | 允许singleton Bean关联prototype Bean, 如果prototype是有状态的,此时该中注入的最好的选择 | 1.需要提供额外的接口或者抽象类 2.需要实现Bean生命级接口,造成对Bean的污染 |
4 Bean和Bean之间的关系
4.1 引用同一个容器中的bean
<bean id="beanId" class="Bean实现类">
<constructor-arg name="fieldName" ref="当前容器中存在的Bean"></constructor-arg>
......
</bean>
4.2 引用父容器中的Bean
<bean id="beanId" class="Bean实现类">
<constructor-arg name="fieldName">
<ref parent=”父容器中存在的Bean”/>
</constructor-arg>
......
</bean>
如何建立父子容器关系,在创建子容器是将已经存在的容器对象以参数的形式传给子容器,那么已存在容器就是子容器的父容器
Public abstract class AbstractApplicationContext
extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean {
public AbstractApplicationContext(ApplicationContext parent)
}
5 内部bean,null以及集合的注入
5.1 内部bean注入
内部Bean和匿名内部内类似,只允许当前Bean是,而不允许容器中其他的Bean使用
<bean id="beanId" class="Bean实现类">
<property name=”field名称”>
<bean class="内部Bean实现类">
......
</bean>
</property>
</bean>
5.2 null注入
<bean id="beanId" class="Bean实现类">
<property name=”field名称”><null></null></property>
</bean>
5.3 集合注入
5.3.1 List
<bean id="beanId" class="Bean实现类">
<property name=”field名称”>
<list value-type="指定存入list的默认Java类型" merge=”....”>
<value></value>
<ref bean=”容器中存在的Bean”></ref>
</list>
</property>
</bean>
5.3.2 Set
<bean id="beanId" class="Bean实现类">
<property name=”field名称”>
<set value-type="指定存入set的默认Java类型" merge=”....”>
<value></value>
<ref bean=”容器中存在的Bean”></ref>
</set>
</property>
</bean>
5.3.3 Map
<bean id="beanId" class="Bean实现类">
<property name=”field名称”>
<map value-type="指定存入map value的默认Java类型" key-type="指定存入map key的默认Java类型" merge=”....”>
<entry>
<key><value>key</value></key>
<value>value</value>
</entry>
<entry>
<key><ref bean="已经存在的Bean被设置为Key"></ref></key>
<ref bean="已经存在的Bean被设置为value"></ref>
</entry>
</map>
</property>
</bean>
5.3.4 Properties
没有ref子标签,所以引用已经存在的Bean.属于配置属性文件,所有必要引用已经存在的Bean
<bean id="beanId" class="Bean实现类">
<property name=”field名称”>
<props value-type=”指定存入Properties value的默认Java类型” merge=”....”>
<prop key="key">value</prop>
</props>
</property>
</bean>
其中的value-type & key-type &merge都是可选的.
5.4 集合合并
<bean id="super beanId" class="Bean实现类">
<property name="field名称">
<set>
<ref bean="beanId"></ref>
</set>
</property>
</bean>
<bean id="beanId" parent="super beanId">
<property name="field名称">
<set merge=“true”>
<ref bean="beanId"></ref>
</set>
</property>
</bean>
在合并操作时候需要指定Bean的parent节点并且是针对同一个field的操作,如果声明了value-type,那么数据类型也需要一致.
6 属性级联
class Student {
//需要持有非空实例,并提供对应的get方法,作用是获得对象实例
private Teacher teacher = new Teacher();
private String teacherName;
//必须提供get方法以便获取实例
public Teacher getTeacher() {
return teacher;
}
}
class Teacher {
private String name;
//必须提供set方法
public void setName(String name) {
this.name = name;
}
}
<bean id="student" class="com.bean.lifecycle.entery.Student">
<property name="teacher.name" value="AAA"></property>
</bean>
7 bean自动装配类型
装配类型 | 说明 |
---|---|
byName | 根据bean的名称自动装配, 在容器中根据名字查找和属性名字一致的Bean进行装配 Student定义了名为teacher变量并提供了对应的setter, 如果容器中有一个Bean且名字为teacher, 那么就将teacher自动装配给Student的teacher变量 |
byType | 根据bean类型自动装配 在容器中根据类型查找和指定属性类型一致的Bean进行装配, 如果容器中存在多个bean就会抛出异常,否则不抛出异常且属性不会被装配 如果不希望这样可以i使用dependency-check=“object”强制让容器抛出异常 Student定义了类型为Teacher的属性, 如果容器中有一个Bean且类型为Teacher, 那么就将其自动装配给Student的Teacher属性 |
constructor | 与byType的方式类似,不同之处在于它应用于构造器参数。 如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常 |
Autodetect | 通过bean类的自省机制来决定是使用constructor 还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式 |
default | 如果bean没有显示声明autowrie属性, 那么容器将采用beans根标签中default-autowrie配置类型来装载bean, default-autowrie为全局装配类型配置 |
on | 不使用自动装配,必须通过ref元素指定依赖,默认设置 |
在实际应用中一般不会在xml中启用自动装配,一般都会采用注解的方式,该方式下采用的byType类型的装配 。byName 和byType在使用的过程中必须保证bean能够初始化,否则的话会出现bug(属性为默认值,只是实例化没有初始化)如果有默认的无参数的构造器就不需要多余的配置,如果有带有参数的构造器,那在bean的配置中必须配置初始化参数或者在bean中添加无参数的构造器
8 方法替换
可以使用某个Bean的方法替换另外一个Bean的方法,需要实现MethodReplacer接口
public class Boss implements MethodReplacer{
@Override
public Object reimplement(Object o, Method method, Object[] objects) throws Throwable {
return o;
}
}
<bean id="bean1" class="lookup.Boss" scope="singleton">
<replaced-method name="methodName" replacer="bean2"></replaced-method>
</bean>
采用bean2实现了MethodReplacer接口,配置文件的含义是采用bean2 reimplement方法中的逻辑替换bean1中methodName中对应的逻辑
9 Bean和Bean之间的关系
9.1 继承
和java的继承一样,子Bean可以继承父Bean的行为,也可以修改父Bean的行为
<bean id="boss" class="lookup.Boss" abstract="true">
<property name="" value=""></property>
<property name="" value=""></property>
</bean>
<bean id="boss2" class="lookup.Boss" parent="boss">
<property name="" value=""></property>
<property name="" value=""></property>
</bean>
9.2 依赖
<bean id="car" class="lookup.Car" scope="prototype"></bean>
<bean id="boss" class="lookup.Boss" depends-on="car"></bean>
9.3 引用
<bean id="car" class="lookup.Car" scope="prototype"></bean>
<bean id="boss" class="lookup.Boss">
<property name="car" ref="car"></property>
</bean>
10 Bean的作用域
作用域 | 说明 |
---|---|
singleton | 在容器中Bean只存在一个实例,即Bean是以单例模式存在 |
prototype | 每次请求容器时,都会返回一个新Bean,请求操作类似于new的操作 |
request | 只在一次请求中有作用 |
session | 只在一次会话中有作用 |
globalsession | 全局Session共享一个Bean,类似于application的作用域 |
singleton Bean引用单例Bean
<bean id="car" class="lookup.Car" scope="request">
<aop:scoped-proxy/>
</bean>
<bean id="boss" class="lookup.Boss">
<property name="car" ref="car"></property>
</bean>
car的作用域是request而boss的作用域是singleton,也就说当http请求过来时候都会创建一个car Bean。采用代理后spring可以上的boss引用到对应请求的car。而此时注入boss中的car已经不是原来的car而是car的动态代理对象。该动态代理对象是Car的自类或者实现类,此时Spring在动态代理中加入了当前boss Bean需要注入的是那个请求中的car Bean的判断逻辑
11 整合多个配置文件
在大型系统中可能存在多个xml配置文件,在容器启动时可以通过通过String数组指定这些配置文件,如果要在配置文件层面实现不同配置文件中的Bean互相引用需要使用<import>标签,通过该标签引入后,多个配置文件中的Bean相当于同时配置在同一个文件中,此时就可以在配置文件层面实现互相引用
<import resource="其他配置文件路径"></import>
12 基于注解配置Bean的自动装配
12.1 扫描注解定义的Bean
<context:component-scan base-package="package" resource-pattern="*.class">
<context:include-filter type="" expression="package | package.classNamepattern"></context:include-filter>
<context:exclude-filter type="" expression="package | package.classNamepattern"></context:exclude-filter>
</context:component-scan>
component-scan含有一个默认的属性use-default-filters,其默认值为true(use-default-filters=true),如果该属性为默认值那么会扫描@Component,@Service,@Controller,@Repository标注的Class。eg:假设只需要扫描@Service,此时如果该属性等于true,那么也会扫描其他的组件(@Component,@Controller,@Repository).如果要实现该需求需要将use-default-filters的属性值修改为false。
base-package定义了自动扫描package路径,也就是说需要加载哪些package,支持匹配模式。
resource-pattern定义了在base-package路径需要加载的class,其默认值是**/*.class。即base-package路径下所有的class
context:include-filter含义是需要饱含expression定义中包含的class
context:exclude-filter含义是不包含expression定义中包含的class
这两个标签结合base-package起到了过滤的作用
type属性取值 | 说明 | 示例 | 示例说明 |
---|---|---|---|
annotation | 通过采用目标类是否标注了某个特定Annotation进行过滤 | a.b.XXAnnotation | 所有标注了XXAnnotation的类 |
assignable | 通过采用目标类是否扩展(继承/实现)了某个特定类进行过滤 | a.b.XXService | 所有扩展了XXService的类 |
aspectj | 通过采用类名匹配和目标类是否扩展(继承/实现)了某个特定类进行过滤 | a.b.*Service+ | 以Service结尾的类以及扩展 了扩展了Service结尾的类的类 |
regex | 通过采用正则表达式进行过滤 | a.b.c.* | a.b.c package包下所有的类 |
custom | 采用自定的filter规则扫描类,必须要实现TypeFilter接口 |
@Component | Service | Controller | Repository (value = "beanName")
public class Boss{}
其中value指定了当前类对应的bean的名称,除了Component外还提供了一更为细致化的注解,这些注解标注了该类本身的用途
@Service:用于对Service类进行标注
@Controller:用于对Controller类进行标注
@Repository:用于对DAO类进行标注
13 采用注解装配Bean
@Lazy //延迟注解
@Service(value = "serviceName")
@Order(value = 1)//指定组件的加载顺序
@Scope (value = "scope") //Beand的作用范围
public class Boss implements MethodReplacer{
private Car bossCar;
@Lazy
private final Car bus;
@Autowired(required = false)
public Boss(Car bossCar, @Qualifier("bus") Car bus, List<Car> cars) {
this.bossCar = bossCar;
this.bus = bus;
this.cars = cars;
}
@Autowired
@Qualifier("car")
public void setCar(Car car) {
this.bossCar = car;
}
@Autowired
//将容器中所有类型为Car的Bean注入集合中
private final List<Car> cars;
@Resource("myCar")
private Car myCar;
@PostConstruct //和init-method属性含义一样,init-method只能配置一个mothod,采用注解可以配置多个mothod
public void init() {}
@PreDestroy //和destory-method属性含义一样,destory-method只能配置一个method,采用注解可以配置多个mothod
public void destoryMethod() {}
Autowired和Resource注解都能实现Bean的装配,不同的是前者默认使用的是byType方式后者默认使用的是byName方式,采用byType的方式,如果容器中存在多个类型相同的Bean会抛出异常,此时采用@Autowired(required=false)来解决上述问题,@Autowired和@Qualifier("...")可以将注入方式修改为byName,其中@Qualifier("...")括号中的参数即为该beanName的名称,如果要实施延迟依赖注入,@Lazy注解必须同时标注在属性以及目标Bean上,否则延迟注入无效
14 基于JAVA类配置Bean
@Configuration //将一个类定义为Bean
@Import(Boss.class) //引入外部Bean,该处可以是数组{class1.class,class2.class},可以直接访问引入的Bean
@ImportResource("resourcePath") //引入配置文件,该处可以是数组{resourePath1,resourePath2},可以直接访问引入的Bean
public class Car {
@Bean(name = "Boss") //定义Bean并提供Bean实例化逻辑以及名称
@Scope(value = "prototype")
public Boss getBoss() {
return new Boss();
}
}
基于JAVA类配置的Bean采用如下两种方式中的一种启动Spring容器
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Car.class);
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext();
acac.register(class1.class);
acac.register(class2.class);
acac.refresh();
15 动态向容器中注册Bean
DefaultListableBeanFactory默认实现了ConfigurableListableBeanFactory接口提供了可扩展配置的功能,只需要获得该类实例就可以动态的注册Bean到容器中。为了实现该功能需要实现工厂后置处理器接口BeanFactoryPostProcess
public class DynamicRegistrationBean implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
//1.将ConfigurableListableBeanFactory转换为DefaultListableBeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
//2.创建Bean定义
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Car.class);
//3.设置Bean属性,引用已经存在的Bean
beanDefinitionBuilder.addPropertyReference("name","beanName");
//4.注册Bean
defaultListableBeanFactory.registerBeanDefinition("car", beanDefinitionBuilder.getRawBeanDefinition());
//5.直接注册Bean实例
defaultListableBeanFactory.registerSingleton("bean", new Object());
}
}
16 FectoryBean
实例化过程复杂,导致配置繁琐。此时可以实现该接口实现客制化的实例化过程
T getObject():返回实例化后的Bean,如果singleton返回为true。则实例后被放入Bean缓存池中
boolean isSingleton():判断Bean是否为单实例Bean
Class<?> getObjectTyep():返回Bean类型
public class BikeFactory implements FactoryBean<Bike> {
private String bikeInitInfors;
public void setBikeInitInfors(String bikeInitInfors) {
this.bikeInitInfors = bikeInitInfors;
}
@Override
public Bike getObject() throws Exception {
String[] args = this.bikeInitInfors.split(";");
Bike bike = new Bike();
bike.setter
return bike;
}
@Override
public Class<?> getObjectType() {
return Bike.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
public class Bike {
//properties
//setter
//getter
}
<bean id="bikeFactory" class="lookup.BikeFactory">
<property name="bikeInitInfors"><value>a;b;c;d;e</value></property>
</bean>
<bean id="bike" class="lookup.Bike"></bean>
通过getBean("bike")时,Spring通过反射机制发现BikeFactory实现了FactoryBean接口,此时Spring会调用getObject方法并返回结果。如果想要获得FactoryBean对象可以采用getBean("&bike").