Spring是一款轻量级的IOC框架,Spring的核心就是Ioc和DI,并通过俩者解耦。Ioc(Inversion of control)控制反转,可以把创建对象和查找依赖对象的权限交给Ioc容器控制,而不是传统的由这些对象的使用方(消费者)进行创建初始化操作。IoC是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。DI(Dependency Injection)依赖注入,指容器复制创建和维护对象之间的依赖关系,而不是通过对象本身复制自己的创建和解决自己的依赖。控制反转是通过依赖注入实现的。

1 概述

在Spring中可通过一些注解(Annotation)来使用Spring DI引擎功能,它们定义在org.springframework.beans.factory.annotation 和 org.springframework.context.annotation包中。我们通常将这些称为“Spring核心注解”,本文将对它们进行介绍。

2 DI相关注解

2.1 @Autowired

我们可以使用@Autowired来标记Spring将要解析和注入的依赖项。有三种注入方式:构造函数注入,setter注入或字段注入。

构造函数注入:

class Car {
    Engine engine;
 
    @Autowired
    Car(Engine engine) {
        this.engine = engine;
    }
}

setter注入

class Car {
    Engine engine;
 
    @Autowired
    void setEngine(Engine engine) {
        this.engine = engine;
    }
}

Field注入

class Car {
    @Autowired
    Engine engine;
}

@Autowired有一个名为required的布尔参数,默认值为true。当它没有找到合适的bean来连接时,Spring 容器将抛出 BeanCreationException 异常。如果为false,表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。

注意,如果我们使用构造函数注入,则所有构造函数参数都是必需的。

从版本4.3开始,除非我们声明至少两个构造函数,否则我们不需要显式地使用@Autowired注解构造函数。

2.2 @Bean

@Bean标记了一个实例化Spring bean的工厂方法:

@Bean
Engine engine() {
    return new Engine();
}

当需要返回类的新实例时,Spring会调用这些方法。

默认情况下bean的名称和方法名称相同,你也可以使用name属性来指定:

@Bean(name="myEngine")
Engine engine() {
    return new Engine();
}

请注意,所有使用@Bean注释的方法都必须在@Configuration类中。

2.3 @Qualifier

使用@Qualifier和@Autowired来提供我们想要在不明确的情况下使用的bean id或bean名称。

例如,以下两个bean实现相同的接口:

class Bike implements Vehicle {}
 
class Car implements Vehicle {}

如果Spring需要注入一个Vehicle bean,它最终会有多个匹配的定义。在这种情况下,我们可以使用@Qualifier注释显式提供bean的名称。

使用构造函数注入:

@Autowired
Biker(@Qualifier("bike") Vehicle vehicle) {
    this.vehicle = vehicle;
}

使用setter注入:

@Autowired
void setVehicle(@Qualifier("bike") Vehicle vehicle) {
    this.vehicle = vehicle;
}

@Autowired
@Qualifier("bike")
void setVehicle(Vehicle vehicle) {
    this.vehicle = vehicle;
}

使用Field注入:

@Autowired
@Qualifier("bike")
Vehicle vehicle;

2.4 @Required

@Required注解适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。

@Required
void setColor(String color) {
    this.color = color;
}

<bean class="com.peterwanghao.annotations.Bike">
    <property name="color" value="green" />
</bean>

2.5 @Value

可以使用@Value将属性值注入bean。它有三种方式:构造函数,setter和字段注入。

构造函数注入:

Engine(@Value("8") int cylinderCount) {
    this.cylinderCount = cylinderCount;
}

Setter注入:

@Autowired
void setCylinderCount(@Value("8") int cylinderCount) {
    this.cylinderCount = cylinderCount;
}
或
@Value("8")
void setCylinderCount(int cylinderCount) {
    this.cylinderCount = cylinderCount;
}

Field注入:

@Value("8")
int cylinderCount;

当然,注入静态值是没有用的。因此,可以使用@Value中的占位符字符串来连接外部资源中定义的值,例如,在.properties或.yaml文件中定义的值。

我们假设.properties文件中定义了:

engine.fuelType=petrol

我们可以使用以下内容注入engine.fuelType的值:

@Value("${engine.fuelType}")
String fuelType;

即使使用SpEL,我们也可以使用@Value。

2.6 @DependsOn

指定Spring容器初始化当前Bean之前先初始化所依赖的Bean。

当依赖项是隐式时,我们只需要这个注释,例如,JDBC驱动程序加载或静态变量初始化。我们可以在依赖类上使用@DependsOn来指定依赖关系bean的名称。

