Spring依赖注入

Spring依赖注入

spring依赖注入包括三种:1.setter注入,2.构造器注入,3.属性注入

1.基于构造器注入

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserServiceImpl {

private final UserDao userDao;

@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}

}

2.基于setter注入

1
2
3
4
5
6
7
8
9
@Service
public class UserServiceImpl {

private UserDao userDao;

@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

3.基于Filed(注解)注入

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserServiceImpl {

@Autowired
//@Inject
private UserDao userDao;

//简单的使用例子,下同
public List listFoo() {
return userDao.list();
}

我们使用最多的方式就是属性注入的方式:

  1. 注入方式非常简单:加入要注入的字段,附上@Autowired,即可完成。
  2. 使得整体代码简洁明了,看起来美观大方

4.spring为什么推荐构造器注入方式?

先来看看Spring在文档里怎么说:

The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.”

简单的翻译一下:这个构造器注入的方式能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任。

对于这个类可能承担了过多的责任这个问题,说明你的类当中有太多的责任,那么你要好好想一想是不是自己违反了类的单一性职责原则,从而导致有这么多的依赖要注入。

  • 依赖不可变:其实说的就是final关键字。

  • 依赖不为空(省去了我们对其检查):当要实例化UserServiceImpl的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。

  • 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法),所以返回来的都是初始化之后的状态。

5.@Autowired、@Resource@Inject

Spring 支持使用@Autowired, @Resource, @Inject 三个注解进行依赖注入。那@Autowired和@Resource以及@Inject等注解注入有何区别

5.1 @Autowired

在Spring 2.5 引入了 @Autowired 注解:

1
2
3
4
5
6
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}

从Autowired注解源码上看,可以使用在下面这些地方:

1
2
3
4
5
@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.PARAMETER) #方法参数
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.ANNOTATION_TYPE) #注解
  • 字段属性
1
2
@Autowired
private HelloDao helloDao;
  • 构造函数,方法参数
1
2
3
4
5
6
7
private HelloDao helloDao;

//@Autowired
public HelloServiceImpl(@Autowired HelloDao helloDao) {
this.helloDao = helloDao;
}
// 构造器注入也可不写@Autowired,也可以注入成功。
  • 方法
1
2
3
4
5
6
7
8
9
10
private HelloDao helloDao;

public HelloDao getHelloDao() {
return helloDao;
}

@Autowired
public void setHelloDao(HelloDao helloDao) {
this.helloDao = helloDao;
}

将@Autowired写在被注入的成员变量上,setter或者构造器上,就不用再xml文件中配置了。

如果有多个类型一样的Bean候选者,则默认根据设定的属性名称进行获取。如 HelloDao 在Spring中有 helloWorldDao 和 helloDao 两个Bean候选者。

首先根据类型获取,发现多个HelloDao,然后根据helloDao进行获取,如果要获取限定的其中一个候选者,结合@Qualifier进行注入。

1
2
3
@Autowired
@Qualifier("helloWorldDao")
private HelloDao helloDao;

注入名称为helloWorldDao 的Bean组件。@Qualifier("XXX") 中的 XX是 Bean 的名称,所以 @Autowired@Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了。

注入名称为helloWorldDao 的Bean组件。@Qualifier("XXX")中的 XX是 Bean 的名称,所以 @Autowired@Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了。

多个类型一样的Bean候选者,也可以@Primary进行使用,设置首选的组件,也就是默认优先使用哪一个。

注意:使用@Qualifier 时候,如何设置的指定名称的Bean不存在,则会抛出异常,如果防止抛出异常,可以使用:

1
2
3
@Qualifier("xxxxyyyy")
@Autowired(required = false)
private HelloDao helloDao;

举例:

声明一个接口UserDao与2个实现类UserDaoImpl、UserDaoImpl2,如下:

UserDao:

1
2
3
public interface UserDao {
public void sayHello(String data1, int data2);
}

UserDaoImpl:

1
2
3
4
5
6
7
8
@Repository
public class UserDaoImpl implements UserDao {

@Override
public void sayHello(String data1, int data2) {
System.out.println("hello111..." + data1 + " " + data2);
}
}

UserDaoImpl2:

1
2
3
4
5
6
7
8
@Repository
public class UserDaoImpl2 implements UserDao {

@Override
public void sayHello(String data1, int data2) {
System.out.println("hello222..." + data1+" " + data2);
}
}

UserServiceImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service("userService")
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;

@Override
public void gogo(String data1,int data2) {
System.out.println("UserServiceImpl.gogo");
userDao.sayHello(data1,data2);
}

}

然后通过测试类:

