作者:毅航
來源:juejin.cn/post/7393533304505204787
在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
來實現控制層與資料層的互動。
Spring Boot 基礎就不介紹了,推薦看這個實戰專案:
https://github.com/javastacks/spring-boot-best-practice
具體程式碼如下:
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框架中是如何一步步被注入到容器的。
進一步,筆者曾在揭秘@Controller
內部方法與URL繫結的全流程中談到,對於控制層內的方法,其會在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 對映,從而實現請求處理功能。
更多文章推薦:
1.Spring Boot 3.x 教程,太全了!
2.2,000+ 道 Java面試題及答案整理(2024最新版)
3.免費獲取 IDEA 啟用碼的 7 種方式(2024最新版)
覺得不錯,別忘了隨手點贊+轉發哦!