1. 程式人生 > 其它 >Spring學習(五)Spring 基於註解裝配Bean

Spring學習(五)Spring 基於註解裝配Bean

Spring 基於註解裝配Bean

前言

​ 在 Spring 中,儘管可以使用 XML 配置檔案實現 Bean 的裝配工作,但如果應用中 Bean 的數量較多,會導致 XML 配置檔案過於臃腫,從而給維護和升級帶來一定的困難。

​ Java 從 JDK 5.0 以後,提供了 Annotation(註解)功能,Spring 2.5 版本開始也提供了對 Annotation 技術的全面支援,我們可以使用註解來配置依賴注入。

​ Spring 預設不使用註解裝配 Bean,因此需要在配置檔案中新增 <context:annotation-config/>,啟用註解。

  • 註解:就是一個類,使用@註解名稱
  • 開發中:使用註解 取代 xml配置檔案。

什麼是註解

Annontation是Java5開始引入的新特徵,中文名稱叫註解

​ 它提供了一種安全的類似註釋的機制,用來將任何的資訊或元資料(metadata)與程式元素(類、方法、成員變數等)進行關聯。為程式的元素(類、方法、成員變數)加上更直觀更明瞭的說明,這些說明資訊是與程式的業務邏輯無關,並且供指定的工具或框架使用。Annontation像一種修飾符一樣,應用於包、型別、構造方法、方法、成員變數、引數及本地變數的宣告語句中。
  Java註解是附加在程式碼中的一些元資訊,用於一些工具在編譯、執行時進行解析和使用,起到說明、配置的功能。註解不會也不能影響程式碼的實際邏輯,僅僅起到輔助性的作用。包含在 java.lang.annotation 包中。

註解的用處:

1、生成文件。這是最常見的,也是java 最早提供的註解。常用的有@param @return 等
2、跟蹤程式碼依賴性,實現替代配置檔案功能。比如Dagger 2依賴注入,未來java開發,將大量註解配置,具有很大用處;
3、在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法並不是覆蓋了超類方法,則編譯時就能檢查出。

更多註解資訊點選這裡-JAVA 註解的基本原理 - Single_Yam - 部落格園 (cnblogs.com)

為什麼要使用註解裝配Bean

傳統的Spring做法是使用.xml檔案來對bean進行注入或者是配置aop、事物,這麼做有兩個缺點:

  1. 如果所有的內容都配置在.xml檔案中,那麼.xml檔案將會十分龐大;如果按需求分開.xml檔案,那麼.xml檔案又會非常多,總之這將導致配置檔案的可讀性與可維護性變得很低。
  2. 在開發中在.java檔案和.xml檔案之間不斷切換,是一件麻煩的事,同時這種思維上的不連貫也會降低開發的效率。

為了解決這兩個問題,Spring引入了註解,通過"@XXX"的方式,讓註解與Java Bean緊密結合,大大減少了配置檔案的體積,又增加了Java Bean的可讀性與內聚性。

使用Spring註解

0.啟用元件掃描

1)使用XML進行掃描

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       					   http://www.springframework.org/schema/beans/spring-beans.xsd
       					   http://www.springframework.org/schema/context
       					   http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 元件掃描,掃描含有註解的類 -->
    <context:component-scan base-package="com.spring.learn2" />
</beans>

配置屬性<context:component-scan base-package="com.spring.learn2" />

