在SpringBoot
開發中,@Controller
和@Service
基本上是日常開發中使用的最頻繁的兩個註解。但你有沒考慮過@Service
代替@Controller
註解來標註到控制層
的場景?換言之,經過@Service
標註的控制層能否實現將使用者請求分發到服務層
的功能?
前言
在SpringBoot
開發中,@Controller
註解用於標識一個控制器類,該類負責處理Web
請求。而控制器類通常包含若干個方法,每個方法對應一個HTTP
請求的處理邏輯。而控制器是MVC(Model-View-Controller)
架構的一部分,其主要負責將使用者請求分發到適當的服務層,並返回檢視或響應資料。而@Service
註解用於標識一個服務類,用以負責處理業務邏輯和與資料訪問層互動。
相信對於大多數Java
開發者來說@Controller
和@Service
註解的使用都不算太難。但進一步,用@Service
標註控制層能否達到和@Controller
註解相同的功能呢? 對於這個操作你可能會覺得很瘋狂,並下意識的說出不可能。但事實果真如此嗎?我們不妨先透過一個簡單的例子來驗證一下。
@Sercice
代替@Controller
我們首先自定義一個ServiceController
的控制層,其內部透過Autowired
註解注入一個UserMapper
,並透過userMapper
來實現控制層與資料層的互動。具體程式碼如下:
ServiceController
@Service @RequestMapping("/ts") public class ServiceController { @Autowired private UserMapper userMapper; @GetMapping("get-services") @ResponseBody public User getServices() { User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class) .eq(User::getUsername, "zhangSan")); return user; } }
然後,透過PostMan
傳送一個Get
請求,以請求 http://localhost:8080/ts/get-services
其返回內容如下 :
Date: Sun, 21 Jul 2024 02:37:39 GMT Keep-Alive: timeout=60 Connection: keep-alive { "username": "zhangSan", "id": 1, "type": null, "remark": "test1" }
透過返回內容,不難看出我們的請求順利到ServiceController
的getServices
方法。也就是說我們完全可以用@Service
來替代@Controller
標註在控制層上!
揭秘背後原理
你可能會覺得@Service
來替代@Controller
這樣的操作有的反常規,因為在學習SpringBoot
時,從來也沒有那個教程告訴我們@Service
註解還有這樣的騷操作
。那@Service
可以這樣使用的背後原因到底是什麼呢?
衆所周知,@Service
和@Controller
註解都能被Spring
容器所載入,並注入到Spring
容器中。我們以SpringBoot
應用為例來分析其注入容器的全過程。
在分析之前我們首先明確一點,對於Spring
而言其會根據配置(如 XML 檔案或 @ComponentScan
註解)掃描指定的包及其子包,查詢標記有 @Controller
、@Service
、@Repository
和 @Component
的類。 但對於Springboot
應用而言,其並沒有顯示的使用XmL
配置或@ComponentScan
指定掃描路徑的方式來載入對應路徑下的Bean
資訊。
這背後的原因主要在於@SpringBootApplication
註解的使用,@SpringBootApplication
其實是一個組合註解,其內部包括以下三個註解:
@EnableAutoConfiguration
: 啟用Spring Boot
的自動配置機制。@ComponentScan
: 啟用元件掃描,以便自動發現並註冊Spring
元件。@Configuration
: 表示這是一個Spring
配置類。
在預設情況下,Spring Boot
會從主應用類所在的包開始進行元件掃描,這意味著只要 @Controller
和 @Service
註解的類位於主應用類所在包及其子包中,它們就會被自動發現並註冊到 Spring
容器中。
明白了SpringBoot
對於Bean
的載入邏輯後,我們再來深入到其內部來看。SpringBoot
對於這部分Bean
的載入流程如圖所示:
當我們在main
方法中執行SpringApplication.run
時,其在run
方法內容會完成Spring
容器的建立,以及Bean
的載入。具體來看,其在AbstractApplicationContext
中的invokeBeanPostBeanFactoryPostProcessore
時,會透過 ConfigurationClassPostProcessor
的 postProcessBeanDefinitionRegistry
方法載入路徑下所有的bean
名稱資訊,然後在finshBeanFactoryInitialization
完成bean
的例項化。而我們繞了這麼一大圈就是皆在說明,被@Service
,@Controller
所標註的類在SpringBoot
框架中是如何一步步被注入到容器的。
進一步,對於控制層
內的方法,其會在AbstractHandlerMethodMapping
中的 afterPropertiesSet()
完成請求url
與方法的對映繫結。更進一步,afterPropertiesSet
的處理邏輯全部委託于于 getCandidateBeanNames
和processCandidateBean
兩個方法。
而getCandidateBeanNames
會獲取當前容器中所有的bean
的名稱集合,並篩選類中標有@Controller
或者 @RequestMapping
註解的類交給processCandidateBean
處理,以完成請求url
與方法的對映。
此時,我們不妨來回看我們一開始的ServiceController
的樣例程式碼:
@Service @RequestMapping("/ts") public class ServiceController { // ....省略內部細節資訊 }
不難發現,其雖然沒使用@Controller
修飾,但其卻被@RequestMapping
註解資訊,也就是說其能被processCandidateBean
所處理,進而其也就能完成url
和方法對映關係的維護。更進一步,當請求至DispatcherServlet
時,SpringMVC
變更透過其url
資訊,找到能處理相應請求的HandlerMethod
。從而也就能完成url
的處理以及檢視的渲染。
事實上,只要你能確保Bean
資訊注入到容器,並且類資訊上至少有@Controller
或者 @RequestMapping
註解,那便能在AbstractHandlerMethodMapping
中所解析,然後完成url
與方法的繫結!這也就是為什麼我們一開始花費精力研究@Controller
和 @Service
注入容器的原因。
總結
在 SpringBoot
開發中,@Controller
和 @Service
是最常用的註解。通常,@Controller
用於標識控制器類,處理 Web
請求並將請求分發到服務層;而 @Service
用於標識服務類,處理業務邏輯。然而,如果用 @Service
來標註控制層是否可以實現與 @Controller
相同的功能呢?答案是肯定的。只要類被 @Service
標註,並且包含 @RequestMapping
等註解,Spring Boot
依然能夠將其作為控制器來處理請求並返回響應。這背後的原理在於 @Service
和 @Controller
都能被 Spring
容器載入和註冊,並且 @RequestMapping
註解能夠使類的方法與 URL 對映,從而實現請求處理功能。