什么是@Autowired和@Reource以及其机制

前情提要

上一篇【Spring框架的自动装配解析】我们讲了什么是自动装配,而且用xml给大家模拟了一遍自动装配的原理是什么,这一篇就要讲解我们用的最多的自动装配的注解@Autowited和@Resource到底是什么机制。更多Spring内容进入【Spring解读系列目录】

@Autowired

我们还是用几个例子讲解,这次就不需要xml文件了,一切都是springboot的web项目配置。我们现在有一个接口DemoDao,两个实现类DemoDaoImpl和DemoDaoImpl2,一个业务类DemoService,以及一个测试类DemoTest。

public interface DemoDao {
    void test();
}
@Repository
public class DemoDaoImpl implements DemoDao{
    @Override
    public void test() {
        System.out.println("test");
    }
}
@Repository
public class DemoDaoImpl2 implements DemoDao{
    @Override
    public void test() {
        System.out.println("test---2");
    }
}
@Service
public class DemoService {
    @Autowired
    private DemoDao demoDao;
    public void doTest(){
        demoDao.test();
    }

    public void setDemoDao (DemoDao demoDao) {
        this. demoDao = demoDao;
    }
}
public class DemoTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac=new AnnotationConfigApplicationContext(Spring.class);
        DemoService service= (DemoService) acac.getBean("demoService");
        service.doTest();
    }
}

那么我直接运行看看会打印出来哪个实现类的内容。运行结果是报异常了,哪个都打不出来。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'service': Unsatisfied dependency expressed through field 'dao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.DemoDao' available: expected single matching bean but found 2: demoDaoImpl,demoDaoImpl2
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130)
	...
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
	at com.example.demo.DemoTest.main(DemoTest.java:8)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.demo.DemoDao' available: expected single matching bean but found 2: demoDaoImpl,demoDaoImpl2
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
	... 14 more

为什么会这样呢,大家看这个异常expected single matching bean but found 2: demoDaoImpl,demoDaoImpl2,看过我上一篇的同学一定都记得这个异常。这个是说冲突了,因为每一个实例只能根据一种type去配置。这也间接的证明了,其实@Autowired是首先根据byType配置的。

注意,我说的是首先根据byType配置的,为什么是首先呢?因为@Autowired如果发现有冲突,其实会自动转去byName去匹配,比如我们改造一下DemoService,让它里面的DemoDao就指向DemoDaoImpl2:

@Service
public class DemoService {
    @Autowired
    private DemoDao demoDaoImpl2; //byType失败,就用byName
    public void doTest(){
        demoDaoImpl2.test();
    }

    public void setDemoDaoImpl(DemoDao demoDao) {
        this.demoDaoImpl2 = demoDao;
    }
}

这样也是可以运行的。而且打印的结果就是DemoDaoImpl2里面的test---2这个结果。说明@Autowired不仅仅byType设置,同样可以byName识别。如果两个都找不到,才会报错。这点我相信很多同学都不知道,或者就认为@Autowired只是根据byType设置的,因为网络上大多贴子都是这么写的。

@Resource

这个注解也是Spring框架中自动装配的注解之一,上面我们说@Autowired是根据byTypebyName自动配置的,那么@Resource它的首要功能就是根据byName配置的。@Resource可以指定一个name属性。虽然这个是根据byName来的,但是却和我们上一篇通过xml配置的byName不一样。上一篇中,我们说过也测试过,byName是by类中的setter方法来的。而基于注解的则是根据属性的名字来的,和setter方法没有任何关系。如果name属性不配置上,默认就是属性名对应的实现类。
为了验证我们的说法,我们去修改一下DemoService和 DemoDaoImpl这个实现类对应上:

@Service
public class DemoService {
    @Resource
    private DemoDao demoDaoImpl; //@Resource根据属性的byName来的
    public void doTest(){
        demoDaoImpl.test();
    }
    public void setdemoDaoImpl (DemoDao demoDao) {
        this. demoDaoImpl = demoDaoImpl;
    }
}

这里运行打印的记是test,为了验证的更加彻底,我们把setter方法都改了,运行则打印的test。我们继续测试修改为DemoDaoImpl2的实现:

@Service
public class DemoService {
    @Resource
    private DemoDao demoDaoImpl2;
    public void doTest(){
        demoDaoImpl2.test();
    }
    public void setAAAAA (DemoDao demoDao) {
        this. demoDaoImpl2 = demoDaoImpl2;
    }
}

运行则打印的test---2。说明@Resource基于byName的注解,确实和属性名有关系。那么我们可以得到进一步的推论,如果不需要自己调用,就算是不要setter方法也没问题。这里大家自己验证,就不贴代码了。

说完首要的功能,其实@Resource也可以基于type去配置。是不是很毁三观?因为@Resource里有一个type的属性,只要指定了就可以使用指定的类型去装配。

@Service
public class DemoService {
    @Resource(type = DemoDaoImpl.class) //指定由DemoDaoImpl注入
    private DemoDao dao;  //绝对不可以叫demoDaoImpl2
    public void doTest(){
        dao.test();
    }
    public void setDemoDaoImpl(DemoDao demoDao) {
        this.dao = demoDao;
    }
}

但是这里要注意,如果指定了一个类,那么这个属性的名字就不能和别的类重名。这个例子中如果指定注入DemoDaoImpl这个类,这个属性的名字就不能叫做demoDaoImpl2,否则就会报冲突错误。因为Spring的查着逻辑就是首先把小写的属性名首字母替换成大写的,然后查找是不是有这么一个类,如果有就new出来注入,如果没有,就报错。那么如果制定了类,又写重名了,就会发生一个接口不知道执行哪个实现类的错误。

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'demoService': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'demoDaoImpl2' is expected to be of type 'com.example.demo.DemoDaoImpl' but was actually of type 'com.example.demo.DemoDaoImpl2'
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
	...
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
	at com.example.demo.DemoTest.main(DemoTest.java:8)
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'demoDaoImpl2' is expected to be of type 'com.example.demo.DemoDaoImpl' but was actually of type 'com.example.demo.DemoDaoImpl2'
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:399)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
	...
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:318)
	... 12 more

那么这部分在哪里有写呢?官网上就是在【beans-scanning-name-generator】写的。以下英文段落和例子摘自官方文档。

大意就是你可以在组件上自己指定:

When a component is autodetected as part of the scanning process, its bean name is generated by the BeanNameGenerator strategy known to that scanner. By default, any Spring stereotype annotation (@Component, @Repository, @Service, and @Controller) that contains a name value thereby provides that name to the corresponding bean definition.
@Service(“myMovieLister”)
public class SimpleMovieLister {
// …
}

如果不自己指定,就替换第一个字母变成小写的,官网这里的例子就是MovieFinderImpl变为movieFinderImpl

If such an annotation contains no name value or for any other detected component (such as those discovered by custom filters), the default bean name generator returns the uncapitalized non-qualified class name. For example, if the following component classes were detected, the names would be myMovieLister and movieFinderImpl:
@Repository
public class MovieFinderImpl implements MovieFinder {
// …
}

如果你更牛逼,甚至使用可以BeanNameGenerator 自己定义。

If you do not want to rely on the default bean-naming strategy, you can provide a custom bean-naming strategy. First, implement the BeanNameGenerator interface, and be sure to include a default no-arg constructor. Then, provide the fully qualified class name when configuring the scanner, as the following example annotation and bean definition show:

附加知识@Component

Spring框架的开发当中,必然会用到很多注解,比如@Component, @Service, and @Controller等等,这些都是什么意思呢?在讲解自动注入后顺便就带大家去官方文档里探索一下这些都是什么。官网的位置是【beans-classpath-scanning】

@Component 官网是这么解释的:

Spring provides further stereotype annotations: @Component, @Service, and @Controller. @Component is a generic stereotype for any Spring-managed component. @Repository, @Service, and @Controller are specializations of @Component for more specific use cases (in the persistence, service, and presentation layers, respectively). Therefore, you can annotate your component classes with @Component, but, by annotating them with @Repository, @Service, or @Controller instead, your classes are more properly suited for processing by tools or associating with aspects. For example, these stereotype annotations make ideal targets for pointcuts. @Repository, @Service, and @Controller can also carry additional semantics in future releases of the Spring Framework. Thus, if you are choosing between using @Component or @Service for your service layer, @Service is clearly the better choice. Similarly, as stated earlier, @Repository is already supported as a marker for automatic exception translation in your persistence layer.

很长我们拆开看,首先这句话,@Component is a generic stereotype for any Spring-managed component.@Component 可以注解到任意被Spring管理的容器上。

既然如此,为什么还要有@Repository, @Service, and @Controller 这些东西呢?其实@Component 就是上面几个的父类。如果你点去任意的@Service注解,都会发现,这些注解上包含@Component。@Repository, @Service, and @Controller are specializations of @Component for more specific use cases.这句话就说的很清楚,@Repository, @Service, and @Controller是特殊化的@Component。只不过为了区分类型,就把他们分开了。也就是说任意@Repository, @Service, and @Controller 这些地方都可用@Component 把他们替代。

虽然理论上可以都用@Component 注解,但是Spring并不建议全都用,因为上面这段话中还有一句非常重要的信息:@Repository, @Service, and @Controller can also carry additional semantics in future releases of the Spring Framework. 这句话说在未来的Spring Framework版本中,这三个注解也会携带额外的语义。也就是说虽然现在没有,但是未来可能会添加。那么如果你都用@Component 注解,未来版本升级以后很有可能就有兼容问题了。所以编程中,还是应该规范注解不能随便就省略。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值