🏆本文收录于「滚雪球学SpringBoot」(全网一个名)专栏,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
🤔 为什么要使用 @Service
注解?它到底有什么用处?
背景:由于公司在业务方受到同行的强力推介,导致公司销售部门的内需出现了横向增长。正因如此,我所在的项目组岗位编制终于得到了扩充。招聘信息一发布,HR那边就收到了大量的简历,筛选出了几位候选人。让我印象深刻的,便是在前几天面试的一位自称有十来年工作经验的后端开发者。在我们聊到 Spring 框架时,我简单的聊到了几个常见的注解,比如 @Component
、@Controller
、@Repository
等,它们每一个都有自己的作用。
当我准备继续聊下一个话题时,突然他反问了我一个问题:"@Service
注解,面试官,你知道它有哪些好处吗?"这一瞬间,我... 确实有点被冒犯到了。到底是我在面试他,还是他在面试我?我心里越想越觉得不得劲劲。于是我决定,今天就必须让他重新认识下 @Service
的真正价值!
今天我们就来聊聊这个在日常开发中非常常见,但却很少有人深入理解其好处的 Spring 注解------@Service
。
可能你们有人会觉得,@Service
不就是一个简单的标记吗?加了它,Spring 就能把这个类当作服务层来管理,是这样没错,但它背后可不仅仅是这样简单。其实,@Service
让你在开发业务逻辑的时候,可以减少很多重复的工作,使得开发更高效、代码更整洁,而且还与其他注解一起协作,帮助开发者更好地管理应用程序的各个层次🤔。
接下来,我就要贴脸开大了,为什么 @Service
在实际开发中如此有用,看看它到底带来了哪些好处!(必须把这个面子给挣回来!)
好戏开始,接着奏乐接着舞!
📌 1. 自动注册和自动注入:让你开发省心更省力
当你在一个类上添加 @Service
注解时,Spring 会自动识别它,并将这个类注册为 Spring 容器中的一个 Bean。也就是说,你不需要手动去配置 Bean 了,Spring 会自动帮你搞定。
这种自动管理的机制,不仅减少了开发中的繁琐步骤,还能让你专注于业务逻辑的编写。特别是在开发较为复杂的应用时,Spring 的自动注入功能让我们的代码变得更简洁、易懂。
1.1 案例代码
java
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String getUserInfo() {
return "用户信息:张三,年龄 25 岁";
}
}
在这个示例中,UserService
被 @Service
注解标记后,Spring 会自动将它注册为一个 Bean。你只需要在需要的地方使用 @Autowired
来注入它,就能方便地调用它的方法。
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private UserService userService;
public void showUserInfo() {
System.out.println(userService.getUserInfo());
}
}
看,UserService
被自动注入到 UserController
中,你无需手动创建 UserService
实例,Spring 会在背后自动帮你完成这一切。这样的自动化管理,让你在开发中不必担心类的实例化和依赖管理,所有的事情都交给 Spring 来处理。简直爽到不行。💡
1.2 案例剖析
在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。 如上代码完整展示了Spring框架中常见的两种组件:@Service
和 @Controller
注解,以及依赖注入(DI)的基本用法。下面是代码的深度剖析,以辅助大家深入理解。
1.1.1 UserService
类
-
@Service
注解 :@Service
是 Spring 中的一个标记注解,用于定义服务层的 Bean。它的作用是将UserService
类标识为一个服务类,以便 Spring 容器在启动时自动扫描并将其注册为 Spring 管理的 Bean。这样可以方便其他组件依赖于它,进行服务调用。 -
getUserInfo()
方法 :这个方法返回一个字符串"用户信息:张三,年龄 25 岁"
。它是该服务类的主要功能,提供用户的基本信息。
1.1.2 UserController
类
-
@Controller
注解 :@Controller
是 Spring 中的一个标记注解,用于定义控制器层的 Bean。控制器用于处理 HTTP 请求(在 Spring MVC 中)并返回相应的视图或数据。这里虽然没有展示 HTTP 请求和视图的部分,但该类依然作为一个普通的 Spring Bean,可以在应用程序中进行管理。 -
@Autowired
注解 :@Autowired
用于自动注入依赖。Spring 会自动查找匹配的 Bean,并将其注入到UserController
中的userService
字段。这里通过依赖注入,UserController
不需要手动创建UserService
实例,而是由 Spring 容器来管理对象的生命周期和依赖关系。 -
showUserInfo()
方法 :该方法调用了userService.getUserInfo()
,并将结果输出到控制台。它展示了如何通过UserService
提供的服务来获取用户信息。

