在之前的工作中,一直复制着前辈们注入依赖的方法。没有去细细品味这些注入依赖的注解到底有何不同。
今天看到某博客的一个用户提出了一个观点:@Resource只有外包才用,人家国外早淘汰了,都用@Inject。深感惭愧,由于我本人实习以来都一直使用的@Resource,看到这种观点就很诧异,我尼玛我刚工作就落伍了啊,吓得我感觉来了解学习一波。
在了解三者的区别异同之前,先得熟悉他们得来源。下表中表述了本次内容中所涉及到的注解及作用说明。
注解名 | 包名 | 所属 | Java规范 | 作用 |
@Resource | javax.annotation.Resource | Java | JSR-250(Spring2.5后支持) | 依赖注入 |
@Inject | javax.inject.Inject | Java | JSR-330(Spring3.0后支持) | 依赖注入 |
@Named | javax.inject.Named | Java | JSR-330(Spring3.0后支持) | 被@Qualifier注解,注入时根据名称匹配bean |
@Qualifier | javax.inject.Qualifier | Java | JSR-330(Spring3.0后支持) | 只能注解其他注解。用于实现根据名称匹配bean |
@Autowired | org.springframework.beans.factory.annotation.Autowired | Spring | 依赖注入 | |
@Qualifier | org.springframework.beans.factory.annotation.Qualifier | Spring | 注入时根据名称匹配bean |
总的来说,三种注解进行注入时,一般只有根据name和type两种执行路径(execution paths)并以此路径的先后顺序作为注入的匹配规则。当使用@Qualifier注解时,才会根据@Qualifier设置的名称进行匹配这条规则。
Match by Name 通过名称匹配bean
Match by Type 通过类型匹配bean
Match by Qualifier 指定要匹配bean的名字 @Qulifier注解为spring下的注解类,支持JSR-330规范时也可以使用@Named类,效果相同。
一般情况下,三种注解的匹配顺序为下:
@Resource:当和@Qualifier一起使用时,不会影响匹配的顺序。
匹配顺序:
Match by Name
Match by Type
Match by Qualifier(当已经通过名称匹配bean时,忽略该匹配)
@Inject:可以和@Named使用,效果同@Qulifier相同。
匹配顺序:
Match by Type
Match by Qualifier
Match by Name
@Autowired:在和@Qualifier(此时,一般有多个同类型的bean,以至于type匹配失效。如果没有多个而使用Qualifier注解是完全没必要的)一起使用时,会匹配Qualifier设置的名称。此时的匹配规则同@Resource相同
匹配顺序:
Match by Type
Match by Qualifier
Match by Name
@Resource注解来自javax.annotation.Resource包,使用CommonAnnotationBeanPostProcessor类注入依赖。属于JSR-250的规范,因此所有支持该规范的框架都可以使用该注解。@Resource有两个主要参数:
name:当@Resource用于注解字段或setter时,name如果不填,会将字段名作为默认值(注解setter时,也是获取该字段的字段名而非传入的形参名)。
type:Resource也可以为需要注入的字段指定具体的类,来进行注入依赖。以此改变匹配顺序。
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
/**
* The JNDI name of the resource. For field annotations,
* the default is the field name. For method annotations,
* the default is the JavaBeans property name corresponding
* to the method. For class annotations, there is no default
* and this must be specified.
*/
String name() default "";
/**
* The Java type of the resource. For field annotations,
* the default is the type of the field. For method annotations,
* the default is the type of the JavaBeans property.
* For class annotations, there is no default and this must be
* specified.
*/
Class> type() default java.lang.Object.class;
}
@Inject注解和@Named注解位于同一个包javax.inject下,都属于JSR-330规范。@inject注解的作用与@Autowired注解相同。且@inject注解无参数。
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
// 这里的写法是因为引入枚举值时,将其设置为静态字段
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}
@Inject和@Named同时使用时其作用等同于@Autowired和@Qualifier(Spring)。不过需要注意的是,前者基于Java的JSR-330规范,在任何支持该规范的框架都能使用,而后者仅支持在Spring框架中使用。
@Name注解有着与@Qualifier(Spring)注解相同的作用,就是因为被同包下的@Qualifier(Java)所注解。
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
我们可以通过被@Qualifier(Java)注解,来生成新的自定义名称扫描的注解:
/**
* Identifies qualifier annotations. Anyone can define a new qualifier. A
* qualifier annotation:
*
*
* - is annotated with {@code @Qualifier}, {@code @Retention(RUNTIME)},
* and typically {@code @Documented}.
* - can have attributes.
* - may be part of the public API, much like the dependency type, but
* unlike implementation types which needn't be part of the public
* API.
* - may have restricted usage if annotated with {@code @Target}. While
* this specification covers applying qualifiers to fields and
* parameters only, some injector configurations might use qualifier
* annotations in other places (on methods or classes for example).
*
*
* For example:
*
*
* @java.lang.annotation.Documented
* @java.lang.annotation.Retention(RUNTIME)
* @javax.inject.Qualifier
* public @interface Leather {
* Color color() default Color.TAN;
* public enum Color { RED, BLACK, TAN }
* }
*
* @see javax.inject.Named @Named
*/
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Qualifier {}
@Autowired注解与@Inject注解功能相同,都是使用AutowiredAnnotationBeanPostProcessor类注入依赖。仅有一个注解标明该依赖是否为必需。
@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;
}
@Qualifier注解与@Autowired一起使用时可以用来消除歧义。使用@Qualifier的场景,一般是有多个同类型的bean导致@Autowired根据type匹配失效,这时就可以通过@Qualifier精准定位到我们需要注入的bean。其实在多个同类型的bean的情况下,我们也可以不使用@Qualifier,使用@Autowired的name匹配规则一样可以达成目的。
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER
, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
参考:
Wiring in Spring: @Autowired, @Resource and @Inject
The Spring @Qualifier Annotation
新建一个接口类MatchService,两个实现类DogServiceImpl和CatServiceImpl。之后会分别演示三种注解的实现方式。
// 匹配接口
public interface MatchService {
String getName();
}
// 狗
@Service(value = "dog")
public class DogServiceImpl implements MatchService {
@Override
public String getName() {
return "Dog";
}
}
// 猫
@Service(value = "cat")
public class CatServiceImpl implements MatchService {
@Override
public String getName() {
return "Cat";
}
}
// 由于有多个同类型的bean (dog,cat都属于MatchService类)
// 这里type匹配失效,qualifier没有设置,到最后还是会根据名字匹配
@Autowired
MatchService dog;
// 同上
@Inject
MatchService dog;
@Resource
MatchService dog;
三种注解根据类型匹配时,只有在该类型再容器中只有一个时才会生效,有多个同类型的bean会抛出异常org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type。
这里我们可以把cat类先注释掉,用例如下:
// 这里的字段名是为了区分bean名,
@Autowired
MatchService matchService;
@Inject
MatchService matchService;
// 此时由于设置了type参数,使得type优先级提到了最前
@Resource(type = MatchService.class)
MatchService matchService;
当Resource同时使用Named和Qualifier时,如何指定得名称相同,则正常运行,否则程序将会启动失败。
下面得例子中,都不满足type和name匹配,确保用例是由Named和Qualifier匹配。
@Inject
@Named(value = "dog")
MatchService matchService;
@Autowired
@Qualifier(value = "cat")
MatchService matchService;
// 在之前说过得条件满足下两种使用都可以
@Resource
@Named(value = "dog")
// @Qualifier(value = "cat")
MatchService matchService;
Named和Qualifier名称不同时,resource报错信息
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-09-03 15:58:05.999 ERROR 20704 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean of type 'com.training.spring.service.MatchService' that could not be found.
The following candidates were found but could not be injected:
- User-defined bean
- User-defined bean
Action:
Consider revisiting the entries above or defining a bean of type 'com.training.spring.service.MatchService' in your configuration.
@Resource @Autowired @Inject三者异同:
相同点:
1. 相同点在于三者都是用于依赖注入(DI),其功能相同。
不同点:
2. 所属框架和版本不同。@Resource和@Inject都来自Java源代码,但是@Inject版本更高,二者都可以用于支持其规范的框架中。而@Autowired来自Spring框架,且只用于Spring框架。
3. 匹配规则不同。@Resource优先匹配名称,@Autowired、@Inject优先匹配类型。
4. @Qualifier或@Named影响范围不同。@Resource中即使配置了@Qualifier,如果已经通过名称匹配到了bean,那么还是会根据名称匹配来进行注入。而在多类型中,@Autowired和@Inject则是会先进行@Qualifier匹配,匹配失败才会根据名称匹配来完成注入。
至此解惑了,前文提到的@Resource过时的观点有一定道理但是也不全对,@Resource对于老项目老说还是中流砥柱般的存在的,因为老项目框架太杂,啥玩意都有,甚至存在多个版本同时导入的可能,因此用Resource是比较稳定的。当然我觉得还是根据使用场景,用自己喜欢用的注解就好。