在 Spring 中,自定义注解可以帮助我们实现自定义的功能,比如切面逻辑、权限控制、数据校验等。自定义注解通常结合 Spring 的 AOP 或其他功能使用,以增强业务逻辑。下面是创建自定义注解的一般步骤,以及使用示例。
一、创建自定义注解
1.1 定义自定义注解
创建一个自定义注解需要使用 @interface
关键字,同时可以添加一些元注解来控制注解的行为:
@Target
:指定注解可以应用的位置(类、方法、字段等)。@Retention
:指定注解的生命周期。@Documented
:将注解包含在 Javadoc 中。@Inherited
:允许子类继承父类的注解。
示例:定义一个自定义注解 @LogExecutionTime
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
的方法,计算并打印执行时间。
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 会自动拦截并记录执行时间。
import org.springframework.stereotype.Service;@Service
public class MyService {@LogExecutionTimepublic void serve() {// 模拟耗时操作try {Thread.sleep(200);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Service executed");}
}
三、实现基于自定义注解的权限控制示例
下面示例通过自定义注解实现简单的权限控制,模拟权限验证功能。
3.1 定义权限控制注解
创建 @RoleRequired
注解,指定需要的角色。
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
注解的方法,实现权限验证的逻辑。
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
注解添加到需要权限控制的方法上。
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
注解,用于校验字段不为空。
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
注解的字段是否为空。
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
注解标记需要校验的字段。
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
注解的对象时,如果字段为空,会触发校验异常。
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
)
自定义注解使代码更加简洁、可读、可复用。根据业务需求,可以灵活实现多种注解应用。