這是每一個Java開發人員都應該知道的最重要的Spring註解。感謝優銳課老師對本文提供的一些幫助。sql
隨着愈來愈多的功能被打包到單個應用程序或一組應用程序中,現代應用程序的複雜性從未中止增加。儘管這種增加帶來了一些驚人的好處,例如豐富的功能和使人印象深入的多功能性,但它要求開發人員使用愈來愈多的範例和庫。爲了減小開發人員的工做量以及開發人員必須記住的信息量,許多Java框架都轉向了註解。數據庫
特別是Spring,它以註解的使用而聞名,它使開發人員僅用少數幾個註解就能夠建立完整的表示狀態轉移(REST)應用程序編程接口(APIs)。這些註解減小了執行基本功能所需的樣板代碼量,但也能夠掩蓋幕後發生的事情。例如,對字段應用依賴項注入(DI)註釋如何致使在運行時注入特定的bean?或者,REST批註如何知道綁定到哪一個URL路徑?編程
儘管這些問題彷佛是特定於Spring的(這引出了爲何非Spring開發人員須要知道對他們的答案的問題),但它們的影響深遠,使人耳目一新。根據Baeldung進行的2018年調查,有90.5%的參與者使用的是Spring。此外,根據2019年Stackoverflow開發人員調查,接受調查的全部開發人員中有16.2%使用Spring,有65.6%的人表示他們喜歡Spring。Spring的廣泛存在乎味着即便使用其餘框架或根本不須要任何企業框架的Java開發人員也可能會遇到Spring代碼。即便是將知識僅限於Spring註解的一小部分的Spring開發人員,也會從他們的視野中受益。緩存
在本文中,咱們將深刻探討Spring中可用的四個最相關的註解,特別注意註解背後的概念以及如何在較大的應用程序上下文中正確應用註解。儘管咱們將詳細介紹這些註解及其相關注解,可是有關Spring註解的大量信息使人st目結舌,所以沒法在本篇文章中找到。有興趣的讀者應查閱Spring的官方文檔以獲取更多詳細信息。安全
從本質上講,Spring是一個DI框架。本質上,DI框架負責以Java Bean形式將依賴項注入其餘Bean中。這種範例與大多數基本應用程序相反,後者直接實例化其依賴關係。可是,在DI中,將使用間接級別建立bean,並指望DI框架爲其注入依賴項。例如,一個設計良好的bean將具備一個帶有依賴項參數的構造函數——並容許DI框架傳入一個知足該依賴關係的對象,而不是直接在構造函數中實例化該依賴關係。這種逆轉稱爲控制反轉(IoC),而且是許多各類Spring庫所基於的基礎:app
1 public class Bar {} 2 // The non-DI way 3 public class Foo { 4 private final Bar bar; 5 public Foo() { 6 this.bar = new Bar(); 7 } 8 } 9 // The DI way 10 public class Foo { 11 private final Bar bar; 12 public Foo(Bar bar) { 13 this.bar = bar; 14 } 15 }
DI框架要回答的最關鍵的問題之一是:哪些bean能夠注入其餘bean中?爲了回答這個問題,Spring提供了@Component註解
。 將該註釋應用於類將通知Spring該類是一個組件,而且能夠實例化該類的對象並將其注入到另外一個組件中。@Component
接口經過如下方式應用於類:框架
1 @Component 2 public class FooComponent {}
儘管@Component註解
足以通知Spring Bean的可注入性;Spring還提供了專門的註解,可用於建立具備更有意義的上下文信息的組件。ide
@Service
(顧名思義)表示Bean是服務。 根據官方的@Service註解文檔:函數
[@Service
批註]指示帶註解的類是「服務」,最初由Domain-Driven Design(Evans,2003)定義爲「做爲接口提供的操做,在模型中獨立存在,沒有封裝狀態」。
可能還代表某個類是「業務服務門面」(就核心J2EE模式而言)或相似的東西。測試
一般,企業應用程序中服務的概念含糊不清,可是在Spring應用程序的上下文中,服務是提供與域邏輯或外部組件交互的方法而無需保持更改服務總體行爲的狀態的任何類。例如,服務能夠表明應用程序來從數據庫獲取文檔或從外部REST API獲取數據。
1 @Service 2 public class FooService {}
儘管沒有關於服務狀態的明確規則,可是服務一般不像域對象那樣包含狀態。例如,與將名稱,地址和社會安全號碼視爲域對象的狀態的方式相同,不會將REST客戶端,緩存或鏈接池視爲服務的狀態。實際上,因爲服務的所有定義,@Service
和@Component
一般能夠互換使用。
@Service
是用於更多通用目的的,而@Repository註解
是@Component註解
的一種特殊化,它是爲與數據源(例如數據庫和數據訪問對象(DAOs))進行交互的組件而設計的。
1 @Repository 2 public class FooRepository {}
根據官方的@Repository
文檔:
指示帶註解的類是「存儲庫」,最初由Domain-Driven Design(Evans,2003)定義爲「一種封裝存儲,檢索和搜索行爲的機制,該機制模仿對象的集合」。
實現諸如「數據訪問對象」之類的傳統Java EE模式的團隊也能夠將這種構造型應用於DAO類,儘管在這樣作以前應注意理解數據訪問對象和DDD樣式存儲庫之間的區別。此註解是通用的刻板印象,各個團隊能夠縮小其語義並適當使用。
除了將特定的類標記爲處理數據源的組件以外,Spring框架還將對@Repository註解
的bean進行特殊的異常處理。 爲了維護一致的數據接口,Spring能夠將本機存儲庫引起的異常(例如SQL或Hibernate實現)轉換爲能夠統一處理的常規異常。 爲了包括用@Repository註解
的類的異常翻譯,咱們實例化了PersistenceExceptionTranslationPostProcessor類型的bean(咱們將在後面的部分中看到如何使用@Configuration
和@Bean註解
):
1 @Configuration 2 public class FooConfiguration { 3 @Bean 4 public PersistenceExceptionTranslationPostProcessor exceptionTranslator() { 5 return new PersistenceExceptionTranslationPostProcessor() 6 } 7 }
包括該bean將通知Spring尋找PersistenceExceptionTranslator的全部實現,並在可能的狀況下使用這些實現將本機RuntimeException
轉換爲DataAccessExceptions
。有關使用@Repository註解
進行異常轉換的更多信息,請參見官方的Spring Data Access文檔。
@Component註解
的最後一個專業化能夠說是三人組中最經常使用的。Spring Model-View-Controller(MVC)是Spring Framework最受歡迎的部分之一,它使開發人員可使用@Controller註解輕鬆建立REST API。該註解在應用於類時,指示Spring框架將該類視爲應用程序的Web界面的一部分。
經過將@RequestMapping註解
應用於該類的方法來在此類中建立端點——其中@RequestMapping註解
的值是路徑(相對於API端點綁定到的控制器的根路徑),而且 method是終結點綁定到的超文本傳輸協議(HTTP)方法。例如:
1 @Controller 2 public class FooController { 3 @RequestMapping(value = "/foo", method = RequestMethod.GET) 4 public List<Foo> findAllFoos() { 5 // ... return all foos in the application ... 6 } 7 }
這將建立一個端點,該端點在/foo
路徑上偵聽GET
請求,並將全部Foo
對象的列表(默認狀況下表示爲JavaScript Object Notation(JSON)列表)返回給調用方。例如,若是Web應用程序在https://localhost
上啓動,則端點將綁定到https://localhost/foo
。咱們將在下面更詳細地介紹@RequestMapping註解
,可是就目前而言,足以知道 @Controller註解
是Spring框架的重要組成部分,而且它指示Spring框架建立大型而複雜的Web服務實現。
如在Java中建立註解中所述,註解自己不會執行任何邏輯。相反,註解只是標記,它們表示有關構造的某些信息,例如類,方法或字段。爲了使註釋有用,必須對其進行處理。對於@Component註解
及其專業化,Spring不知道在哪裏能夠找到全部使用@Component註解
的類。
爲此,咱們必須指示Spring應該掃描類路徑上的哪些包。在掃描過程當中,Spring DI Framework處理提供的包中的每一個類,並記錄全部用@Component
或@Component
特化註解的類。掃描過程完成後,DI框架就會知道哪些類適合進行注入。
爲了指示Spring掃描哪些軟件包,咱們使用@ComponentScan註解
:
1 @Configuration 2 @ComponentScan 3 public class FooConfiguration { 4 // ... 5 }
在後面的部分中,咱們將深刻研究@Configuration註解
,但就目前而言,足以知道@Configuration註解
指示Spring批註的類提供了可供DI框架使用的配置信息。默認狀況下(若是沒有爲@ComponentScan註解
提供任何參數)將掃描包含配置的包及其全部子包。要指定一個包或一組包,請使用basePackages
字段:
1 @Configuration 2 @ComponentScan(basePackages = "com.example.foo") 3 public class FooConfiguration { 4 // ... 5 }
在上面的示例中,Spring將掃描com.example.foo
軟件包及其全部子軟件包中的合格組件。若是僅提供一個基本軟件包,則@ComponentScan註解
能夠簡化爲@ComponentScan("com.example.foo")
。若是須要多個基本軟件包,則能夠爲basePackages
字段分配一組字符串:
1 @Configuration 2 @ComponentScan(basePackages = {"com.example.foo", "com.example.otherfoo"}) 3 public class FooConfiguration { 4 // ... 5 }
對於任何DI框架,第二個相當重要的問題是:建立bean時必須知足哪些依賴關係?爲了通知Spring框架咱們指望將哪些字段或構造函數參數與依賴項一塊兒注入或鏈接,Spring提供了@Autowired
annotation。此註解一般適用於字段或構造函數——儘管也能夠將其應用於設置方法(這種用法不太常見)。
當應用於字段時,即便沒有設置器,Spring也會在建立時將符合條件的依賴項直接注入到字段中:
1 @Component 2 public class FooComponent { 3 @Autowired 4 private Bar bar; 5 }
這是將依賴項注入組件的便捷方法,可是在測試類時確實會產生問題。例如,若是咱們要編寫一個執行FooComponent
類的測試夾具,而沒有在夾具中包括Spring測試框架,那麼咱們將沒法在bar
字段中注入模擬Bar
值(而無需執行繁瑣的反射)。咱們能夠將@Autowired註解
添加到接受Bar
參數並將其分配給bar
字段的構造函數中:
1 @Component 2 public class FooComponent { 3 private final Bar bar; 4 @Autowired 5 public Foo(Bar bar) { 6 this.bar = bar; 7 } 8 }
這仍然使咱們可使用模擬Bar
實現直接實例化FooComponent
類的對象,而不會給Spring測試配置增長負擔。例如,如下將是有效的JUnit測試用例(使用Mockito進行模擬):
1 public class FooTest { 2 @Test 3 public void exerciseSomeFunctionalityOfFoo() { 4 Bar mockBar = Mockito.mock(Bar.class); 5 FooComponent foo = new FooComponent(mockBar); 6 // ... exercise the FooComponent object ... 7 }
使用@Autowired註解
構造函數還容許咱們在將注入的Bar
bean分配給bar
字段以前對其進行訪問和操做。 例如,若是咱們要確保注入的Bar
Bean永遠不會爲null
,則能夠在將提供的Bar Bean分配給bar
字段以前執行此檢查:
1 @Component 2 public class FooComponent { 3 private final Bar bar; 4 @Autowired 5 public FooComponent(Bar bar) { 6 this.bar = Objects.requireNonNull(bar); 7 } 8 } 9
在某些狀況下,可能有多個候選關係。這給Spring帶來了一個問題,由於它必須在建立組件時決定要注入哪一個特定的bean,不然,若是沒法肯定單個候選對象,它將失敗。例如,如下代碼將引起 NoUniqueBeanDefinitionException
:
1 public interface FooDao { 2 public List<Foo> findAll(); 3 } 4 @Repository 5 public class HibernateFooDao implements FooDao { 6 @Override 7 public List<Foo> findAll() { 8 // ... find all using Hibernate ... 9 } 10 } 11 @Repository 12 public class SqlFooDao implements FooDao { 13 @Override 14 public List<Foo> findAll() { 15 // ... find all using SQL ... 16 } 17 } 18 @Controller 19 public class FooController { 20 private final FooDao dao; 21 @Autowired 22 public FooController(FooDao dao) { 23 this.dao = dao; 24 } 25 }
Spring不知道是否要注入HibernateDooDao
或SqlFooDao
,所以會拋出致命的NoUniqueBeanDefinitionException
。爲了幫助Spring解決選擇哪一個bean,咱們可使用@Qualifier註解
。經過爲@Qualifier註解
提供與@Component註解
(或其任何專業化)提供的名稱相匹配的鍵,以及@Autowired註解
,咱們能夠縮小合格的注入候選對象的範圍。例如,在如下代碼段中,將HibernateFooDao
注入到FooController
中,而且不會引起NoUniqueBeanDefinitionException
:
1 public interface FooDao { 2 public List<Foo> findAll(); 3 } 4 @Repository("hibernateDao") 5 public class HibernateFooDao implements FooDao { 6 @Override 7 public List<Foo> findAll() { 8 // ... find all using Hibernate ... 9 } 10 } 11 @Repository("sqlDao") 12 public class SqlFooDao implements FooDao { 13 @Override 14 public List<Foo> findAll() { 15 // ... find all using SQL ... 16 } 17 } 18 @Controller 19 public class FooController { 20 private final FooDao dao; 21 @Autowired 22 @Qualifier("hibernateDao") 23 public FooController(FooDao dao) { 24 this.dao = dao; 25 } 26 }
因爲Spring框架的巨大規模-處理從DI到MVC到事務管理的全部內容,所以須要開發人員提供的配置級別。例如,若是咱們但願定義一組可用於自動裝配的Bean(例如上面看到的PersistenceExceptionTranslationPostProcessor Bean),則必須告知Spring一些配置機制。Spring經過適當命名的@Configuration註解
提供了這種機制。當將此註解應用於類時,Spring將該類視爲包含可用於參數化框架的配置信息的類。根據官方的Spring @Configuration
文檔:
指示一個類聲明瞭一個或多個@Bean
方法,而且能夠由Spring容器進行處理以在運行時爲這些bean生成bean定義和服務請求,例如:
正如咱們在上面看到的,咱們能夠手動建立Spring將包含的新bean做爲注入的候選對象,而無需註解類自己。當咱們沒法訪問該類的源代碼或者該類存在於不屬於組件掃描過程的軟件包中時,可能就是這種狀況。在上面的@Qualifier
示例中,咱們也能夠放棄@Repository
annotations並在帶有@Configuration
註釋的類中使用@Bean註解
,以指示Spring在須要FooDao
時使用HibernateFooDao
:
1 public interface FooDao { 2 public List<Foo> findAll(); 3 } 4 public class HibernateFooDao implements FooDao { 5 @Override 6 public List<Foo> findAll() { 7 // ... find all using Hibernate ... 8 } 9 } 10 public class SqlFooDao implements FooDao { 11 @Override 12 public List<Foo> findAll() { 13 // ... find all using SQL ... 14 } 15 } 16 @Configuration 17 public class FooConfiguration { 18 @Bean 19 public FooDao fooDao() { 20 return new HibernateFooDao(); 21 } 22 }
使用此配置,Spring如今將具備在請求FooDao
時實例化HibernateDooDao
所需的邏輯。本質上,咱們建立了一個Factory方法,框架能夠在須要時使用該方法來實例化FooDao
的實例。若是在建立bean時排除了@Autowired
參數,咱們能夠經過向使用@Bean註解
的方法中添加參數來反對這種依賴性。若是咱們用@Component
或@Component
的任何特化來註解組件,Spring會在建立組件時知道注入依賴項,可是因爲咱們是在Spring Framework外部直接調用構造函數,所以必須提供依賴項。例如:
1 @Component 2 public class Bar {} 3 public class FooComponent { 4 private final Bar bar; 5 @Autowired 6 public FooComponent(Bar bar) { 7 this.bar = bar; 8 } 9 } 10 @Configuration 11 public class FooConfiguration { 12 @Bean 13 public FooComponent fooComponent(Bar bar) { 14 return new FooComponent(bar); 15 } 16 }
Spring尋找知足fooComponent
方法參數的已註冊候選者,當找到一個候選者時,它將被傳入並最終傳遞給FooComponent
構造函數。請注意,任何使用@Component註解
或任何特殊化註解的bean或使用其餘@Bean
method建立的bean均可以注入@Bean
方法參數中。例如:
1 public class Bar {} 2 public class FooComponent { 3 private final Bar bar; 4 @Autowired 5 public FooComponent(Bar bar) { 6 this.bar = bar; 7 } 8 } 9 @Configuration 10 public class FooConfiguration { 11 @Bean 12 public Bar bar() { 13 return new Bar(); 14 } 15 @Bean 16 public FooComponent fooComponent(Bar bar) { 17 return new FooComponent(bar); 18 } 19 }
請注意,使用@Bean註解方法的慣例與@Bean
相同,首字母小寫。例如,若是咱們要建立一個FooComponent
,則用於建立bean(並用@Bean註解
)的方法一般稱爲fooComponent
.。
@Controller註解
的大部分功能都來自@RequestMapping註解
,該註解指示Spring建立一個映射到帶註解方法的Web終結點。建立Web API時,框架須要知道如何處理對特定路徑的請求。例如,若是對https://localhost/foo
進行了HTTP GET
調用,Spring須要知道如何處理該請求。此綁定(或映射)過程是@RequestMapping註解
的權限,該註解通知Spring應該將特定的HTTP動詞和路徑映射到特定的方法。例如,在上一節中,咱們看到咱們能夠指示Spring使用如下代碼段將HTTP GET
映射到/ foo
:
1 @Controller 2 public class FooController { 3 @RequestMapping(value = "/foo", method = RequestMethod.GET) 4 public List<Foo> findAll() { 5 // ... return all foos in the application ... 6 } 7 }
請注意,能夠將多個HTTP動詞提供給method參數,但這在實踐中是異常的。 因爲幾乎老是將單個HTTP動詞提供給method參數——而且這些動詞一般最終以GET
, POST
, PUT
, 和 DELETE
結尾,所以Spring還包括四個附加註解,可用於簡化@RequestMapping
方法的建立:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
若是須要根路徑(即與控制器路徑匹配的路徑),則不須要value參數。@RequestMapping註解
也能夠應用於控制器自己,該控制器設置整個控制器的根路徑。例如,如下控制器在/foo
路徑中建立一個GET
端點,在/foo/bar
中建立另外一個POST
端點:
1 @Controller 2 @RequestMapping("/foo") 3 public class FooController { 4 @GetMapping 5 public List<Foo> findAll() { 6 // ... return all foos in the application ... 7 } 8 @PostMapping("/bar") 9 public void doSomething() { 10 // ... do something ... 11 } 12 }
在某些狀況下,可能會在路徑中提供路徑變量,這是正確處理請求所必需的。若要獲取此路徑變量的值,能夠向使用@RequestMapping註解
的方法提供參數,而且能夠將@PathVariable註解
應用於此參數。例如,若是須要實體的ID來刪除它,則能夠將該ID做爲路徑變量提供,例如對/foo/1
的DELETE
請求。爲了捕獲提供給負責處理DELETE
請求的方法的1
,咱們捕獲路徑變量,方法是用大括號將變量名括起來,併爲處理程序方法的參數應用@PathVariable註解
,其中將值提供給@PathVariable
匹配路徑中捕獲的變量的名稱:
1 @Controller 2 public class FooController { 3 @DeleteMapping("/foo/{id}") 4 public void deleteById(@PathVariable("id") String id) { 5 // ... delete Foo with ID "id" ... 6 } 7 }
默認狀況下,假定@PathVariable
的名稱與帶註解的參數的名稱匹配,所以,若是參數的名稱與路徑中捕獲的變量的名稱徹底匹配,則無需爲@PathVariable註解
提供任何值:
1 @Controller 2 public class FooController { 3 @DeleteMapping("/foo/{id}") 4 public void deleteById(@PathVariable String id) { 5 // ... delete Foo with ID "id" ... 6 } 7 }
Spring將嘗試將捕獲的路徑變量強制轉換爲以@PathVariable註解
的參數的數據類型。例如,若是咱們將ID path變量的值除爲整數,則能夠將id參數的數據類型更改成int
:
1 @Controller 2 public class FooController { 3 @DeleteMapping("/foo/{id}") 4 public void deleteById(@PathVariable int id) { 5 // ... delete Foo with ID "id" ... 6 } 7 }
若是在路徑中提供了諸如字符串baz
之類的值(即/foo/baz
),則會發生錯誤。
除了捕獲路徑變量以外,咱們還可使用@RequestParam註解
捕獲查詢參數。@RequestParam
以與@PathVariable註解
相同的方式將參數裝飾處處理程序方法,可是提供給@RequestParam
annotation的值與查詢參數的鍵匹配。例如,若是咱們但願對/foo?limit=100
的路徑進行HTTP GET
調用,則能夠建立如下控制器來捕獲限制值:
1 @Controller 2 public class FooController { 3 @GetMapping("/foo") 4 public List<Foo> findAll(@QueryParam("limit") int limit) { 5 // ... return all Foo objects up to supplied limit ... 6 } 7 }
與@PathVariable
同樣,能夠省略提供給@RequestParam註解
的值,而且默認狀況下將使用參數的名稱。一樣,若是可能的話,Spring將把捕獲的查詢參數的值強制轉換爲參數的類型(在上述狀況下爲int
)。
在調用中提供請求正文的狀況下(一般經過建立或更新條目的POST
或PUT
調用完成),Spring提供了@RequestBody註解
。與前兩個註解同樣,@RequestBody註解
應用於處理程序方法的參數。 而後,Spring會將提供的請求主體反序列化爲參數的類型。例如,咱們可使用具備相似於如下內容的請求主體的HTTP調用建立新的Foo
:
1 {"name": "some foo", "anotherAttribute": "bar"}
而後,咱們能夠建立一個包含與指望的請求主體匹配的字段的類,並建立一個捕獲該請求主體的處理程序方法:
1 public class FooRequest { 2 private String name; 3 private String anotherAttribute; 4 public void setName(String name) { 5 this.name = name; 6 } 7 public String getName() { 8 return name; 9 } 10 public void setAnotherAttribute(String anotherAttribute) { 11 this.anotherAttribute = anotherAttribute; 12 } 13 public String getAnotherAttribute() { 14 return anotherAttribute; 15 } 16 } 17 @Controller 18 public class FooController { 19 @PostMapping("/foo") 20 public void create(@RequestBody FooRequest request) { 21 // ... create a new Foo object using the request body ... 22 } 23 }
儘管有許多Java框架,但Spring倒是無處不在的,它是最廣泛的一種。 從REST API到DI,Spring包括豐富的功能集,這些功能使開發人員無需編寫大量樣板代碼便可建立複雜的應用程序。 Spring提供的一種機制是註解,它使開發人員能夠修飾類和方法,併爲它們提供上下文信息,Spring框架可使用這些信息來表明咱們建立組件和服務。因爲Spring的廣泛性,每一個Java開發人員均可以從理解這些Spring註解以及它們在實踐中的應用中受益不淺。