现在是最后一种配置Spring的方式,就是Java代码的方式,依然还是像原来一样,分为Bean,依赖注入,Bean生命周期管理等方面。

通过XML和自动扫描的方式,都不能完全脱离开XML文件,但使用Java代码的方式,无需使用XML文件。

配置的方式是将一个Java类通过@Configuration注解变为Spring的配置类,然后添加组件扫描@ComponentScan,Spring在启动的时候就会读入这个配置类,然后就可以从容器里获得Bean了。

通过配置类自动扫描配置Bean

将上边所有写好了注解的Bean复制到一个新包里,这次不用新建XML文件了,而是在新包里创建一个Java类,名字可以任意,但为了方便,可以叫SpringConfig.java:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("javaConfig")
public class SpringConfig {
}

这里的@Configuration就启用了配置类,之后的@ComponentScan("javaConfig")是启用了当前包的自动扫描功能。

现在先什么都别做,创建一个应用JavaApp.java试着从容器里获取Bean,很显然,这次就不能用加载XML文件的上下文处理器了,而要加载配置类:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class JavaApp {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        BaseballCoach baseballCoach = context.getBean("baseballCoach", BaseballCoach.class);
        System.out.println(baseballCoach.getDailyFortune());
        System.out.println(baseballCoach.getDailyWorkout());
        context.close();
    }
}

结果完全正常获取了Bean和操作Bean。这是最简单的,通过配置类和开启扫描功能来配置Spring容器。

不过这只是Java配置类中比较讨巧的做法,其本质还是自动扫描这种方法。下边要学习如何通过Java配置类直接配置Bean。

通过Java代码配置Bean

这里我们通过新创建一个SwimCoach并一步一步最终完成注入来学习如何使用Java代码配置Bean和之后的依赖注入。在开始之前看一下步骤:

  1. 在配置类中创建一个方法用于描述Bean
  2. 注入Bean的依赖
  3. 载入配置类
  4. 从容器中获取并使用Bean

先清除掉@ComponentScan("javaConfig"),之后创建最简单的SwimCoach类:

public class SwimCoach implements Coach {
    @Override
    public String getDailyWorkout() {
        return "This is SwimCoach work";
    }

    @Override
    public String getDailyFortune() {
        return null;
    }
}

然后在SpringConfig.java中写如下代码:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    @Bean
    public Coach swimCoach() {
        SwimCoach mySwimCoach = new SwimCoach();
        return mySwimCoach;
    }
}

这里要解释几点:

  1. @Bean注解表示这个方法定义了一个Bean
  2. 方法的返回类型Coach表示返回的Bean实现的接口
  3. 方法的名称就是Bean id
  4. 方法体是创建一个Coach的实现对象并且返回这个对象

此时如果运行应用代码:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
SwimCoach swimCoach = context.getBean("swimCoach", SwimCoach.class);
System.out.println(swimCoach.getDailyWorkout());
context.close();

可以发现.getDailyWorkout()已经可以正常工作了。Coach接口的另外一个方法.getDailyFortune()需要依赖注入,因此我们需要定义更多的Bean来达成依赖注入。

通过Java代码配置Bean的依赖

和之前一样,如果要配置依赖,那么被注入的类也要成为一个Bean,因此先给SwimCoach加上一个接受依赖注入的方法,然后修改配置类:

//SwimCoach.java
public class SwimCoach implements Coach {

    public FortuneService fortuneService;

    public SwimCoach(FortuneService fortuneService) {
        this.fortuneService = fortuneService;
    }

    @Override
    public String getDailyWorkout() {
        return "This is SwimCoach work";
    }

    @Override
    public String getDailyFortune() {
        return fortuneService.getFortune();
    }
}

//SpringConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    @Bean
    public FortuneService happyFortuneService() {
        return new HappyFortuneService();
    }

    @Bean
    public Coach swimCoach() {
        SwimCoach mySwimCoach = new SwimCoach(happyFortuneService());
        return mySwimCoach;
    }
}

现在我们配置了一个新的Bean,是基于HappyFortuneService的Bean。最关键的一点,是在swimCoach这个Bean中,将happyFortuneService()方法传入,这就是依赖注入。

之后通过应用调用一下方法:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
SwimCoach swimCoach = context.getBean("swimCoach", SwimCoach.class);
System.out.println(swimCoach.getDailyWorkout());
System.out.println(swimCoach.getDailyFortune());
context.close();

发现成功的调用了.getDailyFortune()方法,完成了依赖注入。

注入字面量

由于没有了XML文件,现在注入属性需要增加一个配置类的注解@PropertySource,沿用之前的sport.properties文件,修改配置类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:sport.properties")
public class SpringConfig {

    @Bean
    public FortuneService happyFortuneService() {
        return new HappyFortuneService();
    }

    @Bean
    public Coach swimCoach() {
        return new SwimCoach(happyFortuneService());
    }
}

这里用@PropertySource配置属性来源为classpath下的sport.properties文件,然后还需要在SwimCoach中设置两个域和进行@Value注解,补上getter方法:

import org.springframework.beans.factory.annotation.Value;

public class SwimCoach implements Coach {

    public FortuneService fortuneService;

    @Value("${foo.email}")
    private String email;
    @Value("${foo.team}")
    private String team;


    public SwimCoach(FortuneService fortuneService) {
        this.fortuneService = fortuneService;
    }

    @Override
    public String getDailyWorkout() {
        return "This is SwimCoach work";
    }

    @Override
    public String getDailyFortune() {
        return fortuneService.getFortune();
    }

    public String getEmail() {
        return email;
    }

    public String getTeam() {
        return team;
    }
}

然后在应用中获取一下值看看:

System.out.println(swimCoach.getEmail());
System.out.println(swimCoach.getTeam());

成功注入了字面量。

小知识

Spring在运行的时候红色的字体是日志记录,在Spring 5.1版之后,日志级别被调整了,默认将不再输出INFO级别的日志。实际上可以单独再创建一个日志的配置类,通过AnnotationConfigApplicationContext加载多个配置类(依次作为参数),具体不详述了。


视频教学没有提到用Java代码配置生命周期管理的方式,这里补充一下。生命周期管理是通过@Bean参数来完成的,比如:

@Bean(initMethod = "setup", destroyMethod = "cleanup")

@Scope("prototype")可以直接添加于Java代码的@Bean之后,比如将SwimCoach设置成prototype:

@Configuration
@PropertySource("classpath:sport.properties")
public class SpringConfig {

    @Bean
    public FortuneService happyFortuneService() {
        return new HappyFortuneService();
    }

    @Bean
    @Scope("prototype")
    public Coach swimCoach() {
        return new SwimCoach(happyFortuneService());
    }
}