ITPub博客

首页 > 数据库 > 数据库开发技术 > Spring ⑤ @Resource @Inject 注解依赖注入详解

Spring ⑤ @Resource @Inject 注解依赖注入详解

原创 数据库开发技术 作者:DevXTalk 时间:2023-01-28 19:52:15 0 删除 编辑

Spring ⑤  @Resource @Inject 注解依赖注入详解

Spring 源码系列文章会遵循由浅入深,由易到难,由宏观到微观的原则,目标是尽量降低学习难度,而不是一上来就迷失在源码当中. 文章会从一个场景作为出发点,针对性的目的性极强的针对该场景对 Spring 的实现原理,源码进行探究学习。该系列文章会让你收获什么? 从对 Spring 的使用者成为 Spring 专家。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。

本章的内容是对 bean 创建过程中依赖项注入的探究,针对 @Resource 和 @Inject (JSR330 标准) 注解进行讲解分析。

@Inject 注解依赖注入

在第4章 Spring ④ Autowired 注解依赖注入详解 | 看不懂你打我 中我们详细分析了使用 @Autowired 注解如何进行依赖注入的过程(对于理解了上期内容的读者,本期内容对你来说会是非常简单的。)。核心是 AutowiredAnnotationBeanPostProcessor 的 postProcessProperties 方法实现的。AutowiredAnnotationBeanPostProcessor 不仅支持处理 @Autowired 注解同时也支持 JSR-330 标准的 @Inject 注解。所以在使用过程中 @Autowired 注解和 @Inject 注解并没有实质上的不同,理解了 @Autowired 的原理也就理解了 @Inject 。

在 AutowiredAnnotationBeanPostProcessor 构造函数中将支持的注解添加到 autowiredAnnotationTypes 中。如果项目中引入了 JSR-330 的 jar 包会将 @Inject 注解也添加进去。

/**
* Create a new {@code AutowiredAnnotationBeanPostProcessor} for Spring's
* standard {@link Autowired @Autowired} and {@link Value @Value} annotations.
* <p>Also supports JSR-330's {@link javax.inject.Inject @Inject} annotation,
* if available.
*/
@SuppressWarnings("unchecked")
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
try {
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}

AutowiredAnnotationBeanPostProcessor#findAutowiredAnnotation

将需要注入的元素上的注解和 autowiredAnnotationTypes 中的注解进行比对。

@Nullable
private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
MergedAnnotations annotations = MergedAnnotations.from(ao);
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
MergedAnnotation<?> annotation = annotations.get(type);
if (annotation.isPresent()) {
return annotation;
}
}
return null;
}

JSR-330

Java 依赖注入标准(JSR-330,Dependency Injection for Java)1.0 规范 2009 年 10 月 13 日发布。该规范主要是面向依赖注入使用者,而对注入器实现、配置并未作详细要求。目前 Spring 、Guice 已经开始兼容该规范,JSR-299(Contexts and Dependency Injection for Java EE platform,参考实现 Weld )在依赖注入上也使用该规范。JSR-330 规范并未按 JSR 惯例发布规范文 档,只发布了规范 API 源码,本文翻译了该规范 API 文档(Javadoc )以作为对 Java 依赖注入标准规 范的简介。

spring 从 3.0 版本开始实现了 JSR-330 标准。

@Resource 注解依赖注入

场景

该场景中包含 属性注入 、方法注入。与 @Autowired 依赖注入场景不同的是,不再使用 AutowiredAnnotationBeanPostProcessor 而是换成了 CommonAnnotationBeanPostProcessor.

public class ResourceInjectionExp {

public static void main(String[] args) {

// @Resource 注解依赖注入

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
CommonAnnotationBeanPostProcessor processor = new CommonAnnotationBeanPostProcessor();
processor.setBeanFactory(factory);
factory.addBeanPostProcessor(processor);

factory.registerBeanDefinition("bean1" , BeanDefinitionBuilder
.genericBeanDefinition(Bean1.class)
.getBeanDefinition());

factory.registerBeanDefinition("bean2" , BeanDefinitionBuilder
.genericBeanDefinition(Bean2.class)
.getBeanDefinition());

factory.registerBeanDefinition("bean3" , BeanDefinitionBuilder
.genericBeanDefinition(Bean3.class)
.getBeanDefinition());

Bean1 bean1 = factory.getBean(Bean1.class);
System.out.println("bean1 -> " + bean1);
}

@ToString
static class Bean1 {

@Resource
Bean2 bean2;

Bean3 bean3;

public Bean1() {
System.out.println("======================== bean1 实例化");
}

@Resource
public void setBean3(Bean3 bean3) {
this.bean3 = bean3;
}
}

static class Bean2 {

public Bean2() {
System.out.println("++++++++++++++++++++++++++ bean2 实例化");
}
}

static class Bean3 {

public Bean3() {
System.out.println("++++++++++++++++++++++++++ Bean3 实例化");
}
}
}

CommonAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor 静态代码块中将 @Resource 注解加到了 resourceAnnotationTypes 中.

