Spring Boot 学习之路 -- Service 层

前言

  1. 最近因为业务需要,被拉去研究后端的项目,代码框架基于 Spring Boot,对我来说完全小白,需要重新学习研究…
  2. 出于个人习惯,会以 Blog 文章的方式做一些记录,文章内容基本来源于「 Spring Boot 从入门到精通(明日科技) 」一书,做了一些整理,更易于个人理解和回顾查找,所以大家如果希望更系统性的学习,可以阅读此书(比较适合我这种新手)。

一、Service 层与 @Service 注解

在实际开发中,Service 层主要负责业务模块的逻辑应用设计。在设计 Service 层的过程中,首先设计接口,然后设计接口的实现类。通常情况下,Service 层用于封装项目中一些通用的业务逻辑,这么做的好处是有利于业务逻辑的独立性和重复利用性。因此,为了处理一个 Spring Boot 项目中的业务逻辑,Service 层是不可或缺的。

Spring Boot 中的 Service 层是业务逻辑层,其作用是处理业务需求,封装业务方法,执行 Dao 层中用于访问、处理数据的操作。Service 层通常由一个接口和这个接口的实现类组成。其中,Service 层的接口可以在 Controller 层中被调用,用于实现数据的传递和处理;Service 层的实现类须使用 @Service 注解予以标注。

说明:

Dao 层介于 Service 层和数据库之间,用于访问、操作数据库中的数据。Dao 层通常由 Dao 接口、Dao 实现类和 Dao 工厂类这 3 个部分组成。在 Dao 接口中,定义了一系列用于访问、操作数据库中数据的方法。在 Dao 实现类中,实现了 Dao 接口中的方法。Dao 工厂类的作用是返回一个 Dao 实现类的对象。

Controller 层的作用是通过调用 Service 层的接口,控制各个业务模块的业务流程。Controller 层通过解析用户通过 URL 地址发送的请求,调用不同的 Service 层的接口以处理这个请求,把处理结果返回给客户端。

在 Spring Boot 中,把被 @Service 注解标注的类称作「服务类」。@Service 注解属于 Component 组件,可以被 Spring Boot 的组件扫描器扫描到。当启动 Spring Boot 项目时,服务类的对象会被自动地创建,并被注册成 Bean。


二、Service 层的实现过程

大多数的 Spring Boot 项目采用接口模式实现 Service 层。那么,在实际开发中,如何实现 Service 层呢?如下图所示,Service 层的实现过程如下。

在这里插入图片描述

  1. 定义一个 Service 层的接口,在这个接口中定义用于传递和处理数据的方法。例如,定义一个 Service 层的接口 ProductService,代码如下:
public interface ProductService {... // 省略用于传递和处理数据的方法
}
  1. 定义一个 Service 层的接口的实现类,使用 @Service 注解予以标注。这个实现类的作用有两个:一个是实现 Service 层的接口中的业务方法;另一个是执行 Dao 层中用于访问、处理数据的操作。
    例如,使用 @Service 注解标注实现 ProductService 接口的 ProductServiceImpl 类,代码如下:
public class ProductServiceImpl implements ProductService {... // 省略用于实现接口的业务方法和用于执行访问、处理数据的操作的代码
}
  1. 在服务类的对象被自动地创建并被注册成 Bean 之后,其他 Component 组件即可直接注入这个 Bean。

三、同时存在多个实现类的情况

在上一节中,通过简单的示例只演示了一个 Service 层的接口存在一个实现类的情况。但是在实际开发中,一个 Service 层的接口可能会针对多种业务场景而存在多个实现类。

本节将介绍如何处理 Spring Boot 中的 “一个 Service 层的接口同时存在多个实现类” 的情况。

3.1 按照实现类的名称映射服务类的对象

使用 @Service 注解标注一个 Service 层的接口的实现类,这个实现类被称作服务类,这个实现类的对象被称作服务类的对象。服务类的对象会被自动地创建,并被注册成 Bean。

综上所述,Bean 的名称就是实现类的名称。需要注意的是,实现类的名称的首字母要大写,Bean 的名称的首字母是小写的。

