摘要:本文旨在深入探討Spring框架的註解驅動配置與XML配置,揭示兩者之間的相似性與差異。 本文分享自華為雲社區《Spring高手之路2——深入理解註解驅動配置與XML配置的融合與區別》,作者:磚業洋__ 。 本文旨在深入探討Spring框架的註解驅動配置與XML配置,揭示兩者之間的相似性與差異 ...
摘要:本文旨在深入探討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上下文。
列印結果:
![](https://pic2.zhimg.com/80/v2-752975b1d1ce29724726ce2d27bf9d31_720w.webp)
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註解標識的類不在當前包或者子包,那麼就會報錯。
![](https://pic3.zhimg.com/80/v2-d6395386f32cce2131958302a4637f3e_720w.webp)
難道@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()); } }
![](https://pic2.zhimg.com/80/v2-97fcb488273ff44089f0ff536254d83d_720w.webp)
@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應用程式啟動時,以下步驟將會發生:
- 首先,Spring框架通過@ComponentScan註解掃描類路徑,找到了BookController、BookService和BookMapper等類,併為它們創建Bean定義,註冊到Spring的應用上下文中。
- 當一個請求到達並需要使用到BookController時,Spring框架會嘗試創建一個BookController的Bean實例。
- 在創建BookController的Bean實例的過程中,Spring框架發現BookController類中需要一個BookService的Bean實例(通過@Autowired註解指定),於是Spring框架會先去創建一個BookService的Bean實例。
- 同樣,在創建BookService的Bean實例的過程中,Spring框架發現BookService類中需要一個BookMapper的Bean實例(通過@Autowired註解指定),於是Spring框架會先去創建一個BookMapper的Bean實例。
- 在所有依賴的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路徑的,可以接受一個或多個包的名字作為參數,然後掃描這些包及其子包。
![](https://pic4.zhimg.com/80/v2-dbe43852658150d38e5491d17e3749cf_720w.webp)
運行結果如下:
![](https://pic2.zhimg.com/80/v2-6cfa6334033581da1f15646d1aa1f605_720w.webp)
在這個例子中,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 註解的使用表示了一種語義化的分層結構,使得控制層代碼更加清晰。
![](https://pic3.zhimg.com/80/v2-5543ae6e1c89af724e69e958aad98522_720w.webp)
- @Service:這個註解通常用於標註業務層的類,這些類負責處理業務邏輯。使用 @Service 註解表明該類是業務處理的核心類,使得代碼更具有語義化。
![](https://pic4.zhimg.com/80/v2-c66bfdae1cbfe2310144fc391732cf3f_720w.webp)
- @Repository:這個註解用於標記數據訪問層,也就是數據訪問對象或DAO層的組件。在資料庫操作的實現類上使用 @Repository 註解,這樣Spring將自動處理與資料庫相關的異常並將它們轉化為Spring的DataAccessExceptions。
![](https://pic1.zhimg.com/80/v2-2d91dd23fa2df48f28be37fc4ab218bc_720w.webp)
在實際開發中,幾乎很少看到@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目錄)。
![](https://pic4.zhimg.com/80/v2-3e55c1accf05bb50f1b3e73226aecddf_720w.webp)
![](https://pic3.zhimg.com/80/v2-a3adca3e4dced383803506ddb4ed91e2_720w.webp)
然後當你運行程式時,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在