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

2020-04-30  本文已影响0人  LENN123

前言

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于注解有关,我们看看它的构造方法
/**
     * 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,让我们实验一下啊

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测试类,添加相应的注解。

@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类,则在配置类上利用注解

@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个简单的方法。

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

        Person person1 = (Person) applicationContext.getBean("person");
        Person person2 = (Person) applicationContext.getBean("person");
        System.out.println(person1==person2);
System.out.println(person1==person2);
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()方法。

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");
    }
}

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

@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...");
    }
}
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销毁前被调用

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;
    }
}
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

@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;
    }
}
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
上一篇 下一篇

猜你喜欢

热点阅读