Java 自定义注解
什么是自定义注解?
在Java中,注解(Annotation)是一种用于为代码提供元数据的特殊标记。Java提供了一些内置注解(如@Override
、@Deprecated
等),但有时这些内置注解无法满足特定需求。这时,我们可以创建自己的注解,即自定义注解。
自定义注解使我们能够:
- 为代码提供特定的元数据信息
- 通过反射机制在运行时获取这些信息
- 实现各种功能,如依赖注入、配置管理、代码检查等
注解本身并不执行任何操作,它们只是信息载体。要使注解发挥作用,需要通过反射等机制来读取和处理它们。
注解的基础知识
在创建自定义注解前,我们需要了解一些基础知识:
元注解
元注解是用来注解其他注解的注解,主要有以下几种:
-
@Retention:定义注解的保留策略
RetentionPolicy.SOURCE
:只在源码中保留,编译时会被丢弃RetentionPolicy.CLASS
:保留到编译后的class文件中,但JVM运行时不保留RetentionPolicy.RUNTIME
:保留到JVM运行时,可以通过反射获取
-
@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+)
-
@Documented:表示注解应被包含在JavaDoc中
-
@Inherited:表示注解可以被子类继承
注解的基本结构
自定义注解的基本结构如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface 注解名称 {
// 定义注解的元素(类似方法)
String value() default "默认值";
int count() default 0;
// ...
}
创建自定义注解
下面我们将一步步学习如何创建和使用自定义注解。
步骤1:定义注解
创建一个简单的注解,用于标记方法的作者信息:
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:使用注解
现在我们可以在代码中使用这个注解:
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:处理注解
要使注解发挥作用,我们需要通过反射来获取和处理它们:
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
- 以上类型的数组
例如:
@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:自定义验证注解
创建一个用于验证字段不能为空的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {
String message() default "字段不能为空";
}
使用示例:
public class User {
@NotNull(message = "用户名不能为空")
private String username;
@NotNull(message = "邮箱不能为空")
private String email;
// 构造函数、getter和setter略
}
验证处理器:
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;
}
}
测试代码:
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:自定义日志注解
创建一个用于记录方法执行时间的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {
boolean enabled() default true;
}
使用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 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应用中):
@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开始,可以在同一个位置多次使用同一个注解:
// 定义容器注解
@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();
}
使用示例:
public class User {
@Role("admin")
@Role("user")
public void doSomething() {
// 方法实现
}
}
类型注解(Java 8+)
Java 8引入了类型注解,可以应用在几乎任何使用类型的位置:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface NotEmpty {
}
// 使用示例
public List<@NotEmpty String> getNames() {
return new ArrayList<>();
}
注解处理器
如果想在编译时处理注解,可以使用注解处理器(Annotation Processor):
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:创建注解
// 组件注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
// 自动装配注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
步骤2:创建容器类
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:创建示例组件
@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框架
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自定义注解的相关知识:
- 什么是自定义注解以及为什么使用它们
- 如何使用元注解定义注解的特性
- 如何创建和使用自定义注解
- 如何通过反射处理注解
- 常见的实际应用场景
- 高级注解功能(Java 8+)
- 通过实战案例展示了注解的强大功能
自定义注解是一种强大的编程工具,它们使代码更具可读性、更易于维护,并且可以减少重复代码。随着对注解的深入理解和巧妙运用,你将能够构建出更加灵活、可扩展的应用程序。
练习
- 创建一个
@Validate
注解,用于验证方法参数的有效性 - 实现一个
@Cacheable
注解,用于缓存方法的返回结果 - 创建一个
@Permission
注解,用于控制方法的访问权限 - 实现一个
@Retry
注解,在方法执行失败时自动重试
进一步学习资源
- Java官方文档 - 注解
- Spring框架的注解实现源码
- 《Effective Java》第6章:枚举和注解
- 探索开源项目(如Hibernate Validator、Jackson、JUnit)中的注解使用
通过深入学习和实践,你将能够更加熟练地使用自定义注解,并在自己的项目中创造性地应用它们。