在 Spring 中,自定义注解可以帮助我们实现自定义的功能,比如切面逻辑、权限控制、数据校验等。自定义注解通常结合 Spring 的 AOP 或其他功能使用,以增强业务逻辑。下面是创建自定义注解的一般步骤,以及使用示例。
一、创建自定义注解
1.1 定义自定义注解
创建一个自定义注解需要使用 @interface
关键字,同时可以添加一些元注解来控制注解的行为:
@Target
:指定注解可以应用的位置(类、方法、字段等)。@Retention
:指定注解的生命周期。@Documented
:将注解包含在 Javadoc 中。@Inherited
:允许子类继承父类的注解。
示例:定义一个自定义注解 @LogExecutionTime
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) // 只能作用于方法
@Retention(RetentionPolicy.RUNTIME) // 在运行时可见
public @interface LogExecutionTime {
}
二、使用 AOP 处理自定义注解
可以通过 AOP 来处理带有 @LogExecutionTime
注解的方法,记录方法执行时间。
2.1 创建切面类
在切面类中,通过 @Around
注解拦截标注了 @LogExecutionTime
的方法,计算并打印执行时间。
java
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 LoggingAspect {
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
// 执行目标方法
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
}
2.2 在方法上使用注解
可以将 @LogExecutionTime
注解添加到任何方法上,Spring AOP 会自动拦截并记录执行时间。
java
import org.springframework.stereotype.Service;
@Service
public class MyService {
@LogExecutionTime
public void serve() {
// 模拟耗时操作
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Service executed");
}
}
三、实现基于自定义注解的权限控制示例
下面示例通过自定义注解实现简单的权限控制,模拟权限验证功能。
3.1 定义权限控制注解
创建 @RoleRequired
注解,指定需要的角色。
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RoleRequired {
String value(); // 指定需要的角色
}
3.2 创建权限控制的切面类
在切面类中,通过拦截 @RoleRequired
注解的方法,实现权限验证的逻辑。
java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.JoinPoint;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class RoleAspect {
// 假设当前用户角色(在实际中,这个信息可能来自上下文或会话)
private static final String currentUserRole = "USER";
@Before("@annotation(roleRequired)")
public void checkRole(JoinPoint joinPoint, RoleRequired roleRequired) {
String requiredRole = roleRequired.value();
// 检查当前用户的角色
if (!currentUserRole.equals(requiredRole)) {
throw new SecurityException("Access Denied: insufficient permissions");
}
System.out.println("Access Granted: " + joinPoint.getSignature());
}
}
3.3 使用权限控制注解
将 @RoleRequired
注解添加到需要权限控制的方法上。
java
import org.springframework.stereotype.Service;
@Service
public class AdminService {
@RoleRequired("ADMIN")
public void performAdminTask() {
System.out.println("Admin task performed");
}
}
在运行时,如果当前用户的角色不匹配 @RoleRequired
指定的角色,将抛出异常 Access Denied: insufficient permissions
。
四、使用自定义注解进行参数校验
自定义注解还可以用于参数校验,结合 Spring 的 @Validated
注解,利用 AOP 实现校验逻辑。
4.1 定义注解 @NotEmpty
创建一个 @NotEmpty
注解,用于校验字段不为空。
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotEmpty {
String message() default "Field cannot be empty";
}
4.2 创建校验器
编写 AOP 切面类,检测带有 @NotEmpty
注解的字段是否为空。
java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.JoinPoint;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
@Aspect
@Component
public class ValidationAspect {
@Before("execution(* com.example..*.*(..))")
public void validateNotEmptyFields(JoinPoint joinPoint) throws IllegalAccessException {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg != null) {
Field[] fields = arg.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(NotEmpty.class)) {
field.setAccessible(true);
Object value = field.get(arg);
if (value == null || (value instanceof String && ((String) value).trim().isEmpty())) {
NotEmpty notEmpty = field.getAnnotation(NotEmpty.class);
throw new IllegalArgumentException(notEmpty.message());
}
}
}
}
}
}
}
4.3 使用校验注解
在实体类中使用 @NotEmpty
注解标记需要校验的字段。
java
public class User {
@NotEmpty(message = "Username must not be empty")
private String username;
@NotEmpty(message = "Password must not be empty")
private String password;
// Constructors, getters, and setters
}
4.4 在服务中进行校验
调用带有 @NotEmpty
注解的对象时,如果字段为空,会触发校验异常。
java
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void registerUser(User user) {
System.out.println("User registered: " + user.getUsername());
}
}
总结
自定义注解结合 AOP 可以极大地简化代码,并添加业务逻辑。以上示例展示了几种常见的自定义注解应用场景:
- 方法执行时间记录 (
@LogExecutionTime
) - 权限控制 (
@RoleRequired
) - 字段校验 (
@NotEmpty
)
自定义注解使代码更加简洁、可读、可复用。根据业务需求,可以灵活实现多种注解应用。