在Java中,自定义和使用注解可以分为以下几个步骤:
定义注解:使用@interface关键字定义注解。
注解的元素:定义注解中的属性(可带默认值)。
指定目标和生命周期:通过元注解如@Target和@Retention指定注解的适用范围和生命周期。
使用注解:在类、方法或字段等位置使用自定义注解。
处理注解:通过反射机制读取并处理注解。
以下是一个完整的示例,演示如何自定义和使用注解:
示例代码
import java.lang.annotation.*;
import java.lang.reflect.Method;
// 1. 定义注解
@Retention(RetentionPolicy.RUNTIME) // 指定注解的生命周期为运行时
@Target(ElementType.METHOD) // 指定注解的目标为方法
public @interface MyAnnotation {
String value(); // 注解的一个属性,默认为无值
int count() default 1; // 注解的另一个属性,带有默认值
}
// 2. 使用注解
class MyService {
@MyAnnotation(value = "Hello", count = 3)
public void sayHello() {
System.out.println("Hello, world!");
}
@MyAnnotation(value = "Goodbye")
public void sayGoodbye() {
System.out.println("Goodbye, world!");
}
}
// 3. 处理注解
public class AnnotationExample {
public static void main(String[] args) {
Class<MyService> clazz = MyService.class;
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println("Method: " + method.getName());
System.out.println("Value: " + annotation.value());
System.out.println("Count: " + annotation.count());
// 调用被注解的方法
try {
for (int i = 0; i < annotation.count(); i++) {
method.invoke(clazz.getDeclaredConstructor().newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
输出示例
运行上述程序后,将输出如下内容:
Method: sayHello
Value: Hello
Count: 3
Hello, world!
Hello, world!
Hello, world!
Method: sayGoodbye
Value: Goodbye
Count: 1
Goodbye, world!
说明
@Retention(RetentionPolicy.RUNTIME):使注解在运行时可用,便于通过反射机制访问。
@Target(ElementType.METHOD):限制注解只能用于方法。
注解处理部分:通过Method.isAnnotationPresent()检查方法是否使用了指定注解,并通过Method.getAnnotation()获取注解实例,从而访问其属性。
动态调用:通过反射机制调用被注解的方法。
这种方式可以扩展为复杂场景,例如验证输入参数、生成文档或实现自定义逻辑。
参数验证注解
创建注解,用于约束方法参数的值范围。
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // 在运行时可用
@Target(ElementType.PARAMETER) // 作用于方法参数
public @interface Range {
int min(); // 最小值
int max(); // 最大值
}
定义一个类,其中的方法参数使用@Range注解。
public class Calculator {
public int add(
@Range(min = 0, max = 100) int a,
@Range(min = 0, max = 100) int b
) {
return a + b;
}
public int subtract(
@Range(min = 0, max = 100) int a,
@Range(min = 0, max = 100) int b
) {
return a - b;
}
}
通过反射机制检查注解并验证方法参数。
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class AnnotationProcessor {
public static void process(Object obj, String methodName, Object... args) throws Exception {
Class<?> clazz = obj.getClass();
Method method = null;
// 查找目标方法
for (Method m : clazz.getDeclaredMethods()) {
if (m.getName().equals(methodName) && m.getParameterCount() == args.length) {
method = m;
break;
}
}
if (method == null) {
throw new NoSuchMethodException("Method " + methodName + " not found.");
}
// 验证参数
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
Annotation[] annotations = parameter.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof Range) {
Range range = (Range) annotation;
int value = (int) args[i];
if (value < range.min() || value > range.max()) {
throw new IllegalArgumentException(
String.format("Parameter %d is out of range (%d - %d). Provided: %d",
i + 1, range.min(), range.max(), value));
}
}
}
}
// 调用方法
Object result = method.invoke(obj, args);
System.out.println("Result: " + result);
}
}
通过注解处理器调用并验证方法。
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator();
try {
// 调用有效参数
AnnotationProcessor.process(calculator, "add", 50, 30);
// 调用无效参数
AnnotationProcessor.process(calculator, "subtract", 120, 30);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行程序时,输出如下:
Result: 80
java.lang.IllegalArgumentException: Parameter 1 is out of range (0 - 100). Provided: 120
通过反射解析类和方法注解,生成Markdown文档。
import java.io.FileWriter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class DocumentationGenerator {
public static void generateMarkdown(Class<?> clazz, String outputFile) throws IOException {
StringBuilder markdown = new StringBuilder();
markdown.append("# API Documentation\n\n");
markdown.append("## Class: ").append(clazz.getSimpleName()).append("\n\n");
for (Method method : clazz.getDeclaredMethods()) {
markdown.append("### Method: ").append(method.getName()).append("\n");
markdown.append("- **Return Type**: ").append(method.getReturnType().getSimpleName()).append("\n");
markdown.append("- **Parameters**:\n");
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
markdown.append(" - **").append(parameter.getName()).append("**: ")
.append(parameter.getType().getSimpleName());
// 提取注解信息
for (Annotation annotation : parameter.getAnnotations()) {
if (annotation instanceof Range) {
Range range = (Range) annotation;
markdown.append(" (Range: ").append(range.min())
.append(" - ").append(range.max()).append(")");
}
}
markdown.append("\n");
}
markdown.append("\n");
}
// 写入文件
try (FileWriter writer = new FileWriter(outputFile)) {
writer.write(markdown.toString());
}
}
public static void main(String[] args) {
try {
generateMarkdown(Calculator.class, "API_Documentation.md");
System.out.println("Documentation generated successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
基于正则表达式的自定义验证逻辑
创建一个支持正则验证的注解@PatternCheck:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface PatternCheck {
String pattern(); // 正则表达式
String message() default "Invalid input format."; // 验证失败提示信息
}
定义一个方法,参数需要满足正则验证:
public class UserService {
public void register(
@PatternCheck(pattern = "^[a-zA-Z0-9]{6,12}$", message = "Username must be 6-12 alphanumeric characters.") String username,
@PatternCheck(pattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$", message = "Invalid email format.") String email
) {
System.out.println("User registered successfully: " + username + ", " + email);
}
}
通过反射机制处理注解逻辑,动态验证输入参数是否符合规则。
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class CustomLogicProcessor {
public static void process(Object obj, String methodName, Object... args) throws Exception {
Class<?> clazz = obj.getClass();
Method method = null;
// 查找目标方法
for (Method m : clazz.getDeclaredMethods()) {
if (m.getName().equals(methodName) && m.getParameterCount() == args.length) {
method = m;
break;
}
}
if (method == null) {
throw new NoSuchMethodException("Method " + methodName + " not found.");
}
// 验证参数
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
Annotation[] annotations = parameter.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof PatternCheck) {
PatternCheck patternCheck = (PatternCheck) annotation;
String value = args[i].toString();
if (!value.matches(patternCheck.pattern())) {
throw new IllegalArgumentException(
String.format("Parameter %d validation failed: %s", i + 1, patternCheck.message()));
}
}
}
}
// 调用方法
method.invoke(obj, args);
}
}
通过CustomLogicProcessor调用并验证输入参数。
public class Main {
public static void main(String[] args) {
UserService userService = new UserService();
try {
// 验证通过
CustomLogicProcessor.process(userService, "register", "User123", "[email protected]");
// 验证失败(用户名格式错误)
CustomLogicProcessor.process(userService, "register", "U", "[email protected]");
// 验证失败(邮箱格式错误)
CustomLogicProcessor.process(userService, "register", "User123", "invalid-email");
} catch (Exception e) {
e.printStackTrace();
}
}
}
AOP整合:将注解处理逻辑交给Spring AOP自动拦截,无需显式调用CustomLogicProcessor。
使用@PatternCheck作为输入验证注解。
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface PatternCheck {
String pattern(); // 正则表达式
String message() default "Invalid input format."; // 验证失败提示信息
}
定义业务逻辑类
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void register(String username, String email) {
System.out.println("User registered successfully: " + username + ", " + email);
}
}
切面拦截方法参数,动态处理注解。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@Aspect
@Component
public class ValidationAspect {
@Before("execution(* com.example.service.*.*(..))")
public void validateParameters(JoinPoint joinPoint) throws Exception {
Method method = getMethodFromJoinPoint(joinPoint);
Object[] args = joinPoint.getArgs();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Annotation[] annotations = parameters[i].getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof PatternCheck) {
PatternCheck patternCheck = (PatternCheck) annotation;
String value = args[i].toString();
if (!value.matches(patternCheck.pattern())) {
throw new IllegalArgumentException(
String.format("Parameter %d validation failed: %s", i + 1, patternCheck.message()));
}
}
}
}
}
// 从 JoinPoint 中提取目标方法
private Method getMethodFromJoinPoint(JoinPoint joinPoint) throws NoSuchMethodException {
String methodName = joinPoint.getSignature().getName();
Class<?>[] parameterTypes = ((Method) joinPoint.getSignature()).getParameterTypes();
return joinPoint.getTarget().getClass().getMethod(methodName, parameterTypes);
}
}
在 Spring Boot 中启用 AOP:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy
public class AopValidationApplication {
public static void main(String[] args) {
SpringApplication.run(AopValidationApplication.class, args);
}
}
编写测试代码,调用UserService.register方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class ApplicationRunner implements CommandLineRunner {
@Autowired
private UserService userService;
@Override
public void run(String... args) throws Exception {
try {
// 验证通过
userService.register("User123", "[email protected]");
// 验证失败:用户名格式错误
userService.register("U", "[email protected]");
// 验证失败:邮箱格式错误
userService.register("User123", "invalid-email");
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
示例输出
User registered successfully: User123, [email protected]
Parameter 1 validation failed: Username must be 6-12 alphanumeric characters.
Parameter 2 validation failed: Invalid input format.
优势
分离关注点:将验证逻辑与业务逻辑完全分离,业务代码更加简洁。
全局处理:切面可以应用到所有带注解的方法,无需单独调用验证逻辑。
可扩展性:支持更多自定义注解,如@NotNull、@MinLength、@MaxLength,统一处理。
动态注解:根据方法或参数注解动态实现不同的逻辑。