自动注入(也称为自动装配)是Spring框架中的一个核心概念,它与手动装配相对立,提供了一种更简洁、更灵活的方式来管理Bean之间的依赖关系。
在Spring应用程序中,如果类A依赖于类B,通常需要在类A中定义一个类型为B的属性,并提供相应的setter方法。然而,在自动注入的上下文中,我们无需在XML配置文件中显式地描述这两个类之间的依赖关系。当Spring容器启动时,它会根据配置自动实例化bean。在实例化类A的过程中,Spring容器会检查类A的属性,并尝试自动装配这些属性。由于Spring容器能够识别类A依赖于类B(通过反射或其他机制),它会自动将合适的类B的实例注入到类A的对应属性中。这种由Spring容器自动处理依赖关系的方式称为自动注入。
而与之相对的是手动装配,即程序员在XML配置文件中显式地指定bean之间的依赖关系。在手动装配中,程序员需要明确告诉Spring容器哪些bean应该被注入到哪些属性中。
总结来说,自动注入是Spring框架提供的一种简化依赖管理的机制,它通过自动解析bean之间的依赖关系来减少手动配置的工作量。而手动装配则需要程序员在配置文件中显式地指定依赖关系,如下:
package com.zhaoshu.xmlioc;
import com.zhaoshu.di.A;
import com.zhaoshu.xmlioc.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.jupiter.api.Test
void testdi() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A a = (A) context.getBean("a");
a.print();
}
}
但是实际开发中手动装配的场景比较少(比如在缺少源码的情况下可能会使用这种手动装配情况)。
在Spring框架中,依赖注入(DI)是实现控制反转(IOC)的重要手段,它允许对象在运行时从外部源接收其依赖项,而不是通过硬编码或构造函数参数来创建它们。DI主要有两种主要的实现方式:基于构造方法的依赖注入和基于setter方法的依赖注入。然而,这两种方式并不是唯一的,它们还有各自的变体形式。
首先,基于构造方法的依赖注入是一种常见的方式,它要求将依赖项作为参数传递给被依赖对象的构造函数。这种方式确保了依赖项在对象创建时就被注入,从而确保了对象的完整性和不变性。其次,基于setter方法的依赖注入则允许在对象创建后的某个时间点注入依赖项。它通过在对象上调用setter方法来设置依赖项的值。这种方式提供了更大的灵活性,因为可以在对象的不同生命周期阶段注入不同的依赖项。
除了这两种主要方式外,Spring还支持其他变体形式的依赖注入。例如,通过在类的属性上使用@Autowired注解,Spring可以利用Java的反射机制自动注入依赖项。这种方式可以视为setter注入方式的一种变体,因为它同样是在对象创建后注入依赖项,但无需显式调用setter方法。
在Spring中,无论是手动装配还是自动装配,本质上都是基于上述主要和变体形式的依赖注入方式来实现的。手动装配通常涉及在XML配置文件中显式地指定bean之间的依赖关系,而自动装配则通过Spring的自动检测机制自动解析bean之间的依赖关系,并在需要时注入依赖项。
在Spring框架中,当我们说“基于setter的注入”时,我们通常意味着依赖是通过一个特定的setter方法来注入的,这个setter方法通常会与类的某个私有属性相对应。但是,从Spring容器的角度来看,它并不关心这个setter方法是否确实对应了一个私有属性。在Spring的setter注入中,setter方法的存在仅仅是为了给Spring容器一个“钩子”,让容器可以在创建bean的实例时通过这个方法注入依赖。
因此,你可以说在Spring的setter注入中,setter方法本身并不直接依赖于类的某个私有属性。setter方法只是一个接口,允许Spring容器在创建bean时注入依赖。当然,在实际应用中,你通常会在类内部使用私有属性来保存这些依赖,以便在类的其他方法中使用它们。但这不是Spring容器所关心的,它只关心setter方法的存在和签名。
可能有的读者会认为上面的xml配置文件中已经手动装配了B给A,肯定会调用setXxx方法,其实不然,即是我使用自动装配也还是会调用的。笔者改一下代码,把A的注入模型改成自动注入来看看结果:
把代码改成上面这样自动装配结果是一样的,A类当中的setXxx方法还是会调用,不需要提供任何属性,可能有读者会说如果使用注解呢?比如如下代码:
上面代码同样会注入b,但是是调用反射机制去完成注入的,不是通过setter,当然再次说明一下这是setter的一种变体;上面代码直到A完成注入B后setter方法也不会调用。
重点来了!!!笔者看过很多资料说@Autowired也算自动装配,关于这点笔者不敢苟同,在一个属性上面加@Autowired注解应该属于手动装配,我会通过大量源码和例子来证明笔者的这个理论。
@Autowired注解被理解为自动装配的原因,主要是因为其基于类型的自动装配机制。以下是关于@Autowired和自动装配的详细解释:
- 自动装配的定义:
- 在Spring框架中,自动装配是指Spring容器能够自动满足bean之间的依赖关系,而无需在配置文件中显式指定。
- @Autowired的作用:
- @Autowired是Spring框架提供的一个注解,用于自动装配bean。它可以标注在成员变量、构造器或setter方法上,指示Spring容器自动查找并注入依赖的bean。
- 基于类型的自动装配:
- @Autowired默认采用基于类型的自动装配。当Spring容器在创建一个bean时,会检查该bean中所有被@Autowired标注的字段、构造器参数或方法参数,并在Spring容器中查找与这些字段、参数类型相匹配的bean。
- 如果Spring容器中只有一个与所需类型匹配的bean,那么Spring会自动将其注入到相应的字段或方法中。
- 如果有多个与所需类型匹配的bean,Spring会抛出一个异常,除非使用其他机制(如@Qualifier注解)来指定要注入的bean。
- 其他机制:
- 除了基于类型的自动装配外,Spring还支持基于名称的自动装配(例如使用@Resource注解)。但@Autowired主要是基于类型的。
- @Autowired还可以与@Qualifier注解一起使用,以指定在多个相同类型bean存在时应该注入哪个bean。
- 代码示例:
@Autowired
是 Spring 框架中用于自动装配(Autowiring)的一个核心注解。它允许 Spring 容器自动解析和注入依赖项,而无需使用 XML 配置文件进行显式配置。下面我将通过几个例子来展示如何使用 @Autowired
。
- 成员变量上的
@Autowired
假设我们有两个类,一个是 UserService
类,它依赖于 UserRepository
类。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// ... 其他方法和代码 ...
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
@Repository
public interface UserRepository {
List<User> findAll();
// ... 其他方法 ...
}
// 假设有一个实现类
@Repository
public class UserRepositoryImpl implements UserRepository {
// 实现细节...
}
在这个例子中,UserService
类中的 userRepository
成员变量被 @Autowired
注解标记,这意味着 Spring 容器在创建 UserService
的实例时会自动查找一个 UserRepository
类型的 Bean 并将其注入到 userRepository
变量中。
- 构造器上的
@Autowired
从 Spring 4.3 开始,推荐在构造器上使用 @Autowired
来进行依赖注入。
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// ... 其他方法和代码 ...
}
在这个例子中,UserService
的构造器被 @Autowired
注解标记,这意味着 Spring 容器在创建 UserService
的实例时会自动调用这个构造器,并传入一个 UserRepository
类型的 Bean 作为参数。
- Setter 方法上的
@Autowired
虽然现在不太推荐使用 setter 方法进行自动装配(因为构造器注入提供了更好的不可变性和测试性),但下面是一个例子:
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
// ... 其他方法和代码 ...
}
在这个例子中,setUserRepository
方法被 @Autowired
注解标记,这意味着 Spring 容器在创建 UserService
的实例后会自动调用这个方法,并传入一个 UserRepository
类型的 Bean 作为参数。
注意事项:
- 默认情况下,
@Autowired
是按类型(byType)进行注入的。如果容器中存在多个相同类型的 Bean,你需要使用@Qualifier
注解来指定要注入的 Bean 的名称。 - 在使用
@Autowired
时,要确保你的类在 Spring 容器中是被管理的(即它们有@Component
、@Service
、@Repository
或@Controller
等注解)。 - 如果你的类中有多个构造函数或多个同名的 setter 方法,你需要明确指定哪个应该用于自动装配,否则可能会引发异常。
当你的类中有多个构造函数或多个同名的 setter 方法时,Spring 容器在尝试进行自动装配时可能会遇到困惑,因为它不知道应该使用哪个构造函数或 setter 方法来注入依赖。为了解决这个问题,你可以使用 @Autowired
注解明确指定要使用的构造函数或 setter 方法,或者使用 @Primary
或 @Qualifier
注解来进一步区分 Bean。
- 多个构造函数
如果你有两个构造函数,并且都没有被 @Autowired
明确标记,Spring 容器将不知道在创建 Bean 时应该调用哪个构造函数。在这种情况下,通常最好只提供一个默认构造函数或者标记其中一个构造函数用于自动装配。
@Service
public class UserService {
private final UserRepository userRepository;
// 标记这个构造函数用于自动装配
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 另一个构造函数,可能用于其他目的(如测试)
public UserService(UserRepository userRepository, AnotherDependency anotherDependency) {
// 通常,这个构造函数不会被自动装配,除非你在某个地方明确指定了它
}
// ... 其他方法和代码 ...
}
在这个例子中,只有第一个构造函数被 @Autowired
标记,因此 Spring 容器在创建 UserService
的实例时会调用这个构造函数。
- 多个同名的 setter 方法
如果你的类中有多个同名的 setter 方法(这通常不会发生,因为 setter 方法通常遵循特定的命名约定,如 setFieldName
),并且你试图使用 @Autowired
进行自动装配,Spring 容器同样会不知道应该调用哪个 setter 方法。但是,这种情况在实际开发中很少见,因为 setter 方法的命名通常是根据字段名生成的。
然而,如果你确实遇到了这种情况(可能是通过继承或其他方式),你可以使用 @Autowired
明确指定要使用的 setter 方法,或者使用 @Qualifier
来区分 Bean。但是,由于 setter 方法通常不会同名,因此这个问题通常不会出现。
- 使用
@Primary
或@Qualifier
解决歧义
当你有多个相同类型的 Bean 时,你可以使用 @Primary
或 @Qualifier
来解决歧义。
@Primary
:当存在多个相同类型的 Bean 时,标记其中一个 Bean 为首选 Bean。如果自动装配时发现多个匹配项,首选的 Bean 会被选中。@Qualifier
:在自动装配时,你可以使用@Qualifier
注解明确指定要注入的 Bean 的名称。
@Repository
@Primary // 标记这个 Bean 为首选 Bean
public class PrimaryUserRepository implements UserRepository {
// ... 实现细节 ...
}
@Repository
public class SecondaryUserRepository implements UserRepository {
// ... 实现细节 ...
}
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
@Qualifier("secondaryUserRepository") // 明确指定要注入的 Bean 的名称
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
// ... 其他方法和代码 ...
}
在这个例子中,虽然有两个 UserRepository
类型的 Bean,但 UserService
中的 setUserRepository
方法使用 @Qualifier
注解明确指定了要注入的 Bean 的名称。同时,PrimaryUserRepository
被标记为首选 Bean,如果在其他地方没有指定特定的 Bean 名称,它将会被选中。
总结:
- @Autowired注解之所以被理解为自动装配,是因为它能够根据字段、构造器参数或方法参数的类型,在Spring容器中自动查找并注入匹配的bean,从而实现了bean之间的自动装配。这种基于类型的自动装配机制是@Autowired注解的核心功能之一。
自动装配(Autowiring)的出现确实是为了解决手动装配时配置文件过于冗余和臃肿的问题。在Spring的早期版本中,开发者通常需要在XML配置文件中显式地定义每个bean以及它们之间的依赖关系,这对于复杂的应用程序来说可能会导致配置文件变得非常庞大和难以管理。Spring的自动装配功能通过减少配置文件中需要编写的代码量,来简化bean之间的依赖管理。它允许容器自动检测并满足bean之间的依赖关系,而无需在配置文件中显式地指定这些关系。
之所以会有人把@Autowired也理解为自动装配的原因是因为bytype引起的,因为spring官网有说明自动装配有四种模型分表是no、bytype、byname、constructor
;现在流行着这么一种说法:@Autowired就是通过bytype来完成注入的。如果你也认为这种说法成立,那么就是默认了@Autowired是自动装配且装配模型是bytype。笔者见过很多程序员和很多资料都支持这种说法,其实严格意义上来讲这句话大错特错,甚至有误人子弟的嫌疑。因为bytype仅仅是一种自动注入模型而已,这种模型有其固有的技术手段,而@Autowired是一个注解,这个注解会被spring的后置处理器解析,和处理bytype不是同一回事。如果需要讲清楚他们的区别和证明@Autowired不是自动装配则首先要搞明白什么自动装配。笔者接下来会花一定篇幅来解释自动装配的知识,然后回过头来讲他们的区别和证明@Autowired属性手动装配。
如果现在已经理解了手动装配也叫手动注入,也已经理解了注入方式(setter和构造方法),那么接下来讨论自动注入或者叫自动装配;自动注入的出现是因为手动装配过于麻烦,比如某个类X当中依赖了10个其他类那么配置文件将会变的特别冗余和臃肿,spring的做法是可以为这个X类提供一种叫做自动装配的模型,无需程序员去手动配置X类的依赖关系。有读者会疑问,用注解不也是可以解决这个xml臃肿的问题?确实用注解可以解决,但是我们现在讨论的是自动装配的问题,就不能用注解;为什么不能用注解来讨论自动装配的问题呢?因为在不配置BeanFactoryPostProcessor和修改beanDefinition的情况下注解的类是不支持自动装配的(关于BeanFactoryPostProcessor和beanDefinition的知识笔者以后有时间更新);这也是证明@Autowired默认不是自动装配的一个证据,那又如何证明注解类是默认不支持自动装配呢?下文我会解释一个注解类默认是不支持自动装配的。也就是说如果讨论自动装配最好是用xml形式来配置spring容器才会有意义;比如下面这个例子:
上面代码运行起来,A能注入B,但是在xml配置文件中并没有去手动维护、描述他们之间的依赖关系,而是在xml的根标签上面写了一行default-autowire=“byType”
,其实关于自动注入的歧义或者被人误解的地方就是这个default-autowire="byType"
引起的;那么这行代码表示什么意思呢?表示所在配置在当前xml当中的bean都以bytype这种模式自动装配模式(如果没有特殊指定,因为bean还可以单独配置装配模式的);这需要注意笔者的措辞,笔者说的bytype这自动装配模式,是一种模式,这个不是笔者信口开河,因为在官网文档里面spring也是这么定义的。
自动注入模型和前面提到的依赖注入方式(setter和构造方法)是两回事,简而言之:依赖注入是一个过程,主要通过setter和构造方法以及一些变体的方式完成把对象依赖、填充,这个过程叫做依赖注入,不管手动装配还是自动装配都有这个过程;而自动装配模型是一种完成自动装配依赖的手段体现,每一种模型都使用了不同的技术去查找和填充bean;
从spring官网上面可以看到spring只提出了4中自动装配模型(严格意义上是三种、因为第一种是no,表示不使用自动装配、使用),这四个模型分别用一个整形来表示,存在spring的beanDefinition当中,任何一个类默认是no这个装配模型,也就是一个被注解的类默认的装配模型是no也就是手动装配;其中no用0来表示;bytype用2来表示;
如果某个类X,假设X的bean对应的beanDefinition当中的autowireMode=2则表示这个类X的自动装配模型为bytype;如果autowireMode=1则表示为byname装配模型;需要注意的是官网上面说的四种注入模型其中并没有我们熟悉的@Autowired;可能有读者会提出假设我在A类的某个属性上面加上@Autowired之后这个A类就会不会成了自动装配呢?@Autowired是不是会改变这个类A当中的autowireMode呢?我们可以写一个例子来证明一下:
接下来笔者验证一个通过注解配置的类加上@Autowried后的注入模型的值:
可以看到结果为0,说明这个A类不是自动装配,其实这已经能证明@Autowried不是自动装配了,但是还有更直接的证据证明他不是自动装配,就是通过spring源码当中处理@Autowried的代码可以看出,首先我们写一个bytype的例子看看spring如何处理的:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">
<!--自动装配-->
<bean id="a" class="com.luban.app.A">
</bean>
<bean id="b" class="com.luban.app.B">
</bean>
</beans>
A.java
public class A {
B b;
/**
* 如果是自动装配需要提供setter
* 或者提供构造方法
*/
public void setB(B b) {
this.b = b;
}
}
B.java
public class B {
}
上面代码运行起来,调试spring源码当中的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean方法,这个方法主要就是完成属性填充的,也就是大家说的注入:
可以看上图929行有一个判断,判断当前类的注入模式是否bynane或者bytype如果是其中一种则会进入930行代码执行自动装配的逻辑;因为当前代码我在xml当中配置了自动注入模型为bytype所以这里一定会进入,从上图debug的结果我们可以得知确实也进入了930行,那我们再看看@Autowried到底是否是一样的呢?
我们使用@Autowired注解去注入一个属性的时候spring在完成属性注入的过程中和自动注入(byname、bytype)的过程不同,spring注解跳过了那个判断,因为不成立,而是用后面的代码去完成属性注入;这也是能说明@Autowired不是自动装配的证据、更是直接打脸@Autowired是先bytype的这种说法;当然除了这个证据还有更加直接的证据,先看代码:
其实最关键的就是这行代码
if (ctors != null || mbd.getResolvedAutowireMode() ==AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args);
}
这里有个判断 首先判断 ctors != null 然后判断类的自动注入模型是不是等于AUTOWIRE_CONSTRUCTOR(3);分两种情况来分析:
- 第一种情况:若我们预先设定了类的自动注入模式为通过构造函数(constructor)进行,那么相关的条件判断(采用逻辑或||操作符)将会因为至少有一个条件成立而整个if语句执行。然而,在当前的上下文中,这种情况并不适用,因为作者并未明确指定类A的自动注入模式为特定值(如数字3,这通常不是标准的自动注入模式标识)。根据提供的代码片段,我们可以观察到mbd.getResolvedAutowireMode()的返回值是0,这表示自动装配模式尚未被明确设置或默认为不进行自动装配(即autowireMode=0,对应于AutowireMode.NO)。这再次印证了,在没有明确指定的情况下,注解类默认不会启用自动注入模式。
- 第二种情况:没有指定类的自动注入模型,笔者代码例子就是这种情况,那么spring会首先推断出构造方法,笔者A里面一个带参数的构造方法,所以再进行第一个判断ctors!=
null的时候就已经成立了,于是也会进入。 - 结论:在一个注解类里面提供了一个构造方法之所以能达到和自动注入的效果一样并不是因为这种方式就是自动装配,而是因为spring源码当中做了判断;使这种情况下调用的代码和自动装配调用的逻辑一下。但是有的读者会说那这样也能算自动装配啊,当然如果读者一定这么认为那么也无可厚非;
@Autowried到底和bytype有什么关系呢?为什么有资料会把他们联系在一起呢?
首先bytype是一种自动注入模型,spring会根据类型去查找一个符合条件的bean,如果没找到则不注入,程序也不会报错;如果找到多个spring也不报错,但是也不完成注入,让这个属性等于null,比如你有个接口I,提供两个实现I1和I2都存在容器当中,然后在A类当中去注入I,并且指定自动注入模型为bytype那么这个时候会找到两个符合条件的bean,spring就迷茫了,注入哪个呢?spring哪个都不注入直接让这个属性为null而且也不出异常;但是如果找到了一个那么注入的时候会调用setter,如果没有提供setter就不会。
spring会首先根据属性的类型去容器中找,如果没有找到在根据属性的名字找,找到了则注入,没有找到则异常,下图结果就是容器当中没有一个符合的类型和名字都不符合的则异常;
如果先根据类型找到了一个,那么直接注入,下图就是只有一个符合要求的类型能够完美注入;
如果先根据类型找到了多个,那么spring不会立马异常,而是根据名字再找去找,如果根据名字找到一个合理的则注入这个合理的,下图就是I1和I2都符合,但是spring最后却没有异常,是因为属性名字叫i1故而先类型在名字找到了合理的;
以后如果再听到@Autowried是bytype请你纠正他,bytype是一种自动注入模型;@Autowried是一个注解,两个人没有关系,一点关系都没有;@Autowried讲道理算是手动装配;那么一个注解的类到底能不能开启自动装配呢?答案是可以的,但是需要你对spring比较精通,以后笔者再更新;