1.1.3 依赖注入流程
- 当 Spring 容器启动时,会扫描项目中的类,发现带有
@Service
和@Controller
注解的类,并自动将它们注册为 Bean。 - 在
UserController
中,userService
字段会被自动注入为UserService
的实例。 - 调用
showUserInfo()
方法时,Spring 会提供注入的userService
实例,执行getUserInfo()
方法,并打印用户信息。
1.1.4 小结
UserService
类作为一个服务类提供业务逻辑。UserController
类作为控制器类负责处理请求和展示结果。- 通过
@Autowired
注解,UserController
自动依赖于UserService
,这展现了 Spring 框架中的依赖注入(DI)功能,使得代码更松耦合,易于管理和测试。

📌 2. 清晰的层次结构:让代码结构更加明了
如果你的类没有任何注解,那么这些类看起来都差不多,没人能一眼看出每个类的职责。@Service
就是让你的代码更加清晰的关键所在。
通过使用 @Service
,你告诉别人:这个类是服务层的类,负责处理业务逻辑,应该放在项目的业务层。而 @Repository
是数据访问层的类,@Controller
则是控制层的类,三者职责分明。
这种层次结构的划分,让项目更加规范、易于维护。尤其在团队协作时,其他开发人员一眼就能明白某个类的功能是什么,从而避免了代码的混乱。
📌 3. 与其他注解的协作:完美融合,提升开发效率
@Service
并不是孤立存在的。它和其他注解一起,共同构成了 Spring 的注解体系。比如,@Controller
负责处理 HTTP 请求,@Repository
负责数据层的操作,而 @Service
则专注于服务层的业务逻辑。
当你在项目中使用这些注解时,Spring 会根据不同注解的用途自动管理 Bean。比方说,@Service
会自动管理服务层的 Bean,而 @Repository
则会特别处理数据访问相关的操作,甚至会帮你处理数据库异常的转换。
3.1 案例代码:控制层、服务层、数据访问层的协作
java
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public String fetchUserData() {
return "从数据库中获取用户数据";
}
}
java
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public String getUserInfo() {
return userRepository.fetchUserData();
}
}
java
@Controller
public class UserController {
@Autowired
private UserService userService;
public void showUserData() {
System.out.println(userService.getUserInfo());
}
}
通过这种方式,Spring 会自动将 UserRepository
、UserService
和 UserController
这三层的依赖注入给处理好,整个应用的架构一目了然,各层之间的依赖也都能自动解决。
3.2 代码解析
在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。
如上案例代码我主要是为了展示如何使用 Spring 框架的依赖注入(DI)和 @Repository
、@Service
、@Controller
注解来实现一个简单的分层架构。下面是代码的详细剖析,以辅助大家深入理解。
3.2.1 UserRepository
类
-
@Repository
注解:@Repository
是 Spring 中用于标记数据访问层(DAO层)组件的注解,目的是让 Spring 知道这个类负责与数据库或其他数据源进行交互。- 它的主要作用是将类声明为持久层 Bean,Spring 会自动处理异常转换(例如数据库相关的异常转为 Spring 的
DataAccessException
类型)。
-
fetchUserData()
方法:- 模拟从数据库获取数据。返回了一个简单的字符串
"从数据库中获取用户数据"
。在实际应用中,这个方法可能会与数据库交互,查询用户信息。
- 模拟从数据库获取数据。返回了一个简单的字符串
3.2.2 UserService
类
-
@Service
注解:@Service
注解表明这个类是服务层的 Bean。它通常用于包含业务逻辑的方法。- 在这段代码中,
UserService
类负责处理与用户数据相关的业务逻辑。
-
构造函数注入(
@Autowired
):@Autowired
注解被用在构造函数上,表明UserService
类需要一个UserRepository
实例作为依赖。- 这种构造函数注入方式是 Spring 推荐的最佳实践,它确保了所有依赖在创建
UserService
实例时就已注入,并且使得UserService
类的依赖关系更加明确。
-
getUserInfo()
方法:- 这个方法调用
userRepository.fetchUserData()
,并返回从UserRepository
获取的数据。它将服务层的业务逻辑与数据访问逻辑解耦,使得代码更易于维护。
- 这个方法调用

