深入理解註解驅動設定與XML設定的融合與區別

2023-06-07 18:00:49
摘要:本文旨在深入探討Spring框架的註解驅動設定與XML設定,揭示兩者之間的相似性與差異。

本文分享自華為雲社群《Spring高手之路2——深入理解註解驅動設定與XML設定的融合與區別》,作者:磚業洋__ 。

本文旨在深入探討Spring框架的註解驅動設定與XML設定,揭示兩者之間的相似性與差異。我們首先介紹了設定類的編寫與Bean的註冊,然後比較了註解驅動的IOC依賴注入與XML依賴注入。文章進一步解析了Spring的元件註冊與元件掃描,包括使用@ComponentScan和XML啟用component-scan的情況,以及不使用@ComponentScan的場景。接下來,我們深入探討了其他相關的元件

1.設定類的編寫與Bean的註冊

XML設定中,我們通常採用ClassPathXmlApplicationContext,它能夠載入類路徑下的XML組態檔來初始化Spring應用上下文。然而,在註解驅動的設定中,我們則使用以Annotation開頭和ApplicationContext結尾的類,如AnnotationConfigApplicationContext。AnnotationConfigApplicationContext是Spring容器的一種,它實現了ApplicationContext介面。

對比於 XML 檔案作為驅動,註解驅動需要的是設定類。一個設定類就可以類似的理解為一個 XML 。設定類沒有特殊的限制,只需要在類上標註一個 @Configuration 註解即可。

我們建立一個 Book 類:

public class Book {
 private String title;
 private String author;
 public String getTitle() {
 return title;
 }
 public void setTitle(String title) {
 this.title = title;
 }
 public String getAuthor() {
 return author;
 }
 public void setAuthor(String author) {
 this.author = author;
 }
}

在 xml 中宣告 Bean 是通過 <bean> 標籤

<bean id="book" class="com.example.Book">
 <property name="title" value="Java Programming"/>
 <property name="author" value="Unknown"/>
</bean>

如果要在設定類中替換掉 <bean> 標籤,需要使用 @Bean 註解

我們建立一個設定類來註冊這個 Book bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LibraryConfiguration {
 @Bean
 public Book book() {
 Book book = new Book();
 book.setTitle("Java Programming");
 book.setAuthor("Unknown");
 return book;
 }
}

在這個設定中,我們使用了 @Configuration 註解來表示這是一個設定類,類似於一個 XML 檔案。我們在 book() 方法上使用了 @Bean 註解,這意味著這個方法將返回一個由 Spring 容器管理的物件。這個物件的型別就是 Book,bean 的名稱id就是方法的名稱,也就是 「book」。

類似於 XML 設定的 <bean> 標籤,@Bean 註解負責註冊一個 bean。你可以把 @Bean 註解看作是 <bean> 標籤的替代品。

如果你想要更改這個 bean 的名稱,你可以在 @Bean 註解中使用 name 屬性:

 @Bean(name="mybook")
 public Book book() {
 Book book = new Book();
 book.setTitle("Java Programming");
 book.setAuthor("Unknown");
 return book;
 }

這樣,這個 Book bean 的名稱就變成了 「mybook」。

啟動並初始化註解驅動的IOC容器

@SpringBootApplication
public class DemoApplication {
 public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfiguration.class);
 // 從容器中獲取 Book bean
 LibraryConfiguration libraryConfiguration = context.getBean(LibraryConfiguration.class);
 System.out.println(libraryConfiguration.book().getTitle());
 System.out.println(libraryConfiguration.book().getAuthor());
 }
}

ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfiguration.class)這個語句建立了一個Spring的應用上下文,它是以設定類LibraryConfiguration.class作為輸入的,這裡明確指定設定類的Spring應用上下文,適用於更一般的Spring環境。

