Spring @Autowired、@Resource 和 @Service


原文:Spring5:@Autowired 注解、@Resource 注解和 @Service 注解。简单完善。

什么是注解

传统的 Spring 做法是使用 .xml 文件来对 bean 进行注入或者是配置 AOP、事务,这么做有两个缺点:

  1. 如果所有的内容都配置在 .xml 文件中,那么 .xml 文件将会十分庞大;如果按需求分开 .xml 文件,那么 .xml 文件又会非常多。总之这将导致配置文件的可读性与可维护性变得很低

  2. 在开发中在 .java 文件和 .xml 文件之间不断切换,是一件麻烦的事,同时这种思维上的不连贯也会降低开发的效率

为了解决这两个问题,Spring 引入了注解,通过“@XXX”的方式,让注解与 Java Bean 紧密结合,既大大减少了配置文件的体积,又增加了 Java Bean 的可读性与内聚性。

本篇文章,讲讲最重要的三个 Spring 注解,也就是 @Autowired、@Resource 和 @Service。

不使用注解

先看一个不使用注解的 Spring 示例,在这个示例的基础上,改成注解版本的,这样也能看出使用与不使用注解之间的区别,先定义一个老虎:

public class Tiger
{
    private String tigerName = "TigerKing";
    
    public String toString()
    {
        return "TigerName:" + tigerName;
    }
}

再定义一个猴子:

public class Monkey
{
    private String monkeyName = "MonkeyKing";
    
    public String toString()
    {
        return "MonkeyName:" + monkeyName;
    }
}

定义一个动物园:

public class Zoo
{
    private Tiger tiger;
    private Monkey monkey;
    
    public void setTiger(Tiger tiger)
    {
        this.tiger = tiger;
    }
    
    public void setMonkey(Monkey monkey)
    {
        this.monkey = monkey;
    }
    
    public Tiger getTiger()
    {
        return tiger;
    }
    
    public Monkey getMonkey()
    {
        return monkey;
    }
    
    public String toString()
    {
        return tiger + "/n" + monkey;
    }
}

Spring 的配置文件这么写:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns="http://www.springframework.org/schema/beans"  
    xmlns:context="http://www.springframework.org/schema/context"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.2.xsd"
    default-autowire="byType">
    
    <bean id="zoo" class="com.xrq.bean.Zoo" >
        <property name="tiger" ref="tiger" />
        <property name="monkey" ref="monkey" />
    </bean>
    
    <bean id="tiger" class="com.xrq.domain.Tiger" />
    <bean id="monkey" class="com.xrq.domain.Monkey" />
    
</beans>

@Autowired

@Autowired 顾名思义,就是自动装配,其作用是为了消除 Java 代码里面的 getter/setter 与 xml bean 标签中的 property。当然,getter 看个人需求,如果私有属性需要对外提供的话,应当予以保留。

因此,引入 @Autowired 注解,先看一下 Spring 配置文件怎么写:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
 3     xmlns="http://www.springframework.org/schema/beans"  
 4     xmlns:context="http://www.springframework.org/schema/context"  
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 6         http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
 7         http://www.springframework.org/schema/context
 8         http://www.springframework.org/schema/context/spring-context-4.2.xsd">
 9     
10     <context:component-scan base-package="com.xrq" />
11     
12     <bean id="zoo" class="com.xrq.bean.Zoo" />
13     <bean id="tiger" class="com.xrq.domain.Tiger" />
14     <bean id="monkey" class="com.xrq.domain.Monkey" />
15     
16 </beans>

注意第 10 行,使用必须告诉 Spring 一下我要使用注解了,告诉的方式有很多,<context:component-scan base-package="xxx" /> 是一种最简单的,Spring 会自动扫描 xxx 路径下的注解。

看到第 12 行,原来 zoo 里面应当注入两个属性 tiger、monkey,现在不需要注入了。再看下,Zoo.java 也很方便,getter/setter 都可以去掉:

public class Zoo
{
    @Autowired
    private Tiger tiger;
    
