• [技术干货] Spring Boot 统一数据返回格式 分析 和 处理-转载
     实现统一数据格式 统⼀的数据返回格式使⽤ @ControllerAdvice 和 ResponseBodyAdvice 的⽅式实现; @ControllerAdvice : 表⽰控制器通知类. 比如:添加类 ResponseAdvice , 实现 ResponseBodyAdvice 接⼝, 并在类上添加 @ControllerAdvice 注解. import com.example.demo.model.Result; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;   @ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice {     @Override     public boolean supports(MethodParameter returnType, Class converterType) {         return true;     }       @Override     public Object beforeBodyWrite(Object body, MethodParameter returnType,                                   MediaType selectedContentType, Class selectedConverterType,                                   ServerHttpRequest request, ServerHttpResponse response) {         return Result.success(body); //返回 Result 类型的数据     } }   supports⽅法: 判断是否要执⾏beforeBodyWrite⽅法. true为执⾏, false不执⾏. 通过该⽅法可以 选择哪些类或哪些⽅法的response要进⾏处理, 其他的不进⾏处理.  beforeBodyWrite⽅法:对response⽅法进⾏具体操作处理.   测试   写一些不同的返回结果,看看哪些会出现问题!  import com.example.demo.model.BookInfo; import com.example.demo.model.Result; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController;   @RestController @RequestMapping("/test") public class TestController {     @RequestMapping("/t1")     public Integer t1() {         return 12334;     }       @RequestMapping("/t2")     public String t2() {         return "hello";     }       @RequestMapping("/t3")     public Boolean t3() {         return true;     }       @RequestMapping("/t4")     public BookInfo t4() {         return new BookInfo();     }       @RequestMapping("/t5")     public Result t5() {         return Result.success("success");     } }  1.返回结果为Integer,可以正确响应.     2.返回结果为String,结果显示内部错误.      控制台查看日志:  3.返回结果为Boolean,可以正常响应.      4.返回结果为BookInfo对象,可以正常响应.   5.返回结果为Result,发现又进行了一次封装.       问题:1.返回结果为String,不能正常进行处理了;2.返回结果为Result,又多进行了一次封装.   原因分析 这时候就会非常纳闷了,为什么就处理String的时候会出错呢?就不得不去看源码分析了.  SpringMVC (也就是在初始化时) 默认会注册⼀些⾃带的 HttpMessageConverter (转换器) (从先后顺序排列分别为 ByteArrayHttpMessageConverter , StringHttpMessageConverter , SourceHttpMessageConverter , SourceHttpMessageConverter , AllEncompassingFormHttpMessageConverter )    这些转换器是有先后顺序的,是用ArrayList存储的.会根据我们的返回结果挨个判断使用哪个转换器!!!   此时,就会发现问题就出现在 StringHttpMessageConverter.  处理的内容主要是在AbstractMessageConverterMethodProcessor 中 会有一个writeWithMessageConverters()方法.    通过getAdvice()拿到了beforBodyWrite 就会对body进行处理,处理完之后并没有结束(body变成了Result类型),此时,也并没有出错。会继续执行((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage).    由于 StringHttpMessageConverter 重写了addDefaultHeaders⽅法, 所以会执⾏⼦类的⽅法  然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String, 此  时t为Result类型, 所以出现类型不匹配" Result cannot be cast to java.lang.String "的异常.  解决方案 1.当返回结果为Result时,就直接返回body.   2.当返回结果为String时,采用SpringBoot内置提供的Jackson来实现信息的序列化    @ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice {       @Autowired     private ObjectMapper objectMapper;       @Override     public boolean supports(MethodParameter returnType, Class converterType) {         return true;     }       @SneakyThrows     @Override     public Object beforeBodyWrite(Object body, MethodParameter returnType,                                   MediaType selectedContentType, Class selectedConverterType,                                   ServerHttpRequest request, ServerHttpResponse response) {         //body 是返回的结果         //当返回结果为Result类型时,就直接返回body         if (body instanceof Result) {             return body;         }         //返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化         if (body instanceof String) {             return objectMapper.writeValueAsString(Result.success(body));         }         return Result.success(body); //返回 Result 类型的数据     } }  ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/m0_65465009/article/details/137143458 
  • 【SpringBoot】带你一文彻底搞懂RestController和Controller的关系与区别-转载
     什么是@RestController,什么是@Controller       @RestController 和 @Controller 是 Spring Framework 中用于定义控制器的注解。      @RestController 是一个组合注解,它结合了 @Controller 和 @ResponseBody 注解的功能(就相当于把两个注解组合在一起)。在使用 @RestController 注解标记的类中,每个方法的返回值都会以 JSON 或 XML 的形式直接写入 HTTP 响应体中,相当于在每个方法上都添加了 @ResponseBody 注解。      @Controller 注解标记的类则是传统的控制器类。它用于处理客户端发起的请求,并负责返回适当的视图(View)作为响应。在使用 @Controller 注解的类中,通常需要在方法上使用 @ResponseBody 注解来指示该方法的返回值要作为响应的主体内容,而不是解析为视图。          简而言之,@RestController 适用于构建 RESTful 风格的 API,其中每个方法的返回值会直接序列化为 JSON 或 XML 数据并发送给客户端。而 @Controller 适用于传统的 MVC 架构,它负责处理请求并返回相应的视图。(@RestController下的方法默认返回的是数据格式,@Controller注解标注的类下面的方法默认返回的就是以视图为格式)  使用@ResponseBody注解让方法返回值作为响应内容是什么意思         在使用 @Controller 注解标记的类中,默认情况下,方法的返回值会被解析为一个视图名称,并寻找与该名称匹配的视图进行渲染。这意味着返回的结果会被解析为一个 HTML 页面或者模板引擎所需的数据。          但是有时候需要将方法的返回值直接作为响应的主体内容,而不是解析为视图。为了实现这个目的,我们可以在方法上使用 @ResponseBody 注解。    @ResponseBody 注解表示方法的返回值应该直接写入 HTTP 响应体中,而不是被解析为视图。它告诉 Spring MVC 框架将方法的返回值序列化为特定格式(如 JSON、XML 等)并作为响应的主体内容返回给客户端。  下面是一个使用 @Controller 和 @ResponseBody 的示例:  @Controller @RequestMapping("/hello") public class HelloController {     @GetMapping     @ResponseBody     public String sayHello() {         return "Hello, World!";     } }         当客户端发起 /hello 的 GET 请求时,sayHello() 方法会返回一个字符串 "Hello, World!"。因为在方法上使用了 @ResponseBody 注解,返回值不会被解析为视图,而是直接作为响应的主体内容返回给客户端。  举例说明         现在假设有一个简单的订单系统,其中有一个功能是获取订单信息。我们来看如何使用 @RestController 和 @Controller 分别实现同一个功能:  @RestController @RestController @RequestMapping("/orders") public class OrderController {     @GetMapping("/{id}")     public Order getOrderById(@PathVariable int id) {         // 从数据库中获取订单信息         Order order = orderService.getOrderById(id);         return order;     } }         使用 @RestController 注解标记类,并在方法上使用 @GetMapping 注解定义了一个 GET 请求的处理方法。方法的返回值是 Order 类型的对象,它将会直接序列化为 JSON 格式的数据,并作为 HTTP 响应的主体内容返回给客户端。  @Controller @Controller @RequestMapping("/orders") public class OrderController {     @GetMapping("/{id}")     @ResponseBody     public ModelAndView getOrderById(@PathVariable int id) {         // 从数据库中获取订单信息         Order order = orderService.getOrderById(id);         ModelAndView modelAndView = new ModelAndView("order-details");         modelAndView.addObject("order", order);         return modelAndView;     } }         使用 @Controller 注解标记类,并在方法上使用 @GetMapping 注解定义了一个 GET 请求的处理方法。方法的返回值是 ModelAndView 类型的对象,它将包含要渲染的视图名称和需要传递给视图的数据。在方法上使用 @ResponseBody 注解,表示方法的返回值应该作为响应的主体内容,而不是解析为视图。          通俗一点说就是——有时候并不需要返回视图,只需要一组数据,这样在方法加上一个@ResponseBody,就可以让返回的格式转换为数据格式  什么时候需要返回的是视图,什么时候需要返回数据? 当设计 RESTful API 时,一般的原则是: 如果客户端希望获取数据(例如 JSON、XML),则返回数据。 如果客户端希望展示数据(例如 HTML 页面),则返回视图。 下面是一些示例情况: 当你在开发一个单页应用的后端接口时,前端通常会通过 Ajax 请求获取数据(例如 JSON),然后使用 JavaScript 动态更新页面。在这种情况下,你应该返回数据(例如使用 @ResponseBody 注解)。 当你需要为前端渲染 HTML 页面时,需要返回视图。视图可以包含动态生成的数据,但最终会经过服务器端模板引擎的处理,形成最终的 HTML 页面。 再以实例说明,更通俗易懂的理解: 需要返回视图的实例: 假设你正在开发一个博客应用的后端接口。有一个页面需要显示所有文章的列表,并且希望以 HTML 形式展示。在这种情况下,你可以设计一个 GET 请求的接口 /api/articles,返回一个包含所有文章数据的视图,让前端直接展示这个页面。这里需要返回视图而不是仅返回数据,因为需要服务端渲染整个 HTML 页面。  只需要返回数据的实例: 假设正在开发一个电子商务网站,前端使用 React 或 Vue.js 等框架构建。在购物车页面上,需要获取当前用户的购物车数据以便展示。在这种情况下,你可以设计一个 GET 请求的接口 /api/cart,返回一个 JSON 对象,包含当前用户的购物车数据。这里只需要返回数据而不是整个 HTML 页面,因为前端通过 JavaScript 来处理和展示数据。  总结 @RestController 是 @Controller 和 @ResponseBody 的组合注解,用于创建 RESTful 风格的 API。 @RestController 返回的数据会直接作为响应的主体内容(JSON 或 XML),不进行页面跳转或视图解析。 @Controller 用于传统的 MVC 架构,负责处理请求并返回视图作为响应。 @Controller 方法通常需要配合 @ResponseBody 注解,才能将返回值作为响应的主体内容。 传统的springMVC一般就需要直接返回视图,而现在新兴的前端技术vue在项目中为前后端分离的架构,前端框架负责处理数据和渲染页面,而后端 API 则负责提供数据即可,所以对返回视图的要求也就比较少了 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/miles067/article/details/132567377 
  • [技术干货] 【SpringBoot】自定义工具类实现Excel数据新建表存入MySQL数据库-转载
     前言 本文主要介绍使用EasyExcel读取Excel内数据并转换为csv格式数据(String字符串),然后实现字符串分割,分割出属性名和属性值建表插入MySQL数据库中。  一、EasyExcel转CSV 使用EasyExcel读取Excel文件,转换为csv数据,也就是转化为一个字符串。  工具类:  /**  * @Version: 1.0.0  * @Author: Dragon_王  * @ClassName: ExcelUtils  * @Description: Excel相关工具类  * @Date: 2024/3/9 11:24  */  import cn.hutool.core.collection.CollUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.support.ExcelTypeEnum; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.web.multipart.MultipartFile;  import java.io.IOException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors;  /**  * Excel 相关工具类  */ @Slf4j public class ExcelUtils {      /**      * excel 转 csv      *      * @param multipartFile      * @return      */     public static String excelToCsv(MultipartFile multipartFile) {         // 读取数据         List<Map<Integer, String>> list = null;         try {             list = EasyExcel.read(multipartFile.getInputStream()).excelType(ExcelTypeEnum.XLSX).sheet().headRowNumber(0).doReadSync();         } catch (IOException e) {             log.error("表格处理错误", e);         }         if (CollUtil.isEmpty(list)) {             return "";         }         // 转换为 csv         StringBuilder stringBuilder = new StringBuilder();         // 读取表头         LinkedHashMap<Integer, String> headerMap = (LinkedHashMap) list.get(0);         List<String> headerList = headerMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());         stringBuilder.append(StringUtils.join(headerList, ",")).append("\n");         // 读取数据         for (int i = 1; i < list.size(); i++) {             LinkedHashMap<Integer, String> dataMap = (LinkedHashMap) list.get(i);             List<String> dataList = dataMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList());             stringBuilder.append(StringUtils.join(dataList, ",")).append("\n");         }         return stringBuilder.toString();     } } 实际运用中,只需要如下调用:  //传入的是Excel文件,不是路径哦 ExcelUtils.excelToCsv(Excel文件); Excel文件格式如下:  读取的数据如下格式(这里我用加号拼接更清晰,实际上就是一个包含换行符的字符串,并不包含+号):                  "日期,阅读量\n" +                 "3,253\n" +                 "4,408\n" +                 "5,363\n" +                 "6,955\n" +                 "7,496\n" +                 "8,1310\n" +                 "9,748"; 二、分割建表入库 将获取的csv数据,其实这里就是一个String字符串,记住现在是字符串不是数组。 首先我们分析一下,如何从字符串从分割出属性名(日期、阅读量)以及插入表中的每行属性值(3 253;4 408…): 找出第一个换行符(\n)的位置index,然后从第一位切割到index,这时候就得到属性名的字符串。再以英文逗号为分割符进行分割,得到属性名数组。 从index切割到字符串最后的位置就是全部属性值,那么如何分割得到每行的属性值呢?同样以换行符为分割符进行分割得到每行属性值的数组。(这里注意一下,数组中的每个元素是一个包含一行值的字符串,如:“3,253”) 分割得到的属性值数组内的每个元素再以英文逗号为分割符进行分割得到每行属性值,如"3"和"253" 最后根据属性名和属性值动态构建sql语句进行创建表,插入值的操作。 分割csv数据并调用自定义建表和插入函数:          //定义表名         String tableName = "test";         //获取第一个换行符的索引         int index = csvData.indexOf("\n");         //分割出属性名字符串,如"日期,阅读量"         String colum = csvData.substring(0,index);         //得到属性名数组,如{"日期","阅读量"}         String[] colums = colum.split(",");         //得到全部属性值字符串,如"3,253\n4,408\n5,363\n6,955\n7,496\n8,1310\n9,748"         String data = csvData.substring(index).trim();         //得到全部属性值数组,如:{"3,253","4,408","5,363""6,955".......}         String[] split_data = data.split("\n");         //调用建表         tableCreationUtils.createTable(tableName,colums);         //调用插入         tableCreationUtils.Dynamicinsert(tableName,colums,split_data); 动态构造建表sql和插入sql工具类:  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component;  import javax.sql.DataSource; import java.sql.SQLException;  /**  * 构建生成表sql  *  * @version 1.0.0  * @Author: dragon_王  * @Date: 2024/3/11 20:45:16  */ @Component public class TableCreationUtils {      private final JdbcTemplate jdbcTemplate;     @Autowired     public TableCreationUtils(DataSource dataSource){         this.jdbcTemplate = new JdbcTemplate(dataSource);     }      /**      * 动态构建创建表的sql语句并执行      *      * @param tableName 表名字符串      * @param columnNames 属性名数组      * @return: void      * @Date: 2024-03-13 23:23:36      */     public  void createTable(String tableName, String[] columnNames) {         String sql = "CREATE TABLE " + tableName + " (";         for (int i = 0; i < columnNames.length; i++) {             sql += columnNames[i] + " VARCHAR(255)";             if (i < columnNames.length - 1) {                 sql += ", ";             }         }         sql += ");";         jdbcTemplate.execute(sql);     }      /**      * 动态构建插入sql语句并执行      *      * @param tableName 表名字符串      * @param columnName 属性名数组,如{"日期","阅读"}      * @param rowData 属性值数组,如{"3,253","4,408","5,363""6,955".......}      * @return: void      * @Date: 2024-03-13 23:24:09      */     public void Dynamicinsert(String tableName, String[] columnName,String[] rowData) throws SQLException {         //属性值为空就抛出异常         if (rowData == null || rowData.length == 0) {             throw new IllegalArgumentException("Row data must not be null or empty");         }          for (int i = 0;i < rowData.length;i++){             //传进来的是所有属性值数组,如:{"3,253","4,408","5,363""6,955".......}             //所以以将每个元素取出以英文逗号分割,得到插入的每行元素如["3","253"]             String[] row = rowData[i].split(",");             // 构建占位符             StringBuilder columnNames = new StringBuilder();             StringBuilder placeholders = new StringBuilder();             for (int j = 0; j < row.length; j++) {                 if (j > 0) {                     columnNames.append(", ");                     placeholders.append(", ");                 }                 columnNames.append(columnName[j]);                 placeholders.append("?"); // 使用?作为PreparedStatement的占位符             }              // 构建完整的SQL语句             String sql = "INSERT INTO " + tableName + " (" + columnNames + ") VALUES (" + placeholders + ")";              // 使用JdbcTemplate执行插入操作,如第一次循环:insert into tablename (日期,阅读) values (?,?)             //row:["3","253"]             jdbcTemplate.update(sql,row);         }     }  } 上面代码有以上面EXcel数据为例子的详细讲解,我就不再赘诉,很简单的思路。  总结 以上就是使用EasyExcel读取Excel内数据并转换为csv格式数据(String字符串),然后实现字符串分割,分割出属性名和属性值建表插入MySQL数据库中的详细讲解。  文章知识点与官方知识档案匹配,可进一步学习相关知识 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/weixin_62951900/article/details/136694021 
  • [技术干货] java+saas模式医院云HIS系统源码Java+Spring+MySQL + MyCat融合BS版电子病历系统,支持电子病历四级
    java+saas模式医院云HIS系统源码Java+Spring+MySQL + MyCat融合BS版电子病历系统,支持电子病历四级云HIS系统是一款满足基层医院各类业务需要的健康云产品。该产品能帮助基层医院完成日常各类业务,提供病患预约挂号支持、病患问诊、电子病历、开药发药、会员管理、统计查询、医生工作站和护士工作站等一系列常规功能,还能与公卫、PACS等各类外部系统融合,实现多层机构之间的融合管理。云HIS系统源码框架:技术框架(1)总体框架:SaaS应用,全浏览器访问前后端分离,多服务协同服务可拆分,功能易扩展(2)技术细节:前端:Angular+Nginx后台:Java+Spring,SpringBoot,SpringMVC,SpringSecurity,MyBatisPlus,等数据库:MySQL + MyCat缓存:Redis+J2Cache消息队列:RabbitMQ任务调度中心:XxlJob接口技术:RESTful API + WebSocket + WebService报表组件:itext + POI + ureport2数据库监控组件:Canal云HIS系统电子 病历系统简介门诊电子病历:门诊电子病历自动补充门诊信息、病历模板可定制。住院电子病历:住院病历及住院病程管理;住院病历存为模板、也可通过模板快速新建病历;住院护理记录管理;住院护理记录可存为模板、也可通过模板快速建护理记录。病历质控:医生个人质控,查看历史已归档患者的缺陷信息;查看医生提交的延期申请情况;查看已存在缺陷、缺陷申诉提交操作、以及查看缺陷申诉情况;进行病历召回操作,以及查看病历召回状态;查看个人电子病历质控评分情况。病历控制:质控人员查看和审核医生发起的病历召回申请。缺陷监控:质控人员查看一段时间内不同类型缺陷的数量,分为在院、未归档、已归档三个状态;质控人员查看和审核医生发起的病历延期书写申请;质控人员查看和审核医生发起的缺陷申诉请求;质控人员查看和审核患者病历书写质量评分结果;质控人员查看各个科室在一段时间内病历的质控等级情况。质控设置:质控规则、评分规则、评级规则等的设置。患者列表:患者信息,自动判别患者当前就诊状态(待诊、接诊、已诊);系统自动识别已登记过的患者,自动补充基础信息;支持快速选择未挂号患者接诊;云HIS电子病历模板板块合并预览:该功能仅在住院病程中使用,目的是将某个患者的住院病程中所有的病历聚合在一起形成一张大的病历并能够打印,合并预览后的病历仅支持打印功能,不支持保存以及控件编辑功能。普通病历:在该模式下,可以对单个患者的病历数据进行新建、编辑、预览、保存,以及打印的操作,是医院比较常用和重要的功能模块,暂不支持在同一窗口下打开多张病历的相关操作。自定义模板:模板编辑:医疗机构涉及的病历模板均可以按需设计制作,可通过运维运营分系统模板管理子模块病历模板中的‘编辑’功能实现该操作。存为模板:医师将当前病历通过存为模板的方式设置为医师的个人病历模板,以便相同患者的同一病历后续能够得到复用。数据同步:针对同一个患者不同病历之间的数据共享而存在的,同步功能主要是针对病历中的6类控件(提纲元素、宏元素、日期元素、选择元素、单选元素、复选元素)数据进行同步。病历打印:常规打印、PDF打印辅助输入:辅助输入提供当前日期、当前时间、医师签名等便捷操作。页面布局:调节纸张方向、大小;设置边距、打印方式。导出PDF:将当前任意病历(普通病历、自定义个人模板、合并预览、历史病历)直接导出成PDF下载到本地。云HIS系统优势(1)客户/用户角度无需安装,登录即用多终端同步,轻松应对工作环境转换系统使用简单、易上手,信息展示主次分明、重点突出极致降低用户操作负担:关联功能集中、减少跳转,键盘快捷操作,自由批量执行,自动模糊搜索全方位信息保护,确保信息安全客户方零运维(2)开发/运维角度采用主流成熟技术,软件结构简洁、代码规范易阅读支持多样化灵活配置,提取大量公共参数,无需修改代码即可满足不同客户需求服务组织合理,功能高内聚,服务间通信简练功能易扩展,轻松应对个性化定制需求专业系统运维运营工具,助力快速、准确运维,支持规模化运营
  • [技术干货] spring boot3登录开发-微信小程序用户登录设计与实现
     写在前面 本文介绍了springboot开发微信小程序后端服务中,用户登录功能的设计与实现,坚持看完相信对你有帮助。  同时欢迎订阅springboot系列专栏,持续分享spring boot的使用经验。  登录流程 如图: 图片请查看原文链接(最下面)。  这是微信官方文档中微信小程序登录的流程时序图,我在图中红色序号标注的五步就是完整的微信小程序登录流程。  流程解析 小程序端通过wx.login()获取用户登录凭证code。 小程序将code发送到服务端的登录接口。 服务端的登录接口使用该code、小程序的AppID和AppSecret向微信服务器发起请求,获取用户的openid。 服务端拿到openid后,可以根据业务需求生成用户令牌(Token),通常包括用户信息、权限等,并返回给小程序。 小程序在本地缓存该用户令牌。 后续小程序发起请求时,在请求头中携带该用户令牌(Token)。 服务端接收到请求时,验证用户令牌的有效性,确保用户是经过认证和授权的。 具体实现 登录接口设计思路  接收小程序端传递的code参数。 使用code、小程序的AppID和AppSecret向微信服务器发起请求,获取用户的openid。 在数据库用户表中查询是否存在该openid。 如果存在该openid,则使用查询到的用户信息生成令牌(Token)。 如果不存在该openid,则进行用户注册操作,将新用户信息插入数据库用户表,并生成令牌(Token)。 返回生成的令牌(Token)给小程序端。 相关代码 说明 下面代码基于jdk17,发请求用的时jdk自带的http工具类,如果你的jdk版本低于11,可以使用okhttp依赖来发请求,然后jwt相关工具类代码在我本专栏的其他文章里面,用到的ORM框架为mybatis-plus整合相关代码也在本专栏。  服务端 登录dto  @Data public class UserLoginDTO {     @NotBlank(message = "code不能为空")     private String code;       private Map<String,Object> userInfo;//用户完善信息   } 登录返回vo  @Data @Builder public class UserLoginVO implements Serializable {     private Long id; //用户id       private String openid;//用户在小程序唯一标识       private String token;//用户登录凭证     } 获取openid方法  使用的是jdk17自带的HttpClient,也可以使用okhttp,或者糊涂工具包里面的。   private String getOpenid(String code) {         HttpClient httpClient = HttpClient.newHttpClient();         // 构建请求参数字符串         String params = String.format("appid=%s&secret=%s&js_code=%s&grant_type=%s",                 URLEncoder.encode(wxMiniConfig.getAppId(), StandardCharsets.UTF_8),                 URLEncoder.encode(wxMiniConfig.getAppSecret(), StandardCharsets.UTF_8),                 URLEncoder.encode(code, StandardCharsets.UTF_8),                 URLEncoder.encode("authorization_code", StandardCharsets.UTF_8));           // 创建 GET 请求         String url = "https://api.weixin.qq.com/sns/jscode2session?" + params;         HttpRequest httpRequest = HttpRequest.newBuilder()                 .uri(URI.create(url))                 .build();           try {             // 发送 GET 请求             HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());               // 获取响应结果             int statusCode = httpResponse.statusCode();             String responseBody = httpResponse.body();             JSONObject responseJson = JSONObject.parseObject(responseBody);             String openid = responseJson.getString("openid");               // 处理响应结果             System.out.println("状态代码: " + statusCode);             System.out.println("响应正文: " + responseBody);             return  openid;         } catch (Exception e) {            log.error("发送请求时出错: {}", e.getMessage());            return null;         }     }  登录逻辑代码  在判断出是新用户时,进行注册操作还需要将请求携带过来的userInfo完善信息,用户名,头像等一同封装进User对象再插入到数据库这里我就省略了      @Override     public UserLoginVO login(UserLoginDTO userLoginDTO) {         //获取openid         String openid = getOpenid(userLoginDTO.getCode());         if(StringUtils.isBlank(openid)){             throw new GeneralBusinessException(ResultEnum.USER_OPENID_ERROR); // openid获取失败         }           User user = new LambdaQueryChainWrapper<>(userMapper).eq(User::getOpenid, openid).one();           if(Objects.isNull(user)){             // 用户不存在则注册             user = new User();             //获取完善信息,用户呢称、头像url             userLoginDTO.getUserInfo             //封装信息。。。。。。             //。。。。。。。。。。                          user.setOpenid(openid);               // 注册用户             if(!this.save(user)){                 throw new GeneralBusinessException(ResultEnum.USER_REGISTER_FAIL); // 用户注册失败             }         }           return UserLoginVO.builder()                 .id(user.getUserId())                 .openid(user.getOpenid())                 .token(jwtUtils.generateToken(Map.of("userId", user.getUserId()), "user"))                 .build();     }  小程序端  如图点击登录按钮会进行登录(注册)  wxml:   <button bindtap="handleLogin">登录</button> js:  // 点击登录按钮时触发的方法 handleLogin: function() {   // 调用微信登录接口获取 code   wx.login({     success: res => {       const code = res.code;       if (code) {         // 调用微信登录接口获取用户信息         wx.getUserProfile({           desc: '用于完善会员资料',           success: res => {             const userInfo = res.userInfo;               // 将 code 和用户信息发送到后端服务器进行登录验证             wx.request({               url: 'http://localhost:8080/user/login',               method: 'POST',               data: {                 code: code,                 userInfo: userInfo               },               success: res => {                 const { token } = res.data; // 假设后端返回包含 token 的数据                 if (token) {                   // 登录成功,保存 token到本地存储                   wx.setStorageSync('token', token);                     // 跳转到主页或其他页面                   wx.navigateTo({                     url: '/pages/home/home'                   });                 } else {                   // 登录失败,显示提示信息                   wx.showToast({                     title: '登录失败,请重试',                     icon: 'none'                   });                 }               },               fail: err => {                 console.error('登录请求失败', err);               }             });           },           fail: err => {             console.error('获取用户信息失败', err);           }         });       } else {         console.error('获取登录凭证失败', res.errMsg);       }     },     fail: err => {       console.error('调用登录接口失败', err);     }   }); }  补充:实际开发中不会直接使用wx.request进行登录请求,一般都会进行封装,这里作为演示我就直接使用了,实际发送的post请求,请求体会携带code,和用户昵称、头像路径等信息。  写在最后 本文完整的介绍了springboot开发微信小程序服务端中用户登录功能的设计思路,希望对你有帮助。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/qq_62262918/article/details/136660347 
  • [技术干货] Spring Boot 3核心技术与最佳实践-转载
     引言 Spring Boot作为一个轻量级的Java开发框架,旨在简化Spring应用程序的搭建和开发过程。随着Spring Boot 3的发布,我们将探讨其核心技术和最佳实践,以帮助开发者更好地理解并利用这一强大框架。  1. 自动配置(Auto-Configuration) Spring Boot 3继续沿用自动配置的理念,通过对类路径下的特定条件进行判断,自动配置应用程序。这种方式可以大大减少开发者的配置工作量,并提高应用程序的可移植性。以下是一个简单的示例:  @SpringBootApplication public class MyApplication {     public static void main(String[] args) {         SpringApplication.run(MyApplication.class, args);     } } 在这个示例中,@SpringBootApplication注解隐式地启用了自动配置,包括组件扫描、自动配置Spring MVC等功能。  2. 独立运行(Standalone Application) Spring Boot 3支持将应用程序打包成独立的可执行JAR文件,使得应用程序的部署和运行变得更加简单。只需执行如下命令即可启动应用程序:  java -jar myapplication.jar 1 这种方式不仅方便了部署,还能够避免对外部服务器的依赖。  3. 内嵌容器(Embedded Containers) Spring Boot 3集成了多种内嵌容器,包括Tomcat、Jetty和Undertow等,开发者可以根据自己的需求选择合适的容器。以下是一个使用Tomcat作为内嵌容器的示例:  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication public class MyApplication {     public static void main(String[] args) {         SpringApplication.run(MyApplication.class, args);     } } 4. 外部化配置(Externalized Configuration) Spring Boot 3支持通过属性文件、YAML文件、环境变量等多种方式进行外部化配置。这样做的好处在于,可以将配置与代码分离,便于管理和维护。以下是一个简单的application.properties文件示例:  server.port=8080 spring.datasource.url=jdbc:mysql://localhost:3306/mydb spring.datasource.username=root spring.datasource.password=secret 5. 监控与管理(Monitoring and Management) Spring Boot 3提供了丰富的监控和管理功能,包括健康检查、指标收集、应用信息展示等。通过整合Actuator模块,可以轻松地获取关于应用程序运行状态的详细信息。  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 6. 数据访问与集成(Data Access and Integration) Spring Boot 3提供了对多种数据源的无缝集成,包括关系型数据库、NoSQL数据库以及消息队列等。例如,通过spring-boot-starter-data-jpa可以轻松地集成JPA和Hibernate,并实现对关系型数据库的访问。  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> 7. 测试(Testing) Spring Boot 3鼓励开发者编写各种类型的测试,包括单元测试、集成测试和端到端测试。通过整合JUnit、Mockito等测试框架,可以有效地保证应用程序的质量和稳定性。  @SpringBootTest class MyServiceTest {      @Autowired     private MyService myService;      @Test     void testSomething() {         // 进行测试     } } 8. 安全(Security) Spring Boot 3提供了强大的安全功能,包括身份认证、授权、加密解密等。通过整合Spring Security模块,可以轻松地为应用程序添加各种安全特性。  @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {      @Override     protected void configure(HttpSecurity http) throws Exception {         http             .authorizeRequests()                 .antMatchers("/public/**").permitAll()                 .anyRequest().authenticated()                 .and()             .formLogin()                 .loginPage("/login")                 .permitAll()                 .and()             .logout()                 .permitAll();     } } 9. 异步处理(Asynchronous Processing) Spring Boot 3支持异步处理,通过使用@Async注解和CompletableFuture等方式,可以提高应用程序的并发能力和响应速度。  @Service public class MyService {      @Async     public CompletableFuture<String> doSomething() {         // 执行异步操作     } } 总结 本文介绍了Spring Boot 3的核心技术和最佳实践,包括自动配置、独立运行、内嵌容器、外部化配置、监控与管理、数据访问与集成、测试、安全以及异步处理等方面。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/qq_44273429/article/details/136536450 
  • [技术干货] 【SpringBoot系列】Spring Boot 3核心技术与最佳实践-转载
     引言 Spring Boot 3 是对 Spring Boot 框架的一个重要更新版本,它延续了 Spring Boot 简化 Spring 应用程序开发的宗旨,进一步提升了开发者体验和应用程序性能。  1. 自动配置(Auto-Configuration) Spring Boot通过自动配置大大简化了应用程序的搭建和配置过程。  它根据应用程序的依赖关系和类路径上的内容来推断和提供Spring应用程序的默认行为。  通过简单的添加依赖,开发者可以轻松地集成数据库、消息队列、安全性等常见功能,而无需手动配置繁琐的XML或Java代码。  假设你的Spring Boot应用程序使用了Spring Data JPA和MySQL数据库,你只需在pom.xml中添加相应的依赖:  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId> </dependency>  然后,在application.properties中添加数据库连接信息:  spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase spring.datasource.username=root spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=update Spring Boot将自动配置数据源、EntityManagerFactory和事务管理器,无需额外的配置。  2. 独立运行(Standalone Application) Spring Boot支持将应用程序打包成独立的可执行JAR文件,这意味着应用程序不再依赖于外部的应用服务器。开发者可以通过命令行或脚本来启动应用程序,从而简化了部署和管理的流程,并且可以更方便地在不同环境中进行部署和迁移。  通过使用Spring Boot Maven插件,你可以将应用程序打包成一个可执行的JAR文件。只需在pom.xml中添加插件配置:  <build>     <plugins>         <plugin>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-maven-plugin</artifactId>         </plugin>     </plugins> </build> 然后在命令行中运行mvn package,即可生成可执行的JAR文件。使用java -jar命令即可启动应用程序。  3.内嵌容器(Embedded Containers) Spring Boot支持内嵌式容器,如Tomcat、Jetty和Undertow,可以在单个应用程序中同时包含Web服务器和应用程序代码。这种内嵌式容器的设计不仅简化了应用程序的部署和配置,还提高了应用程序的性能和可移植性。  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-web</artifactId> </dependency> 4.外部化配置(Externalized Configuration) Spring Boot鼓励将应用程序的配置与代码分离,从而使得配置更加灵活和易于管理。可以使用.properties、.yml文件或环境变量来配置应用程序,甚至可以在不同环境中使用不同的配置文件,而无需修改代码。  spring:   datasource:     url: jdbc:mysql://localhost:3306/mydatabase     username: root     password: password 5. 监控与管理(Monitoring and Management) Spring Boot提供了Actuator模块,用于监控和管理应用程序。通过暴露一系列的端点(endpoints),可以查看应用程序的运行状况、性能指标、日志等信息,从而及时发现和解决问题,保证应用程序的健康运行。  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 6. 数据访问与集成(Data Access and Integration) Spring Boot提供了丰富的数据访问和集成支持,包括Spring Data、Spring JDBC、Spring ORM等。可以轻松地与各种数据源进行集成,如关系型数据库、NoSQL数据库、消息队列等,从而实现数据的持久化和交互。  假设使用Spring Data JPA来访问数据库,你可以定义一个简单的Repository接口:  @Repository public interface UserRepository extends JpaRepository<User, Long> {     User findByUsername(String username); } Spring Boot将自动创建该接口的实现,并且可以通过自动配置的数据源访问数据库。  7. 测试(Testing) Spring Boot鼓励编写良好的测试来保证应用程序的质量和稳定性。它提供了多种测试支持,包括单元测试、集成测试、端到端测试等。可以使用JUnit、Mockito等测试框架来编写和运行测试,并且可以通过Spring Boot Test模块来简化测试环境的配置和管理。  @SpringBootTest class MyControllerTests {      @Autowired     private MockMvc mockMvc;      @Test     void testController() throws Exception {         this.mockMvc.perform(get("/")).andExpect(status().isOk());     } } 8. 安全(Security) Spring Boot提供了强大的安全框架,可以用于保护应用程序的资源不被未授权的访问。可以轻松地实现身份认证、权限控制、加密解密等功能,并且可以通过配置文件或注解来定制安全策略,以满足应用程序的特定需求。  使用Spring Security可以保护应用程序资源。例如,可以通过以下方式配置基本身份验证:  @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {      @Override     protected void configure(HttpSecurity http) throws Exception {         http.authorizeRequests()             .antMatchers("/admin/**").hasRole("ADMIN")             .antMatchers("/user/**").hasRole("USER")             .anyRequest().authenticated()             .and()             .httpBasic();     }      @Autowired     public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {         auth.inMemoryAuthentication()             .withUser("user").password("{noop}password").roles("USER")             .and()             .withUser("admin").password("{noop}admin").roles("ADMIN");     } }  9. 异步处理(Asynchronous Processing) Spring Boot支持异步处理,包括异步方法调用、异步消息处理等。通过使用Spring的异步特性,可以提高应用程序的并发性和响应性,从而提升用户体验和系统性能。  假设需要处理大量的IO操作,可以使用Spring Boot提供的异步特性来提高性能:  @Service public class MyService {      @Async     public CompletableFuture<String> doSomethingAsync() {         // 执行异步任务         return CompletableFuture.completedFuture("Result");     } } 通过在方法上添加@Async注解,Spring Boot将在后台启动一个线程池来执行异步任务。  总结 本文介绍了Spring Boot 3的核心技术和最佳实践,通过本文的介绍更深入的了解SpringBoot3的相关特性和实践。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/jinxinxin1314/article/details/136548925 
  • [技术干货] 【SpringBoot系列】Spring Boot 3核心技术与最佳实践-转载
     引言 Spring Boot 3 是对 Spring Boot 框架的一个重要更新版本,它延续了 Spring Boot 简化 Spring 应用程序开发的宗旨,进一步提升了开发者体验和应用程序性能。  1. 自动配置(Auto-Configuration) Spring Boot通过自动配置大大简化了应用程序的搭建和配置过程。  它根据应用程序的依赖关系和类路径上的内容来推断和提供Spring应用程序的默认行为。  通过简单的添加依赖,开发者可以轻松地集成数据库、消息队列、安全性等常见功能,而无需手动配置繁琐的XML或Java代码。  假设你的Spring Boot应用程序使用了Spring Data JPA和MySQL数据库,你只需在pom.xml中添加相应的依赖:  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId> </dependency> 然后,在application.properties中添加数据库连接信息:  spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase spring.datasource.username=root spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=update Spring Boot将自动配置数据源、EntityManagerFactory和事务管理器,无需额外的配置。  2. 独立运行(Standalone Application) Spring Boot支持将应用程序打包成独立的可执行JAR文件,这意味着应用程序不再依赖于外部的应用服务器。开发者可以通过命令行或脚本来启动应用程序,从而简化了部署和管理的流程,并且可以更方便地在不同环境中进行部署和迁移。  通过使用Spring Boot Maven插件,你可以将应用程序打包成一个可执行的JAR文件。只需在pom.xml中添加插件配置:  <build>     <plugins>         <plugin>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-maven-plugin</artifactId>         </plugin>     </plugins> </build> 然后在命令行中运行mvn package,即可生成可执行的JAR文件。使用java -jar命令即可启动应用程序。  3.内嵌容器(Embedded Containers) Spring Boot支持内嵌式容器,如Tomcat、Jetty和Undertow,可以在单个应用程序中同时包含Web服务器和应用程序代码。这种内嵌式容器的设计不仅简化了应用程序的部署和配置,还提高了应用程序的性能和可移植性。  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-web</artifactId> </dependency> 4.外部化配置(Externalized Configuration) Spring Boot鼓励将应用程序的配置与代码分离,从而使得配置更加灵活和易于管理。可以使用.properties、.yml文件或环境变量来配置应用程序,甚至可以在不同环境中使用不同的配置文件,而无需修改代码。  spring:   datasource:     url: jdbc:mysql://localhost:3306/mydatabase     username: root     password: password 5. 监控与管理(Monitoring and Management) Spring Boot提供了Actuator模块,用于监控和管理应用程序。通过暴露一系列的端点(endpoints),可以查看应用程序的运行状况、性能指标、日志等信息,从而及时发现和解决问题,保证应用程序的健康运行。  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 6. 数据访问与集成(Data Access and Integration) Spring Boot提供了丰富的数据访问和集成支持,包括Spring Data、Spring JDBC、Spring ORM等。可以轻松地与各种数据源进行集成,如关系型数据库、NoSQL数据库、消息队列等,从而实现数据的持久化和交互。  假设使用Spring Data JPA来访问数据库,你可以定义一个简单的Repository接口:  @Repository public interface UserRepository extends JpaRepository<User, Long> {     User findByUsername(String username); } Spring Boot将自动创建该接口的实现,并且可以通过自动配置的数据源访问数据库。  7. 测试(Testing) Spring Boot鼓励编写良好的测试来保证应用程序的质量和稳定性。它提供了多种测试支持,包括单元测试、集成测试、端到端测试等。可以使用JUnit、Mockito等测试框架来编写和运行测试,并且可以通过Spring Boot Test模块来简化测试环境的配置和管理。  @SpringBootTest class MyControllerTests {      @Autowired     private MockMvc mockMvc;      @Test     void testController() throws Exception {         this.mockMvc.perform(get("/")).andExpect(status().isOk());     } } 8. 安全(Security) Spring Boot提供了强大的安全框架,可以用于保护应用程序的资源不被未授权的访问。可以轻松地实现身份认证、权限控制、加密解密等功能,并且可以通过配置文件或注解来定制安全策略,以满足应用程序的特定需求。  使用Spring Security可以保护应用程序资源。例如,可以通过以下方式配置基本身份验证:  @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {      @Override     protected void configure(HttpSecurity http) throws Exception {         http.authorizeRequests()             .antMatchers("/admin/**").hasRole("ADMIN")             .antMatchers("/user/**").hasRole("USER")             .anyRequest().authenticated()             .and()             .httpBasic();     }      @Autowired     public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {         auth.inMemoryAuthentication()             .withUser("user").password("{noop}password").roles("USER")             .and()             .withUser("admin").password("{noop}admin").roles("ADMIN");     } } 9. 异步处理(Asynchronous Processing) Spring Boot支持异步处理,包括异步方法调用、异步消息处理等。通过使用Spring的异步特性,可以提高应用程序的并发性和响应性,从而提升用户体验和系统性能。  假设需要处理大量的IO操作,可以使用Spring Boot提供的异步特性来提高性能:  @Service public class MyService {      @Async     public CompletableFuture<String> doSomethingAsync() {         // 执行异步任务         return CompletableFuture.completedFuture("Result");     } } 通过在方法上添加@Async注解,Spring Boot将在后台启动一个线程池来执行异步任务。  总结 本文介绍了Spring Boot 3的核心技术和最佳实践,通过本文的介绍更深入的了解SpringBoot3的相关特性和实践。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/jinxinxin1314/article/details/136548925 
  • [技术干货] 【SpringBoot】mybaitsPlus的多数据源配置-转载
     mybatisPlus的多数据源配置 适用于多种场景:纯粹多库、   读写分离、    一主多从、   混合模式等  目前我们就来模拟一个纯粹多库的一个场景,其他场景类似  场景说明: 我们创建两个库,分别为:  mybatis_plus(以前的库不动)与mybatis_plus_1  (新建),将  mybatis_plus库的product表移动到mybatis_plus_1库,这样每个库一张表,通过一个测试用例 分别获取用户数据与商品数据,如果获取到说明多库模拟成功  1、创建数据库及表 创建数据库mybatis_plus_1和表product  CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; use `mybatis_plus_1`; CREATE TABLE product ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称 ', price INT(11) DEFAULT 0 COMMENT '价格 ', version INT(11) DEFAULT 0 COMMENT '乐观锁版本号 ', PRIMARY KEY (id)); 添加测试数据  INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本 ', 100);  删除mybatis_plus库product表  use mybatis_plus; DROP TABLE IF EXISTS product;  2、引入依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.0</version> </dependency> 3、配置多数据源 说明:注释掉之前的数据库连接,添加新配置  spring: # 配置数据源信息 datasource: dynamic: # 设置默认的数据源或者数据源组 ,默认值即为master primary: master # 严格匹配数据源 ,默认false.true未匹配到指定数据源时抛异常 ,false使用默认数据源 strict: false datasource: master: url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf- 8&useSSL=false driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 slave_1: url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf- 8&useSSL=false driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456  4、创建用户service @DS("master") //指定所操作的数据源 @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements  UserService { } 5、创建商品service @DS("slave_1") @Service public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements  ProductService { } 6、测试 @Autowired private UserService userService; @Autowired private ProductService  productService;   @Test public void testDynamicDataSource(){ System.out.println(userService.getById(1L)); System.out.println(productService.getById(1L)); } 结果:  1、都能顺利获取对象,则测试成功  2、如果我们实现读写分离,将写操作方法加上主库数据源,读操作方法加上从库数据源,自动切换,是不是就能实现读写分离?  注意事项 可以设置默认的数据源或者数据源组 ,默认值即为master # strict:严格匹配数据源 ,默认false。true未匹配到指定数据源时抛异常 ,false使用默认数据源 如下, 二者都没有使用@DS("数据源名称"),就是没有数据源,当为false时,就是会默认使用master,所以报错是mybatis_plus.t_product不存在,而不是数据源错误。 当为true时,就严格@DS中的数据源,没有就报错 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/m0_62645012/article/details/136418624 
  • [技术干货] 【Spring Cloud】实现微服务调用的负载均衡-转载
     什么是负载均衡 通俗的讲,负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。  根据负载均衡发生位置的不同,一般分为服务端负载均衡和客户端负载均衡。  服务端负载均衡指的是发生在服务提供者一方,比如常见的 nginx 负载均衡。 客户端负载均衡指的是发生在服务请求的一方,也就是在发送请求之前已经选好了由哪个实例处理请求。  我们在微服务调用关系中一般会选择客户端负载均衡,也就是在服务调用的一方来决定服务由哪个提供者执行。  自定义实现负载均衡 启动shop-product微服务 在启动 shop-product 微服务的基础上,通过idea再启动一个 shop-product 微服务,设置其端口为8082。  命令为-Dserver.port=8082  通过nacos查看微服务的启动情况  自定义实现负载均衡 修改 shop-order 的代码  public ShopOrder order(@PathVariable("pid") Long pid) {         log.info("客户下单,这时候要调用商品微服务查询商品信息。。。");         //从nacos中获取服务地址         List<ServiceInstance> instances = discoveryClient.getInstances("shop-product");         //自定义规则实现随机挑选服务         int index = new Random().nextInt(instances.size());         ServiceInstance serviceInstance = instances.get(index);         String url = serviceInstance.getHost()+":"+serviceInstance.getPort();         log.info(">>从nacos中获取到的微服务地址为:"+ url);         //通过restTemplate调用商品微服务         ShopProduct shopProduct = restTemplate.getForObject("http://"+url+"/product/"+pid, ShopProduct.class);         log.info("当前用户信息为自己,假设我们设置为1");         ShopOrder shopOrder = new ShopOrder();         shopOrder.setUid(1L);         shopOrder.setUsername("公众号:阿Q说代码");         shopOrder.setPid(shopProduct.getId());         shopOrder.setPname(shopProduct.getPname());         orderService.save(shopOrder);         //商品扣减库存的逻辑         ProductReduceDTO productReduceDTO = new ProductReduceDTO();         productReduceDTO.setProductId(pid);         productReduceDTO.setReductCount(1);         Integer count = restTemplate.postForObject("http://"+url+"/product/reduceStock", productReduceDTO, Integer.class);         return shopOrder;     }  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 启动两个服务提供者和一个服务消费者,多访问几次消费者,测试效果:  基于Ribbon实现负载均衡 Ribbon 是Spring Cloud的一个组件,它可以让我们使用一个注解就能轻松的搞定负载均衡。  添加注解 在 RestTemplate 的生成方法上添加@LoadBalanced注解  @Bean @LoadBalanced  public RestTemplate getRestTemplate() {      return new RestTemplate();  } 1 2 3 4 5 修改服务调用的方法 将上一步中的代码注释掉,改为直接使用微服务名字,从nacos中获取服务地址  //从nacos中获取服务地址 //自定义规则实现随机挑选服务 //List<ServiceInstance> instances = discoveryClient.getInstances("shop-product"); //int index = new Random().nextInt(instances.size()); //ServiceInstance serviceInstance = instances.get(index); //String url = serviceInstance.getHost()+":"+serviceInstance.getPort();  //直接使用微服务名字,从nacos中获取服务地址 String url="shop-product"; 重启服务进行测试,微服务调用成功。 Ribbon支持的负载均衡策略 Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule,具体的负载策略如下图所示:  通过修改配置来调整 Ribbon 的负载均衡策略 # 负载均衡规则 shop-product:   ribbon:     NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule 通过注入Bean来调整 Ribbon 的负载均衡策略  @Bean  public IRule randomRule(){      return new RandomRule();  } 饥饿加载 为何要开启饥饿加载,因为在我们第一次加载时候,响应时间比较慢,原因是第一次加服务需要从服务注册列表中拉取服务实例,以及初始化相关的组件到 Spring 中。  开启饥饿加载的相关配置之后这些操作会在服务启动就会完成。  ribbon:   eager-load:     clients:       - shop-product 总结 到这儿,我们微服务调用的负载均衡实现就结束了。下一篇将为大家带来基于feign实现微服务调用的文章,敬请期待吧!  后续的文章,我们将继续完善我们的微服务系统,集成更多的Alibaba组件。想要了解更多JAVA后端知识,请点击文末名片与我交流吧。留下您的一键三连,让我们在这个寒冷的东西互相温暖吧!                          原文链接:https://blog.csdn.net/Qingai521/article/details/135912210 
  • [技术干货] SpringBoot+Vue前后端分离实现审核功能的示例【转】
    一、前言在实际开发中,审核功能是一个非常常用的功能,例如管理后台的文章审核等等。本篇博文将介绍如何基于SpringBoot+Vue的前后端分离技术实现审核功能。二、项目准备本项目使用的技术栈为:前端:Vue+ElementUI后端:SpringBoot+MySQL首先,你需要在本地搭建好Vue和SpringBoot的开发环境,建议使用最新版本。三、数据库设计本项目需要用到一个审核表,设计如下:CREATE TABLE `audit` (   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',   `title` varchar(50) NOT NULL COMMENT '标题',   `content` text NOT NULL COMMENT '内容',   `status` tinyint(4) NOT NULL COMMENT '状态,0-待审核,1-审核通过,2-审核不通过',   `create_time` datetime NOT NULL COMMENT '创建时间',   `update_time` datetime NOT NULL COMMENT '更新时间',   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='审核表'; 四、后端实现1. 数据库操作首先,我们需要在后端设计一个操作数据库的Dao层来实现对审核表的增删改查操作。@Repository public interface AuditDao extends JpaRepository<Audit, Long> { } 以上是使用Spring Data JPA实现的Dao层。2. 业务逻辑在Service层中,我们需要实现审核操作的相关逻辑: @Service public class AuditService {      @Autowired     private AuditDao auditDao;      /**      * 提交审核      *      * @param audit 审核实体类      * @return 审核实体类      */     public Audit submitAudit(Audit audit) {         audit.setStatus(Constant.AUDIT_STATUS_WAIT); // 默认状态为未审核         audit.setCreateTime(LocalDateTime.now());         audit.setUpdateTime(LocalDateTime.now());         return auditDao.save(audit);     }      /**      * 审核通过      *      * @param auditId 审核ID      * @return 审核实体类      */     public Audit auditPass(Long auditId) {         Audit audit = auditDao.findById(auditId).orElse(null);         if (audit == null) {             throw new RuntimeException("审核ID不存在!");         }         audit.setStatus(Constant.AUDIT_STATUS_PASS);         audit.setUpdateTime(LocalDateTime.now());         return auditDao.save(audit);     }      /**      * 审核不通过      *      * @param auditId 审核ID      * @return 审核实体类      */     public Audit auditReject(Long auditId) {         Audit audit = auditDao.findById(auditId).orElse(null);         if (audit == null) {             throw new RuntimeException("审核ID不存在!");         }         audit.setStatus(Constant.AUDIT_STATUS_REJECT);         audit.setUpdateTime(LocalDateTime.now());         return auditDao.save(audit);     }      /**      * 查询审核列表      *      * @param pageNum  分页页码      * @param pageSize 分页大小      * @return 审核列表数据      */     public Page<Audit> getAuditList(int pageNum, int pageSize) {         return auditDao.findAll(PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Order.desc("createTime"), Sort.Order.desc("id"))));     } } 3. 接口实现在Controller层中,我们需要实现审核相关接口的实现: @RestController @RequestMapping("/audit") public class AuditController {      @Autowired     private AuditService auditService;      /**      * 提交审核      *      * @param audit 审核实体类      * @return 审核实体类      */     @PostMapping("/submitAudit")     public Audit submitAudit(@RequestBody Audit audit) {         return auditService.submitAudit(audit);     }      /**      * 审核通过      *      * @param auditId 审核ID      * @return 审核实体类      */     @PostMapping("/auditPass")     public Audit auditPass(@RequestParam("auditId")Long auditId) {         return auditService.auditPass(auditId);     }      /**      * 审核不通过      *      * @param auditId 审核ID      * @return 审核实体类      */     @PostMapping("/auditReject")     public Audit auditReject(@RequestParam("auditId") Long auditId) {         return auditService.auditReject(auditId);     }      /**      * 查询审核列表      *      * @param pageNum  分页页码      * @param pageSize 分页大小      * @return 审核列表数据      */     @GetMapping("/getAuditList")     public Page<Audit> getAuditList(@RequestParam("pageNum") int pageNum, @RequestParam("pageSize") int pageSize) {         return auditService.getAuditList(pageNum, pageSize);     } } 这样我们的后端实现就完成了。五、前端实现1. 页面设计在前端中,我们需要设计审核列表页面和审核详情页面。审核列表页面用来展示所有未审核通过的审核记录,审核详情页面则用来展示审核详情并提供审核通过和审核不通过的操作。审核列表页面设计如下: <template>   <div>     <el-button type="primary" class="mb-md" @click="dialogFormVisible = true">提交审核</el-button>     <el-table :data="tableData" border stripe style="margin-left: -12px">       <el-table-column prop="id" label="ID" width="70"></el-table-column>       <el-table-column prop="title" label="标题" width="250"></el-table-column>       <el-table-column prop="content" label="内容" width="580"></el-table-column>       <el-table-column prop="createTime" label="创建时间" width="170"></el-table-column>       <el-table-column prop="updateTime" label="更新时间" width="170"></el-table-column>       <el-table-column           prop="status"           label="状态"           width="90"           :formatter="statusFormatter"           :cell-style="{textAlign: 'center'}"       ></el-table-column>       <el-table-column           label="操作"           width="120"           :cell-style="{textAlign: 'center'}"           :render-header="renderHeader"       >         <template slot-scope="scope">           <el-button type="primary" size="mini" v-if="scope.row.status === 0" @click="handlePass(scope.row.id)">通过</el-button>           <el-button type="danger" size="mini" v-if="scope.row.status === 0" @click="handleReject(scope.row.id)">不通过</el-button>           <el-button type="info" size="mini" :disabled="scope.row.status !== 1" @click="handleView(scope.row.id)">查看详情</el-button>         </template>       </el-table-column>     </el-table>      <el-pagination         background         :page-size="pageSize"         :total="total"         layout="prev, pager, next"         @current-change="getCurrentPage"     >     </el-pagination>      <el-dialog         title="提交审核"         :visible.sync="dialogFormVisible"         :close-on-click-modal="false"         :before-close="handleDialogClose"     >       <el-form :model="form" :rules="rules" ref="form" label-width="120px">         <el-form-item label="标题" prop="title">           <el-input v-model="form.title"></el-input>         </el-form-item>         <el-form-item label="内容" prop="content">           <el-input v-model="form.content" type="textarea"></el-input>         </el-form-item>         <el-form-item>           <el-button type="primary" @click="submitAudit">提交</el-button>           <el-button @click="dialogFormVisible = false">取消</el-button>         </el-form-item>       </el-form>     </el-dialog>      <el-dialog         title="审核详情"         :visible.sync="viewDialogVisible"         :close-on-click-modal="false"         :before-close="handleDialogClose"     >       <div v-html="viewData.content"></div>       <el-divider content-position="center" style="margin-top: 20px;">审核结果</el-divider>       <el-alert           class="mt-md"           :title="viewData.status === 1 ? '审核通过' : '审核不通过'"           :type="viewData.status === 1 ? 'success' : 'error'"           :description="'审核时间:' + viewData.updateTime"       ></el-alert>       <div style="text-align: center">         <el-button type="primary" @click="viewDialogVisible = false">关闭</el-button>       </div>     </el-dialog>   </div> </template> 审核详情页面设计如下:<template>   <div>     <div v-html="viewData.content"></div>     <el-divider content-position="center" style="margin-top: 20px;">审核结果</el-divider>     <el-alert         class="mt-md"         :title="viewData.status === 1 ? '审核通过' : '审核不通过'"         :type="viewData.status === 1 ? 'success' : 'error'"         :description="'审核时间:' + viewData.updateTime"     ></el-alert>     <div style="text-align: center">       <el-button type="primary" @click="handleBack">返回</el-button>     </div>   </div> </template> 2. 数据交互然后我们需要通过Axios实现前端向后端的数据交互。首先,我们定义一个api.js文件: import axios from 'axios'  axios.defaults.baseURL = '/api'  // 审核操作 export function auditOp(opType, auditId) {   return axios.post(`/audit/${opType}`, {auditId}) }  // 获取审核列表 export function getAuditList(pageNum, pageSize) {   return axios.get(`/audit/getAuditList?pageNum=${pageNum}&pageSize=${pageSize}`) }  // 提交审核 export function submitAudit(audit) {   return axios.post('/audit/submitAudit', audit) }  // 获取审核详情 export function getAuditDetail(auditId) {   return axios.get(`/audit/${auditId}`) } 然后在页面中使用这些api: import * as api from '@/api'  export default {   name: 'AuditList',   data() {     return {       tableData: [],       total: 0,       currentPage: 1,       pageSize: 10,       dialogFormVisible: false,       viewDialogVisible: false,       viewData: {},       form: {         title: '',         content: ''       },       rules: {         title: [           {required: true, message: '请输入标题', trigger: 'blur'}         ],         content: [           {required: true, message: '请输入内容', trigger: 'blur'}         ]       }     }   },   mounted() {     this.getAuditList(this.currentPage, this.pageSize)   },   methods: {     // 获取审核列表     getAuditList(pageNum, pageSize) {       api.getAuditList(pageNum, pageSize)         .then(res => {           this.tableData = res.content           this.total = res.totalElements         })     },     // 审核通过     handlePass(id) {       this.$confirm('确定要通过该审核吗?', '提示', {         confirmButtonText: '确定',         cancelButtonText: '取消',         type: 'warning'       }).then(() => {         api.auditOp('auditPass', id)           .then(() => {             this.$message({type: 'success', message: '审核通过'})             this.getAuditList(this.currentPage, this.pageSize)           })       })     },     // 审核不通过     handleReject(id) {       this.$prompt('请输入不通过原因', '提示', {         distinguishCancelAndClose: true,         cancelButtonText: '取消',         confirmButtonText: '确定'       }).then(({value}) => {         api.auditOp('auditReject', id)           .then(() => {             this.$message({type: 'success', message: '审核不通过'})             this.getAuditList(this.currentPage, this.pageSize)           })       })     },     // 查看详情     handleView(id) {       api.getAuditDetail(id)         .then(res => {           this.viewData = res           this.viewDialogVisible = true         })     },     // 提交审核     submitAudit() {       this.$refs['form'].validate(valid => {         if (valid) {           api.submitAudit(this.form)             .then(() => {               this.$message({type: 'success', message: '提交审核成功'})               this.dialogFormVisible = false               this.getAuditList(this.currentPage, this.pageSize)             })         }       })     },     // 获取当前分页页码     getCurrentPage(page) {       this.currentPage = page       this.getAuditList(page, this.pageSize)     },     // 对话框关闭事件     handleDialogClose(done) {       this.$confirm('确定要关闭吗?')         .then(() => {           done()         }).catch(() => {})     },     // 返回     handleBack() {       this.viewDialogVisible = false     },     // 状态格式化     statusFormatter(row) {       if (row.status === 0) {         return '待审核'       } else if (row.status === 1) {         return '审核通过'       } else {         return '审核不通过'       }     },     // 表头渲染     renderHeader() {       return '操作'     }   } } 到此为止,我们的前端实现就完成了。六、补充说明1. 前后端分离架构本项目采用的是前后端分离的架构模式,这个模式中前后端各自拥有自己的代码库,前端代码负责渲染页面和处理用户交互,后端代码负责处理数据逻辑和提供API接口。前端和后端通过API接口进行通信。这种架构模式的好处是可以很好地实现前后端分离,并且可以使开发效率更高。2. 审核功能审核功能是一个非常常用的功能,本文中实现了一个基本的审核功能,但实际开发中仍需要考虑更多的业务需求。例如:支持多种审核状态、支持审核流程配置、支持审核人员配置等等。
  • [技术干货] CORS就是跨域吗【转】
    首先,跨域的域是什么?跨域的英文是:Cross-Origin。Origin 中文含义为:起源,源头,出生地。在跨域中,"域"指的是一个 Web 资源(比如网页、脚本、图片等)的源头。包括该资源的协议、主机名、端口号。在同源策略中,如果两个资源的域相同,则它们属于同一域,可以自由进行交互和共享数据。反之,如果两个资源的域不同,就会出现跨域问题。这时就需要特殊的方式来处理,如跨域资源共享(CORS)。那什么是同源策略?同源策略(Same-Origin Policy)是浏览器中的一项安全机制,用于保护用户的隐私和安全。它限制了一个网页或者脚本只能从同一个源加载的资源进行访问,而不能访问其他来源的资源。这样做可以防止恶意网站利用用户身份信息进行跨站请求伪造(CSRF)攻击,保护用户的数据安全。什么是跨站请求伪造?跨站请求伪造(CSRF,Cross-Site Request Forgery)是一种网络攻击方式。在 CSRF 攻击中,攻击者利用已认证的用户身份(例如用户在银行网站上登录后的会话信息)来伪造请求,以执行未经授权的操作。举个例子:我登录了银行网站,浏览器根据我的登录信息生成了一个会话令牌,也就是 session token。但是这个令牌被而恶意网站给拿到了,它拿着我的 token 去服务器发送请求。就可以把我银行卡里的 29 块八毛五全部转走。但是如果有同源策略的限制,恶意网站就无法直接发送请求到银行。我的 29 块八毛五就可以保住。因为恶意网站的域名与银行网站的域名不同,浏览器会阻止这种抢劫行为。什么是跨域资源共享(CORS)?为了防止被面试官笑话,这里一定要知道:跨域资源共享(CORS,Cross-Origin Resource Sharing)是一种用来解决由于浏览器的同源策略而导致的跨域请求问题的一种机制。浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。什么是简单请求?只要同时满足以下两大条件,就属于简单请求。(1)请求方法是以下三种方法之一: - HEAD - GET - POST (2)HTTP的头信息不超出以下几种字段: - Accept - Accept-Language - Content-Language - Last-Event-ID - Content-Type:只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain简单请求的工作流程如下:1. 浏览器在请求中增加一个 Origin 头部字段,其中包含当前页面的源信息(协议、主机、端口)。2. 服务器在收到这个请求后,会根据请求中的 Origin 头部信息来判断是否允许该请求。3. 如果服务器允许该请求,会在响应头部中包含一个 Access-Control-Allow-Origin 头部,"*"表示允许所有来源。4. 浏览器在收到响应后,决定是否允许页面访问该资源。什么是非简单请求?不是简单请求的,就是非简单请求。非简单请求它非简单在哪?或者说:它非简单又能怎么样?非简单请求在发起正式请求之前,会先发起一个预检请求。什么是预检请求?预检请求是用于在实际的跨域请求之前进行探测和验证,以确保服务器能够正确处理,预防跨域请求可能会引发的安全性问题。一句话就是:我去前面探探路!只有得到服务器的肯定答复,浏览器才会发出正式的 XMLHttpRequest 请求,否则就报错。实际 java 开发中的 CORS 解决跨域配置长这样:@Configuration public class CorsConfig implements WebMvcConfigurer {     @Override     public void addCorsMappings(CorsRegistry registry) {         // 允许所有的URL路径都可以跨域访问         registry.addMapping("/**")             // 允许所有来源(即允许任何域名)的请求跨域访问             .allowedOrigins("*")             // 允许发送身份验证信息(如cookies、HTTP身份验证或客户端SSL证明)             .allowCredentials(true)             // 允许跨域请求的HTTP方法,包括GET、POST、PUT、DELETE和OPTIONS。             .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")             // 预检请求(OPTIONS请求)的有效期,单位为秒             .maxAge(3600);     } }还有别的方式可以解决跨域问题吗?有的。使用 Nginx 部署为同一域。什么意思呢?就是说 Nginx 作为唯一域,代理所有服务端,在客户端眼里,只有 Nginx 这一个域,也就不存在跨域问题,由 Nginx 拿到请求再分发给对应服务器。这里我们就不再展开。转载自https://www.cnblogs.com/cosimo/p/18023596
  • [技术干货] Spring Cloud与Spring的区别
    前言随着微服务架构的兴起,Spring Cloud逐渐崭露头角,成为了构建分布式系统的重要工具。对于很多初次接触Spring Cloud的开发者来说,可能不太清楚它与传统的Spring框架有何区别。本文旨在探讨Spring Cloud与Spring之间的主要差异。一、Spring框架简介Spring是一个开源的Java平台,它提供了一套全面的编程和配置模型,用于构建企业级应用程序。Spring的核心特性包括依赖注入(DI)、面向切面编程(AOP)、数据访问抽象等。通过Spring,开发者可以更加高效、简洁地开发应用程序。二、Spring Cloud简介Spring Cloud是基于Spring Boot的一套微服务工具集,它提供了一整套微服务解决方案,包括服务发现、配置管理、熔断器、负载均衡等。Spring Cloud的目标是让微服务架构的搭建变得更加简单、快速和可靠。三、Spring Cloud与Spring的主要区别关注点不同:Spring框架主要关注的是应用程序的本身,提供了一系列的基础功能,如依赖注入、事务管理等。而Spring Cloud则更加关注微服务架构中的各个组件和服务之间的通信与协作,为构建分布式系统提供了丰富的工具和解决方案。组件和服务的集成:Spring Cloud集成了许多优秀的开源项目,如Netflix的Eureka、Hystrix、Zuul等,这些组件共同构成了微服务架构的核心部分。而Spring本身并不包含这些组件,需要开发者自行集成。服务治理:Spring Cloud提供了强大的服务治理功能,包括服务发现、配置管理、熔断器、负载均衡等。这些功能使得微服务架构更加健壮、可靠和易于维护。相比之下,Spring本身并不提供这些服务治理功能。部署和扩展性:Spring Cloud的设计初衷就是为了支持快速部署和横向扩展,它允许开发者将应用程序拆分成多个独立的服务,每个服务都可以独立部署和扩展。而传统的Spring应用程序通常需要整体部署,扩展性相对较差。与Spring Boot的整合:Spring Cloud是建立在Spring Boot基础之上的,因此它充分利用了Spring Boot的优点,如自动配置、快速启动等。这使得开发者在构建微服务应用程序时,可以更加高效、简洁地开发、部署和维护。四、总结综上所述,Spring Cloud与Spring的主要区别在于它们的关注点、组件和服务的集成、服务治理、部署和扩展性以及与Spring Boot的整合。Spring Cloud作为一套微服务工具集,为构建分布式系统提供了丰富的解决方案,使得开发者可以更加轻松地应对复杂的业务需求。然而,在使用Spring Cloud时,也需要关注其复杂性、学习曲线和潜在的兼容性问题。因此,在选择使用Spring Cloud还是传统的Spring框架时,需要根据项目的具体需求和团队的实际情况进行权衡。
  • [技术干货] 【Spring】SpringBoot 统一功能处理-转载
     前言 在日常使用 Spring 框架进行开发的时候,对于一些板块来说,可能需要实现一个相同的功能,这个功能可以是验证你的登录信息,也可以是其他的,但是由于各个板块实现这个功能的代码逻辑都是相同的,如果一个板块一个板块进行添加的话,开发效率就会很低,所以 Spring 也想到了这点,为我们程序员提供了 SpringBoot 统一功能处理的方法实现,我们是可以直接使用的。这篇文章我将带大家一起学习 SpringBoot 统一功能的处理。  1. 拦截器 正常的判断用户是否登录的逻辑就是通过 session 来判断,对于一些网站来说,很多的功能都是需要用户进行登录之后才可以使用的,如果此时通过 session 判断出来用户处于未登录状态的话,咱们的服务器会强制用户进行登录,通过代码来显示就是这样的:  //检验用户是否登录 HttpSession session = request.getSession(false); if (session == null || session.getAttribute("userInfo") == null) {     return "您未登录,请登录后再试试该功能吧"; } 我们想要在哪个功能前判断用户的登录信息,就需要在该功能的模块中添加上面这些代码,如果需要添加的功能较少还好,如果很多,那么就需要花费很多的时间,那么有人就说了:我是否可以将这些代码封装成函数,然后哪个模块需要使用只需要调用这些函数就可以了呢?可以是可以,但是使用函数封装代码还是需要在原代码的基础上调用这个函数,并且显得也不是那么优雅。那么是否有方法既不需要更改原代码,也可以使得我们写的代码很优雅呢?SpringBoot 为我们提供了一种功能——拦截器。  1.1 什么是拦截器 在 Spring Boot 中,拦截器是一种用于在处理请求之前或之后执行特定操作的组件。拦截器通常用于实现一些通用的功能,比如权限验证、日志记录等。拦截器可以拦截通过Controller的请求,并在请求处理前后执行特定的操作。  拦截器的思想正好符合我们执行其他功能之前进行身份验证验证,并且不仅在方法执行之前拦截器可以起作用,方法执行之后。我们的拦截器也可以起到作用。   1.2 拦截器的使用 拦截器的使用分为两个步骤:  定义拦截器 注册配置拦截器 1.2.1 自定义拦截器 我们自定义的拦截器需要实现 HandlerInterceptor 接口,并且重写这个接口中的方法。  package com.example.springbootbook2.interceptor;  import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;  import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;  @Component @Slf4j public class LoginInterceptor implements HandlerInterceptor {     @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {         log.info("目标方法执行前执行...");         HttpSession session = request.getSession(false);         if (session == null || session.getAttribute("userInfo") == null) {             response.setStatus(401);             return false;         }         return true;     }      @Override     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {         log.info("目标方法执行后执行...");     }      @Override     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {         log.info("视图渲染完毕之后执行,最后执行...");     } } preHandle() 方法是在目标方法执行之前执行的,返回 true,继续执行后面的代码;返回 false,中断后续的操作 postHandle() 方法是在目标方法执行之后执行的 afterCompletion() 方法是在视图渲染之后才执行的,它还在 postHandle() 方法之后执行,并且现在因为前后端分离,我们后端基本上接触不到视图的渲染,所以这个方法使用的较少 1.2.2 注册配置拦截器 注册配置拦截器需要实现 WebMvcConfiguer 接口,并实现 addInterceptors 方法。  package com.example.springbootbook2.config;  import com.example.springbootbook2.interceptor.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;  @Configuration public class WebConfig implements WebMvcConfigurer {     @Autowired     private LoginInterceptor loginInterceptor;      @Override     public void addInterceptors(InterceptorRegistry registry) {         registry.addInterceptor(loginInterceptor)                 .addPathPatterns("/**")  //指定路径配置拦截器                 .excludePathPatterns("/user/login");  //指定路径不配置拦截器     } } 在添加拦截器进行身份校验之前,我们可以直接通过对应的 url 访问到指定功能页面。   而在我们添加拦截器之后,再看是否能直接访问某些功能。   我们启动添加了拦截器之后的代码之后,点击刷新就发现不能够直接访问到某些功能了,再搭配着前端我们就可以实现强制登录的功能了。  1.3 拦截器详解 拦截器的⼊⻔程序完成之后,接下来我们来介绍拦截器的使⽤细节。拦截器的使⽤细节我们主要介绍两个部分:  拦截器的拦截路径配置 拦截器实现原理 1.3.1 拦截路径 拦截路径是指我们定义的这个拦截器,对哪些请求生效,我们在注册配置拦截器的时候,通过 addPathPatterns() 方法指定要拦截哪些 HTTP 请求,也可以通过 excludePathPatterns() 方法指定不拦截哪些请求,上面我们的 /** 表示拦截所有的 HTTP 请求,除了可以设置拦截所有请求外,还有一些其他的拦截设置:  拦截路径    含义    举例 /*    ⼀级路径    能匹配/user,/book,/login,不能匹配 /user/login /**    任意级路径    能匹配/user,/user/login,/user/reg /book/*    /book下的⼀级路径    能匹配/book/addBook,不能匹配/book/addBook/1,/book /book/**    /book下的任意级路径    能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login 这些拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS、和 CSS 等⽂件)。  知道了如何配置拦截路径之后,我们就可以解决网页上身份验证的问题了。因为 /** 会拦截所有的请求,包括前端页面请求,所以我们需要将前端页面请求排除在拦截之外。  如果我们不讲前端页面请求在外的话机会出现这种情况:  然后我们将前端页面请求不添加拦截器的话,就可以实现完整的身份校验强制登录功能了。  @Configuration public class WebConfig implements WebMvcConfigurer {     @Autowired     private LoginInterceptor loginInterceptor;      private List<String> excludePath = Arrays.asList("/user/login",             "/css/**",             "/js/**",             "/pic/**",             "/**/*.html");  // /**/*html中的/**表示所有路径下的所有以html结尾的文件 * 表示通配符                  @Override     public void addInterceptors(InterceptorRegistry registry) {         registry.addInterceptor(loginInterceptor)                 .addPathPatterns("/**")                 .excludePathPatterns(excludePath);     } } 当我们未登录,然后访问其他功能的话,就会强制跳转到登录页面:   1.3.2 拦截器执行流程 正常的调用顺序是这样的:  有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图:   当我们添加拦截器之后,在执行 Controller 方法之前,请求会先被拦截器拦截,执行 preHandle() 方法,这个方法会返回一个布尔类型的值,如果返回的是 true,那么会继续执行后面的操作;如果返回的是 false,则不会执行后面的操作 Controller 当中的方法执行完毕之后,会继续执行 postHandle() 方法以及 afterHandle() 方法,执行完毕之后,返回给浏览器响应数据。 在源码中的 DispatcherServlet 类中的 doDispatch 方法中可以看到这三个方法的执行流程:   1.3.3 适配器模式 在拦截器执行的过程中还使用了适配器模式:  适配器模式(Adapter Pattern)在计算机编程中是一种常用的设计模式,主要用于解决两个不兼容的类之间的接口匹配问题。通过将一个类的接口转换成客户端所期望的另一种接口,原本因为接口不匹配而无法一起工作的两个类现在可以一起工作。  适配器模式的优点在于它能够使原本由于接口不兼容而无法一起工作的类一起工作,提高了系统的灵活性和可扩展性。同时,它也能够减少代码的重复性,因为多个源可以共享同一个适配器。  HandlerAdapter 主要⽤于⽀持不同类型的处理器(如 Controller、HttpRequestHandler 或者 Servlet 等),让它们能够适配统⼀的请求处理流程。这样,SpringMVC可以通过⼀个统⼀的接⼝ 来处理来⾃各种处理器的请求。  本来 target 和 adaptee 是两个无法正常对接的事物,但是通过适配器 adapter,这两者之间就可以进行对接,也就类似于下面这个情况:  适配器模式⻆⾊:  Target:⽬标接口(可以是抽象类或接口)客户希望直接⽤的接口 Adaptee:适配者,但是与Target不兼容 Adapter:适配器类,此模式的核⼼。通过继承或者引⽤适配者的对象,把适配者转为⽬标接⼝ client:需要使⽤适配器的对象 前⾯学习的 slf4j 就使⽤了适配器模式,slf4j 提供了⼀系列打印⽇志的 api,底层调⽤的是 log4j 或者logback 来打⽇志,我们作为调⽤者,只需要调⽤ slf4j 的 api 就⾏了。  /** * slf4j接⼝ */ interface Slf4jApi{     void log(String message); }  /** * log4j 接⼝ */ class Log4j{     void log4jLog(String message){         System.out.println("Log4j打印:"+message);     } }  /** * slf4j和log4j适配器 */ class Slf4jLog4JAdapter implements Slf4jApi{     private Log4j log4j;     public Slf4jLog4JAdapter(Log4j log4j) {         this.log4j = log4j;     }     @Override     public void log(String message) {         log4j.log4jLog(message);     } }  /** * 客⼾端调⽤ */ public class Slf4jDemo {     public static void main(String[] args) {         Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());         slf4jApi.log("使⽤slf4j打印⽇志");     } } 可以看出,我们不需要改变 log4j 的api,只需要通过适配器转换下,就可以更换⽇志框架,保障系统的平稳运行。  ⼀般来说,适配器模式可以看作⼀种"补偿模式",⽤来补救设计上的缺陷。应⽤这种模式算是"⽆奈之举",如果在设计初期,我们就能协调规避接⼝不兼容的问题,就不需要使⽤适配器模式了。  所以适配器模式更多的应⽤场景主要是对正在运⾏的代码进⾏改造,并且希望可以复⽤原有代码实现新的功能.。⽐如版本升级等。  2. 统一数据返回格式 如果我们完成了一个项目的开发,但是这时候,我们突然觉得后端的各个功能的返回值返回的信息不够全面,我们需要补充一些返回信息,那么这时候就意味着所有方法的返回值都需要做出修改,这也是一个不小的工作量。这时 SpringBot 又为我们提供了解决方法——统一数据返回格式。  SpringBoot 统一数据返回格式会在各个方法进行 return 返回值之前插入一些代码逻辑,从而达到改变返回值的功能。  统一的数据返回格式使用 @ControllerAdvice 和 ResponseBodyAdvice 的方式实现。ControllerAdvice 表示控制器通知类。  我们先定义一个统一的返回类型:  public enum ResultCode {     SUCCESS(0),     FAIL(-1),     UNLOGIN(-2);      //0-成功  -1 失败  -2 未登录     private int code;      ResultCode(int code) {         this.code = code;     }      public int getCode() {         return code;     }      public void setCode(int code) {         this.code = code;     } }  @Data public class Result<T> {     /**      * 业务状态码      */     private ResultCode code;  //0-成功  -1 失败  -2 未登录     /**      * 错误信息      */     private String errMsg;     /**      * 数据      */     private T data;      public static <T> Result<T> success(T data){         Result result = new Result();         result.setCode(ResultCode.SUCCESS);         result.setErrMsg("");         result.setData(data);         return result;     }     public static <T> Result<T> fail(String errMsg){         Result result = new Result();         result.setCode(ResultCode.FAIL);         result.setErrMsg(errMsg);         result.setData(null);         return result;     }     public static <T> Result<T> fail(String errMsg,Object data){         Result result = new Result();         result.setCode(ResultCode.FAIL);         result.setErrMsg(errMsg);         result.setData(data);         return result;     }     public static <T> Result<T> unlogin(){         Result result = new Result();         result.setCode(ResultCode.UNLOGIN);         result.setErrMsg("用户未登录");         result.setData(null);         return result;     }  } package com.example.springbootbook2.config;  import com.example.springbootbook2.model.Result; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;  @ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice {     @Override     public boolean supports(MethodParameter returnType, Class converterType) {         return true;     }      @Override     public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {         return Result.success(body);     } } 继承 ResponseBodyAdvice 接口后,需要实现该接口下的 supports 方法和 beforeBodyWrite 方法,supports 方法只需要更改返回值为 true 就可以了,表示是否要执行 beforeBodyWrite 方法,返回 true 表示执行,false 表示不执行,beforeBodyWrite 方法中的 body 参数就是我们原方法的返回值。  当我们关闭拦截器,访问图书列表页之后,就发现我们的返回类型发生了变化:  但是这个统一数据返回格式也存在问题,当我们的返回值为 String 类型的话机会出现错误:   这是为什么呢?SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter (从先后顺序排列分别为 ByteArrayHttpMessageConverter ,StringHttpMessageConverter , SourceHttpMessageConverter ,AllEncompassingFormHttpMessageConverter )  其中 AllEncompassingFormHttpMessageConverter 会根据项⽬依赖情况添加对应的 HttpMessageConverter   在依赖中引⼊jackson包后,容器会把 MappingJackson2HttpMessageConverter ⾃动注册到 messageConverters 链的末尾。Spring会根据返回的数据类型,从 messageConverters 链选择合适的 HttpMessageConverter。当返回的数据是⾮字符串时,使⽤的MappingJackson2HttpMessageConverter 写⼊返回对象。当返回的数据是字符串时, StringHttpMessageConverter 会先被遍历到,这时会认为 StringHttpMessageConverter 可以使用。   然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String,此 时t为Result类型,所以出现类型不匹配"Result cannot be cast to java.lang.String"的异常。  那么如何解决返回类型为 String 类型的问题呢?如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化。  @ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice {     @Autowired     private ObjectMapper objectMapper;     @Override     public boolean supports(MethodParameter returnType, Class converterType) {         return true;     }      @SneakyThrows     @Override     public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {         if (body instanceof String) {             return objectMapper.writeValueAsString(Result.success(body));         }else if (body instanceof Result) {             return body;         }else {             return Result.success(body);         }     } } @SneakyThrows 的主要目的是解决 Java 的异常处理问题。当我们在代码中抛出一个异常时,如果这个异常被包裹在一个方法中,并且这个方法没有 throws 关键字来声明会抛出这个异常,那么编译器会报错。通过使用 @SneakyThrows,你可以告诉编译器:“我知道这个方法可能会抛出异常,但我保证在 catch 块中处理它。” 这样编译器就不会报错了。 通过这个处理,当返回的数据类型为 String 的时候就不会出现错误了。  统一数据返回格式的优点:  ⽅便前端程序员更好的接收和解析后端数据接口返回的数据 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接口都是这样返回的 有利于项目统⼀数据的维护和修改 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容 3. 统一异常处理 当我们的程序中出现异常的时候,我们需要解决这些异常,因为前面做了统一数据返回格式的处理,所以这里的异常也可以进行统一的处理。  统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表⽰控制器通知类, @ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执⾏某个⽅法事件。  package com.example.springbootbook2.config;  import com.example.springbootbook2.model.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody;  @ControllerAdvice @Slf4j @ResponseBody  //因为返回的数据都不是视图类型,所以加上这个注解防止出现问题 public class ErrorHandler {     @ExceptionHandler     public Result<String> exception(Exception e) {         log.error("发生异常:e{}",e);         return Result.fail("内部异常");     }      @ExceptionHandler     public Result<String> exception(NullPointerException e) {         log.error("发生异常:e{}",e);         return Result.fail("NullPointerException异常,请联系管理员");     }      @ExceptionHandler     public Result<String> exception(ArithmeticException e) {         log.error("发生异常:e{}",e);         return Result.fail("ArithmeticException异常,请联系管理员");     } } @Controller @RequestMapping("/test") public class TestController {     @RequestMapping("/t1")     public Integer test1() {         return 10/0;     } }  这个 Exception 和 ArithmeticException 的先后顺序可以不考虑,这个不会因为 Exception 在前面捕获就报的 Exception 异常,而是会根据自己出现的最接近的异常来捕获。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/m0_73888323/article/details/135587096 
  • SpringBoot:详解依赖注入和使用配置文件-转载
     前言 在上一篇文章中,讲诉了SpringIoC的Bean装配,但是对于如何进行获取,也就是Bean之间的依赖还未讲诉,下面开始讲诉依赖注入(Dependency Injection,DI)以及如何使用属性文件。涉及主要注解@Autowired、@Primary、@Quelifier、@PropertySource和@ConfigurationProperties。  一、🌕依赖注入 例:人类(Person)有时候利用一些动物(Animal)去完成一些事情,比方说狗(Dog)是用来看门的,猫(Cat)是用来抓老鼠的, 鹦鹉(Paηot)是用来迎客的……于是做一些事情就依赖于那些可爱的动物。假设现在需要用狗狗来看门。  //定义人类接口 public interface Person {     void service();      void setAnimal(Animal animal); } //定义动物接口 public interface Animal {     void user(); } //定义狗 @Component public class Dog implements Animal {     @Override     public void user() {         System.out.println("狗【" + Dog.class.getSimpleName() + "】是用来看门的");     } } //定义年轻人 @Component public class YoungPerson implements Person {     @Autowired     private Animal animal = null;      @Override     public void service() {         this.animal.user();     }      @Override     public void setAnimal(Animal animal) {         this.animal = animal;     } } //定义配置类 @Configuration @ComponentScan("com.dragon.restart")//所有的包和类都在restart下 public class AppConfig { } 测试类:  ApplicationContext ctx =new AnnotationConfigApplicationContext(AppConfig.class) ;         Person person= ctx.getBean(YoungPerson.class) ;         person.service() ; 测试是成功的,这个时候SpringIoC 容器己经通过注解@Autowired成功地将Dog注入到了 YoungPerson 实例中。 但是这只是一个比较简单的例子,我们有必要继续探讨@Autowired。  🌖注解@Autowired @Autowired 是我们使用得最多的注解之一, 因此在这里需要进一步地探讨它。 它注入的机制最基本的一条是根据类型 (bytype), 我们回顾IoC 容器的顶级接口 BeanFactory,就可以知道 IoC 容器是通过getBean 方法获取对应Bean 的,而 getBean 又支持根据类型(by type)或者根据名称(by name)。这里明显根据类型注入,将狗狗实例注入Animal对象。 再回到上面的例子,我们只是创建了一个动物一一狗,而实际上动物还可以有猫 (Cat),猫可以为我们抓老鼠, 于是我们又创建了一个猫的类。  @Component public class Cat implements Animal{     @Override     public void user() {         System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠的");     } } 好了,如果我们还使用着YoungPerson类,那么麻烦来了,因为这个类只是定义了一个动物属性(Animal),而我们却有两个动物,一个狗, 一个猫, SpringIoC 如何注入呢? 运行测试,可以看到IoC容器抛出异常,如下:  Description: Field animal in com.dragon.restart.pojo.impl.YoungPerson required a single bean, but 2 were found:     - cat: defined in file [E:\IDEA_projects\restart\target\classes\com\dragon\restart\pojo\Cat.class]     - dog: defined in file [E:\IDEA_projects\restart\target\classes\com\dragon\restart\pojo\impl\Dog.class] 那么使用@Autowired 能处理这个问题吗?答案是肯定的。假设我们目前需要的是狗提供服务,那么可以把属性名称转化为dog,也就是原来YoungPerson的private Animal animal = null; 改为private An工mal dog = null;  @Component public class YoungPerson implements Person {     @Autowired     private Animal dog = null;      @Override     public void service() {         this.dog.user();     }      @Override     public void setAnimal(Animal animal) {         this.dog = animal;     } } 这里, 我们只是将属性的名称从animal 修改为了 dog,那么我们再测试的时候,你可以看到是采用狗来提供服务的。那是因为@Autowired提供这样的规则:首先它会根据类型找到对应的Bean,如果对应类型的 Bean 不是唯一的,那么它会根据其属性名称和 Bean 的名称进行匹配。如果匹配得上,就会使用该Bean:如果还无法匹配,就会抛出异常。这里还要注意的是@Autowired 是一个默认必须找到对应 Bean 的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null, 那么你可以配置@Autowired属性 required 为 false如@Autowired(required = false)  @Autowired除了可以标注属性外,还可以标注方法, 如setAnimal方法,如下所示:  @Override @Autowired  public void setAnimal (Animal animal) {  this.animal = animal; 这样它也会使用setAnimal 方法从 IoC 容器中找到对应的动物进行注入,甚至我们还可以使用在方法的参数上,后续文章会再谈到它。  🌗消除歧义性——@Quelifier和@Primary 在上面我们发现有猫有狗的时候, 为了使@Autowired 能够继续使用,我们做了一个决定,将YoungPerson 的属性名称从 animal 修改为 dog。显然这是一个憋屈的做法,好好的一个动物,却被我们定义为了狗,毕竟不能每次换个对象就改一次,这样太麻烦了。产生注入失败的问题根本是按类型(bytype) 查找, 正如动物可以有多种类型,这样会造成 Spring IoC 容器注入的困扰,我们把这样的一个问题称为歧义性。知道这个原因后, 那么这两个注解是从哪个角度去解决这些问题的呢?  首先是一个注解@Primary,它是一个修改优先权的注解,当我们有猫有狗的时候,假设这次需要使用猫, 那么只需要在猫类的定义上加入@Primarγ就可以了,如下:  @Component @Primary public class Dog implements Animal {     @Override     public void user() {         System.out.println("狗【" + Dog.class.getSimpleName() + "】是用来看门的");     } } 这里的@Primary 的含义告诉 Spring IoC 容器, 当发现有多个同样类型的 Bean 时,请优先使用我进行注入,于是再进行测试时会发现,系统将用狗狗为你提供服务。 因为当 Spring 进行注入的时候,虽然它发现存在多个动物, 但因为Dog被标注为了@Primarγ,所以优先采用Dog的实例进行了注入,这样就通过优先级的变换使得IoC容器知道注入哪个具体的实例来满足依赖注入。然后,有时候@Primary 也可以使用在多个类上,也许无论是猫还是狗狗都可能带上@Primary 注解, 其结果是IoC容器还是无法区分采用哪个Bean的实例进行注入, 又或者说我们需要更加灵活的机制来实现注入,那么**@Quelifier** 可以满足你的这个愿望。  它将与@Autowired 组合在一起,通过类型和名称一起找到Bean。我们知道Bean 名称在 Spring IoC 容器中是唯一的标识,通过这个就可以消除歧义性了。此时你是否想起了BeanFactory接口中的这个方法呢?<T> T getBean(String name, Class<T> requiredType) throws BeansException;  代码:  @Component public class YoungPerson implements Person {     @Autowired     @Qualifier("dog")     private Animal animal = null;      @Override     public void service() {         this.animal.user();     }      @Override     public void setAnimal(Animal animal) {         this.animal = animal;     } } 一旦这样声明, Spring IoC 将会以类型和名称去寻找对应的Bean进行注入。根据类型和名称,显然也只能找到狗狗为我们服务了。  🌑带有参数的构造方法类装配 在上面,我们都基于一个默认的情况,那就是不带参数的构造方法下实现依赖注入。但事实上,有些类只有带有参数的构造方法,于是上述的方法都不能再使用了。为了满足这个功能,我们可以使用@Autowired 注解对构造方法的参数进行注入,例如,修改类YoungPerson来满足这个功能。  @Component public class YoungPerson implements Person {     private Animal animal = null;      public YoungPerson(@Autowired @Qualifier("dog") Animal animal) {         this.animal = animal;     }      @Override     public void service() {         this.animal.user();     }      @Override     public void setAnimal(Animal animal) {         this.animal = animal;     } } @Autowired 和@Qualifier 注解,使其注入进来。这里使用@Qualifier 是为了避免歧义性。当然如果你的环境中不是有猫有狗,则可以完全不使用@Qualifier,而单单使用@Autowired就可以了。  二、📝使用属性文件 Java 开发使用属性文件已经十分普遍,所以这里谈谈这方面的内容。在Spring Boot 中使用属性文件,可以采用其默认为我们准备的application.properties,也可以使用自定义的配置文件。 应该说读取配置文件的方法很多, 这里没有必要面面俱到地介绍每一个细节,只是介绍那些最常用的方法。  在SpringBoot项目的application.properties:  database.driverName=com.mysql.jdbc.Driver database.url=jdbc:mysql://localhost:3306/my database.username=root database.password=root 创建个DataBaseProperties类:  /**  * @Version: 1.0.0  * @Author: Dragon_王  * @ClassName: DataBaseProperties  * @Description: TODO描述  * @Date: 2024/1/16 16:07  */ @Component public class DataBaseProperties {     @Value("${database.driverName}")     private String driverName = null;     @Value("${database.url}")     private String url = null;     private String username = null;     private String password = null;      public void setDriverName(String driverName) {         System.out.println(driverName);         this.driverName = driverName;     }      public void setUrl(String url) {         System.out.println(url);         this.url = url;     }      @Value("${database.username}")     public void setUsername(String username) {         System.out.println(username);         this.username = username;     }      @Value("${database.password}")     public void setPassword(String password) {         System.out.println(password);         this.password = password;     }      public String getDriverName() {         return driverName;     }      public String getUrl() {         return url;     }      public String getUsername() {         return username;     }      public String getPassword() {         return password;     } } 通过@Value注解, 使用${… }这样的占位符读取配置在属性文件的内容。这里的@Value 注解,既可以加载属性, 也可以加在方法上。 这样就能成功将属性文件内容成功注入了。但是我们可以使用过注解@ConfigurationProperties来简化一下,同样能实现注入,如下:  /**  * @Version: 1.0.0  * @Author: Dragon_王  * @ClassName: DataBaseProperties  * @Description: TODO描述  * @Date: 2024/1/16 16:07  */ @Component @ConfigurationProperties("database") public class DataBaseProperties {     private String driverName = null;     private String url = null;     private String username = null;     private String password = null;      public void setDriverName(String driverName) {         System.out.println(driverName);         this.driverName = driverName;     }      public void setUrl(String url) {         System.out.println(url);         this.url = url;     }      public void setUsername(String username) {         System.out.println(username);         this.username = username;     }      public void setPassword(String password) {         System.out.println(password);         this.password = password;     }      public String getDriverName() {         return driverName;     }      public String getUrl() {         return url;     }      public String getUsername() {         return username;     }      public String getPassword() {         return password;     } } 这里@ConfigurationProperties注解内的database将会与属性名组成属性的全限定名去配置文件里查找,如属性driveName就会和database组成database.driverName去查找,这里注意一下不需要管字母的大小写,不影响。  如果我不用默认的application.properties文件怎么办呢?现在我们重新创建一个jdbc.properties文件,将原配置文件内容移入其中。 DataBaseProperties 类不变如上,启动类配置如下:  @SpringBootApplication @PropertySource(value = {"classpath:jdbc.properties"},ignoreResourceNotFound = true) public class RestartApplication {     public static void main(String[] args) {         SpringApplication.run(RestartApplication.class, args);     }  }  value 可以配置多个配置文件。使用 classpath 前缀, 意味着去类文件路径下找到属性文件;ignoreResourceNotFound 则是是否忽略配置文件找不到的问题。 ignoreResourceNotFound 的默认值为false,也就是没有找到属性文件, 就会报错;这里配置为true,也就是找不到就忽略掉,不会报错。通过运行日志,可以看出成功注入。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/weixin_62951900/article/details/135626611