3.2.3 UserController
类
-
@Controller
注解:@Controller
用于标记控制器类,通常用于处理 HTTP 请求并返回视图或数据。- 在这里,
UserController
负责接收请求并展示用户数据。
-
@Autowired
注解:- 在
UserController
中,@Autowired
自动注入UserService
。这意味着 Spring 容器会自动为userService
字段提供一个UserService
的实例。
- 在
-
showUserData()
方法:- 这个方法调用
userService.getUserInfo()
来获取用户数据并将其打印到控制台。它展示了如何从服务层获取数据并处理该数据。
- 这个方法调用
3.2.4 小结
-
分层架构:
- 这段代码实现了经典的三层架构:控制器层(Controller) 、服务层(Service) 和 数据访问层(Repository)。
- 控制器(
UserController
)负责处理用户请求,并将数据返回给客户端;服务层(UserService
)负责业务逻辑;数据访问层(UserRepository
)负责与数据库交互。
-
依赖注入(DI):
- Spring 的依赖注入机制帮助自动将依赖项(如
UserRepository
和UserService
)注入到相应的类中,减少了手动创建对象的麻烦,从而使得代码更松耦合、易于测试和维护。
- Spring 的依赖注入机制帮助自动将依赖项(如
-
构造函数注入:
- 在
UserService
中,使用构造函数注入方式提供UserRepository
的实例。这种方式比字段注入更容易进行单元测试,并且符合不可变性原则,确保依赖项在对象创建时就已提供。
- 在
3.2.5 整体流程
UserController
通过自动注入UserService
来获取用户服务。UserService
通过自动注入UserRepository
来获取用户数据。UserRepository
模拟从数据库中获取用户信息。UserController
将最终获取到的数据打印到控制台。
按如上这种设计将各层功能清晰分离,利于代码的扩展和维护。
📌 4. 与 AOP 配合:让业务逻辑更加灵活
AOP(面向切面编程)是 Spring 框架中一个很强大的功能,它允许你在不修改类的源码的情况下,给类的特定方法添加额外的功能。比如,你可以在方法调用前后增加日志记录,或者在服务方法上做事务处理。
@Service
注解的类通常会作为 AOP 的切入点,这意味着,你可以非常方便地为服务类的方法增加额外的行为。通过 AOP,你可以做到一些横切关注点的处理,比如日志、事务等,而不需要在业务代码中硬编码。
4.1 代码示例:AOP 增强
java
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.UserService.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("即将调用方法:" + joinPoint.getSignature().getName());
}
@After("execution(* com.example.service.UserService.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法执行完毕:" + joinPoint.getSignature().getName());
}
}
在这个示例中,我们用 @Before
和 @After
注解增强了 UserService
的方法。在方法调用之前和之后,分别记录了日志。借助 AOP,我们能够方便地为方法添加额外的操作,而不需要修改原本的业务逻辑。
4.2 代码解析
在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。
如上案例代码我主要是为了展示如何在 Spring 中使用 AOP(面向切面编程) 来为方法添加日志记录功能。AOP 允许我们在不修改业务逻辑的情况下,添加横切关注点(如日志、性能监控、安全等)。下面是对这段代码的详细剖析,以辅助大家深入理解。
4.2.1 @Aspect
注解
-
@Aspect
:这是 AOP 的核心注解,标记这个类是一个切面(Aspect)。一个切面可以包含多个切点(Pointcut)和通知(Advice)。在这里,LoggingAspect
类就是一个切面,它负责日志记录功能。 -
@Component
:这个注解是 Spring 的常见注解,用于标记该类是一个 Spring Bean。通过这个注解,Spring 会自动将LoggingAspect
类注册到 Spring 容器中,便于管理。