對比一下ApplicationContext context = SpringApplication.run(DemoApplication.class, args);這個語句則是Spring Boot應用的入口,啟動一個Spring Boot應用。SpringApplication.run()方法會建立一個Spring Boot應用上下文(也就是一個SpringApplication物件),這個上下文包含了Spring Boot應用所有的Bean和設定類,還有大量的預設設定。這個方法之後,Spring Boot的自動設定就會起作用。你可以把SpringApplication.run()建立的Spring Boot上下文看作是更加功能豐富的Spring上下文。

列印結果:

Java Programming和Unknown被列印,執行成功。

注意:@SpringBootApplication是一個複合註解,它等效於同時使用了@Configuration,@EnableAutoConfiguration和@ComponentScan。這三個註解的作用是:

  • @Configuration:指明該類是一個設定類,它可能會有零個或多個@Bean註解,方法產生的範例由Spring容器管理。
  • @EnableAutoConfiguration:告訴Spring Boot根據新增的jar依賴自動設定你的Spring應用。
  • @ComponentScan:Spring Boot會自動掃描該類所在的包以及子包,查詢所有的Spring元件,包括@Configuration類。

在非Spring Boot的傳統Spring應用中,我們通常使用AnnotationConfigApplicationContext或者ClassPathXmlApplicationContext等來手動建立和初始化Spring的IOC容器。

"非Spring Boot的傳統Spring應用"是指在Spring Boot專案出現之前的Spring專案,這些專案通常需要手動設定很多東西,例如資料庫連線、事務管理、MVC控制器等。這種型別的Spring應用通常需要開發者對Spring框架有深入的瞭解,才能做出正確的設定。

Spring Boot是Spring專案的一個子專案,它旨在簡化Spring應用的建立和設定過程。Spring Boot提供了一系列的"起步依賴",使得開發者只需要新增少量的依賴就可以快速開始專案的開發。此外,Spring Boot還提供了自動設定的特性,這使得開發者無需手動設定資料庫連線、事務管理、MVC控制器等,Spring Boot會根據專案的依賴自動進行設定。

因此,"非Spring Boot的傳統Spring應用"通常需要手動建立和初始化Spring的IOC容器,比如使用AnnotationConfigApplicationContext或ClassPathXmlApplicationContext等。在Spring Boot應用中,這個過程被自動化了,開發者只需要在main方法中呼叫SpringApplication.run方法,Spring Boot就會自動建立和初始化Spring的IOC容器。SpringApplication.run(Application.class, args);語句就是啟動Spring Boot應用的關鍵。它會啟動一個應用上下文,這個上下文會載入所有的Spring元件,並且也會啟動Spring的IOC容器。在這個過程中,所有通過@Bean註解定義的bean都會被建立,並註冊到IOC容器中。

有人說,那學習Spring Boot就好了,學什麼Spring和Spring MVC啊,這不是落後了嗎

Spring Boot並不是Spring框架的替代品,而是建立在Spring框架之上的一種工具,它內部仍然使用Spring框架的很多核心技術,包括Spring MVC。所以,當我們在使用Spring Boot時,我們實際上仍然在使用Spring MVC來處理Web層的事務。

簡而言之,Spring MVC是一個用於構建Web應用程式的框架,而Spring Boot是一個用於簡化Spring應用程式開發的工具,它內部仍然使用了Spring MVC。你在Spring Boot應用程式中使用的@Controller、@Service、@Autowired等註解,其實都是Spring框架提供的,所以,原理性的東西還是需要知道。

2. 註解驅動IOC的依賴注入與XML依賴注入對比

我們就以上面的例子來說,假設設定類註冊了兩個bean,並設定相關的屬性:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LibraryConfiguration {
 @Bean
 public Book book() {
 Book book = new Book();
 book.setTitle("Java Programming");
 book.setAuthor("Unknown");
 return book;
 }
 @Bean
 public Library library() {
 Library library = new Library();
 library.setBook(book());
 return library;
 }
}

這裡的方法有@Bean註解,這個註解告訴Spring,這個方法返回的物件需要被註冊到Spring的IOC容器中。

如果不用註解,要實現相同功能的話,對應的XML設定如下:

