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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容