4.2.2 方法拦截器(@Before
和 @After
)
这两个注解定义了在方法执行前后要执行的操作:
4.2.2.1 @Before
注解
-
@Before
:这个注解指定了一个前置通知(Before Advice),即在目标方法执行之前执行该方法。这里的"execution(* com.example.service.UserService.*(..))"
表示切点表达式,它会匹配com.example.service.UserService
类中的所有方法。 -
JoinPoint
:JoinPoint
提供了对目标方法的相关信息。在这里,joinPoint.getSignature().getName()
获取正在被调用的方法的名称,用于打印日志。 -
日志内容 :在方法调用之前,输出
"即将调用方法:" + 方法名
。
4.2.2.2 @After
注解
java
@After("execution(* com.example.service.UserService.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法执行完毕:" + joinPoint.getSignature().getName());
}
-
@After
:这个注解指定了一个后置通知(After Advice),即在目标方法执行完毕之后执行该方法。切点表达式"execution(* com.example.service.UserService.*(..))"
同样会匹配UserService
类中的所有方法。 -
日志内容 :在方法执行完毕后,输出
"方法执行完毕:" + 方法名
。

4.2.3 AOP 切点表达式
java
"execution(* com.example.service.UserService.*(..))"
- 这个表达式用于定义哪些方法会被切面拦截。具体来说:
execution
:这是 AOP 的关键字,表示方法执行时的连接点。*
:表示任意返回类型。com.example.service.UserService.*(..)
:匹配com.example.service.UserService
类中的所有方法,其中*
表示所有方法名,(..)
表示匹配任意参数。
4.2.4 JoinPoint
类
JoinPoint
是 Spring AOP 提供的一个接口,它代表了目标方法的执行过程中的连接点。JoinPoint
可以让我们获取方法的相关信息:
getSignature()
:获取方法的签名(包括方法名、参数类型等信息)。getName()
:获取方法名。
4.2.5 小结:AOP 在日志记录中的应用
如上案例代码的主要功能是为 UserService
类中的所有方法添加日志功能,分别在方法调用前和方法执行后打印日志:
- 前置通知 :在方法执行之前打印
"即将调用方法:<方法名>"
。 - 后置通知 :在方法执行完之后打印
"方法执行完毕:<方法名>"
。
4.2.6 AOP 优点
- 松耦合:日志功能被提取到一个单独的切面中,业务逻辑和日志记录逻辑解耦,便于维护和扩展。
- 代码复用:同一套日志记录逻辑可以应用于多个方法或类,避免重复代码。
- 增强功能:通过 AOP 可以轻松地为方法添加横切关注点(如日志、事务管理、性能监控等),无需修改业务逻辑代码。

4.2.7 典型应用
这种方式通常用于:
- 日志记录:记录方法执行前后的日志。
- 权限控制:在方法执行前进行权限验证。
- 性能监控:记录方法的执行时间。
- 事务管理:在方法执行前后控制事务的提交与回滚。
📌 5. 统一管理:让维护变得更加轻松
随着项目的延伸,业务代码会变得越来越复杂。@Service
的最大优势之一就是能够让你的代码结构更清晰,服务类的管理变得更为规范。所有的服务类都通过 @Service
注解标识出来,开发者可以轻松知道哪些类是业务逻辑相关的,哪些类是控制层、数据层的,从而快速定位问题和进行维护。
如果没有这些注解,所有的类都没有任何标识,项目一旦规模扩大,开发和维护就变得异常困难。通过合理使用注解,项目的层次结构变得更加清晰,代码的可读性和可维护性都得到了提升。
所以,我把这个问题讲清楚了,你们都学会了木有?
📣 关于我
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主&最具价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。
-End-