前言
Spring
的核心是一个IOC
容器,之前我们利用xml
文件的方式来描述需要注入到IOC
容器中的Bean
,在使用的时候利用该xml
生成一个ApplicationContext
实例,然后根据id
就可以直接获得被SpringIOC
管理的Bean
了。除了这种方式,我们还可以使用配置类加注解的方式来配置Spring
,本文主要分为两大部分,第一部分介绍如果通过配置类和注解来注入Bean
及Bean
的作用范围,第二部分简要介绍Bean
的生命周期
第一部分
配置类
配置类就是在一个普通的类上添加@Configuration
完成,创建一个名为MyConfig
的类,并在其上添加@Configuration
注解。
@Configuration
public class MyConfig {
@Bean
public Person person(){
return new Person(1, "Tom", 23);
}
}
对于每个要注入其中对象,比如上面的Person
,都要实现一个方法,这里约定方法的返回值即是该Bean
的类型,方法的名称即对映于xml
文件中每个Bean
的id
。此外为了让Spring
能够识别出这个Bean
,还要在方法上添加@Bean
注解。在方法内部我们可以通过构造方法等对这个Bean
的属性进行赋值。
利用配置类生成ApplicationContext
在基于xml
文件的方式进行配置时,我们需要利用xml
文件生成一个ApplicationContext
实现类的实例,利用该实例进行Bean
的获取,如下
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
People people = (People) applicationContext.getBean("people");
那么利用配置类如何生成这个ApplicationContext
呢?首先我们看一下ApplicationContext
有哪些具体的实现类
初步发现只有
AnnotationConfigApplicationContext
于注解有关,我们看看它的构造方法
-
AnnotationConfigApplicationContext
的构造方法
/**
* Create a new AnnotationConfigApplicationContext, deriving bean definitions
* from the given component classes and automatically refreshing the context.
* @param componentClasses one or more component classes — for example,
* {@link Configuration @Configuration} classes
*/
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
refresh();
}
这里说的已经很明白了,只要我们传入配置类的.class
对象,就会自动为我们填充这个Context
,让我们实验一下啊
- Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
}
- 输出结果
Person{id=1, name='Tom', age=23}
Process finished with exit code 0
可以看到已经可以成功拿到之前注入到SpringIOC
中的Peson
对象实例了。
利用注解配置扫描器
在使用xml
配置SpringIOC
容器的时候,我们可以利用在xml
文件中配置扫描器,来对某个包目录下的添加了诸如@Component
、@Service
的类进行扫描,并把它们纳入到SpringIOC
中,在使用配置类时,我们可以利用注解来
配置扫描器,从而达到同样的效果。
新建一个包controller
,并在其下创建MyController
测试类,添加相应的注解。
- MyController.java
@Controller
public class MyController {
public void test(){
System.out.println("This is MyContoller");
}
}
利用注解在MyConfig
上配置扫描器,并指明包路径(相对于classpath
)
@Configuration
@ComponentScan(value = "controller")
public class MyConfig {
@Bean
public Person person(){
return new Person(1, "Tom", 23);
}
}
之后我们就可以跟据id
来拿到这个Mycontroller
的实例了,问题是id
是什么呢?在没有手动设置的情况下就是类名首字母小写。
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
MyController myController = (MyController)applicationContext.getBean("myController");
myController.test();
}
}
或者也可以手动设置id
@Controller("mycon")
public class MyController {
public void test(){
System.out.println("This is MyContoller");
}
}
利用@Import注入Bean
利用@Import
,也可以把Bean
注入到IOC
容器中,家属我们有一个Car
的java
类,则在配置类上利用注解
- 利用
@Import
注入
@Configuration
@ComponentScan(value = "controller")
@Import(entity.Car.class)
public class MyConfig {
@Bean
public Person person(){
return new Person(1, "Tom", 23);
}
}
注入到IOC
容器的Car
实例的id
为其全类名,即entity.Car
Car car = (Car)applicationContext.getBean("entity.Car");
利用FactoryBean注入Bean
这里首先要和BeanFactory
区分开,它不是一个工厂类,它的内部包装了一个真正要注入到IOC
容器中对象,并对外暴露该对象。FactroyBean
是一个接口,需要我们实现它,接口中定义了3
个简单的方法。
- 实现
FactroyBean
接口
public class MyFactoryBean implements FactoryBean {
@Override
public boolean isSingleton() {
return false;
}
@Override
public Object getObject() throws Exception {
return null;
}
@Override
public Class<?> getObjectType() {
return null;
}
}
假设我们要利用该MyFactoryBean
把Car
对象注入到IOC
容器中,则可以使用如下方式重写上述3
种方法
public class MyFactoryBean implements FactoryBean {
@Override
public boolean isSingleton() {
return true;
}
@Override
public Object getObject() throws Exception {
return new Car("Benz", 3200000);
}
@Override
public Class<?> getObjectType() {
return Car.class;
}
}
之后我可以利用注解的方式,将MyFactoryBean
交给IOC
容器管理
@Configuration
@ComponentScan(value = "controller")
@Import(entity.Car.class)
public class MyConfig {
@Bean
public Person person(){
return new Person(1, "Tom", 23);
}
@Bean
public MyFactoryBean myFactoryBean(){
return new MyFactoryBean();
}
}
我们可以来获取IOC
容器中这个Bean
对象
Object myFactoryBean = applicationContext.getBean("myFactoryBean");
System.out.println(myFactoryBean.getClass());
输出结果
class entity.Car
可见真正获得的并不是MyFactoryBean
类型本身,而是包装在其中Car
类型。FactoryBean
一般主要被用在框架实现内部,比如AOP
,但我们也可以使用它来对一些Bean
进行包装。
Bean的作用范围
Bean
的作用范围 (Scope
)共有五种,这里先介绍最基础的singleton
和prototype
。
-
singleton
指的是在一个IOC
容器内部,只存放某种类型的唯一实例,即通过applicationContext
拿到的所有引用都指向唯一的实例。默认情况下所有的Bean
都是singleton
的。 - 实验验证
Person person1 = (Person) applicationContext.getBean("person");
Person person2 = (Person) applicationContext.getBean("person");
System.out.println(person1==person2);
- 运行结果
System.out.println(person1==person2);
- 而
prototype
会在每次向IOC
容器请求Bean
的时候为我们创建一个该Bean
类型的新的实例,我们可以利用注解的方式, 修改一个Bean
的Scope
,比如 - 修改
Scope
public class MyConfig {
@Bean
@Scope("prototype")
public Person person(){
return new Person(1, "Tom", 23);
}
}
第二部分
Bean的生命周期
简单的说,Bean
即是由SpringIOC
容器接管的Java
对象,因此一个Bean
的创建销毁过程由SpringIOC
处理,不需要我们过多的关注,但是Spring
也给我们提供了一些方法,使得可以在Bean
的生命周期中某个时间点(创建/销毁)来执行一些特定方法。简单的来说一个Bean
的生命周期主要分为3
大部分,即创建、使用、销毁。使用这个过程不必多说,我们可以通过设置,赖在一个对象创建后,调用一些方法进行初始化,在销毁之前,调用一些方法来进行收尾工作。
利用注解来设置init和destory方法
我们可以在@Bean
注解中补充一些内容,来指明创建之后要执行何种方法,以及在销毁之前之前要执行何种方法
- 配置类
@Configuration
@ComponentScan(value = "controller")
@Import(entity.Car.class)
public class MyConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public Person person(){
return new Person(1, "Tom", 23);
}
}
initMethod
关联的是Person
中的init()
方法,destroyMethod
关联的是Person
中的destory()
方法。
- Person.java
public class Person implements Serializable {
private int id;
private String name;
private int age;
public Person(){};
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public void init() {
System.out.println("Person init");
}
public void destroy() {
System.out.println("Person destroy");
}
}
- Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
Person person = (Person)applicationContext.getBean("person");
((AnnotationConfigApplicationContext) applicationContext).close();
}
}
这里,对于scope=singleton
的Bean
,ApplicationContext
创建时会自动创建这些Bean
,而后执行我们设置好的init()
方法,但是我们需要对ApplicationContext
手动关闭,才能让这些Bean
销毁。
- 运行结果
Person init
Person destroy
@PostConstruct与 @PreDestroy来管理生命周期
对于用@Service
、@Controller
、@Repository
、@Component
注解修饰的Bean
,我们没法用上述的方法设置initMethod
和destoryMethod
,为此Spring
给我们提供了@PostConstruct
与 @PreDestroy
两个注解,被@PostConstruct
修饰的方法会在Bean
创建后调用,相当于initMethod
,被@PreDestroy
修饰的方法会在Bean
销毁前调用,相当于destoryMethod
- MyController.java
@Controller(value="mycon")
public class MyController {
public void test(){
System.out.println("This is MyContoller");
}
@PostConstruct
public void init() {
System.out.println("myController init...");
}
@PreDestroy
public void destory() {
System.out.println("myController destroy...");
}
}
- Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
MyController myController = (MyController)applicationContext.getBean("mycon");
myController.test();
((AnnotationConfigApplicationContext) applicationContext).close();
}
}
- 测试结果
myController init...
This is MyContoller
myController destroy...
利用InitializingBean和DisposableBean接口来管理生命周期
我们还可通过实现InitializingBean
和DisposableBean
接口来完成上述功能,InitializingBean
接口中的afterPropertiesSet()
方法,会在Bean
创建之后调用,DisposableBean
接口中的destroy()
方法会在Bean
销毁前被调用
- Car.java
public class Car implements InitializingBean, DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("Car destory...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Car init...");
}
private String brand;
private int price;
public Car() {
}
public Car(String brand, int price) {
this.brand = brand;
this.price = price;
}
}
- Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
((AnnotationConfigApplicationContext) applicationContext).close();
}
}
- 输出结果
Car init...
Car destory...
利用 BeanPostProcessor接口完成多个Bean的生命周期管理
BeanPostProcessor
也可以用管里Bean
的生命周期,与之前的InitializingBean
和DisposableBean
接口不同,BeanPostProcessor
一次性提供了在bean
创建后以及bean
销毁前两个时期可被调用执行的方法,可以说是InitializingBean
和DisposableBean
接口的结合,此外只要将实现了BeanPostProcessor
接口的类作为一个Bean
注入到IOC
容器中,就可以对该IOC
容器中所有的Bean
起作用,也就是说的它的作用范围是一个IOC
容器,而不是单个Bean
- 实现了
BeanPostProcessor
的A.java
@Component
public class A implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + " init...");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + " destory...");
return bean;
}
}
- Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
((AnnotationConfigApplicationContext) applicationContext).close();
}
}
- 输出
myConfig init...
myConfig destory...
mycon init...
mycon destory...
entity.Car init...
entity.Car destory...
Person的构造方法
person init...
person destory...
myFactoryBean init...
myFactoryBean destory...
Process finished with exit code 0