@Resource注解的使用及源码解析

前言

阅读这边文章之前我们先保持这样一个疑问 : @Resource注解真的是根据名称注入的么?

@Resource注解如何使用

代码准备

创建一个接口,以及三个实现其接口的bean

public interface Animal {
}

@Component
public class Cat implements Animal {
}

@Component
public class Dog implements Animal {
}

@Component
public class Fish implements Animal {
}

创建一个类型为House的bean

@Component
public class House {

}

创建配置文件resource.properties

animal=fish

创建配置类

@ComponentScan("com.test.resource")
@PropertySource("classpath:resource.properties")
public class AppConfig {

}

创建启动类

public class Main {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        House bean = context.getBean(House.class);
        System.out.println(bean);
    }
}

作用属性上

单体依赖指定name (name和beanName一致)
@Component
public class House {

    @Resource(name = "dog")
    private Dog dog;

}

单体依赖指定name (name和beanName不一致)
@Component
public class House {

    @Resource(name = "dog1")
    private Dog dog;

}

单体依赖不指定name
@Component
public class House {

    @Resource
    private Dog dog;

    @Resource
    private Dog dog1;

}

单体依赖指定name(EL表达式)
@Component
public class House {

    @Resource(name = "${animal}")
    private Fish fish;

}

集合不指定name (属性名与所有beanName不一致)
@Component
public class House {

    @Resource
    private List<Animal> animals;

}

集合不指定name (属性名和其中一个beanName一致)
@Component
public class House {

    @Resource
    private List<Animal> fish;
}

集合指定name (name与所有beanName不一致)
@Component
public class House {

    @Resource(name = "animals")
    private List<Animal> animals;

}

集合指定name (name和其中一个beanName一致) 
@Component
public class House {

    @Resource(name = "fish")
    private List<Animal> animals;

}

作用于方法上

方法指定name (name和beanName一致)
@Component
public class House {

    private Cat cat;

    @Resource(name = "cat")
    public void setCat(Cat cat) {
        this.cat = cat;
    }

}

方法指定name (name和beanName不一致)
@Component
public class House {

    private Cat cat;

    @Resource(name = "cat1")
    public void setCat(Cat cat) {
        this.cat = cat;
    }

}

方法不指定name
public class House {

    private Cat cat;

    private Cat cat1;

    @Resource
    public void setCat(Cat cat) {
        this.cat = cat;
    }

    @Resource
    public void setCat1(Cat cat1) {
        this.cat1 = cat1;
    }

}

如果参数类型是基类,则@Resource的name必须和其中一个子类的beanName一致或者方法名和其中一个子类的beanName一致

name和beanName一致
@Component
public class House {

    private Animal animal;

    @Resource(name = "fish")
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

方法名和beanName一致 
@Component
public class House {

    private Animal animal;

    @Resource
    public void setDog(Animal animal) {
        this.animal = animal;
    }
}

小结

我们通过观察发现,其实作用在属性上和作用在方法上是一样的。作用在方法上的时候,我们可以把参数当做属性的类型,把方法名当成属性名。

源码分析

前置知识

1.处理@Resource的后置处理器是CommonAnnotationBeanPostProcessor

2.各个后置处理器会在bean的实例化过程中执行特定的方法,执行顺序如下

  1. postProcessBeforeInstantiation
  2. postProcessMergedBeanDefinition : 发现注入点
  3. postProcessAfterInstantiation 
  4. postProcessProperties 属性注入
  5. postProcessBeforeInitialization
  6. postProcessAfterInitialization

在上面的前提下我们开始分析

1.发现注入点

CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

CommonAnnotationBeanPostProcessor#findResourceMetadata

CommonAnnotationBeanPostProcessor#buildResourceMetadata

 由上述源码我们得出结论

如果field或者method上存在@Resource注解,我们就可以将其当做一个注入点

注意点

  • field或者method不能是static的
  • method的参数有且仅有一个

注入点的类型是ResourceElement,我们看一下其构造方法

    public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
        super(member, pd);
        Resource resource = ae.getAnnotation(Resource.class);
        // 获取我们指定的name
        String resourceName = resource.name();
        Class<?> resourceType = resource.type();
        // 如果我们指定了name,则isDefaultName为false
        // PS:isDefaultName这个属性很重要,重点关注一下
        this.isDefaultName = !StringUtils.hasLength(resourceName);
        if (this.isDefaultName) {
            // 如果未指定name,resourceName就是属性名
            resourceName = this.member.getName();
            // 如果member是一个Method,并且方法名以set开头,并且方法名的长度大于3,则进行相应处理,大概有下列几种方法
            // setAbc ==> abc
            // setABc ==> ABc
            // setABC ==> ABC
            // abc => abc
            if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
                resourceName = Introspector.decapitalize(resourceName.substring(3));
            }
        }
        // 正常情况是beanFactory是存在embeddedValueResolver的,所有如果我们将name指定成${xx}这样的形式也是会解析的
        // PS:如果不存在beanFactory会添加一个默认的embeddedValueResolver
        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());
    }

2.属性注入

CommonAnnotationBeanPostProcessor#postProcessProperties

InjectionMetadata#inject

InjectedElement#inject

ResourceElement#getResourceToInject

CommonAnnotationBeanPostProcessor#getResource

CommonAnnotationBeanPostProcessor#autowireResource

    protected Object autowireResource(BeanFactory factory, CommonAnnotationBeanPostProcessor.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();
            // fallbackToDefaultTypeMatch这个值默认是true,暂不讨论
            // isDefaultName这个属性就很重要了,如果这个值为false,则表明我们指定了name,不会进入这个if分支
            // if分支和else分支最大的区别就是,if分支返回的结果可能是一个集合,而else分支只可能返回单个bean
            // 一般的beanName为类名的驼峰形式,所以我们属性名也很重要,如果与beanName一致则进入else分支,否则进入if分支
            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;
    }

我们主要关注的是,根据我们的设置,Spring是执行的resolveDependency方法,还是执行resolveBeanByName方法。执行resolveDependency可能会返回一个集合,执行resolveBeanByName最多有一个bean

PS : resolveDependency方法解析流程可以看我上篇博文 : Spring之@Autowired注解

我们画一个流程图,演示上述源码流程,也可以用上文中@Resource的使用案例来一一验证

@Resource是根据类型查找bean?

我们在上文的分析中得出,如果注入点是一个集合类,未指定name,并且属性名在不和任意一个beanName一致的情况下,是可以根据类型查找出多个bean的情况的,所以@Resource不完全是根据名称查找bean的

我们看一下resolveBeanByName方法细节

执行resolveBeanByName方法,会在方法传递指定了requiredType,如果requiredType不为null,Spring会执行adaptBeanInstance方法,来校验bean的类型与requiredType的类型是否一致,再次证明了@Resource注解不是完全根据名称查找bean,如果完全是根据名称查找bean,则不应该传递requiredType来校验bean的类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值