<bean id="book" class="com.example.Book">
 <property name="title" value="Java Programming"/>
 <property name="author" value="Unknown"/>
</bean>
<bean id="library" class="com.example.Library">
 <property name="book" ref="book"/>
</bean>

在這個XML設定中,我們定義了兩個<bean>元素,分別用來建立Book物件和Library物件。在建立Book物件時,我們使用了<property>元素來設定title和author屬性。在建立Library物件時,我們也使用了<property>元素,但是這次我們使用了ref屬性來參照已經建立的Book物件,這就相當於將Book物件注入到Library物件中。

3. Spring中元件的概念

在Spring框架中,當我們說 「元件」 的時候,我們通常指的是被Spring管理的各種Java物件,這些物件在Spring的應用上下文中作為Bean存在。這些元件可能是服務層的類、資料存取層的類、控制器類、設定類等等。

@ComponentScan註解會掃描指定的包(及其子包)中的類,如果這些類上標註了@Component、@Controller、@Service、@Repository、@Configuration等註解,那麼Spring就會為這些類建立Bean定義,並將這些Bean定義註冊到Spring的應用上下文中。因此,我們通常說@ComponentScan進行了"元件掃描",因為它掃描的是標註了上述註解的類,這些類在Spring中都被視為元件。

而這些註解標記的類,最終在Spring的應用上下文中都會被建立為Bean,因此,你也可以理解@ComponentScan為"Bean掃描"。但是需要注意的是,@ComponentScan只負責掃描和註冊Bean定義,Bean定義就是後設資料描述,包括瞭如何建立Bean範例的資訊。

總結一下,@ComponentScan註解會掃描並註冊的"元件"包括:

  • 標註了@Component註解的類
  • 標註了@Controller註解的類(Spring MVC中的控制器元件)
  • 標註了@Service註解的類(服務層元件)
  • 標註了@Repository註解的類(資料存取層元件)
  • 標註了@Configuration註解的類(設定類)

這些元件最終都會在Spring的應用上下文中以Bean的形式存在。

4. 元件註冊

這裡Library 標註 @Configuration 註解,即代表該類會被註冊到 IOC 容器中作為一個 Bean。

@Component
public class Library {
}

相當於 xml 中的:

<bean id="library" class="com.example.demo.configuration.Library">

如果想指定 Bean 的名稱,可以直接在 @Configuration 中宣告 value 屬性即可

@Component("libra")
public class Library {
}

@Component("libra")就將這個bean的名稱改為了libra,如果不指定 Bean 的名稱,它的預設規則是 「類名的首字母小寫」(例如Library預設名稱是 library )

5. 元件掃描

如果我們只寫了@Component, @Configuration 這樣的註解,IOC容器是找不到這些元件的。

5.1 使用@ComponentScan的元件掃描

忽略掉之前的例子,在這裡我們需要執行的程式碼如下:

@Component
public class Book {
 private String title = "Java Programming";
 private String author = "Unknown";
 public String getTitle() {
 return title;
 }
 public void setTitle(String title) {
 this.title = title;
 }
 public String getAuthor() {
 return author;
 }
 public void setAuthor(String author) {
 this.author = author;
 }
}
@Component
public class Library {
 @Resource
 private Book book;
 public Book getBook() {
 return book;
 }
 public void setBook(Book book) {
 this.book = book;
 }
}

如果不寫@ComponentScan,而且@Component註解標識的類不在當前包或者子包,那麼就會報錯。

難道@Component註解標識的類在當前包或者當前包的子包,主程式上就可以不寫@ComponentScan了嗎?

是的!前面說了,@SpringBootApplication 包含了 @ComponentScan,其實已經幫我們寫了!只有元件和主程式不在一個共同的根包下,才需要顯式地使用 @ComponentScan 註解。由於 Spring Boot 的設計原則是「約定優於設定」,所以推薦將主應用類放在根包下。

