本文重点在于充分应用 Spring 提供的 IoC 特性,介绍如何创建一个好用的 Bean。基础篇不涉及后置处理器、 BeanDefinition
以及 Spring 加载原理相关的知识。
引入
ioc 的起源
- ** 接口与实现类的需求变更 ** :最初的静态工厂模式。
- ** 反射机制 ** :延迟加载,避免编译时依赖。
- ** 外部化配置 ** :将 Bean 的全限定名存储在配置文件中。
- ** 缓存机制 ** :存储已创建的 Bean 对象。
入门
使用 Spring
根据xml配置文件来创建一个对象,并且完成属性的注入
依赖查找
使用 ClassPathXmlApplicationContext
创建 Spring 容器,并根据配置文件中的 Bean ID 或类型获取 Bean 对象。
<bean id="person" class="com.linkedbear.spring.basic_dl.a_quickstart_byname.bean.Person"></bean>
java">public static void main(String[] args) throws Exception {BeanFactory factory = new ClassPathXmlApplicationContext("basic_dl/quickstart-byname.xml");Person person = (Person) factory.getBean("person");System.out.println(person);
}
- 通过bean的id查找
getBean(String name)
,需要强转 - 通过类型查找
getBean(Class<T> requiredType)
,不需要强转
依赖注入
创建一个带有属性的 Bean,并注入属性值。
基础类型注入
java">public class Person {private String name;private Integer age;// getter and setter ......
}
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person"><property name="name" value="test-person-byset"/><property name="age" value="18"/>
</bean>
引用类型注入
java">public class Cat {private String name;private Person master;// getter and setter ......
}
<bean id="cat" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Cat"><property name="name" value="test-cat"/><property name="master" ref="person"/>
</bean>
:::info
基础类型 <property name="name" value="xxx"/>
引用类型 <property name="name" ref="xxx"/>
:::
基础
依赖查找高级
根据接口类型获取所有实现bean
java">Map<String, Object> beans = ctx.getBeansOfType(SomeInterface.class);
根据被注解标注的类
java">Map<String, Object> beans = ctx.getBeansWithAnnotation(Color.class);
获取容器中所有 bean
java">String[] beanNames = ctx.getBeanDefinitionNames();
延迟查找(缺省加载)
java">ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
Dog dog = dogProvider.getIfAvailable(Dog::new);
ApplicationContext 与 BeanFactory
ApplicationContext
是BeanFactory
的子接口,提供了更多功能。- AOP 支持、配置元信息、资源管理、事件驱动机制、国际化、Environment 抽象等。
注解驱动Ioc和组件扫描
xml驱动的ioc使用的是ClassPathXmlApplicationContext
,注解驱动的ioc需要使用AnnotationConfigApplicationContext
配置类(对应之前的xml)
java">@Configuration
public class QuickstartConfiguration {@Beanpublic Person person() {Person person = new Person();person.setName("person");person.setAge(123);return person;}@Beanpublic Cat cat() {Cat cat = new Cat();cat.setName("test-cat-anno");// 直接拿上面的person()方法作为返回值即可,相当于refcat.setMaster(person());return cat;}
}
- 配置类使用
@Configuration
标注 - 每一个需要创建出来的bean,使用一个 @Bean 注解标注的方法,方法返回值对应需要创建出来Bean的类型,默认方法名是bean的id(也可以在 @Bean 注解中再次指定)
- 方法体中可以具体去实现bean的创建逻辑包括依赖注入
- 复杂类型注入使用方法的返回值
组件扫描
除了使用 @Bean 来手动指定 Bean 的注册以外,还可以使用组件扫描来根据类路径批量加载 Bean
java">@Configuration
@ComponentScan("com.linkedbear.spring.annotation.c_scan")
public class ComponentScanConfiguration {}@Component
public class Cat {private final String name;private final Person master;@Autowired // spring 4.3 后可省略public Cat(Person master, @Value("test-cat") String name) {this.master = master;this.name = name;}
}
组件扫描 @ComponentScan
-> 扫描所有 @Componnet
及其派生注解(比如@Service …)的类
组件扫描的方式的依赖注入有多种方式,我们目前使用构造器注入
简单类型使用 @Value,复杂类型,默认按照类型注入,如果存在多个@Qualifier
指定bean名称
依赖注入
setter 属性注入
无参构造器 + set 方法
xml -> 之前用的最多的方式
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person"><property name="name" value="test-person-byset"/><property name="age" value="18"/>
</bean>
注解
java">@Bean
public Person person() {Person person = new Person();person.setName("test-person-anno-byset");person.setAge(18);return person;
}
构造器注入
需要添加全参构造器
xml
<bean id="person" class="com.linkedbear.spring.basic_di.b_constructor.bean.Person"><constructor-arg index="0" value="test-person-byconstructor"/><constructor-arg index="1" value="18"/>
</bean>
注解
java">@Bean
public Person person() {return new Person("test-person-anno-byconstructor", 18);
}
组件扫描简单类型注入
java">@Value("0")
private Integer order;
我们在 @Value
中引用的值不仅可以来自常量,还可以使用配置文件中的值,或者使用 SpEL 表达式来引用更复杂的情况(其他 Bean 的值或计算属性)。
PropertySource配置文件注入
@PropertySource({properties配置文件的位置}) 可引入配置文件中的键值对
java">@PropertySource("classpath:basic_di/value/red.properties")
public class InjectValueConfiguration {@Value("${red.name}")private String name;
}
properties 文件(先转换成一个一个map)等配置项信息会读取到 Environment
对象中
补充-> SpEL表达式
在注入时可以使用 #{}
来引用其他bean的属性值,或者使用复杂的计算属性
自动注入机制-@Autowired
@Autowired
java">
@Component
public class Dog {// 1.属性上标注@Autowiredprivate Person person;// 2.构造器上标注@Autowiredpublic Dog(Person person) {this.person = person;}// 3.setter 方法上标注@Autowiredpublic void setPerson(Person person) {this.person = person;}
}
注入的bean不存在
java">@Autowired(required = false)
private Person person;
解决多个相同类型的bean注入冲突
java">@Autowired
@Qualifier("cat")
private Animal animal;@Primary
@Bean
public Animal cat() {return new Cat();
}
@Autowired
注入逻辑分析
- 优先按照类型查找
- 如果存在多个相同类型的bean,再按照名称查找
- 如果存在多个相同类型的bean,且没有指定名称,报错
注入多个相同类型的bean
使用 List
或者 Set
注入
java">@Autowired
private List<Animal> animals; // 会注入所有类型为Animal的bean
@Resource
和 @Inject
注入
@Resource
根据名称注入。@Inject
根据类型注 入,需要额外导入 JSR330 依赖。
回调注入-Aware接口&InitializingBean回调
使用 Aware
接口实现回调注入。 回调注入不依赖于框架的扩展,属于内置功能,如果我们需要一个 不依赖于后处理器 就可以注入的场景,可以选它。@Autowired(其他依赖后处理器的注入机制)依赖于后处理器,某些情况下会失效。
接口名 | 用途 |
---|---|
BeanFactoryAware | 回调注入 BeanFactory |
ApplicationContextAware | 回调注入 ApplicationContext(与上面不同,后续 IOC 高级讲解) |
EnvironmentAware | 回调注入 Environment(后续IOC高级讲解) |
ApplicationEventPublisherAware | 回调注入事件发布器 |
ResourceLoaderAware | 回调注入资源加载器(xml驱动可用) |
BeanClassLoaderAware | 回调注入加载当前 Bean 的 ClassLoader |
BeanNameAware | 回调注入当前 Bean 的名称 |
InitializingBean
可以执行一些初始化操作
java">public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {private static final Logger log = LoggerFactory.getLogger(MyBean.class);@Overridepublic void setBeanName(String name) {log.debug("当前bean " + this + " 名字叫:" + name);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {log.debug("当前bean " + this + " 容器是:" + applicationContext);}@Overridepublic void afterPropertiesSet() throws Exception {log.debug("当前bean " + this + " 初始化");}@Autowiredpublic void aaa(ApplicationContext applicationContext) { // 没有后处理器无法执行log.debug("当前bean " + this + " 使用@Autowired 容器是:" + applicationContext);}@PostConstructpublic void init() { // 没有后处理器无法执行log.debug("当前bean " + this + " 使用@PostConstruct 初始化");}
}
在 Bean 初始化的时候会把对应的对象注入进去(注入到重写的 setApplicationContext
和 setBeanName
形参中,然后我们就可以在这个函数中执行手动赋值的操作,这个函数会在 Bean 生命周期中自动调用)。
:::color1
@Autowired 失效场景
config 配置类中创建 BeanFactoryPostProcessor
,此时,config 配置类的创建实际提前了,BeanPostProcessor
还未创建,所以依赖于BeanPostProcessor
的 @Autowired 自动注入无法生效
:::
bean 的类型
分为普通bean和FactoryBean
工厂bean
FactoryBean
用来创建一些初始化流程比较复杂的bean
java">public interface FactoryBean<T> {// 返回创建的对象@NullableT getObject() throws Exception;// 返回创建的对象的类型(即泛型类型)@NullableClass<?> getObjectType();// 创建的对象是单实例Bean还是原型Bean,默认单实例default boolean isSingleton() {return true;}
}
我们创建一个工厂bean,用于指定某一个类型的bean的生产逻辑,后续就可以直接从spring容器中按照我们顶底好的生产逻辑,直接拿到我们想要的类型的bean了
java">public class ToyFactoryBean implements FactoryBean<Toy> {public ToyFactoryBean() {System.out.println("ToyFactoryBean 初始化了。。。");}private Child child;@Overridepublic Toy getObject() throws Exception {switch (child.getWantToy()) {case "ball":return new Ball("ball");case "car":return new Car("car");default:return null;}}@Overridepublic Class<Toy> getObjectType() {return Toy.class;}public void setChild(Child child) {this.child = child;}
}@Configuration
public class BeanTypeConfiguration {@Beanpublic Child child() {return new Child();}@Beanpublic ToyFactoryBean toyFactory() {ToyFactoryBean toyFactory = new ToyFactoryBean();/// 给工厂bean注入属性toyFactory.setChild(child());return toyFactory;}
}public class BeanTypeAnnoApplication {public static void main(String[] args) throws Exception {ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);// 可以直接拿目标生产的bean,工厂bean本身对用户是不可见的Map<String, Toy> toys = ctx.getBeansOfType(Toy.class);toys.forEach((name, toy) -> {System.out.println("toy name : " + name + ", " + toy.toString());});// 测试工厂bean创建出来bean的作用域Toy toy1 = ctx.getBean(Toy.class);Toy toy2 = ctx.getBean(Toy.class);System.out.println(toy1 == toy2);ToyFactoryBean factoryBean = ctx.getBean(ToyFactoryBean.class);System.out.println(factoryBean);System.out.println(ctx.getBean("toyFactory"));// 拿工厂bean本身需要加 &System.out.println(ctx.getBean("&toyFactory"));}
}
- FactoryBean 加载时机 -> 伴随 ApplicationContext 被加载
- FactoryBean 创建bean的时机 -> 延迟生产,等到getBean的时候再去加载
- 生产出来的bean是否单例 -> 默认单例,可重写 isSingleton() 来改变生产的bean的作用域
- 可通过 & 取出 BeanFactory 本体
:::color1
在 Spring 发展阶段中重要,但目前已经很鸡肋的接口 FactoryBean
的使用要点:
- 作用是用于创建较为复杂的产品如
SqlSessionFactory
,但@Bean
已具备等价功能。 - 使用较为古怪,一不留神就会用错:
a. 被Factory
创建的Bean
:- 会认为创建、依赖注入、
Aware
接口回调、前初始化这些都是FactoryBean
的职责,这些流程都不会走。 - 唯有后初始化的流程会走,也就是产品可以被代理增强。
- 单例的产品不会存储于
BeanFactory
的singletonObjects
成员中,而是另一个factoryBeanObjectCache
成员中。
- 会认为创建、依赖注入、
- b. 按名字获取时,拿到的是产品对象,名字前加
&
获取的是工厂对象。
工厂管理的Bean
在初始化后可以被增强,其他阶段不会。
:::
bean 的作用域
- 单例 -> 默认 -> 每次获取的或者注入的都是同一个对象 ->
ApplicationContext
被初始化就会被创建。 - 原型 Bean -> 指定
@Scope("prototype")
-> 每次获取就会创建一个新的对象 -> 延迟了 Bean 创建的时机。
bean 实例化方式
spring对bean的多种对象创建模式的支持
普通 Bean
通过 <bean>
标签或 @Bean
注解创建。
FactoryBean 方式
通过实现 FactoryBean
接口创建。
静态工厂
这里初始化对象的方法是静态的
java">public class CarStaticFactory {public static Car getCar() {return new Car();}
}
xml 中指定工厂类,以及静态初始化方法即可,自动根据静态初始化方法判断生产出来bean的类型
<bean id="car2" class="com.linkedbear.spring.bean.c_instantiate.bean.CarStaticFactory" factory-method="getCar"/>
注解驱动创建的写法
java">@Bean
public Car car2() {return CarStaticFactory.getCar();
}
静态工厂不会注册到spring容器,只是需要用到这一点静态方法的逻辑
实例工厂
创建bean的方式不是静态的
java">public class CarInstanceFactory {public Car getCar() {return new Car();}
}
<bean id="carInstanceFactory" class="com.linkedbear.spring.bean.c_instantiate.bean.CarInstanceFactory"/>
<bean id="car3" factory-bean="carInstanceFactory" factory-method="getCar"/>
需要先把实例工厂类注册到spring容器,再去指定这个bean是实例工厂bean,并且这个实例工厂bean的实例化方法
注解驱动写法
java">@Bean
public Car car3(CarInstanceFactory carInstanceFactory) {return carInstanceFactory.getCar();
}
先注册工厂,再用工厂指定创建bean逻辑
bean 的生命周期
bean 生命周期时序图
生命周期回调机制
init-method
和destroy-method
。@PostConstruct
和@PreDestroy
。InitializingBean
和DisposableBean
接口。