在Spring中使用@Autowired注解进行依赖注入时,一般有三种注入方式:
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的@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;
}
// ...
}
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注入。
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属性注入时,如果写错了依赖类型,那只有在使用这个依赖时才能发现空指针异常,而不是在项目运行后就及时报出来,这对我们及时排查问题造成了阻碍。
从Spring 3.0开始,Spring提供了对JSR-330标准注释(依赖注入)的支持。这些注释的扫描方式与Spring注释相同。要使用它们,在pom文件中引入相关jar包。
<dependency>
<groupId>javax.injectgroupId>
<artifactId>javax.injectartifactId>
<version>1version>
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;
}
// ...
}
注入的优先级:
由于@Inject注解没有属性,在加载所需bean失败时,会报错,这是区别于@Autowired的。