利用注解配置SpringIOC及Bean的生命周期

前言

Spring的核心是一个IOC容器,之前我们利用xml文件的方式来描述需要注入到IOC容器中的Bean,在使用的时候利用该xml生成一个ApplicationContext实例,然后根据id就可以直接获得被SpringIOC管理的Bean了。除了这种方式,我们还可以使用配置类加注解的方式来配置Spring,本文主要分为两大部分,第一部分介绍如果通过配置类和注解来注入BeanBean的作用范围,第二部分简要介绍Bean的生命周期

第一部分

配置类

配置类就是在一个普通的类上添加@Configuration完成,创建一个名为MyConfig的类,并在其上添加@Configuration注解。

@Configuration
public class MyConfig {
    @Bean
    public Person person(){
        return new Person(1, "Tom", 23);
    }
}

对于每个要注入其中对象,比如上面的Person,都要实现一个方法,这里约定方法的返回值即是该Bean的类型,方法的名称即对映于xml文件中每个Beanid。此外为了让Spring能够识别出这个Bean,还要在方法上添加@Bean注解。在方法内部我们可以通过构造方法等对这个Bean的属性进行赋值。

利用配置类生成ApplicationContext

在基于xml文件的方式进行配置时,我们需要利用xml文件生成一个ApplicationContext实现类的实例,利用该实例进行Bean的获取,如下

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
People people = (People) applicationContext.getBean("people");

那么利用配置类如何生成这个ApplicationContext呢?首先我们看一下ApplicationContext有哪些具体的实现类

Application实现类

初步发现只有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容器中,家属我们有一个Carjava类,则在配置类上利用注解

  • 利用@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;
    }
}

假设我们要利用该MyFactoryBeanCar对象注入到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)共有五种,这里先介绍最基础的singletonprototype

  • 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类型的新的实例,我们可以利用注解的方式, 修改一个BeanScope,比如
  • 修改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=singletonBeanApplicationContext创建时会自动创建这些Bean,而后执行我们设置好的init()方法,但是我们需要对ApplicationContext手动关闭,才能让这些Bean销毁。

  • 运行结果
Person init
Person destroy

@PostConstruct与 @PreDestroy来管理生命周期

对于用@Service@Controller@Repository@Component注解修饰的Bean,我们没法用上述的方法设置initMethoddestoryMethod,为此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接口来管理生命周期

我们还可通过实现InitializingBeanDisposableBean接口来完成上述功能,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的生命周期,与之前的InitializingBeanDisposableBean接口不同,BeanPostProcessor一次性提供了在bean创建后以及bean销毁前两个时期可被调用执行的方法,可以说是InitializingBeanDisposableBean接口的结合,此外只要将实现了BeanPostProcessor接口的类作为一个Bean注入到IOC容器中,就可以对该IOC容器中所有的Bean起作用,也就是说的它的作用范围是一个IOC容器,而不是单个Bean

  • 实现了BeanPostProcessorA.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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353