springboot的一些应用总结

1. Spring Boot 的概念、优缺点

  • 概念: Spring Boot 是一个开源的 Java 框架,旨在简化 Spring 应用程序的开发和部署。它基于 Spring 框架,通过提供约定优于配置、内嵌服务器、自动配置和 starter 依赖等特性,让开发者能够快速构建独立的、生产级别的 Spring 应用程序。

  • 优点:

    • 快速开发: 提供了大量开箱即用的功能和默认配置,减少了手动配置的工作量。
    • 独立运行: 内嵌了 Tomcat、Jetty 或 Undertow 等服务器,可以直接打包成可执行 JAR 文件运行,无需外部应用服务器。
    • 约定优于配置: 减少了 XML 配置,使用注解和默认值进行配置,降低了开发的复杂性。
    • Starter 依赖: 提供了一系列 "starter" POM,可以方便地引入所需的功能模块,并自动管理依赖版本。
    • 自动配置: 根据项目依赖和配置,自动配置 Spring 应用程序的各个方面,如数据源、JPA、Web 等。
    • 生产就绪特性: 提供了健康检查、外部化配置、指标监控等生产级别的特性。
    • 强大的生态系统: 基于 Spring 框架,可以充分利用 Spring 提供的丰富功能。
  • 缺点:

    • 学习曲线: 对于初学者来说,Spring Boot 的一些高级特性和原理可能需要一定的学习成本。
    • 过度依赖: 如果不了解底层原理,过度依赖 Spring Boot 的自动配置可能导致一些问题难以排查。
    • 内存占用: 相对于传统的 WAR 包部署,内嵌服务器的 JAR 包可能会占用更多的内存。

2. Spring Boot 核心注解和基本注解

  • 核心注解:

    • @SpringBootApplication: 这是一个组合注解,等同于 @Configuration@EnableAutoConfiguration@ComponentScan。它是 Spring Boot 应用程序的入口,通常放在主应用程序类上。

      • @Configuration: 声明一个类为配置类,可以包含 @Bean 定义。
      • @EnableAutoConfiguration: 启用 Spring Boot 的自动配置机制,根据 classpath 依赖和自定义配置自动配置应用程序。
      • @ComponentScan: 扫描指定包及其子包下的组件(@Component, @Service, @Repository, @Controller 等),并将它们注册到 Spring 容器中。
  • 基本注解:

    • @Controller: 标识一个类为 Spring MVC 的控制器,处理 HTTP 请求。
    • @RestController: 组合了 @Controller@ResponseBody,用于构建 RESTful API,直接将返回值作为响应体。
    • @Service: 标识一个类为业务逻辑层组件。
    • @Repository: 标识一个类为数据访问层组件,通常用于 DAO (Data Access Object)。
    • @Component: 通用的组件注解,任何 Spring 管理的组件都可以使用此注解。
    • @Autowired: 自动装配依赖,可以通过类型或名称进行注入。
    • @Value: 注入配置文件中的属性值。
    • @RequestMapping: 映射 HTTP 请求到控制器方法,可以指定请求路径、请求方法等。
    • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping: @RequestMapping 的快捷方式,分别对应不同的 HTTP 请求方法。
    • @PathVariable: 从 URL 路径中提取参数。
    • @RequestParam: 从请求参数中获取值。
    • @RequestBody: 将请求体的内容绑定到方法参数上,通常用于接收 JSON 或 XML 数据。
    • @ResponseBody: 将方法返回值直接作为响应体返回,通常用于 RESTful API。

3. Spring Boot 的全局配置文件,及语法如何通过注解给对象注入数据

  • 全局配置文件: Spring Boot 提供了两种主要的全局配置文件:

    • application.properties: 采用键值对的形式配置,例如 server.port=8080

    • application.ymlapplication.yaml: 采用 YAML 格式配置,具有更清晰的层级结构和更少的冗余,例如:

      yaml 复制代码
      yaml
      server:
        port: 8080
      spring:
        datasource:
          url: jdbc:mysql://localhost:3306/mydb

    这些配置文件通常放在 src/main/resources 目录下。

  • 通过注解给对象注入数据:

    • @Value 注解: 用于注入单个属性值。

      kotlin 复制代码
      java
      @Component
      public class MyConfig {
          @Value("${my.app.name}")
          private String appName;
      
          @Value("${my.app.version:1.0.0}") // 提供默认值
          private String appVersion;
      
          public void printConfig() {
              System.out.println("App Name: " + appName);
              System.out.println("App Version: " + appVersion);
          }
      }

      application.properties 中:

      ini 复制代码
      properties
      my.app.name=My Spring Boot App
    • @ConfigurationProperties 注解: 用于将配置文件中的一组相关属性绑定到一个 Java 对象上,实现批量注入。

      typescript 复制代码
      java
      @Component
      @ConfigurationProperties(prefix = "my.app")
      public class AppProperties {
          private String name;
          private String version;
          private List<String> authors; // 支持集合类型
      
          // Getter and Setter methods
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getVersion() {
              return version;
          }
      
          public void setVersion(String version) {
              this.version = version;
          }
      
          public List<String> getAuthors() {
              return authors;
          }
      
          public void setAuthors(List<String> authors) {
              this.authors = authors;
          }
      
          public void printProperties() {
              System.out.println("App Name: " + name);
              System.out.println("App Version: " + version);
              System.out.println("Authors: " + authors);
          }
      }

      application.yml 中:

      yaml 复制代码
      yaml
      my:
        app:
          name: My Spring Boot App
          version: 1.0.0
          authors:
            - Alice
            - Bob

