跳到主要内容

Java 自定义注解

什么是自定义注解?

在Java中,注解(Annotation)是一种用于为代码提供元数据的特殊标记。Java提供了一些内置注解(如@Override@Deprecated等),但有时这些内置注解无法满足特定需求。这时,我们可以创建自己的注解,即自定义注解

自定义注解使我们能够:

  • 为代码提供特定的元数据信息
  • 通过反射机制在运行时获取这些信息
  • 实现各种功能,如依赖注入、配置管理、代码检查等
备注

注解本身并不执行任何操作,它们只是信息载体。要使注解发挥作用,需要通过反射等机制来读取和处理它们。

注解的基础知识

在创建自定义注解前,我们需要了解一些基础知识:

元注解

元注解是用来注解其他注解的注解,主要有以下几种:

  1. @Retention:定义注解的保留策略

    • RetentionPolicy.SOURCE:只在源码中保留,编译时会被丢弃
    • RetentionPolicy.CLASS:保留到编译后的class文件中,但JVM运行时不保留
    • RetentionPolicy.RUNTIME:保留到JVM运行时,可以通过反射获取
  2. @Target:定义注解可以应用的目标元素类型

    • ElementType.TYPE:类、接口、枚举
    • ElementType.FIELD:字段
    • ElementType.METHOD:方法
    • ElementType.PARAMETER:方法参数
    • ElementType.CONSTRUCTOR:构造方法
    • ElementType.LOCAL_VARIABLE:局部变量
    • ElementType.ANNOTATION_TYPE:注解类型
    • ElementType.PACKAGE:包
    • ElementType.TYPE_PARAMETER:类型参数(Java 8+)
    • ElementType.TYPE_USE:类型使用(Java 8+)
  3. @Documented:表示注解应被包含在JavaDoc中

  4. @Inherited:表示注解可以被子类继承

注解的基本结构

自定义注解的基本结构如下:

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface 注解名称 {
// 定义注解的元素(类似方法)
String value() default "默认值";
int count() default 0;
// ...
}

创建自定义注解

下面我们将一步步学习如何创建和使用自定义注解。

步骤1:定义注解

创建一个简单的注解,用于标记方法的作者信息:

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Author {
String name() default "Unknown";
String date() default "N/A";
String description() default "";
}

步骤2:使用注解

现在我们可以在代码中使用这个注解:

java
public class Calculator {

@Author(name = "John Doe",
date = "2023-06-15",
description = "一个简单的加法方法")
public int add(int a, int b) {
return a + b;
}

@Author(name = "Jane Smith",
description = "减法方法")
public int subtract(int a, int b) {
return a - b;
}

@Author // 使用默认值
public int multiply(int a, int b) {
return a * b;
}
}

步骤3:处理注解

要使注解发挥作用,我们需要通过反射来获取和处理它们:

java
import java.lang.reflect.Method;

public class AnnotationProcessor {

public static void processAuthorAnnotation(Class<?> clazz) {
// 获取类中的所有方法
Method[] methods = clazz.getDeclaredMethods();

for (Method method : methods) {
// 检查方法是否有Author注解
if (method.isAnnotationPresent(Author.class)) {
// 获取注解实例
Author author = method.getAnnotation(Author.class);

// 输出注解信息
System.out.println("Method: " + method.getName());
System.out.println("Author: " + author.name());
System.out.println("Date: " + author.date());
System.out.println("Description: " + author.description());
System.out.println("------------------------");
}
}
}

public static void main(String[] args) {
processAuthorAnnotation(Calculator.class);
}
}

输出结果

Method: add
Author: John Doe
Date: 2023-06-15
Description: 一个简单的加法方法
------------------------
Method: subtract
Author: Jane Smith
Date: N/A
Description: 减法方法
------------------------
Method: multiply
Author: Unknown
Date: N/A
Description:
------------------------

注解元素的类型限制

注解元素的类型只能是以下几种:

  • 基本数据类型(int, float, boolean等)
  • String
  • Class
  • enum
  • Annotation
  • 以上类型的数组

例如:

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestInfo {
int priority() default 5;
String[] tags();
Class<?> testClass();
Status status() default Status.UNKNOWN;

enum Status {
PASS, FAIL, UNKNOWN
}
}

实际应用场景

场景1:自定义验证注解

创建一个用于验证字段不能为空的注解:

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {
String message() default "字段不能为空";
}

使用示例:

java
public class User {
@NotNull(message = "用户名不能为空")
private String username;

@NotNull(message = "邮箱不能为空")
private String email;

// 构造函数、getter和setter略
}

验证处理器:

java
public class Validator {
public static <T> List<String> validate(T object) throws IllegalAccessException {
List<String> errorMessages = new ArrayList<>();
Class<?> clazz = object.getClass();
Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {
field.setAccessible(true);
if (field.isAnnotationPresent(NotNull.class)) {
Object value = field.get(object);
if (value == null || "".equals(value)) {
NotNull annotation = field.getAnnotation(NotNull.class);
errorMessages.add(annotation.message());
}
}
}

return errorMessages;
}
}

测试代码:

java
User user = new User();
user.setUsername("john");
// 未设置email

