Java反射机制及注解在框架中的应用

想知道Spring这类框架中的注解为什么能实现那么强大的功能,请看本篇博客,用几乎逐行的注释讲解原理。

反射

利用反射可以动态获取类的信息,并在程序运行期间动态创建对象,许多框架如spring、mybatis等均利用到了这一机制。

在编写代码或编译的过程中,可能无法得知要创建哪个对象,只有在运行时才能确定,这种情况下就需要利用反射机制,在运行时获取对象的各种信息。

利用一个例子帮助理解:

创建两个实体类;

1
2
3
4
5
public class Pizaa {
// 省略构造方法及Getter和Setter等方法
private Integer id;
private String type;
}
1
2
3
4
5
public class Hamburger {
// 省略构造方法及Getter和Setter等方法
private Integer id;
private String type;
}

创建一个配置文件,模拟spring等框架的配置文件,此处以properties配置文件为例;

1
2
# 指定要在运行时创建的类
bean=reflection.Pizaa

创建一个测试类;

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
public class Test {

private static Properties properties;

static {
try {
properties = new Properties();
// 获取类加载器,调用getResourceAsStream方法将配置文件作为流读入
properties.load(Test.class.getClassLoader().getResourceAsStream("bean.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}

public static void main(String[] args) throws Exception{
// 获取配置文件中的参数
String bean = properties.getProperty("bean");
// 获取参数指定的类
Class clazz = Class.forName(bean);
// 获取该类的无参构造器
Constructor constructor = clazz.getConstructor(null);
// 利用构造器新建实例,此时已经获取到了配置文件中指定类型的实例
Object target = constructor.newInstance(null);
System.out.println(target);
}
}

此时运行结果为:

1
Pizaa{id=null, type='null'}

如果将配置文件改为

1
bean=reflection.Hamburger

则运行结果为

1
Hamburger{id=null, type='null'}

利用这一机制可以实现一定程度上的解耦,无需用在编码时就指定要创建的实例类型,只需在配置文件中指定即可,使得类型的修改变得容易。

注解

注解需要结合反射来实现,注解本身只起到标记的作用,不进行实际的操作,实际操作由反射进行。

创建两个自定义注解,这里模拟Spring框架中的@Component注解和@Value注解;

注意:此处自定义注解的名称和Spring框架中相同,是因为笔者并未引入Spring相关的依赖,故不会产生冲突。

1
2
3
4
5
6
7
// 如下两个注解用来描述该注解
// 指定该注解生效的时机,此处为运行时生效
@Retention(RetentionPolicy.RUNTIME)
// 指定标记的目标,此处表示该注解用来标记一个Java类型
@Target(ElementType.TYPE)
public @interface Component {
}
1
2
3
4
5
6
7
8
@Retention(RetentionPolicy.RUNTIME)
// 此处表示该注解用来标记一个属性
// 可以通过这种格式指定多个目标:@Target({ElementType.TYPE,ElementType.FIELD})
@Target(ElementType.FIELD)
public @interface Value {
// 利用一个方法来接收参数
String value();
}

然后在实体类上打注解;

1
2
3
4
5
6
7
@Component
public class Pizaa {
@Value("1")
private Integer id;
@Value("bacon")
private String type;
}

创捷另一个测试类;

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
public class Test2 {

private static Properties properties;

static {
try {
properties = new Properties();
// 获取类加载器,调用getResourceAsStream方法将配置文件作为流读入
properties.load(Test.class.getClassLoader().getResourceAsStream("bean.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}

public static void main(String[] args) throws Exception {
// 获取到目标类
String bean = properties.getProperty("bean");
Class clazz = Class.forName(bean);
// 获取目标类的Component注解
Annotation componentAnno = clazz.getAnnotation(Component.class);
// 若该注解不为空则说明该类添加了该注解
if (componentAnno != null) {
// 该类添加了Component注解
// 该注解的作用是创建对象,故获取该类的构造器
Constructor constructor = clazz.getConstructor(null);
// 利用构造器创建实例
Object target = constructor.newInstance(null);

// 处理Value注解
// 获取该类所有的属性,但不包括继承得来的属性,仅有该类自身的属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
Value valueAnnoOnId = declaredField.getAnnotation(Value.class);
if (valueAnnoOnId != null) {
// 该属性添加了Value注解
// 通过调用注解中定义的方法即可取得参数
String value = valueAnnoOnId.value();
// 暴力反射机制,设置为ture,则可以强行给private修饰的属性赋值
declaredField.setAccessible(true);
// 处理属性的类型问题
switch (declaredField.getType().getName()) {
// 可以添加多个case以处理不同类型
case "java.lang.Integer":
Integer val = Integer.parseInt(value);
// 通过set方法将value的值赋给target对象的该属性
declaredField.set(target, val);
break;
default:
declaredField.set(target, value);
break;
}
}
}
System.out.println(target);
} else {
// 该类未添加Component注解
System.out.println("无法创建" + clazz.getName() + "对象");
}
}
}

此时运行结果为:

1
Pizaa{id=1, type='bacon'}

若将实体类上的注解注释掉;

1
2
3
4
5
6
7
// @Component
public class Pizaa {
@Value("1")
private Integer id;
@Value("bacon")
private String type;
}

则运行结果为:

1
无法创建reflection.Pizaa对象

通过上述例子可以看到,注解并不进行任何实际的操作,仅仅作为标记作用,而实际的操作需要通过反射机制,在运行时获取目标类后进行判断,若不为空则说明添加了该注解,而后进行一系列的业务处理。

Java反射机制及注解在框架中的应用

https://deleter-d.github.io/posts/46441/

作者

亦初

发布于

2022-05-15

更新于

2024-06-19

许可协议

# 相关文章
  1.JVM垃圾回收详解
评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...