SpringBoot控制层中,@Service可以完全替代@Controller吗?

科技   2024-11-03 17:21   江苏  

点击关注公众号,Java干货及时送达

来源:juejin.cn/post/7393533304505204787


    在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/

    @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"
    }

    通过返回内容,不难看出我们的请求顺利到ServiceControllergetServices方法。也就是说我们完全可以用@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时,会通过ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法加载路径下所有的bean名称信息,然后在finshBeanFactoryInitialization完成bean的实例化。而我们绕了这么一大圈就是皆在说明,被@Service@Controller所标注的类在SpringBoot框架中是如何一步步被注入到容器的。

    进一步,笔者曾在揭秘@Controller内部方法与URL绑定的全流程中谈到,对于控制层内的方法,其会在AbstractHandlerMethodMapping中的afterPropertiesSet()完成请求url与方法的映射绑定。更进一步,afterPropertiesSet的处理逻辑全部委托于于getCandidateBeanNamesprocessCandidateBean两个方法。

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


    宝子们,小编给大家安排福利了~

    你喜欢本次推文吗?欢迎各位评论区聊一聊

    留言抽 3位 家人,赠送8.88元现金红包

    ↓ 分享在看点赞
    至少我要拥有一个吧

      

    1、一个小公司技术开发者的心酸往事(已倒闭)

    2、6个月收入狂揽700万!17岁高中生开发一个新AI应用爆火,网友:别低估小孩子

    3、存QQ号码,究竟该用int类型还是string类型?

    4、美团一面:Spring Cloud 远程调用为啥要采用 HTTP,而不是 RPC?

    5、存QQ号码,究竟该用int类型还是string类型?

    6、解读JDK 23新特性,原则就在这本“Java四大名著”之一里

    点在看

    Java技术迷
    专注于计算机编程语言知识分享。主要包括Java基础技术、数据结构、相关工具、spring Cloud、intellij idea......,送书、送红包福利等你来!
     最新文章