ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Spring @Autowired、@Resource 和 @Service

2022-08-08 09:33:39  阅读:402  来源: 互联网

标签:Resource Service Autowired Spring springframework bean org java public


原文: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 用法

标签:Resource,Service,Autowired,Spring,springframework,bean,org,java,public
来源: https://www.cnblogs.com/Higurashi-kagome/p/16560637.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有