其中`base-package

2) @ComponentScan註解啟用了元件掃描

package com.spring.learn2.config;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class BeanConfig {}
package com;

import com.spring.learn2.config.BeanConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(BeanConfig.class);

        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames) {
            System.out.println("beanName: " + beanName);
        }
    }
}

執行效果

beanName: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
beanName: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
beanName: org.springframework.context.annotation.internalCommonAnnotationProcessor
beanName: org.springframework.context.event.internalEventListenerProcessor
beanName: org.springframework.context.event.internalEventListenerFactory
beanName: beanConfig

除了 spring 本身註冊的一些 bean 之外,可以看到最後一行,已經將 BeanConfig 這個類註冊進容器中了。

建立一個配置類,在配置類上新增 @ComponentScan 註解。該註解預設會掃描該類所在的包下所有的配置類,相當於之前的 <context:component-scan>

類BeanConfig通過Java程式碼定義了Spring的裝配規則。觀察可知,BeanConfig類並沒有顯式地宣告任何bean,只不過它使用了@ComponentScan註解,這個註解能夠在Spring中啟用元件掃描

如果沒有其他配置的話,@ComponentScan預設會掃描與配置類相同的包。因為BeanConfig類位於com.spring.learn2.config包中,因此Spring將會掃描這個包以及這個包下的所有子包,查詢帶有@Component註解的類。這樣的話,就能發現使用了註解的類,並且會在Spring中自動為其建立一個Bean

3)指定要掃描的包

(使用@ComponentScan 的 valule 屬性來配置)

@ComponentScan(value = "com.spring.learn2")
public class BeanConfig {

}
excludeFilters 和 includeFilters 的使用
使用 excludeFilters 來按照規則排除某些包的掃描。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

@ComponentScan(value = "com.spring.learn2",
        excludeFilters = {@Filter(type = FilterType.ANNOTATION,
        value = {Controller.class})})
public class BeanConfig {

}

excludeFilters 的引數是一個 Filter[] 陣列,然後指定 FilterType 的型別為 ANNOTATION,也就是通過註解來過濾,最後的 value 則是Controller 註解類。

​ 配置之後,在 spring 掃描的時候,就會跳過 com.spring.learn2 包下,所有被 @Controller 註解標註的類

使用 includeFilters 來按照規則只包含某些包的掃描。
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

@ComponentScan(value = "com.spring.learn2", includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})})
public class BeanConfig {

}

spring 預設會自動發現被 @Component、@Repository、@Service 和 @Controller 標註的類,並註冊進容器中。要達到只包含某些包的掃描效果,就必須將這個預設行為給禁用掉(在 @ComponentScan 中將 useDefaultFilters 設為 false 即可)。

@ComponentScan 的 useDefaultFilters 屬性,該屬性預設值為 true。

1. 定義Bean@Component

需要在類上使用註解@Component,該註解的value屬性用於指定該bean的id值。

@Component(value = "role")
public class Role {
    private Long id;
    private String roleName;
    private String note;
}

2. Bean的作用域@Scope

需要在類上使用註解@Scope,其value屬性用於指定作用域。預設為singleton。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    ...
}

3. 基本型別屬性注入@Value

需要在屬性上使用註解@Value,該註解的value屬性用於指定要注入的值。
使用該註解完成屬性注入時,類中無需setter。當然,若屬性有setter,則也可將其加到setter上。

package com.spring.learn2;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "role")
public class Role {
    @Value("1")
    private Long id;
    @Value("role_name_1")
    private String roleName;
    @Value("role_note_1")
    private String note;
}

4.按型別注入域屬性@Autowired

@Autowired是spring的註解

需要在域屬性上使用註解@Autowired,該註解預設使用按型別自動裝配Bean的方式。
使用該註解完成屬性注入時,類中無需setter。當然,若屬性有setter,則也可將其加到setter上。

package com.spring.learn2;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Autowired
    private Role role;

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

5. 按名稱注入域屬性@Autowired與@Qualifier

需要在域屬性上聯合使用註解@Autowired與@Qualifier。

@Qualifier的value屬性用於指定要匹配的Bean的id值。同樣類中無需setter,也可加到setter上。

package com.spring.learn2;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Autowired(required =false)
 	@Qualifier("role")
    private Role role;

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

@Autowired還有一個屬性required,預設值為true,表示當匹配失敗後,會終止程式執行。若將其值設定為false,則匹配失敗,將被忽略,未匹配的屬性值為null。

6. 域屬性註解@Resource

@Resource是java自帶的註解

Spring提供了對JSR-250規範中定義@Resource標準註解的支援。@Resource註解既可以按名稱匹配Bean,也可以按型別匹配Bean。使用該註解,要求JDK必須是6及以上版本。

1. 按型別注入域屬性

@Resource註解若不帶任何引數,則會按照型別進行Bean的匹配注入。

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Resource
    private Role role;

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

2. 按名稱注入域屬性

@Resource註解指定其name屬性,則name的值即為按照名稱進行匹配的Bean的id。

package com.spring.learn2;


import org.springframework.stereotype.Component;
import javax.annotation.Resource;


@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Resource(name="role")
    private Role role;

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

7. Bean的生命始末@PostConstruct與@PreDestroy

在方法上使用@PostConstruct,與原來的init-method等效。在方法上使用@PreDestroy,與destroy-method等效。

package com.spring.learn2;


import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;


@Component("RoleService")
public class RoleServiceImpl implements RoleService {
    @Resource(name="role")
    private Role role;

    @PostConstruct
    public void init(){
        System.out.println("Bean初始化後執行");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("Bean銷燬前執行");
    }

    /**** setter and getter ****/
    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }
}

Spring 中常用的註解

@Component取代<bean class="">

@Component("id") 取代 <bean id="" class="">

web開發,提供3個@Component註解衍生註解(功能一樣)取代<bean class="">

@Repository :dao層 。

@Service:service層 。

@Controller:web層。

@Component

可以使用此註解描述 Spring 中的 Bean,但它是一個泛化的概念,僅僅表示一個元件(Bean),並且可以作用在任何層次。使用時只需將該註解標註在相應類上即可。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "role")
public class Role {
    @Value("1")
    private Long id;
    @Value("role_name_1")
    private String roleName;
    @Value("role_note_1")
    private String note;
}
  • 註解@Component代表Spring IoC會把這個類掃描生成Bean例項,而其中的value屬性代表這個類在Spring中的id,這就相當於XML方式定義的Bean的id,也可以簡寫成@Component("role"),甚至直接寫成@Component,對於不寫的,Spring IoC容器就預設類名,但是以首字母小寫的形式作為id,為其生成物件,配置到容器中。
  • 註解@Value代表的是值的注入,這裡只是簡單注入一些值,其中id是一個long型,注入的時候Spring會為其轉化型別。

@Repository

用於將資料訪問層(DAO層)的類標識為 Spring 中的 Bean,其功能與 @Component 相同。

@Service

通常作用在業務層(Service 層),用於將業務層的類標識為 Spring 中的 Bean,其功能與 @Component 相同。

@Controller

通常作用在控制層(如 Struts2 的 Action、SpringMVC 的 Controller),用於將控制層的類標識為 Spring 中的 Bean,其功能與 @Component 相同。

@Autowired

自動裝配

​ 通過學習Spring IoC容器,我們知道Spring是先完成Bean的定義和生成,然後尋找需要注入的資源。也就是當Spring生成所有的Bean後,如果發現這個註解,他就會在Bean中查詢,然後找到對應的型別,將其注入進來,這樣就完成依賴注入了。

​ 所謂自動裝配技術是一種由Spring自己發現對應的Bean,自動完成裝配工作的方式,它會應用到一個十分常用的註解@Autowired上,這個時候Spring會根據型別去尋找定義的Bean然後將其注入,這裡需要留意按型別(Role)的方式。

​ 可以應用到 Bean 的屬性變數、屬性的 setter 方法、非 setter 方法及建構函式等,配合對應的註解處理器完成 Bean 的自動配置工作。預設按照 Bean 的型別進行裝配。

  • 示例

    role

    package com.spring.learn2;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    @Component(value = "role")
    public class Role {
        @Value("1")
        private Long id;
        @Value("role_name_1")
        private String roleName;
        @Value("role_note_1")
        private String note;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getRoleName() {
            return roleName;
        }
    
        public void setRoleName(String roleName) {
            this.roleName = roleName;
        }
    
        public String getNote() {
            return note;
        }
    
        public void setNote(String note) {
            this.note = note;
        }
    }
    

    RoleService

    package com.spring.learn2;
            
    public interface RoleService {
        public void printRoleInfo();
    }
    
    package com.spring.learn2;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
      
    @Component("RoleService")
    public class RoleServiceImpl implements RoleService {
        @Autowired
        private Role role;
      
        @Override
        public void printRoleInfo() {
            System.out.println("id =" + role.getId());
            System.out.println("roleName =" + role.getRoleName());
            System.out.println("note =" + role.getNote());
        }
      
        /**** setter and getter ****/
        public Role getRole() {
            return role;
        }
      
        public void setRole(Role role) {
            this.role = role;
        }
    }
      
    

    MainApp

    package com;
    
    
    import com.spring.learn2.RoleService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MainApp {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
            RoleService uc = (RoleService) ctx.getBean("RoleService");
            uc.printRoleInfo();
        }
    }
    
    

    Beans.xml

    掃描含有註解類

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           					   http://www.springframework.org/schema/beans/spring-beans.xsd
           					   http://www.springframework.org/schema/context
           					   http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 元件掃描,掃描含有註解的類 -->
        <context:component-scan base-package="com.spring.learn2" />
    </beans>
    

    ​ 這裡的@Autowired註解,表示在Spring IoC定位所有的Bean後,這個欄位需要按型別注入,這樣IoC容器就會尋找資源,然後將其注入。。比如程式碼Role和程式碼RoleServiceImpl的兩個Bean,假設將其定義,那麼Spring IoC容器會為它們先生成對應的例項,然後依據@Autowired註解,按照型別找到定義的例項,將其注入。

     IoC容器有時候會尋找失敗,在預設的情況下尋找失敗它就會丟擲異常,也就是說預設情況下,Spring IoC容器會認為一定要找到對應的Bean來注入這個欄位,有些時候這並不是一個真實的需要,比如日誌,有時候我們會覺得這是可有可無的,這個時候可以通過@Autowired的配置項required來改變它,比如@Autowired(required=false)。

    ​ 正如之前所談到的在預設情況下是必須注入成功的,所以這裡的required的預設值為true。當把配置修改為了false時,就告訴Spring IoC容器,假如在已經定義好的Bean中找不到對應的型別,允許不注入,這樣也就沒有了異常丟擲,只是這樣這個欄位可能為空,讀者要自行校驗,以避免發生空指標異常。在大部分的情況下,都不需要這樣修改。
    ​ @Autowired除可以配置在屬性之外,還允許方法配置,常見的Bean的setter方法也可以使用它來完成注入。

@Resource

​ 作用與 Autowired 相同,區別在於 @Autowired 預設按照 Bean 型別裝配,而 @Resource 預設按照 Bean 例項名稱進行裝配。

​ @Resource 中有兩個重要屬性:name 和 type。

​ Spring 將 name 屬性解析為 Bean 的例項名稱,type 屬性解析為 Bean 的例項型別。如果指定 name 屬性,則按例項名稱進行裝配;如果指定 type 屬性,則按 Bean 型別進行裝配。如果都不指定,則先按 Bean 例項名稱裝配,如果不能匹配,則再按照 Bean 型別進行裝配;如果都無法匹配,則丟擲 NoSuchBeanDefinitionException 異常。

​ 另外要注意,@Resource是java自帶的註解,不是Spring中的註解。@Resource註解完整的包路徑為

import  javax.annotation.Resource;

示例

@Resource(name = "userServiceImpl")
private UserService userService;

@Qualifier

與 @Autowired 註解配合使用,會將預設的按 Bean 型別裝配修改為按 Bean 的例項名稱裝配,Bean 的例項名稱由 @Qualifier 註解的引數指定。

 @Autowired
 @Qualifier("userServiceImp")
 private UserSerevice userService;

@Service@Controller 這些註解要放在介面的實現類上,而不是介面上面。

@Autowired@Resource是用來修飾字段,建構函式,或者設定方法,並做注入的。

@Service@Controller@Repository@Component則是用來修飾類,標記這些類要生成bean。

註解使用前提,新增名稱空間,讓spring掃描含有註解類

示例

Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       					   http://www.springframework.org/schema/beans/spring-beans.xsd
       					   http://www.springframework.org/schema/context
       					   http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 元件掃描,掃描含有註解的類 -->
    <context:component-scan base-package="com.spring.learn1" />
</beans>

UserDao

package com.spring.learn1;

public interface UserDao {
    /**
     * 輸出方法
     */
    public void outContent();
}

UserDaoImpl

package com.spring.learn1;

import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void outContent() {
        System.out.println("dao方法");
    }
}
package com.spring.learn1;

public interface UserService {
    /**
     * 輸出方法
     */
    public void outContent();
}

UserServiceImpl

package com.spring.learn1;

import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserServiceImpl implements UserService{

    @Resource(name="userDao")
    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void outContent() {
        userDao.outContent();
        System.out.println("service");
    }
}

UserController

package com.spring.learn1;

import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
@Controller("userController")
public class UserController {
    @Resource(name = "userService")
    private UserService userService;
    public UserService getUserService() {
        return userService;
    }
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    public void outContent() {
        userService.outContent();
        System.out.println("content");
    }
}

MainApp

package com;


import com.spring.learn1.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
        UserController uc = (UserController) ctx.getBean("userController");
        uc.outContent();
    }
}

執行時以下錯誤

org.springframework.context.support.AbstractApplicationContext refresh

警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [。。。]: Post-processing of merged bean definition failed; nested exception is java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [。。。]: Post-processing of merged bean definition failed; nested exception is java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
。。。。
Caused by: java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;

檢查發現不存在javax.annotation這個包

在pom.xml檔案中引入

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.1</version>
</dependency>

執行如下

dao方法
service
content

@Resource vs @Autowired

@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之間的耦合。

總結一下:

@Resource根據name和type,是先Name後Type,@Autowired是Type,一般情況下我們最好使用@Resource。

​ 文中詳細講解了@Service、@Autowired、@Resource和@Qualifier的用法,其中重點講述了@Autowired、@Resource的區別,那麼對於@Component、@Repository、@Controller這3個註解,文中也就開頭提到,這3個註解其實和@Service一個含義,只是我們在寫程式碼時,會進行分層,比如DAO層、Service層、Action層,分別可以用@Repository、@Service、@Controller表示,其實也就字面含義不一樣,效果其實是一樣的,然後@Component可以作用在任何層次。所以看起來有7個註解,其實你可以理解只有4個。