    @Autowired
    private Monkey monkey;
    
    public String toString()
    {
        return tiger + "/n" + monkey;
    }
}

这里 @Autowired 注解的意思就是,当 Spring 发现 @Autowired 注解时,将自动在代码上下文中找到和其匹配(默认是类型匹配)的 Bean,并自动注入到相应的地方去。

有一个细节性的问题是,假如 xml bean 标签里面有两个 property,Zoo.java 里面又去掉了属性的 getter/setter 并使用 @Autowired 注解标注这两个属性那会怎么样?答案是 Spring 会按照 xml 优先的原则去 Zoo.java 中寻找这两个属性的 getter/setter,导致的结果就是初始化 bean 报错。

OK,假设此时我把 .xml 文件的 13 行、14 行两行给去掉,再运行,会抛出异常:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'Zoo': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.xrq.domain.Tiger com.xrq.bean.Zoo.ttiger; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xrq.domain.Tiger] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:305)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:301)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:835)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
    at com.xrq.test.MyTest.main(MyTest.java:13)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.xrq.domain.Tiger com.xrq.bean.Zoo.ttiger; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xrq.domain.Tiger] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:571)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
    ... 13 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xrq.domain.Tiger] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1373)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1119)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:543)
    ... 15 more

因为,@Autowired 注解要去寻找的是一个 Bean,Tiger 和 Monkey 的 Bean 定义都给去掉了,自然就不是一个 Bean 了,Spring 容器找不到也很好理解。那么,如果属性找不到我不想让 Spring 容器抛出异常,而就是显示 null,可以吗?可以的,其实异常信息里面也给出了提示了,就是将 @Autowired 注解的 required 属性设置为 false 即可:

public class Zoo
{
    @Autowired(required = false)
    private Tiger tiger;
    
    @Autowired(required = false)
    private Monkey monkey;
    
    public String toString()
    {
        return tiger + "/n" + monkey;
    }
}

此时,找不到 tiger、monkey 两个属性,Spring 容器不再抛出异而是认为这两个属性为 null。

@Autowired 接口注入

上面的比较简单,我们只是简单注入一个 Java 类,那么如果有一个接口,有多个实现,Bean 里引用的是接口名,又该怎么做呢?比如有一个 Car 接口:

public interface Car
{
    public String carName();
}

两个实现类 Audi 和 Benz:

@Service
public class Audi implements Car
{
    public String carName()
    {
        return "Audi car";
    }
}
@Service
public class Benz implements Car
{
    public String carName()
    {
        return "Benz car";
    }
}

写一个 CarFactory,引用 Car:

@Service
public class CarFactory
{
    @Autowired
    private Car car;
    
    public String toString()
    {
        return car.carName();
    }
}

不用说,一定是报错的,Car 接口有两个实现类,Spring 并不知道应当引用哪个实现类。这种情况通常有两个解决办法:

  1. 删除其中一个实现类,Spring 会自动去 base-package 下寻找 Car 接口的实现类,发现 Car 接口只有一个实现类,便会直接引用这个实现类

  2. 实现类就是有多个该怎么办?此时可以使用 @Qualifier 注解:

    @Service
    public class CarFactory
    {
        @Autowired
        @Qualifier("audi")
        private Car car;
    
        public String toString()
        {
            return car.carName();
        }
    }
    

    注意 @Qualifier 注解括号里面的是 Car 接口实现类的 bean id

@Resource

把 @Resource 注解放在 @Autowired 下面说,是因为它们作用非常相似,这个就简单说了,例子过后点明一下 @Resource 和 @Autowired 的区别。先看一下 @Resource,直接写 Zoo.java 了:

@Service
public class Zoo
{
    @Resource(name = "tiger")
    private Tiger tiger;
    
    @Resource(type = Monkey.class)
    private Monkey monkey;
    
    public String toString()
    {
        return tiger + "/n" + monkey;
    }
}