在應用中,我們的元件(帶有 @Component、@Service、@Repository、@Controller 等註解的類)和主設定類位於不同的包中,並且主設定類或者啟動類沒有使用 @ComponentScan 指定掃描這些包,那麼在執行時就會報錯,因為Spring找不到這些元件。

主程式:

@SpringBootApplication
@ComponentScan(basePackages = "com.example")
public class DemoApplication {
 public static void main(String[] args) {
 ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
 Library library = context.getBean(Library.class);
 System.out.println(library.getBook().getTitle());
 System.out.println(library.getBook().getAuthor());
 }
}

@ComponentScan 不一定非要寫在主程式(通常是指 Spring Boot 的啟動類)上,它可以寫在任何設定類(標記有 @Configuration 註解的類)上。@ComponentScan 註解會告訴 Spring 從哪些包開始進行元件掃描。

為了簡化設定,我們通常會將 @ComponentScan 放在主程式上,因為主程式一般會位於根包下,這樣可以掃描到所有的子包。這裡為了演示,並沒有把主程式放在根目錄。

我們上面說過,@ComponentScan只負責掃描和註冊Bean定義,只有需要某個Bean時,這個Bean才會範例化。

那怎麼才能知道是不是需要這個Bean呢?

我來給大家舉例子,並且還會說明Bean的建立順序問題,"需要某個Bean"通常體現在以下幾個方面:

  • 依賴注入(Dependency Injection): 如果一個BeanA的欄位或者構造方法被標註為@Autowired或者@Resource,那麼Spring就會嘗試去尋找型別匹配的BeanB並注入到BeanA中。在這個過程中,如果BeanB還沒有被建立,那麼Spring就會先建立BeanB的範例。
@Component
public class BeanA {
 @Autowired
 private BeanB beanB;
}
@Component
public class BeanB {
}

BeanA依賴於BeanB。在這種情況下,當你嘗試獲取BeanA的範例時,Spring會首先建立BeanB的範例,然後把這個範例注入到BeanA中,最後建立BeanA的範例。在這個例子中,BeanB會先於BeanA被建立。

這種方式的一個主要優點是,我們不需要關心Bean的建立順序,Spring會自動解決這個問題。這是Spring IoC容器的一個重要特性,也是為什麼它能夠使我們的程式碼更加簡潔和易於維護的原因。

  • Spring框架呼叫: 有些情況下,Spring框架的一些元件或者模組可能需要用到你定義的Bean。比如,如果你定義了一個@Controller,那麼在處理HTTP請求時,Spring MVC就會需要使用到這個@Controller Bean。如果這個時候Bean還沒有被建立,那麼Spring也會先建立它的範例。

假設我們有一個名為BookController的類,該類需要一個BookService物件來處理一些業務邏輯。

@Controller
public class BookController {
 @Autowired
 private BookService bookService;
 // 其他的控制器方法
}

BookService類

@Service
public class BookService {
 @Autowired
 private BookMapper bookMapper;
 // 一些業務邏輯方法
}

當Spring Boot應用程式啟動時,以下步驟將會發生:

  1. 首先,Spring框架通過@ComponentScan註解掃描類路徑,找到了BookController、BookService和BookMapper等類,併為它們建立Bean定義,註冊到Spring的應用上下文中。
  2. 當一個請求到達並需要使用到BookController時,Spring框架會嘗試建立一個BookController的Bean範例。
  3. 在建立BookController的Bean範例的過程中,Spring框架發現BookController類中需要一個BookService的Bean範例(通過@Autowired註解指定),於是Spring框架會先去建立一個BookService的Bean範例。
  4. 同樣,在建立BookService的Bean範例的過程中,Spring框架發現BookService類中需要一個BookMapper的Bean範例(通過@Autowired註解指定),於是Spring框架會先去建立一個BookMapper的Bean範例。
  5. 在所有依賴的Bean都被建立並注入之後,BookController的Bean範例最終被建立完成,可以處理來自使用者的請求了。

