前言

  • Java进阶课程的第六篇,也是最后一篇,junit单元测试,反射,注解,动态代理相关内容

  • 学习完基础之后就是进阶的内容了。


包含的知识

junit单元测试

反射

  1. 内部类Student:
  • 包含私有/公共字段和方法
  • 包含默认构造器和私有构造器
  1. 获取Class对象的三种方式:
  • .class 语法直接获取
  • 通过对象实例的getClass()方法获取
  • 最常用的Class.forName()动态加载方式
  1. 反射创建对象:
  • 通过无参构造器创建实例
  • 访问私有构造器创建实例(需要设置setAccessible(true))
  1. 字段操作:
  • 访问公共字段直接修改值
  • 访问私有字段需要设置可访问权限
  1. 方法调用:
  • 调用公共方法
  • 调用私有方法需要设置可访问权限
  • 演示带参数方法的调用
  1. 类结构信息获取:
  • 获取类名和方法列表
  • 区分getMethods()和getDeclaredMethods()的区别
  1. 关键点说明:
  • setAccessible(true)可以突破私有成员的访问限制,但会破坏封装性
  • 反射操作需要处理各种异常(本示例直接抛出简化代码)
  • 反射的性能较低,适合框架开发等需要高度灵活性的场景
  • 反射可以访问到类的完整结构信息,包括注解、泛型等

注解

  1. 自定义注解:
  • 使用@interface关键字来定义一个新的注解类型。例如:public @interface MyInterface {}。
  • 注解可以包含成员变量(也称作元素),这些成员变量可以有默认值或必须在使用时提供值。
  • 元注解(Meta-annotations):
    • @Target指定注解的应用目标(如方法、字段等)。例如:@Target({ElementType.METHOD})表示该注解只能应用于方法上。
    • @Retention定义了注解的生命周期,RetentionPolicy.RUNTIME表示该注解将在运行时保留,可以通过反射读取。
  1. 注解的使用:
  • 注解可以直接应用到类、方法或字段上,通过在它们之前加上注解名称并提供必要的参数值。例如:@MyTest(count = 2)。
  1. 反射与注解:
  • 利用Java的反射机制,可以在运行时检查类、方法或字段上的注解。例如,通过method.isAnnotationPresent(MyTest.class)判断方法是否被特定注解标记,并通过method.getDeclaredAnnotation(MyTest.class)获取注解实例。
  • 反射还可以用于调用带有注解的方法,如示例中method.invoke(ad)执行被@MyTest注解标记的方法。
  1. 注解属性的默认值:
  • 注解成员可以设置默认值,如果在使用注解时不提供值,则会使用默认值。例如:int count() default 1;。
  1. 特殊属性value:
  • 如果注解只有一个名为value的成员,在使用注解时可以省略成员名直接提供值。例如:@MyInterface(“delete”)相当于@MyInterface(value=”delete”)。

动态代理

  1. 业务接口 (UserService)
  • 定义代理类和真实类共同遵守的规范
  • 动态代理只能基于接口实现
  1. 真实对象 (UserServiceImpl)
  • 实际业务逻辑的实现类
  • 包含需要被增强的核心逻辑
  1. 调用处理器 (LoggingHandler)
  • 实现InvocationHandler接口
  • 持有真实对象引用(目标对象)
  • invoke()方法中实现统一代理逻辑
  • 通过反射调用真实方法(method.invoke())
  • 可以添加前置/后置增强逻辑(如日志、事务等)
  1. 代理对象创建 (Proxy.newProxyInstance)
  • 参数1:类加载器(通常使用接口的类加载器)
  • 参数2:代理类需要实现的接口数组
  • 参数3:调用处理器实例
  • 返回实现指定接口的代理对象
  1. 动态代理特点:
  • 运行时动态生成代理类(通过ProxyGenerator生成)
  • 代理类名通常为$Proxy+数字
  • 继承Proxy类(所以不能代理类,只能代理接口)
  • 实现了指定的业务接口
  • 方法调用会被路由到InvocationHandler
  1. 典型应用场景:
  • AOP编程(日志、事务、权限控制)
  • RPC框架调用
  • 服务接口的监控统计
  • 单元测试Mock对象
  1. 注意事项:
  • 代理对象的方法调用都会经过invoke方法
  • 在invoke方法中谨慎使用proxy参数,容易引发递归调用
  • 性能相比静态代理略低(反射调用)
  • 无法代理final类和final方法

具体代码

junit单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package ADV_0;

import org.junit.Test;

class StringUtil {
//业务类的一个方法
public static void printNumber(String name){
if(name == null){
System.out.println("参数为null");
return;
}
System.out.println("名字长度是:" + name.length());
}
}