这是详细一些的用法,说一下 @Resource 的装配顺序:

  1. @Resource 后面没有任何内容,默认通过 name 属性去匹配 bean,找不到再按 type 去匹配

  2. 指定了 name 或者 type 则根据指定的类型去匹配 bean

  3. 指定了 name 和 type 则根据指定的 name 和 type 去匹配 bean,任何一个不匹配都将报错

然后,区分一下 @Autowired 和 @Resource 两个注解的区别:

  1. @Autowired 默认按照 byType 方式进行 bean 匹配,@Resource 默认按照 byName 方式进行 bean 匹配

  2. @Autowired 是 Spring 的注解,@Resource 是 J2EE 的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了

Spring 属于第三方的,J2EE 是 Java 自己的东西,因此,建议使用 @Resource 注解,以减少代码和 Spring 之间的耦合。

@Service

上面这个例子,还可以继续简化,因为 Spring 的配置文件里面还有 12 行~14 行三个 bean,下一步的简化是把这三个 bean 也给去掉,使得 Spring 配置文件里面只有一个自动扫描的标签,增强 Java 代码的内聚性并进一步减少配置文件。

要继续简化,可以使用 @Service。先看一下配置文件,当然是全部删除了:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns="http://www.springframework.org/schema/beans"  
    xmlns:context="http://www.springframework.org/schema/context"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.2.xsd">
    
    <context:component-scan base-package="com.xrq" />
    
</beans>

是不是感觉很爽?起码我觉得是的。OK,下面以 Zoo.java 为例,其余的 Monkey.java 和 Tiger.java 都一样:

@Service
public class Zoo
{
    @Autowired
    private Tiger ttiger;
    
    @Autowired
    private Monkey mmonkey;
    
    public String toString()
    {
        return ttiger + "/n" + mmonkey;
    }
}

这样,Zoo.java 在 Spring 容器中存在的形式就是“zoo”,即可以通过 ApplicationContext 的 getBean(“zoo”) 方法来得到 Zoo.java。@Service 注解,其实做了两件事情:

  1. 声明 Zoo.java 是一个 bean,这点很重要,因为 Zoo.java 是一个 bean,其他的类才可以使用 @Autowired 将 Zoo 作为一个成员变量自动注入

  2. Zoo.java 在 bean 中的 id 是“zoo”,即类名且首字母小写

如果,我不想用这种形式怎么办,就想让 Zoo.java 在 Spring 容器中的名字(id)叫做“Zoo”,可以的:

@Service("Zoo")
@Scope("prototype")
public class Zoo
{
    @Autowired
    private Monkey monkey;
    @Autowired
    private Tiger tiger;
    
    public String toString()
    {
        return "MonkeyName:" + monkey + "/nTigerName:" + tiger;
    }
}

这样,就可以通过 ApplicationContext 的 getBean(“Zoo”) 方法来得到 Zoo.java 了。

这里我还多加了一个 @Scope 注解,应该很好理解。因为 Spring 默认产生的 bean 是单例的,假如我不想使用单例怎么办,xml 文件里面可以在 bean 里面配置 scope 属性。注解也是一样,配置 @Scope 即可,默认是“singleton”即单例,“prototype”表示原型即每次都会 new 一个新的出来。

补充细节

最后再补充一个我发现的细节。假如 animal 包下有 Tiger、domain 包下也有 Tiger,它们二者都加了 @Service 注解,那么在 Zoo.java 中即使明确表示我要引用的是 domain 包下的 Tiger,程序运行的时候依然会报错。

细想,其实这很好理解,两个 Tiger 都使用 @Service 注解标注,意味着两个 Bean 的名字都是“tiger”,那么我在 Zoo.java 中自动装配的是哪个 Tiger 呢?不明确,因此,Spring 容器会抛出 BeanDefinitionStoreException 异常,Caused by:

Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'monkey' for bean class [com.xrq.domain.Monkey] 

参考:Spring 的注解 @Qualifier 用法

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/java/279489.html

(0)
上一篇 2022年8月8日 09:36
下一篇 2022年8月8日 09:52

相关推荐

发表回复

登录后才能评论