try {
List<String> errors = Validator.validate(user);
if (!errors.isEmpty()) {
for (String error : errors) {
System.out.println("Error: " + error);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}

输出结果

Error: 邮箱不能为空

场景2:自定义日志注解

创建一个用于记录方法执行时间的注解:

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {
boolean enabled() default true;
}

使用AOP(面向切面编程)处理注解:

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 LogExecutionTimeAspect {

@Around("@annotation(logExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
if (!logExecutionTime.enabled()) {
return joinPoint.proceed();
}

long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();

System.out.println(joinPoint.getSignature() + " executed in " + (endTime - startTime) + "ms");

return result;
}
}

使用示例(在Spring应用中):

java
@Service
public class UserService {

@LogExecutionTime
public User findById(Long id) {
// 模拟数据库操作延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new User(id, "user" + id);
}

@LogExecutionTime(enabled = false)
public void saveUser(User user) {
// 保存用户逻辑
}
}

高级注解功能

重复注解(Java 8+)

从Java 8开始,可以在同一个位置多次使用同一个注解:

java
// 定义容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Roles {
Role[] value();
}

// 定义可重复注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Roles.class) // 指定容器注解
public @interface Role {
String value();
}

使用示例:

java
public class User {
@Role("admin")
@Role("user")
public void doSomething() {
// 方法实现
}
}

类型注解(Java 8+)

Java 8引入了类型注解,可以应用在几乎任何使用类型的位置:

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface NotEmpty {
}

// 使用示例
public List<@NotEmpty String> getNames() {
return new ArrayList<>();
}

注解处理器

如果想在编译时处理注解,可以使用注解处理器(Annotation Processor):

java
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("com.example.NotNull")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class NotNullProcessor extends AbstractProcessor {

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);

for (Element element : annotatedElements) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.NOTE,
"Found @NotNull at " + element.toString()
);
}
}
return true;
}
}

实战案例:构建一个简单的依赖注入框架

让我们创建一个简单的依赖注入框架,类似于Spring的基本功能:

步骤1:创建注解

java
// 组件注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}

// 自动装配注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}

步骤2:创建容器类

java
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class DIContainer {
// 存储组件的容器
private Map<String, Object> components = new HashMap<>();

// 扫描并注册组件
public void scanComponents(String packageName) throws Exception {
// 获取包下所有类(实际项目中可使用反射库如Reflections)
Set<Class<?>> classes = getClassesInPackage(packageName);

// 第一步:实例化所有带有@Component的类
for (Class<?> clazz : classes) {
if (clazz.isAnnotationPresent(Component.class)) {
Component component = clazz.getAnnotation(Component.class);
String beanName = component.value();
if (beanName.isEmpty()) {
beanName = getDefaultBeanName(clazz);
}

Object instance = clazz.getDeclaredConstructor().newInstance();
components.put(beanName, instance);
}
}

// 第二步:注入依赖
for (Object component : components.values()) {
injectDependencies(component);
}
}

// 注入依赖
private void injectDependencies(Object component) throws IllegalAccessException {
Class<?> clazz = component.getClass();
Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) {
String beanName = getDefaultBeanName(field.getType());
Object dependency = components.get(beanName);

if (dependency != null) {
field.setAccessible(true);
field.set(component, dependency);
} else {
throw new IllegalStateException("No bean found for " + field.getType());
}
}
}
}

// 获取默认的bean名称(类名首字母小写)
private String getDefaultBeanName(Class<?> clazz) {
String className = clazz.getSimpleName();
return Character.toLowerCase(className.charAt(0)) + className.substring(1);
}

// 获取组件
public <T> T getComponent(String name) {
return (T) components.get(name);
}

public <T> T getComponent(Class<T> clazz) {
String beanName = getDefaultBeanName(clazz);
return (T) components.get(beanName);
}

// 获取包下所有类(实际实现需要使用类加载器或第三方库)
private Set<Class<?>> getClassesInPackage(String packageName) {
// 实际项目中可以使用如下库:
// return new Reflections(packageName).getTypesAnnotatedWith(Component.class);

// 这里为简化,仅返回示例类
return Set.of(UserService.class, UserRepository.class);
}
}

步骤3:创建示例组件

java
@Component
public class UserRepository {
public User findById(Long id) {
System.out.println("Finding user by id: " + id);
return new User(id, "User " + id);
}
}

@Component
public class UserService {
@Autowired
private UserRepository userRepository;

public User getUserById(Long id) {
return userRepository.findById(id);
}
}

步骤4:测试我们的DI框架

java
public class DITest {
public static void main(String[] args) throws Exception {
DIContainer container = new DIContainer();
container.scanComponents("com.example");

UserService userService = container.getComponent(UserService.class);
User user = userService.getUserById(1L);

System.out.println("Found user: " + user.getName());
}
}

输出结果

Finding user by id: 1
Found user: User 1
提示

以上实现是一个非常简化的依赖注入框架,真实的框架(如Spring)要复杂得多,包括循环依赖解决、生命周期管理、作用域控制等高级功能。

总结

在本文中,我们学习了Java自定义注解的相关知识:

  1. 什么是自定义注解以及为什么使用它们
  2. 如何使用元注解定义注解的特性
  3. 如何创建和使用自定义注解
  4. 如何通过反射处理注解
  5. 常见的实际应用场景
  6. 高级注解功能(Java 8+)
  7. 通过实战案例展示了注解的强大功能

自定义注解是一种强大的编程工具,它们使代码更具可读性、更易于维护,并且可以减少重复代码。随着对注解的深入理解和巧妙运用,你将能够构建出更加灵活、可扩展的应用程序。

练习

  1. 创建一个@Validate注解,用于验证方法参数的有效性
  2. 实现一个@Cacheable注解,用于缓存方法的返回结果
  3. 创建一个@Permission注解,用于控制方法的访问权限
  4. 实现一个@Retry注解,在方法执行失败时自动重试

进一步学习资源

  • Java官方文档 - 注解
  • Spring框架的注解实现源码
  • 《Effective Java》第6章:枚举和注解
  • 探索开源项目(如Hibernate Validator、Jackson、JUnit)中的注解使用

通过深入学习和实践,你将能够更加熟练地使用自定义注解,并在自己的项目中创造性地应用它们。