@Autowired与@Resource有何区别

一、相同点

这个两个注解都是用来完成组件的装配的,即利用依赖注入(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的装配顺序图
1
2、@Resource的装配顺序图
2.1 如果同时指定了name和type
2
2.2 如果指定了name
3
2.3 如果指定了type
4
2.4 如果既没有指定name,也没有指定type
5

三、选择

@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.
     * 

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 userList;

    @Autowired
    private Set userSet;

    @Autowired
    private Map 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 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是单例的,依然会出现循环依赖问题。

你可能感兴趣的:(@Autowired与@Resource有何区别)