前言
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