04 Spring的@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.nnngu.bean.Zoo" >
        <property name="tiger" ref="tiger" />
        <property name="monkey" ref="monkey" />
    </bean>
    
    <bean id="tiger" class="com.nnngu.domain.Tiger" />
    <bean id="monkey" class="com.nnngu.domain.Monkey" />
    
</beans>

都很熟悉,權當復習一遍了。

@Autowired

@Autowired顧名思義,就是自動裝配,其作用是為了消除Java代碼里面的getter/setter與bean屬性中的property。當然,getter看個人需求,如果私有屬性需要對外提供的話,應當保留該getter

因此,引入@Autowired注解,先看一下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">
    
    <context:component-scan base-package="com.nnngu" />
    
    <bean id="zoo" class="com.nnngu.bean.Zoo" />
    <bean id="tiger" class="com.nnngu.domain.Tiger" />
    <bean id="monkey" class="com.nnngu.domain.Monkey" />
    
</beans>

注意上面代碼中的<context:component-scan base-package="com.nnngu" /> ,作用是告訴Spring我要使用注解了,Spring會自動掃描com.nnngu路徑下的注解。

之前在zoo這個bean里面應當注入的兩個屬性tigermonkey,現在不需要注入了。再看一下 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,並自動注入到相應的地方去。

(有一個細節性的問題是,假如配置文件的bean里面有兩個property,Zoo.java里面又去掉了屬性的getter/setter並使用@Autowired注解標注這兩個屬性那會怎么樣?答案是Spring會按照xml優先的原則去Zoo.java中尋找這兩個屬性的getter/setter,導致的結果就是初始化bean報錯。)

此時如果我把.xml文件的<bean id="tiger" class="com.nnngu.domain.Tiger" /><bean id="monkey" class="com.nnngu.domain.Monkey" /> 去掉,再運行,會拋出如下異常:

org.springframework.beans.factory.BeanCreationException: Could not autowire field

因為,@Autowired注解要去尋找的是一個bean,Tiger 和 Monkey 的bean定義都給去掉了,自然就不是一個bean了,Spring容器找不到也很好理解。那么,如果屬性的bean找不到,我又不想讓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 這兩個屬性的 bean,Spring容器也不再拋出異常,而是認為這兩個屬性為null。

@Autowired接口注入

上面的比較簡單,我們只是簡單注入一個Java類,那么如果有一個接口,有多個實現,Bean里引用的是接口名,又該怎么做呢?比如有一個Car接口:

public interface Car
{
    public String carName();
}

兩個實現類BMW和Benz:


public class BMW implements Car
{
    public String carName()
    {
        return "BMW car";
    }
}

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

寫一個CarFactory,引用Car:


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

不用說,一定是報錯的,Car接口有兩個實現類,Spring並不知道應當引用哪個實現類。這種情況通常有兩個解決辦法:

1、刪除其中一個實現類,Spring會自動去base-package下尋找Car接口的實現類,發現Car接口只有一個實現類,便會直接引用這個實現類。

2、實現類就是有多個該怎么辦?此時可以使用@Qualifier注解:


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

注意@Qualifier注解括號里面的應當是Car接口實現類的類名,我之前試的時候一直以為是bean的名字,所以寫了"bMW",結果一直報錯。

@Resource

@Resource注解放在@Autowired下面說,是因為它們的作用非常相似。先看一下@Resource,直接在Zoo.java中寫:


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的配置文件里面還有三個bean,下一步的簡化是把這三個bean也給去掉,使得Spring配置文件里面只有一個自動掃描的標簽。這樣做增強了Java代碼的內聚性並進一步減少配置文件。

先看一下配置文件(三個bean都刪除了):

<?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.nnngu" />
    
</beans>

下面以Zoo.java為例,其余的Monkey.javaTiger.java都一樣:

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

這樣,Zoo.java在Spring容器中存在的形式就是zoo,即可以通過 ApplicationContextgetBean("zoo")方法來得到Zoo.java@Service注解,其實做了兩件事情:

1、聲明Zoo.java是一個bean,這點很重要,因為Zoo.java是一個bean,其他的類才可以使用@Autowired將Zoo作為一個成員變量自動注入

2、Zoo.java在bean中的id是zoo,即類名且首字母小寫

如果我不想用這種形式,而想讓Zoo.java在Spring容器中的名字就叫做Zoo,可以嗎?答案是可以的:

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

這樣,就可以通過ApplicationContextgetBean("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容器會拋出ConflictingBeanDefinitionException異常。


本文永久更新地址:https://github.com/nnngu/LearningNotes/blob/master/Spring/04%20Spring%E7%9A%84%40Autowired%E6%B3%A8%E8%A7%A3%E3%80%81%40Resource%E6%B3%A8%E8%A7%A3%E3%80%81%40Service%E6%B3%A8%E8%A7%A3.md


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM