日积月累,水滴石穿 😄
前言
我相信@Autowired
注解已经是我们实际开发过程中使用最多的注解之一啦!那你真会了它的全部使用方式吗?接下来,我将带你们去完完全全掌握它。
定义
我们先来看看 @Autowired
注解的定义
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
*/
boolean required() default true;
}
注意@Target
的值,发现它可以在 构造方法上
、方法上
、方法参数上
、字段属性上
、注解类上
可以被使用。而且注解内只有一个属性 required
,并且默认为 true
,该属性的作用是被Autowired
标注的属性是否是必须被注入的。为true
代表是必须的,如果没有找到所匹配的注入项,会抛出异常。如果为false
,没有找到所匹配的注入项,将 null 赋予标注的属性,不会抛出异常。
使用方式
参数上使用
如下例,这种方式肯定是我们最最最常用的方式,没有之一:
@Component
public class UserService {
@Autowired
OrderService orderService;
public UserService(){
System.out.println("无参构造注入" + orderService);
}
public void test(){
System.out.println("test=" +orderService);
}
}
@Component
public class OrderService {
}
@ComponentScan(basePackages = {"com.gongj.populateBean"})
public class AppApplication {
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppApplication.class);
UserService bean = context.getBean(UserService.class);
bean.test();
}
结果:
xml 无参构造注入null
test=com.gongj.populateBean.OrderService@156643d4
构造方法使用:
@Component
public class UserService {
//@Autowired
OrderService orderService;
public UserService(){
System.out.println("xml 无参构造注入" + orderService);
}
@Autowired
public UserService(OrderService orderService1){
System.out.println("有参构造注入" + orderService1);
orderService = orderService1;
}
public UserService(OrderService orderService1,OrderService orderService2){
System.out.println("两个参数有参构造注入" + orderService1 +"==="+ orderService2);
orderService = orderService1;
}
public void test(){
System.out.println("test=" + orderService);
}
}
结果:
有参构造注入com.gongj.populateBean.OrderService@3d012ddd
test=com.gongj.populateBean.OrderService@3d012ddd
在哪个构造方法上加 @Autowired
注解,就使用哪个构造方法实例化对象。
普通方法上使用:
@Autowired
public void yyy(OrderService orderService){
System.out.println("yyy=" + orderService);
this.orderService = orderService;
}
结果:
xml 无参构造注入null
yyy=com.gongj.populateBean.OrderService@6504e3b2
test=com.gongj.populateBean.OrderService@6504e3b2
方法名可以随便起。
方法参数上使用:
public void xxx(@Autowired OrderService orderService){
System.out.println("xxx=" + orderService);
this.orderService = orderService;
}
结果:
xml 无参构造注入null
null
可以看到在方法参数上使用 @Autowired
注解并没有生效。这是为什么呢?其实 Spring
已经说明了。不知道各位有没有注意,在#
注解的源码最上面有一段很长的英文注释,其中有一段是这样的:
* <h3>Autowired Parameters</h3>
* <p>Although {@code @Autowired} can technically be declared on individual method
* or constructor parameters since Spring Framework 5.0, most parts of the
* framework ignore such declarations. The only part of the core Spring Framework
* that actively supports autowired parameters is the JUnit Jupiter support in
* the {@code spring-test} module (see the
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-
reference/testing.html#testcontext-junit-jupiter-di">TestContext framework</a>
* reference documentation for details).
大概意思就是:@Autowired
注解虽然可以在方法参数上被声明,但是被Spring Framework 5.0
的大部分框架所忽略,支持这种写法的只有 spring-test
模块。但其实我使用 4.3.10.RELEASE
在 方法参数上使用
也是没有效果的,在 3.0.5.RELEASE
版本中都不支持在方法参数上
使用@Autowired
注解。
但是你可以在这种情况下使用:
@Bean
public OrderService user(@Autowired OrderService o){
System.out.println("user.o = "+ o);
return new OrderService();
}
结果:
xml 无参构造注入null
user.o = com.gongj.populateBean.OrderService@1cd072a9
test=null
集合、Map、数组注入
@Autowired
private List<OrderService> os;
@Autowired
private Map<String,OrderService> maps;
@Autowired
OrderService[] ar;
public void otherAutowiring() {
System.out.println(os);
System.out.println(maps);
System.out.println(ar);
}
结果:
[com.gongj.populateBean.OrderService@182decdb]
{orderService=com.gongj.populateBean.OrderService@182decdb}
[Lcom.gongj.populateBean.OrderService;@3796751b
如果注入的类型为 Map,其中 key 的类型必须为String
类型, key 的默认值为每个 bean 的beanName
,value为指定的bean类型。这种方式使用的挺多的,Spring
与 策略模式 结合,真的不要再爽了。
类型找到多个
我们都知道@Autowired
是默认按照注入类型(byType
)进行注入的,如果容器中存在两个及以上的相同类型的bean 时,再根据属性名称
或者方法参数名称
进行寻找(byName
)。大部分人都会这么说:先byType
后byName
,其实在 byType
后面还有几层筛选,如果都不满足才会进行 byName
。接下来就分别使用示例介绍这几种方式。
第一步
创建一个IPro
接口,并编写一个get
方法。
public interface IPro {
void get();
}
第二步
创建两个子类,一个是 ProductService
,另外一个是 ScienceService
,并重写其父接口的方法。
@Service
public class ProductService implements IPro{
@Override
public void get() {
System.out.println("ProductService = " + ProductService.class);
}
}
@Service
public class ScienceService implements IPro{
@Override
public void get() {
System.out.println("ScienceService = " + ScienceService.class);
}
}
第三步
创建注入类TypeManyTest
,在TypeManyTest
类中注入 IPro
并调用其get
方法。
@Component
public class TypeManyTest {
@Autowired
IPro pro;
public void test(){
pro.get();
}
}
第四步
修改启动类,并从容器中获得 TypeManyTest
这个bean,调用其 test
方法。
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppApplication.class);
TypeManyTest bean = context.getBean(TypeManyTest.class);
bean.test();
}
如果不出意外,会出现以下异常:
No qualifying bean of type 'com.gongj.populateBean.IPro' available:
expected single matching bean but found 2: productService,scienceService
根据 IPro
类型去找,找到了两个bean,然后再根据 pro
作为 beanName
再去寻找,发现没有匹配的,抛出异常。各位肯定想说,将 pro
修改为 productService
或者scienceService
就好了。的确没毛病,先byType
再byName
。不过这里我先分析其它几种解决方式。
@Qualifier
增加注解@Qualifier
,通过使用 @Qualifier
注解,我们可以明确的指出需要注入哪个 bean。
@Autowired
@Qualifier("productService")
IPro pro;
再次启动项目,结果如下:
ProductService = class com.gongj.populateBean.ProductService
可能各位会觉得@Qualifier
的值不就是 beanName
吗。注意:@Qualifier的值并不一定是beanName,它只是会拿这个值会去与类型匹配成功的 beanName 进行比较而已
。我们也可以在 ProductService
类上使用,也能达到相同的效果。
@Service
@Qualifier("XXXX")
public class ProductService implements IPro{
@Override
public void get() {
System.out.println("ProductService = " + ProductService.class);
}
}
使用:
@Autowired
@Qualifier("XXXX")
IPro pro;
不过可不能这么使用哦!
@Autowired
IPro XXXX;
@Primary
@Primary
可以理解为 默认优先选择,不可以同时设置多个, 其实是 BeanDefinition
的primary
属性。我们在需要默认使用的类上增加注解。
@Service
@Primary
public class ScienceService implements IPro{
@Override
public void get() {
System.out.println("ScienceService = " + ScienceService.class);
}
}
// 需要将之前的注解 @Qualifier("XXXX") 去除,不然还是使用 ProductService这个对象
// 因为 @Qualifier 的解析在 @Primary 之前
使用:
@Autowired
IPro pro;
结果:
ScienceService = class com.gongj.populateBean.ScienceService
@Priority
@Priority
它属于javax.annotation
,JSR250 规范。通过@Priority
来定义优先级,数字越小,优先级越高。
@Service
@Qualifier("XXXX")
@Priority(3) //优先级为 3
public class ProductService implements IPro{
@Override
public void get() {
System.out.println("ProductService = " + ProductService.class);
}
}
@Service
@Priority(1) //优先级为 1
public class ScienceService implements IPro{
@Override
public void get() {
System.out.println("ScienceService = " + ScienceService.class);
}
}
使用:
@Autowired
IPro pro;
结果:
ScienceService = class com.gongj.populateBean.ScienceService
上述都不满足,才进行 byName
匹配。
源码地址
该方法位于 DefaultListableBeanFactory
类。
介绍一下 candidates
这个 Map 的值。就拿上述例子来说,值为两个,因为先根据 IPro
类型能找到两个符合的 Bean。
- scienceService:ScienceService 实例对象
- productService:ProductService 实例对象
@Nllable
protected String determineAutowireCandidate(Map<String, Object> candidates,
DependencyDescriptor descriptor) {
Class<?> requiredType = descriptor.getDependencyType();
// 取@Primary的bean
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
}
// 取优先级最高的bean 通过@Priority来定义优先级,数字越小,优先级越高
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
}
// byName
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateName = entry.getKey();
Object beanInstance = entry.getValue();
if ((beanInstance != null &&
this.resolvableDependencies.containsValue(beanInstance)) ||
// 根据属性名确定
//descriptor.getDependencyName():获得字段属性名称或者方法参数名称
matchesBeanName(candidateName, descriptor.getDependencyName())) {
return candidateName;
}
}
return null;
}
至于对@Qualifier
注解是在另外一个地方处理的,本篇不详细概述。
总结
1、@Autowired
默认按照类型(byType
)进行注入,如果容器中存在两个及以上的相同类型的 bean 时,会存在以下几种筛选情况:
- 1、首先根据
@Qualifier
注解指定的名称去进行匹配,匹配成功则返回。 - 2、寻找被
@Primary
标注的bean,有则返回。 - 3、根据
@Priority(x)
指定的优先级寻找,数字越小,优先级越高。 - 4、最后才会根据
属性名称
或者方法参数名称
进行寻找,如果还没有找到指定名称的 bean,则返回 null。 - 5、判断
@Autowired
的required
属性是否为true
或者要注入的类型不是数组、Collection、Map,则抛出异常。
@Autowired(required = false)
IPro pro;
// 上述这写法还是会抛出异常的
贴一下源码
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
// 没有确认出来唯一需要的beanName
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
// 当前依赖是required,或者不是数组或Collection或Map ,则抛异常
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
return null;
}
}
2、@Autowired
可以标注在同一个类的多个构造器上面,但是required
属性必须都为false
。当有一个required
为true
时,不允许其他构造器标有@Autowired
注解,即使required
属性为false
也不行。
- 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。