@DependsOn("engine")
class Car implements Vehicle {}

2.7 @Lazy

一般情况下,Spring容器在启动时会创建所有的Bean对象,使用@Lazy注解可以将Bean对象的创建延迟到第一次使用Bean的时候。

这个注解会根据放置的位置产生不同的效果:

  • 与@Bean放在一起,用于延迟方法调用
  • 与@Configuration放在一起,影响类中所有@Bean的方法
  • 与@Component放在一起,这个bean将会懒初始化
  • 与@Autowired放在一起,将懒加载依赖项

此注解具有名为value的参数,其默认值为true。当为false时,代表不延迟立刻加载。

@Configuration
@Lazy
class VehicleFactoryConfig {
 
    @Bean
    @Lazy(false)
    Engine engine() {
        return new Engine();
    }
}

2.8 @Lookup

@Lookup注解是一个作用在方法上的注解,被其标注的方法会被重写,然后根据其返回值的类型,容器调用BeanFactory的getBean()方法来返回一个bean。

2.9 @Primary

有时我们需要定义多个相同类型的bean。在这种情况下,注入将失败,因为Spring不知道我们需要哪种bean。

我们已经看到了一个处理这种情况的方法:用@Qualifier标记所有连接点并指定所需bean的名称。

但是,大多数时候我们需要一个特定的bean而很少需要其他bean。我们可以使用@Primary来简化这种情况:如果我们用@Primary标记最常用的bean,它将在不确定的情况下作为首选:

@Component
@Primary
class Car implements Vehicle {}
 
@Component
class Bike implements Vehicle {}
 
@Component
class Driver {
    @Autowired
    Vehicle vehicle;
}
 
@Component
class Biker {
    @Autowired
    @Qualifier("bike")
    Vehicle vehicle;
}

在前面的例子中,Car是主要的载体。因此,在Driver类中,Spring注入了一个Car bean。当然,在Biker bean中,指定了值将是一个Bike对象。

2.10 @Scope

spring中scope是一个非常关键的概念,简单说就是对象在spring容器(IOC容器)中的生命周期,也可以理解为对象在spring容器中的创建方式。

目前,scope的取值有5种取值:

  • 在Spring 2.0之前,有singleton和prototype两种;
  • 在Spring 2.0之后,为支持web应用的ApplicationContext,增强另外三种:request,session和global session类型,它们只实用于web程序,通常是和XmlWebApplicationContext共同使用。
@Component
@Scope("prototype")
class Engine {}

3 上下文配置注释

3.1 @Profile

们可以在不同的环境中激活不同的配置文件来引导我们需要的bean。使用@Profile注释 - 我们将bean映射到该特定的配置文件。

假设,一个应用的工作环境有:dev、test、prod。我们有一个bean,它应该只在开发期间处于活动状态,但不会在生产中部署。我们使用“ dev ”配置文件注解该bean ,并且它只会在开发期间出现在容器中 - 在生产中,dev将不会处于活动状态:

@Component
@Profile("dev")
public class DevDatasourceConfig

3.2 @Import

导入资源。在应用中,有时没有把某个类注入到IOC容器中,但在运用的时候需要获取该类对应的bean,此时就需要用到@Import注解。

@Import(VehiclePartSupplier.class)
class VehicleFactoryConfig {}

3.3 @ImportResource

使用此注解导入XML配置。我们可以使用locations参数或其别名value参数指定XML文件位置:

@Configuration
@ImportResource("classpath:/annotations.xml")
class VehicleFactoryConfig {}

3.4 @PropertySource

使用此注解,我们可以为应用程序设置定义的属性文件,将properties配置文件中的值存储到Spring的Environment中:

@Configuration
@PropertySource("classpath:/annotations.properties")
class VehicleFactoryConfig {}

@PropertySource引用了Java 8重复注解功能,这意味着我们可以多次使用它标记一个类:

@Configuration
@PropertySource("classpath:/annotations.properties")
@PropertySource("classpath:/vehicle-factory.properties")
class VehicleFactoryConfig {}

3.5 @PropertySources

我们可以使用此注解指定多个@PropertySource配置:

@Configuration
@PropertySources({ 
    @PropertySource("classpath:/annotations.properties"),
    @PropertySource("classpath:/vehicle-factory.properties")
})
class VehicleFactoryConfig {}

注意,自Java 8以来,我们可以通过如上所述的重复注解功能实现相同的功能。

4 结论

在本文中,我们看到了最常见的Spring核心注解的介绍。我们了解了如何配置bean和应用程序上下文,以及如何为组件扫描标记类。