• [技术干货] 【JAVA】Spring MVC 详解-转载
     Spring MVC 基本概念 1. Spring MVC 概述 Spring MVC 是 Spring 框架中的一个模块,专注于为 Web 应用程序提供 Model-View-Controller (MVC) 架构。它帮助开发者构建可扩展、可维护的 Web 应用,并且能够轻松集成到 Spring 生态系统中。  2. DispatcherServlet DispatcherServlet 是 Spring MVC 的核心组件,负责接收 HTTP 请求,并将请求分发给相应的处理器(Controller)。它起到了中央控制器的作用。 示例:  @Configuration public class WebAppInitializer implements WebApplicationInitializer {      @Override     public void onStartup(ServletContext servletContext) throws ServletException {         AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();         context.register(AppConfig.class);                  ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(context));         dispatcher.setLoadOnStartup(1);         dispatcher.addMapping("/");     } } 在这个例子中,DispatcherServlet 被注册到 ServletContext 中,并映射到根路径。  3. Controller Controller 是 Spring MVC 中的一个组件,负责处理用户请求,并返回一个 ModelAndView 对象。它将用户的输入与应用程序的业务逻辑连接起来。 示例:  import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.servlet.ModelAndView;  @Controller public class HomeController {      @GetMapping("/")     public ModelAndView home() {         ModelAndView mav = new ModelAndView("home");         mav.addObject("message", "Welcome to Spring MVC");         return mav;     } } 在这个例子中,HomeController 处理根路径的 GET 请求,并返回一个包含消息的视图。  4. ModelAndView ModelAndView 是 Spring MVC 中的一个类,包含了视图名称和模型数据。它将控制器的处理结果传递给视图层。 示例:  ModelAndView mav = new ModelAndView("home"); mav.addObject("message", "Welcome to Spring MVC"); 在这个例子中,ModelAndView 对象包含视图名称 “home” 和模型数据 “message”。  5. @RequestMapping @RequestMapping 是 Spring MVC 中的一个注解,用于映射 HTTP 请求到控制器的方法。它支持多种 HTTP 方法(GET、POST 等),还可以指定路径和参数。 示例:  import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod;  @Controller public class HomeController {      @RequestMapping(value = "/", method = RequestMethod.GET)     public String home() {         return "home";     } } 这个例子展示了如何使用 @RequestMapping 将根路径的 GET 请求映射到 home() 方法。  6. @GetMapping 和 @PostMapping @GetMapping 和 @PostMapping 是 @RequestMapping 的快捷方式,分别用于处理 GET 和 POST 请求。 示例:  @GetMapping("/hello") public String hello() {     return "hello"; }  @PostMapping("/submit") public String submit() {     return "submit"; } 在这个例子中,@GetMapping 处理 GET 请求,@PostMapping 处理 POST 请求。  7. @PathVariable @PathVariable 注解用于将 URL 路径中的变量绑定到方法参数上。 示例:  @GetMapping("/user/{id}") public String getUserById(@PathVariable("id") String userId) {     return "User ID: " + userId; } 在这个例子中,@PathVariable(“id”) 将 URL 中的 {id} 绑定到方法参数 userId。  8. @RequestParam @RequestParam 注解用于将查询参数绑定到方法参数上。 示例:  @GetMapping("/search") public String search(@RequestParam("q") String query) {     return "Search query: " + query; } 在这个例子中,@RequestParam(“q”) 将查询参数 q 绑定到方法参数 query。  9. @ModelAttribute @ModelAttribute 注解用于将请求中的数据绑定到模型对象上,通常用于表单处理。 示例:  @PostMapping("/submit") public String submitForm(@ModelAttribute User user) {     return "Form submitted for user: " + user.getName(); } 在这个例子中,@ModelAttribute 将请求中的表单数据绑定到 User 对象上。  10. 视图解析器(View Resolver) 视图解析器负责将逻辑视图名称解析为物理视图路径。Spring MVC 提供了多种视图解析器,如 InternalResourceViewResolver 和 ThymeleafViewResolver。 示例:  @Bean public InternalResourceViewResolver viewResolver() {     InternalResourceViewResolver resolver = new InternalResourceViewResolver();     resolver.setPrefix("/WEB-INF/views/");     resolver.setSuffix(".jsp");     return resolver; } 在这个例子中,InternalResourceViewResolver 将视图名称解析为 JSP 文件路径。  11. 表单处理 Spring MVC 提供了对表单处理的全面支持,包括表单绑定、验证和表单回显。 示例:  import javax.validation.Valid; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping;  @PostMapping("/register") public String register(@Valid @ModelAttribute User user, BindingResult result) {     if (result.hasErrors()) {         return "register";     }     return "success"; } 在这个例子中,@Valid 注解用于验证 User 对象,BindingResult 用于处理验证结果。  12. 数据验证(Validation) Spring MVC 支持基于 JSR-303 的数据验证,可以使用注解对模型对象进行验证。 示例:  import javax.validation.constraints.NotEmpty;  public class User {     @NotEmpty(message = "Name is required")     private String name;      // getter 和 setter 方法 } 在这个例子中,@NotEmpty 注解用于验证 name 字段不能为空。  13. 消息转换器(Message Converters) 消息转换器负责将请求数据转换为对象,以及将对象转换为响应数据。Spring MVC 提供了多种内置的消息转换器,如 JSON 和 XML。 示例:  import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody;  @PostMapping("/user") @ResponseBody public User createUser(@RequestBody User user) {     // 处理用户数据     return user; } 在这个例子中,@RequestBody 和 @ResponseBody 注解用于将 JSON 请求数据转换为 User 对象,并将 User 对象转换为 JSON 响应数据。  14. 拦截器(Interceptor) 拦截器用于在请求处理之前或之后执行额外的逻辑。它们类似于过滤器,但更强大,可以访问 Spring MVC 上下文。 示例:  import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;  public class MyInterceptor implements HandlerInterceptor {     @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {         // 执行前置逻辑         return true;     } } 在这个例子中,MyInterceptor 实现了 HandlerInterceptor 接口,可以在请求处理之前执行逻辑。  15. 异常处理(Exception Handling) Spring MVC 提供了多种异常处理机制,包括 @ExceptionHandler、@ControllerAdvice 和 ResponseEntityExceptionHandler。 示例:  import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;  @RestControllerAdvice public class GlobalExceptionHandler {      @ExceptionHandler(Exception.class)     public String handleException(Exception e) {         return "An error occurred: " + e.getMessage();     } } 在这个例子中,@ExceptionHandler 注解用于处理控制器中的异常,@RestControllerAdvice 注解用于全局异常处理。  16. 文件上传 Spring MVC 支持文件上传功能,可以处理 Multipart 文件上传请求。 示例:  import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile;  @PostMapping("/upload") public String uploadFile(@RequestParam("file") MultipartFile file) {     if (!file.isEmpty()) {         // 处理文件         return "File uploaded successfully";     }     return "File upload failed"; } 在这个例子中,@RequestParam 注解用于将上传的文件绑定到 MultipartFile 对象上。  17.RestController @RestController 是 @Controller 和 @ResponseBody 的组合注解,通常用于构建 RESTful Web 服务。 示例:  import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;  @RestController public class ApiController {      @GetMapping("/api/hello")     public String hello() {         return "Hello, RESTful world!";     } } 在这个例子中,@RestController 注解用于构建一个 RESTful API。  18. 国际化(Internationalization, i18n) Spring MVC 提供了对国际化的全面支持,允许根据用户的区域设置提供不同的语言和格式。 示例:  import org.springframework.context.annotation.Bean; import org.springframework.context.support.ResourceBundleMessageSource;  @Bean public ResourceBundleMessageSource messageSource() {     ResourceBundleMessageSource source = new ResourceBundleMessageSource();     source.setBasename("messages");     return source; } 在这个例子中,ResourceBundleMessageSource 用于加载国际化资源文件。  19. Spring MVC 与 Thymeleaf 集成 Thymeleaf 是一种流行的模板引擎,可以与 Spring MVC 集成,用于生成动态的 HTML 页面。 示例:  import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping;  @Controller public class ThymeleafController {      @GetMapping("/greeting")     public String greeting(Model model) {         model.addAttribute("message", "Hello, Thymeleaf!");         return "greeting";     } } 在这个例子中,ThymeleafController 处理 /greeting 请求,并返回一个包含消息的 Thymeleaf 模板视图。 20. WebSocket 支持 Spring MVC 提供了对 WebSocket 的支持,允许在 Web 应用程序中实现实时双向通信。 示例:  import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;  @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer {      @Override     public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {         registry.addHandler(new MyWebSocketHandler(), "/ws");     } } 在这个例子中,WebSocketConfig 配置了一个 WebSocket 处理器,用于处理 /ws 路径的 WebSocket 连接。  总结 Spring MVC 是一个功能强大的框架,提供了广泛的工具和特性来构建现代 Web 应用程序。通过理解和掌握上述每一个知识点,开发者可以更高效地构建出可扩展、维护性强的 Web 应用。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/weixin_37559642/article/details/142127152 
  • [技术干货] 【Spring】MVC-转载
     一、简介         Spring MVC 是基于Servlet API构建的原始的Web框架,是 Spring 框架的一个重要模块,它实现了模型 - 视图 - 控制器(MVC)设计模式,用于构建灵活、可维护的 Web 应用程序。  MVC 设计模式将应用程序分为三个主要部分:  模型(Model):负责处理业务逻辑和数据存储。它包含了应用程序的数据和业务规则,可以是 Java 对象、数据库实体等。 视图(View):负责呈现用户界面。它可以是 HTML 页面、JSP 文件、XML 视图等,用于将模型中的数据展示给用户。 控制器(Controller):负责处理用户请求并协调模型和视图之间的交互。它接收用户的输入,调用模型进行业务处理,并选择合适的视图来呈现结果。         Spring MVC 通过将这些部分解耦,使得开发人员可以更加专注于各自的领域,提高了开发效率和代码的可维护性。  什么是Servlet呢?         Servlet 是一种实现动态页面的技术. 准确来讲Servlet是一套 Java Web 开发的规范,或者说是一套 Java Web 开发的技术标准。         只有规范并不能做任何事情,必须要有人去实现它. 所谓实现 Servlet 规范,就是真正编写代码去实现 Servlet 规范提到的各种功能,包括类、方法、属性等.         Servlet 规范是开放的,除了 Sun 公司,其它公司也可以实现 Servlet 规范,目前常见的实现了 Servlet 规范的产品包括 Tomcat、Weblogic、Jetty、Jboss、WebSphere 等,它们都被称 为"Servlet 容器". Servlet 容器用来管理程序员编写的 Servlet 类. 😄二、工作原理 客户端发送请求:用户通过浏览器发送 HTTP 请求到服务器。 DispatcherServlet 接收请求:Spring MVC 的核心组件 DispatcherServlet 接收请求,并根据请求的 URL 找到对应的控制器(Controller)。 控制器处理请求:控制器接收请求后,调用相应的业务逻辑处理方法,对请求进行处理。控制器可以访问模型(Model)来获取数据,并将数据传递给视图(View)进行呈现。 选择视图:控制器根据处理结果选择合适的视图,并将模型数据传递给视图。 视图呈现结果:视图使用模型数据生成响应内容,并将其返回给 DispatcherServlet。 DispatcherServlet 返回响应:DispatcherServlet 将视图生成的响应内容返回给客户端浏览器。 🤣三、核心组件 ​          MVC 是 Model,View,Controller 的缩写,他是软件工程的一种软件架构设计模式,把软件系统分为模型,视图,控制器三个基本部分:   Controller:控制器负责处理用户请求,调用业务逻辑处理方法,并选择合适的视图来呈现结果。可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型来处理,以及处理完后需要跳回哪一个视图,即用来连接 Model 和 View Model:是应用程序的主体部分,用来处理程序中的数据逻辑部分 View:应用程序当中专门用来与浏览器进行交互,展示数据的资源 此外还有一些组件如下:  DispatcherServlet:Spring MVC 的核心 servlet,负责接收用户请求并将其转发给相应的控制器进行处理。它还负责选择合适的视图来呈现响应结果。 HandlerMapping:用于将请求 URL 映射到相应的控制器。Spring MVC 提供了多种实现方式,如基于注解的映射和基于 XML 配置的映射。 ModelAndView:用于封装模型数据和视图信息。控制器可以返回一个 ModelAndView 对象,其中包含了要呈现的视图名称和模型数据。 ViewResolver:用于将视图名称解析为实际的视图对象。Spring MVC 提供了多种视图解析器,如 JSP 视图解析器、FreeMarker 视图解析器等。 🙂四、注解配置 创建 Spring MVC项目  ​   Spring MVC 提供了丰富的注解来简化开发过程。  1.@RestController @RestController是一个组合注解,它相当于@Controller和@ResponseBody的结合。  作用:用于标识一个控制器类,表明该类中的方法将返回数据而不是视图。当一个方法被注解为@RestController的类中的方法被调用时,Spring 会将方法的返回值直接序列化为 JSON、XML 或其他适当的格式,并将其作为 HTTP 响应体返回给客户端。  示例:     @RestController    public class MyController {        // 方法定义...    } 2.@RequestMapping @RequestMapping用于映射请求的 URL 到特定的处理方法上。  作用:可以在类级别和方法级别上使用。在类级别上使用时,它定义了一个基本的请求路径前缀,而在方法级别上使用时,它进一步细化了请求的路径和 HTTP 方法等。  属性:  value:指定请求的 URL 路径。 method:指定请求的 HTTP 方法,如GET、POST、PUT、DELETE等。 consumes:指定请求的内容类型。 produces:指定响应的内容类型。 示例:     @RestController    @RequestMapping("/api")    public class MyController {        @RequestMapping(value = "/users", method = RequestMethod.GET)        public List<User> getUsers() {            // 返回用户列表        }    } 3.@RequestParam @RequestParam用于获取请求参数。  作用:可以从 URL 的查询参数或者表单数据中获取参数值,并将其传递给方法参数。  属性:  value:指定参数的名称。 required:表示该参数是否必填,默认为true。 示例:     @RestController    @RequestMapping("/api")    public class MyController {        @GetMapping("/search")        public List<User> searchUsers(@RequestParam("keyword") String keyword) {            // 根据关键字搜索用户        }    } 4.@RequestHeader 作用:用于从 HTTP 请求的头部获取特定的值,并将其注入到方法参数中。 示例:    @Controller    public class MyController {          @RequestMapping("/header")        public String getHeaderValue(@RequestHeader("Accept-Language") String language) {            // 使用 language            return "resultPage";        }    } 这个例子中,方法参数language会被注入请求头部中Accept-Language的值。  5.@RequestPart 作用:接收特定部分的请求内容,绑定复杂的对象 示例:   @RestController public class FileUploadController {       @PostMapping("/upload")     public String uploadFile(@RequestPart("file") MultipartFile file,                              @RequestPart("description") String description) {         // 处理文件上传和文本参数         if (!file.isEmpty()) {             // 保存文件等操作             System.out.println("File name: " + file.getOriginalFilename());             System.out.println("Description: " + description);             return "File uploaded successfully.";         } else {             return "Failed to upload file.";         }     } }  这个代码就实现了文件的上传   6.@ResponseBody 作用:该注解用于将控制器方法的返回值直接写入 HTTP 响应体中,而不是将其解析为视图名称。通常用于返回 JSON、XML 等数据格式的响应。 示例:    @RestController    public class MyController {          @RequestMapping("/data")        @ResponseBody        public MyData getData() {            MyData data = new MyData();            // 设置数据            return data;        }    }         这里的方法返回一个自定义对象MyData,由于有@ResponseBody注解,这个对象会被序列化为合适的数据格式(如 JSON)并写入响应体。  7.@PathVariable @PathVariable用于获取 URL 中的路径变量。  作用:当请求的 URL 中包含动态的部分时,可以使用@PathVariable注解将其提取出来作为方法参数。  示例:     @RestController    @RequestMapping("/api/users")    public class MyController {        @GetMapping("/{id}")        public User getUserById(@PathVariable("id") Long id) {            // 根据用户 ID 返回用户对象        }    } 在上述例子中,当客户端发送请求/api/users/123时,id参数将被赋值为123。  8.@CookieValue 作用:用于从 HTTP 请求中获取特定的 Cookie 值,并将其注入到方法参数中。 示例:    @Controller    public class MyController {          @RequestMapping("/test")        public String testMethod(@CookieValue("myCookie") String cookieValue) {            // 使用 cookieValue            return "resultPage";        }    } 在这个例子中,方法参数cookieValue会被注入请求中名为myCookie的 Cookie 的值。  9.@SessionAttribute 作用:用于将模型中的属性存储到 HTTP 会话中,或者从会话中获取特定的属性并注入到方法参数中。 示例:    @Controller    public class MyController {          @RequestMapping("/set")        public String setAttribute(Model model) {            model.addAttribute("sessionData", "someValue");            return "redirect:/get";        }          @RequestMapping("/get")        public String getAttribute(@SessionAttribute("sessionData") String data) {            // 使用 data            return "resultPage";        }    } 在setAttribute方法中,将一个值存储到会话中,然后在getAttribute方法中,通过@SessionAttribute注解从会话中获取这个值并注入到方法参数中。     😉五、Spring MVC的优势 松耦合:Spring MVC 实现了模型、视图和控制器的分离,使得各个部分之间的耦合度降低,提高了代码的可维护性和可扩展性。 灵活性:Spring MVC 提供了丰富的配置选项和扩展点,可以根据不同的需求进行定制和扩展。 易于测试:由于 Spring MVC 的各个部分之间解耦,使得单元测试和集成测试更加容易。 强大的视图技术支持:Spring MVC 支持多种视图技术,如 JSP、FreeMarker、Thymeleaf 等,可以根据项目需求选择合适的视图技术。 良好的社区支持:Spring MVC 是 Spring 框架的一部分,拥有庞大的社区和丰富的文档资源,遇到问题可以很容易地找到解决方案。 😊六、JSON         JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于一个子集的 JavaScript 编程语言,是易于人阅读和编写同时也易于机器解析和生成的数据格式。JSON 格式广泛用于 Web 应用程序之间传输数据,并且得到了几乎所有编程环境的支持。  1.基本语法 JSON 数据以键值对的形式表示。 键必须是字符串,而值可以是合法的 JSON 数据类型(字符串、数字、对象、数组、布尔值或 null)。 JSON 对象使用花括号 {} 来定义。 JSON 数组使用方括号 [] 来定义。 成员之间用逗号分隔。 字符串必须使用双引号包围。 2.特点 简洁性:JSON 的语法非常简洁,易于阅读和编写。与 XML 相比,JSON 没有过多的标签和属性,减少了数据的冗余。 可读性:JSON 的文本格式可以很容易地理解和编辑数据。这对于调试和手动修改数据非常有帮助。 跨平台性:JSON 可以在不同的操作系统和编程语言中使用,因为它是一种基于文本的格式。大多数编程语言都提供了对 JSON 的支持,可以轻松地解析和生成 JSON 数据。 可扩展性:JSON 可以很容易地扩展,因为它可以包含任意复杂的对象和数组结构。可以根据需要添加新的键值对或数组元素,而不会影响现有的数据结构。  3.JSON 与 XML 的比较 JSON 比 XML 更小、更快,更易解析。 JSON 直接映射到数据结构,如数组和对象。 XML 更适合于文档标记,提供了更多的元数据选项。 JSON 不支持注释,XML 支持。 4.JSON 在实际中的应用 API 数据交互:Web API 常返回 JSON 格式的数据。 存储配置信息:应用程序可以将配置保存为 JSON 文件。 客户端存储:例如,通过浏览器的 localStorage 或 sessionStorage 存储 JSON 数据。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/2302_79806056/article/details/143066637 
  • [技术干货] SpringBoot高级-底层原理-转载
     1 SpringBoot自动化配置原理 01-SpringBoot2高级-starter依赖管理机制 目的:通过依赖能了解SpringBoot管理了哪些starter  讲解:  通过依赖 spring-boot-dependencies 搜索 starter- 发现非常多的官方starter,并且已经帮助我们管理好了版本。 项目中使用直接引入对应的 starter 即可,这个场景下需要的依赖就会自动导入到项目中,简化了繁琐的依赖。如果需要修改版本可以有两种方式: 重写maven属性 使用Maven依赖管理的就近原则       3.引入 starter 不仅仅是帮助我们管理了依赖,还帮我做了很多的默认的配置信息,简化了大量的配置,使用更加的简单。        4.所有的场景启动器的底层都依赖 spring-boot-starter  <dependency>   <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter</artifactId>     <version>2.3.10.RELEASE</version>     <scope>compile</scope>   </dependency> 小结:  引入官方starter依赖默认都可以不写版本 如果配置满足您当前开发需要,则默认配置即可 02-SpringBoot2高级-自动化配置初体验 目的:以web MVC自动化配置原理为例讲解,能够理解web MVC自动化配置加入了哪些依赖,做了哪些默认配置。  讲解:  回忆一下:SpringMVC博文中,我们在 SSM整合时;  添加spring及spring web mvc相关依赖  springmvc配置类:  1、扫描controller层  2、静态资源控制  3、......  servlet容器配置类:  1、扫描springmvc配置类  2、扫描spring配置类  3、设置哪些请求交给springmvc处理  4、POST请求乱码过滤器  部署还需要单独的tomcat  也就是说:我们现在需要在开发业务代码前,就必须要准备好这些环境,否则无法完成业务代码,这就是我们现在的问题。  让这些问题成为过去,现在我们就探索一下SpringBoot是如何帮助我们完成强大而又简单自动化配置的。  引入 web 开发场景启动器依赖:  <!--web开发的起步依赖   场景启动器依赖--> <dependency>     <groupId>org.springframework.boot</groupId>       <artifactId>spring-boot-starter-web</artifactId>   </dependency> 帮助我们做了以下自动化配置:  依赖版本和依赖什么jar都不需要开发者关注 自动化配置 自动配好SpringMVC 引入SpringMVC全套组件 自动配好SpringMVC常用组件(三大组件,文件上传等) 自动配好Web常见功能,如:字符编码问题,静态资源管理 自动配好Tomcat 小结:  有了SpringBoot以后,让开发人员重点关注业务本身,而不是环境上,提升了开发效率。 03-SpringBoot2高级-底层原理-@Configuration配置注解 目的:掌握@Configuration注解的作用及新特性  讲解:  1、@Configuration注解的作用是替代原始 spring配置文件 功能  演示:  1)编写配置类  package com.itheima.sh.config;   import com.itheima.sh.pojo.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   /**  * 1、@Configuration 替代 spring配置文件(配置bean)  * 2、组件源码中包含 @Component 注解,当前类也会注册到 IOC 容器,默认类名小写  * 3、默认都是单例的  */ @Configuration public class MyConfig {          @Bean   // 默认方法名称作为容器中的name     public User getUser() {         return new User();     } }  2)在引导类编写代码测试:  @SpringBootApplication @MapperScan(basePackages = "com.itheima.sh.mapper") public class DataApplication {   public static void main(String[] args) {     ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);      // 根据name获取容器中的bean     User user1 = applicationContext.getBean("getUser", User.class);     User user2 = applicationContext.getBean("getUser", User.class);     System.out.println(user1 == user2);          MyConfig myConfig1 = applicationContext.getBean("myConfig", MyConfig.class);     MyConfig myConfig2 = applicationContext.getBean("myConfig", MyConfig.class);     System.out.println(myConfig1 == myConfig2);     // 注意:如果 MYConfig配置类没有按照规范编写,则容器中bean 的name为 类名   } }  SpringBoot 提供一个注解和当前注解功能一样:  @SpringBootConfiguration  2、proxyBeanMethods:代理bean的方法属性(since spring 5.2以后)   功能:  proxyBeanMethods = true:Full模式,保证每个@Bean方法被调用多少次返回的组件都是单实例的 proxyBeanMethods = false:Lite模式,每个@Bean方法被调用多少次返回的组件都是新创建的 演示:  默认 proxyBeanMethods=true,springBoot会检查这个组件是否在容器中有,有则直接引用 // 默认 proxyBeanMethods=true springBoot会检查这个组件是否在容器中有,有则直接引用 User user3 = myConfig1.getUser(); System.out.println(user1 == user3);  // true 修改 proxyBeanMethods=false,则每调用一次Spring就会创建一个新的Bean对象  在执行结果则为 false, 证明两次获取的bean不是同一个bean。  小结:  组件依赖必须使用Full模式默认。 Full模式每次都会检查bean,效率较Lite模式慢 04-SpringBoot2高级-底层原理-@Import注解使用1 目的:能够理解@Import注解作用及4种使用方式  讲解:  作用:使用@Import导入的类会被Spring加载到IOC容器中  @Import提供4种用法:  导入Bean 导入配置类 导入 ImportSelector 实现类。一般用于加载配置文件中的类 导入 ImportBeanDefinitionRegistrar 实现类 实现:  1、导入Bean  package com.itheima.sh;   import com.itheima.sh.pojo.User; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import;   import java.util.Map;   @SpringBootApplication @Import(User.class)   //会自动执行当前类的构造方法创建对象,存到IOC容器, bean名称为:类的全路径   public class DataApplication {   public static void main(String[] args) {     ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);       Map<String, User> map = applicationContext.getBeansOfType(User.class);     System.out.println(map);          User user1 = applicationContext.getBean("com.itheima.sh.pojo.User", User.class);     System.out.println(user1);     } }  2、导入配置类  package com.itheima.sh;   import com.itheima.sh.config.MyConfig; import com.itheima.sh.pojo.User; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import;   import java.util.Map;   @SpringBootApplication @MapperScan(basePackages = "com.itheima.sh.mapper") //@Import(User.class) //1、会自动执行当前类的构造方法创建对象,存到IOC容器, bean名称为:类的全路径   @Import(MyConfig.class) //2、创建MyConfig bean,并且类中有 带有@Bean注解方法,创建对象存到IOC容器,bean名称为:默认方法名称 public class DataApplication {     public static void main(String[] args) {         ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);           //{getUser=com.itheima.sh.pojo.User@1b4a3a1}         Map<String, User> map = applicationContext.getBeansOfType(User.class);         System.out.println(map);           User user1 = applicationContext.getBean("getUser", User.class);         System.out.println(user1);           Map<String, MyConfig> config = applicationContext.getBeansOfType(MyConfig.class);         //{com.itheima.sh.config.MyConfig=com.itheima.sh.config.MyConfig@7e848aea}         System.out.println(config);     } }  05-SpringBoot2高级-底层原理-@Import注解使用2 目的:讲解@Import注解使用另外两种使用方式  步骤:  导入 ImportSelector 实现类。一般用于加载配置文件中的类 导入 ImportBeanDefinitionRegistrar 实现类 实现:  导入 ImportSelector 实现类。一般用于加载配置文件中的类  1、编写 ImportSelector 实现类,MyImportSelector  2、引导类导入  package com.itheima.sh;   import com.itheima.sh.config.MyImportSelector; import com.itheima.sh.pojo.User; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import;   import java.util.Map;   @SpringBootApplication @MapperScan(basePackages = "com.itheima.sh.mapper") //@Import(User.class) //1、会自动执行当前类的构造方法创建对象,存到IOC容器, bean名称为:类的全路径   //@Import(MyConfig.class) //2、创建MyConfig bean,并且类中有 带有@Bean注解方法,创建对象存到IOC容器,bean名称为:默认方法名称   @Import(MyImportSelector.class) //3、创建MyConfig bean 名称为:类名全路径,创建带有@Bean注解方法实例,名称为:方法名称 public class DataApplication {     public static void main(String[] args) {         ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);         Map<String, MyConfig> map = applicationContext.getBeansOfType(MyConfig.class);       //{com.itheima.sh.config.MyConfig=com.itheima.sh.config.MyConfig@44384b4a}       System.out.println(map);         Map<String, User> userMap = applicationContext.getBeansOfType(User.class);       //{getUser=com.itheima.sh.pojo.User@5cc3e49b}       System.out.println(userMap);       } }  导入 ImportBeanDefinitionRegistrar 实现类  1、编写 ImportBeanDefinitionRegistrar 实现类,MyImportBeanDefinitionRegistrar  package com.itheima.sh.config;   import com.itheima.sh.pojo.User; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata;   public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {     /**      * @param importingClassMetadata 导入类的元注解信息      * @param registry Bean注册表      */   @Override   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {     AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder       .rootBeanDefinition(User.class).getBeanDefinition();     registry.registerBeanDefinition("user", beanDefinition);   } }  2、引导类测试  package com.itheima.sh;   import com.itheima.sh.config.MyImportBeanDefinitionRegistrar; import com.itheima.sh.pojo.User; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import;   import java.util.Map;   @SpringBootApplication @MapperScan(basePackages = "com.itheima.sh.mapper")   //@Import(User.class) //1、会自动执行当前类的构造方法创建对象,存到IOC容器, bean名称为:类的全路径   //@Import(MyConfig.class) //2、创建MyConfig bean,并且类中有 带有@Bean注解方法,创建对象存到IOC容器,bean名称为:默认方法名称   //@Import(MyImportSelector.class) //3、创建MyConfig bean 名称为:类名全路径,创建带有@Bean注解方法实例,名称为:方法名称   @Import(MyImportBeanDefinitionRegistrar.class) //4、创建Bean,名称:在registerBeanDefinition中定义 public class DataApplication {     public static void main(String[] args) {         ConfigurableApplicationContext applicationContext = SpringApplication.run(DataApplication.class, args);           Map<String, User> userMap = applicationContext.getBeansOfType(User.class);         //{user=com.itheima.sh.pojo.User@23c7cb18}         System.out.println(userMap);       } }  小结:  讲解当前小节的目的主要是为源码准备 还有我们也可以知道创建Bean对象,还可以使用@Import四种方式 06-SpringBoot2高级-底层原理-@Conditional衍生条件装配 目的:理解@Conditional衍生条件装配的作用  讲解:  作用:条件装配,满足Conditional指定的条件,则进行组件注入,初始化Bean对象到IOC容器 。   演示:  在RedisConfig类中添加注释:  方法中定义:   类上定义:   注意:也可以添加到 类上, 满足条件则类及类中的对象生效。  小结:  @ConditionalOnXXX 注解存在的意义是:满足条件当前类或者Bean才有效,按需导入。 07-SpringBoot2高级-底层原理-@ConfigurationProperties配置绑定 目的:  回顾 @ConfigurationProperties配置绑定 存在的目的是:获取配置属性或者是配置文件指定前缀的属性信息,并且初始化Bean对象到 IOC 容器。  由此我们可以想:将来的配置我们可以放在配置文件中,通过这个注解来读取并封装成对象   08-SpringBoot2高级-自动化配置原理-@SpringBootApplication入口分析 目的:能够理解SpringBoot自动化配置流程中@SpringBootApplication是一个组合注解,及每一个注解的作用能够知道作用。  讲解:  1、SpringBoot是一个组合注解   2、@SpringBootConfiguration注解作用  @SpringBootConfiguration是对@Configuration注解的包装,proxyBeanMethods 默认配置 true, full模式(单例Bean) 标识是一个配置类,所以 引导类也是配置类 3、@ComponentScan注解作用  组件扫描,默认扫描的规则 引导类所在的包及其子包所有带注解的类 问题:  在引导类中配置 @Bean 注解可以吗? 为什么Controller、service类添加完注解后,不需要添加扫描包? 09-SpringBoot2高级-自动化配置原理-@EnableAutoConfiguration自动配置注解 目的:理解@EnableAutoConfiguration自动化配置核心实现注解  讲解:  1、@EnableAutoConfiguration是一个组合注解   2、@AutoConfigurationPackage注解作用  作用:利用Registrar给容器中导入一系列组件   点击 Registrar 进入到源码的 register 方法,添加 断点,测试   通过 debug 程序发现,默认情况下 将引导类的所有包及其子包的组件导入进来  3、@Import(AutoConfigurationImportSelector.class)注解作用  作用:是利用selectImports方法中的 getAutoConfigurationEntry 方法给容器中批量导入相关组件  调用流程分析:  调用AutoConfigurationImportSelector类中的selectImports方法 调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类 利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)得到所有的组件 从META-INF/spring.factories位置来加载一个文件。 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件  spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories   通过这个配置文件加载的自动配置:当前版本(2.3.10)是有127个默认的自动化配置   小结:  自动化配置默认加载的配置文件在哪? 10-SpringBoot2高级-自动化配置原理-按条件开启自动配置类和配置项 目的:  能够理解所有的自动化配置虽然会全部加载,底层有大量的@ConditionalOnXXX,有很多自动配置类并不能完全开启。 如果配置生效了,则会加载默认的属性配置类,实现默认的对应场景的自动化配置 讲解:  1、以上通过 META-INF/spring.factories 配置文件找到所有的自动化配置类,但 是不是全部的生效的呢?很显然是不可能全部都生效的。  2、以 JdbcTemplateAutoConfiguration 为例讲解, 进入到 JdbcTemplateAutoConfiguration 自动化配置类。  //配置类,Lite模式 @Configuration(proxyBeanMethods = false) //存在 DataSource、JdbcTemplate 类时再加载当前类 @ConditionalOnClass({DataSource.class, JdbcTemplate.class })   //容器中只有一个指定的Bean,或者这个Bean是首选Bean 加载当前类 @ConditionalOnSingleCandidate(DataSource.class)   //在配置类 DataSourceAutoConfiguration 之后执行 @AutoConfigureAfter(DataSourceAutoConfiguration.class)   //如果条件满足:开始加载自动化配置的属性值 JdbcProperties @EnableConfigurationProperties(JdbcProperties.class)   @Import({JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class }) public class JdbcTemplateAutoConfiguration {   }  3、JdbcProperties,用于加载默认的配置,如果配置文件配置了该属性,则配置文件就生效。   4、通过@Import导入JdbcTemplateConfiguration  //配置类,Lite模式 @Configuration(proxyBeanMethods = false) //没有JdbcOperations类型的bean时加载当前类,而 JdbcTemplate 是 JdbcOperations 接口实现类 @ConditionalOnMissingBean(JdbcOperations.class) class JdbcTemplateConfiguration {       @Bean     @Primary     JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);         JdbcProperties.Template template = properties.getTemplate();         jdbcTemplate.setFetchSize(template.getFetchSize());         jdbcTemplate.setMaxRows(template.getMaxRows());         if (template.getQueryTimeout() != null) {             jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());         }         return jdbcTemplate;     }   }  验证:我们可以在我们自己的项目里面创建一个 JdbcTemplate Bean,看容器创建的Bean执行的是哪一个方法。  @Configuration public class MyConfig {       @Bean     public JdbcTemplate jdbcTemplate(DataSource dataSource){         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);         System.out.println("自定义 JdbcTemplate");         return jdbcTemplate;     } } 结果:保证容器中只有一个 Bean 实例   问题:  这些不用的 starter 的依赖,能不能导入到我们工程里面? 为什么? 11-SpringBoot2高级-自动化配置原理-debug全流程 目的:能够理解整个SpringBoot启动的完成自动化配置及属性加载的全过程  12-SpringBoot2高级-自动化配置原理-总结 SpringBoot自动化配置流程总结:  程序启动找到自动化配置包下 META-INF/spring.factories 的EnableAutoConfiguration SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration 每个自动配置类按照条件进行生效。 生效的配置类就会给容器中装配很多组件 只要容器中有这些组件,相当于这些功能就有了 定制化配置 用户直接自己@Bean替换底层的组件 用户去看这个组件是获取的配置文件什么值就去修改。  开发人员使用步骤总结:  引入场景依赖 查看自动配置了哪些(选做) 自己分析,引入场景对应的自动配置一般都生效了 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效) 自己分析是否需要修改 参照文档修改配置项,xxxxProperties绑定了配置文件的哪些。 自定义加入或者替换组件,@Bean、@Component等 2 SpringBoot健康监控 13-SpringBoot2高级-监控-健康监控服务 目的:能够理解健康监控actuator的作用  讲解:  每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。  实现:  1、引入依赖  <dependency>     <groupId>org.springframework.boot</groupId>       <artifactId>spring-boot-starter-actuator</artifactId>   </dependency> 2、启动项目,访问 http://localhost:80/actuator   3、暴露所有监控信息为HTTP  management:   endpoints:     enabled-by-default: true #暴露所有端点信息     web:       exposure:         include: '*'  #以web方式暴露     endpoint:     health:       enabled: true   # 开启健康检查详细信息       show-details: always 访问 http://localhost:80/actuator 会发现内容多了,里面的地址分别都可以访问,记录的是对应的健康监测的信息。  14-SpringBoot2高级-监控-Admin可视化 目的:能够搭建 可视化监控平台  讲解:  SpringBoot Admin 有两个角色,客户端(Client)和服务端(Server)。  Spring Boot Admin为注册的应用程序提供以下功能:  显示健康状况 显示详细信息,例如 JVM和内存指标 micrometer.io指标 数据源指标 缓存指标 显示内部信息 关注并下载日志文件 查看JVM系统和环境属性 查看Spring Boot配置属性 支持Spring Cloud的可发布/ env-和// refresh-endpoint 轻松的日志级别管理 与JMX-beans交互 查看线程转储 查看http-traces 查看审核事件 查看http端点 查看预定的任务 查看和删除活动会话(使用spring-session) 查看Flyway / Liquibase数据库迁移 下载heapdump 状态更改通知(通过电子邮件,Slack,Hipchat等) 状态更改的事件日志(非持久性) 快速入门:Spring Boot Admin Reference Guide  实现:  以下为创建服务端和客户端工程步骤:  搭建Server端:  1、创建 admin_server 模块,引入依赖  <parent>     <groupId>org.springframework.boot</groupId>       <artifactId>spring-boot-starter-parent</artifactId>       <version>2.3.10.RELEASE</version>       <relativePath/> <!-- lookup parent from repository --> </parent>   <dependencies>     <dependency>         <groupId>de.codecentric</groupId>           <artifactId>spring-boot-admin-starter-server</artifactId>           <version>2.3.1</version>       </dependency>       <dependency>         <groupId>org.springframework.boot</groupId>           <artifactId>spring-boot-starter-web</artifactId>       </dependency>   </dependencies>  2、开启注解支持  package com.itheima.sh;   import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;   @SpringBootApplication @EnableAdminServer public class AdminApplication {     public static void main(String[] args) {         SpringApplication.run(AdminApplication.class, args);     } } 注意端口修改为:9999  搭建Client端:  1、在任意服务里面引入依赖  <dependency>     <groupId>de.codecentric</groupId>       <artifactId>spring-boot-admin-starter-client</artifactId>       <version>2.3.1</version>   </dependency> 2、配置文件    # 执行admin.server地址 spring:      boot:     admin:       client:         url: http://localhost:9999  # admin 服务地址         instance:           prefer-ip: true   # 显示IP   application:     name: boot_data  # 项目名称      management:   endpoints:     enabled-by-default: true #暴露所有端点信息     web:       exposure:         include: '*'  #以web方式暴露     endpoint:     health:       enabled: true   # 开启健康检查详细信息       show-details: always  3、启动服务,访问admin Server http://localhost:9999/ ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/2301_80237574/article/details/143192434 
  • [技术干货] SpringBoot实现微信支付接口调用及回调函数(商户参数获取)-转载
     一、具体业务流程 1. 用户下单 - 前端操作:   - 用户在应用中选择商品、填写订单信息(如地址、联系方式等),并点击“下单”按钮。   - 前端将订单信息(商品ID、数量、价格等)发送到后端。  - 后端处理:   - 接收到订单请求后,生成唯一的订单号(`out_trade_no`)。   - 将订单信息存储到数据库中,设置订单状态为“待支付”。   2. 后端创建订单 - 构建请求参数:   - 使用商户号、应用ID、随机字符串、订单描述、商户订单号、金额(单位:分)、IP 地址等构建 XML 格式的请求数据。  - 发送请求:   - 使用 HTTP POST 方法将请求数据发送到微信的统一下单 API(`https://api.mch.weixin.qq.com/pay/unifiedorder`)。 - 处理响应:   - 接收微信返回的响应数据(XML 格式),解析响应内容。   - 检查返回的 `return_code` 和 `result_code`,确保请求成功。   - 获取 `prepay_id`,并根据它生成支付签名等信息。   3. 返回支付信息 - 返回给前端:   - 将 `prepay_id` 和其他必要参数(如时间戳、随机字符串、签名等)封装成 JSON 响应返回给前端。 - 前端支付:   - 前端使用微信支付 SDK,调用支付接口启动支付流程。   - 用户确认支付后,微信客户端处理支付。   4. 用户确认支付 - 用户行为:   - 用户在微信中查看支付信息,确认后进行支付。  - 支付结果:   - 微信处理支付请求,完成后将结果异步通知你的服务器。   5. 微信支付回调 - 回调 URL 配置:   - 在微信商户平台配置你的回调 URL(如 `https://yourdomain.com/wechat/notify`)。  - 处理回调请求:   - 接收到来自微信的 POST 请求,读取请求体中的 XML 数据。  - 验证签名:   - 提取回调数据中的签名字段,使用相同的参数生成新的签名,与返回的签名进行比较,确保数据的完整性和有效性。  - 更新订单状态:   - 根据回调数据中的 `result_code` 更新数据库中的订单状态。如果支付成功,修改订单状态为“已支付”,并进行相应的业务处理(如发货)。 - 返回处理结果:   - 向微信返回处理结果,通常是 `<xml><return_code>SUCCESS</return_code></xml>`。   6. 返回处理结果 - 响应微信:   - 确保响应格式正确,避免微信因无法解析而重发通知。  7. 订单状态查询(可选) - 查询订单状态:   - 在用户支付后的一段时间内,可以调用微信的订单查询 API(`https://api.mch.weixin.qq.com/pay/orderquery`)来确认订单的状态。    - 处理结果:   - 根据查询结果更新本地订单状态,确保数据一致性。   8. 订单完成 - 后续处理:   - 一旦订单支付成功并发货,可以根据业务需求进行后续操作,例如发送确认邮件、更新库存等。  二、代码具体实现 1. 商户参数配置 在 application.properties 中配置微信支付的相关参数:  # 微信支付配置 wechat.pay.appId=your_app_id wechat.pay.mchId=your_mch_id wechat.pay.apiKey=your_api_key wechat.pay.notifyUrl=https://yourdomain.com/wechat/notify 2. 创建 Spring Boot 项目 确保你的项目引入了必要的依赖。在 pom.xml 中添加以下内容:  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency>     <groupId>org.apache.httpcomponents</groupId>     <artifactId>httpclient</artifactId> </dependency> <dependency>     <groupId>com.thoughtworks.xstream</groupId>     <artifactId>xstream</artifactId>     <version>1.4.18</version> </dependency> 3. 创建微信支付服务类 创建一个服务类 WeChatPayService,用于处理订单的创建和签名等操作。  import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map;   @Service public class WeChatPayService {       @Value("${wechat.pay.appId}")     private String appId;       @Value("${wechat.pay.mchId}")     private String mchId;       @Value("${wechat.pay.apiKey}")     private String apiKey;       @Value("${wechat.pay.notifyUrl}")     private String notifyUrl;       private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";       public String createOrder(String orderNo, double amount) throws Exception {         String nonceStr = String.valueOf(System.currentTimeMillis());         String xmlData = "<xml>"                 + "<appid>" + appId + "</appid>"                 + "<mch_id>" + mchId + "</mch_id>"                 + "<nonce_str>" + nonceStr + "</nonce_str>"                 + "<body>Product Description</body>"                 + "<out_trade_no>" + orderNo + "</out_trade_no>"                 + "<total_fee>" + (int) (amount * 100) + "</total_fee>"                 + "<spbill_create_ip>127.0.0.1</spbill_create_ip>"                 + "<notify_url>" + notifyUrl + "</notify_url>"                 + "<trade_type>APP</trade_type>"                 + "</xml>";           // 生成签名并添加到请求数据         String sign = WeChatPayUtil.generateSign(xmlData, apiKey);         xmlData = xmlData.replace("</xml>", "<sign>" + sign + "</sign></xml>");           try (CloseableHttpClient client = HttpClients.createDefault()) {             HttpPost post = new HttpPost(UNIFIED_ORDER_URL);             post.setEntity(new StringEntity(xmlData, "UTF-8"));             post.setHeader("Content-Type", "text/xml");               String response = EntityUtils.toString(client.execute(post).getEntity(), "UTF-8");             return response; // 解析并返回需要的信息         }     } }  4. 创建微信支付控制器 创建一个控制器 WeChatPayController,处理用户的下单请求(@PostMapping("/createOrder"))和回调@PostMapping("/notify")。  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;   import javax.servlet.http.HttpServletRequest;   @RestController @RequestMapping("/wechat") public class WeChatPayController {       @Autowired     private WeChatPayService weChatPayService;       @PostMapping("/createOrder")     public String createOrder(@RequestParam String orderNo, @RequestParam double amount) {         try {             return weChatPayService.createOrder(orderNo, amount);         } catch (Exception e) {             e.printStackTrace();             return "Error creating order";         }     }       @PostMapping("/notify")     public String handleCallback(HttpServletRequest request) {         StringBuilder sb = new StringBuilder();         try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {             String line;             while ((line = reader.readLine()) != null) {                 sb.append(line);             }         } catch (IOException e) {             e.printStackTrace();         }           String xmlData = sb.toString();         Map<String, String> data = WeChatPayUtil.parseXml(xmlData); // 解析 XML 数据           // 验证签名         String sign = data.get("sign");         if (WeChatPayUtil.generateSign(xmlData, apiKey).equals(sign)) {             // 处理业务逻辑,例如更新订单状态             String resultCode = data.get("result_code");             if ("SUCCESS".equals(resultCode)) {                 String orderNo = data.get("out_trade_no");                 // 更新订单状态为已支付                 // updateOrderStatus(orderNo, "PAID");             }             return "<xml><return_code>SUCCESS</return_code></xml>";         } else {             return "<xml><return_code>FAIL</return_code></xml>";         }     } }  5. 签名和 XML 处理工具类 创建一个工具类 WeChatPayUtil,负责签名和 XML 解析。  import com.thoughtworks.xstream.XStream;   import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; import java.util.TreeMap;   public class WeChatPayUtil {       public static String generateSign(String xmlData, String apiKey) {         // 将 XML 转换为 Map         Map<String, String> data = parseXml(xmlData);         TreeMap<String, String> sortedMap = new TreeMap<>(data);         StringBuilder stringBuilder = new StringBuilder();         for (Map.Entry<String, String> entry : sortedMap.entrySet()) {             if (!entry.getKey().equals("sign") && entry.getValue() != null) {                 stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");             }         }         stringBuilder.append("key=").append(apiKey);         return md5(stringBuilder.toString()).toUpperCase();     }       public static String md5(String input) {         try {             MessageDigest md = MessageDigest.getInstance("MD5");             byte[] digest = md.digest(input.getBytes());             StringBuilder hexString = new StringBuilder();             for (byte b : digest) {                 String hex = Integer.toHexString(0xFF & b);                 if (hex.length() == 1) hexString.append('0');                 hexString.append(hex);             }             return hexString.toString();         } catch (Exception e) {             throw new RuntimeException(e);         }     }       public static Map<String, String> parseXml(String xml) {         // 使用 XStream 解析 XML         XStream xStream = new XStream();         xStream.alias("xml", HashMap.class);         return (Map<String, String>) xStream.fromXML(xml);     } }  三、参数配置及获取 一、回调函数的配置步骤 在微信商户平台配置回调地址:  登录微信商户平台。 找到“账户设置”或“API安全”选项。 在“支付结果通知 URL”中填写你的回调地址(如 https://yourdomain.com/wechat/notify)。 二、商户参数获取 商户参数主要包括微信支付的相关信息,这些信息可以在微信商户平台上获取。  商户参数 appId: 公众账号ID,由微信开放平台或微信支付商户平台提供。 mchId: 商户号,由微信支付商户平台提供。 apiKey: API 密钥,在微信支付商户平台设置,用于签名请求。 notifyUrl: 支付结果通知地址,即微信支付成功后,微信服务器将异步通知该地址。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/m0_66572126/article/details/143175353 
  • [技术干货] 【SpringBoot】什么是Maven,以及如何配置国内源实现自动获取jar包-转载
     📚️1.Maven的了解 1.1什么是Maven  在官方的文档中对于Maven的介绍是如下所示的:Welcome to Apache Maven – Maven   翻译过来就是: Maven是⼀个项⽬管理⼯具。基于POM(Project Object Model,项⽬对象模型)的概念,Maven可以通 过⼀⼩段描述信息来管理项⽬的构建,报告和⽂档的项⽬管理⼯具软件。  但是大白话就是:  Maven是⼀个项⽬管理⼯具, 通过pom.xml⽂件的配置获取jar包,⽽不⽤⼿动去添加jar包   1.2创建一个Maven项目 那么接下来就跟着小编进行操作吧~~~  注意:IDEA本⾝已经集成了Maven, 我们可以直接使⽤, ⽆需安装 步骤:File -> New -> Project 然后点击创建即可,此时我们就成功创建了一个Maven项目; 1.3Maven的核心功能 1.项目构建 Maven 提供了标准的,跨平台(Linux, Windows, MacOS等)的⾃动化项⽬构建⽅式  当我们开发了⼀个项⽬之后, 代码需要经过编译, 测试, 打包, 发布等流程, 每次代码的修改, 都需要经过 这些流程, 如果代码反复调试修改, 这个流程就需要反复进⾏, 就显得特别⿇烦,, ⽽Maven 给我们提供了 ⼀套简单的命令来完成项目的构建 解释: 打包就是把所有的class⽂件, 全部放在⼀起, 打成jar包或者war包 jar包和war包都是⼀种压缩⽂件  jar包:就是把开发⼈员已经写好的⼀些代码进⾏打包. 打好的jar包就可以引⼊到其他项⽬中, 也可以直 接使⽤这些jar包中的类和属性.  打成可执⾏jar包:这样的包就可以通过java -jar命令来执⾏  war包:可以理解为是⼀个web项⽬,⾥⾯是项⽬的所有东西, 通常⽤于⽹站  2.管理依赖 上⾯说到, Maven是⼀个项⽬管理⼯具, 通过pom.xml⽂件的配置获取jar包,⽽不⽤⼿动去添加jar包 获取的jar包, 其实就是依赖  依赖配置  在之前学习JDBC的时候,我们要添加mysql-connector-java的包.并且添加到项⽬中,此时才可以建立数据库与idea的连接 此时我们使用Maven的时候,直接可以通过代码操作直接导入jar包的操作,我们只需要在pom.xml中引⼊mysql-connector-java的依赖就可以了; 此时代码如下所示:  <dependencies>     <dependency>         <groupId>mysql</groupId>         <artifactId>mysql-connector-java</artifactId>         <version>8.0.30</version>     </dependency> </dependencies> 解释:  在<dependencies>中部的所有代码描述的就是建立依赖的坐标  注意引入代码后一定要记得刷新页面;  Maven管理依赖的好处 使⽤maven的话, 就可以避免管理所需依赖的关系。我们只需要在pom⽂件中, 定义直接依赖就可以 了, 由于maven的依赖具有传递性,所以会⾃动把所依赖的其他jar包也⼀起导⼊ 解释:  在Maven项目引入jar包A后,那么此时就会自动的引入jar包A所依赖的jar包,那么此时就会自动一如jar包B与jar包C;  依赖的排除 当前阶段我们需要依赖的库并不多, 但随着项⽬的越来越复杂, 库之间的依赖关系也会变得越来越复杂. 如上图中, 如果项⽬A不需要Jar B,也可以通过排除依赖的⽅式来实现  此时我们主动使用代码进行依赖的排除:  <dependency>      <groupId>org.springframework</groupId>      <artifactId>spring-core</artifactId>      <version>6.0.6</version>      <!--排除依赖-->      <exclusions>           <exclusion>           <artifactId>spring-jcl</artifactId>           <groupId>org.springframework</groupId>           </exclusion>      </exclusions> </dependency> 但是在idea中有一个插件可以帮助执行依赖的排除;  Maven helper插件 当项⽬⽐较复杂时, 我们就会有Jar包冲突的问题, 这时候就需要去解决依赖冲突. 解决冲突之前, 需要先 找到冲突, 我们可以使⽤Maven Help插件来观察包和包之间的依赖关系  下载方式:  File -> Settings ->Plugins -> 搜索'Maven Help' -> 找到对应插件, 点击Install 安装即可, 安装 后需要重启下idea, 才能⽣效 注意:下载后记得一定一定要进行重启的操作;  然后我们点击下面的依赖选项,可以看到依赖的关系: 此时我们可以通过手动右击鼠标,然后就可以进行手动的删除这里的某些依赖;  📚️2.Maven的仓库 我们通过短短⼏⾏代码, 就把依赖jar包放在了项⽬⾥, 具体是如何做的呢?  <dependencies>     <dependency>         <groupId>mysql</groupId>         <artifactId>mysql-connector-java</artifactId>         <version>8.0.30</version>     </dependency> </dependencies> 解释:  这个代码, 我们称之为 "坐标", 也就是唯⼀的. 在Maven中, 根据 groupId、artifactId、version 的配置, 来唯⼀识别⼀个 jar 包, 缺⼀不可. 当我们在pom⽂件中配置完依赖之后, 点击刷新, Maven会根据坐标的配置, 去仓库⾥寻找Jar包, 并把他 下载下来, 添加到项⽬中. 这个Jar包下载的地⽅就称为仓库  Maven仓库分为两⼤类: 本地仓库和远程仓库. 其中远程仓库⼜分为中央仓库, 私服 和其他公共库  2.1本地仓库 本地仓库: ⾃⼰计算机上的⼀个⽬录(⽤来存储jar包) 当项⽬中引⼊对应依赖jar包后,⾸先会查找本地仓库中是否有对应的jar包 • 如果有,则在项⽬直接引⽤ • 如果没有,则去中央仓库中下载对应的jar包到本地仓库;   本地仓库地址可以通过Maven配置查看:   注意:小编这里的本地仓库已经是通过了国内源的配置的(下文有讲到)   2.2中央仓库  中央仓库: maven 软件中内置⼀个远程仓库地址,就是中央仓库,服务于整个互联⽹. 由 Maven 团队 维护,全球唯⼀.  仓库地址:https://repo1.maven.org/maven2/  可以通过https://mvnrepository.com 这个⽹站来查询并下载  我们可以把⾃⼰写好的Jar包上传到中央仓库(具备⼀定的要求), 也可以从中央仓库下载Jar包  例如:我们进行jar包的查询     这里由于是国外的网站,所以访问比较慢,所以小编推荐使用加速器~~~  点击进入:   注意:一般来说点击没有漏洞的,并且这里的使用的人数尽可能多,这里点击8.3.0~~~   往下面进行翻,可以看到此时的依赖的代码;  这就是我们添加进入我们idea中的依赖的代码源,就是我们jar包的坐标;   2.3私有服务器 私服: ⼀般由公司团队搭建的私有仓库. 私服属于某个公司,或者某个部⻔, 往往需要⼀定权限. 有了私服之后, Maven依赖下载的顺序⼜发⽣了变化 解释:  1. 先从本地仓库获取, 本地仓库存在, 则直接返回  2. 如果本地仓库没有, 就从私服请求, 私服存在该资源, 就直接返回  3. 如果私服上不存在该资源,则从中央仓库下载, 中央仓库不存在, 就报错了...  4. 如果中央仓库中存在, 就先缓存在私服上之后,再缓存到本地仓库⾥, 再为Maven的下载请求提供服 务  2.4Maven设置国内源 因为中央仓库在国外, 所以下载起来会⽐较慢, 所以咱们选择借助国内⼀些公开的远程仓库来下载资源 接下来介绍, 如何设置国内源  国内使⽤时会⽐较慢,我们可以更换为阿⾥云 的仓库  打开settings.xml, 在 mirrors 节点上,添加内容如下:  <mirror>  <id>aliyunmaven</id>  <mirrorOf>central</mirrorOf>  <name>阿⾥云公共仓库</name>  <url>http://maven.aliyun.com/nexus/content/groups/public/</url>  </mirror> 然后将这个这个settings的xml文件添加到第一排这个文件路径目录上   小编这里是将这里的repository复制到新的路径上面去了,所以这里的路径就不是默认的;  然后这里的setting的文件就是如下: 点击进入:小编这里使用的是sublime文本编辑器,所以具体的样式就是如下所示的: 小编这里只截取了一小部分,所以就在你们的settings中添加就可以了,如果没有就私信小编哦,小编为你解答~~~ 📚️3.总结 本期总结了关于Maven在项目开发中的重要作用,从什么是Maven到理解它的核心功能,以及如何管理它的仓库,进行jar包坐标的配置,以及如何设置国内源都进行了详细的讲述;  🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!! ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/GGBond778/article/details/143827892 
  • [技术干货] Spring AOP 详解-转载
     Spring 框架有两大核心 IoC,AOP。在前面我们已经学习过了 IoC 的相关知识,今天就让我们开始 AOP 的学习。 一、AOP 概述 Aspect Oriented Programming(面向切面编程)。  切面就是指某一类特定问题,所以 AOP 也可以理解为面向特定方法编程。  **AOP 是一种思想,是对某一类事情的集中处理。**Spring AOP 是其中的一种实现方式。  AOP 的作用:在程序运行期间,在不修改源代码的基础上,对已有方法进行增强(无侵入性:解耦)。  二、Spring AOP 快速入门 我们先通过下面的程序体验下 AOP 的开发,并掌握 Spring 中 AOP 的开发步骤。  2.1 引入 AOP 依赖: 在 pom.xml 文件中添加配置:  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-aop</artifactId> </dependency> 2.2 编写 AOP 程序: @Aspect @Slf4j @Component public class TestAspect {     @Around("execution(* com.example.demo.controller.*.*(..))")     public Object demo(ProceedingJoinPoint joinPoint) throws Throwable {         log.info("方法执行前执行");         Object result = joinPoint.proceed();         log.info("方法执行后执行");         return result;     } } controller 类:  @RequestMapping("/test") @RestController @Slf4j public class TestController {      @RequestMapping("/t1")     public void test1(){         log.info("我是 test1");     } } 调用 controller 中的 test1 方法。  结果如下:   对程序进行简单的讲解:  @Aspect:标识这是一个切面类。  @Around:环绕通知,在目标方法的前后都会被执行。后面的表达式表示对哪些方法进行增强。  ProceedingJoinPoint.proceed()让原始方法执行。  整个代码划分为三部分。  通过上面的程序,我们也可以感受到 AOP 面向切面编程的一些优势:  代码无侵入:不修改原始的业务方法,就可以对原始的业务方法进行了功能的增强或者是功能的改变。 减少了重复代码。 提高开发效率。 维护方便。 三、Spring AOP 详解 3.1 Spring AOP 核心概念: 3.1.1 切点(Pointcut): 切点(Pointcut),也称之为"切入点"。  Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述),告诉程序对哪些方法来进行功能增强。  上面的表达式 execution(* com.example.demo.controller..(…)) 就是切点表达式。  3.1.2 连接点(Join Point): 满足切点表达式规则的方法,就是连接点。也就是可以被 AOP 控制的方法。  切点和连接点的关系: 连接点是满足切点表达式的元素。切点可以看做是保存了众多连接点的一个集合。  3.1.3 通知(Advice): 通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)。  在 AOP 面向切面编程当中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容。  3.1.4 切面(Aspect): 切面(Aspect)= 切点(Pointcut)+ 通知(Advice)。  通过切面就能够描述当前 AOP 程序需要针对于哪些方法,在什么时候执行什么样的操作。  切面既包含了通知逻辑的定义,也包括了连接点的定义。 切面所在的类,我们一般称为切面类(被 @Aspect 注解标识的类)。  3.2 通知类型: 上面我们讲了什么是通知,接下来学习通知的类型。@Around 就是其中一种通知类型,表示环绕通知。Spring 中 AOP 的通知类型有以下几种:  @Around:环绕通知,此注解标注的通知方法在目标方法前后都被执行。  @Before:前置通知,此注解标注的通知方法在目标方法前被执行。  @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行。  @AfterReturning:返回后通知,此注解标注的通知方法在目标方法返回后被执行,有异常不会执行。  @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行。  没有异常的运行顺序:  程序正常运行的情况下,@AfterThrowing 标识的通知方法不会执行。 出现异常的运行顺序:  @AfterReturning 标识的通知方法不会执行,@AfterThrowing 标识的通知方法执行了。  @Around 环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会再执行了(因为原始方法调用出异常了)。 注意: @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行。  @Around 环绕通知方法的返回值,必须指定为 Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。  一个切面类可以有多个切点。  3.3 @PointCut: Spring 提供了 @PointCut 注解,把公共的切点表达式提取出来,需要用到时引用该切入点表达式即可,便于后续代码的维护。  @Aspect @Slf4j @Component public class TestAspect {     @Pointcut("execution(* com.example.demo.controller.*.*(..))")     public void pt(){}     @Around("pt()")     public Object demo(ProceedingJoinPoint joinPoint) throws Throwable {         log.info("方法执行前执行");         Object result = joinPoint.proceed();         log.info("方法执行后执行");         return result;     } } 当切点定义使用 private 修饰时,仅能在当前切面类中使用,当其他切面类也要使用当前切点定义时,就需要把 private 改为 public。引用方式为:全限定类名.方法名()。  @Slf4j @Component @Aspect public class TestAspect2 {     @Before("com.example.demo.aspect.TestAspect.pt()")     public void doBefore() {         log.info("执⾏ TestAspect2 -> Before ⽅法");     } } 3.4 切面优先级 @Order: 当我们在一个项目中,定义了多个切面类时,并且这些切面类的多个切入点都匹配到了同一个目标方法。当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?  我们通过程序来进行验证。  @Slf4j @Component @Aspect public class TestAspect2 {     @Pointcut("execution(* com.example.demo.controller.*.*(..))")     private void pt(){}     //前置通知     @Before("pt()")     public void doBefore() {         log.info("执行 TestAspect2 -> Before 方法");     }     //后置通知     @After("pt()")     public void doAfter() {         log.info("执行 TestAspect2 -> After 方法");     } }  @Aspect @Component @Slf4j public class TestAspect3 {     @Pointcut("execution(* com.example.demo.controller.*.*(..))")     private void pt(){}     //前置通知     @Before("pt()")     public void doBefore() {         log.info("执行 TestAspect3 -> Before 方法");     }     //后置通知     @After("pt()")     public void doAfter() {         log.info("执行 TestAspect3 -> After 方法");     } }  @Aspect @Component @Slf4j public class TestAspect4 {     @Pointcut("execution(* com.example.demo.controller.*.*(..))")     private void pt(){}     //前置通知     @Before("pt()")     public void doBefore() {         log.info("执行 TestAspect4 -> Before 方法");     }     //后置通知     @After("pt()")     public void doAfter() {         log.info("执行 TestAspect4 -> After 方法");     } } 运行上面程序:   通过上述程序的运行结果,可以看出:  存在多个切面类时,默认按照切面类的类名字母排序:  @Before 通知:字母排名靠前的先执行。  @After 通知:字母排名靠前的后执行。  但这种方式不方便管理,我们的类名更多还是具备一定含义的。  Spring 给我们提供了一个新的注解,来控制这些切面通知的执行顺序:@Order。  @Slf4j @Component @Aspect @Order(10) public class TestAspect2 {     //代码省略 }  @Aspect @Component @Slf4j @Order(5) public class TestAspect3 {     //代码省略 }  @Aspect @Component @Slf4j @Order(1) public class TestAspect4 {     //代码省略 } 运行程序:  通过上述程序的运行结果,得出结论:  @Order 注解标识的切面类,执行顺序如下:  @Before 通知:数字越小先执行。  @After 通知:数字越大先执行。  @Order 的执行顺序可以抽象成下面这张图:  3.5 切点表达式: 上面的代码中,我们一直在使用切点表达式来描述切点。下面我们来介绍一下切点表达式的语法。  切点表达式常见有两种表达方式:  execution:根据方法的签名来匹配。 @annotation:根据注解匹配。 3.5.1 execution 表达式: execution() 是最常用的切点表达式,用来匹配方法,语法为:  execution (<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>) 其中:访问修饰符和异常可以省略。  切点表达式支持通配符表达:  * :匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)。 包名使用 * 表示任意包(一层包使用一个 * )。 类名使用 * 表示任意类。 返回值使用 * 表示任意返回值类型。 方法名使用 * 表示任意方法(参数可能有限制)。 参数使用 * 表示一个任意类型的参数。 ..:匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数。 使用..配置包名,标识此包以及此包下的所有子包。 可以使用..配置参数,任意个任意类型的参数。 3.5.2 @annotation: execution 表达式更适用有规则的,如果我们要匹配多个无规则的方法呢,比如:TestController 中的 t1() 和 UserController 中的 u1() 这两个方法。这个时候我们使用 execution 这种切点表达式来描述就不是很方便了。我们可以借助自定义注解的方式以及另一种切点表达式 @annotation 来描述这一类的切点。  实现步骤:  编写自定义注解。 使用 @annotation 表达式来描述切点。 在方法上添加自定义注解。 创建一个注解类(和创建 Class 文件一样的流程,选择 Annotation 就可以了)  @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAspect { } 使用 @annotation 表达式来描述切点。  @Component @Slf4j @Aspect public class MyAspectDemo {     @Before("@annotation(com.example.demo.aspect.MyAspect)")     public void doBefore(){         log.info("我是 MyAspectDemo");     } } 在方法上添加自定义注解。  @RequestMapping("/test") @RestController @Slf4j public class TestController {     @MyAspect     @RequestMapping("/t1")     public void test1(){         log.info("我是 test1");     } } 运行程序,访问 test1 方法。 3.6 Spring AOP 的实现方式(常见面试题): 基于注解 @Aspect。  基于自定义注解(@annotation)。  基于 Spring API(通过 xml 配置的方式,自从 SpringBoot 广泛使用之后,这种方法几乎看不到了)。  基于代理来实现(更加久远的一种实现方式,写法笨重,不建议使用)。  四、代理模式 Spring AOP 是基于动态代理来实现 AOP 的。  代理模式,也叫委托模式。  定义:  为其他对象提供一种代理,以控制对这个对象的访问。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。  代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。  根据代理的创建时期,代理模式分为静态代理和动态代理。  静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。 动态代理:在程序运行时,运用反射机制动态创建而成。 结语: 其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话,还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。 ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/2301_80035594/article/details/143078123 
  • [技术干货] 【Spring MVC】快速学习使用Spring MVC的注解及三层架构-转载
     Spring Web MVC 一: 什么是Spring Web MVC? SpringWebMVC是基于ServletAPI构建的原始Web框架,从⼀开始就包含在Spring框架中。它的正式名称“SpringWebMVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为)SpringMVC). MVC是⼀种架构设计模式,也⼀种思想,⽽SpringMVC是对MVC思想的具体实现.除此之外,SpringMVC还是⼀个Web框架. 总结来说,SpringMVC是⼀个实现了MVC模式的Web框架  MVC的定义 MVC是ModelViewController的缩写,它是软件⼯程中的⼀种软件架构设计模式,它把软件系统分 为模型、视图和控制器三个基本部分  • View(视图)指在应⽤程序中专⻔⽤来与浏览器进⾏交互,展⽰数据的资源. • Model(模型)是应⽤程序的主体部分,⽤来处理程序中数据逻辑的部分. • Controller(控制器)可以理解为⼀个分发器,⽤来决定对于视图发来的请求,需要⽤哪⼀个模型 来处理,以及处理完后需要跳回到哪⼀个视图。即⽤来连接视图和模型  二:Spring MVC中常用注解的使用 2.1 @RequestMapping:地址映射 @RequestMapping既可修饰类,也可以修饰⽅法,当修饰类和⽅法时,访问的地址是类路径+⽅ 法路径. @RequestMapping标识⼀个类:设置映射请求的请求路径的初始信息 @RequestMapping标识⼀个⽅法:设置映射请求请求路径的具体信息  @RequestMapping("/user") @RestController public class UserController {     @RequestMapping("/sayHi")     public String sayHi(){     return "hello,Spring MVC";     } } 2.2 @RequestBody:请求正文 RequestBody:请求正⽂,意思是这个注解作⽤在请求正⽂的数据绑定,请求参数必须在写在请求正 ⽂中  @RequestMapping(value = "/m7") public Object method7(@RequestBody Person person) {     return person.toString(); } 2.3 @PathVariable:路径变量 pathvariable:路径变量 和字⾯表达的意思⼀样,这个注解主要作⽤在请求URL路径上的数据绑定 默认传递参数写在URL上,SpringMVC就可以获取到 后端实现代码:  @RequestMapping("/m8/{id}/{name}") public String method8(@PathVariable Integer id, @PathVariable("name") StringuserName){         return "解析参数id:"+id+",name:"+userName; } 如果⽅法参数名称和需要绑定的URL中的变量名称⼀致时,可以简写,不⽤给@PathVariable的属性赋 值,如上述例⼦中的id变量 如果⽅法参数名称和需要绑定的URL中的变量名称不⼀致时,需要@PathVariable的属性value赋值, 如上述例⼦中的userName变量  2.4 @RequestPart:上传文件 RequestPart主要是用于客户端上传文件交给服务器进行处理 后端代码实现:  @RequestMapping("/m9") public String getfile(@RequestPart("file") MultipartFile file) throwsIOException {     //获取⽂件名称         String fileName = file.getOriginalFilename();     //⽂件上传到指定路径         file.transferTo(new File("D:/temp/" + file.getOriginalFilename()));         return "接收到⽂件名称为: "+fileName; } 2.5 @RequestParam:重命名 某些特殊的情况下,前端传递的参数key和我们后端接收的key可以不⼀致,⽐如前端传递了⼀个 time给后端,⽽后端是使⽤createtime字段来接收的,这样就会出现参数接收不到的情况,如果出现 这种情况,我们就可以使⽤ @RequestParam 来重命名前后端的参数值 后端实现代码:  @RequestMapping("/m4") public Object method_4(@RequestParam("time") String createtime) {         return "接收到参数createtime:" + createtime; } 可以得出结论:  使⽤ @RequestParam 进⾏参数重命名时,请求参数只能和 @RequestParam 声明的名称⼀ 致,才能进⾏参数绑定和赋值. 使⽤ @RequestParam 进⾏参数重命名时,参数就变成了必传参数. 2.6 @RestController:数据控制器 是Contronller和ResponseBody的封装,用于返回数据同时能够将注解内容交给Spring进行管理 @RestController = @Controller + @ResponseBody @Controller :定义⼀个控制器,Spring框架启动时加载,把这个对象交给Spring管理. @ResponseBody :定义返回的数据格式为⾮视图,返回⼀个text/html信息  2.7 @ResponseBody:数据格式 @ResponseBody :定义返回的数据格式为⾮视图,返回⼀个text/html信息  @ResponseBody public class IndexController {     @RequestMapping("/index")         public Object index(){         return "/index.html";     } } @ResponseBody 既是类注解,⼜是⽅法注解 如果作⽤在类上,表⽰该类的所有⽅法,返回的都是数据,如果作⽤在⽅法上,表⽰该⽅法返回的是数据. 也就是说:在类上添加 @ResponseBody 就相当于在所有的⽅法上添加了 @ResponseBody 注解. 同样,如果类上有 @RestController 注解时:表⽰所有的⽅法上添加了 @ResponseBody 注 解,也就是当前类下所有的⽅法返回值做为响应数据 设置状态码 SpringMVC会根据我们⽅法的返回结果⾃动设置响应状态码,程序员也可以⼿动指定状态码 通过SpringMVC的内置对象HttpServletResponse提供的⽅法来进⾏设置      @ResponseBody     public String setStatus(HttpServletResponse response) {         response.setStatus(401);         return "设置状态码成功";     } 2.8 @CookieValue:获取Cookie Cook是用户客户端用于保存用户的基本信息,以便于用户在下一次登录时可以直接进入  @RequestMapping("/getCookie") public String cookie(@CookieValue("bite") String bite) {     return "bite:" + bite; } 2.9 @SessionAttribute:获取Session Session是服务器为了保存⽤⼾信息⽽创建的⼀个特殊的对象  Session的本质就是⼀个"哈希表",存储了⼀些键值对结构.Key就是SessionID,Value就是⽤⼾信息(⽤ ⼾信息可以根据需求灵活设计). SessionId是由服务器⽣成的⼀个"唯⼀性字符串",从Session机制的⻆度来看,这个唯⼀性字符串称 为)SessionId).但是站在整个登录流程中看待,也可以把这个唯⼀性字符串称为)token).  当⽤⼾登陆的时候,服务器在Session中新增⼀个新记录,并把sessionId返回给客⼾端.(通过 HTTP响应中的Set-Cookie字段返回). 客⼾端后续再给服务器发送请求的时候,需要在请求中带上sessionId.(通过HTTP请求中的 Cookie字段带上). 服务器收到请求之后,根据请求中的sessionId在Session信息中获取到对应的⽤⼾信息,再进⾏后 续操作.找不到则重新创建Session,并把SessionID返回 @RequestMapping("/getSess2") public String sess2(@SessionAttribute(value = "username",required = false)String username) {         return "username:"+username; } Cookie和Session的区别 • Cookie是客⼾端保存⽤⼾信息的⼀种机制.Session是服务器端保存⽤⼾信息的⼀种机制. • Cookie和Session之间主要是通过SessionId关联起来的,SessionId是Cookie和Session之间的 桥梁 • Cookie和Session经常会在⼀起配合使⽤.但是不是必须配合. ◦ 完全可以⽤Cookie来保存⼀些数据在客⼾端.这些数据不⼀定是⽤⼾⾝份信息,也不⼀定是 SessionId ◦ Session中的sessionId也不需要⾮得通过Cookie/Set-Cookie传递,⽐如通过URL传递.  2.10 @Data:类注解 Lombok是⼀个Java⼯具库,通过添加注解的⽅式,简化Java的开发其中@Data 注解会帮助我们⾃动⼀些⽅法,包含getter/setter,equals,toString等 Lombok的作⽤如下图所⽰:  如果觉得@Data⽐较粗暴(⽣成⽅法太多),lombok也提供了⼀些更精细粒度的注解  注解    作⽤ @Getter    ⾃动添加getter⽅法 @Setter    ⾃动添加setter⽅法 @ToString    ⾃动添加toString⽅法 @EqualsAndHashCode    ⾃动添加equals和hashCode⽅法 @NoArgsConstructor    ⾃动添加⽆参构造⽅法 @AllArgsConstructor    ⾃动添加全属性构造⽅法,顺序按照属性的定义顺序 @NonNull    属性不能为null @RequiredArgsConstructor    ⾃动添加必需属性的构造⽅法,final+@NonNull的属性为必需 @Data=@Getter+@Setter+@ToString+@EqualsAndHashCode+@RequiredArgsConstructor+@NoArgsConstructor 因此可以说明在创建类对象时可以通过引入lambok工具包中的@Data注解,可以自动为我们添加其很多常用的赋值方法  三:应用分层-三层架构 ⽬前现在更主流的开发⽅式是"前后端分离"的⽅式,后端开发⼯程师不再需要关注前端的实现,所以对 于Java后端开发者,⼜有了⼀种新的分层架构:把整体架构分为表现层、业务逻辑层和数据层.这种分层 ⽅式也称之为"三层架构".  表现层:就是展⽰数据结果和接受⽤⼾指令的,是最靠近⽤⼾的⼀层; 业务逻辑层:负责处理业务逻辑,⾥⾯有复杂业务的具体实现; 数据层:负责存储和管理与应⽤程序相关的数据 可以看到,咱们前⾯的代码,并不符合这种设计思想,⽽是所有的代码堆砌在⼀起 按照上⾯的层次划分,SpringMVC站在后端开发⼈员的⻆度上,也进⾏了⽀持, • 请求处理、响应数据:负责,接收⻚⾯的请求,给⻚⾯响应数据. • 逻辑处理:负责业务逻辑处理的代码. • 数据访问:负责业务数据的维护操作,包括增、删、改、查等操作  • Controller:控制层。接收前端发送的请求,对请求进⾏处理,并响应数据。 • Service:业务逻辑层。处理具体的业务逻辑。 • Dao:数据访问层,也称为持久层。负责数据访问操作,包括数据的增、删、改、查 MVC和三层架构的区别和联系 关于⼆者的关系,⼀直存在不同的观点.有⼈认为三层架构是MVC模式的⼀种实现,也有⼈认为MVC是 三层架构的替代⽅案,等等各种说法都有.根本原因是⼤家站在不同的⻆度来看待这个问题的. JavaEE部分的学习重在"实践",⼤家根据⾃⼰的理解,能够⾃圆其说,说出⾃⼰的观点即可,也不建议 ⼤家去背书.  从概念上来讲,⼆者都是软件⼯程领域中的架构模式. MVC架构模式由三部分组成,分别是:模型(Model),视图(View)和控制器(Controller). 三层架构将业务应⽤划分为:表现层,业务逻辑层,数据访问层.  MVC中,视图和控制器合起来对应三层架构中的表现层.模型对应三层架构中的业务逻辑层,数据层, 以及实体类 ⼆者其实是从不同⻆度对软件⼯程进⾏了抽象. MVC模式强调数据和视图分离,将数据展⽰和数据处理分开,通过控制器对两者进⾏组合. 三层架构强调不同维度数据处理的⾼内聚和低耦合,将交互界⾯,业务处理和数据库操作的逻辑分开. ⻆度不同也就谈不上互相替代了,在⽇常的开发中可以经常看到两种共存的情况,⽐如我们设计模型 层的时候往往也会拆分出业务逻辑层(Service层)和数据访问层(Dao层)  四:总结 1.学习SpringMVC,其实就是学习各种Web开发需要⽤的到注解 a. @RequestMapping:路由映射 b. @RequestParam:后端参数重命名 c. @RequestBody:接收JSON类型的参数 d. @PathVariable:接收路径参数 e. @RequestPart:上传⽂件 f. @ResponseBody:返回数据 g. @CookieValue:从Cookie中获取值 h. @SessionAttribute:从Session中获取值 i. @RequestHeader:从Header中获取值 j. @Controller:定义⼀个控制器,Spring框架启动时加载,把这个对象交给Spring管理.默认返回 视图. k. @RestController:@ResponseBody+@Controller返回数据 2. Cookie和Session都是会话机制,Cookie是客⼾端机制,Session是服务端机制.⼆者通过SessionId 来关联.SpringMVC内置HttpServletRequest,HttpServletResponse两个对象.需要使⽤时,直接在 ⽅法中添加对应参数即可,Cookie和Session可以从HttpServletRequest中来获取,也可以直接使⽤ HttpServletResponse设置Http响应状态码. ————————————————                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                          原文链接:https://blog.csdn.net/smile_sundays/article/details/137207581 
  • [技术干货] 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
  • [技术干货] 【SpringBoot篇】登录校验 — JWT令牌-转载
     🌹简述JWT令牌 JWT全称为JSON Web Token,是一种用于身份验证的开放标准。它是一个基于JSON格式的安全令牌,主要用于在网络上传输声明或者用户身份信息。JWT通常被用作API的认证方式,以及跨域身份验证。  JWT令牌由三部分组成,分别是头部(Header)、载荷(Payload)和签名(Signature)。头部包含了令牌使用的加密算法信息,载荷包含了所需传输的用户信息,签名用于保证令牌的完整性和真实性,防止令牌被篡改。   官网https://jwt.io/  ⭐JWT特点 可以跨语言、跨平台使用,因为它是基于JSON标准的。 可以直接嵌入到HTTP请求头中,方便传输和验证。 令牌的有效期可以通过设置过期时间来进行控制,提高了安全性。 由于令牌中包含了用户信息,因此可以避免频繁查询数据库的情况出现,提高了系统的性能。 🌺JWT使用流程 用户向服务器发送登录请求,服务器进行身份验证,如果验证成功则返回一个JWT令牌给客户端。  客户端收到JWT令牌后,将其保存在本地。每次向服务器发送请求时,在请求的头部中携带该令牌,以便服务器对请求进行身份验证。  服务器收到请求后,从请求头中提取JWT令牌,并进行解析和验证。如果令牌有效,则允许请求继续执行;否则返回错误信息。  生成令牌,校验令牌  在服务端拦截所有的请求,判断算法有合法的jwt请求,如果有,直接放行,否则进行拦截  🛸JWT令牌代码实现 我把代码脚手架传到网盘里面了,大家跟着代码来学习 我用夸克网盘分享了「tlias-web-management」,点击链接即可保存。 链接:https://pan.quark.cn/s/1f4f6c129be8  添加依赖  <!--        JWT令牌-->         <dependency>             <groupId>io.jsonwebtoken</groupId>             <artifactId>jjwt</artifactId>             <version>0.9.1</version>         </dependency> 生成JWT令牌   //生成jwt     @Test     public void testGenJwt(){         Map<String, Object> claims = new HashMap<>();         claims.put("id",1);         claims.put("name","Tom");          String jwt = Jwts.builder()                 .signWith(SignatureAlgorithm.HS512, "itheima")  //签名算法                 .setClaims(claims)   //自定义内容                                 //有参构造方法                 .setExpiration(new Date(System.currentTimeMillis()+3600))  //令牌过期时间                .compact();         System.out.println(jwt);      }  运行后发现,出现了jwt令牌  我们把这一段jwt令牌复制粘贴到jwt官网进行解析一下  https://jwt.io/  解析jwt令牌 (相当于校验令牌,只要解析令牌不报错,就相当于校验jwt令牌正确)     //解析jwt     @Test     public void testParseJwt() {         Claims claims=Jwts.parser()                 .setSigningKey("itheima")                 //写入你刚才运行出来的jwt令牌                 .parseClaimsJws("eyJhbGciOiJIUzUxMiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwMDcyMzQ1M30.GMp1Z-osnaOJ08nM3uswPKRFIaKS4e6_UvZXq2Q4QjYBFRcJNk7WgQRkFJHXIUrZfKovXUZhd8-OOKtXYDyrbg")                 .getBody();         System.out.println(claims);     } 解析出来了 可能会发生这种报错,是因为jwt令牌过期了,重新生成一个即可  🍔JWT应用 我们接着上面的代码写,引入jwt工具类   package com.itheima.utils;  import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm;  import java.util.Date; import java.util.Map;  public class JwtUtils {      private static String signKey = "itheima";    //签名密钥     private static Long expire = 43200000L;        //过期时间      /**      * 生成JWT令牌      * @param claims JWT第二部分负载 payload 中存储的内容      * @return      */     public static String generateJwt(Map<String, Object> claims){         String jwt = Jwts.builder()                 .addClaims(claims)                 .signWith(SignatureAlgorithm.HS256, signKey)                 .setExpiration(new Date(System.currentTimeMillis() + expire))                 .compact();         return jwt;     }      /**      * 解析JWT令牌      * @param jwt JWT令牌      * @return JWT第二部分负载 payload 中存储的内容      */     public static Claims parseJWT(String jwt){         Claims claims = Jwts.parser()                 .setSigningKey(signKey)                 .parseClaimsJws(jwt)                 .getBody();         return claims;     } } 创建LoginController,里面包含了生成jwt令牌的代码  package com.itheima.controller;  import com.itheima.pojo.Emp; import com.itheima.pojo.Result; import com.itheima.service.EmpService; import com.itheima.utils.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController;  import java.util.HashMap; import java.util.Map;  @Slf4j @RestController public class LoginController {      @Autowired     private EmpService empService;      @PostMapping("/login")     public Result login(@RequestBody Emp emp){         log.info("员工登录: {}", emp);         Emp e = empService.login(emp);          //登录成功,生成令牌,下发令牌         if (e != null){             Map<String, Object> claims = new HashMap<>();             claims.put("id", e.getId());             claims.put("name", e.getName());             claims.put("username", e.getUsername());              String jwt = JwtUtils.generateJwt(claims); //jwt包含了当前登录的员工信息             return Result.success(jwt);         }          //登录失败, 返回错误信息         return Result.error("用户名或密码错误");     }  } 在技术的道路上,我们不断探索、不断前行,不断面对挑战、不断突破自我。科技的发展改变着世界,而我们作为技术人员,也在这个过程中书写着自己的篇章。让我们携手并进,共同努力,开创美好的未来!愿我们在科技的征途上不断奋进,创造出更加美好、更加智能的明天! ———————————————— 版权声明:本文为CSDN博主「在下小吉.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_72853403/article/details/134686838 
  • [技术干货] Spring 中对 controller和service层的一些见解
    接触Spring Boot开发一年不到,回想起前几年使用spring MVC的时候,因为当时公司业务比较简单,所以service层和dao层实际上是一样的,业务逻辑全部放在了controller层来做;当时觉得很纳闷,service层感觉是多余的,根本用不到;最近接触的项目,架构师设计的框架,直接根据模型设计dao层接口和service接口,代码写了不少,突然发现这么定义接口很多功能是没法实现的。于是回头重新思考了spring MVC模型,刚才看了篇 非常不错的博客 ,感觉作者能把这个问题解释清楚了。还是从MVC三层模型开始,这三层模型的设计之初,就是为了将业务层(controller)、视图层(view)以及模型层(modal)区分开来。需要注意的是,这里并没有数据库这个概念,所以模型层会有一些冗杂,两个表的联合查询出来的数据,会被封装成一个模型交给控制层;同样的,控制层因为没有服务的概念,如果项目比较大,也会变的有些冗余。基于controller和modal层并没有很好的实现模块化,因此,我们将modal层去掉,改为更加原子化的dao层;同时,将controller层的业务逻辑,划分成多个服务(service)。每个服务可以组合使用dao层数据,组装成一个服务,比如用户的注册服务;而controller层,调用多个service服务完成url请求。简单来说,增加service层,替换modal层,第一是细化了数据模型,使得我们在改动某张表时,只需要改动dao层实现即可,最大化的减少了代码的改动成本;当然,更多的情况是service服务和controller可能都需要更改; service层将controller的逻辑分类,保证了controller的逻辑更加清晰。举个生活中的例子,用户预约某个酒店的客房,这是酒店首先会调用验证服务对用户提供的信息进行验证,之后调用预约服务进行预约,如果预约失败,酒店可能会把客户的预约信息提交给另外一家酒店请求它们的预约服务,然后将结果返回给客户;对于服务层来说,需要判断酒店是否有空余客房,之后修改客房信息,同时将客房和用户信息存入临时表。这里至少需要两种不同的dao层服务实现service。所以整体上来看,controllrt->service->dao至少是一对一,更多的情况下是一对多。这也就是service层存在的意义了。