Java SE-12Java加强-Java高级技术(反射、注解、动态代理)
Java高级技术(了解)
本次Java高级技术学习围绕单元测试、反射、注解、动态代理展开,均为源码、框架、架构师层面的核心技术,以下是详细内容整理:
一、单元测试
1. 传统单元测试的问题
传统在main方法中编写测试代码,存在明显缺陷:
- 无法实现自动化测试,一个方法测试失败可能影响其他方法;
- 无测试报告,需程序员手动观察测试结果;
- 测试方法耦合度高,无法单独针对某个方法测试。
2. Junit单元测试框架
Junit是第三方开源的单元测试框架,主流开发工具(如IDEA)已集成,核心针对方法(最小功能单元)做正确性测试。
核心优点
- 可灵活选择单个/全部方法测试,支持自动化,且测试方法相互独立;
- 自动生成测试报告,绿色代表测试通过,红色代表失败,无需手动分析。
使用步骤
- 导入Junit框架jar包(IDEA集成,无需手动导入);
- 为业务类定义对应测试类,为每个业务方法编写公共、无参、无返回值的测试方法;
- 测试方法上声明@Test注解,在方法内调用被测试的业务方法;
- 执行测试:右键单个测试方法执行单测,右键测试类/模块执行全测。
3. 核心小结
- 作用:测试类中方法的正确性;
- 执行方式:单方法右键运行,全方法选择类/模块运行;
- 结果标识:绿色通过,红色失败。
二、反射(Reflection)
1. 反射的定义
加载类,并允许以编程的方式解剖类中的各种成分,包括成员变量、构造器、成员方法等。
2. 反射的核心学习内容
- 加载类,获取类的字节码:Class对象;
- 获取类的构造器:Constructor对象;
- 获取类的成员变量:Field对象;
- 获取类的成员方法:Method对象。
3. 第一步:获取Class对象的三种方式
反射的入口是获取类的Class对象,有三种核心方式:
- 直接通过类名获取(最常用);
- 通过全类名获取(需处理异常);
- 通过对象实例获取。
4. 获取并操作类的各种成分
Class类提供了获取类成分的方法,分为仅获取public修饰和获取所有存在的成分两类,部分成分需通过setAccessible(true)实现暴力反射,突破访问权限修饰符的限制。
(1)获取并操作构造器
构造器的核心作用:初始化对象并返回。
| Class获取构造器的方法 | 说明 |
|---|---|
| 获取全部public修饰的构造器 | 获取全部public修饰的构造器 |
| 获取全部构造器(任意修饰符) | 获取全部构造器(任意修饰符) |
| 获取单个public修饰的构造器(指定参数类型) | 获取单个public修饰的构造器(指定参数类型) |
| 获取单个构造器(任意修饰符,指定参数类型) | 获取单个构造器(任意修饰符,指定参数类型) |
| Constructor的操作方法 | 说明 |
|---|---|
| 调用构造器,传入参数创建对象 | 调用构造器,传入参数创建对象 |
| 设置为true,开启暴力反射,突破访问权限 | 设置为true,开启暴力反射,突破访问权限 |
(2)获取并操作成员变量
成员变量的核心作用:赋值、取值。
| Class获取成员变量的方法 | 说明 |
|---|---|
| 获取全部public修饰的成员变量 | 获取全部public修饰的成员变量 |
| 获取全部成员变量(任意修饰符) | 获取全部成员变量(任意修饰符) |
| 获取单个public修饰的成员变量(指定变量名) | 获取单个public修饰的成员变量(指定变量名) |
| 获取单个成员变量(任意修饰符,指定变量名) | 获取单个成员变量(任意修饰符,指定变量名) |
| Field的操作方法 | 说明 |
|---|---|
| 为指定对象的该变量赋值 | 为指定对象的该变量赋值 |
| 获取指定对象的该变量值 | 获取指定对象的该变量值 |
| 开启暴力反射,突破访问权限 | 开启暴力反射,突破访问权限 |
(3)获取并操作成员方法
成员方法的核心作用:执行方法。
| Class获取成员方法的方法 | 说明 |
|---|---|
| 获取全部public修饰的成员方法(含父类继承的) | 获取全部public修饰的成员方法(含父类继承的) |
| 获取本类全部成员方法(任意修饰符,不含父类) | 获取本类全部成员方法(任意修饰符,不含父类) |
| 获取单个public修饰的方法(指定方法名+参数类型) | 获取单个public修饰的方法(指定方法名+参数类型) |
| 获取单个本类方法(任意修饰符,指定方法名+参数类型) | 获取单个本类方法(任意修饰符,指定方法名+参数类型) |
| Method的操作方法 | 说明 |
|---|---|
| 触发指定对象的该方法执行,返回方法执行结果 | 触发指定对象的该方法执行,返回方法执行结果 |
| 开启暴力反射,突破访问权限 | 开启暴力反射,突破访问权限 |
5. 反射的作用与应用场景
核心作用
- 运行时动态获取类的全部成分并操作;
- 可以破坏类的封装性(暴力反射);
- 可以绕过泛型的约束;
- 最核心用途:开发Java通用框架,主流框架(如Spring)均基于反射设计。
实战案例:简易对象属性保存框架
需求:对任意对象,将其字段名和对应值保存到文件中。
实现步骤:
- 定义接收任意对象的方法;
- 通过反射获取对象的Class对象,再获取全部成员变量;
- 遍历成员变量,提取对象中该变量的具体值;
- 将变量名和值写入文件。
三、注解
1. 注解的概述
- 定义:Java代码中的特殊标记(如@Override、@Test),让其他程序根据注解信息决定执行逻辑;
- 使用位置:可标记在类、构造器、方法、成员变量、参数等位置。
2. 自定义注解
定义语法
public @interface 注解名称 {
public 属性类型 属性名() default 默认值 ;
}
- 特殊属性名:value,若注解只有一个value属性,使用时可省略value=;
- 注解本质:一个继承了Annotation接口的接口,@注解(…)本质是该接口的实现类对象。
示例
// 自定义注解
public @interface MyTest1 {
String aaa();
boolean bbb();
String[] ccc();
}
// 使用注解
@MyTest1(aaa = "李四", bbb=true, ccc={"Go", "Python"})
public void test(){}
3. 元注解
定义:注解注解的注解,用于约束自定义注解的使用规则,核心有两个:@Target、@Retention。
(1)@Target
- 作用:声明被修饰的注解可使用的位置;
- 常用取值:
- ElementType.TYPE:类、接口;
- ElementType.FIELD:成员变量;
- ElementType.METHOD:成员方法;
- ElementType.PARAMETER:方法参数;
- ElementType.CONSTRUCTOR:构造器;
- ElementType.LOCAL_VARIABLE:局部变量。
(2)@Retention
- 作用:声明注解的保留周期;
- 常用取值(优先级递增):
- RetentionPolicy.SOURCE:仅源码阶段,字节码文件中无;
- RetentionPolicy.CLASS:默认值,保留到字节码文件,运行时无;
- RetentionPolicy.RUNTIME:开发常用,保留到运行阶段(可通过反射解析)。
示例
// 约束@Test注解仅能标记方法,且保留到运行时
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
}
4. 注解的解析
定义:判断类/方法/变量上是否存在注解,并将注解中的属性值解析出来。
核心指导思想
要解析谁上面的注解,先获取谁的对象(如解析方法注解先获取Method对象,解析类注解先获取Class对象)。
解析的核心接口
Class、Method、Field、Constructor均实现了AnnotatedElement接口,拥有解析注解的能力,核心方法如下:
| 方法 | 说明 |
|---|---|
| 获取当前对象上的所有注解 | 获取当前对象上的所有注解 |
| 获取当前对象上指定类型的注解对象 | 获取当前对象上指定类型的注解对象 |
| 判断当前对象上是否存在指定注解 | 判断当前对象上是否存在指定注解 |
5. 注解的实战案例
案例1:自定义注解并解析
- 定义注解MyTest4:包含String value()、double aaa() default 100、String[] bbb();约束可标记在类和方法,保留到运行时;
- 定义类Demo,在类和其方法test1上标记MyTest4;
- 定义测试类AnnotationTest3,通过反射获取Demo的Class对象和Method对象,解析注解信息。
案例2:简易版Junit框架
需求:标记@MyTest的方法自动执行,未标记的不执行。
实现步骤:
- 定义自定义注解MyTest:@Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME);
- 定义若干方法,部分标记@MyTest;
- 通过反射获取类的所有方法,遍历判断是否存在@MyTest,若存在则执行。
6. 注解的核心作用
对Java代码的各类成分做标记,后续程序通过解析注解对标记的成分做特殊处理(如Junit中@Test标记的方法被当作测试方法执行)。
四、动态代理
1. 代理的核心思想
对象将部分职责转移给代理对象,原对象专注于核心业务,代理对象负责非核心业务(如日志、统计、前置准备);
核心要求:代理对象必须拥有与原对象相同的方法(通常通过实现同一接口保证)。
生活类比
明星(原对象)专注于唱歌、跳舞(核心业务),经纪人(代理对象)负责准备场地、收钱、对接资源(非核心业务),二者均实现“明星”接口(含唱歌、跳舞方法)。
2. Java动态代理的核心API
Java通过java.lang.reflect.Proxy类创建动态代理对象,核心方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法参数说明
- ClassLoader loader:指定加载生成的代理类的类加载器;
- Class<?>[] interfaces:指定代理对象实现的接口(决定代理对象有哪些方法);
- InvocationHandler h:指定代理对象的核心逻辑,即代理对象执行方法时要做的事情。
3. InvocationHandler的核心方法
InvocationHandler接口只有一个方法invoke,代理对象的所有方法执行都会触发该方法,方法定义:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
参数说明
- Object proxy:生成的代理对象本身;
- Method method:当前执行的原对象的方法(反射的Method对象);
- Object[] args:当前执行方法传入的参数;
返回值
原对象方法执行后的返回结果。
4. 动态代理的实战案例
场景
用户管理类包含登录、删除用户、查询用户方法,需统计每个方法的执行耗时,且不修改原类代码。
实现思路
- 定义用户管理接口(含登录、删除、查询方法),原用户管理类实现该接口;
- 创建代理对象,实现同一接口;
- 在InvocationHandler的invoke方法中编写逻辑:记录方法执行开始时间 → 调用原对象方法 → 记录结束时间 → 计算并打印耗时。
核心好处
无侵入式增强:不修改原业务代码,通过代理对象实现功能增强,符合“开闭原则”。
5. 动态代理的核心价值
是Java框架的核心底层技术(如Spring AOP),实现业务代码与非业务代码的解耦,让开发人员专注于核心业务开发。
更多推荐



所有评论(0)