例如,使用 @Service 注解标注实现 Service 接口的 ServiceImpl 类,代码如下:

​​​​@Service
​​​​public class ServiceImpl implements Service {  }​​

在上述代码中,实现类的名称是 ServiceImpl。因为 Bean 的名称就是实现类的名称,所以 Bean 的名称是 serviceImpl。因此,上述代码就等同于如下的用于注册 Bean 的代码:

​​​​@Bean("serviceImpl")
​​​​public Service createBean() {
​​​​    return new ServiceImpl();
​​​​}​​

这样,其他 Component 组件即可通过指定 Bean 的名称的方式注入与服务类的对象对应的 Bean。代码如下:

​​​​@Autowired
​​​​Service serviceImpl;​​

上述代码等同于如下的代码:

​​​​@Autowired
​​​​@Qualifier("serviceImpl")
​​​​Service impl;​​

掌握了以上内容后,下面编写一个实例来演示如何按照实现类的名称映射服务类的对象的方式来处理 “一个 Service 层的接口同时存在多个实现类” 的情况。

  1. 首先,创建 TranslateService 翻译服务接口,接口中只定义一个翻译方法。代码如下:
public interface TranslateService {String translate(String word);
}

然后,新建一个 Impl 包,并在包下创建 English2ChineseImpl 英译汉类,并实现 TranslateService 接口,同时使用 @Service 注解标注此类。在实现的翻译方法中,如果用户传入的单词是“Good morning”(不区分大小写),则返回中文“早上好”;如果传入其他内容,则返回“我还没有学会这个短句,你可以举例说明吗?”的提示信息。

English2ChineseImpl 类的代码如下:

@Service
public class English2ChineseImpl implements TranslateService {@Overridepublic String translate(String word) {if ("Good morning".equalsIgnoreCase(word)) {return "Good morning -> 早上好";}return "我还没有学会这个短句,你可以举例说明嘛?";}
}

接着,在 Impl 包下创建 French2ChineseImpl 法译汉类,并实现 TranslateService 接口,同时使用 @Service 标注此类。在实现的翻译方法中,如果用户传入的单词是 “bonjour”(不区分大小写),则返回中文“早上好”;如果传入其他内容,则返回“我还没有学会这个短句,你可以举例说明吗?”的提示信息。

French2ChineseImpl 类的代码如下:

@Service
public class French2ChineseImpl implements TranslateService {@Overridepublic String translate(String word) {if ("bonjour".equals(word)) {return "bonjour -> 早上好";}return "我还没有学会这个短句,你可以举例说明吗?";}
}

最后,创建 TranslateController 控制器类,分别创建两个服务类的对象,并分别按照两个实现类的名称(但首字母小写)映射这两个服务类的对象,使用 @Autowired 注解自动注入这两个服务类的对象。如果客户端访问的是 “/english” 地址,就将发来的参数交由负责英译汉的服务处理;如果访问的是 “/french” 地址,就将发来的参数交由负责法译汉的服务处理。

TranslateController类的代码如下:

@RestController
public class TranslateController {@Autowired@Qualifier("english2ChineseImpl")TranslateService english2ChineseImpl;  // 英译汉服务@Qualifier("french2ChineseImpl")@AutowiredTranslateService french2EnglishImpl;   // 汉译英服务@RequestMapping("/english")public String english(String word) {return english2ChineseImpl.translate(word);}@RequestMapping("/french")public String french(String word) {return french2EnglishImpl.translate(word);}
}

使用 Postman 模拟用户通过 URL 地址发送的请求。访问 http://127.0.0.1:8080/english 地址,并添加 word 参数,参数值为 Good morning。发送请求后,即可看到下图结果,服务器将 Good morning 翻译成了“早上好”。

在这里插入图片描述
在这里插入图片描述

3.2 按照 @Service 的 value 属性映射服务类的对象

在 @Service 注解中,只包含一个 value 属性。value 属性是 @Service 注解的默认属性,它的两种语法格式如下:

@Service("id")          //在语法格式中省略了“value = ”
​​​​@Service(value = "id")  //在语法格式中没有省略“value = ”​​

