Java高级技术(了解)

本次Java高级技术学习围绕单元测试、反射、注解、动态代理展开,均为源码、框架、架构师层面的核心技术,以下是详细内容整理:

一、单元测试

1. 传统单元测试的问题

传统在main方法中编写测试代码,存在明显缺陷:

  • 无法实现自动化测试,一个方法测试失败可能影响其他方法;
  • 无测试报告,需程序员手动观察测试结果;
  • 测试方法耦合度高,无法单独针对某个方法测试。

2. Junit单元测试框架

Junit是第三方开源的单元测试框架,主流开发工具(如IDEA)已集成,核心针对方法(最小功能单元)做正确性测试

核心优点
  • 可灵活选择单个/全部方法测试,支持自动化,且测试方法相互独立;
  • 自动生成测试报告,绿色代表测试通过,红色代表失败,无需手动分析。
使用步骤
  1. 导入Junit框架jar包(IDEA集成,无需手动导入);
  2. 为业务类定义对应测试类,为每个业务方法编写公共、无参、无返回值的测试方法;
  3. 测试方法上声明@Test注解,在方法内调用被测试的业务方法;
  4. 执行测试:右键单个测试方法执行单测,右键测试类/模块执行全测。

3. 核心小结

  • 作用:测试类中方法的正确性;
  • 执行方式:单方法右键运行,全方法选择类/模块运行;
  • 结果标识:绿色通过,红色失败。

二、反射(Reflection)

1. 反射的定义

加载类,并允许以编程的方式解剖类中的各种成分,包括成员变量、构造器、成员方法等。

2. 反射的核心学习内容

  1. 加载类,获取类的字节码:Class对象;
  2. 获取类的构造器:Constructor对象;
  3. 获取类的成员变量:Field对象;
  4. 获取类的成员方法:Method对象。

3. 第一步:获取Class对象的三种方式

反射的入口是获取类的Class对象,有三种核心方式:

  1. 直接通过类名获取(最常用);
  2. 通过全类名获取(需处理异常);
  3. 通过对象实例获取。

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. 反射的作用与应用场景

核心作用
  1. 运行时动态获取类的全部成分并操作;
  2. 可以破坏类的封装性(暴力反射);
  3. 可以绕过泛型的约束;
  4. 最核心用途:开发Java通用框架,主流框架(如Spring)均基于反射设计。
实战案例:简易对象属性保存框架

需求:对任意对象,将其字段名和对应值保存到文件中。
实现步骤

  1. 定义接收任意对象的方法;
  2. 通过反射获取对象的Class对象,再获取全部成员变量;
  3. 遍历成员变量,提取对象中该变量的具体值;
  4. 将变量名和值写入文件。

三、注解

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:自定义注解并解析
  1. 定义注解MyTest4:包含String value()、double aaa() default 100、String[] bbb();约束可标记在类和方法,保留到运行时
  2. 定义类Demo,在类和其方法test1上标记MyTest4;
  3. 定义测试类AnnotationTest3,通过反射获取Demo的Class对象和Method对象,解析注解信息。
案例2:简易版Junit框架

需求:标记@MyTest的方法自动执行,未标记的不执行。
实现步骤

  1. 定义自定义注解MyTest:@Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME);
  2. 定义若干方法,部分标记@MyTest;
  3. 通过反射获取类的所有方法,遍历判断是否存在@MyTest,若存在则执行。

6. 注解的核心作用

对Java代码的各类成分做标记,后续程序通过解析注解对标记的成分做特殊处理(如Junit中@Test标记的方法被当作测试方法执行)。

四、动态代理

1. 代理的核心思想

对象将部分职责转移给代理对象,原对象专注于核心业务,代理对象负责非核心业务(如日志、统计、前置准备);
核心要求:代理对象必须拥有与原对象相同的方法(通常通过实现同一接口保证)。

生活类比

明星(原对象)专注于唱歌、跳舞(核心业务),经纪人(代理对象)负责准备场地、收钱、对接资源(非核心业务),二者均实现“明星”接口(含唱歌、跳舞方法)。

2. Java动态代理的核心API

Java通过java.lang.reflect.Proxy类创建动态代理对象,核心方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

方法参数说明

  1. ClassLoader loader:指定加载生成的代理类的类加载器;
  2. Class<?>[] interfaces:指定代理对象实现的接口(决定代理对象有哪些方法);
  3. InvocationHandler h:指定代理对象的核心逻辑,即代理对象执行方法时要做的事情。

3. InvocationHandler的核心方法

InvocationHandler接口只有一个方法invoke,代理对象的所有方法执行都会触发该方法,方法定义:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

参数说明

  1. Object proxy:生成的代理对象本身;
  2. Method method:当前执行的原对象的方法(反射的Method对象);
  3. Object[] args:当前执行方法传入的参数;
返回值

原对象方法执行后的返回结果。

4. 动态代理的实战案例

场景

用户管理类包含登录、删除用户、查询用户方法,需统计每个方法的执行耗时,且不修改原类代码。

实现思路
  1. 定义用户管理接口(含登录、删除、查询方法),原用户管理类实现该接口;
  2. 创建代理对象,实现同一接口;
  3. 在InvocationHandler的invoke方法中编写逻辑:记录方法执行开始时间 → 调用原对象方法 → 记录结束时间 → 计算并打印耗时。
核心好处

无侵入式增强:不修改原业务代码,通过代理对象实现功能增强,符合“开闭原则”。

5. 动态代理的核心价值

是Java框架的核心底层技术(如Spring AOP),实现业务代码与非业务代码的解耦,让开发人员专注于核心业务开发。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