• [技术干货] SpringWebFlux响应式框架---HandlerAdapter
    HandlerAdapter 接口在 Spring WebFlux 中扮演着至关重要的角色,它的作用是将 DispatcherHandler 找到的处理器(handler)适配到具体的执行逻辑上。HandlerAdapter 使得 DispatcherHandler 无需关心具体的处理器类型,只需要通过 HandlerAdapter 来调用处理器即可。以下是对 HandlerAdapter 组件的源码实现逻辑和步骤的详细分析:HandlerAdapter 接口定义HandlerAdapter 接口定义了以下关键方法:public interface HandlerAdapter { boolean supports(Object handler); Mono<Void> handle(ServerWebExchange exchange, Object handler, Object... args); }supports:检查给定的处理器是否被当前 HandlerAdapter 支持。handle:调用处理器,并返回一个 Mono<Void> 对象,表示异步的调用过程。主要实现类Spring WebFlux 提供了几个 HandlerAdapter 的实现类,主要包括:RequestMappingHandlerAdapter:支持基于注解的控制器方法,如带有 @RequestMapping 注解的方法。HttpHandlerAdapter:支持 HttpHandler 接口的处理器。ControllerEndpointHandlerAdapter:支持 ControllerEndpoint 接口的处理器,通常用于 WebFlux 函数式编程。RouterFunctionHandlerAdapter:支持 RouterFunction 接口,用于函数式路由。RequestMappingHandlerAdapter 源码分析RequestMappingHandlerAdapter 是最常用的 HandlerAdapter 实现之一,下面是它的一些关键实现逻辑:支持性检查:supports 方法检查给定的处理器是否是 Controller 或者 RequestMapping 注解的方法。@Override public boolean supports(Object handler) { return (handler instanceof HandlerFunction) || (handler instanceof Controller) || AnnotationUtils.findAnnotation(handler.getClass(), RequestMapping.class) != null; }调用处理器:handle 方法调用处理器,并处理返回值。 @Override public Mono<Void> handle(ServerWebExchange exchange, Object handler) { // 调用具体的处理器 return ((HandlerFunction<ServerResponse>) handler).handle(exchange); }调用处理器的逻辑调用处理器的逻辑通常涉及以下步骤:参数解析:解析请求中的参数,并将其转换为方法参数。调用方法:调用处理器的方法,并将解析后的参数传递给方法。处理返回值:处理方法的返回值,将其转换为响应。异步处理:如果处理器返回的是 Mono 或 Flux,HandlerAdapter 需要处理这些异步结果。错误处理HandlerAdapter 还负责处理调用过程中的异常,将异常转换为合适的响应。小结一下HandlerAdapter 组件是 Spring WebFlux 请求处理流程中的关键部分,它解耦了 DispatcherHandler 和具体的处理器实现。通过使用不同的 HandlerAdapter 实现,Spring WebFlux 支持了多种类型的处理器,包括基于注解的控制器、函数式路由以及 HttpHandler 接口的实现。这种设计提高了框架的灵活性和可扩展性。转载自https://www.cnblogs.com/wgjava/p/18282994
  • [技术干货] SpringWebFlux响应式框架---HandlerMapping
    HandlerMapping 是 Spring WebFlux 中的一个接口,它定义了将请求映射到处理器(handler)的逻辑。HandlerMapping 的实现类负责根据请求的类型、URL 模式等信息来确定哪个具体的处理器应该处理当前的请求。以下是对 HandlerMapping 组件的源码实现逻辑和步骤的详细分析:HandlerMapping 接口定义HandlerMapping 接口定义了以下关键方法:public interface HandlerMapping { Mono<Object> getHandler(ServerWebExchange exchange); void afterPropertiesSet(); }getHandler:根据给定的 ServerWebExchange 对象,返回一个 Mono 对象,该 Mono 完成时包含请求的处理器。afterPropertiesSet:在所有属性都设置之后调用,允许 HandlerMapping 实现进行初始化。主要实现类Spring WebFlux 提供了几个 HandlerMapping 的实现类,主要包括:RequestMappingHandlerMapping:处理基于注解的映射,例如 @RequestMapping、@GetMapping 等。RouterFunctionMapping:处理基于 RouterFunction 的函数式路由。SimpleUrlHandlerMapping:处理简单的 URL 到对象的映射。RequestMappingHandlerMapping 源码分析RequestMappingHandlerMapping 是最常用的 HandlerMapping 实现之一,下面是它的一些关键实现逻辑:注册和解析:在初始化时,RequestMappingHandlerMapping 会扫描所有的 beans,查找带有 @RequestMapping 注解的方法,并注册这些方法作为请求的处理器。映射处理:RequestMappingHandlerMapping 使用 Pattern 对象来存储和匹配 URL 模式。getHandler 方法实现:@Override public Mono<Object> getHandler(ServerWebExchange exchange) { String lookupPath = getPath(exchange); return getHandlerInternal(exchange) .filter(h -> matchesRoute(lookupPath, h)) .switchIfEmpty(Mono.defer(() -> getBestMatchingHandler(lookupPath, exchange))); }getPath:从 ServerWebExchange 中提取请求路径。getHandlerInternal:返回一个包含所有注册处理器的 Mono。filter 和 matchesRoute:检查处理器是否与请求路径匹配。getBestMatchingHandler:如果没有找到精确匹配的处理器,尝试找到最佳匹配的处理器。映射匹配逻辑映射匹配逻辑通常涉及以下步骤:路径匹配:检查请求的路径是否与注册的 URL 模式匹配。请求方法匹配:如果 URL 模式匹配,进一步检查请求的方法(GET、POST 等)是否与处理器支持的方法匹配。参数条件匹配:检查请求是否包含处理器所需的参数。头信息匹配:检查请求头是否满足特定的条件。消费和产生媒体类型匹配:检查请求的 Accept 头和 Content-Type 是否与处理器支持的媒体类型匹配。性能优化RequestMappingHandlerMapping 还实现了一些性能优化措施,例如缓存匹配的 URL 模式,以减少重复的模式匹配操作。小结一下HandlerMapping 组件是 Spring WebFlux 请求处理流程中的关键部分,它负责将进入的请求映射到正确的处理器。通过使用不同的 HandlerMapping 实现,Spring WebFlux 支持灵活的请求映射策略,以适应不同的应用场景。转载自https://www.cnblogs.com/wgjava/p/18282994
  • [技术干货] SpringWebFlux响应式框架---DispatcherHandler
    DispatcherHandler 是 Spring WebFlux 的核心组件,它的作用类似于 Spring MVC 中的 DispatcherServlet。它负责将传入的 HTTP 请求分发给相应的处理器(handler),并处理请求的映射、调用和结果处理。以下是对 DispatcherHandler 组件源码实现逻辑和步骤的详细分析:初始化过程ApplicationContextAware 实现:DispatcherHandler 实现了 ApplicationContextAware 接口,这意味着它可以访问到 Spring 应用上下文中的 Bean。HandlerMapping、HandlerAdapter 和 HandlerResultHandler 的初始化:DispatcherHandler 在初始化时会查找 Spring 应用上下文中所有的 HandlerMapping、HandlerAdapter 和 HandlerResultHandler 并初始化它们。 protected void initStrategies(ApplicationContext context) { // ... 省略部分代码 ... this.handlerMappings = ...; this.handlerAdapters = ...; this.resultHandlers = ...; }请求处理过程获取 HandlerMappings:DispatcherHandler 会通过 handlerMappings 来查找能够处理当前请求的 HandlerMapping。映射请求到 Handler:使用找到的 HandlerMapping 将请求映射到具体的处理器(可能是一个 @Controller 方法或者一个 RouterFunction)。调用 Handler:一旦找到处理器,DispatcherHandler 会使用适当的 HandlerAdapter 来调用处理器。处理结果:处理器的执行结果会被 HandlerResultHandler 处理,生成响应。核心方法:handleDispatcherHandler 的核心方法是 handle,它定义了请求处理的流程:public Mono<Void> handle(ServerWebExchange exchange) { // 检查是否初始化了 handlerMappings if (this.handlerMappings == null) { return createNotFoundError(); } // 使用 handlerMappings 来查找 handler return Flux.fromIterable(this.handlerMappings) .concatMap(mapping -> mapping.getHandler(exchange)) .next() // 获取第一个 handler .switchIfEmpty(createNotFoundError()) // 如果没有找到 handler,返回错误 .flatMap(handler -> invokeHandler(exchange, handler)) // 调用 handler .flatMap(result -> handleResult(exchange, result)); // 处理结果 }错误处理createNotFoundError:如果没有找到合适的处理器,DispatcherHandler 会创建一个表示 "Not Found" 的响应。其他组件的协同工作HandlerMapping:负责将请求 URL 映射到具体的处理器。HandlerAdapter:负责调用具体的处理器,Spring WebFlux 支持多种类型的处理器,HandlerAdapter 使得 DispatcherHandler 无需关心具体的调用细节。HandlerResultHandler:负责处理处理器的返回值,并将其转换为 HTTP 响应。DispatcherHandler 的设计使得它非常灵活,可以很容易地扩展新的 HandlerMapping、HandlerAdapter 或 HandlerResultHandler 来支持不同的处理器类型和返回类型。以上就是 DispatcherHandler 组件的源码实现逻辑和步骤的分析。通过这种方式,Spring WebFlux 能够以非阻塞的方式处理 Web 请求,提高应用的性能和可伸缩性。转载自https://www.cnblogs.com/wgjava/p/18282994
  • [技术干货] 深度长文解析SpringWebFlux响应式框架15个核心组件源码(一)
    Spring WebFlux 介绍Spring WebFlux 是 Spring Framework 5.0 版本引入的一个响应式 Web 框架,它与 Spring MVC 并存,提供了一种全新的编程范式,支持异步非阻塞的 Web 应用开发。WebFlux 完全基于响应式编程模型,支持 Reactive Streams 规范,可以在诸如 Netty、Undertow 以及 Servlet 3.1+ 容器上运行。WebFlux 的核心控制器是 DispatcherHandler,它类似于 Spring MVC 中的 DispatcherServlet,负责将请求分发给相应的处理器。DispatcherHandler 通过查找 Spring 配置中的 HandlerMapping、HandlerAdapter 和 HandlerResultHandler 来处理请求。在 WebFlux 中,Flux 和 Mono 是 Reactor 库中的两个基本概念,分别用于表示包含 0 到 N 个元素和 0 或 1 个元素的异步序列。Flux 可以用于表示一个包含多个响应式元素的流,而 Mono 用于表示单个元素的响应式流。Spring WebFlux 支持多种编程模式,包括基于注解的控制器和函数式端点。开发者可以使用 @RestController 注解来创建响应式控制器,并使用 @GetMapping、@PostMapping 等注解来处理 HTTP 请求。同时,WebFlux 也支持使用 WebClient 作为非阻塞的 HTTP 客户端来与其它服务进行通信。WebFlux 的并发模型与传统的 Spring MVC 有显著不同。它利用了少量的线程来处理大量的并发请求,这得益于其非阻塞的特性。当运行在 Netty 服务器上时,WebFlux 使用事件循环线程来处理请求,避免了传统 Servlet 容器中每个请求都需要一个线程的模型。Spring WebFlux 的适用场景主要是 IO 密集型的应用,例如微服务网关,它可以显著提升对下游服务转发的吞吐量。然而,如果现有的 Spring MVC 应用能够满足性能需求,并且项目中使用了许多基于 Servlet 线程模型的库,那么可能没有必要迁移到 WebFlux。源码层面,Spring WebFlux 的请求处理流程涉及到多个组件,包括 Netty 服务器的初始化、请求的接收、DispatcherHandler 的请求分发,以及最终的请求处理和响应。在 Netty 服务器中,请求处理涉及到 ChannelHandler,ConnectionObserver,以及 HttpHandler 等多个组件。这些组件协同工作,实现了 WebFlux 的非阻塞和响应式特性。Spring WebFlux 都有哪些核心组件Spring WebFlux 包含多个核心组件,它们共同构成了完整的响应式 Web 应用框架。下面是一些主要的核心组件:DispatcherHandler:这是 WebFlux 的中央调度器,类似于 Spring MVC 中的 DispatcherServlet。它负责发现和调度 HTTP 请求处理器(handlers),并处理请求映射、调用和结果处理。HandlerMapping:这个接口用于将请求映射到对应的处理器(handler)。它在应用程序上下文中被检测到,并用于确定请求应该由哪个处理器处理。HandlerAdapter:这个接口帮助 DispatcherHandler 调用任何类型的处理器,而不需要关心具体的调用方式。它为不同的处理器提供了调用策略。HandlerResultHandler:这个接口处理处理器调用后的结果,并生成最终的响应。它负责将处理器的结果转换为客户端可以接收的格式。WebFilter:WebFilter 接口定义了一组过滤器,这些过滤器可以对请求和响应进行预处理和后处理。ServerWebExchange:这个类封装了 HTTP 请求和响应的所有信息,例如请求头、请求体、URI、参数等。ServerHttpRequest 和 ServerHttpResponse:这两个类分别代表服务器接收的 HTTP 请求和发送的 HTTP 响应。WebSession:用于管理特定客户端的会话信息。Reactive Streams:WebFlux 基于 Reactive Streams 规范,使用非阻塞背压机制来处理数据流。Reactor 库:作为 Spring 5 的反应式编程基础,Reactor 提供了非阻塞的编程模型和工具,包括 Flux 和 Mono 等反应式类型。WebClient:这是 Spring 5 中引入的非阻塞、支持响应式流的 HTTP 客户端,用于与其它服务进行通信。Spring Data Reactive:提供对响应式数据访问的支持,例如 Reactive Repositories。Spring Security Reactive:提供对响应式安全访问控制的支持。HttpHandler:定义了最低级别的反应式 HTTP 请求处理合同,作为不同运行时之间的共同基础。ContextPathCompositeHandler:允许在不同的上下文路径上注册多个应用程序。这些组件共同工作,为开发人员提供了一个强大且灵活的响应式 Web 应用开发平台。通过这些组件,开发者可以构建出能够高效处理大量并发请求的应用程序。下面针对这些组件,V 哥将一一详细介绍核心源码的实现过程,帮助兄弟们彻底理解。转载自https://www.cnblogs.com/wgjava/p/18282994
  • [技术干货] SpringBootWeb 篇-入门了解 Apache POI 使用方法 -转载
     1.0 Apache POI 概述         Apache POI 是一个处理 Miscrosoft Office 各种文件格式的开源项目。简单来说就是,开源使用 POI 在 Java 程序中对 Miscrosoft Office 各种文件进行读写操作。          一般情况下, POI 都是用于操作 Excel 文件。           2.0 使用 Apache POI 读写 Excel 文件         2.1 写入 Excel 文件         首先添加 Apache POI 依赖:  <dependency>     <groupId>org.apache.poi</groupId>     <artifactId>poi</artifactId>     <version>3.16</version> </dependency> <dependency>     <groupId>org.apache.poi</groupId>     <artifactId>poi-ooxml</artifactId>     <version>3.16</version> </dependency> 具体用到的方法:          1)创建 XSSFWorkbook对象:          使用 XSSFWorkbook 创建 XLSX 格式的 Excel 文件。  Workbook workbook = new XSSFWorkbook();         2)创建工作表对象:  Sheet sheet = workbook.createSheet("Sheet1"); // 创建新的工作表         3)创建行和单元格对象:  Row row = sheet.createRow(0); // 创建新的行 Cell cell = row.createCell(0); // 创建新的单元格         4)设置具体的值:  cell.setCellValue("Hello, World!");//在单元格中写入的内容         5)将在内存创建的 Excel 的文件保存到磁盘中:  FileOutputStream file = new FileOutputStream("example.xlsx"); workbook.write(file);         2.2 写入 Excel 文件代码演示 import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest;   import java.io.File; import java.io.FileOutputStream; import java.io.IOException;   @SpringBootTest(classes = {demo.demo2.class})   public class demo2 {       /**      * 写入 Excel 文件操作      */     public void write() throws IOException {           //创建 XSSFWorkbook 对象         XSSFWorkbook workbook = new XSSFWorkbook();         //创建 Sheet 对象,指定名称 sheet1         XSSFSheet sheet = workbook.createSheet("sheet1");         //创建行,创建第二行的行对象         XSSFRow row = sheet.createRow(1);         //创建单元格,创建第二格单元格对象         XSSFCell cell = row.createCell(1);         //设置具体的值         cell.setCellValue("姓名");           //创建单元格,创建第三格单元格对象         XSSFCell cell1 = row.createCell(2);         //设置具体的值         cell1.setCellValue("性别");           //创建第三行对象         XSSFRow row1 = sheet.createRow(2);         //创建第二格对象         XSSFCell cell2 = row1.createCell(1);         cell2.setCellValue("小板");         //创建第三格对象         XSSFCell cell3 = row1.createCell(2);         cell3.setCellValue("男");           //创建第四行对象         XSSFRow row2 = sheet.createRow(3);         //创建第二格对象         XSSFCell cell4 = row2.createCell(1);         //设置具体的值         cell4.setCellValue("童童");         //创建第三格对象         XSSFCell cell5 = row2.createCell(2);         cell5.setCellValue("女");           //将该 Excel 从内存中放到磁盘中         //首先创建存放的文件         FileOutputStream out = new FileOutputStream(new File("D:\\software\\code\\example.xlsx"));         workbook.write(out);           //最后需要关闭资源         out.close();         workbook.close();       }     @Test     public void test11() throws IOException {         write();     } }  运行结果:           2.3 读取 Excel 文件         首先需要添加 POI 库的依赖,在前面写入 Excel 文件中已经添加了,这里就没有必要继续添加依赖了。  <dependency>     <groupId>org.apache.poi</groupId>     <artifactId>poi</artifactId>     <version>3.16</version> </dependency> <dependency>     <groupId>org.apache.poi</groupId>     <artifactId>poi-ooxml</artifactId>     <version>3.16</version> </dependency> 相关的方法:          1)指定读取的 Excel 文件:          通过 new File("路径") Flie 对象作为参数来指定要读取的 Excel 文件。  FileInputStream file = new FileInputStream(new File("sample.xlsx")); Workbook workbook = WorkbookFactory.create(file);         2)获取 Sheet 对象:          根据索引或者名字来获取指定的 Sheet 对象。  //通过名字来获取 XSSFSheet sheet1 = workbook.getSheet("指定名字来获取"); //通过索引来获取 XSSFSheet sheet2 = workbook.getSheetAt(0);         3)获取 Row 对象          根据索引来获取 Row 对象。  XSSFRow row = sheet.getRow(1);         获取 Sheet 最后一行的行数。  int lastRowNum = sheet.getLastRowNum();         4)获取单元格对象          根据索引来获取 Cell 对象。  XSSFCell cell = row.getCell(1);         5)获取到单元格中的值          通过 cell.getStringCellValue() 方法来获取单元格中的值。  String stringCellValue = cell.getStringCellValue();         2.4 读取 Excel 文件代码演示     public void read() throws Exception {         //创建字节输入流文件对象         File file = new File("D:\\software\\code\\example.xlsx");         FileInputStream in = new FileInputStream(file);           //再进一步封装         XSSFWorkbook workbook = new XSSFWorkbook(in);         //获取 Sheet 对象,根据索引获取         XSSFSheet sheet = workbook.getSheetAt(0);           //获取Row对象         //获取最后一行的行数         int lastRowNum = sheet.getLastRowNum();         for (int i = 1; i <= lastRowNum ; i++) {             XSSFRow row = sheet.getRow(i);             String stringCellValue1 = row.getCell(1).getStringCellValue();             String stringCellValue2 = row.getCell(2).getStringCellValue();             System.out.println(stringCellValue1 + " " + stringCellValue2);         }                  //最后关闭资源         workbook.close();         in.close();     }       @Test     public void test12() throws Exception {         read();     }  运行结果:  ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/Tingfeng__/article/details/139752796 
  • [技术干货] SpringMVC:@ResponseBody注解与HttpServletResponse对象
    在Spring MVC框架中,@ResponseBody注解和HttpServletResponse对象都扮演着将处理结果发送回客户端的重要角色,但它们的使用方式和目的有所不同。@ResponseBody@ResponseBody注解用于将方法的返回值绑定到web响应体(response body)上。当你使用@ResponseBody注解一个方法的返回值时,Spring会自动选择一个合适的HttpMessageConverter,将返回值转换为对应的格式(如JSON、XML等),并写入HTTP响应体中。这通常用于RESTful Web服务中,当你需要直接返回数据(如JSON或XML)给客户端时。使用示例@RestController public class MyController { @GetMapping("/greeting") @ResponseBody // 通常与@RestController一起使用时可以省略 public String greeting() { return "Hello, World!"; } // 使用@RestController时,下面的@ResponseBody可以省略 @GetMapping("/jsonGreeting") public MyResponseObject jsonGreeting() { MyResponseObject response = new MyResponseObject(); response.setMessage("Hello in JSON"); return response; // Spring将自动使用HttpMessageConverter转换为JSON } }HttpServletResponseHttpServletResponse是Servlet API的一部分,它代表了Servlet对客户端的响应。你可以通过它来直接控制HTTP响应的各个方面,包括状态码、响应头以及响应体。当你需要更细粒度的控制响应时(比如设置特定的响应头、发送二进制文件等),HttpServletResponse就显得非常有用。使用示例@Controller public class MyServletController { @GetMapping("/customResponse") public void customResponse(HttpServletResponse response) throws IOException { response.setContentType("text/plain"); response.setCharacterEncoding("UTF-8"); response.getWriter().write("Custom response using HttpServletResponse"); } @GetMapping("/fileDownload") public void fileDownload(HttpServletResponse response) throws IOException { // 设置响应头 response.setContentType("application/pdf"); response.setHeader("Content-Disposition", "attachment; filename=\"example.pdf\""); // 假设你有一个获取文件输入流的方法 InputStream inputStream = getFileAsStream("path/to/example.pdf"); // 使用ServletOutputStream将文件内容写入响应 ServletOutputStream outputStream = response.getOutputStream(); IOUtils.copy(inputStream, outputStream); // 使用Apache Commons IO库来复制流 outputStream.flush(); } }总结@ResponseBody主要用于将方法的返回值自动转换为JSON、XML等格式,并写入HTTP响应体中,适用于RESTful Web服务。HttpServletResponse提供了对HTTP响应的细粒度控制,适用于需要直接操作响应头、响应体等场景。在Spring MVC中,@RestController注解已经隐式地为所有处理方法的返回值应用了@ResponseBody注解,因此在@RestController注解的控制器中,你可以省略@ResponseBody。转载自https://www.cnblogs.com/cydestiny/p/18308254
  • [技术干货] ApplicationContext 详细介绍
    一、概述ApplicationContext是Spring框架中的一个核心接口,它扩展了BeanFactory接口,并提供了更全面的功能。ApplicationContext不仅包含了BeanFactory的所有功能,还添加了国际化支持、资源访问、事件传播、以及更高级的容器特性,如自动装配和生命周期管理等。它是Spring应用中的核心容器,负责管理和配置应用中的对象(称为beans)。二、主要功能Bean工厂:作为BeanFactory的扩展,ApplicationContext提供了更丰富的Bean管理功能。它可以自动检测并注册Bean定义,管理Bean的生命周期,支持依赖注入等。国际化支持:通过MessageSource接口的实现,ApplicationContext支持国际化的消息资源。这允许开发者根据用户的语言环境提供不同语言的消息。资源访问:ApplicationContext提供了对资源的访问能力,如文件、URL等。这通过Resource接口和ResourceLoader接口实现,使得访问外部资源变得简单。事件传播:ApplicationContext实现了ApplicationEventPublisher接口,允许发布事件到注册的监听器。这是实现松耦合组件之间通信的一种有效方式。环境抽象:ApplicationContext提供了对环境的抽象,包括配置文件和程序化配置。这允许开发者在不同环境下(如开发、测试、生产)灵活地配置应用。Web支持:对于Web应用,Spring提供了WebApplicationContext接口,它是ApplicationContext的扩展,提供了对Web环境的支持,如请求处理、会话管理等。三、Bean的生命周期在ApplicationContext中,Bean的生命周期包括以下几个阶段:实例化:首先,Spring容器会实例化Bean。属性设置:然后,Spring容器会将Bean的依赖关系(通过构造器注入或setter方法注入)注入到Bean中。BeanNameAware**和**BeanFactoryAware`接口:如果Bean实现了这些接口,Spring容器会调用相应的方法,将Bean的名称和BeanFactory传递给Bean。**BeanPostProcessor接口**:Spring容器会调用实现了BeanPostProcessor接口的Bean的postProcessBeforeInitialization`方法,在Bean的初始化之前进行处理。初始化:如果Bean实现了InitializingBean接口或在其配置中指定了init-method,Spring容器会调用相应的方法来初始化Bean。**BeanPostProcessor接口(续)**:在Bean初始化之后,Spring容器会调用实现了BeanPostProcessor接口的Bean的postProcessAfterInitialization`方法。使用:Bean现在已准备好被应用使用。销毁:如果Bean实现了DisposableBean接口或在其配置中指定了destroy-method,当容器关闭时,Spring容器会调用相应的方法来销毁Bean。四、配置方式ApplicationContext的配置可以通过多种方式实现,包括基于XML的配置文件、基于注解的配置(如@Component、@Autowired等)、基于Java的配置(通过@Configuration和@Bean注解)以及混合使用这些方式。五、总结ApplicationContext是Spring框架中功能强大的核心容器,它扩展了BeanFactory接口,并提供了更全面的功能。通过ApplicationContext,开发者可以轻松地管理应用中的对象(Bean),并利用Spring提供的各种特性,如依赖注入、国际化支持、事件传播等,来构建松耦合、可扩展的应用。转载自https://www.cnblogs.com/kongyang/p/18309195
  • [技术干货] 一文分清SaaS、Pass和LaaS及代表性应用
    前言在云计算的浪潮中,SaaS(Software as a Service)、Pass(Passport as a Service,虽然并非传统云计算服务模式,但在此文中我们假设其为一个基于认证或通行证的服务模式)和LaaS(Location as a Service)作为三大服务模式,各自在不同的领域发挥着重要作用。本文将为您详细解析这三种服务模式,并介绍其代表性应用。一、SaaS(Software as a Service)SaaS,即软件即服务,是一种通过互联网提供软件应用的方式。用户无需购买、安装和维护软件,只需按需租用服务,通过云端访问和使用软件。SaaS的特点包括:使用先进的应用程序、无需购买和维护软硬件、只为自己使用的功能付费、无需安装客户端软件、轻松增强员工移动性等。代表性应用:SalesForce:作为CRM(客户关系管理)领域的领军企业,SalesForce提供全面的销售、市场营销和客户服务解决方案,帮助企业实现客户关系的数字化转型。Slack:作为一款团队协作工具,Slack通过即时通讯、文件共享等功能,帮助团队成员进行高效的沟通和协作,提升团队整体效率。ERP软件:SaaS模式下的ERP软件,如金蝶云、用友云等,可以帮助企业实现精细化管理,降低运营成本,提高工作效率。二、Pass(Passport as a Service)虽然Pass并非传统云计算服务模式,但我们可以将其理解为一种基于认证或通行证的服务模式。Pass旨在提供安全、便捷的认证和通行证服务,使用户能够轻松访问和使用各种资源和服务。代表性应用:数字身份证:在数字化时代,数字身份证成为了一种新型的Pass服务。通过数字身份证,用户可以安全、便捷地进行身份验证,享受各种线上服务。企业通行证:在企业内部,Pass服务可以为企业提供统一的通行证解决方案。员工可以通过企业通行证轻松访问公司内部的各种资源和服务,提高工作效率。三、LaaS(Location as a Service)LaaS,即位置即服务,是一种基于位置信息的服务模式。通过获取和利用位置数据,LaaS可以为用户提供与地理位置相关的各种服务,如导航、位置跟踪、位置分析等。代表性应用:导航应用:如高德地图、百度地图等导航应用,通过LaaS服务获取用户的位置信息,为用户提供精准的导航服务。物流跟踪:在物流领域,LaaS服务可以帮助企业实时跟踪货物的位置信息,提高物流效率和服务质量。位置分析:通过LaaS服务获取的位置数据,企业可以进行深入的位置分析,了解用户的活动轨迹、消费习惯等信息,为精准营销和产品优化提供有力支持。总结SaaS、Pass和LaaS作为云计算的三大服务模式,各自在不同的领域发挥着重要作用。通过了解这些服务模式和其代表性应用,我们可以更好地把握云计算的发展趋势和应用前景。
  • [技术干货] 使用@RequestParam和@RequestBody在Spring MVC中处理HTTP请求
    知识点在Spring MVC中,处理HTTP请求时,我们经常需要提取请求的不同部分,如查询参数、路径变量、请求头以及请求体。@RequestParam和@RequestBody是两个常用的注解,用于帮助我们从HTTP请求中抽取这些信息。尽管它们通常用于不同的场景,但Spring允许我们在同一个方法中使用它们,以满足复杂的数据处理需求。 @RequestParam@RequestParam注解通常用于从请求URL的查询参数中提取数据。当客户端发起一个GET请求,并在URL后附带查询参数时,我们可以使用@RequestParam来捕获这些参数的值。 @RequestBody@RequestBody注解则用于处理请求体中的数据。这在客户端发送POST、PUT或PATCH请求,并带有请求体(例如JSON或XML格式的数据)时非常有用。Spring会使用HTTP消息转换器(如MappingJackson2HttpMessageConverter)将请求体的内容转换为对应的Java对象。在同一方法中使用@RequestParam和@RequestBody在某些情况下,我们可能希望在一个请求中同时处理查询参数和请求体数据。虽然这在RESTful API设计中可能不是最佳实践,但在某些特定的应用场景中,这样的需求是合理的。Spring MVC允许我们在同一个方法中同时使用@RequestParam和@RequestBody。下面是一个示例,展示了如何在Spring MVC控制器中同时使用这两个注解:import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api") public class MyController { @PostMapping("/submit") public String submitData( @RequestParam("name") String name, @RequestBody MyData myData) { // 使用name变量处理查询参数 // 使用myData对象处理请求体中的数据 // 示例逻辑:打印接收到的数据 System.out.println("Name from query parameter: " + name); System.out.println("Data from request body: " + myData); // 返回响应 return "Data submitted successfully"; } // 定义与请求体数据对应的Java类 static class MyData { private String field1; private int field2; // 省略getter和setter方法... @Override public String toString() { return "MyData{" + "field1='" + field1 + '\'' + ", field2=" + field2 + '}'; } } }在这个例子中,我们定义了一个submitData方法,它接受一个名为name的查询参数和一个MyData类型的请求体对象。当客户端发送一个POST请求到/api/submit,并带有查询参数name和请求体中的JSON数据时,Spring MVC会自动将这些数据绑定到对应的方法参数上。注意事项当同时使用@RequestParam和@RequestBody时,确保客户端的请求格式正确,即同时包含查询参数和请求体。如果请求体中的数据无法映射到指定的Java对象,Spring MVC会抛出异常。因此,确保请求体的格式与Java对象的字段匹配,并且使用了正确的HTTP消息转换器。在设计RESTful API时,通常建议将不同的数据类型放在不同的请求部分中,以保持API的一致性和清晰度。例如,查询参数通常用于过滤或分页,而请求体则用于发送完整的资源表示。通过合理使用@RequestParam和@RequestBody注解,我们可以轻松地处理各种复杂的HTTP请求,并在Spring MVC控制器中实现灵活的数据绑定和验证。
  • [技术干货] 【监控】spring actuator源码速读-转载
     1.前言 版本:spring-boot-starter-actuator  2.6.3  阅读源码一定要带着疑问去阅读,这个疑问就是你阅读的主线,不然在浩如烟海的源码里面很容易迷路。我们当前的疑问是什么?之前我们已经聊过spring actuator的使用了:  Spring Boot 监控_springboot 监控-CSDN博客  本文要搞清楚的两个问题在于:  EndPoint是怎么被注入IOC又怎么暴露出去能通过HTTP访问到的?  EndPoint是怎么实现监控能力的?  2.先搂一眼EndPoint 首先我们找一个EndPoint来看看,此处以HealthEndPoint为例。点看源码我们可以看到这个EndPoint被@EndPoint注解所注释,id为health。然后其中的2个方法被@ReadOperation所注释:   这里其实猜都能猜到被@EndPoint注解,然后被注解的类被归类为EndPoint,然后被集中暴露出去,变成可访问的。  3.EndPoint如何被注入 我们是通过stater来引入actuator的,Spring Boot体系内如何注入stater的?那肯定是通过autoConfiguration来的撒。点进actuator的配置文件也可以看到:   于是我们来到spring-boot-starter-actuator来看看,看看它的spring.factories里面注入了些什么:  见名知意了,这些众多的XXXautoConfiguration是拿来做什么的就不必多说了吧,health、metrics......分门别类,各种EndPoint的autoConfiguration。   我们来看看HealthEndpointAutoConfiguration里面做了什么:  其实就是加载了HealthEndpointConfiguration、ReactiveHealthEndpointConfiguration、HealthEndpointWebExtensionConfiguration、HealthEndpointReactiveWebExtensionConfiguration这几个类  @Configuration(     proxyBeanMethods = false ) @ConditionalOnAvailableEndpoint(     endpoint = HealthEndpoint.class ) @EnableConfigurationProperties({HealthEndpointProperties.class}) @Import({HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class, HealthEndpointReactiveWebExtensionConfiguration.class}) public class HealthEndpointAutoConfiguration {     public HealthEndpointAutoConfiguration() {     } } 我们先看HealthEndpointConfiguration,它里面向IOC中注入了health的Registry,HealthContributorRegistry中注册了HealthContributor类型的实体。   我们随便打开一个health的EndPoint,发现它都继承同一个父类:   而这个父类实现了HealthContributor接口:   所以其实就是在将注册器注入IOC的时候,就将所有属于该类型的EndPoint注册到注册器中了。  4.EndPoint如何被暴露 4.1.如何通过http暴露 在SpringBoot体系中,谁来负责http请求?那当然是SpringMVC的DispatcherServlet。把path和对应处理的类注册到DispatcherServlet中就行了。spring actuator就是这样对外通过HTTP的方式暴露EndPoint的。  回到spring.factories,找ManagementContextAutoConfiguration,这个类中完成了通过HTTP的方式来暴露EndPoint的过程:   这个类的代码并不多,我们去掉不要的部分,把和对外暴露EndPoint相关的代码保留,来读一下:  @ManagementContextConfiguration(     proxyBeanMethods = false ) @ConditionalOnWebApplication(     type = Type.SERVLET )//只能在Web环境中生效 public class ServletEndpointManagementContextConfiguration {     public ServletEndpointManagementContextConfiguration() {     } ​     @Configuration(         proxyBeanMethods = false     )     @ConditionalOnClass({DispatcherServlet.class})//当SpringMVC存在时向IOC中注入     public static class WebMvcServletEndpointManagementContextConfiguration {         public WebMvcServletEndpointManagementContextConfiguration() {         }         @Bean         public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier, DispatcherServletPath dispatcherServletPath) {             return new ServletEndpointRegistrar(dispatcherServletPath.getRelativePath(properties.getBasePath()), servletEndpointsSupplier.getEndpoints());//核心的一步,将EndPoint和对于的Path注册给DispatcherServlet         }     } }  最后就是开头我们看见的在HealthEndPoint被@ReadOperation注解的方法,就相当于@RequetMapping,拿来处理读请求的。  4.2.如何通过jmx暴露 jmx的对外暴露更简单。直接找JmxEndpointAutoConfiguration:   进去整个逻辑一目了然,去扫Jmx的EndPoint然后注册进mBeanServer里:   5.EndPoint是怎么实现监控能力的 spring actuator获取各种监控的值是怎么获取到的?  内置指标获取: Spring Boot 提供了一些内置的监控指标获取器,用于获取常见的监控数据,比如 JVM 内存使用情况、系统负载、数据库连接池状态等。这些指标获取器会周期性地获取数据,并将其暴露为 Actuator 端点,以便外部系统或者工具可以通过相应的接口来获取。例如,MemoryHealthIndicator 获取 JVM 内存使用情况,DataSourceHealthIndicator 获取数据库连接池状态等。  自定义指标获取: 除了内置的指标获取器外,开发者还可以通过实现 HealthIndicator 接口来自定义监控指标获取器,用于获取应用程序特定的监控数据。HealthIndicator 接口定义了一个 health() 方法,用于返回健康状态信息。开发者可以在 health() 方法中编写自定义的监控逻辑,比如检查某个依赖服务的可用性、计算某个指标的值等。  JMX 获取: Spring Actuator 还可以通过 Java Management Extensions (JMX) API 来获取一些系统级的监控数据,比如 JVM 运行时信息、操作系统信息等。Spring Actuator 中的一些监控指标获取器会使用 JMX API 来获取数据,然后将其暴露为 Actuator 端点。例如,JvmInfoContributor 使用 JMX API 来获取 JVM 运行时信息。  系统调用获取: 有些监控数据可能需要通过系统调用来获取,比如获取操作系统的 CPU 使用率、磁盘使用情况等。Spring Actuator 中的一些监控指标获取器会使用系统调用来获取这些数据,然后将其暴露为 Actuator 端点。  6.知道这些的意义是什么 本文是作者java监控系列文章的第三篇,之前两篇文章我们着重讲了java监控的基石——JMX。  【JMX】JAVA监控的基石-CSDN博客  详解tomcat中的jmx监控-CSDN博客  在spring actuator里面我们知道了目前市面上一个成熟的框架是如何通过http、JMX等不同方式来对外暴露监控能力的。基本上走到这里我们就已经对JAVA整个的监控技术体系最核心的部分有了认识了。作为监控框架来说核心点有哪些?无非是:  获取数据  对外暴露口子  监控的核心肯定是怎么获取数据以及如何将获取的数据暴露出去,只要这两点搞定了,后面的对接各种可视化平台就很好办了。所有知道为啥这篇文章为啥要关心spring actuator这些地方了吧,主要是看看实现思想。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/Joker_ZJN/article/details/136154345 
  • [专题汇总] 快进来宝子们,二月份干货汇总来了。
     大家好,二月的合集又来了,本次涵盖了java,linux,spirng诸多内容供大家学习。 1.Vue中的$nextTick有什么作用?【转】 https://bbs.huaweicloud.com/forum/thread-02109144317537776031-1-1.html  2.基于Python的地图绘制教程【转】 https://bbs.huaweicloud.com/forum/thread-0240144319944756030-1-1.html  3.hcache 介绍(1)--Ehcache 功能特性 【转】 https://bbs.huaweicloud.com/forum/thread-0268144320319800023-1-1.html  4.Java 操作 XML(1)--DOM 方式处理 XML【转】 https://bbs.huaweicloud.com/forum/thread-02109144320463478034-1-1.html  5.Java操作XML(3)--StAX方式处理XML 【转】 https://bbs.huaweicloud.com/forum/thread-0240144320615655031-1-1.html  6.Java操作XML(4)--使用woodstox处理XML【转】 https://bbs.huaweicloud.com/forum/thread-0270144320690142025-1-1.html  7.Java操作XML(5)--使用JDOM处理XML【转】 https://bbs.huaweicloud.com/forum/thread-0240144320817429032-1-1.html  8.Java操作XML(6)--使用dom4j处理XML【转】 https://bbs.huaweicloud.com/forum/thread-02101144320893290020-1-1.html  9.基于原生Go语言开发一个博客系统【转】 https://bbs.huaweicloud.com/forum/thread-0269144558086016006-1-1.html  10.详解Golang如何使用Debug库优化代码【转】 https://bbs.huaweicloud.com/forum/thread-0225144558556243005-1-1.html  11.Python3中的指针你了解吗【转】 https://bbs.huaweicloud.com/forum/thread-0274144558593208011-1-1.html  12.Python绘图实现坐标轴共享与复用详解【转】 https://bbs.huaweicloud.com/forum/thread-02104144558721772010-1-1.html  13.使用Golang开发一个简易版shell【转】 https://bbs.huaweicloud.com/forum/thread-0226144559187797004-1-1.html  14.python导入其它py文件的实现步骤【转】 https://bbs.huaweicloud.com/forum/thread-0284144559442791006-1-1.html  15.Python property函数的具体使用【转】 https://bbs.huaweicloud.com/forum/thread-0259144559705810008-1-1.html  16.正则表达式的神奇世界之表达、匹配和提取全解析【转】 https://bbs.huaweicloud.com/forum/thread-02104144569438876012-1-1.html  17.正则去除中括号(符号)及里面包含的内容(最新推荐)【转】 https://bbs.huaweicloud.com/forum/thread-0226144569604407008-1-1.html  18.Javaweb项目启动Tomcat常见的报错解决方案【转】 https://bbs.huaweicloud.com/forum/thread-0274144571948983013-1-1.html  19.SpringBoot+Vue前后端分离实现审核功能的示例【转】 https://bbs.huaweicloud.com/forum/thread-0269144572025869007-1-1.html  20.Java中Collections.sort()排序方法举例详解【转】 https://bbs.huaweicloud.com/forum/thread-0269144572170830008-1-1.html  21.Java中回调函数 (callback) 及其实际应用场景【转】 https://bbs.huaweicloud.com/forum/thread-0259144572260172012-1-1.html  22.Redis实现商品秒杀的示例代码【转】 https://bbs.huaweicloud.com/forum/thread-0274144574350854015-1-1.html 
  • [技术干货] 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框架时,需要根据项目的具体需求和团队的实际情况进行权衡。
  • [技术干货] 多线程事务回滚的处理【转】
     转自 : https://blog.csdn.net/weixin_43225491/article/details/117705686背景介绍  1.最近有一个大数据量插入的操作入库的业务场景,需要先做一些其他修改操作,然后在执行插入操作,由于插入数据可能会很多,用到多线程去拆分数据并行处理来提高响应时间,如果有一个线程执行失败,则全部回滚.  2.在spring中可以使用@Transactional注解去控制事务,使出现异常时会进行回滚,在多线程中,这个注解则不会生效,如果主线程需要先执行一些修改数据库的操作,当子线程在进行处理出现异常时,主线程修改的数据则不会回滚,导致数据错误。 3.下面用一个简单示例演示多线程事务. 公用的类和方法  /**      * 平均拆分list方法.      * @param source      * @param n      * @param <T>      * @return      */     public static <T> List<List<T>> averageAssign(List<T> source,int n){         List<List<T>> result=new ArrayList<List<T>>();         int remaider=source.size()%n;          int number=source.size()/n;          int offset=0;//偏移量         for(int i=0;i<n;i++){             List<T> value=null;             if(remaider>0){                 value=source.subList(i*number+offset, (i+1)*number+offset+1);                 remaider--;                 offset++;             }else{                 value=source.subList(i*number+offset, (i+1)*number+offset);             }             result.add(value);         }         return result;     }      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  /**  线程池配置  * @version V1.0  * @since 2021-06-08 15:39  */ public class ExecutorConfig {     private static int maxPoolSize = Runtime.getRuntime().availableProcessors();     private volatile static ExecutorService executorService;     public static ExecutorService getThreadPool() {         if (executorService == null){             synchronized (ExecutorConfig.class){                 if (executorService == null){                     executorService =  newThreadPool();                 }             }         }         return executorService;     }      private static  ExecutorService newThreadPool(){         int queueSize = 500;         int corePool = Math.min(5, maxPoolSize);         return new ThreadPoolExecutor(corePool, maxPoolSize, 10000L, TimeUnit.MILLISECONDS,             new LinkedBlockingQueue<>(queueSize),new ThreadPoolExecutor.AbortPolicy());     }     private ExecutorConfig(){} }      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     26  /** 获取sqlSession  * @author 86182  * @version V1.0  * @since 2021-06-03 15:08  */ @Component public class SqlContext {     @Resource     private SqlSessionTemplate sqlSessionTemplate;      public SqlSession getSqlSession(){         SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();         return sqlSessionFactory.openSession();     } }      1     2     3     4     5     6     7     8     9     10     11     12     13     14     15  示例事务不成功操作    /**      * 测试多线程事务.      * @param employeeDOList      */     @Override     @Transactional     public void saveThread(List<EmployeeDO> employeeDOList) {         try {             //先做删除操作,如果子线程出现异常,此操作不会回滚             this.getBaseMapper().delete(null);             //获取线程池             ExecutorService service = ExecutorConfig.getThreadPool();             //拆分数据,拆分5份             List<List<EmployeeDO>> lists=averageAssign(employeeDOList,2);             //执行的线程             Thread []threadArray = new Thread[lists.size()];             //监控子线程执行完毕,再执行主线程,要不然会导致主线程关闭,子线程也会随着关闭             CountDownLatch countDownLatch = new CountDownLatch(lists.size());             AtomicBoolean atomicBoolean = new AtomicBoolean(false);             for (int i =0;i<lists.size();i++){                 List<EmployeeDO> list  = lists.get(i);                 threadArray[i] =  new Thread(() -> {                     try {                         if (!atomicBoolean.get()){                             throw new ServiceException("001","出现异常");                         }                         //批量添加,mybatisPlus中自带的batch方法                         this.saveBatch(list);                     }finally {                         countDownLatch.countDown();                     }                 });             }             for (int i = 0; i <lists.size(); i++){                 service.execute(threadArray[i]);             }             //当子线程执行完毕时,主线程再往下执行             countDownLatch.await();             System.out.println("添加完毕");         }catch (Exception e){             log.info("error",e);             throw new ServiceException("002","出现异常");         }     }      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     26     27     28     29     30     31     32     33     34     35     36     37     38     39     40     41     42     43     44     45  数据库中存在一条数据: 在这里插入图片描述  //测试用例 @RunWith(SpringRunner.class) @SpringBootTest(classes = { ThreadTest01.class, MainApplication.class}) public class ThreadTest01 {      @Resource     private EmployeeBO employeeBO;      /**      *   测试多线程事务.      * @throws InterruptedException      */     @Test     public  void MoreThreadTest2() throws InterruptedException {         int size = 10;         List<EmployeeDO> employeeDOList = new ArrayList<>(size);         for (int i = 0; i<size;i++){             EmployeeDO employeeDO = new EmployeeDO();             employeeDO.setEmployeeName("lol"+i);             employeeDO.setAge(18);             employeeDO.setGender(1);             employeeDO.setIdNumber(i+"XX");             employeeDO.setCreatTime(Calendar.getInstance().getTime());             employeeDOList.add(employeeDO);         }         try {             employeeBO.saveThread(employeeDOList);             System.out.println("添加成功");         }catch (Exception e){             e.printStackTrace();         }     } }      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     26     27     28     29     30     31     32     33  测试结果: 在这里插入图片描述 在这里插入图片描述 可以发现子线程组执行时,有一个线程执行失败,其他线程也会抛出异常,但是主线程中执行的删除操作,没有回滚,Transactional注解没有生效. 使用sqlSession控制手动提交事务   @Resource    SqlContext sqlContext;  /**      * 测试多线程事务.      * @param employeeDOList      */     @Override     public void saveThread(List<EmployeeDO> employeeDOList) throws SQLException {         // 获取数据库连接,获取会话(内部自有事务)         SqlSession sqlSession = sqlContext.getSqlSession();         Connection connection = sqlSession.getConnection();         try {             // 设置手动提交             connection.setAutoCommit(false);             //获取mapper             EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);             //先做删除操作             employeeMapper.delete(null);             //获取执行器             ExecutorService service = ExecutorConfig.getThreadPool();             List<Callable<Integer>> callableList  = new ArrayList<>();             //拆分list             List<List<EmployeeDO>> lists=averageAssign(employeeDOList, 5);             AtomicBoolean atomicBoolean = new AtomicBoolean(true);             for (int i =0;i<lists.size();i++){                 if (i==lists.size()-1){                     atomicBoolean.set(false);                 }                 List<EmployeeDO> list  = lists.get(i);                 //使用返回结果的callable去执行,                 Callable<Integer> callable = () -> {                     if (!atomicBoolean.get()){                         throw new ServiceException("001","出现异常");                     }                   return employeeMapper.saveBatch(list);                 };                 callableList.add(callable);             }             //执行子线程            List<Future<Integer>> futures = service.invokeAll(callableList);             for (Future<Integer> future:futures) {             //如果有一个执行不成功,则全部回滚                 if (future.get()<=0){                     connection.rollback();                      return;                 }             }             connection.commit();             System.out.println("添加完毕");         }catch (Exception e){             connection.rollback();             log.info("error",e);             throw new ServiceException("002","出现异常");         }finally {              connection.close();          }     }              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     26     27     28     29     30     31     32     33     34     35     36     37     38     39     40     41     42     43     44     45     46     47     48     49     50     51     52     53     54     55     56     57     58     59    // sql    <insert id="saveBatch" parameterType="List">     INSERT INTO     employee (employee_id,age,employee_name,birth_date,gender,id_number,creat_time,update_time,status)     values         <foreach collection="list" item="item" index="index" separator=",">         (         #{item.employeeId},         #{item.age},         #{item.employeeName},         #{item.birthDate},         #{item.gender},         #{item.idNumber},         #{item.creatTime},         #{item.updateTime},         #{item.status}             )         </foreach>     </insert>      1     2     3     4     5     6     7     8     9     10     11     12     13     14     15     16     17     18     19  数据库中一条数据: 在这里插入图片描述 测试结果:抛出异常, 在这里插入图片描述 删除操作的数据回滚了,数据库中的数据依旧存在,说明事务成功了. 在这里插入图片描述  成功操作示例:   @Resource     SqlContext sqlContext;     /**      * 测试多线程事务.      * @param employeeDOList      */     @Override     public void saveThread(List<EmployeeDO> employeeDOList) throws SQLException {         // 获取数据库连接,获取会话(内部自有事务)         SqlSession sqlSession = sqlContext.getSqlSession();         Connection connection = sqlSession.getConnection();         try {             // 设置手动提交             connection.setAutoCommit(false);             EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);             //先做删除操作             employeeMapper.delete(null);             ExecutorService service = ExecutorConfig.getThreadPool();             List<Callable<Integer>> callableList  = new ArrayList<>();             List<List<EmployeeDO>> lists=averageAssign(employeeDOList, 5);             for (int i =0;i<lists.size();i++){                 List<EmployeeDO> list  = lists.get(i);                 Callable<Integer> callable = () -> employeeMapper.saveBatch(list);                 callableList.add(callable);             }             //执行子线程            List<Future<Integer>> futures = service.invokeAll(callableList);             for (Future<Integer> future:futures) {                 if (future.get()<=0){                     connection.rollback();                      return;                 }             }             connection.commit();             System.out.println("添加完毕");         }catch (Exception e){             connection.rollback();             log.info("error",e);             throw new ServiceException("002","出现异常");            // throw new ServiceException(ExceptionCodeEnum.EMPLOYEE_SAVE_OR_UPDATE_ERROR);         }     }      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     26     27     28     29     30     31     32     33     34     35     36     37     38     39     40     41     42  测试结果: blog.csdnimg.cn/20210608165840483.png)  数据库中数据: 删除的删除了,添加的添加成功了,测试成功. 在这里插入图片描述 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/weixin_43225491/article/details/117705686 
  • [技术干货] 40 个 SpringBoot 常用注解:让生产力爆表!【转】
    企业开发项目SpringBoot已经是必备框架了,其中注解是开发中的小工具,用好了开发效率大大提升,当然用错了也会引入缺陷。一、Spring Web MVC与Spring Bean注解Spring Web MVC注解@RequestMapping@RequestMapping注解的主要用途是将Web请求与请求处理类中的方法进行映射。Spring MVC和Spring WebFlux都通过RquestMappingHandlerMapping和RequestMappingHndlerAdapter两个类来提供对@RequestMapping注解的支持。@RequestMapping注解对请求处理类中的请求处理方法进行标注;@RequestMapping注解拥有以下的六个配置属性:value:映射的请求URL或者其别名method:兼容HTTP的方法名params:根据HTTP参数的存在、缺省或值对请求进行过滤header:根据HTTP Header的存在、缺省或值对请求进行过滤consume:设定在HTTP请求正文中允许使用的媒体类型product:在HTTP响应体中允许使用的媒体类型提示:在使用@RequestMapping之前,请求处理类还需要使用@Controller或@RestController进行标记,公众 号Java精选,回复java面试,获取面试资料,支持在线刷题。下面是使用@RequestMapping的两个示例:@RequestMapping还可以对类进行标记,这样类中的处理方法在映射请求路径时,会自动将类上@RequestMapping设置的value拼接到方法中映射路径之前,如下:@RequestBody@RequestBody在处理请求方法的参数列表中使用,它可以将请求主体中的参数绑定到一个对象中,请求主体参数是通过HttpMessageConverter传递的,根据请求主体中的参数名与对象的属性名进行匹配并绑定值。此外,还可以通过@Valid注解对请求主体中的参数进行校验。下面是一个使用@RequestBody的示例:@GetMapping@GetMapping注解用于处理HTTP GET请求,并将请求映射到具体的处理方法中。具体来说,@GetMapping是一个组合注解,它相当于是@RequestMapping(method=RequestMethod.GET)的快捷方式。下面是@GetMapping的一个使用示例:@PostMapping@PostMapping注解用于处理HTTP POST请求,并将请求映射到具体的处理方法中。@PostMapping与@GetMapping一样,也是一个组合注解,它相当于是@RequestMapping(method=HttpMethod.POST)的快捷方式。下面是使用@PostMapping的一个示例:@PutMapping@PutMapping注解用于处理HTTP PUT请求,并将请求映射到具体的处理方法中,@PutMapping是一个组合注解,相当于是@RequestMapping(method=HttpMethod.PUT)的快捷方式。下面是使用@PutMapping的一个示例:@DeleteMapping@DeleteMapping注解用于处理HTTP DELETE请求,并将请求映射到删除方法中。@DeleteMapping是一个组合注解,它相当于是@RequestMapping(method=HttpMethod.DELETE)的快捷方式。下面是使用@DeleteMapping的一个示例:@PatchMapping@PatchMapping注解用于处理HTTP PATCH请求,并将请求映射到对应的处理方法中。@PatchMapping相当于是@RequestMapping(method=HttpMethod.PATCH)的快捷方式。下面是一个简单的示例:@ControllerAdvice@ControllerAdvice是@Component注解的一个延伸注解,Spring会自动扫描并检测被@ControllerAdvice所标注的类。@ControllerAdvice需要和@ExceptionHandler、@InitBinder以及@ModelAttribute注解搭配使用,主要是用来处理控制器所抛出的异常信息。首先,我们需要定义一个被@ControllerAdvice所标注的类,在该类中,定义一个用于处理具体异常的方法,并使用@ExceptionHandler注解进行标记。此外,在有必要的时候,可以使用@InitBinder在类中进行全局的配置,还可以使用@ModelAttribute配置与视图相关的参数。使用@ControllerAdvice注解,就可以快速的创建统一的,自定义的异常处理类。下面是一个使用@ControllerAdvice的示例代码:@ResponseBody@ResponseBody会自动将控制器中方法的返回值写入到HTTP响应中。特别的,@ResponseBody注解只能用在被@Controller注解标记的类中。如果在被@RestController标记的类中,则方法不需要使用@ResponseBody注解进行标注。@RestController相当于是@Controller和@ResponseBody的组合注解。下面是使用该注解的一个示例@ExceptionHandler@ExceptionHander注解用于标注处理特定类型异常类所抛出异常的方法。当控制器中的方法抛出异常时,Spring会自动捕获异常,并将捕获的异常信息传递给被@ExceptionHandler标注的方法。面试宝典:https://www.yoodb.com下面是使用该注解的一个示例:@ResponseStatus@ResponseStatus注解可以标注请求处理方法。使用此注解,可以指定响应所需要的HTTP STATUS。特别地,我们可以使用HttpStauts类对该注解的value属性进行赋值。下面是使用@ResponseStatus注解的一个示例:@PathVariable@PathVariable注解是将方法中的参数绑定到请求URI中的模板变量上。可以通过@RequestMapping注解来指定URI的模板变量,然后使用@PathVariable注解将方法中的参数绑定到模板变量上。特别地,@PathVariable注解允许我们使用value或name属性来给参数取一个别名。下面是使用此注解的一个示例:模板变量名需要使用{ }进行包裹,如果方法的参数名与URI模板变量名一致,则在@PathVariable中就可以省略别名的定义。下面是一个简写的示例:提示:如果参数是一个非必须的,可选的项,则可以在@PathVariable中设置require = false@RequestParam@RequestParam注解用于将方法的参数与Web请求的传递的参数进行绑定。使用@RequestParam可以轻松的访问HTTP请求参数的值。下面是使用该注解的代码示例:该注解的其他属性配置与@PathVariable的配置相同,特别的,如果传递的参数为空,还可以通过defaultValue设置一个默认值。示例代码如下:@Controller@Controller是@Component注解的一个延伸,Spring 会自动扫描并配置被该注解标注的类。此注解用于标注Spring MVC的控制器。下面是使用此注解的示例代码:@RestController@RestController是在Spring 4.0开始引入的,这是一个特定的控制器注解。此注解相当于@Controller和@ResponseBody的快捷方式。当使用此注解时,不需要再在方法上使用@ResponseBody注解。下面是使用此注解的示例代码:@ModelAttribute通过此注解,可以通过模型索引名称来访问已经存在于控制器中的model。下面是使用此注解的一个简单示例:与@PathVariable和@RequestParam注解一样,如果参数名与模型具有相同的名字,则不必指定索引名称,简写示例如下:特别地,如果使用@ModelAttribute对方法进行标注,Spring会将方法的返回值绑定到具体的Model上。示例如下:在Spring调用具体的处理方法之前,被@ModelAttribute注解标注的所有方法都将被执行。@CrossOrigin@CrossOrigin注解将为请求处理类或请求处理方法提供跨域调用支持。如果我们将此注解标注类,那么类中的所有方法都将获得支持跨域的能力。使用此注解的好处是可以微调跨域行为。使用此注解的示例如下:@InitBinder@InitBinder注解用于标注初始化WebDataBinider 的方法,该方法用于对Http请求传递的表单数据进行处理,如时间格式化、字符串处理等。下面是使用此注解的示例:二、Spring Bean注解在本小节中,主要列举与Spring Bean相关的4个注解以及它们的使用方式。@ComponentScan@ComponentScan注解用于配置Spring需要扫描的被组件注解注释的类所在的包。可以通过配置其basePackages属性或者value属性来配置需要扫描的包路径。面试宝典:https://www.yoodb.comvalue属性是basePackages的别名。此注解的用法如下:@Component@Component注解用于标注一个普通的组件类,它没有明确的业务范围,只是通知Spring被此注解的类需要被纳入到Spring Bean容器中并进行管理。此注解的使用示例如下:@Service@Service注解是@Component的一个延伸(特例),它用于标注业务逻辑类。与@Component注解一样,被此注解标注的类,会自动被Spring所管理。下面是使用@Service注解的示例:@Repository@Repository注解也是@Component注解的延伸,与@Component注解一样,被此注解标注的类会被Spring自动管理起来,@Repository注解用于标注DAO层的数据持久化类。此注解的用法如下:三、Spring Dependency Inject与Bean Scops注解Spring DI注解@DependsOn@DependsOn注解可以配置Spring IoC容器在初始化一个Bean之前,先初始化其他的Bean对象。下面是此注解使用示例代码:@Bean@Bean注解主要的作用是告知Spring,被此注解所标注的类将需要纳入到Bean管理工厂中。@Bean注解的用法很简单,在这里,着重介绍@Bean注解中initMethod和destroyMethod的用法。示例如下:Scops注解@Scope@Scope注解可以用来定义@Component标注的类的作用范围以及@Bean所标记的类的作用范围。@Scope所限定的作用范围有:singleton、prototype、request、session、globalSession或者其他的自定义范围。这里以prototype为例子进行讲解。当一个Spring Bean被声明为prototype(原型模式)时,在每次需要使用到该类的时候,Spring IoC容器都会初始化一个新的改类的实例。在定义一个Bean时,可以设置Bean的scope属性为prototype:scope=“prototype”,也可以使用@Scope注解设置,如下:@Scope(value=ConfigurableBeanFactory.SCOPE_PROPTOTYPE)下面将给出两种不同的方式来使用@Scope注解,示例代码如下:@Scope 单例模式当@Scope的作用范围设置成Singleton时,被此注解所标注的类只会被Spring IoC容器初始化一次。在默认情况下,Spring IoC容器所初始化的类实例都为singleton。同样的原理,此情形也有两种配置方式,示例代码如下:四、容器配置注解@Autowired@Autowired注解用于标记Spring将要解析和注入的依赖项。此注解可以作用在构造函数、字段和setter方法上。作用于构造函数下面是@Autowired注解标注构造函数的使用示例:作用于setter方法下面是@Autowired注解标注setter方法的示例代码:作用于字段@Autowired注解标注字段是最简单的,只需要在对应的字段上加入此注解即可,示例代码如下:@Primary当系统中需要配置多个具有相同类型的bean时,@Primary可以定义这些Bean的优先级。下面将给出一个实例代码来说明这一特性:输出结果:thisissendDingDingmethodmessage.@PostConstruct与@PreDestroy值得注意的是,这两个注解不属于Spring,它们是源于JSR-250中的两个注解,位于common-annotations.jar中。@PostConstruct注解用于标注在Bean被Spring初始化之前需要执行的方法。@PreDestroy注解用于标注Bean被销毁前需要执行的方法。下面是具体的示例代码:@Qualifier当系统中存在同一类型的多个Bean时,@Autowired在进行依赖注入的时候就不知道该选择哪一个实现类进行注入。此时,我们可以使用@Qualifier注解来微调,帮助@Autowired选择正确的依赖项。另外,推荐公众 号Java精选,回复java面试,获取资料。下面是一个关于此注解的代码示例:五、Spring Boot注解@SpringBootApplication@SpringBootApplication注解是一个快捷的配置注解,在被它标注的类中,可以定义一个或多个Bean,并自动触发自动配置Bean和自动扫描组件。此注解相当于@Configuration、@EnableAutoConfiguration和@ComponentScan的组合。在Spring Boot应用程序的主类中,就使用了此注解。示例代码如下:@SpringBootApplication publicclassApplication{ publicstaticvoidmain(String[]args){ SpringApplication.run(Application.class,args); } }@EnableAutoConfiguration@EnableAutoConfiguration注解用于通知Spring,根据当前类路径下引入的依赖包,自动配置与这些依赖包相关的配置项。@ConditionalOnClass与@ConditionalOnMissingClass这两个注解属于类条件注解,它们根据是否存在某个类作为判断依据来决定是否要执行某些配置。下面是一个简单的示例代码:@Configuration @ConditionalOnClass(DataSource.class) classMySQLAutoConfiguration{ //... }@ConditionalOnBean与@ConditionalOnMissingBean这两个注解属于对象条件注解,根据是否存在某个对象作为依据来决定是否要执行某些配置方法。示例代码如下:@Bean @ConditionalOnBean(name="dataSource") LocalContainerEntityManagerFactoryBeanentityManagerFactory(){ //... } @Bean @ConditionalOnMissingBean publicMyBeanmyBean(){ //... }@ConditionalOnProperty@ConditionalOnProperty注解会根据Spring配置文件中的配置项是否满足配置要求,从而决定是否要执行被其标注的方法。示例代码如下:@Bean @ConditionalOnProperty(name="alipay",havingValue="on") Alipayalipay(){ returnnewAlipay(); }@ConditionalOnResource此注解用于检测当某个配置文件存在使,则触发被其标注的方法,下面是使用此注解的代码示例:@ConditionalOnResource(resources="classpath:website.properties") PropertiesaddWebsiteProperties(){ //... }@ConditionalOnWebApplication与@ConditionalOnNotWebApplication这两个注解用于判断当前的应用程序是否是Web应用程序。如果当前应用是Web应用程序,则使用Spring WebApplicationContext,并定义其会话的生命周期。下面是一个简单的示例:@ConditionalOnWebApplication HealthCheckControllerhealthCheckController(){ //... }@ConditionalExpression此注解可以让我们控制更细粒度的基于表达式的配置条件限制。当表达式满足某个条件或者表达式为真的时候,将会执行被此注解标注的方法。@Bean @ConditionalException("${localstore}&&${local=='true'}") LocalFileStorestore(){ //... }@Conditional@Conditional注解可以控制更为复杂的配置条件。在Spring内置的条件控制注解不满足应用需求的时候,可以使用此注解定义自定义的控制条件,以达到自定义的要求。下面是使用该注解的简单示例:@Conditioanl(CustomConditioanl.class) CustomPropertiesaddCustomProperties(){ //... }总结本次课程总结了Spring Boot中常见的各类型注解的使用方式,让大家能够统一的对Spring Boot常用注解有一个全面的了解。