Java開發學習(十二)----基於註解開發依賴注入

2022-07-14 06:01:07

Spring為了使用註解簡化開發,並沒有提供建構函式注入setter注入對應的註解,只提供了自動裝配的註解實現。

1、環境準備

首先準備環境:

  • 建立一個Maven專案

  • pom.xml新增Spring的依賴

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
    </dependencies>
  • 新增一個設定類SpringConfig

    @Configuration
    @ComponentScan("com.itheima")
    public class SpringConfig {
    }
  • 新增BookDao、BookDaoImpl、BookService、BookServiceImpl類

    public interface BookDao {
        public void save();
    }
    @Repository
    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ..." );
        }
    }
    public interface BookService {
        public void save();
    }
    @Service
    public class BookServiceImpl implements BookService {
        private BookDao bookDao;
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;
        }
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
        }
    }
  • 建立執行類App

    public class App {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            BookService bookService = ctx.getBean(BookService.class);
            bookService.save();
        }
    }

最終建立好的專案結構如下:

環境準備好後,執行後會發現有問題

出現問題的原因是,在BookServiceImpl類中新增了BookDao的屬性,並提供了setter方法,但是目前是沒有提供設定注入BookDao的,所以bookDao物件為Null,呼叫其save方法就會報空指標異常

2、註解實現按照型別注入

對於這個問題使用註解該如何解決?

(1) 在BookServiceImpl類的bookDao屬性上新增@Autowired註解

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
​
//    @Autowired    
//    public void setBookDao(BookDao bookDao) {
//        this.bookDao = bookDao;
//    }
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

注意:

  • @Autowired可以寫在屬性上,也可也寫在setter方法上,最簡單的處理方式是寫在屬性上並將setter方法刪除掉

  • 為什麼setter方法可以刪除呢?

    • 自動裝配基於反射設計建立物件並通過暴力反射為私有屬性進行設值

    • 普通反射只能獲取public修飾的內容

    • 暴力反射除了獲取public修飾的內容還可以獲取private修改的內容

    • 所以此處無需提供setter方法

(2)@Autowired是按照型別注入,那麼對應BookDao介面如果有多個實現類,比如新增BookDaoImpl2

@Repository
public class BookDaoImpl2 implements BookDao {
    public void save() {
        System.out.println("book dao save ...2");
    }
}

這個時候再次執行App,就會報錯

此時,按照型別注入就無法區分到底注入哪個物件,解決方案:按照名稱注入

  • 先給兩個Dao類分別起個名稱

    @Repository("bookDao")
    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ..." );
        }
    }
    @Repository("bookDao2")
    public class BookDaoImpl2 implements BookDao {
        public void save() {
            System.out.println("book dao save ...2" );
        }
    }

    此時就可以注入成功,但是得思考個問題:

    • @Autowired是按照型別注入的,給BookDao的兩個實現起了名稱,它還是有兩個bean物件,為什麼不報錯?

    • @Autowired預設按照型別自動裝配,如果IOC容器中同類的Bean找到多個,就按照變數名和Bean的名稱匹配。因為變數名叫bookDao而容器中也有一個booDao,所以可以成功注入。

    • 分析下面這種情況是否能完成注入呢?

    • 不行,因為按照型別會找到多個bean物件,此時會按照bookDao名稱去找,因為IOC容器只有名稱叫bookDao1bookDao2,所以找不到,會報NoUniqueBeanDefinitionException

3、註解實現按照名稱注入

當根據型別在容器中找到多個bean,注入引數的屬性名又和容器中bean的名稱不一致,這個時候該如何解決,就需要使用到@Qualifier來指定注入哪個名稱的bean物件。

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    @Qualifier("bookDao1")
    private BookDao bookDao;
    
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

@Qualifier註解後的值就是需要注入的bean的名稱。

注意:@Qualifier不能獨立使用,必須和@Autowired一起使用

4、簡單資料型別注入

參照型別看完,簡單型別注入就比較容易懂了。簡單型別注入的是基本資料型別或者字串型別,下面在BookDaoImpl類中新增一個name屬性,用其進行簡單型別注入

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    private String name;
    public void save() {
        System.out.println("book dao save ..." + name);
    }
}

資料型別換了,對應的註解也要跟著換,這次使用@Value註解,將值寫入註解的引數中就行了

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    @Value("itheima")
    private String name;
    public void save() {
        System.out.println("book dao save ..." + name);
    }
}

注意資料格式要匹配,如將"abc"注入給int值,這樣程式就會報錯。

介紹完後,會有一種感覺就是這個註解好像沒什麼用,跟直接賦值是一個效果,還沒有直接賦值簡單,所以這個註解存在的意義是什麼?

5、註解讀取properties組態檔

@Value一般會被用在從properties組態檔中讀取內容進行使用,具體如何實現?

步驟1:resource下準備properties檔案

jdbc.properties

name=itheima888
步驟2: 使用註解載入properties組態檔

在設定類上新增@PropertySource註解

@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
​
步驟3:使用@Value讀取組態檔中的內容
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    @Value("${name}")
    private String name;
    public void save() {
        System.out.println("book dao save ..." + name);
    }
}

步驟4:執行程式

執行App類,檢視執行結果,說明組態檔中的內容已經被載入到

注意:

  • 如果讀取的properties組態檔有多個,可以使用@PropertySource的屬性來指定多個

    @PropertySource({"jdbc.properties","xxx.properties"})
  • @PropertySource註解屬性中不支援使用萬用字元*,執行會報錯

    @PropertySource({"*.properties"})
  • @PropertySource註解屬性中可以把classpath:加上,代表從當前專案的根路徑找檔案

    @PropertySource({"classpath:jdbc.properties"})

知識點1:@Autowired

名稱 @Autowired
型別 屬性註解 或 方法註解(瞭解) 或 方法形參註解(瞭解)
位置 屬性定義上方 或 標準set方法上方 或 類set方法上方 或 方法形參前面
作用 為參照型別屬性設定值
屬性 required:true/false,定義該屬性是否允許為null

知識點2:@Qualifier

名稱 @Qualifier
型別 屬性註解 或 方法註解(瞭解)
位置 屬性定義上方 或 標準set方法上方 或 類set方法上方
作用 為參照型別屬性指定注入的beanId
屬性 value(預設):設定注入的beanId

知識點3:@Value

名稱 @Value
型別 屬性註解 或 方法註解(瞭解)
位置 屬性定義上方 或 標準set方法上方 或 類set方法上方
作用 為 基本資料型別 或 字串型別 屬性設定值
屬性 value(預設):要注入的屬性值

知識點4:@PropertySource

名稱 @PropertySource
型別 類註解
位置 類定義上方
作用 載入properties檔案中的屬性值
屬性 value(預設):設定載入的properties檔案對應的檔名或檔名組成的陣列