在SpringBoot开发中,
@Controller
和
@Service
基本上是日常开发中使用的最频繁的两个注解。但你有没考虑过
@Service
代替
@Controller
注解来标注到控制层的场景?换言之,经过
@Service
标注的控制层能否实现将用户请求分发到服务层的功能?
在SpringBoot开发中,
@Controller
注解用于标识一个控制器类,该类负责处理Web请求。而控制器类通常包含若干个方法,每个方法对应一个HTTP请求的处理逻辑。而控制器是MVC(
Model-View-Controller
)架构的一部分,其主要负责将用户请求分发到适当的服务层,并返回视图或响应数据。而@Service注解用于标识一个服务类,用以负责处理业务逻辑和与数据访问层交互。
相信对于大多数Java开发者来说
@Controller
和
@Service
注解的使用都不算太难。但进一步,用@Service标注控制层能否达到和
@Controller
注解相同的功能呢?对于这个操作你可能会觉得很疯狂,并下意识的说出不可能。但事实果真如此吗?我们不妨先通过一个简单的例子来验证一下。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
我们首先自定义一个
ServiceController
的控制层,其内部通过Autowired注解注入一个
UserMapper
,并通过
userMapper
来实现控制层与数据层的交互。具体代码如下:
@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
标注在控制层上!
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
你可能会觉得
@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的处理以及视图的渲染。