1
2
3
4
5
6
@Test
public void test(){
ApplicationContext applicationContext= new ClassPathXmlApplicationContext("com/xxx/ioc/spring-config.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.gogo("xx1",798);
}

就会发现控制台报错:

1
2
3
4
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.itszt.ioc.dao.UserDao' available: expected single matching bean but found 2: userDaoImpl,userDaoImpl2
... ...

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.itszt.ioc.dao.UserDao' available: expected single matching bean but found 2: userDaoImpl,userDaoImpl2

加上@Qualifier注解,:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service("userService")
public class UserServiceImpl implements UserService {

@Autowired
@Qualifier(value = "userDaoImpl")
private UserDao userDao;

@Override
public void gogo(String data1,int data2) {
System.out.println("UserServiceImpl.gogo");
userDao.sayHello(data1,data2);
}

}

则运行正常:

1
2
3
17:35:58,153  INFO XmlBeanDefinitionReader:317 - Loading XML bean definitions from class path resource [com/xxx/ioc/spring-config.xml]
UserServiceImpl.gogo
hello111...xx1 798
  • 简单总结

1、@Autowired是Spring自带的注解,通过AutowiredAnnotationBeanPostProcessor 类实现的依赖注入

2、@Autowired可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION_TYPE

3、@Autowired默认是根据类型(byType )进行自动装配的

4、如果有多个类型一样的Bean候选者,需要指定按照名称(byName )进行装配,则需要配合@Qualifier。

指定名称后,如果Spring IOC容器中没有对应的组件bean抛出NoSuchBeanDefinitionException。也可以将@Autowired中required配置为false,如果配置为false之后,当没有找到相应bean的时候,系统不会抛异常。

如果容器中有同种类型的多个实现,使用@Autowired则会抛出NoUniqueBeanDefinitionException异常,存在多个实现类会不知道选择哪一个而报错。所以说,存在多个实现类的情况,不能用byType 的形式。

5.2 @Resouce

  • Resource注解源码
1
2
3
4
5
6
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
// 其他省略
}

从Resource注解源码上看,可以使用在下面这些地方:

1
2
3
@Target(ElementType.TYPE) #接口、类、枚举、注解
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.METHOD) #方法

name 指定注入指定名称的组件。

  • 字段属性
1
2
@Resouce
private HelloDao helloDao;

同理,如果有多个候选bean,则也会抛出NoUniqueBeanDefinitionException异常

也可以直接指定注入的bean,如:

1
2
@Resource(name = "userDaoImpl")
private UserDao userDao ;

name 的作用类似 @Qualifier,所以Autowired

  • 简单总结

1、@Resource是JSR250规范的实现,在javax.annotation包下

2、@Resource可以作用TYPE、FIELD、METHOD上

3、@Resource是默认根据属性名称进行自动装配的,如果有多个类型一样的Bean候选者,则可以通过name进行指定进行注入

5.3 @Inject

  • Inject注解源码
1
2
3
4
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}

从Inject注解源码上看,可以使用在下面这些地方:

1
2
3
@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.FIELD) #字段、枚举的常量
  • 字段属性
1
2
@Inject
private HelloDao helloDao;

同理,如果有多个候选bean,则也会抛出NoUniqueBeanDefinitionException异常

也可以直接指定注入的bean,如:

1
2
3
@Inject
@Named(name = "userDaoImpl")
private UserDao userDao ;

name 的作用类似 @Qualifier,所以Autowired

  • 简单总结

1、@Inject是JSR330 (Dependency Injection for Java)中的规范,需要导入javax.inject.Inject jar包 ,才能实现注入

2、@Inject可以作用CONSTRUCTOR、METHOD、FIELD上

3、@Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named;

6.@Autowired、@Resource@Inject总结

1、@Autowired是Spring自带的@Resource是JSR250规范实现的@Inject是JSR330规范实现的

2、@Autowired@Inject用法基本一样,不同的是@Inject没有required属性。

3、@Autowired@Inject是默认按照类型匹配的,@Resource是按照名称匹配的。

4、@Autowired如果需要按照名称匹配需要和@Qualifier一起使用@Inject和@Named一起使用@Resource则通过name进行指定

7.Read more

:lollipop::https://pdai.tech/md/spring/spring-x-framework-ioc.html

:lollipop::https://www.cnblogs.com/diandianquanquan/p/11518365.html


博客说明

文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,不用于任何的商业用途。如有侵权,请联系本人删除。谢谢!


Spring依赖注入
https://nanchengjiumeng123.top/2022/05/27/framework/spring/Spring IoC/2022-05-22_Spring依赖注入/
作者
Yang Xin
发布于
2022年5月27日
许可协议