@Autowired三种注入方式的区别以及@Inject注解的基本使用
对于必需的依赖项,建议使用构造器注入,且一般配合final使用,这样保证依赖注入后不可变。使用构造函数注入可以非常明确地表达一个类的依赖关系,使得代码依赖性更加明显和易于维护。此外,由于所有必须的依赖项都必须在构造函数中声明,因此这种方式可以提高代码的可测试性。对于可选的依赖,推荐setter注入。setter注入可以避免直接访问类的属性,从而实现更好的封装性。属性注入能够减少类的代码,使其更加简
文章目录
@Autowired三种注入方式的区别
在Spring中使用@Autowired注解进行依赖注入时,一般有三种注入方式:
- Autowired Constructors (构造器注入)
- Autowired Methods (setter注入)
- Autowired Fields (属性注入)
@Autowired三种注入方式
1、构造器注入
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
由于Bean的声明周期大致有以下阶段:实例化 -> 属性赋值 -> 初始化 -> 销毁。也就是说需要先执行构造方法然后再进行属性依赖注入,在进行实例化过程中,如果在ioc容器中找不到构造参数对应的实例对象,那就不会执行构造函数,这就保障了只要实例化了某对象,那么他的所有依赖都不会为null(前提是所有成员变量都作为构造参数进行注入或者本身附有初始值)。于是在这种使用方式下,在对象实例化阶段就能发现问题并抛出异常,区别于下面说到的属性注入。
从Spring Framework 4.3开始,如果目标bean一开始只定义一个构造函数,那么就不需要在这样的构造函数上使用@Autowired注释。但是,如果有几个构造函数可用,并且没有默认构造函数,则必须至少用@Autowired注释其中一个构造函数,以便指示容器使用哪一个。
lombok注解实现构造器注入
如果觉得构造器注入写起来比较麻烦,可以使用lombok的@RequiredArgsConstructor注解来自动生成带@Autowired的构造器。
注意:使用lombok实现构造器注入的依赖必须是final或者@NonNull修饰,否则lombok不会注入这些依赖。
@RequiredArgsConstructor(onConstructor=@_(@Autowired))
@Controller
public class BasicController {
private final UserService userService;
@RequestMapping("/hello")
@ResponseBody
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
return "Hello " + name;
}
// ...
}
2、setter注入
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
setter注入会在实例化过程之后进行,虽然理论上这样可能会导致依赖为null,但实际上Spring不会把空值赋值给依赖,如果依赖在ioc容器中不存在,spring会抛出异常,所以setter注入和构造器注入在使用效果上是相同的,都会及时抛出异常,区别在于依赖注入的时间不同,一个是在实例化阶段,一个是在属性赋值阶段。
setter注入对方法名称和方法参数的个数没有特定要求,比如:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
上边的例子依然属于一个setter注入。
3、属性注入
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
属性注入和构造器注入可以同时使用。属性注入会被idea提醒 Field injection is not recommended
。
@Autowired执行时刻:属性注入本质上是通过反射的方式直接注入到field,因为只有对象创建完成之后才能调用反射方法进行注射,所以执行时刻是对象创建完成之后。
这种方式可能会存在一些隐患:
问题一
@Autowired
private User user;
private String company;
public UserDaoImpl(){
this.company = user.getCompany();
}
编译过程不会报错,但是运行之后报NullPointerException。
Java 在初始化一个类时,是按照静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 -> @Autowired 的顺序。所以在执行这个类的构造方法时,user对象尚未被注入,它的值还是 null。
问题二
当我们Autowired属性注入时,如果写错了依赖类型,那只有在使用这个依赖时才能发现空指针异常,而不是在项目运行后就及时报出来,这对我们及时排查问题造成了阻碍。
总结
- 对于必需的依赖项,建议使用构造器注入,且一般配合final使用,这样保证依赖注入后不可变。使用构造函数注入可以非常明确地表达一个类的依赖关系,使得代码依赖性更加明显和易于维护。此外,由于所有必须的依赖项都必须在构造函数中声明,因此这种方式可以提高代码的可测试性。
- 对于可选的依赖,推荐setter注入。setter注入可以避免直接访问类的属性,从而实现更好的封装性。
- 属性注入能够减少类的代码,使其更加简洁易懂,但属性注入有可能注入为null,这些错误只有在使用时才会爆出来,另外属性注入也存在一些隐患。
- 构造器和setter注入是推荐的,而属性注入并不推荐,除非依赖关系非常简单明了。
使用 @Inject 代替 @Autowired
从Spring 3.0开始,Spring提供了对JSR-330标准注释(依赖注入)的支持。这些注释的扫描方式与Spring注释相同。要使用它们,在pom文件中引入相关jar包。
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
与 @Autowired一样,您可以在属性、setter方法和构造函数参数上使用@Inject。
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
// ...
}
}
另外,可以通过Provider.get()方法懒加载方式获得依赖并使用它:
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider<MovieFinder> movieFinder;
@Inject
public void setMovieFinder(Provider<MovieFinder> movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
// ...
}
}
还可以配合@Named使用来指定特定实现类:
import javax.inject.Inject;
import javax.inject.Named;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
注入的优先级:
- Match by Type
- Match by Qualifier
- Match by Name
由于@Inject注解没有属性,在加载所需bean失败时,会报错,这是区别于@Autowired的。
参考
- https://docs.spring.io/spring-framework/docs/5.3.27/reference/html/core.html#beans-autowired-annotation
- https://juejin.cn/post/7023618746501562399
- https://blog.csdn.net/qq_39249094/article/details/121028234
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)