Java注解案例

wen java案例 1

Java注解从入门到实战:5个案例教你玩转自定义注解与反射


目录导读

  1. 注解的本质:不仅仅是@Override
  2. 自定义@Log注解实现方法日志埋点
  3. @NotNull参数校验——告别冗长的if判断
  4. 基于注解的依赖注入(简易版Spring IOC)
  5. @Scheduled定时任务注解的底层实现原理
  6. 注解+动态代理实现权限控制
  7. 常见问题问答(FAQ)
  8. 注解的适用场景与性能陷阱

注解的本质:不仅仅是@Override

很多开发者使用@Override@Deprecated多年,却从未尝试自己定义一个注解,注解在Java中是一种“元数据”形式,它本身不包含业务逻辑,而是通过反射编译期处理(如APT)来驱动逻辑,举个例子:@Override只是告诉编译器“请帮我检查方法签名”,而@Transactional则通过Spring的AOP在运行时解析。

Java注解案例

核心认知:注解 = 标记 + 解释器,没有解释器的注解,如同贴了一张没有翻译的外文标签。


案例一:自定义@Log注解实现方法日志埋点

场景:假设你想在业务层的每个方法执行前打印“调用开始”,执行后打印“调用结束”。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}

配套拦截器(基于AOP)

@Aspect
@Component
public class LogAspect {
    @Around("@annotation(log)")
    public Object around(ProceedingJoinPoint pjp, Log log) throws Throwable {
        System.out.println("开始执行: " + log.value());
        Object result = pjp.proceed();
        System.out.println("结束执行: " + log.value());
        return result;
    }
}

使用

@Log("用户服务-查询")
public User getUserById(Long id) { ... }

解释:这里利用Spring AOP的@annotation切点表达式,自动捕捉被@Log标记的方法。注意@Retention(RUNTIME)是必须的,否则运行时反射拿不到注解信息。


案例二:@NotNull参数校验——告别冗长的if判断

定义注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
    String message() default "参数不能为空";
}

核心校验器(利用反射+代理):

public class ParamValidator {
    public static void validate(Object obj, Method method, Object[] args) throws Exception {
        Annotation[][] annotations = method.getParameterAnnotations();
        for (int i = 0; i < annotations.length; i++) {
            for (Annotation annotation : annotations[i]) {
                if (annotation instanceof NotNull && args[i] == null) {
                    throw new IllegalArgumentException(((NotNull) annotation).message());
                }
            }
        }
    }
}

实战:当你的Controller接收请求时,在调用Service前先执行ParamValidator.validate(),避免了每个方法里写if (param == null)的重复劳动。


案例三:基于注解的依赖注入(简易版Spring IOC)

定义@Inject注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject { }

简易容器实现

public class SimpleIOC {
    public static <T> T getBean(Class<T> clazz) throws Exception {
        T obj = clazz.newInstance();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Inject.class)) {
                field.setAccessible(true);
                field.set(obj, getBean(field.getType())); // 递归注入
            }
        }
        return obj;
    }
}

优势:通过递归构造依赖树,实现了最小的IOC容器,理解了这个原理,再看Spring的@Autowired就豁然开朗了。


案例四:@Scheduled定时任务注解的底层实现原理

先看常见的Spring用法:

@Scheduled(cron = "0 0 12 * * ?")
public void report() { ... }

底层链路

  • 第一步:Spring容器启动时,扫描所有Bean方法,解析@Scheduled注解,存入任务注册表。
  • 第二步:核心是ScheduledAnnotationBeanPostProcessor(继承BeanPostProcessor),它在postProcessAfterInitialization阶段拦截。
  • 第三步:将任务包装成Runnable,交给ThreadPoolTaskScheduler,内部依赖ScheduledExecutorService

启示:注解本身无魔法,靠的是后置处理器拦截和线程池调度。


案例五:注解+动态代理实现权限控制

定义@RequiresRole注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRole {
    String value();
}

动态代理实现

public class SecurityProxy implements InvocationHandler {
    private Object target;
    private String userRole;
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RequiresRole roleAnnotation = method.getAnnotation(RequiresRole.class);
        if (roleAnnotation != null && !userRole.equals(roleAnnotation.value())) {
            throw new AccessDeniedException("权限不足");
        }
        return method.invoke(target, args);
    }
}

核心优势:权限逻辑与业务代码完全解耦,你会看到,Java的动态代理+注解是AOP的“祖师爷”。


常见问题问答(FAQ)

Q1:@Retention的三种值有什么区别?

  • SOURCE:编译期丢弃(如@Override)。
  • CLASS:保留到class文件,但运行时JVM不读取(默认)。
  • RUNTIME:运行时可通过反射读取(自定义注解必须用它)。

Q2:为什么注解无法继承? 注解本身不支持extends,但@Inherited元注解可以让子类继承父类上的注解(只对类有效,对方法无效)。

Q3:注解会影响性能吗? 反射解析注解确实有轻微开销,对于运行频繁的接口,建议缓存解析结果(如Spring的AnnotationUtils)。


注解的适用场景与性能陷阱

适用场景:框架开发(Spring、MyBatis)、单元测试(@Test)、代码生成(Lombok)、声明式事务。

陷阱提示

  • 切勿在for循环内反复调用getAnnotation(),应将注解信息缓存在Map中。
  • 注意@Target的作用域,避免误用在类上导致逻辑混乱。
  • 自定义注解务必设置@Retention(RUNTIME),否则反射获取为null

最后建议:去读一读java.lang.annotation包源码和javax.annotation下的@Resource@PostConstruct,你会发现注解只是接口,真正强大的是那些“注解处理器”,从今天起,尝试在项目里写一个自己的@CheckPassword@CacheResult,你会理解“约定优于配置”的力量。

抱歉,评论功能暂时关闭!