4. Spring Boot 统一异常处理的语法及流程

  • 语法 : Spring Boot 提供了多种方式来实现统一异常处理,最常用的是使用 @ControllerAdvice@ExceptionHandler 注解。

    typescript 复制代码
    java
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    @ControllerAdvice // 声明这是一个全局异常处理类
    public class GlobalExceptionHandler {
    
        // 处理自定义异常
        @ExceptionHandler(MyCustomException.class)
        public ResponseEntity<String> handleMyCustomException(MyCustomException ex) {
            return new ResponseEntity<>("Custom Error: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
        }
    
        // 处理所有其他未被特定处理的异常
        @ExceptionHandler(Exception.class)
        public ResponseEntity<String> handleAllExceptions(Exception ex) {
            return new ResponseEntity<>("An unexpected error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    
        // 可以处理特定的HTTP状态码异常
        // @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
        // public ResponseEntity<String> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex) {
        //     return new ResponseEntity<>("Method not supported: " + ex.getMethod(), HttpStatus.METHOD_NOT_ALLOWED);
        // }
    }

    自定义异常类:

    scala 复制代码
    java
    public class MyCustomException extends RuntimeException {
        public MyCustomException(String message) {
            super(message);
        }
    }
  • 流程:

    1. 当 Spring Boot 应用程序中的控制器方法抛出异常时,Spring 容器会捕获该异常。
    2. Spring 会查找带有 @ControllerAdvice 注解的类。
    3. @ControllerAdvice 类中,Spring 会寻找匹配异常类型的 @ExceptionHandler 方法。
    4. 如果找到匹配的方法,该方法将被执行,并根据其返回值(通常是 ResponseEntity)生成 HTTP 响应。
    5. 如果找不到匹配的 @ExceptionHandler 方法,或者没有定义 @ControllerAdvice 类,Spring Boot 会使用其默认的错误处理机制(例如,返回一个包含错误信息的 HTML 页面或 JSON 对象)。

5. Spring Boot 项目的基本结构

一个典型的 Spring Boot 项目的基本结构如下:

scss 复制代码
scss
my-spring-boot-app/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── myapp/
│   │   │               ├── MySpringBootApplication.java  (主应用程序启动类)
│   │   │               ├── controller/                  (控制器层)
│   │   │               │   └── MyController.java
│   │   │               ├── service/                     (业务逻辑层)
│   │   │               │   └── MyService.java
│   │   │               ├── repository/                  (数据访问层)
│   │   │               │   └── MyRepository.java
│   │   │               ├── entity/                      (实体类/POJO)
│   │   │               │   └── MyEntity.java
│   │   │               └── config/                      (配置类)
│   │   │                   └── WebConfig.java
│   │   └── resources/
│   │       ├── application.properties 或 application.yml (全局配置文件)
│   │       ├── static/                                  (静态资源,如 CSS, JS, 图片)
│   │       │   ├── css/
│   │       │   └── js/
│   │       ├── templates/                               (模板文件,如 Thymeleaf, FreeMarker)
│   │       │   └── index.html
│   │       └── logback-spring.xml 或 log4j2-spring.xml  (日志配置文件,可选)
│   └── test/
│       └── java/
│           └── com/
│               └── example/
│                   └── myapp/
│                       └── MySpringBootApplicationTests.java (测试类)
├── pom.xml                                            (Maven 项目配置文件)
└── .gitignore
  • src/main/java: 存放 Java 源代码。

    • com.example.myapp : 主包,通常包含主应用程序启动类 MySpringBootApplication.java
    • controller: 存放处理 HTTP 请求的控制器类。
    • service: 存放业务逻辑处理类。
    • repository: 存放数据访问层接口和实现类。
    • entity: 存放实体类或 POJO(Plain Old Java Object)。
    • config: 存放自定义配置类。
  • src/main/resources: 存放资源文件。

    • application.properties / application.yml: 全局配置文件。
    • static: 存放静态资源,如 HTML、CSS、JavaScript、图片等。
    • templates: 存放模板引擎(如 Thymeleaf、Freemarker)使用的模板文件。
    • 日志配置文件: 可选,用于配置日志系统。
  • src/test/java: 存放测试代码。

  • pom.xml: Maven 项目配置文件,用于管理项目依赖、构建配置等。

6. Servlet 的概念、作用、语法、配置和并描述 Servlet 的生命周期

  • 概念: Servlet (Server Applet) 是 Java EE 规范中的一个组件,用于扩展 Web 服务器的功能。它是一个在服务器端运行的 Java 类,用于处理客户端(通常是 Web 浏览器)的请求并生成响应。

  • 作用:

    • 接收并处理客户端的 HTTP 请求。
    • 生成动态内容(如 HTML、XML、JSON)。
    • 与数据库进行交互。
    • 管理会话。
    • 实现业务逻辑。
  • 语法 : 创建一个 Servlet 需要实现 javax.servlet.Servlet 接口,或者继承 javax.servlet.GenericServletjavax.servlet.http.HttpServlet 类。通常我们会继承 HttpServlet,因为它提供了方便的 doGet(), doPost() 等方法来处理不同的 HTTP 请求。

    java 复制代码
    java
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    @WebServlet("/hello") // 使用注解配置 Servlet
    public class MyServlet extends HttpServlet {
    
        @Override
        public void init() throws ServletException {
            // Servlet 初始化时调用,只调用一次
            System.out.println("Servlet initialized.");
        }
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 处理 GET 请求
            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            out.println("<html><body>");
            out.println("<h1>Hello from MyServlet!</h1>");
            out.println("</body></html>");
            out.close();
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 处理 POST 请求
            doGet(request, response); // 简单示例,POST 也调用 GET
        }
    
        @Override
        public void destroy() {
            // Servlet 销毁时调用,只调用一次
            System.out.println("Servlet destroyed.");
        }
    }
  • 配置:

    • 注解配置 (推荐) : 使用 @WebServlet 注解直接在 Servlet 类上进行配置,指定 URL 映射。

    • web.xml 配置 (传统方式) : 在 web.xml 文件中配置 Servlet,包括 Servlet 名称、类名、URL 映射等。

      xml 复制代码
      xml
      <servlet>
          <servlet-name>MyServlet</servlet-name>
          <servlet-class>com.example.MyServlet</servlet-class>
      </servlet>
      <servlet-mapping>
          <servlet-name>MyServlet</servlet-name>
          <url-pattern>/hello</url-pattern>
      </servlet-mapping>

    在 Spring Boot 中,通常直接使用 @WebServlet 注解来注册 Servlet。

  • Servlet 的生命周期:

    1. 加载和实例化: 当 Web 容器(如 Tomcat)启动时,或者当第一次请求到达时,Web 容器会加载 Servlet 类并创建其实例。
    2. 初始化 (init() 方法) : 在 Servlet 实例创建后,Web 容器会调用其 init() 方法,进行一次性的初始化工作(如加载配置、建立数据库连接等)。该方法只在 Servlet 生命周期中调用一次。
    3. 服务 (service() 方法) : 每次客户端请求到达时,Web 容器都会调用 Servlet 的 service() 方法来处理请求。HttpServlet 会根据请求类型(GET、POST 等)进一步分发给 doGet(), doPost() 等方法。这个方法可能会被多次调用。
    4. 销毁 (destroy() 方法) : 当 Web 容器关闭时,或者当 Servlet 被移除时,Web 容器会调用 Servlet 的 destroy() 方法,进行资源释放和清理工作(如关闭数据库连接)。该方法只在 Servlet 生命周期中调用一次。

7. Spring Boot 如何实现打包完成部署发布

Spring Boot 应用程序通常打包成一个可执行的 JAR 文件,可以直接通过 java -jar 命令运行,实现简单的部署发布。

  • 打包:

    1. Maven : 在项目的 pom.xml 中,确保引入了 spring-boot-maven-plugin 插件。

      xml 复制代码
      xml
      <build>
          <plugins>
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
              </plugin>
          </plugins>
      </build>

      然后,在项目根目录下执行 Maven 打包命令:

      go 复制代码
      bash
      mvn clean package

      这会在 target 目录下生成一个 your-application-name.jar 文件。

    2. Gradle : 在 build.gradle 文件中,应用 org.springframework.boot 插件。

      bash 复制代码
      gradle
      plugins {
          id 'org.springframework.boot' version '2.x.x.RELEASE'
          id 'java'
      }

      然后,执行 Gradle 打包命令:

      复制代码
      bash
      gradlew bootJar

      这会在 build/libs 目录下生成一个 your-application-name.jar 文件。

  • 部署发布:

    1. 直接运行 JAR 包 :

      将生成的 JAR 文件上传到目标服务器,然后通过命令行运行:

      复制代码
      bash
      java -jar your-application-name.jar

      这会启动内嵌的 Web 服务器并运行应用程序。

    2. 部署到外部 Servlet 容器 (WAR 包) :

      虽然 Spring Boot 默认推荐 JAR 包部署,但也可以将其打包成 WAR 文件并部署到传统的 Servlet 容器(如 Tomcat、WebLogic)中。

      • pom.xml 中将打包类型改为 war,并排除内嵌服务器的依赖。
      • 创建一个 SpringBootServletInitializer 子类。
      • 执行 mvn clean package,生成 WAR 文件。
      • 将 WAR 文件部署到外部 Servlet 容器的 webapps 目录下。
    3. Docker 部署 :

      将 Spring Boot 应用程序打包成 Docker 镜像,然后部署到 Docker 容器中,这是目前非常流行的部署方式。

      • 创建 Dockerfile 文件,定义构建 Docker 镜像的步骤。
      • 使用 docker build 命令构建镜像。
      • 使用 docker run 命令启动容器。
    4. 云平台部署 :

      许多云平台(如 AWS Elastic Beanstalk, Heroku, Azure App Service, Google App Engine)都提供了对 Spring Boot 应用程序的直接支持,可以方便地将应用程序部署到这些平台上。

8. Spring Boot 集成 MyBatis 的场景依赖,配置,使用的注解和过程

  • 场景依赖 :

    pom.xml 中添加 MyBatis Spring Boot Starter 依赖和数据库驱动(以 MySQL 为例):

    xml 复制代码
    xml
    <dependencies>
        <!-- Spring Boot Web Starter (如果需要构建 Web 应用) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <!-- MyBatis Spring Boot Starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version> <!-- 请使用最新版本 -->
        </dependency>
    
        <!-- 数据库驱动 (以 MySQL 为例) -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
    
        <!-- Lombok (可选,用于简化 JavaBean) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    
        <!-- Spring Boot Test Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • 配置 :

    application.propertiesapplication.yml 中配置数据源和 MyBatis:

    ini 复制代码
    properties
    # application.properties
    spring.datasource.url=jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    spring.datasource.username=your_username
    spring.datasource.password=your_password
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
    # MyBatis 配置
    mybatis.mapper-locations=classpath:mapper/*.xml # 指定 Mapper XML 文件位置
    mybatis.type-aliases-package=com.example.demo.entity # 指定实体类包,用于类型别名

    或者使用 application.yml:

    yaml 复制代码
    yaml
    # application.yml
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        username: your_username
        password: your_password
        driver-class-name: com.mysql.cj.jdbc.Driver
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.example.demo.entity
  • 使用的注解和过程:

    1. 启动类添加 @MapperScan :

      在 Spring Boot 应用程序的启动类上添加 @MapperScan 注解,指定 Mapper 接口所在的包,这样 Spring Boot 会自动扫描并注册这些 Mapper 接口。

      kotlin 复制代码
      java
      package com.example.demo;
      
      import org.mybatis.spring.annotation.MapperScan;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      
      @SpringBootApplication
      @MapperScan("com.example.demo.mapper") // 指定 Mapper 接口所在的包
      public class DemoApplication {
          public static void main(String[] args) {
              SpringApplication.run(DemoApplication.class, args);
          }
      }
    2. 创建实体类 (POJO) :

      kotlin 复制代码
      java
      package com.example.demo.entity;
      
      // 使用 Lombok 可以简化 Getter/Setter
      import lombok.Data;
      
      @Data // Lombok 注解,自动生成 Getter/Setter, equals, hashCode, toString
      public class User {
          private Long id;
          private String name;
          private Integer age;
          // Getters and Setters
      }
    3. 创建 Mapper 接口 :

      定义数据访问接口,使用 @Mapper 注解标识,并定义 CRUD 方法。

      java 复制代码
      java
      package com.example.demo.mapper;
      
      import com.example.demo.entity.User;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Select;
      import java.util.List;
      
      @Mapper // 标识这是一个 MyBatis Mapper 接口
      public interface UserMapper {
      
          @Select("SELECT * FROM user WHERE id = #{id}")
          User findById(Long id);
      
          @Select("SELECT * FROM user")
          List<User> findAll();
      
          // 也可以使用 XML 配置 SQL 语句
          void insert(User user);
      
          void update(User user);
      
          void delete(Long id);
      }
    4. 创建 Mapper XML 文件 (可选,但推荐复杂 SQL 使用) :

      src/main/resources/mapper 目录下创建与 Mapper 接口同名的 XML 文件,例如 UserMapper.xml

      xml 复制代码
      xml
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.example.demo.mapper.UserMapper">
          <resultMap id="UserResultMap" type="com.example.demo.entity.User">
              <id column="id" property="id" />
              <result column="name" property="name" />
              <result column="age" property="age" />
          </resultMap>
      
          <insert id="insert" parameterType="com.example.demo.entity.User" useGeneratedKeys="true" keyProperty="id">
              INSERT INTO user (name, age) VALUES (#{name}, #{age})
          </insert>
      
          <update id="update" parameterType="com.example.demo.entity.User">
              UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}
          </update>
      
          <delete id="delete" parameterType="Long">
              DELETE FROM user WHERE id = #{id}
          </delete>
      
          <!-- @Select 注解的 SQL 也可以放在这里 -->
          <!-- <select id="findById" parameterType="Long" resultMap="UserResultMap">
              SELECT * FROM user WHERE id = #{id}
          </select> -->
      </mapper>
    5. 在 Service 或 Controller 中注入并使用 Mapper:

      typescript 复制代码
      java
      package com.example.demo.service;
      
      import com.example.demo.entity.User;
      import com.example.demo.mapper.UserMapper;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import java.util.List;
      
      @Service
      public class UserService {
      
          @Autowired
          private UserMapper userMapper;
      
          public User getUserById(Long id) {
              return userMapper.findById(id);
          }
      
          public List<User> getAllUsers() {
              return userMapper.findAll();
          }
      
          public void addUser(User user) {
              userMapper.insert(user);
          }
      
          public void updateUser(User user) {
              userMapper.update(user);
          }
      
          public void deleteUser(Long id) {
              userMapper.delete(id);
          }
      }

9. Spring Boot 拦截器的语法,创建和注册过程

  • 概念: Spring Boot 拦截器 (Interceptor) 类似于 Servlet 过滤器,但它是在 Spring MVC 框架内部工作,可以拦截对控制器方法的请求,在请求处理之前、之后或视图渲染之后执行一些操作。它更专注于业务逻辑或与 Spring MVC 相关的横切关注点。

  • 语法 : 创建一个拦截器需要实现 org.springframework.web.servlet.HandlerInterceptor 接口。

    java 复制代码
    java
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    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 {
            // 在控制器方法执行之前执行
            // 返回 true 表示继续执行后续的拦截器和控制器方法
            // 返回 false 表示中断请求,不再执行后续操作
            System.out.println("preHandle: Request URI: " + request.getRequestURI());
            // 可以在这里进行权限验证、日志记录等
            // if (request.getSession().getAttribute("user") == null) {
            //     response.sendRedirect("/login");
            //     return false;
            // }
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            // 在控制器方法执行之后,但在视图渲染之前执行
            System.out.println("postHandle: Controller method executed.");
            // 可以在这里对 ModelAndView 进行修改,添加公共数据等
            if (modelAndView != null) {
                modelAndView.addObject("globalMessage", "Hello from Interceptor!");
            }
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            // 在整个请求处理完成之后,即视图渲染之后执行,通常用于资源清理
            System.out.println("afterCompletion: Request completed.");
            if (ex != null) {
                System.err.println("Request completed with exception: " + ex.getMessage());
            }
        }
    }
  • 创建和注册过程:

    1. 创建拦截器类 :

      如上所示,创建一个实现 HandlerInterceptor 接口的类。

    2. 注册拦截器 :

      在 Spring Boot 中,需要创建一个配置类来注册拦截器,该配置类需要实现 org.springframework.web.servlet.config.annotation.WebMvcConfigurer 接口(或者继承 WebMvcConfigurerAdapter,但已被废弃)。

      typescript 复制代码
      java
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
      import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
      
      @Configuration // 标识这是一个配置类
      public class WebConfig implements WebMvcConfigurer {
      
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              // 注册自定义拦截器
              registry.addInterceptor(new MyInterceptor())
                      .addPathPatterns("/**") // 拦截所有路径
                      .excludePathPatterns("/login", "/register"); // 排除登录和注册路径
          }
      }
      • addPathPatterns(): 指定拦截器要拦截的路径模式,支持 Ant 风格的路径匹配。
      • excludePathPatterns(): 指定拦截器要排除的路径模式。

10. 过滤器的概念作用以及编码过滤器的语法和配置和工作过程

  • 概念: 过滤器 (Filter) 是 Java Servlet 规范中的一个组件,它可以在请求到达 Servlet 或 JSP 页面之前以及响应发送回客户端之后,对请求和响应进行预处理或后处理。过滤器是独立于 Servlet 的,可以用于通用的任务,如字符编码、认证、日志记录、缓存等。

  • 作用:

    • 字符编码: 设置请求和响应的字符编码,解决中文乱码问题。
    • 认证与授权: 检查用户是否登录,是否有权限访问某个资源。
    • 日志记录: 记录请求信息、响应时间等。
    • 数据压缩: 对响应数据进行压缩。
    • URL 重写: 修改请求的 URL。
    • 安全性: 防止 XSS 攻击、CSRF 攻击等。
  • 编码过滤器的语法和配置:

    1. 语法 : 创建一个过滤器需要实现 javax.servlet.Filter 接口。

      java 复制代码
      java
      import javax.servlet.Filter;
      import javax.servlet.FilterChain;
      import javax.servlet.FilterConfig;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.annotation.WebFilter;
      import java.io.IOException;
      
      @WebFilter(urlPatterns = "/*") // 使用注解配置过滤器,拦截所有请求
      public class CharacterEncodingFilter implements Filter {
      
          private String encoding = "UTF-8"; // 默认编码
      
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
              // 过滤器初始化时调用
              String configEncoding = filterConfig.getInitParameter("encoding");
              if (configEncoding != null) {
                  this.encoding = configEncoding;
              }
              System.out.println("CharacterEncodingFilter initialized with encoding: " + this.encoding);
          }
      
          @Override
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
              // 核心过滤逻辑
              System.out.println("Before CharacterEncodingFilter: Request URI: " + ((HttpServletRequest) request).getRequestURI());
      
              request.setCharacterEncoding(encoding); // 设置请求编码
              response.setCharacterEncoding(encoding); // 设置响应编码
              response.setContentType("text/html;charset=" + encoding); // 设置响应内容类型和编码
      
              chain.doFilter(request, response); // 放行请求,继续执行下一个过滤器或目标资源
      
              System.out.println("After CharacterEncodingFilter: Response sent.");
          }
      
          @Override
          public void destroy() {
              // 过滤器销毁时调用
              System.out.println("CharacterEncodingFilter destroyed.");
          }
      }
    2. 配置 (在 Spring Boot 中) :

      • 注解配置 (推荐) : 使用 @WebFilter 注解直接在过滤器类上进行配置。需要确保 Spring Boot 应用程序的启动类上添加了 @ServletComponentScan 注解,以便扫描和注册 Servlet 组件(包括过滤器)。

        typescript 复制代码
        java
        package com.example.demo;
        
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.boot.web.servlet.ServletComponentScan; // 导入此注解
        
        @SpringBootApplication
        @ServletComponentScan // 启用 Servlet 组件扫描
        public class DemoApplication {
            public static void main(String[] args) {
                SpringApplication.run(DemoApplication.class, args);
            }
        }
      • FilterRegistrationBean 配置 : 另一种方式是通过 FilterRegistrationBean 在配置类中手动注册过滤器。这种方式更灵活,可以控制过滤器的顺序。

        kotlin 复制代码
        java
        import org.springframework.boot.web.servlet.FilterRegistrationBean;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        
        @Configuration
        public class FilterConfig {
        
            @Bean
            public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilterRegistration() {
                FilterRegistrationBean<CharacterEncodingFilter> registration = new FilterRegistrationBean<>();
                registration.setFilter(new CharacterEncodingFilter());
                registration.addUrlPatterns("/*"); // 拦截所有路径
                registration.setName("characterEncodingFilter");
                registration.setOrder(1); // 设置过滤器的执行顺序,数字越小越先执行
                return registration;
            }
        
            // 可以注册多个过滤器,并设置不同的 order
            // @Bean
            // public FilterRegistrationBean<AnotherFilter> anotherFilterRegistration() {
            //     FilterRegistrationBean<AnotherFilter> registration = new FilterRegistrationBean<>();
            //     registration.setFilter(new AnotherFilter());
            //     registration.addUrlPatterns("/admin/*");
            //     registration.setName("anotherFilter");
            //     registration.setOrder(2);
            //     return registration;
            // }
        }
  • 工作过程:

    1. 当客户端请求到达 Web 服务器时,服务器会首先检查是否有过滤器与请求的 URL 模式匹配。
    2. 如果匹配,请求会首先进入匹配的过滤器链中的第一个过滤器。
    3. 过滤器的 doFilter() 方法被调用。在 doFilter() 方法中,可以对请求进行预处理。
    4. 调用 chain.doFilter(request, response) 将请求传递给过滤器链中的下一个过滤器或最终的目标资源(Servlet/JSP)。
    5. 当目标资源处理完请求并生成响应后,响应会沿着过滤器链逆向返回,经过每个过滤器的 doFilter() 方法的后续部分。
    6. chain.doFilter() 调用之后的部分,可以对响应进行后处理。
    7. 最后,响应发送回客户端。

11. 监听器的概念作用以及对 application 内置对象和 session 内置对象的创建,销毁及属性的变化做监听的语法和配置

  • 概念: 监听器 (Listener) 是 Servlet 规范中的一个组件,用于监听 Web 应用程序中的特定事件,并在事件发生时执行相应的操作。它允许开发者在 Web 应用程序的生命周期中插入自定义逻辑。

  • 作用:

    • 生命周期管理: 监听 Web 应用程序的启动和关闭、HTTP 会话的创建和销毁等。
    • 资源初始化/清理: 在应用程序启动时加载资源,在关闭时释放资源。
    • 会话管理: 统计在线用户数量、记录用户登录/登出事件。
    • 数据共享: 在应用程序或会话级别共享数据。
  • application 内置对象 (ServletContext) 的创建,销毁及属性的变化做监听的语法和配置:

    • 监听 ServletContext 的创建和销毁 :

      实现 javax.servlet.ServletContextListener 接口。

      typescript 复制代码
      java
      import javax.servlet.ServletContextEvent;
      import javax.servlet.ServletContextListener;
      import javax.servlet.annotation.WebListener;
      
      @WebListener // 使用注解配置监听器
      public class MyServletContextListener implements ServletContextListener {
      
          @Override
          public void contextInitialized(ServletContextEvent sce) {
              // Web 应用程序启动时调用
              System.out.println("ServletContext initialized. Application started.");
              // 可以在这里加载全局配置、初始化资源等
              sce.getServletContext().setAttribute("appName", "My Web App");
          }
      
          @Override
          public void contextDestroyed(ServletContextEvent sce) {
              // Web 应用程序关闭时调用
              System.out.println("ServletContext destroyed. Application stopped.");
              // 可以在这里释放资源、关闭连接等
          }
      }
    • 监听 ServletContext 属性的变化 :

      实现 javax.servlet.ServletContextAttributeListener 接口。

      typescript 复制代码
      java
      import javax.servlet.ServletContextAttributeEvent;
      import javax.servlet.ServletContextAttributeListener;
      import javax.servlet.annotation.WebListener;
      
      @WebListener
      public class MyServletContextAttributeListener implements ServletContextAttributeListener {
      
          @Override
          public void attributeAdded(ServletContextAttributeEvent scae) {
              // 当 ServletContext 中添加属性时调用
              System.out.println("ServletContext attribute added: " + scae.getName() + " = " + scae.getValue());
          }
      
          @Override
          public void attributeRemoved(ServletContextAttributeEvent scae) {
              // 当 ServletContext 中移除属性时调用
              System.out.println("ServletContext attribute removed: " + scae.getName() + " = " + scae.getValue());
          }
      
          @Override
          public void attributeReplaced(ServletContextAttributeEvent scae) {
              // 当 ServletContext 中替换属性时调用
              System.out.println("ServletContext attribute replaced: " + scae.getName() + " = " + scae.getOldValue() + " -> " + scae.getValue());
          }
      }
  • session 内置对象 (HttpSession) 的创建,销毁及属性的变化做监听的语法和配置:

    • 监听 HttpSession 的创建和销毁 :

      实现 javax.servlet.http.HttpSessionListener 接口。

      typescript 复制代码
      java
      import javax.servlet.annotation.WebListener;
      import javax.servlet.http.HttpSessionEvent;
      import javax.servlet.http.HttpSessionListener;
      
      @WebListener
      public class MyHttpSessionListener implements HttpSessionListener {
      
          private static int activeSessions = 0;
      
          @Override
          public void sessionCreated(HttpSessionEvent se) {
              // 当 HttpSession 创建时调用
              activeSessions++;
              System.out.println("Session created. Session ID: " + se.getSession().getId() + ", Active sessions: " + activeSessions);
          }
      
          @Override
          public void sessionDestroyed(HttpSessionEvent se) {
              // 当 HttpSession 销毁时调用
              activeSessions--;
              System.out.println("Session destroyed. Session ID: " + se.getSession().getId() + ", Active sessions: " + activeSessions);
          }
      
          public static int getActiveSessions() {
              return activeSessions;
          }
      }
    • 监听 HttpSession 属性的变化 :

      实现 javax.servlet.http.HttpSessionAttributeListener 接口。

      typescript 复制代码
      java
      import javax.servlet.annotation.WebListener;
      import javax.servlet.http.HttpSessionAttributeEvent;
      import javax.servlet.http.HttpSessionAttributeListener;
      
      @WebListener
      public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener {
      
          @Override
          public void attributeAdded(HttpSessionAttributeEvent se) {
              // 当 HttpSession 中添加属性时调用
              System.out.println("Session attribute added: " + se.getName() + " = " + se.getValue() + ", Session ID: " + se.getSession().getId());
          }
      
          @Override
          public void attributeRemoved(HttpSessionAttributeEvent se) {
              // 当 HttpSession 中移除属性时调用
              System.out.println("Session attribute removed: " + se.getName() + " = " + se.getValue() + ", Session ID: " + se.getSession().getId());
          }
      
          @Override
          public void attributeReplaced(HttpSessionAttributeEvent se) {
              // 当 HttpSession 中替换属性时调用
              System.out.println("Session attribute replaced: " + se.getName() + " = " + se.getOldValue() + " -> " + se.getValue() + ", Session ID: " + se.getSession().getId());
          }
      }
  • 配置 :

    在 Spring Boot 中,使用 @WebListener 注解来配置监听器。同样,需要确保在 Spring Boot 应用程序的启动类上添加了 @ServletComponentScan 注解,以便扫描和注册这些监听器。

    typescript 复制代码
    java
    package com.example.demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletComponentScan;
    
    @SpringBootApplication
    @ServletComponentScan // 启用 Servlet 组件扫描
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }

12. MVC 设计模式概念,MVC 设计模式的优点

  • 概念: MVC (Model-View-Controller) 是一种经典的软件架构模式,用于将应用程序的业务逻辑、数据和用户界面分离,以提高应用程序的模块化、可维护性和可扩展性。

    • Model (模型) :

      • 负责应用程序的数据和业务逻辑。
      • 与数据源(如数据库)进行交互。
      • 独立于用户界面,不直接与视图或控制器通信。
      • 当数据发生变化时,会通知视图进行更新。
    • View (视图) :

      • 负责显示数据给用户,是用户界面的呈现。
      • 从模型中获取数据,并以用户友好的方式展示。
      • 不包含业务逻辑,只是单纯地显示数据。
      • 通常是 HTML 页面、JSP、Thymeleaf 模板等。
    • Controller (控制器) :

      • 负责接收用户输入,并根据输入来更新模型和视图。
      • 充当模型和视图之间的协调者。
      • 处理用户的请求,调用模型层的方法来处理业务逻辑,然后选择合适的视图来显示结果。
  • 工作流程:

    1. 用户通过视图与应用程序进行交互(例如,点击按钮、提交表单)。
    2. 视图将用户请求发送给控制器。
    3. 控制器接收请求,并解析用户的意图。
    4. 控制器调用模型层的方法来执行相应的业务逻辑和数据操作。
    5. 模型处理数据,并通知控制器数据已发生变化。
    6. 控制器根据模型的更新,选择合适的视图来显示结果。
    7. 视图从模型中获取更新后的数据,并将其呈现给用户。
  • MVC 设计模式的优点:

    • 职责分离: 将不同的职责(数据、UI、控制逻辑)分离到不同的组件中,使代码更清晰、更易于理解和维护。
    • 提高可维护性: 某个部分的修改不会影响其他部分,降低了维护成本。
    • 提高可扩展性: 容易添加新的功能或改变现有功能的实现,而无需修改其他不相关的部分。
    • 提高可重用性: 模型可以在不同的视图中重用,控制器也可以处理多种视图。
    • 便于团队协作: 不同的团队成员可以专注于不同的组件,并行开发。
    • 提高测试性: 模型和控制器可以独立于视图进行测试。
    • 灵活性: 视图和模型之间的解耦,允许在不影响业务逻辑的情况下更改用户界面。

13. Spring Boot 文件上传的配置及在表单中处理上传的语法和控制层接收上传文件的语法和工作过程

  • Spring Boot 文件上传的配置 :

    Spring Boot 默认支持文件上传,通常只需要在 application.propertiesapplication.yml 中进行一些基本配置。

    ini 复制代码
    properties
    # application.properties
    spring.servlet.multipart.enabled=true             # 启用文件上传功能,默认为 true
    spring.servlet.multipart.max-file-size=10MB       # 单个文件最大大小,默认 1MB
    spring.servlet.multipart.max-request-size=100MB   # 单次请求最大文件大小(所有文件总和),默认 10MB
    spring.servlet.multipart.file-size-threshold=0    # 文件写入磁盘的阈值,默认 0KB,即所有文件都先写入内存
    # spring.servlet.multipart.location=/tmp/uploads  # 临时文件存储路径,默认为系统临时目录

    或者使用 application.yml:

    yaml 复制代码
    yaml
    spring:
      servlet:
        multipart:
          enabled: true
          max-file-size: 10MB
          max-request-size: 100MB
          file-size-threshold: 0
          # location: /tmp/uploads

    注意:在 Spring Boot 2.x 之后,spring.http.multipart 已经被 spring.servlet.multipart 替代。

  • 在表单中处理上传的语法 :

    HTML 表单必须使用 method="POST"enctype="multipart/form-data" 才能支持文件上传。

    xml 复制代码
    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>File Upload</title>
    </head>
    <body>
        <h2>Upload Single File</h2>
        <form action="/upload" method="post" enctype="multipart/form-data">
            <input type="file" name="file"><br><br>
            <input type="submit" value="Upload">
        </form>
    
        <hr>
    
        <h2>Upload Multiple Files</h2>
        <form action="/upload-multiple" method="post" enctype="multipart/form-data">
            <input type="file" name="files" multiple><br><br>
            <input type="submit" value="Upload Multiple">
        </form>
    </body>
    </html>
    • name="file": 这是文件输入字段的名称,在后端控制器中会使用这个名称来接收文件。
    • multiple: 允许选择多个文件。
  • 控制层接收上传文件的语法和工作过程:

    在 Spring Boot 的控制器中,可以使用 org.springframework.web.multipart.MultipartFile 对象来接收上传的文件。

    java 复制代码
    java
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.multipart.MultipartFile;
    import org.springframework.web.servlet.mvc.support.RedirectAttributes;
    
    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.Arrays;
    import java.util.List;
    import java.util.UUID;
    import java.util.stream.Collectors;
    
    @Controller
    public class FileUploadController {
    
        // 定义文件存储目录
        private static final String UPLOAD_FOLDER = "src/main/resources/static/uploads/";
    
        @PostMapping("/upload")
        public String singleFileUpload(@RequestParam("file") MultipartFile file,
                                       RedirectAttributes redirectAttributes) {
    
            if (file.isEmpty()) {
                redirectAttributes.addFlashAttribute("message", "Please select a file to upload.");
                return "redirect:/uploadStatus";
            }
    
            try {
                // 创建上传目录(如果不存在)
                Path uploadPath = Paths.get(UPLOAD_FOLDER);
                if (!Files.exists(uploadPath)) {
                    Files.createDirectories(uploadPath);
                }
    
                // 获取原始文件名
                String originalFilename = file.getOriginalFilename();
                // 确保文件名安全,防止路径遍历攻击
                String filename = originalFilename.substring(originalFilename.lastIndexOf(File.separator) + 1);
    
                // 生成唯一文件名,避免文件名冲突
                String uniqueFilename = UUID.randomUUID().toString() + "_" + filename;
    
                // 文件的完整路径
                Path targetPath = uploadPath.resolve(uniqueFilename);
    
                // 保存文件到目标路径
                Files.copy(file.getInputStream(), targetPath);
    
                redirectAttributes.addFlashAttribute("message",
                        "You successfully uploaded '" + originalFilename + "'");
    
            } catch (IOException e) {
                e.printStackTrace();
                redirectAttributes.addFlashAttribute("message", "Failed to upload file: " + e.getMessage());
            }
    
            return "redirect:/uploadStatus";
        }
    
        @PostMapping("/upload-multiple")
        public String multiFileUpload(@RequestParam("files") MultipartFile[] files,
                                      RedirectAttributes redirectAttributes) {
    
            if (files.length == 0) {
                redirectAttributes.addFlashAttribute("message", "Please select at least one file to upload.");
                return "redirect:/uploadStatus";
            }
    
            List<String> uploadedFileNames = Arrays.stream(files)
                    .map(file -> {
                        try {
                            if (file.isEmpty()) {
                                return "Failed to upload an empty file.";
                            }
                            // 创建上传目录(如果不存在)
                            Path uploadPath = Paths.get(UPLOAD_FOLDER);
                            if (!Files.exists(uploadPath)) {
                                Files.createDirectories(uploadPath);
                            }
    
                            String originalFilename = file.getOriginalFilename();
                            String filename = originalFilename.substring(originalFilename.lastIndexOf(File.separator) + 1);
                            String uniqueFilename = UUID.randomUUID().toString() + "_" + filename;
                            Path targetPath = uploadPath.resolve(uniqueFilename);
                            Files.copy(file.getInputStream(), targetPath);
                            return "Successfully uploaded '" + originalFilename + "'";
                        } catch (IOException e) {
                            return "Failed to upload '" + file.getOriginalFilename() + "': " + e.getMessage();
                        }
                    })
                    .collect(Collectors.toList());
    
            redirectAttributes.addFlashAttribute("message", uploadedFileNames);
    
            return "redirect:/uploadStatus";
        }
    
        // 可以在这里创建一个页面来显示上传状态
        // @GetMapping("/uploadStatus")
        // public String uploadStatus() {
        //     return "uploadStatus"; // 对应 uploadStatus.html 模板
        // }
    }
  • 工作过程:

    1. 客户端提交表单 : 用户在 HTML 页面中选择文件并提交表单。由于 enctype="multipart/form-data",浏览器会将文件内容以 multipart 编码的形式发送到服务器。
    2. Spring MVC 接收请求 : Spring MVC 的 DispatcherServlet 接收到文件上传请求。
    3. MultipartResolver 处理 : DispatcherServlet 会委托 MultipartResolver 来解析请求中的文件数据。Spring Boot 默认使用 StandardServletMultipartResolver,它依赖于 Servlet 3.0+ 规范提供的文件上传 API。
    4. 文件封装 : MultipartResolver 将解析后的文件数据封装成 MultipartFile 对象。
    5. 控制器接收 : MultipartFile 对象作为控制器方法的参数传入。
    6. 文件处理 : 在控制器方法中,可以通过 MultipartFile 对象获取文件的元数据(如文件名、文件大小、内容类型),以及文件的内容(通过 getInputStream()getBytes())。
    7. 文件保存 : 开发者可以调用 MultipartFiletransferTo() 方法将文件保存到服务器的指定位置,或者手动读取文件流并写入到目标文件。
    8. 响应客户端: 控制器处理完文件后,返回一个视图或重定向到其他页面,向用户显示上传结果。

14. Spring Boot 日志的概念,作用,日志架构和日志输出格式和日志配置

  • 概念: 日志 (Logging) 是应用程序开发中一个非常重要的组成部分,用于记录应用程序在运行时的事件、状态、错误信息等。

  • 作用:

    • 问题排查: 当应用程序出现错误时,日志可以提供重要的线索,帮助开发者定位和解决问题。
    • 监控与分析: 收集应用程序的运行数据,进行性能监控、用户行为分析等。
    • 审计与合规性: 记录关键操作,满足审计和法规要求。
    • 了解应用程序行为: 帮助开发者理解应用程序在不同情况下的运行逻辑。
  • 日志架构 :

    Spring Boot 默认使用 Logback 作为日志实现,并通过 SLF4J (Simple Logging Facade for Java) 作为日志门面。

    • SLF4J (日志门面) : 提供了一套通用的 API,开发者只需要使用 SLF4J 的 API 来记录日志,而无需关心底层具体的日志实现。
    • Logback (日志实现) : 实现了 SLF4J 接口,负责实际的日志输出。它具有高性能、灵活的配置、丰富的 Appender 等特点。
    • JUL (Java Util Logging) , Log4j , Log4j2: 其他常见的日志实现,Spring Boot 通过适配器将其桥接到 SLF4J,从而实现统一的日志管理。

    Spring Boot 的日志架构简述 :

    应用程序代码 --> SLF4J API --> SLF4J 桥接器 --> Logback (默认实现) --> 日志输出 (控制台、文件、数据库等)

  • 日志输出格式 :

    Spring Boot 默认的控制台日志输出格式通常包含以下元素:

    csharp 复制代码
    csharp
    2023-12-15 10:30:45.123  INFO 12345 --- [  main] com.example.demo.MyApplication       : Starting MyApplication using Java 17.0.9 with PID 12345 (...)
    • 时间戳: 精确到毫秒。
    • 日志级别 : TRACE, DEBUG, INFO, WARN, ERROR
    • 进程 ID: 应用程序的进程 ID。
    • 线程名: 产生日志的线程的名称。
    • Logger 名称: 通常是产生日志的类的全限定名。
    • 消息: 实际的日志内容。
  • 日志配置:

    1. application.propertiesapplication.yml :

      最常见的配置方式,用于设置日志级别、文件路径等。

      ini 复制代码
      properties
      # application.properties
      
      # 设置根日志级别
      logging.level.root=INFO
      
      # 设置特定包的日志级别
      logging.level.com.example.demo=DEBUG
      logging.level.org.springframework.web=WARN
      
      # 将日志输出到文件
      logging.file.name=my-app.log
      # logging.file.path=/var/log/my-app/ # 指定日志文件目录,文件名为 spring.log
      
      # 日志文件滚动策略 (Logback 默认)
      logging.logback.rollingpolicy.max-file-size=10MB    # 每个日志文件最大大小
      logging.logback.rollingpolicy.max-history=7         # 最多保留 7 天的日志文件
      
      # 自定义日志输出格式 (控制台)
      logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %green([%thread]) %blue(%logger{36}) - %msg%n
      # 自定义日志输出格式 (文件)
      logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n
    2. 自定义日志配置文件 :

      对于更复杂的日志配置需求,可以使用 Logback 的 XML 配置文件,通常命名为 logback-spring.xml (推荐) 或 logback.xml,放置在 src/main/resources 目录下。Spring Boot 会自动加载 logback-spring.xml

      logback-spring.xml 示例:

      xml 复制代码
      xml
      <?xml version="1.0" encoding="UTF-8"?>
      <configuration>
          <!-- 引入 Spring Boot 默认的 base.xml 配置 -->
          <include resource="org/springframework/boot/logging/logback/base.xml"/>
      
          <!-- 定义日志文件路径 -->
          <property name="LOG_FILE" value="./logs/my-app.log"/>
      
          <!-- 控制台 Appender (已在 base.xml 中配置) -->
      
          <!-- 文件 Appender -->
          <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
              <file>${LOG_FILE}</file>
              <encoder>
                  <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
              </encoder>
              <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                  <!-- daily rollover -->
                  <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
                  <!-- keep 30 days' worth of history -->
                  <maxHistory>30</maxHistory>
                  <!-- total size of all archive files, if total size exceeds, it will delete old files -->
                  <totalSizeCap>1GB</totalSizeCap>
              </rollingPolicy>
          </appender>
      
          <!-- 设置根 Logger 的级别和 Appender -->
          <root level="INFO">
              <appender-ref ref="CONSOLE"/> <!-- 引用 Spring Boot 默认的控制台 Appender -->
              <appender-ref ref="FILE"/>
          </root>
      
          <!-- 设置特定 Logger 的级别 -->
          <logger name="com.example.demo" level="DEBUG" additivity="false">
              <appender-ref ref="CONSOLE"/>
              <appender-ref ref="FILE"/>
          </logger>
      
          <logger name="org.springframework.web" level="WARN"/>
      
      </configuration>

15. Thymeleaf 引入,Thymeleaf 使用和 Thymeleaf 的语法规则

  • Thymeleaf 引入 :

    在 Maven 项目的 pom.xml 中添加 Thymeleaf Spring Boot Starter 依赖:

    xml 复制代码
    xml
    <dependencies>
        <!-- Spring Boot Web Starter (如果需要构建 Web 应用) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <!-- Thymeleaf Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    
        <!-- (可选) Thymeleaf Layout Dialect,用于页面布局 -->
        <!-- <dependency>
            <groupId>nz.net.ultraq.thymeleaf</groupId>
            <artifactId>thymeleaf-layout-dialect</artifactId>
        </dependency> -->
    </dependencies>
  • Thymeleaf 使用:

    1. 创建控制器:

      kotlin 复制代码
      java
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      
      @Controller
      public class MyWebController {
      
          @GetMapping("/hello-thymeleaf")
          public String helloThymeleaf(Model model,
                                       @RequestParam(name = "name", required = false, defaultValue = "World") String name) {
              model.addAttribute("message", "Hello, " + name + "!");
              model.addAttribute("userList", Arrays.asList("Alice", "Bob", "Charlie"));
              return "hello"; // 返回模板文件的名称 (hello.html)
          }
      }
    2. 创建 Thymeleaf 模板 :

      src/main/resources/templates 目录下创建 HTML 文件,例如 hello.html

      xml 复制代码
      html
      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>Thymeleaf Example</title>
      </head>
      <body>
          <h1 th:text="${message}">Default Message</h1>
          <p>Welcome to our page!</p>
      
          <h3>User List:</h3>
          <ul>
              <li th:each="user : ${userList}" th:text="${user}"></li>
          </ul>
      
          <a th:href="@{/hello-thymeleaf(name='Thymeleaf User')}">Say Hello to Thymeleaf User</a>
          <img th:src="@{/images/logo.png}" alt="Logo">
      </body>
      </html>
      • xmlns:th="http://www.thymeleaf.org": 声明 Thymeleaf 命名空间。
      • th:text="${message}": 将 model 中名为 message 的属性值显示到 HTML 元素的文本内容中。
      • th:each="user : ${userList}": 遍历 userList 集合,每次迭代将当前元素赋值给 user 变量。
      • th:href="@{/hello-thymeleaf(name='Thymeleaf User')}": Thymeleaf 的 URL 表达式,会自动解析为正确的 URL。
      • th:src="@{/images/logo.png}": 引用静态资源。
  • Thymeleaf 的语法规则:

    Thymeleaf 的语法基于 HTML 属性,它使用 th: 前缀的属性来处理动态内容。

    1. 标准表达式语法:

      • 变量表达式 : ${...}

        • 用于访问上下文中的变量,如 th:text="${message}"
        • 可以访问 JavaBean 属性,如 ${user.name}
        • 可以访问 Map 属性,如 ${myMap['key']}
      • 选择表达式 : *{...}

        • th:object 指定的对象的上下文中访问属性,如 <div th:object="${user}"><span th:text="*{name}"></span></div>
      • 消息表达式 : #{...}

        • 用于国际化和本地化,从消息源(如 messages.properties)中获取文本。
        • th:text="#{welcome.message}"
      • 链接 URL 表达式 : @{...}

        • 用于构建 URL,Thymeleaf 会自动处理上下文路径。
        • th:href="@{/users/details(id=${user.id})}"
      • 片段表达式 : ~{...}

        • 用于引用模板片段,实现布局和组件复用。
        • <div th:insert="~{fragments/header :: header}"></div>
    2. 文本操作:

      • th:text: 设置元素的文本内容。
      • th:utext: 设置元素的非转义文本内容(可以包含 HTML 标签)。
    3. 迭代:

      • th:each="item : ${list}": 遍历集合或数组。
      • th:each="stat, item : ${list}": 遍历集合并获取迭代状态对象 stat,包含 index, count, size, current, first, last, even, odd 等属性。
    4. 条件判断:

      • th:if="${condition}": 如果条件为真,则显示元素。
      • th:unless="${condition}": 如果条件为假,则显示元素。
      • th:switch, th:case: 类似于 Java 的 switch-case 语句。
    5. 属性设置:

      • th:attr="attributeName=${value}": 设置任意属性的值。
      • th:value="${value}": 设置 value 属性。
      • th:href="${url}": 设置 href 属性。
      • th:src="${imageUrl}": 设置 src 属性。
      • th:class="${cssClass}": 设置 class 属性。
    6. 内联 (Inlining) :

      • 在 HTML 文本中直接使用表达式,而不需要 th: 属性。
      • 文本内联:[[${variable}]]
      • 脚本内联:<script th:inline="javascript"> var username = [[${user.name}]]; </script>
    7. 其他:

      • th:remove: 从最终的 HTML 中移除元素。
      • th:block: 一个虚拟元素,不会被渲染到最终的 HTML 中,但可以用于逻辑分组和迭代。

16. Spring Boot 的测试类及执行特点

  • Spring Boot 的测试类 :

    Spring Boot 提供了强大的测试支持,通常使用 JUnit 5 (或 JUnit 4) 和 Spring Test 框架进行测试。

    一个典型的 Spring Boot 测试类会使用以下注解:

    java 复制代码
    java
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.mock.mockito.MockBean;
    import org.springframework.boot.test.web.client.TestRestTemplate;
    import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
    import org.springframework.http.MediaType;
    import org.springframework.test.web.servlet.MockMvc;
    
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.mockito.Mockito.when;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    
    @SpringBootTest // 启动整个 Spring Boot 应用程序上下文进行测试
    // @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // 启动一个真实的 Web 环境,使用随机端口
    // @WebMvcTest(MyController.class) // 仅测试 Web 层,不加载完整的 Spring 容器
    @AutoConfigureMockMvc // 自动配置 MockMvc,用于模拟 HTTP 请求
    public class MySpringBootApplicationTests {
    
        @Autowired
        private MyService myService; // 注入业务层服务进行测试
    
        @MockBean // 模拟一个 Bean,避免实际的依赖
        private MyRepository myRepository;
    
        @Autowired
        private MockMvc mockMvc; // 用于模拟 HTTP 请求
    
        @Autowired(required = false) // 仅在 webEnvironment = RANDOM_PORT 时注入
        private TestRestTemplate restTemplate; // 用于对真实运行的 Web 应用程序进行 HTTP 请求
    
        @Test
        void contextLoads() {
            // 简单的测试,确保 Spring 应用程序上下文能够成功加载
            assertThat(myService).isNotNull();
        }
    
        @Test
        void testMyServiceMethod() {
            when(myRepository.findData()).thenReturn("Mocked Data");
            assertThat(myService.getData()).isEqualTo("Mocked Data");
        }
    
        @Test
        void testControllerWithMockMvc() throws Exception {
            mockMvc.perform(get("/hello-api"))
                    .andExpect(status().isOk())
                    .andExpect(content().string("Hello from API!"));
        }
    
        // 仅在 webEnvironment = RANDOM_PORT 时才有效
        // @Test
        // void testControllerWithRestTemplate() {
        //     String body = restTemplate.getForObject("/hello-api", String.class);
        //     assertThat(body).isEqualTo("Hello from API!");
        // }
    }
  • 执行特点:

    1. 集成测试 (Integration Testing) :

      • @SpringBootTest: 这是最常用的注解,它会启动一个完整的 Spring 应用程序上下文,加载所有的 Bean,包括控制器、服务、数据源等。

      • webEnvironment:

        • SpringBootTest.WebEnvironment.MOCK (默认): 使用 MockMvc 模拟 HTTP 请求,不启动真实的 Web 服务器。
        • SpringBootTest.WebEnvironment.RANDOM_PORT: 启动一个真实的内嵌 Web 服务器,并在随机端口上运行,可以使用 TestRestTemplateWebClient 发送真实 HTTP 请求。
        • SpringBootTest.WebEnvironment.DEFINED_PORT: 启动一个真实的内嵌 Web 服务器,并在定义的端口上运行。
        • SpringBootTest.WebEnvironment.NONE: 不启动 Web 环境。
      • 特点: 能够全面测试应用程序的各个组件之间的集成,但测试启动速度较慢,资源消耗较大。

    2. 切片测试 (Slice Testing) :

      Spring Boot 提供了专门的切片测试注解,只加载应用程序的特定部分上下文,从而提高测试效率。

      • @WebMvcTest : 仅测试 Web 层(控制器、过滤器、拦截器等)。不会加载业务层和数据访问层。通常与 MockMvc 结合使用。
      • @DataJpaTest: 仅测试 JPA 相关的组件(Repository、EntityManager)。会配置一个内嵌数据库,并自动回滚事务。
      • @JdbcTest: 仅测试 JDBC 相关的组件。
      • @JsonTest: 仅测试 JSON 序列化和反序列化。
      • 特点: 启动速度快,资源消耗小,专注于测试应用程序的特定层次,但无法测试整个应用程序的集成。
    3. Mocking (模拟) :

      • @MockBean : 用于模拟 Spring 容器中的 Bean。当测试某个组件时,如果它依赖于其他复杂或外部资源(如数据库、第三方服务),可以使用 @MockBean 来替换这些依赖,从而避免实际的外部调用,使测试更加独立和可控。
      • @SpyBean: 用于监视 Spring 容器中的真实 Bean,可以对其部分方法进行模拟,而其他方法仍执行真实逻辑。
    4. 测试工具:

      • MockMvc: 用于模拟 HTTP 请求,测试 Web 层的行为,无需启动真实的 Web 服务器。
      • TestRestTemplate : 在 @SpringBootTest(webEnvironment = RANDOM_PORT) 模式下,用于发送真实的 HTTP 请求到运行的应用程序。
      • JUnit Assertions (如 assertThat) : 用于断言测试结果。
      • Mockito: 用于创建和配置 mock 对象。
    5. 事务管理 :

      在集成测试中,通常希望每次测试都是独立的,不会影响数据库中的数据。@Transactional 注解可以用于测试方法或测试类上,使得每个测试方法在一个事务中运行,并在测试结束后自动回滚,确保数据的清洁。

    总而言之,Spring Boot 的测试特点是提供了从单元测试到集成测试的全面支持,通过不同的注解和工具,开发者可以根据需要选择合适的测试粒度和方式,以高效地进行应用程序测试。

17. 简述 Spring Boot 执行 MVC 模式的 WEB 项目的工作过程

Spring Boot 执行 MVC 模式的 Web 项目的工作过程,核心是围绕 Spring MVC 的 DispatcherServlet 进行的。以下是简化的工作流程:

  1. 应用程序启动:

    • @SpringBootApplication 注解启动 Spring Boot 应用程序。
    • 内嵌的 Servlet 容器(如 Tomcat)启动。
    • Spring IoC 容器初始化,扫描并注册所有的 Bean,包括 Spring MVC 的核心组件(如 DispatcherServletHandlerMappingHandlerAdapterViewResolver 等)。
  2. 客户端发送 HTTP 请求:

    • 用户的浏览器向 Spring Boot 应用程序发送一个 HTTP 请求(例如 GET /users/1)。
  3. DispatcherServlet 接收请求:

    • Web 服务器(内嵌 Tomcat)接收到请求,并将其转发给 DispatcherServletDispatcherServlet 是 Spring MVC 的核心前端控制器,所有请求都会先经过它。
  4. HandlerMapping 查找处理器:

    • DispatcherServlet 委托 HandlerMapping (处理器映射器) 根据请求的 URL 路径查找能够处理该请求的处理器(通常是 @Controller@RestController 注解的类中的方法)。
    • HandlerMapping 会返回一个 HandlerExecutionChain,其中包含一个处理器 (Handler) 和一系列拦截器 (Interceptor)。
  5. HandlerInterceptor (前置处理) :

    • 如果配置了拦截器,DispatcherServlet 会依次调用拦截器的 preHandle() 方法。
    • 如果任何一个 preHandle() 方法返回 false,请求将被中断,不再继续执行后续的拦截器和处理器方法。
  6. HandlerAdapter 调用处理器方法:

    • DispatcherServlet 委托 HandlerAdapter (处理器适配器) 调用找到的处理器方法。
    • HandlerAdapter 负责将请求参数绑定到处理器方法的参数上(如 @PathVariable@RequestParam@RequestBody 等)。
    • 处理器方法执行业务逻辑,可能调用 Service 层和 Repository 层进行数据处理。
  7. 处理器方法返回 ModelAndView 或其他类型:

    • 如果处理器方法返回 String (视图名称)、ModelAndView 对象,或者直接返回数据(如 @RestController),DispatcherServlet 会接收这个返回值。
  8. HandlerInterceptor (后置处理) :

    • 在处理器方法执行之后,但在视图渲染之前,DispatcherServlet 会依次调用拦截器的 postHandle() 方法。
    • 在这里可以对 ModelAndView 进行修改。
  9. ViewResolver 解析视图:

    • 如果处理器方法返回的是视图名称(例如 return "hello";),DispatcherServlet 委托 ViewResolver (视图解析器) 根据视图名称解析出具体的视图对象(例如 hello.html)。
    • 对于 Thymeleaf,ThymeleafViewResolver 会查找 src/main/resources/templates 目录下的 hello.html 文件。
  10. 视图渲染:

    • ViewResolver 返回视图对象后,视图对象负责将模型数据(由控制器添加到 Model 中的数据)填充到模板中,生成最终的 HTML 响应。
  11. HandlerInterceptor (完成处理) :

    • 在整个请求处理完成之后(视图渲染完毕或发生异常),DispatcherServlet 会依次调用拦截器的 afterCompletion() 方法,用于资源清理等。
  12. 响应返回客户端:

    • 最终生成的 HTML 响应通过 DispatcherServlet 和 Servlet 容器发送回客户端浏览器。

总结来说,Spring Boot MVC 的核心流程是:请求 -> DispatcherServlet -> HandlerMapping -> HandlerInterceptor (preHandle) -> HandlerAdapter -> 处理器方法 -> HandlerInterceptor (postHandle) -> ViewResolver -> 视图渲染 -> HandlerInterceptor (afterCompletion) -> 响应。

相关推荐
嘉伟桑1 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康1 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅2 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
AskHarries2 小时前
文件上传系统
后端
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530143 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908403 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端