每一個Java開發人員都應該知道的4個Spring註解

這是每一個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的官方文檔以獲取更多詳細信息。安全

 

1. @Component

從本質上講,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

@Service(顧名思義)表示Bean是服務。 根據官方的@Service註解文檔:函數

[@Service批註]指示帶註解的類是「服務」,最初由Domain-Driven DesignEvans2003)定義爲「做爲接口提供的操做,在模型中獨立存在,沒有封裝狀態」。

可能還代表某個類是「業務服務門面」(就核心J2EE模式而言)或相似的東西。測試

一般,企業應用程序中服務的概念含糊不清,可是在Spring應用程序的上下文中,服務是提供與域邏輯或外部組件交互的方法而無需保持更改服務總體行爲的狀態的任何類。例如,服務能夠表明應用程序來從數據庫獲取文檔或從外部REST API獲取數據。

1 @Service
2 public class FooService {}

 

儘管沒有關於服務狀態的明確規則,可是服務一般不像域對象那樣包含狀態。例如,與將名稱,地址和社會安全號碼視爲域對象的狀態的方式相同,不會將REST客戶端,緩存或鏈接池視爲服務的狀態。實際上,因爲服務的所有定義,@Service@Component一般能夠互換使用。

 

@Repository

 @Service是用於更多通用目的的,而@Repository註解@Component註解的一種特殊化,它是爲與數據源(例如數據庫和數據訪問對象(DAOs))進行交互的組件而設計的。

1 @Repository
2 public class FooRepository {}

 

根據官方的@Repository文檔:

指示帶註解的類是「存儲庫」,最初由Domain-Driven DesignEvans2003)定義爲「一種封裝存儲,檢索和搜索行爲的機制,該機制模仿對象的集合」。

實現諸如「數據訪問對象」之類的傳統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文檔。

 

@Controller

@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服務實現。

 

@ComponentScan

如在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 }

 

2. @Autowired

對於任何DI框架,第二個相當重要的問題是:建立bean時必須知足哪些依賴關係?爲了通知Spring框架咱們指望將哪些字段或構造函數參數與依賴項一塊兒注入或鏈接,Spring提供了@Autowiredannotation。此註解一般適用於字段或構造函數——儘管也能夠將其應用於設置方法(這種用法不太常見)。

當應用於字段時,即便沒有設置器,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  

 

@Qualifier

在某些狀況下,可能有多個候選關係。這給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不知道是否要注入HibernateDooDaoSqlFooDao,所以會拋出致命的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 }

 

3. @Configuration

因爲Spring框架的巨大規模-處理從DI到MVC到事務管理的全部內容,所以須要開發人員提供的配置級別。例如,若是咱們但願定義一組可用於自動裝配的Bean(例如上面看到的PersistenceExceptionTranslationPostProcessor Bean),則必須告知Spring一些配置機制。Spring經過適當命名的@Configuration註解提供了這種機制。當將此註解應用於類時,Spring將該類視爲包含可用於參數化框架的配置信息的類。根據官方的Spring @Configuration文檔:

指示一個類聲明瞭一個或多個@Bean方法,而且能夠由Spring容器進行處理以在運行時爲這些bean生成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.。

 

4. @RequestMapping

@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參數——而且這些動詞一般最終以GETPOSTPUT, 和 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 }

 

@PathVariable

在某些狀況下,可能會在路徑中提供路徑變量,這是正確處理請求所必需的。若要獲取此路徑變量的值,能夠向使用@RequestMapping註解的方法提供參數,而且能夠將@PathVariable註解應用於此參數。例如,若是須要實體的ID來刪除它,則能夠將該ID做爲路徑變量提供,例如對/foo/1DELETE請求。爲了捕獲提供給負責處理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註解捕獲查詢參數。@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)。

 

@RequestBody

在調用中提供請求正文的狀況下(一般經過建立或更新條目的POSTPUT調用完成),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註解以及它們在實踐中的應用中受益不淺。

相關文章
相關標籤/搜索