// 测试类:junit单元测试框架,对业务类方法进行测试
public class StringUtilTest {
// 测试方法:必公开public,无参,无返回值
// 测试方法必须加上@Test注解(Junit核心)
@Test
public void testPrint() {
StringUtil.printNumber("微光zc"); // 5
// 测试用例,测试核心,各种情况下的测试
StringUtil.printNumber("");
StringUtil.printNumber(null);
}
}

反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package ADV_0;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* Java反射机制示例类
* 演示如何通过反射操作类信息、创建对象、访问字段和调用方法
*/
public class ADVZc5 {
// 示例用内部类
static class Student {
private String name;
public int age;
public Student() {
this.name = "Default";
this.age = 18;
}
private Student(String name) {this.name = name;this.age = 20;}

public void showInfo() {System.out.println("Student: " + name + ", " + age);}

private void setAge(int age) {this.age = age;}
}

public static void main(String[] args) throws Exception {
System.out.println("加载类,获取类的字节码:Class对象");
System.out.println("获取类的构造器:Constructor对象");
System.out.println("获取类的成员变量:Field对象");
System.out.println("获取类的成员方法:MMethod对象");
// 1. 获取Class对象的三种方式
// 方式一:通过类名.class获取
Class<Student> clazz1 = Student.class;

// 方式二:通过对象.getClass()获取
Student student = new Student();
Class<? extends Student> clazz2 = student.getClass();

// 方式三:通过Class.forName()获取(最常用)
Class<?> clazz3 = Class.forName("ADV_0.ADVZc6$Student"); // 内部类需要用$符号

System.out.println("三个Class对象是否相同:" + (clazz1 == clazz2 && clazz2 == clazz3));

// 2. 通过反射创建对象
// 使用无参构造器
Constructor<?> constructor1 = clazz1.getDeclaredConstructor();
Student s1 = (Student) constructor1.newInstance();
s1.showInfo();

// 使用私有构造器(需要设置可访问)
Constructor<?> constructor2 = clazz1.getDeclaredConstructor(String.class);
constructor2.setAccessible(true); // 突破私有访问限制
Student s2 = (Student) constructor2.newInstance("Alice");
s2.showInfo();

// 3. 访问字段
// 访问公共字段
Field ageField = clazz1.getField("age");
ageField.set(s1, 25); // 等价于 s1.age = 25
System.out.print("修改公共字段后:");
s1.showInfo();

// 访问私有字段(需要设置可访问)
Field nameField = clazz1.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(s1, "Bob"); // 等价于 s1.name = "Bob"
System.out.print("修改私有字段后:");
s1.showInfo();

// 4. 调用方法
// 调用公共方法
Method showInfoMethod = clazz1.getMethod("showInfo");
System.out.print("反射调用方法:");
showInfoMethod.invoke(s2);

// 调用私有方法(需要设置可访问)
Method setAgeMethod = clazz1.getDeclaredMethod("setAge", int.class);
setAgeMethod.setAccessible(true);
setAgeMethod.invoke(s2, 30);
System.out.print("修改私有字段后:");
s2.showInfo();

// 5. 获取类结构信息
System.out.println("\n类结构信息:");
System.out.println("类名:" + clazz1.getName());
System.out.println("简单类名:" + clazz1.getSimpleName());
System.out.println("公共方法列表:");
for (Method method : clazz1.getMethods()) {
System.out.println(" " + method.getName());
}
System.out.println("声明字段列表:");
for (Field field : clazz1.getDeclaredFields()) {
System.out.println(" " + field.getName());
}

System.out.println("===反射的基本作用===");
// 1、类的全部成分的获取
// 2、可以破坏封装性
// 3、可以绕过泛型的约束

System.out.println("===反射的应用:做框架的通用技术===");
}
}

注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package ADV_0;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

public class ADVZc6 {
// 自定义注解
public @interface MyInterface {
String name();
int age() default 18;
String[] address();
}
}

@Target({ElementType.METHOD}) // 表示注解的作用目标为方法
@Retention(RetentionPolicy.RUNTIME) // 表示注解的保留策略: 编译器运行时(一直活着)
@interface MyTest {
int count() default 1; // 表示注解的属性
}
@Target({ElementType.METHOD, ElementType.FIELD}) // 表示注解的作用目标为方法,成员变量
@Retention(RetentionPolicy.RUNTIME) // 表示注解的保留策略: 编译器运行时(一直活着)
@interface MyTest1 {
}

@Target({ElementType.METHOD, ElementType.TYPE}) // 表示注解的作用目标为方法,成员变量
@Retention(RetentionPolicy.RUNTIME) // 表示注解的保留策略: 编译器运行时(一直活着)
@interface MyTest2 {
String value();
double height() default 169.5;
String[] address();
}

@ADVZc6.MyInterface(name = "赵丽颖", age = 18, address = {"北京", "上海"})
//@MyInterface(age = "delete")
//@MyInterface("delete") // 特殊属性value,在使用时如果只有一个value属性,value名称可以不写
class AnnotationDemo1 {
@ADVZc6.MyInterface(name = "王菲", age = 52, address = {"北京", "香港"})
public static void main( String[] args ) {
// 目标:自定义注解。
int a;
}
}

