Spring注解驱动开发(下)

141 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情

接着上篇的内容咱们继续聊Spring的注解驱动开发。

InitializingBean & DisposableBean

先介绍组件的生命周期方法,在前面,我们了解到组件的生命周期方法是initMethod和destroyMethod,其实,组件的生命周期方法有很多,我们一一来看。

InitializingBean接口提供了一个方法afterPropertiesSet,该方法会在对象属性被设置后,即:调用了setter方法之后被调用,事实上, 在该方法调用之前,还有生命周期方法会被调用,比如Bean的前置处理器,这些我们后面再聊,首先来实现InitializingBean接口:

public class User implements InitializingBean {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("setName...");
        this.name = name;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet...");
    }
}

尝试获取User对象,控制台输出结果如下:

setName...
afterPropertiesSet...

与之对应的是DisposableBean接口,该接口提供了destroy方法,Spring会在组件被销毁时调用它,但它的调用是在destroyMethod方法之前的,一定要注意,使用方法和InitializingBean一致:

public class User implements DisposableBean {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("setName...");
        this.name = name;
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("destroy...");
    }
}

BeanPostProcessor

BeanPostProcessor,意为Bean的后置处理器,这是Spring提供的一个接口,该接口有两个生命周期方法,用法非常简单:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("前置处理...");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后置处理...");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

一定要把后置处理器注册到容器中才会生效,而且后置处理器会对被注册到容器中的每个组件生效,也就是说,每个组件的生命周期中都需要经历这两个方法。

感知接口

在某些情况下,我们需要获取Spring提供的一些组件,比如经常使用的ApplicationContext,但是有些场景下始终无法得到,这个时候我们可以使用Spring的感知接口,如下:

  1. ResourceLoaderAware:资源加载器感知接口
  2. BeanNameAware:Bean配置的的名字感知接口
  3. ApplicationContextAware:应用上下文感知接口
  4. BeanFactoryAware:Bean工厂感知接口
  5. MessageSourceAware:MessageSource感知接口
  6. ApplicationEventPublisherAware:ApplicationEventPublisher感知接口

感知接口均提供了对应的方法将组件提供给开发者,比如:

public class User implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

感知接口的方法也处在组件的生命周期中,到这里,所有的组件生命周期方法就介绍完了,我们先对生命周期进行一个总结,写一个例子来感受一下每个生命周期方法的调用时机:

public class User implements ApplicationContextAware, InitializingBean, DisposableBean {

    private String name;
    private Integer age;

    public User() {
        System.out.println("1--》创建User实例");
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("2--》设置User的name属性");
    }

    public void setAge(Integer age) {
        this.age = age;
        System.out.println("2--》设置User的age属性");
    }

    public void init() {
        System.out.println("6--》调用init-method属性指定的方法");
    }

    public void myDestroy() {
        System.out.println("9--》调用destroy-method属性指定的方法");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("3--》调用对应Aware接口的方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("5--》调用InitializingBean接口的afterPropertiesSet方法");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("8--》调用DisposableBean接口的destroy方法");
    }
}

编写一个Bean的后置处理器:

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("7--》调用MyBeanPostProcessor的postProcessBeforeInitialization方法");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("4--》调用MyBeanPostProcessor的postProcessAfterInitialization方法");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }
}

运行结果如下:

1--》创建User实例
2--》设置User的name属性
2--》设置User的age属性
3--》调用对应Aware接口的方法
4--》调用MyBeanPostProcessor的postProcessAfterInitialization方法
5--》调用InitializingBean接口的afterPropertiesSet方法
6--》调用init-method属性指定的方法
7--》调用MyBeanPostProcessor的postProcessBeforeInitialization方法
8--》调用DisposableBean接口的destroy方法
9--》调用destroy-method属性指定的方法

@PropertySource

该注解用于绑定外部的配置文件,通常与@Value注解配合使用,比如我们有如下的一段关于数据源的配置:

jdbc.url=jdbc:mysql:///test
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=root

将其绑定到组件中:

@PropertySource("classpath:jdbc.properties")
@Component
public class MyDataSource {

    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
}

测试代码:

public static void main(String[] args) throws Exception {
    ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
    MyDataSource myDataSource = context.getBean("myDataSource", MyDataSource.class);
    System.out.println(myDataSource);
}

运行结果如下:

MyDataSource(url=jdbc:mysql:///test, driver=com.mysql.jdbc.Driver, username=root, password=root)

@Inject

我们来回顾一下关于自动装配的方式:

  1. @Autowired
  2. @Resource

@Autowired注解默认按类型装配,若是有两个相同类型的组件,则会装配失败,可以配合@Qualifier注解一起使用;而@Resource注解既可以按类型装配,也可以按名字装配,这里我们介绍第三种装配方式:@Inject

@Component
public class User {

    @Inject
    private Pet pet;
}

@Inject注解默认也是按照类型装配的,所以当要装配的组件有多个相同类型可选择时,仍然会抛出熟悉的异常:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.wwj.spring.demo.entity.Pet' available: expected single matching bean but found 2: cat,dog

可以配合@Named注解指定具体装配哪个组件:

@Component
public class User {

    @Inject
    @Named("cat")
    private Pet pet;
}

需要注意的是@Inject注解是没有required属性的,也就是说,@Inject无法做到像@Autowired注解那样,当组件不存在时也不会抛出异常。

@Inject注解还需要引入javax扩展包才能够使用,坐标如下:

<dependency>
  <groupId>javax.inject</groupId>
  <artifactId>javax.inject</artifactId>
  <version>1</version>
</dependency>

@Profile

与@Conditional注解类似,@Profile注解也可以用于指定条件注册组件,不过,@Profile一般用来限制环境条件,比如在测试环境下注册哪些组件,在开发环境注册哪些组件和在生产环境注册哪些组件,看下面的一个例子:

@Configuration
public class MyConfiguration {

    @Profile("dev")
    @Bean
    public User devUser() {
        return new User("开发");
    }

    @Profile("test")
    @Bean
    public User testUser() {
        return new User("测试");
    }

    @Profile("prod")
    @Bean
    public User prodUser() {
        return new User("生产");
    }
}

在配置类中配置了三个组件,它们分别对应不同的环境,此时添加虚拟机参数:-Dspring.profiles.active=dev,则被注册到容器中的组件为:

devUser

若将环境修改为test,则被注册的组件为testUser,prod环境同理。

我们也可以使用代码设置环境信息,从而决定需要注册的组件:

public static void main(String[] args) throws Exception {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //设置需要激活的环境
    context.getEnvironment().setActiveProfiles("test");
    //注册主配制类
    context.register(MyConfiguration.class);
    //启动刷新容器
    context.refresh();
    for (String beanDefinitionName : context.getBeanDefinitionNames()) {
        System.out.println(beanDefinitionName);
    }
}