摘要:本文旨在深入探討Spring框架的註解驅動設定與XML設定,揭示兩者之間的相似性與差異。
本文分享自華為雲社群《Spring高手之路2——深入理解註解驅動設定與XML設定的融合與區別》,作者:磚業洋__ 。
本文旨在深入探討Spring框架的註解驅動設定與XML設定,揭示兩者之間的相似性與差異。我們首先介紹了設定類的編寫與Bean的註冊,然後比較了註解驅動的IOC依賴注入與XML依賴注入。文章進一步解析了Spring的元件註冊與元件掃描,包括使用@ComponentScan和XML啟用component-scan的情況,以及不使用@ComponentScan的場景。接下來,我們深入探討了其他相關的元件
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。這三個註解的作用是:
在非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框架提供的,所以,原理性的東西還是需要知道。
我們就以上面的例子來說,假設設定類註冊了兩個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物件中。
在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註解會掃描並註冊的"元件"包括:
這些元件最終都會在Spring的應用上下文中以Bean的形式存在。
這裡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 )
如果我們只寫了@Component, @Configuration 這樣的註解,IOC容器是找不到這些元件的。
忽略掉之前的例子,在這裡我們需要執行的程式碼如下:
@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"通常體現在以下幾個方面:
@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容器的一個重要特性,也是為什麼它能夠使我們的程式碼更加簡潔和易於維護的原因。
假設我們有一個名為BookController的類,該類需要一個BookService物件來處理一些業務邏輯。
@Controller public class BookController { @Autowired private BookService bookService; // 其他的控制器方法 }
BookService類
@Service public class BookService { @Autowired private BookMapper bookMapper; // 一些業務邏輯方法 }
當Spring Boot應用程式啟動時,以下步驟將會發生:
在這個過程中,BookController、BookService和BookMapper這三個Bean的建立順序是有嚴格要求的,必須按照他們之間的依賴關係來建立。只有當一個Bean的所有依賴都已經被建立並注入後,這個Bean才能被建立。這就是Spring框架的IoC(控制反轉)和DI(依賴注入)的機制。
總的來說,"需要"一個Bean,是指在執行時有其他程式碼需要使用到這個Bean的範例,這個"需要"可能來源於其他Bean的依賴,也可能來源於框架的呼叫,或者你手動獲取。在這種需要出現時,如果對應的Bean還沒有被建立,那麼Spring就會根據之前通過@ComponentScan等方式註冊的Bean定義,建立對應的Bean範例。
對應於 @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)。
如果我們不寫@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組態檔或僅僅依賴於自動設定更為靈活和強大的方式。
@Controller, @Service, @Repository和@Component 一樣的效果,它們都會被 Spring IoC 容器識別,並將類範例化為 Bean。讓我們來看這些註解:
在實際開發中,幾乎很少看到@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。
有沒有這麼一種可能,一箇舊的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能找到這個檔案。
在Spring框架中,Bean物件是由Spring IoC容器建立和管理的。通常Bean物件是應用程式中的業務邏輯元件,如資料存取物件(DAO)或其他服務類。
元件註冊,或者說在Spring中通過@Component或者其派生註解(@Service, @Controller, @Repository等)標記的類,是告訴Spring框架這個類是一個元件,Spring需要建立它的範例並管理它的生命週期。這樣當使用到這個類的時候,Spring就可以自動地建立這個類的範例並注入到需要的地方。
Bean註冊和元件註冊其實是非常類似的,都是為了讓Spring知道它需要管理哪些類的範例。區別在於Bean註冊通常發生在設定類中,使用@Bean註解來明確地定義每一個Bean,而元件註冊則是通過在類上使用@Component或者其派生註解來告訴Spring,這個類是一個元件,Spring應該自動地為其建立範例。
元件掃描是Spring的一種機制,用於自動發現應用程式中的Spring元件,並自動地為這些元件建立Bean定義,然後將它們註冊到Spring的應用上下文中,我們可以通過使用@ComponentScan註解來啟動元件掃描。
我們需要元件掃描是因為它可以大大簡化設定過程,我們不再需要為應用程式中的每個類都顯式地建立Bean。而是通過簡單地在類上新增@Component或者其派生註解,並啟動元件掃描,就可以讓Spring自動地為我們的類建立Bean並管理它們。
元件掃描的工作過程如下:使用@ComponentScan註解並指定一個或多個包路徑時,Spring會掃描這些包路徑及其子包中的所有類。對於標記了@Component或者其派生註解的類,Spring會在應用上下文啟動時為它們建立Bean,並將這些Bean定義註冊到Spring的應用上下文中。當需要使用這些類的範例時,Spring就可以自動注入這些範例。