为 value 属性赋值后,就相当于在创建与服务类的对象对应的 Bean 时确定了 Bean 的名称。因此,上述的语法格式等同于如下的用于注册 Bean 的代码:

​​​​@Bean("id")
​​​​public Service createBean() {
​​​​    return new ServiceImpl();
​​​​}​​

这样,其他 Component 组件即可通过指定 Bean 的名称的方式注入与服务类的对象对应的 Bean。代码如下:

​​​​@Autowired
​​​​Service id;​​

上述代码等同于如下的代码:

​​​​@Autowired
​​​​@Qualifier("id")
​​​​Service impl;​​

掌握了以上内容后,下面编写一个实例演示如何按照 @Service 的 value 属性映射服务类的对象的方式处理 “一个Service层的接口同时存在多个实现类” 的情况。

  1. 首先,创建 TranscriptsService 考试成绩服务接口,接口中只定义一个排序方法,参数为 List 类型的对象。

TranscriptsService 接口的代码如下:

public interface TranscriptService {void sort(List<Double> score);
}
  1. 创建 ASCTranscriptsServiceImpl 升序排列成绩类,并实现 TranslateService 接口,同时使用 @Service 注解标注此类。在实现的排序方法中,调用 Collections 类的 sort() 方法按照升序重新排列列表中的成绩。

ASCTranscriptsServiceImpl 类的代码如下:

@Service("asc")
public class ASCTTranscriptsServiceImpl implements TranscriptService {@Overridepublic void sort(List<Double> score) {Collections.sort(score);  // 对 List 升序排序,默认排序规则}
}
  1. 接着,再创建 DESCTranscriptsServiceImpl 降序排列成绩类,并实现 TranslateService 接口,同时使用 @Service 注解标注此类。

DESCTranscriptsServiceImpl 类的代码如下:

@Service("desc")
public class DESCTranscriptsServiceImpl implements TranscriptService {@Overridepublic void sort(List<Double> score) {score.sort(Comparator.reverseOrder());}
}
  1. 最后,创建 TranscriptsController 控制器类,分别创建两个用于排序的服务类的对象。其中,用于负责升序排列的服务类的对象使用 @Qualifier(“asc”) 注解予以标注,以表示注入的是名称为 asc 的Bean;负责降序排列的服务类的对象直接被命名为“desc”,@Autowired 注解会自动寻找名称为 “desc” 并且类型相同的 Bean 予以注入。如果前端向 “/asc” 地址发送成绩数据,则按照升序排列列表中的成绩;如果前端向 “/desc” 地址发送成绩数据,则按照降序排列列表中的成绩。

TranscriptsController 类的代码如下:

@RestController
public class TranscriptsController {@Autowired@Qualifier("asc")TranscriptService asc;@AutowiredTranscriptService desc;@RequestMapping("/asc")public String asc(Double class1, Double class2, Double class3) {return getString(class1, class2, class3, asc);}@RequestMapping("/desc")public String desc(Double class1, Double class2, Double class3) {return getString(class1, class2, class3, desc);}private String getString(Double class1, Double class2, Double class3, TranscriptService asc) {List<Double> list = new ArrayList<>();list.add(class1);list.add(class2);list.add(class3);asc.sort(list);StringBuilder sb = new StringBuilder();list.forEach(e -> sb.append(e).append(" "));return sb.toString();}
}

使用 Postman 模拟用户通过 URL 地址发送的请求。访问 http://127.0.0.1:8080/asc 地址,先添加 class1、class2 和 class3 这 3 个参数并为其赋值,再发送请求,而后返回的结果将以升序的方式予以排列:

在这里插入图片描述
在这里插入图片描述


四、不采用接口模式的服务类

在实际开发中,一些功能非常简单的服务可以不采用接口模式,直接创建服务类并用 @Service 注解予以标注即可。下面编写一个实例来演示如何使用不采用接口模式的服务类。

