@RestControllerAdvice注解

1. 基本概念

@RestControllerAdvice 相当于 @ControllerAdvice@ResponseBody 的组合

  • @ControllerAdvice@ControllerAdvice 类可以定义多个 @ExceptionHandler 方法,用于捕获并处理控制器中的异常。

  • @ResponseBody:用于将方法返回值直接写入 HTTP 响应体中,通常返回 JSON 格式的数据。

2. 使用示例

下面是一个示例,展示如何使用 @RestControllerAdvice 来全局处理异常:

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理特定的自定义异常
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ErrorResponse> handleCustomException(CustomException ex) {
        ErrorResponse errorResponse = new ErrorResponse("CUSTOM_ERROR", ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    // 处理其他所有异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex) {
        ErrorResponse errorResponse = new ErrorResponse("INTERNAL_SERVER_ERROR", "An unexpected error occurred");
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

// 错误响应对象
class ErrorResponse {
    private String errorCode;
    private String errorMessage;

    public ErrorResponse(String errorCode, String errorMessage) {
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    // getters and setters
}

3. 详细解释

  • 定义全局异常处理器

    • @RestControllerAdvice 注解标记的类会全局处理所有控制器的异常。

    • @ExceptionHandler 注解标记的方法会捕获特定类型的异常。

  • 处理特定异常

    • 在上面的示例中,handleCustomException 方法处理 CustomException,返回 HTTP 状态码 400(Bad Request)。

    • ErrorResponse 类用于构造 JSON 错误响应对象,包含错误代码和错误信息。

  • 处理所有其他异常

    • handleException 方法处理所有其他未捕获的异常,返回 HTTP 状态码 500(Internal Server Error)。

4. 自定义响应格式

为了确保 API 响应的一致性,可以定义一个标准的错误响应格式,如 ErrorResponse 类。在实际应用中,可以根据需求添加更多字段,如时间戳、错误详细信息、路径等。

@Around

@Around 注解是 AspectJ 提供的一种切面编程(AOP)注解,它在 Spring AOP 中也被广泛使用。@Around 注解可以用于定义一个切面方法,该方法会在目标方法(也就是被切入的方法)执行之前和之后执行。

基本概念

  • 切面:切面是包含横切关注点逻辑的模块。例如,日志记录、安全性、事务管理等。

  • 切入点:切入点是定义在哪些方法或位置应用切面的表达式。

  • 通知:通知是指在切入点处执行的代码。@Around 是一种通知类型,它在方法执行前后都可以运行。

  • 目标方法:目标方法是实际业务逻辑所在的方法,即被切面所切入的方法。

使用示例

下面是一个使用 @Around 注解的示例,展示如何在方法执行前后记录日志:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("Method {} is called with args: {}", joinPoint.getSignature().toShortString(), joinPoint.getArgs());

        Object result;
        try {
            result = joinPoint.proceed(); // 执行目标方法
        } catch (Throwable t) {
            logger.error("Exception in method {}: {}", joinPoint.getSignature().toShortString(), t.getMessage());
            throw t;
        }

        logger.info("Method {} returns: {}", joinPoint.getSignature().toShortString(), result);
        return result;
    }
}

详细解释

  • 定义切面

    • @Aspect 注解表明这是一个切面类。

    • @Component 注解将切面类声明为 Spring 组件,以便 Spring 能够管理它。

  • 定义切入点表达式

    • @Around("execution(* com.example.service.*.*(..))") 定义了一个切入点表达式,表示 com.example.service 包中的所有类的所有方法。

  • ProceedingJoinPoint** 参数**:

    • ProceedingJoinPoint 是 JoinPoint 的子接口,表示目标方法的执行。它提供了 proceed() 方法来执行目标方法。

  • 日志记录

    • 在执行目标方法之前,记录方法名称和参数。

    • 使用 joinPoint.proceed() 执行目标方法。

    • 在方法执行之后,记录方法的返回值。

    • 如果目标方法抛出异常,捕获并记录异常信息,然后重新抛出异常。

@Around 注解的应用场景

  • 日志记录:记录方法的调用、参数、返回值和异常。

  • 性能监控:计算方法执行时间,记录慢速方法。

  • 安全检查:在方法执行前后进行安全性检查。

  • 事务管理:在方法执行前开启事务,执行后提交事务,遇到异常时回滚事务。

其他通知类型

除了 @Around,Spring AOP 还提供了其他几种通知类型:

  • @Before:在目标方法执行之前执行通知。

  • @After:在目标方法执行之后执行通知(无论方法是否抛出异常)。

  • @AfterReturning:在目标方法成功返回之后执行通知。

  • @AfterThrowing:在目标方法抛出异常之后执行通知。

总结

@Around 注解是实现切面编程的强大工具,可以在目标方法的执行前后插入自定义逻辑。在 Spring AOP 中,通过使用 @Around 注解,可以实现日志记录、性能监控、安全检查等功能,提升应用程序的可维护性和可扩展性。通过定义切入点表达式,可以精确控制切面的应用范围,从而实现灵活的横切关注点管理。

如何自定义一个注解

定义自定义注解是 Java 编程中的一项基础技能,可以用来标记类、方法、字段等,通常与切面编程(AOP)、文档生成、框架扩展等场景结合使用。下面是一个如何自定义注解的步骤和示例。

关键知识

  • 切面(@Aspect: 是一个模块,它将多个横切关注点组合在一起。

  • 切入点(@Pointcut: 是一个表达式,用于指定哪些方法应该被切面所拦截。

  • 通知(Advice): 是切面中执行的代码,它定义了在切入点处执行的动作。常见的通知类型有:

    • 前置通知(@Before

    • 后置通知(@After

    • 环绕通知(@Around

自定义注解的步骤

  1. 定义注解接口

  1. 使用 @interface 关键字定义注解。可以设置一些注解的元数据,比如 @Retention@Target@Documented

  1. 使用注解

  1. 在需要的位置应用自定义注解。

  1. 处理注解

  1. 使用反射或 AOP 来处理自定义注解。

示例

假设我们要定义一个简单的注解 @AuthCheck,并在方法执行前后打印日志。下面是具体的步骤:

1. 定义注解接口

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)  // 注解在运行时可见
@Target(ElementType.METHOD)          // 注解只能应用于方法
public @interface AuthCheck {
    String value() default "";        // 可选属性,用于附加信息
}
  • @Retention(RetentionPolicy.RUNTIME):注解的生命周期是运行时,意味着它可以通过反射在运行时获取。

  • @Target(ElementType.METHOD):注解可以应用于方法。

2. 使用注解

在目标方法上应用自定义注解 @AuthCheck

public class MyService {

    @AuthCheck("admin")  // 使用自定义注解
    public void secureMethod() {
        System.out.println("Secure method executed.");
    }
}

3. 处理注解

通常,处理注解的逻辑会放在切面类中(如果使用 AOP)或通过反射直接处理。

3.1 使用 Spring AOP 处理注解:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AuthCheckAspect {

    @Around("@annotation(authCheck)")
    public Object checkAuth(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
        // 获取注解的属性值
        String value = authCheck.value();
        // 检查权限
        System.out.println("Authorization check for: " + value);

        // 执行目标方法
        Object result = joinPoint.proceed();

        // 方法执行后处理
        System.out.println("Authorization check complete for: " + value);

        return result;
    }
}
  • @Aspect 注解:表明这是一个切面类

  • @Component 注解:是 Spring 的“标记注解”,告诉 Spring:“这个类是一个 Bean,请在启动时自动创建它的实例,并放入 IoC 容器中管理。在 Spring Boot 应用中,所有被 @Component(或其衍生注解如 @Service, @Repository)标注的类,都会被 自动扫描 → 实例化 → 注入依赖 → 放入容器

  • @Around("annotation(Authcheck)")

    • 定义一个切入点表达式,表示所有被 @Authcheck 注解标记的方法。

    • annotation(Authcheck) 指定了切入点表达式基于方法注解。

  • checkAuth 方法

    • 接受一个 ProceedingJoinPoint 参数,表示被切入的方法。

    • 在方法执行前打印权限检查信息。

    • 调用 joinPoint.proceed() 执行目标方法。

    • 在方法执行后打印完成信息。

    • 返回目标方法的执行结果。

@Pointcut

也可以使用@Pointcut定义切入点,

  1. 切入点可以是我们新定义的注解:

//表示所有被 @ChangeLog 注解标记的方法都会被匹配到。
@Pointcut("@annotation(com.xhorse.common.annotation.ChangeLog)
  1. 也可以是某个包下的所有方法:

// 定义一个切入点,匹配 com.example.service 包中所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
private void logPointCut() {
    //这个切入点被命名为 logPointCut()
}

// 使用 @Before 注解来在切入点之前执行逻辑
@Before("logPointCut()")
public void logBeforeMethod() {
    System.out.println("方法调用之前的日志记录");
}

Lombok注解

1.@Builder

Lombok 的 @Builder 注解可以帮助快速创建对象实例。它采用建造者模式(Builder Pattern)

基本用法

@Builder 注解通常用于类或静态内部类。它会自动生成一个内部静态类 Builder,提供链式的 setter 方法来设置属性,并生成一个 build() 方法来创建对象实例。

示例

import lombok.Builder;

@Builder
public class Person {
    private String name;
    private int age;
    private String address;
}

在上面的示例中,Lombok 会自动生成一个 PersonBuilder 类,该类包含以下方法:

  • name(String name)

  • age(int age)

  • address(String address)

  • build()

使用这些方法可以轻松构建一个 Person 对象:

public class Main {
    public static void main(String[] args) {
        Person person = Person.builder()
                              .name("John")
                              .age(30)
                              .address("123 Main St")
                              .build();
        System.out.println(person);
    }
}

生成的代码

Lombok 自动生成的 Builder 类代码如下:

public class Person {
    private String name;
    private int age;
    private String address;

    // Builder 类
    public static class PersonBuilder {
        private String name;
        private int age;
        private String address;

        PersonBuilder() {}

        public PersonBuilder name(String name) {
            this.name = name;
            return this;
        }

        public PersonBuilder age(int age) {
            this.age = age;
            return this;
        }

        public PersonBuilder address(String address) {
            this.address = address;
            return this;
        }

        public Person build() {
            return new Person(name, age, address);
        }

        public String toString() {
            return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ", address=" + this.address + ")";
        }
    }

    public static PersonBuilder builder() {
        return new PersonBuilder();
    }
}

自定义构造方法

可以结合 @Builder 和自定义的构造方法来实现复杂的对象初始化逻辑。例如:

import lombok.Builder;

@Builder
public class Person {
    private String name;
    private int age;
    private String address;

    public Person(String name, int age, String address) {
        // 自定义构造方法逻辑
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

使用 @Builder.Default

当字段有默认值时,可以使用 @Builder.Default 注解来确保这些默认值在建造者模式中得到应用。

示例

import lombok.Builder;
import lombok.Builder.Default;

@Builder
public class Person {
    private String name;
    private int age;

    @Builder.Default
    private String address = "Unknown";
}

在这个例子中,如果未显式设置 address 字段,它将默认设置为 "Unknown"

复杂示例

可以使用 @Singular 注解来处理集合字段,自动处理集合类型的字段,并提供方便的方法来添加单个元素。

示例

import lombok.Builder;
import lombok.Singular;

import java.util.List;

@Builder
public class Person {
    private String name;
    private int age;
    @Singular
    private List<String> hobbies;
}

public class Main {
    public static void main(String[] args) {
        Person person = Person.builder()
                              .name("John")
                              .age(30)
                              .hobby("Reading")
                              .hobby("Traveling")
                              .build();
        System.out.println(person);
    }
}

总结

Lombok 的 @Builder 注解简化了对象创建过程,提供了更易读、易维护的代码。它支持多种自定义选项,包括默认值、集合字段和自定义构造方法,使得它在实际开发中非常实用。

@PathVariable

用法举例:

@RequestMapping(value="/addUser4/{username}/{password}")
  public String addUser4(@PathVariable String username, @PathVariable String password) {
    System.out.println("username is:"+username);
    System.out.println("password is:"+password);
    return "success";
}

在postman里测试:需要把参数的值写进url路径里。

@RequestParam

注意事项:

  • @RequestParam 可以有多个,可以用在get,也可用在post请求。

  • 在postman里使用“Params”部分填写键值对参数

  • 可以用来传递封装对象类型的参数,在postman里面依旧使用“Params”部分填写键值对参数

@RequestParam有三个配置参数:

  • required 表示是否必须,默认为 true,必须。

  • defaultValue 可设置请求参数的默认值。

  • value 为接收url的参数名(相当于key值)。

@RequestBody 传参 以及 测试接口

注意事项:

  • 一个 HTTP 请求只能有一个请求体,所以只能有一个 @RequestBody 参数。如果需要同时接收多个对象,应该将它们封装在一个单独的对象中。

  • @RequestBody 只能用在post请求里面!

postman中传递参数:

如果你使用封装类来接收参数,Postman 中的 JSON 数据结构需要与该封装类相匹配。以下是一些具体的示例来说明如何正确设置请求体。

情况一:直接传递对象

如果你的控制器方法接收一个简单的对象(例如 DormitoryDTO),那么 Postman 中的 JSON 数据应该直接对应于该对象的结构:

控制器方法

@PostMapping("/api/dormitory")
public ResponseEntity<String> addDormitory(@RequestBody DormitoryDTO dormitoryDTO) {
    String dormitoryName = dormitoryDTO.getName();
    Long dormitoryId = dormitoryDTO.getId();
    return ResponseEntity.ok("Dormitory added: " + dormitoryName + ", ID: " + dormitoryId);
}

Postman 中的 JSON 数据

{
    "id": 1,
    "name": "john"
}

示例二:封装类

如果你的控制器方法接收一个封装类(例如 RequestParams),该类包含其他对象(例如 DormitoryDTO),那么 Postman 中的 JSON 数据需要匹配封装类的结构:

封装类

public class RequestParams {
    private DormitoryDTO queryDormitory;
    // Getters and Setters
}

public class DormitoryDTO {
    private Long id;
    private String name;
    // Getters and Setters
}

控制器方法

@PostMapping("/api/dormitories")
public ResponseEntity<String> addDormitory(@RequestBody RequestParams requestParams) {
    DormitoryDTO dormitoryDTO = requestParams.getQueryDormitory();
    String dormitoryName = dormitoryDTO.getName();
    Long dormitoryId = dormitoryDTO.getId();
    return ResponseEntity.ok("Dormitory added: " + dormitoryName + ", ID: " + dormitoryId);
}

Postman 中的 JSON 数据

{
    "queryDormitory": {
        "id": 1,
        "name": "john"
    }
}

postman传空、null值:

  1. "name": ""

    1. 空字符串:这是一个有效的字符串,但它的内容为空。

    2. 含义:表示该字段有值,但这个值是空字符串。通常用于表示用户输入了一个空白字符串或者字段明确设置为空字符串。

    3. 应用场景:适用于需要标识字段存在但内容为空的情况。

  2. "name": null

    1. 空值(null):这是一个特殊值,表示该字段没有值。

    2. 含义:表示该字段没有被赋值或者明确表示该字段的值为 null。

    3. 应用场景:适用于需要标识字段未定义、未赋值或明确表示为 null 的情况。