在這個過程中,BookController、BookService和BookMapper這三個Bean的建立順序是有嚴格要求的,必須按照他們之間的依賴關係來建立。只有當一個Bean的所有依賴都已經被建立並注入後,這個Bean才能被建立。這就是Spring框架的IoC(控制反轉)和DI(依賴注入)的機制。

  • 手動獲取: 如果你在程式碼中手動通過ApplicationContext.getBean()方法獲取某個Bean,那麼Spring也會在這個時候建立對應的Bean範例,如果還沒有建立的話。

總的來說,"需要"一個Bean,是指在執行時有其他程式碼需要使用到這個Bean的範例,這個"需要"可能來源於其他Bean的依賴,也可能來源於框架的呼叫,或者你手動獲取。在這種需要出現時,如果對應的Bean還沒有被建立,那麼Spring就會根據之前通過@ComponentScan等方式註冊的Bean定義,建立對應的Bean範例。

5.2 xml中啟用component-scan元件掃描

對應於 @ComponentScan 的 XML 設定是 <context:component-scan> 標籤

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

在這段 XML 設定中,<context:component-scan> 標籤指定了 Spring 需要掃描 com.example 包及其子包下的所有類,這與 @ComponentScan 註解的功能是一樣的。

注意:在使用 <context:component-scan> 標籤時,需要在 XML 組態檔的頂部包含 context 名稱空間和相應的 schema 位置(xsi:schemaLocation)。

5.3 不使用@ComponentScan的元件掃描

如果我們不寫@ComponentScan註解,那麼這裡可以把主程式改為如下:

@SpringBootApplication
public class DemoApplication {
 public static void main(String[] args) {
 ApplicationContext context = new AnnotationConfigApplicationContext("com.example");
 Library library = context.getBean(Library.class);
 System.out.println(library.getBook().getTitle());
 System.out.println(library.getBook().getAuthor());
 }
}

AnnotationConfigApplicationContext 的構造方法中有一個是填寫basePackages路徑的,可以接受一個或多個包的名字作為引數,然後掃描這些包及其子包。

執行結果如下:

在這個例子中,Spring 將會掃描 com.example 包及其所有子包,查詢並註冊所有的 Bean,達到和@ComponentScan註解一樣的效果。

我們也可以手動建立一個設定類來註冊bean,那麼想要執行得到一樣的效果,需要的程式碼如下:

@Component
public class Book {
 private String title = "Java Programming";
 private String author = "Unknown";
 public String getTitle() {
 return title;
 }
 public void setTitle(String title) {
 this.title = title;
 }
 public String getAuthor() {
 return author;
 }
 public void setAuthor(String author) {
 this.author = author;
 }
}
@Component
public class Library {
 private Book book;
 public Book getBook() {
 return book;
 }
 public void setBook(Book book) {
 this.book = book;
 }
}
@Configuration
public class LibraryConfiguration {
 @Bean
 public Book book() {
 Book book = new Book();
 book.setTitle("Java Programming");
 book.setAuthor("Unknown");
 return book;
 }
 @Bean
 public Library library() {
 Library library = new Library();
 library.setBook(book());
 return library;
 }
}

主程式:

@SpringBootApplication
public class DemoApplication {
 public static void main(String[] args) {
 ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfiguration.class);
 Library library = context.getBean(Library.class);
 System.out.println(library.getBook().getTitle());
 System.out.println(library.getBook().getAuthor());
 }
}

我們建立了一個設定類LibraryConfiguration,用於定義Book和Library這兩個bean。然後以設定類LibraryConfiguration.class作為輸入的來建立Spring的IOC容器(Spring應用上下文就是Spring IOC容器)。

執行結果和前面一樣。

