SpringBoot依赖注入以及相关注解

近期的一个项目中需要搭建一个后端,根据ChatGPT之神的指引,决定使用开源、简洁的后端框架Spring Boot。选这个框架的另一个原因是《服务计算基础》这门课中接触了一下Apache Dubbo、Spring Boot和Apache Axis这几个后端的开发方式,发现还是使用Spring Boot开发一个REST接口最简单轻量,毕竟不是专门要走后端方向,最简单的学习成本才是最重要的。

依赖

读完了依赖~我很快就离开~

在了解依赖注入前需要知道什么是依赖,很显然,这里的依赖并不是更广为人知的那个,程序编译运行时所需要的第三方代码。这里的依赖指的是类在构造时所需要的其他对象,或者说这个类包含了哪些字段,这些字段都可以看成是这个类的依赖。

举个例子(没有代码,自己想象一下吧),一个汽车对象Car需要另一个引擎对象Engine,和轮胎对象Wheel来实例化,而Engine可能还需要齿轮对象Gear来实例化,那么这样的关系就可以构成一个依赖关系了。

常规Java对象中的依赖

“常规Java对象”这个词有点老土了,现在似乎根喜欢用**POJO对象(Plain Old Java Object)**来形容一个没有依赖于特定框架、库,且没有继承关系、接口的对象,这样的对象在开发中一般是用于数据封装和传输的。

在POJO对象中,依赖关系通常是通过构造函数来完成的,这点应该比较容易理解,就不再详述了。

Spring Boot中的依赖

在Spring Boot中,很少提到构造方法这个玩意,这就是因为Spring Boot中的依赖是使用**依赖注入(Dependency Injection,DI)**来实现的。

接下来的内容,是参考w3school的Spring Boot教程以及ChatGPT,结合自己的想象而编写的,可能含有错误。

Bean

在Spring Boot中,(可以)将被依赖的对象抽象为Bean,在Spring Boot启动时,会自动将实例化这些Bean对象,并将它们装入到容器中。当有组件依赖于某个Bean时,Spring Boot就会从容器中取出这个Bean(的引用),传递给这个组件。需要注意的事,Spring Boot中的Bean默认是单例模式,也就是说,如果有多个地方依赖某个Bean,则这个Bean不会被实例化多次,每次传递回去的引用都指向同一个Bean对象。

可以通过方法或类来创建Bean。

Bean的创建——通过方法

如果通过方法来创建Bean,则只需要在相应的方法前添加@Bean注解即可,方法的返回值即为Bean,创建的Bean名字默认为方法名,当然也可以通过@Bean(name="xxx")来修改。另外,最好在这个方法所在的类前加上@Configuration注解,这是为了能够保持Bean在容器中是单例模式。被@Configuration注解的类也会被当做一个Bean,可以通过下一节的方法来为其指定依赖注入的方法。

事实上,开发Spring Boot时一般会专门创建一个Config类,用于提供“提供Bean”的方法。

1
2
3
4
5
6
7
8
9
@Configuration
public class AppConfig {

@Bean
public MyService myService() {
return new MyService();
}
}

如果被@Bean注解的方法依赖其他Bean,则Spring会自动解析依赖并注入。

Bean的创建——通过类

也可以通过类来创建Bean,只需要在类前添加@Component注解,默认名字是类名首字母小写,也可以通过@Component(name="xxx")来创建。

1
2
3
4
5
6
@Component
public class MyService {
public void doSomething() {
System.out.println("Doing something...");
}
}

@Component有三个派生注解,分别为@ServiceRepositoryController,根据具体应用场景的不同,可以选择不同的派生注解。@Service注解和@Component注解几乎没有区别,但是@Service有更明显的语义;@Repository提供了与数据库操作相关的异常处理机制,将底层的JDBC异常转化为Spring的异常;而@Controller是用于提供Web层的请求。

Bean的创建——通过配置文件

也可以通过@ConfigurationProperties将配置文件中的属性值绑定到Bean的字段上,这需要配合@Component注解来使用。

1
2
3
4
5
6
7
8
@Component
@ConfigurationProperties(prefix = "myservice")
public class MyServiceConfig {
private String url;
private int timeout;

// getters and setters
}

随后就可以使用AutoWire来注入这个对象。

注入

创建完Bean之后,需要考虑的就是如何将其注入到对象中去。注入也有多种方法,主要靠的是@AutoWired注解。

字段注入

直接在字段上使用@Autowired,Spring会自动注入该Bean。

1
2
3
4
5
6
7
8
9
@Component
public class MyService {
@Autowired
private AnotherService anotherService;

public void execute() {
anotherService.performTask();
}
}

该方法简洁,代码量少,但是不利于单元测试(因为没有构造函数,可以直接注入时较难模拟)。最重要的是,使用字段注入很容易漏掉一些Bean,使得程序在运行过程中出现空指针,所以不推荐使用。

构造函数注入

可以在构造函数前加上@AutoWired来注入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class MyService {
private final AnotherService anotherService;

@Autowired
public MyService(AnotherService anotherService) {
this.anotherService = anotherService;
}

public void execute() {
anotherService.performTask();
}
}

加上@AutoWired注解后,Spring会自动对构造函数的每个函数进行依赖注入,相较于上一种方法,该方法是强制注入的,可以使得代码更健壮,因为不会出现空指针问题。使用构造函数进行依赖注入有以下规则:

  • 一个(被@Component修饰的)类中 只能有一个构造器使用了@Autowired注解标记
  • 如果没有构造器使用@Autowired注解且存在多个构造器,将选择依赖数量最多的构造器完成注入;
  • 如果没有构造器使用@Autowired注解且只存在一个构造器,将选择这个默认的构造器;

Setter函数注入

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class MyService {
private AnotherService anotherService;

@Autowired
public void setAnotherService(AnotherService anotherService) {
this.anotherService = anotherService;
}

public void execute() {
anotherService.performTask();
}
}

Setter函数注入会在对象创建完成后,所以可以在初始化后修改依赖。所以,该方法也容易遗漏依赖,一般将该方法作为可选依赖的注入方法。

通过@Value进行简单注入

@Value注解用于注入来自配置文件(如application.propertiesapplication.yml)的简单值或表达式。

1
2
3
4
5
6
7
8
9
@Component
public class MyService {
@Value("${myapp.username}")
private String username;

public void execute() {
System.out.println("Username: " + username);
}
}

在这种方式下,@Value可以注入配置文件中的值,也可以直接注入常量或SpEL表达式的结果。

指定名称的注入

当容器中存在多个相同类型的Bean时,@Qualifier可以用来指定要注入的具体Bean。

1
2
3
4
5
6
7
8
9
10
@Component
public class MyService {
@Autowired
@Qualifier("anotherServiceA")
private AnotherService anotherService;

public void execute() {
anotherService.performTask();
}
}

在这种情况下,Spring会注入名为anotherServiceA的Bean。@Qualifier可以和@Autowired一起使用,确保注入正确的Bean。

注入前后的操作

如果想在Bean初始化后执行某些操作(例如初始化一些资源),可以使用@PostConstruct注解的方法。类似地,@PreDestroy可以在Bean销毁之前执行一些清理操作。

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class MyService {
@PostConstruct
public void init() {
// 初始化操作
}

@PreDestroy
public void cleanup() {
// 清理操作
}
}

该注解不能标注在静态方法上,且要求标注的方法不能接受任何参数,返回类型必须是void


SpringBoot依赖注入以及相关注解
http://zhouhf.top/2024/12/01/spring-boot-dependency-injection/
作者
周洪锋
发布于
2024年12月1日
许可协议