一、什么是依赖注入?
依赖注入是我们可以用来实现 IoC 的一种模式,其中被反转的控制是设置对象的依赖关系。
将对象与其他对象连接起来,或将对象“注入”到其他对象中,是由程序完成的,而不是由对象本身完成的。以下是我们如何在传统编程中创建对象依赖项:
/**在这个例子中,我们需要在Store类本身中实例化Item接口的实现。*/publicclassStore { privateItemitem; publicStore() { item=newItemImpl1(); } }
通过使用 DI,我们可以重写示例,而无需指定我们想要的Item的实现:
publicclassStore { privateItemitem; publicStore(Itemitem) { this.item=item; } }
二、依赖注入的三种方式
(一)基于构造函数
在基于构造函数的依赖注入的情况下,容器将调用带有参数的构造函数,每个参数代表我们要设置的依赖项。
Spring 主要按类型解析每个参数,然后是属性名称。
- 注解方式
publicclassAppConfig { publicItemitem1() { returnnewItemImpl1(); } publicStorestore() { returnnewStore(item1()); } }
@configuration注释表示该类是一个配置类,试用java配置替代xml配置
我们使用@Bean注解在方法中定义bean。如果我们未指定自定义名称,则Bean名称将默认为方法名称。
对于带有默认单例范围的bean,Spring首先检查Bean的缓存实例是否已存在,如果它没有创建新的字段。如果我们使用的原型范围,容器会返回每个方法调用的新bean实例。
从 Spring 4.3 开始,具有单个构造函数的类可以省略@Autowired注释。
- xml配置方式
<beanid="item1"class="org.baeldung.store.ItemImpl1"/><beanid="store"class="org.baeldung.store.Store"><constructor-argtype="ItemImpl1"index="0"name="item"ref="item1"/></bean>
(二)基于setter方法
对于基于SATTER的注入,容器将在调用NO-Argument构造函数或No-Argument静态工厂方法以实例化Bean之后调用我们类的Setter方法。让我们使用注解创建此配置:
publicclassUserService { privateWolf3Beanwolf3Bean; //通过setter方法实现注入publicvoidsetWolf3Bean(Wolf3Beanwolf3Bean) { this.wolf3Bean=wolf3Bean; } }
xml方式
<beanid="store"class="org.baeldung.store.Store"><propertyname="item"ref="item1"/></bean>
(三)基于属性
在基于属性的依赖注入,我们可以通过用@Autowired注释标记它们来注入依赖项:
publicclassStore { privateItemitem; }
@Autowrite 和 @Resource 以及 @Qualifier 注解的区别
注入一个 Bean 可以通过 @Autowrite,也可以通过 @Resource 注解来注入,这两个注解有什么区别呢?
@Autowrite:通过类型去注入,可以用于构造器、set参数、属性注入。当我们注入接口时,其所有的实现类都属于同一个类型,所以就没办法知道选择哪一个实现类来注入。
@Resource:默认通过名字注入,不能用于构造器和参数注入。如果通过名字找不到唯一的 Bean,则会通过类型去查找。如下可以通过指定 name 或者 type 来确定唯一的实现:
@Resource(name = "wolf2Bean",type = Wolf2Bean.class)
private IWolf iWolf;
而 @Qualifier 注解是用来标识合格者,当 @Autowrite 和 @Qualifier 一起使用时,就相当于是通过名字来确定唯一一个对象
@Qualifier("wolf1Bean")
@Autowired
private IWolf iWolf;
@Qualifier特殊用途
@Resource 注解又是不能用在参数中,下面的这种情况就需要使用 @Qualifier 注解来确认唯一实现了(比如在配置多数据源的时候就经常使用 @Qualifier 注解来实现)
publicclassInterfaceInject2 { publicMyElementtest( ("wolf1Bean") IWolfiWolf){ returnnewMyElement(); } }
@Primary
在某些情况下,我们需要注册多个相同类型的 bean。我们使用 @Primary给一个 bean 更高的优先级。
在这个例子中,我们有Employee类型的JohnEmployee()和TonyEmployee() bean :
publicclassConfig { publicEmployeeJohnEmployee() { returnnewEmployee("John"); } publicEmployeeTonyEmployee() { returnnewEmployee("Tony"); } }
如果我们尝试运行应用程序,Spring 会抛出 NoUniqueBeanDefinitionException。
要访问具有相同类型的 bean,我们通常使用 @Qualifier(“beanName”)注释。
我们将它与@Autowired一起应用在注入点。在我们的例子中,我们在配置阶段选择了 bean,所以 @Qualifier不能在这里应用。我们可以通过以下链接了解更多关于 @Qualifier注解的信息。
为了解决这个问题,Spring 提供了 @Primary注释。
- 将@Primary与@Bean 一起使用
让我们看一下配置类:
publicclassConfig { publicEmployeeJohnEmployee() { returnnewEmployee("John"); } publicEmployeeTonyEmployee() { returnnewEmployee("Tony"); } }
我们用 @Primary 标记 TonyEmployee () bean 。Spring 将优先于JohnEmployee()注入TonyEmployee() bean 。
现在,让我们启动应用程序上下文并从中获取Employee bean:
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(Config.class); Employeeemployee=context.getBean(Employee.class); System.out.println(employee); /**运行程序后得到Employee{name='Tony'}**/
2. 将@Primary与@Component一起使用
让我们看一下以下场景,有两个类实现下面接口
publicinterfaceManager { StringgetManagerName(); } publicclassDepartmentManagerimplementsManager { publicStringgetManagerName() { return"Department manager"; } } publicclassGeneralManagerimplementsManager { publicStringgetManagerName() { return"General manager"; } }
当下面的service使用Manager接口时,注入的是@Primary 标记的GeneralManager
publicclassManagerService { privateManagermanager; publicManagergetManager() { returnmanager; } } ManagerServiceservice=context.getBean(ManagerService.class); Managermanager=service.getManager(); System.out.println(manager.getManagerName());
三、Autowired注解的实现原理
依靠@Autowired注解实现属性注入主要利用了java中的反射原理,spring在创建bean的过程中,对bean的属性进行填充,判断属性是否有@Autowired注解,如果有这个注解,spring就会帮忙在容器中找属性对用的bean对象,再利用反射原理,对属性进行赋值。
- @Autowired注解的实现是通过后置处理器AutowiredAnnotationBeanPostProcessor类的postProcessPropertyValues()方法实现的。
- 当自动装配时,从容器中如果发现有多个同类型的属性时,@Autowired注解会先根据类型判断,然后根据@Primary、@Priority注解判断,最后根据属性名与beanName是否相等来判断,如果还是不能决定注入哪一个bean时,就会抛出NoUniqueBeanDefinitionException异常。