注意,在這個例子裡,如果你寫@ComponentScan,並且SpringApplication.run(Application.class, args);作為Spring上下文,那麼這裡執行設定類需要去掉Book和Library類的@Component註解,不然會報錯A bean with that name has already been defined。這是因為如果同時在 Book 和Library 類上使用了 @Component 註解,而且設定類LibraryConfiguration上使用了@Configuration註解,這都會被 @ComponentScan 掃描到,那麼 Book 和 Library的範例將會被建立並註冊兩次。正確的做法是,要麼在設定類中通過 @Bean 註解的方法建立Book 和 Library的範例,要麼在 Book 和 Library 類上寫 @Component 註解。如果不是第三方庫,我們一般選擇後者。

為什麼要有設定類出現?所有的Bean上面使用@Component,用@ComponentScan註解掃描不就能解決了嗎?

我們在使用一些第三方庫時,需要對這些庫進行一些特定的設定。這些設定資訊,我們可能無法直接通過註解或者XML來完成,或者通過這些方式完成起來非常麻煩。而設定類可以很好地解決這個問題。通過設定類,我們可以在Java程式碼中完成任何複雜的設定邏輯。

假設你正在使用 MyBatis,在這種情況下可能需要設定一個SqlSessionFactory,在大多數情況下,我們無法(也不應該)直接修改第三方庫的程式碼,所以無法直接在SqlSessionFactory類或其他類上新增@Configuration、@Component等註解。為了能夠在Spring中使用和設定這些第三方庫,我們需要建立自己的設定類,並在其中定義@Bean方法來初始化和設定這些類的範例。這樣就可以靈活地控制這些類的範例化過程,並且可以利用Spring的依賴注入功能。

下面是一個使用@Configuration和@Bean來設定MyBatis的例子:

@Configuration
@MapperScan("com.example.demo.mapper")
public class MyBatisConfig {
 @Bean
 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
 SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(
 new PathMatchingResourcePatternResolver().getResources("classpath*:com/example/demo/mapper/*Mapper.xml")
 );
 return factoryBean.getObject();
 }
}

sqlSessionFactory方法建立一個SqlSessionFactoryBean物件,並使用DataSource(Spring Boot預設為你設定的一個Bean)進行初始化。然後,它指定MyBatis mapper XML檔案的位置,最後返回SqlSessionFactory物件。

通過這種方式,你可以靈活地設定MyBatis,並將其整合到Spring應用中。這是一種比使用XML組態檔或僅僅依賴於自動設定更為靈活和強大的方式。

6. 元件註冊的其他註解

@Controller, @Service, @Repository和@Component 一樣的效果,它們都會被 Spring IoC 容器識別,並將類範例化為 Bean。讓我們來看這些註解:

  • @Controller:這個註解通常標註在表示表現層(比如 Web 層)的類上,如Spring MVC 中的控制器。它們處理使用者的 HTTP 請求並返回響應。雖然 @Controller 與 @Component 在功能上是類似的,但 @Controller 註解的使用表示了一種語意化的分層結構,使得控制層程式碼更加清晰。
  • @Service:這個註解通常用於標註業務層的類,這些類負責處理業務邏輯。使用 @Service 註解表明該類是業務處理的核心類,使得程式碼更具有語意化。
  • @Repository:這個註解用於標記資料存取層,也就是資料存取物件或DAO層的元件。在資料庫操作的實現類上使用 @Repository 註解,這樣Spring將自動處理與資料庫相關的異常並將它們轉化為Spring的DataAccessExceptions。

在實際開發中,幾乎很少看到@Repository,而是利用 MyBatis 的 @Mapper 或 @MapperScan 實現資料存取,通常做法是,@MapperScan 註解用於掃描特定包及其子包下的介面,這些介面被稱為 Mapper 介面。Mapper 介面方法定義了 SQL 查詢語句的簽名,而具體的 SQL 查詢語句則通常在與介面同名的 XML 檔案中定義。

@MapperScan("com.example.**.mapper") 會掃描 com.example 包及其所有子包下的名為 mapper 的包,以及 mapper 包的子包。 ** 是一個萬用字元,代表任意深度的子包。

舉個例子,以下是一個 Mapper 介面的定義:

package com.example.demo.mapper;
public interface BookMapper {
 Book findBookById(int id);
}

