面十年开发候选人被反问:当类被标注为@Service后,会有什么好处?我...🫨

🏆本文收录于「滚雪球学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
  1. @Service 注解@Service 是 Spring 中的一个标记注解,用于定义服务层的 Bean。它的作用是将 UserService 类标识为一个服务类,以便 Spring 容器在启动时自动扫描并将其注册为 Spring 管理的 Bean。这样可以方便其他组件依赖于它,进行服务调用。

  2. getUserInfo() 方法 :这个方法返回一个字符串 "用户信息:张三,年龄 25 岁"。它是该服务类的主要功能,提供用户的基本信息。

1.1.2 UserController
  1. @Controller 注解@Controller 是 Spring 中的一个标记注解,用于定义控制器层的 Bean。控制器用于处理 HTTP 请求(在 Spring MVC 中)并返回相应的视图或数据。这里虽然没有展示 HTTP 请求和视图的部分,但该类依然作为一个普通的 Spring Bean,可以在应用程序中进行管理。

  2. @Autowired 注解@Autowired 用于自动注入依赖。Spring 会自动查找匹配的 Bean,并将其注入到 UserController 中的 userService 字段。这里通过依赖注入,UserController 不需要手动创建 UserService 实例,而是由 Spring 容器来管理对象的生命周期和依赖关系。

  3. showUserInfo() 方法 :该方法调用了 userService.getUserInfo(),并将结果输出到控制台。它展示了如何通过 UserService 提供的服务来获取用户信息。

1.1.3 依赖注入流程
  1. 当 Spring 容器启动时,会扫描项目中的类,发现带有 @Service@Controller 注解的类,并自动将它们注册为 Bean。
  2. UserController 中,userService 字段会被自动注入为 UserService 的实例。
  3. 调用 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 会自动将 UserRepositoryUserServiceUserController 这三层的依赖注入给处理好,整个应用的架构一目了然,各层之间的依赖也都能自动解决。

3.2 代码解析

在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

如上案例代码我主要是为了展示如何使用 Spring 框架的依赖注入(DI)和 @Repository@Service@Controller 注解来实现一个简单的分层架构。下面是代码的详细剖析,以辅助大家深入理解。

3.2.1 UserRepository
  1. @Repository 注解

    • @Repository 是 Spring 中用于标记数据访问层(DAO层)组件的注解,目的是让 Spring 知道这个类负责与数据库或其他数据源进行交互。
    • 它的主要作用是将类声明为持久层 Bean,Spring 会自动处理异常转换(例如数据库相关的异常转为 Spring 的 DataAccessException 类型)。
  2. fetchUserData() 方法

    • 模拟从数据库获取数据。返回了一个简单的字符串 "从数据库中获取用户数据"。在实际应用中,这个方法可能会与数据库交互,查询用户信息。
3.2.2 UserService
  1. @Service 注解

    • @Service 注解表明这个类是服务层的 Bean。它通常用于包含业务逻辑的方法。
    • 在这段代码中,UserService 类负责处理与用户数据相关的业务逻辑。
  2. 构造函数注入(@Autowired

    • @Autowired 注解被用在构造函数上,表明 UserService 类需要一个 UserRepository 实例作为依赖。
    • 这种构造函数注入方式是 Spring 推荐的最佳实践,它确保了所有依赖在创建 UserService 实例时就已注入,并且使得 UserService 类的依赖关系更加明确。
  3. getUserInfo() 方法

    • 这个方法调用 userRepository.fetchUserData(),并返回从 UserRepository 获取的数据。它将服务层的业务逻辑与数据访问逻辑解耦,使得代码更易于维护。
3.2.3 UserController
  1. @Controller 注解

    • @Controller 用于标记控制器类,通常用于处理 HTTP 请求并返回视图或数据。
    • 在这里,UserController 负责接收请求并展示用户数据。
  2. @Autowired 注解

    • UserController 中,@Autowired 自动注入 UserService。这意味着 Spring 容器会自动为 userService 字段提供一个 UserService 的实例。
  3. showUserData() 方法

    • 这个方法调用 userService.getUserInfo() 来获取用户数据并将其打印到控制台。它展示了如何从服务层获取数据并处理该数据。
3.2.4 小结
  1. 分层架构

    • 这段代码实现了经典的三层架构:控制器层(Controller)服务层(Service)数据访问层(Repository)
    • 控制器(UserController)负责处理用户请求,并将数据返回给客户端;服务层(UserService)负责业务逻辑;数据访问层(UserRepository)负责与数据库交互。
  2. 依赖注入(DI)

    • Spring 的依赖注入机制帮助自动将依赖项(如 UserRepositoryUserService)注入到相应的类中,减少了手动创建对象的麻烦,从而使得代码更松耦合、易于测试和维护。
  3. 构造函数注入

    • UserService 中,使用构造函数注入方式提供 UserRepository 的实例。这种方式比字段注入更容易进行单元测试,并且符合不可变性原则,确保依赖项在对象创建时就已提供。
3.2.5 整体流程
  1. UserController 通过自动注入 UserService 来获取用户服务。
  2. UserService 通过自动注入 UserRepository 来获取用户数据。
  3. UserRepository 模拟从数据库中获取用户信息。
  4. 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 类中的所有方法。

  • JoinPointJoinPoint 提供了对目标方法的相关信息。在这里,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-

相关推荐
Rabbb几秒前
C# JSON属性排序、比较 Newtonsoft.Json
后端
蓝易云2 分钟前
在Linux、CentOS7中设置shell脚本开机自启动服务
前端·后端·centos
小布不吃竹6 分钟前
SpringMVC框架
spring·mvc
一千柯橘8 分钟前
Nestjs 解决 request entity too large
javascript·后端
柚个朵朵20 分钟前
IDEA中使用Git
java·git·spring
userkang1 小时前
消失的前后端,崛起的智能体
前端·人工智能·后端·ai·硬件工程
慧一居士1 小时前
Kafka HA集群配置搭建与SpringBoot使用示例总结
spring boot·后端·kafka
@_猿来如此2 小时前
Django 实现电影推荐系统:从搭建到功能完善(附源码)
数据库·后端·python·django
言之。2 小时前
【Go语言】ORM(对象关系映射)库
开发语言·后端·golang
uncofish2 小时前
springboot不连接数据库启动(原先连接了mysql数据库)
数据库·spring boot·mysql