⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7393533304505204787 「毅航」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

在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框架中是如何一步步被注入到容器的。

进一步,笔者曾在揭秘@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. 1. 前言
  2. 2. @Sercice代替@Controller
    1. 2.0.0.1. ServiceController
  • 3. 揭秘背后原理
  • 4. 总结