對應的 XML 檔案(通常位於 resources 目錄下,並且與介面在相同的包路徑中)

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.BookMapper">
 <select id="findBookById" parameterType="int" resultType="com.example.demo.Book">
        SELECT title, author FROM book WHERE id = #{id}
 </select>
</mapper>

注意:在 XML 檔案中的 namespace 屬性值必須與 Mapper 介面的全限定類名相同,<select> 標籤的 id 屬性值必須與介面方法名相同。

然後,在 Spring Boot 的主類上,我們使用 @MapperScan 註解指定要掃描的包:

@SpringBootApplication
@MapperScan("com.example.**.mapper")
public class Application {
 public static void main(String[] args) {
 SpringApplication.run(Application.class, args);
 }
}

這樣,MyBatis 就會自動為 UserMapper 介面建立一個實現類(實際上是一個代理物件),並將其註冊到 Spring IOC 容器中,你就可以在你的服務類中直接注入 BookMapper 並使用它。

可能有小夥伴注意到了,這幾個註解中都有這麼一段程式碼

 @AliasFor(
        annotation = Component.class
 )
 String value() default "";

@AliasFor 是 Spring 框架的註解,它允許你在一個註解屬性上宣告別名。在 Spring 的許多核心註解中,@AliasFor 用於宣告一個或多個別名屬性。

舉個例子,在 @Controller, @Service, @Repository註解中,value() 方法上的 @AliasFor 宣告了一個別名屬性,它的目標註解是 @Component,具體的別名屬性是 value。也就是說,當我們在 @Controller, @Service, @Repository 註解上使用 value() 方法設定值時,實際上也就相當於在 @Component 註解上設定了 name 屬性的值。同時,這也表明了 @Controller, @Service, @Repository註解本身就是一個特殊的 @Component。

7. 將註解驅動的設定與XML驅動的設定結合使用

有沒有這麼一種可能,一箇舊的Spring專案,裡面有很多舊的XML設定,現在你接手了,想要全部用註解驅動,不想再寫XML設定了,那應該怎麼相容呢?

假設我們有一箇舊的Spring XML組態檔 old-config.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="oldBean" class="com.example.OldBean" />
</beans>

這個檔案定義了一個名為 「oldBean」 的bean。

然後,我們編寫一個新的註解驅動的設定類:

package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource("classpath:old-config.xml")
public class NewConfig {
 @Bean
 public NewBean newBean() {
 return new NewBean();
 }
}

在這個新的設定類中,我們使用 @ImportResource 註解來引入舊的XML組態檔,並定義了一個新的bean 「newBean」。@ImportResource("classpath:old-config.xml")告訴Spring在初始化AppConfig設定類時,去類路徑下尋找old-config.xml檔案,並載入其中的設定。

當我們啟動應用程式時,Spring會建立一個 ApplicationContext,這個 ApplicationContext 會包含 old-config.xml 檔案中定義的所有beans(例如 「oldBean」),以及 NewConfig 類中定義的所有beans(例如 「newBean」)。

package com.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
 public static void main(String[] args) {
 ApplicationContext context = new AnnotationConfigApplicationContext(NewConfig.class);
 OldBean oldBean = context.getBean("oldBean", OldBean.class);
 NewBean newBean = context.getBean("newBean", NewBean.class);
 System.out.println(oldBean);
 System.out.println(newBean);
 }
}

在以上的main方法中,我們通過使用AnnotationConfigApplicationContext並傳入NewConfig.class作為引數,初始化了一個Spring上下文。在這個上下文中,既包含了從old-config.xml匯入的bean,也包含了在NewConfig設定類中使用@Bean註解定義的bean。

所以,通過使用 @ImportResource,可以在新的註解設定中引入舊的XML設定,這樣就可以在不打斷舊的XML設定的基礎上逐步遷移至新的註解設定。

上面我們說到類路徑,什麼是類路徑?

