注解知识快速掌握
📌 Java 注解核心知识点
一、注解基础
-
定义 :JDK5 引入的代码特殊标记(元数据),附加在代码上,供编译器 / 工具在编译 / 运行时解析。
-
作用:在不修改源码的前提下,添加元数据,实现高级功能(如配置、框架逻辑)。
-
应用场景:
-
生成文档(
javadoc) -
编译时检查(
@Override) -
替代配置文件(Spring 注解)
-
结合反射封装框架组件
-
二、Java 内置标准注解
|-------------------|-------------------------|
| 注解 | 作用 |
| @Override | 标记重写父类方法,编译器会检查是否符合重写规则 |
| @Deprecated | 标记类 / 成员 / 方法已废弃、过时 |
| @SuppressWarnings | 关闭编译器对类 / 方法 / 成员的特定警告 |
三、元注解(定义注解的注解)
元注解是修饰自定义注解的特殊注解,Java 内置 5 种核心元注解:
|-------------|-----------------------|----------------------------------------------------------------------------|
| 元注解 | 作用 | 取值 / 说明 |
| @Target | 定义注解可作用的位置 | ElementType.TYPE(类 / 接口)、METHOD(方法)、FIELD(属性)、CONSTRUCTOR(构造器)、PACKAGE(包)等 |
| @Retention | 定义注解保留的生命周期 | SOURCE(仅源码)、CLASS(字节码,默认)、RUNTIME(运行时,可通过反射获取) |
| @Documented | 标记注解会被包含在 javadoc 文档中 | - |
| @Inherited | 标记子类可继承父类的注解 | - |
| @Repeatable | 标记注解可重复使用在同一元素上 | - |
四、自定义注解
语法格式
// 获取静态方法 Method sleepMethod = clazz.getDeclaredMethod("sleep", String.class); sleepMethod.setAccessible(true); // 调用静态方法:第一个参数传 null Object result = sleepMethod.invoke(null, "冰冰"); System.out.println("静态方法返回值:" + result);
-
自定义注解自动继承
java.lang.annotation.Annotation接口。 -
可通过
default声明属性默认值。 -
运行时注解(
RUNTIME)可通过反射获取注解信息。
关键说明
-
@interface:声明注解的关键字,区别于普通interface。 -
属性本质是方法 ,使用时以键值对 赋值(如
@注解名称(value="test"))。 -
若只有一个属性且名为
value,使用时可省略value=。
五、元注解示例(@Override 源码
// 1. 获取类对象 Class<?> clazz = User.class; // 2. 获取方法对象 Method method = clazz.getDeclaredMethod("方法名", 参数类型.class); // 3. 暴力破解(如果是私有方法) method.setAccessible(true); // 4. 执行方法 // 非静态方法:invoke(对象, 参数) // 静态方法:invoke(null, 参数) Object result = method.invoke(实例对象, 实参);
-
@Target(METHOD):仅作用于方法。 -
@Retention(SOURCE):仅保留在源码,编译后丢弃。
💡 总结
-
注解是元数据,用于描述代码的额外信息。
-
元注解是定义注解的注解,控制注解的行为。
-
自定义注解需配合元注解 和反射,才能在运行时实现业务逻辑。
自定义支持优先级单元测试实战
🎯 需求:实现类似 JUnit 的自定义单元测试注解
目标:批量执行类中加了 @Test 注解的方法,并支持优先级排序 和禁用控制。
1️⃣ 定义自定义注解 @Test
import java.lang.annotation.*; // 作用于方法上 @Target(ElementType.METHOD) // 运行时保留,可通过反射获取 @Retention(RetentionPolicy.RUNTIME) // 可被继承、可生成文档 @Inherited @Documented public @interface Test { /** * 优先级:数字越小,执行越靠前 */ int priority() default 0; /** * 是否禁用:true 表示不执行该方法 */ boolean disabled() default false; }
2️⃣ 编写测试用例类
public class TestCase { @Test(priority = 1, disabled = false) public void testCase1() { System.out.println("测试用例1"); } @Test(priority = 2) public void testCase2() { System.out.println("测试用例2"); } @Test(priority = 3) public void testCase3() { System.out.println("测试用例3"); } @Test(priority = 4, disabled = true) public void testCase4() { System.out.println("测试用例4(被禁用)"); } }
3️⃣ 反射解析注解并执行方法
import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Comparator; import java.util.List; public class TestRunner { public static void main(String[] args) throws Exception { TestCase testCase = new TestCase(); Class<TestCase> clazz = TestCase.class; // 1. 获取类中所有声明的方法 Method[] methods = clazz.getDeclaredMethods(); List<Method> runnableMethods = new ArrayList<>(); // 2. 筛选出加了 @Test 且未被禁用的方法 for (Method method : methods) { if (method.isAnnotationPresent(Test.class)) { Test annotation = method.getAnnotation(Test.class); if (!annotation.disabled()) { runnableMethods.add(method); } } } // 3. 按 priority 排序(数字越小越先执行) runnableMethods.sort(Comparator.comparingInt( m -> m.getAnnotation(Test.class).priority() )); // 4. 批量执行方法 for (Method method : runnableMethods) { method.invoke(testCase); } } }
4️⃣ 核心知识点总结
|--------------------------------|---------------|
| 反射方法 | 作用 |
| isAnnotationPresent(XXX.class) | 判断元素是否存在指定注解 |
| getAnnotation(XXX.class) | 获取指定注解的实例 |
| getDeclaredMethods() | 获取本类所有方法(含私有) |
| Method.invoke(obj, args) | 执行目标方法 |
✅ 运行结果
测试用例1 测试用例2 测试用例3
testCase4因disabled = true被跳过执行。
💡 关键设计点
-
优先级控制 :通过
priority属性排序,实现自定义执行顺序。 -
灵活禁用 :通过
disabled属性控制是否执行某方法。 -
框架化思想:注解 + 反射 = 轻量级单元测试框架(类似 JUnit 核心原理)。
反射技术应用之JDK动态代理实战
📌 代理模式核心总览
代理模式(Proxy Pattern) 是为其他对象提供一种代理以控制对这个对象的访问。客户端不直接调用目标对象,而是通过代理间接调用,符合开闭原则(不修改源码,扩展功能)。
一、静态代理 (Static Proxy)
定义与原理
-
编译期生成 :代理类的
.class文件在程序运行前就已经存在(或手动编写)。 -
实现方式 :目标类和代理类实现同一个接口,代理类持有目标类的实例。
-
核心流程:客户端 -> 代理类 -> 目标类(前后可添加逻辑)。
编码实战
步骤 1:定义公共接口
public interface PayService { String callback(String outTradeNo); int save(int userId, int productId); }
步骤 2:实现目标类
public class PayServiceImpl implements PayService { @Override public String callback(String outTradeNo) { System.out.println("真实PayService执行回调"); return "success"; } @Override public int save(int userId, int productId) { System.out.println("真实PayService执行保存"); return 1; } }
步骤 3:实现代理类(核心)
public class StaticProxyPayServiceImpl implements PayService { // 持有目标对象的引用 private PayService payService; // 构造器注入目标对象 public StaticProxyPayServiceImpl(PayService payService) { this.payService = payService; } @Override public String callback(String outTradeNo) { // 1. 代理前增强:打印日志 System.out.println("StaticProxy callback begin"); // 2. 调用真实对象方法 String result = payService.callback(outTradeNo); // 3. 代理后增强:打印日志 System.out.println("StaticProxy callback end"); return result; } @Override public int save(int userId, int productId) { System.out.println("StaticProxy save begin"); int id = payService.save(userId, productId); System.out.println("StaticProxy save end"); return id; } }
步骤 4:测试
public class Test { public static void main(String[] args) { // 1. 创建目标对象 PayService target = new PayServiceImpl(); // 2. 创建代理对象,关联目标对象 PayService proxy = new StaticProxyPayServiceImpl(target); // 3. 调用方法(实际上走的是代理的invoke,内部调用真实方法) proxy.callback("123456"); } }
优缺点
-
✅ 优点:
-
不修改目标源码,通过代理扩展功能。
-
客户端只需知道代理类,解耦了真实对象的实现。
-
-
❌ 缺点:
-
代码冗余:每个代理类只能对应一个接口,若接口新增方法,代理类必须同步修改。
-
维护复杂:若有多个类需要代理,需创建大量代理类。
-
二、动态代理 (Dynamic Proxy)
定义与原理
-
运行期生成 :通过反射机制动态生成 代理类的字节码,无需手动编写
.java和.class文件。 -
核心依赖:
-
java.lang.reflect.Proxy:生成代理实例。 -
java.lang.reflect.InvocationHandler:调用处理器(重写invoke方法,统一处理方法调用)。
-
-
限制 :JDK 动态代理要求目标类必须实现接口。
核心 API:InvocationHandler
public interface InvocationHandler { /** * 代理所有方法调用的入口 * @param proxy 代理对象实例 * @param method 被调用的方法对象 * @param args 方法入参 * @return 方法返回值 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
编码实战
步骤 1:实现调用处理
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class JdkProxy implements InvocationHandler { // 目标对象(被代理的对象) private Object targetObject; // 绑定目标对象,获取代理实例 public Object getProxyInstance(Object targetObject) { this.targetObject = targetObject; // 1. 类加载器 2. 接口数组 3. 调用处理器 return Proxy.newProxyInstance( targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this ); } // 核心方法:所有调用目标类的方法都会汇聚到这里 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { // 前置增强:打印日志 System.out.println("JDK动态代理调用【" + method.getName() + "】开始"); // 执行目标方法 result = method.invoke(targetObject, args); // 后置增强:打印日志 System.out.println("JDK动态代理调用【" + method.getName() + "】结束"); } catch (Exception e) { // 异常处理 e.printStackTrace(); } return result; } }
步骤 2:测试
public class TestDynamic { public static void main(String[] args) { // 1. 创建目标对象 PayService target = new PayServiceImpl(); // 2. 创建代理处理器 JdkProxy jdkProxy = new JdkProxy(); // 3. 获取动态代理对象 PayService proxy = (PayService) jdkProxy.getProxyInstance(target); // 4. 调用方法 proxy.callback("order_001"); proxy.save(1, 101); } }
动态代理 VS 静态代理
|-------|-----------------|---------------------|
| 特性 | 静态代理 | JDK 动态代理 |
| 代码编写 | 手动写代理类 | 无需手写,运行时生成 |
| 目标类要求 | 实现接口,代理类也实现同一接口 | 必须实现接口(基于接口代理) |
| 灵活性 | 固定,只能代理特定类 | 灵活,一个处理器可代理多个实现类 |
| 性能 | 快(直接调用) | 稍慢(反射调用),但框架级优化后可接受 |
💡 总结与应用场景
-
静态代理:适合简单、固定的单一类代理,代码直观但维护成本高。
-
动态代理:Spring AOP、MyBatis 拦截器、事务管理的底层核心。利用反射在运行时动态织入逻辑,是 Java 框架开发的基石。
📚 延伸:CGLIB 代理
JDK 动态代理要求目标类必须实现接口。如果目标类没有实现接口 ,则可以使用 CGLIB (Code Generation Library) 动态代理。
-
原理 :通过字节码技术,生成目标类的子类作为代理类,重写其中的方法。
-
依赖:Spring 核心包已集成,无需额外引入。