  1. 创建 VerifyService 校验服务类,并用 @Service 注解予以标注。在这个服务类中,提供了一个方法,用来校验指定字符串是不是由 2~4 个中文字符组成的。

VerifyService 类的代码如下:

@Service
public class VerifyService {public boolean chineseName(String name) {String match = "^[\\u4e00-\\u9fa5]{2,4}$";if (name != null) {return name.matches(match);}return false;}
}
  1. 创建 VerifyController 控制器类,创建 VerifyService 服务类的对象,并使用 @Autowired 注解自动注入与服务类的对象对应的 Bean。当客户端发来一个名称时,通过 VerifyService 服务类的对象调用校验方法,判断该名称是否为有效的中文名称,并返回校验结果。

VerifyController 类的代码如下:

@RestController
public class VerifyController {@AutowiredVerifyService verify;@RequestMapping("/verify/name")public String verifyName(String name) {if (verify.chineseName(name)) {return "中文名称检验通过";}return "这不是一个有效的中文名称";}
}

使用 Postman 模拟用户通过 URL 地址发送的请求。访问 http://127.0.0.1:8080/verify/name 地址,并添加 name 参数,name 参数的值为 “David”。因为 David 都是英文字母,所以发送请求后会看到下图结果:

在这里插入图片描述

将 name 参数的值修改为“李四”后,再次发送请求:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/53573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(补充)3DMAX初级小白班第三课:创建物体+物体材质编辑

1.可以点这里来改变材质颜色&#xff08;但是通过材质编辑器给了材质以后就只能在这里改线框颜色&#xff09;。但一般就是用灰色材质和黑色线框 2.材质编辑器快捷键为m 右键可更改个数&#xff0c;最多24个 将材质指定选定对象 如何把材质编辑器面板改成旧版 按f10 改成扫描…

计算机毕设选题推荐-基于python的电子健康信息分析系统【源码+文档+调试】

精彩专栏推荐订阅&#xff1a;在下方主页&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、电子健康信息…

【Linux】解锁管道通信和共享内存通信,探索进程间通信的海洋

目录 引言&#xff1a; 1、进程间通信基础介绍 1.1为什么需要在进程之间通信&#xff1f; 1.2进程间通信是什么&#xff1f; 1.3我们具体如何进行进程间的通信呢&#xff1f; a.一般规律&#xff1a; b.具体做法 2.管道 2.1什么是管道 2.2匿名管道&#xff1a; 创建…

行业展望:线缆行业发展

线缆行业作为国民经济中最大的配套行业之一&#xff0c;在我国机械工业的细分行业中占据举足轻重的地位&#xff0c;仅次于汽车整车制造和零部件及配件制造业。作为电气化、信息化、智能化社会中重要的基础性配套产业&#xff0c;电线电缆被誉为国民经济的"血管"与&q…

用户态缓存:链式缓冲区(Chain Buffer)

目录 链式缓冲区&#xff08;Chain Buffer&#xff09;简介 为什么选择链式缓冲区&#xff1f; 代码解析 1. 头文件与类型定义 2. 结构体定义 3. 宏定义与常量 4. 环形缓冲区的基本操作 5. 其他辅助函数 6. 数据读写操作的详细实现 7. 总结 8. 结合之前的内容 9. 具…

鸿蒙OpenHarmony【小型系统基础内核(进程管理任务)】子系统开发

任务 基本概念 从系统的角度看&#xff0c;任务Task是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源&#xff0c;并独立于其它任务运行。 OpenHarmony 内核中使用一个任务表示一个线程。 OpenHarmony 内核中同优先级进程内的任务统一调度、运…

STM32 map 文件浅析

目录 一、概述二、Section Cross References三、Removing Unused input sections from the image四、Memory Map of the image1、Local Symbols2、全局符号&#xff08;Global Symbols&#xff09; 五、Image Symbol Table六、Image component sizes 一、概述 .map 文件是编译…

【质优价廉】GAP9 AI算力处理器赋能智能可听耳机,超低功耗畅享未来音频体验!

当今世界&#xff0c;智能可听设备已经成为了流行趋势。随后耳机市场的不断成长起来&#xff0c;消费者又对AI-ANC&#xff0c;AI-ENC&#xff08;环境噪音消除&#xff09;降噪的需求逐年增加&#xff0c;但是&#xff0c;用户对于产品体验的需求也从简单的需求&#xff0c;升…

半导体器件制造5G智能工厂数字孪生物联平台,推进制造业数字化转型

半导体器件制造行业作为高科技领域的核心驱动力&#xff0c;正积极探索和实践以5G智能工厂数字孪生平台为核心的新型制造模式。这一创新不仅极大地提升了生产效率与质量&#xff0c;更为制造业的未来发展绘制了一幅智能化、网络化的宏伟蓝图。 在半导体器件制造5G智能工厂中&a…

Java笔试面试题AI答之设计模式(1)

文章目录 1. 简述什么是设计模式 &#xff1f;2. 叙述常见Java设计模式分类 &#xff1f;3. Java 设计模式的六大原则 &#xff1f;4. 简述对 MVC 的理解&#xff0c; MVC 有什么优缺点&#xff1f;MVC 的三个核心部分&#xff1a;MVC 的优点&#xff1a;MVC 的缺点&#xff1a…

巨潮股票爬虫逆向

目标网站 aHR0cDovL3dlYmFwaS5jbmluZm8uY29tLmNuLyMvSVBPTGlzdD9tYXJrZXQ9c3o 一、抓包分析 请求头参数加密 二、逆向分析 下xhr断点 参数生成位置 发现是AES加密&#xff0c;不过是混淆的&#xff0c;但并不影响咱们扣代码 文章仅提供技术交流学习&#xff0c;不可对目标服…

LabVIEW提高开发效率技巧----合理使用数据流与内存管理

理使用数据流和内存管理是LabVIEW开发中提高性能和稳定性的关键&#xff0c;特别是在处理大数据或高频率信号时&#xff0c;优化可以避免内存消耗过大、程序卡顿甚至崩溃。 1. 使用 Shift Register 进行内存管理 Shift Register&#xff08;移位寄存器&#xff09; 是 LabVIE…

前缀和问题

洛谷题面 这个其实可以当模板了。 代码&#xff1a; #include<bits/stdc.h> using namespace std; const int N1e510; int sum[N]; int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);int n,m,x;cin>>n;for(int i1;i<n;i){cin>>x;sum[i]sum[i…

《微信小程序实战(4) · 地图导航功能》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

240924-通过服务器代理ip地址及port端口wget等下载文件

A. 如何下载 在服务器上设置了代理 IP 和端口后&#xff0c;可以使用以下命令行格式通过 wget 下载文件&#xff1a; wget -e use_proxyyes -e http_proxyhttp://代理IP:端口号 目标文件URL或者&#xff0c;如果你使用 HTTPS 协议&#xff0c;可以使用以下命令&#xff1a; …

数据结构应试-1

1. 好像是错的 2. n个元素&#xff0c;插入的可能有n1个位置&#xff0c;所以n&#xff08;n1&#xff09;/2*(n1)2/n 3. 4. 5. 6. 假设我们有一个循环队列&#xff0c;数组的长度为 n 10&#xff0c;并且当前队头指针 f 的位置是 2&#xff0c;队尾指针 r 的位置是 8。我们需…

【开源免费】基于SpringBoot+Vue.JS墙绘产品展示交易平台(JAVA毕业设计)

本文项目编号 T 049 &#xff0c;文末自助获取源码 \color{red}{T049&#xff0c;文末自助获取源码} T049&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

携手SelectDB,观测云实现性能与成本的双重飞跃

在刚刚落下帷幕的2024云栖大会上&#xff0c;观测云又一次迎来了全面革新。携手SelectDB&#xff0c;实现了技术的飞跃&#xff0c;这不仅彰显了观测云在监控观测领域的技术实力&#xff0c;也预示着我们可以为全球用户提供更加高效、稳定的数据监测与分析服务。这一技术升级&a…

Golang | Leetcode Golang题解之第435题无重叠区间

题目&#xff1a; 题解&#xff1a; func eraseOverlapIntervals(intervals [][]int) int {n : len(intervals)if n 0 {return 0}sort.Slice(intervals, func(i, j int) bool { return intervals[i][1] < intervals[j][1] })ans, right : 1, intervals[0][1]for _, p : ra…

【计算机视觉】YoloV8-训练与测试教程

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 制作数据集 Labelme 数据集 数据集选用自己标注的&#xff0c;可参考以下&#xff1a…