resources目錄就是類路徑(classpath)的一部分。所以當我們說"類路徑下"的時候,實際上也包含了"resources「目錄。JVM在執行時,會把」src/main/resources"目錄下的所有檔案和資料夾都新增到類路徑中。

例如有一個XML檔案位於"src/main/resources/config/some-context.xml",那麼可以用以下方式來參照它:

@Configuration
@ImportResource("classpath:config/some-context.xml")
public class AppConfig {
 //...
}

這裡可以描述為在類路徑下的’config‘目錄中查詢’some-context.xml'檔案。

為什麼說JVM在執行時,會把"src/main/resources"目錄下的所有檔案和資料夾都新增到類路徑中?

當你編譯並執行一個Java專案時,JVM需要知道去哪裡查詢.class檔案以及其他資原始檔。這個查詢的位置就是所謂的類路徑(Classpath)。類路徑可以包含檔案系統上的目錄,也可以包含jar檔案。簡單的說,類路徑就是JVM查詢類和資源的地方。

在一個標準的Maven專案結構中,Java原始碼通常在src/main/java目錄下,而像是組態檔、圖片、靜態網頁等資原始檔則放在src/main/resources目錄下。

當你構建專案時,Maven(或者其他的構建工具,如Gradle)會把src/main/java目錄下的.java檔案編譯成.class檔案,並把它們和src/main/resources目錄下的資原始檔一起復制到專案的輸出目錄(通常是target/classes目錄)。

然後當你執行程式時,JVM會把target/classes目錄(即編譯後的src/main/java和src/main/resources)新增到類路徑中,這樣JVM就可以找到程式執行所需的類和資源了。

如果有一個名為application.properties的檔案在src/main/resources目錄下,就可以使用類路徑來存取它,就像這樣:classpath:application.properties。在這裡classpath:字首告訴JVM這個路徑是相對於類路徑的,所以它會在類路徑中查詢application.properties檔案。因為src/main/resources在執行時被新增到了類路徑,所以JVM能找到這個檔案。

8. 思考總結

8.1 為什麼我們需要註冊元件,這與Bean註冊有什麼區別?

在Spring框架中,Bean物件是由Spring IoC容器建立和管理的。通常Bean物件是應用程式中的業務邏輯元件,如資料存取物件(DAO)或其他服務類。

元件註冊,或者說在Spring中通過@Component或者其派生註解(@Service, @Controller, @Repository等)標記的類,是告訴Spring框架這個類是一個元件,Spring需要建立它的範例並管理它的生命週期。這樣當使用到這個類的時候,Spring就可以自動地建立這個類的範例並注入到需要的地方。

Bean註冊和元件註冊其實是非常類似的,都是為了讓Spring知道它需要管理哪些類的範例。區別在於Bean註冊通常發生在設定類中,使用@Bean註解來明確地定義每一個Bean,而元件註冊則是通過在類上使用@Component或者其派生註解來告訴Spring,這個類是一個元件,Spring應該自動地為其建立範例。

8.2 什麼是元件掃描,為什麼我們需要它,它是如何工作的?

元件掃描是Spring的一種機制,用於自動發現應用程式中的Spring元件,並自動地為這些元件建立Bean定義,然後將它們註冊到Spring的應用上下文中,我們可以通過使用@ComponentScan註解來啟動元件掃描。

我們需要元件掃描是因為它可以大大簡化設定過程,我們不再需要為應用程式中的每個類都顯式地建立Bean。而是通過簡單地在類上新增@Component或者其派生註解,並啟動元件掃描,就可以讓Spring自動地為我們的類建立Bean並管理它們。

元件掃描的工作過程如下:使用@ComponentScan註解並指定一個或多個包路徑時,Spring會掃描這些包路徑及其子包中的所有類。對於標記了@Component或者其派生註解的類,Spring會在應用上下文啟動時為它們建立Bean,並將這些Bean定義註冊到Spring的應用上下文中。當需要使用這些類的範例時,Spring就可以自動注入這些範例。

 

點選關注,第一時間瞭解華為雲新鮮技術~