在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 映射,从而实现请求处理功能。