static {
resourceAnnotationTypes.add(Resource.class);

webServiceRefClass = loadAnnotationType("javax.xml.ws.WebServiceRef");
if (webServiceRefClass != null) {
resourceAnnotationTypes.add(webServiceRefClass);
}

ejbClass = loadAnnotationType("javax.ejb.EJB");
if (ejbClass != null) {
resourceAnnotationTypes.add(ejbClass);
}
}
postProcessProperties

和 AutowiredAnnotationBeanPostProcessor 的 postProcessProperties 几乎是一模一样.

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
}
return pvs;
}
findResourceMetadata
private InjectionMetadata findResourceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
metadata = buildResourceMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
}
buildResourceMetadata
private InjectionMetadata buildResourceMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}

List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;

do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

ReflectionUtils.doWithLocalFields(targetClass, field -> {
if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields");
}
currElements.add(new WebServiceRefElement(field, field, null));
}
else if (ejbClass != null && field.isAnnotationPresent(ejbClass)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static fields");
}
currElements.add(new EjbRefElement(field, field, null));
}
else if (field.isAnnotationPresent(Resource.class)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
currElements.add(new ResourceElement(field, field, null));
}
}
});

ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods");
}
if (method.getParameterCount() != 1) {
throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method);
}
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new WebServiceRefElement(method, bridgedMethod, pd));
}
else if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static methods");
}
if (method.getParameterCount() != 1) {
throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);
}
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
}
else if (bridgedMethod.isAnnotationPresent(Resource.class)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length != 1) {
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new ResourceElement(method, bridgedMethod, pd));
}
}
}
});

elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);

return InjectionMetadata.forElements(elements, clazz);
}

ResourceElement

private class ResourceElement extends LookupElement {

private final boolean lazyLookup;

public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
Resource resource = ae.getAnnotation(Resource.class);
String resourceName = resource.name();
Class<?> resourceType = resource.type();
this.isDefaultName = !StringUtils.hasLength(resourceName);
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = Introspector.decapitalize(resourceName.substring(3));
}
}
else if (embeddedValueResolver != null) {
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
if (Object.class != resourceType) {
checkResourceType(resourceType);
}
else {
// No resource type specified... check field/method.
resourceType = getResourceType();
}
this.name = (resourceName != null ? resourceName : "");
this.lookupType = resourceType;
String lookupValue = resource.lookup();
this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}

@Override
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
getResource(this, requestingBeanName));
}
}
CommonAnnotationBeanPostProcessor#getResource
/**
* Obtain the resource object for the given name and type.
* @param element the descriptor for the annotated field/method
* @param requestingBeanName the name of the requesting bean
* @return the resource object (never {@code null})
* @throws NoSuchBeanDefinitionException if no corresponding target resource found
*/
protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
throws NoSuchBeanDefinitionException {

// JNDI lookup to perform?
String jndiName = null;
if (StringUtils.hasLength(element.mappedName)) {
jndiName = element.mappedName;
}
else if (this.alwaysUseJndiLookup) {
jndiName = element.name;
}
if (jndiName != null) {
if (this.jndiFactory == null) {
throw new NoSuchBeanDefinitionException(element.lookupType,
"No JNDI factory configured - specify the 'jndiFactory' property");
}
return this.jndiFactory.getBean(jndiName, element.lookupType);
}

// Regular resource autowiring
if (this.resourceFactory == null) {
throw new NoSuchBeanDefinitionException(element.lookupType,
"No resource factory configured - specify the 'resourceFactory' property");
}
return autowireResource(this.resourceFactory, element, requestingBeanName);
}
CommonAnnotationBeanPostProcessor#autowireResource

autowireResource 中会根据 @Resource 注解 name 属性的值作为 bean 的名称去寻找或创建 bean. @Resource 注解根据名称注入的优先级更高,如果 name 属性的值和实际的 bean 类型不一致会抛出 BeanNotOfRequiredTypeException. 而 @Autowired 和 @Inject 注解则会根据属性的变量名和类型进行 bean 实例的匹配。

/**
* Obtain a resource object for the given name and type through autowiring
* based on the given factory.
* @param factory the factory to autowire against
* @param element the descriptor for the annotated field/method
* @param requestingBeanName the name of the requesting bean
* @return the resource object (never {@code null})
* @throws NoSuchBeanDefinitionException if no corresponding target resource found
*/
protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
throws NoSuchBeanDefinitionException {

Object resource;
Set<String> autowiredBeanNames;
String name = element.name;

if (factory instanceof AutowireCapableBeanFactory) {
AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
DependencyDescriptor descriptor = element.getDependencyDescriptor();
if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
autowiredBeanNames = new LinkedHashSet<>();
resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
if (resource == null) {
throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
}
}
else {
resource = beanFactory.resolveBeanByName(name, descriptor);
autowiredBeanNames = Collections.singleton(name);
}
}
else {
resource = factory.getBean(name, element.lookupType);
autowiredBeanNames = Collections.singleton(name);
}

if (factory instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
for (String autowiredBeanName : autowiredBeanNames) {
if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
}
}
}

return resource;
}

@Resource 、@Inject 注解实现依赖注入的原理和过程基本介绍完毕,更多的技术原理会在系列文章的后续内容中提供。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。



DevXJava 不止于技术

原文作者:DevXTalk;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2019-03-23

  • 博文量
    178
  • 访问量
    82586