//@MyTest1
class AnnotationDemo2 {
private int age;
//@MyTest1
public AnnotationDemo2(){
}
public static void main(String[] args) {
//元注解的作用
}
public void getAgeTest(){
}
}

class AnnotationDemo4 {
// 注解的应用场景:模拟junit框架。有MyTest注解的方法就执行,没有的就不执行
public static void main(String[] args) throws Exception {
AnnotationDemo4 ad = new AnnotationDemo4();
// 1、获取类对象
Class c = AnnotationDemo4.class;
// 2、获取所有方法
Method[] methods = c.getMethods();
// 3、遍历所有方法,判断方法上是否有MyTest注解,有就执行,没有就不执行。
for (Method method : methods) {
// 4、判断方法上是否有MyTest注解
if (method.isAnnotationPresent(MyTest.class)) {
// 获取到这个方法的注解
MyTest myTest = method.getDeclaredAnnotation(MyTest.class);
int count = myTest.count();
// 5、有就执行这个method方法
for (int i = 0; i < count; i++) {
method.invoke(ad);
}
}
}
}

// 测试方法:public 无参 无返回值
@MyTest
public void test1(){
System.out.println("test1方法执行了");
}
public void test2(){
System.out.println("test2方法执行了");
}
@MyTest(count = 2)
public void test3(){
System.out.println("test3方法执行了");
}
@MyTest
public void test4(){
System.out.println("test4方法执行了");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package ADV_0;

import org.junit.Test;

import java.lang.reflect.Method;
import java.util.Arrays;

public class AnnotationDemo3 {
//解析注解
@Test
public void parseClass() throws Exception {
// 1.获取类对象
Class c1 = Demo.class;
// 2、使用isAnnotationPresent判断这个类上是否陈列了注解MyTest2
if (c1.isAnnotationPresent(MyTest2.class)) {
// 3、获取注解对象
MyTest2 myTest2 = (MyTest2) c1.getDeclaredAnnotation(MyTest2.class);

// 4、获取注解属性值
String[] address = myTest2.address();
double height = myTest2.height();
String value = myTest2.value();

// 5、打印注解属性值
System.out.println(Arrays.toString(address));
System.out.println(height);
System.out.println(value);
}
}

@Test
public void parseMethod() throws Exception {
// 1.获取类对象
Class c1 = Demo.class;
// 2、获取方法对象
Method method = c1.getMethod("go");
// 3、使用isAnnotationPresent判断这个方法上是否陈列了注解MyTest2
if (method.isAnnotationPresent(MyTest2.class)) {
// 4、获取注解对象
MyTest2 myTest2 = method.getDeclaredAnnotation(MyTest2.class);

// 5、获取注解属性值
String[] address = myTest2.address();
double height = myTest2.height();
String value = myTest2.value();

// 6、打印注解属性值
System.out.println(Arrays.toString(address));
System.out.println(height);
System.out.println(value);
}
}
}

动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package ADV_0;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* 动态代理示例代码
* 包含:接口定义、真实对象、调用处理器、客户端演示
*/
public class ADVZc7 {
// 1. 定义业务接口
interface UserService {
void addUser(String username);
String getUser(int userId);
}

// 2. 真实对象(被代理类)
static class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("真实方法: 添加用户 " + username);
}
@Override
public String getUser(int userId) {
return "真实用户" + userId;
}
}

// 3. 调用处理器(实现InvocationHandler)
static class LoggingHandler implements InvocationHandler {
private final Object target; // 持有真实对象引用

public LoggingHandler(Object target) {
this.target = target;
}

/**
* 代理方法调用的核心逻辑
* @param proxy 代理对象(慎用)
* @param method 被调用的方法对象
* @param args 方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("[日志] 开始执行方法: " + method.getName());

// 反射调用真实对象的方法
Object result = method.invoke(target, args);

// 后置增强
System.out.println("[日志] 方法执行完成: " + method.getName());

return result;
}
}

public static void main(String[] args) {
// 4. 创建真实对象
UserService realService = new UserServiceImpl();

// 5. 创建调用处理器
InvocationHandler handler = new LoggingHandler(realService);

// 6. 动态生成代理对象
UserService proxyService = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(), // 类加载器
new Class[]{UserService.class}, // 代理接口数组
handler // 调用处理器
);

// 7. 通过代理对象调用方法
proxyService.addUser("张三");
System.out.println("查询结果:" + proxyService.getUser(1001));

// 打印代理类信息
System.out.println("\n代理类名称: " + proxyService.getClass().getName());
System.out.println("代理类父类: " + proxyService.getClass().getSuperclass());
System.out.println("代理类实现的接口: ");
for (Class<?> inter : proxyService.getClass().getInterfaces()) {
System.out.println(" - " + inter.getName());
}
}
}