一、相同点
这个两个注解都是用来完成组件的装配的,即利用依赖注入(DI),完成对IOC容器当中各个组件之间依赖的装配赋值。
二、不同点
2.1 来源不同
2.1.1 @Resource
@Resource
是javaEE的注解,它遵循的是JSR-250规范,需要导入包javax.annotation.Resource
。
2.1.2 @Autowired
@Autowired
为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired
。
2.2 装配顺序不同
2.2.1 @Resource
默认按照byName方式进行装配,属于J2EE自带注解,没有指定name时,name指的是变量名。
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常。
如果既没有指定name,又没有指定type,则自动按照byName方式进行装配。如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
2.2.2 @Autowired
- 默认按byType自动注入,是Spring的注解。
- 默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,
@Autowired(required = false)
。 - 按类型装配的过程中,如果发现找到多个bean,则又按照byName方式进行比对,如果还有多个,则报出异常。
装配顺序图
1、@Autowired的装配顺序图
2、@Resource的装配顺序图
2.1 如果同时指定了name和type
2.2 如果指定了name
2.3 如果指定了type
2.4 如果既没有指定name,也没有指定type
三、选择
@Autowired
跟Spring强耦合了,如果换成了JFinal等其他框架,功能就会失效。而@Resource
是JSR-250提供的,它是Java标准,绝大部分框架都支持,因此个人更倾向于选@Resource
。
四、样例代码
4.1 接口只有一个实现类
4.1.1 接口
public interface Animal {
String talk();
}
4.1.2 实现类
/**
* 注意:@Service 默认value=dog,首字母小写
*/
@Service
public class Dog implements Animal {
@Override
public String talk() {
return "Dog talk:";
}
}
4.1.3 装配类
@RestController
@Controller(value = "/zoo")
@RequestMapping("/zoo")
public class ZooController {
/**
* 1、装配方式:既没有指定name,又没有指定type,则默认按照byName方式进行装配。
* 2、装配过程:通过byName没有找到name=animal的对象,则用byType去装配,找到了type为Animal的对象Dog,这里注入的是Dog对象。
*/
@Resource
private Animal animal;
/**
* 1、装配方式:既没有指定name,又没有指定type,则默认按照byName方式进行装配。
* 2、装配过程:通过byName找到了name=dog的对象,直接注入。
*/
@Resource
private Animal dog;
/**
* 1、装配方式:默认按byType自动注入
* 2、装配过程:通过byType去装配,找到type为Animal的对象Dog,这里注入的是Dog对象。
*/
@Autowired
private Animal animal2;
@GetMapping("/talk")
public void animalTalk() {
animal.talk();
dog.talk();
animal2.talk();
}
}
4.1.4 结果
可以正常编译启动,运行正常。
4.2 接口有多个实现类
4.2.1 接口
public interface Animal {
String talk();
}
4.2.2 实现类
/**
* 注意:@Service 默认value=dog,首字母小写
*/
@Service
public class Dog implements Animal {
@Override
public String talk() {
return "Dog talk:";
}
}
/**
* 注意:@Service 默认value=cat,首字母小写
*/
@Service
public class Cat implements Animal {
@Override
public String talk() {
return "Cat talk";
}
}
4.2.3 装配类
4.2.3.1 @Resource默认按byName-失败
@RestController
@Controller(value = "/zoo")
@RequestMapping("/zoo")
public class ZooController {
/**
* 1、装配方式:既没有指定name,又没有指定type,则默认按照byName方式进行装配。
* 2、装配过程:通过byName没有找到name=animal的对象,则用byType去装配,找到了type为Animal的对象有两个:Dog、Cat,因此注入失败。
*/
@Resource
private Animal animal;
}
启动错误信息
No qualifying bean of type 'com.alanchen.Animal' available: expected single matching bean but found 2: cat,dog
4.2.3.2 @Resource默认按byName-成功
@RestController
@Controller(value = "/zoo")
@RequestMapping("/zoo")
public class ZooController {
/**
* 1、装配方式:既没有指定name,又没有指定type,则默认按照byName方式进行装配。
* 2、装配过程:通过byName找到了name=dog的对象,直接注入。
*/
@Resource
private Animal dog;
}
4.2.3.3 @Resource指定name按byName-成功
@RestController
@Controller(value = "/zoo")
@RequestMapping("/zoo")
public class ZooController {
/**
* 1、装配方式:指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
* 2、装配过程:通过byName找到了name=dog的对象,直接注入。
*/
@Resource(name = "dog")
private Animal animal;
}
4.2.3.4 @Autowired默认按byType-失败
@RestController
@Controller(value = "/zoo")
@RequestMapping("/zoo")
public class ZooController {
/**
* 1、装配方式:默认按byType自动注入
* 2、装配过程:通过byType去装配,找到了type为Animal的对象有两个:Dog、Cat,因此注入失败。
*/
@Autowired
private Animal animal;
}
启动错误信息
Field animal in com.alanchen.ZooController required a single bean, but 2 were found:
4.2.3.5 @Autowired和@Qualifier一起配合按byName-成功
@RestController
@Controller(value = "/zoo")
@RequestMapping("/zoo")
public class ZooController {
/**
* 1、装配方式:和@Qualifier一起配合按byName
* 2、装配过程:通过byName找到了name=dog的对象,直接注入。
*/
@Autowired
@Qualifier("dog")
private Animal animal;
}
五、Spring注解
5.1 @Qualifier
@Qualifier
意思是合格者,一般跟@Autowired
配合使用,需要指定一个bean的名称,通过bean名称就能找到需要装配的bean。
5.2 @Primary
当我们使用自动配置的方式装配bean时,如果这个bean有多个候选者,假如其中一个候选者具有@Primary
注解修饰,该候选者会被选中,作为自动配置的值。
六、@Autowired的使用范围
平时我们使用@Autowired
都是使用在成员变量上,但@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;
}
6.1 成员变量
在成员变量上使用Autowired注解
@Service
public class UserService {
@Autowired
private IUser user;
}
6.2 构造器
在构造器上使用Autowired注解
@Service
public class UserService {
private IUser user;
@Autowired
public UserService(IUser user) {
this.user = user;
System.out.println("user:" + user);
}
}
在构造器上加Autowired注解,实际上还是使用了Autowired装配方式,并非构造器装配。
6.3 方法
在普通方法上加Autowired注解
@Service
public class UserService {
@Autowired
public void test(IUser user) {
user.say();
}
}
Spring会在项目启动的过程中,自动调用一次加了@Autowired注解的方法,我们可以在该方法做一些初始化的工作。
也可以在setter方法上Autowired注解:
@Service
public class UserService {
private IUser user;
@Autowired
public void setUser(IUser user) {
this.user = user;
}
}
6.4 参数
可以在构造器的入参上加Autowired注解
@Service
public class UserService {
private IUser user;
public UserService(@Autowired IUser user) {
this.user = user;
System.out.println("user:" + user);
}
}
也可以在非静态方法的入参上加Autowired注解
@Service
public class UserService {
public void test(@Autowired IUser user) {
user.say();
}
}
七、@Autowired自动装配多个实例
平时我们一般都是通过@Autowired
自动装配单个实例,但这里我会告诉你,它也能自动装配多个实例。
7.1 实现自动装配多个实例
将UserService方法调整一下,用一个List集合接收IUser类型的参数
@Service
public class UserService {
@Autowired
private List<IUser> userList;
@Autowired
private Set<IUser> userSet;
@Autowired
private Map<String, IUser> userMap;
public void test() {
System.out.println("userList:" + userList);
System.out.println("userSet:" + userSet);
System.out.println("userMap:" + userMap);
}
}
增加一个controller
@RequestMapping("/u")
@RestController
public class UController {
@Autowired
private UserService userService;
@RequestMapping("/test")
public String test() {
userService.test();
return "success";
}
}
userList、userSet和userMap都打印出了两个元素,说明@Autowired会自动把相同类型的IUser对象收集到集合中。
7.2 用途
可以与策略模式来搭配使用,变种后的策略模式:
1、不需要content类来按条件选择具体的策略类。
2、一个策略接口,多个具体策略实现类。
3、每个具体策略实现类,通过入参参数判断自己是否需要执行,如果不需要执行,直接返回。
4、调用策略的client类,通过@Autowired自动装配多个实例,一次性拿到所有策略实现类,通过循环调用这些策略实现类。
5、有新的策略实现,新建新的具体实现类就可以了,不需要像以前一样去修改content类,真正实现了开闭原则。
示例代码:
/**
* 推送策略类
*/
public interface IPushStrategy {
/**
* @param userId:用户ID
* @param msg:推送消息
* @param supplier:推送供应商
* @return
*/
boolean push(Long userId, String msg, String supplier);
}
/**
* 华为推送策略
*/
@Service
public class HuaWeiPushService implements IPushStrategy {
@Override
public boolean push(Long userId, String msg, String supplier) {
if ("HuaWei".equals(supplier)) {
System.out.println("华为推送给用户" + userId + ",推送消息:" + msg);
return true;
}
return false;
}
}
/**
* Oppo推送策略
*/
@Service
public class OppoPushService implements IPushStrategy {
@Override
public boolean push(Long userId, String msg, String supplier) {
if ("Oppo".equals(supplier)) {
System.out.println("Oppo推送给用户" + userId + ",推送消息:" + msg);
return true;
}
return false;
}
}
/**
* 小米推送策略
*/
@Service
public class XiaoMiPushService implements IPushStrategy {
@Override
public boolean push(Long userId, String msg, String supplier) {
if ("XiaoMi".equals(supplier)) {
System.out.println("小米推送给用户" + userId + ",推送消息:" + msg);
return true;
}
return false;
}
}
/**
* 推送策略context
*/
@Component
public class PushContext implements IPushStrategy {
@Autowired
private List<IPushStrategy> pushStrategyList;
@Override
public boolean push(Long userId, String msg, String supplier) {
boolean supplierMatched = false;
for (IPushStrategy pushStrategy : pushStrategyList) {
supplierMatched = pushStrategy.push(userId, msg, supplier);
if (supplierMatched) {
break;
}
}
return supplierMatched;
}
}
@Api(tags = "推送策略模式")
@RestController
@RequestMapping("strategy")
public class TestController {
@Resource
private PushContext pushContext;
@ApiOperation(value = "推送")
@GetMapping("push")
public boolean push() {
Long userId = 273L;
String msg = "您有新的订单";
String supplier = "HuaWei";
return pushContext.push(userId, msg, supplier);
}
}
八、@Autowired注入失败场景
8.1 没有加@Service注解
在类上面忘了加@Controller
、@Service
、@Component
、@Repository
等注解,Spring就无法完成自动装配的功能。
8.2 注入Filter或Listener
web应用启动的顺序是:listener
->filter
->servlet
。众所周知,SpringMvc的启动是在DisptachServlet里面做的,而它是在listener和filter之后执行。如果我们想在listener和filter里面@Autowired
某个bean,肯定是不行的,因为filter初始化的时候,此时bean还没有初始化,无法自动装配。如果工作当中真的需要这样做,我们该如何解决这个问题呢?答案是使用WebApplicationContextUtils.getWebApplicationContext
获取当前的ApplicationContext,再通过它获取到bean实例。
8.3 注解未被@ComponentScan扫描
通常情况下,@Controller
、@Service
、@Component
、@Repository
、@Configuration
等注解,是需要通过@ComponentScan
注解扫描,收集元数据的。但是,如果没有加@ComponentScan
注解,或者@ComponentScan
注解扫描的路径不对,或者路径范围太小,会导致有些注解无法收集,到后面无法使用@Autowired
完成自动装配的功能。有个好消息是,在springboot项目中,如果使用了@SpringBootApplication
注解,它里面内置了ComponentScan
注解的功能。
8.4 循环依赖问题
如果A依赖于B,B依赖于C,C又依赖于A,这样就形成了一个死循环。
Spring的bean默认是单例的,如果单例bean使用@Autowired
自动装配,大多数情况,能解决循环依赖问题。但是如果bean是多例的,会出现循环依赖问题,导致bean自动装配不了。还有有些情况下,如果创建了代理对象,即使bean是单例的,依然会出现循环依赖问题。