@Autowired 与@Resource的异同点

前言

最近写代码的时候,碰到一个小问题;
@Autowired 与@Resource的异同点
可以看到,Spring Boot不推荐使用@Autowired的方式进行属性注入。

我之前一直这样写,从来没觉得这有什么问题,看到这个报错,心里咯噔一下,然后忽然想,@Autowired 与@Resource有什么不同。

然后我发现,我竟然完全不记得了。

因此,写这篇笔记记录一下。

先解释一下,为什么Spring Boot不推荐使用@Autowired的方式进行属性注入。

  • @Autowired是spring定义的注解,与Spring框架强耦合,换成别的框架,可能不支持。
  • @Resource是JSR-250定义的注解,是Java标准,支持几乎所有java框架。

换言之,@Autowired能用的,@Resource也能用,@Autowired不能用的,@Resource还能用。

不过现在java程序员几乎等同于Spring程序员,说这个,似乎没有必要。

简单举例

LaymanController.java

@Controller
public class LaymanController {
    
    @Autowired
    public LaymanService laymanService;

    @GetMapping(value = "/sayHello")
    public void sayHello() {
         laymanService.sayHello();
    }
}

LaymanService

public interface LaymanService {
    void sayHello();
}

LaymanServiceImpl

@Service
public class LaymanServiceImpl implements LaymanService{
	public void sayHello(){
		System.out.println("你好,我是layman"); 
	}		
}

@Autowired

@Autowired默认按类型注入,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false。

public @interface Autowired {
	# 注册时该类型必须存在,如果不存在则报错。
	boolean required() default true;
}

如果注入的类型有可能为null,可以这样配置:@Autowired(required = false)

言归正传,还是说下,下面这个例子的加载顺序:

	@Autowired
    public LaymanService laymanService;

项目启动后,由于我们在LaymanServiceImpl 上配置了@Service注解,那么它会被CompentScan扫描进容器内,注册为bean。

由于未指定name属性,使用默认值(类名首字母转小写)为bean命名(laymanServiceImpl

@Autowired查找类型为LaymanService 的bean,如果找不到,则找名称为laymanService的bean。

很明显,有类型为LaymanService 的bean,名为laymanServiceImpl(多态),注入成功!

@Autowired 与@Resource的异同点

做个小改动,让它报个错

此时我们再添加一个类,叫做LaymanServiceImpl2

LaymanServiceImpl2.java

@Service
public class LaymanServiceImpl2 implements LaymanService{
	public void sayHello(){
		System.out.println("你好,我是layman2"); 
	}		
}

此时,IOC容器中,存在两个类型为LaymanService的bean,一个名为laymanServiceImpl,另一个名为laymanServiceImpl2

那么,项目启动会报错,为什么?

因为@Autowired默认按类型匹配,而且只匹配一个,现在找到两个,它不知道应该注入哪个?

于是它纠结,迷茫,最后一咬牙,还是报错吧。

如何解决该BUG

使用@Qualifier 注解。

修改代码。

LaymanServiceImpl2改动如下:

@Service("laymanService")
public class LaymanServiceImpl2 implements LaymanService{
	public void sayHello(){
		System.out.println("你好,我是layman"); 
	}		
}

@Service("laymanService")为LaymanServiceImpl2 指定bean的name属性

LaymanController.java 改动如下:

	@Autowired
	@Qualifier("laymanService")
    public LaymanService laymanService;

@Autowired扫描到两个类型为LaymanService 的bean,但是@Qualifier指定了bean的名称,因此它会去查找名为laymanService的bean,找到一个,装配成功!

@Autowired加载顺序图

@Autowired 与@Resource的异同点

@Resource

@Resource默认按名称进行装配,且有两个极其重要的属性:name和type

Spring将@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。

逻辑

如果既不指定name,也不指定type,则按照Name进行装配(默认为变量名)
如果指定name,则从上下文中查找名称(id)唯一匹配的bean进行装配,找不到则抛出异常
如果指定type,则从上下文中找到类型唯一匹配的bean进行装配,找不到或者找到多个,抛出异常
如果同时指定name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

// 默认查找名为laymanService的bean,如果找到唯一一个,则匹配,如果找不到,再按类型去查找
@Resource  
public LaymanService laymanService;

@Resource的装配顺序

  1. 既不指定name,也不指定type
    @Autowired 与@Resource的异同点

  2. 指定name

@Autowired 与@Resource的异同点

  1. 指定type
    @Autowired 与@Resource的异同点

  2. 同时指定name和type
    @Autowired 与@Resource的异同点

为什么不推荐使用@Autowired,可是依然那么人用

  • 历史遗留问题(以前就这一种方式,开发用习惯了,改不了。)
  • 省事,bean的名字不用写。(按类型匹配,只要扔个@service就行了),@resource要写@service("service_name"),否则就要两次查找,浪费性能。(@resource先找name,因为默认名不匹配,所以找不到,还会再找type,而@Autowired直接找type)

但是,架构设计考虑性能的话,推荐使用@resource

一是因为耦合度,契合大部份java框架。
二是写上@service("service_name"),访问速度会很快,毕竟可以通过name精准搜索。

缺点是,每个bean都要手动添加name属性。

补充

@AutowiredcommonAnnotationBeanPostProcessor处理器负责处理,而@ResourceautowiredAnnotationBeanPostProcessor处理器负责处理。

有兴趣的可以看看源码:

推荐的源码博客:https://blog.csdn.net/ijavaweb/article/details/25553231

上一篇:SpringBoot resource 资源文件的打包配置详解


下一篇:spring boot 无法启动 相关问题