@Autowired三种注入方式的区别以及@Inject注解的基本使用

@Autowired三种注入方式的区别

在Spring中使用@Autowired注解进行依赖注入时,一般有三种注入方式:

  1. Autowired Constructors (构造器注入)
  2. Autowired Methods (setter注入)
  3. 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属性注入时,如果写错了依赖类型,那只有在使用这个依赖时才能发现空指针异常,而不是在项目运行后就及时报出来,这对我们及时排查问题造成了阻碍。

总结

  1. 对于必需的依赖项,建议使用构造器注入,且一般配合final使用,这样保证依赖注入后不可变。使用构造函数注入可以非常明确地表达一个类的依赖关系,使得代码依赖性更加明显和易于维护。此外,由于所有必须的依赖项都必须在构造函数中声明,因此这种方式可以提高代码的可测试性。
  2. 对于可选的依赖,推荐setter注入。setter注入可以避免直接访问类的属性,从而实现更好的封装性。
  3. 属性注入能够减少类的代码,使其更加简洁易懂,但属性注入有可能注入为null,这些错误只有在使用时才会爆出来,另外属性注入也存在一些隐患。
  4. 构造器和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;
    }

    // ...
}

注入的优先级:

  1. Match by Type
  2. Match by Qualifier
  3. 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

猜你喜欢

转载自blog.csdn.net/m0_63323097/article/details/130463814
今日推荐