自定义注解注入属性值(基于反射和静态变量)

本文将举例说明,如果自定义一个注解Name,并通过注解给类的属性注入值

如何通过Spring配置类处理注解

  1. 定义注解
  2. 定义一个Spring配置类
  3. 在配置类中使用Java反射注入静态属性

定义注解

定义一个注解“Name”,Name注解可以接收一个String类型的属性,并且可以使用在类或者属性上

1
2
3
4
5
6
7
8
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Name {

String value() default "";
}

定义Sping 配置类

定义一个Spring配置类,并使用@PostConstruct注解方法实现在Spring启动过程中处理注解

1
2
3
4
5
6
7
8
9
10
@Configuration
public class NameConfig {

List<Class> classList = new ArrayList<>();

@PostConstruct
public void init() {
//处理注解
}
}

使用Java反射注入静态属性

通过Java反射获取指定类的属性,并判断其中是否使用Name注解,有的话就获取Name注解的值,并注入到该静态属性中

tips:这里要指定会使用到Name注解的类,例子中就是Xiaohong.class

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
@Configuration
public class NameConfig {

List<Class> classList = new ArrayList<>();

@PostConstruct
public void init() {
classList.add(Xiaohong.class);
classList.add(Xiaobai.class);
classList.forEach(e -> setName(e));
}

public void setName(Class clazz) {
//处理注解在类上
if (clazz.isAnnotationPresent(Name.class)) {
Name annotation = (Name) clazz.getAnnotation(Name.class);
try {
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(null, annotation.value());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return;
}
//处理注解在属性上
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
Name annotation = declaredField.getAnnotation(Name.class);
if (null == annotation) {
continue;
}
declaredField.setAccessible(true);
try {
declaredField.set(null, annotation.value());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}

在属性上中使用注解

在类中的某个属性上使用注解Name,并指定值为“小红”

1
2
3
4
5
6
7
8
9
public class Xiaohong {

@Name("小红")
static String name;

public void sayHi() {
System.out.println("hello, my name is " + name);
}
}

测试一下

如下例子会打印出:
hello, my name is 小红

1
2
3
4
5
6
7
8
9
10
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloTest {

@Test
public void HelloXiaohongTest() {
Xiaohong xiaohong = new Xiaohong();
xiaohong.sayHi();
}
}

在类上中使用注解

在类上使用注解Name,并指定值为“小白”

1
2
3
4
5
6
7
8
9
@Name("小白")
public class Xiaobai {

static String name;

public void sayHi() {
System.out.println("hello, my name is " + name);
}
}

测试一下

如下例子会打印出:
hello, my name is 小白

1
2
3
4
5
6
7
8
9
10
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloTest {

@Test
public void HelloXiaobaiTest() {
Xiaobai xiaobai = new Xiaobai();
xiaobai.sayHi();
}
}

为什么只能注解在作用与静态属性

通过上面的方法我们自动,Java注入属性的时候是在Spring启动过程中,并不是运行过程,所以这时候只知道类,而不知道对象。
而只有静态属性的注入是不需要指定对象的,所以静态属性可以通过这种方式来注入。

假如我们想要处理非静态属性的注入,可以封装一个注解处理方法,在运行过程中创建对象之后(例如构造方法中),调用注解处理方法,即可达到效果。

例如:

在构造方法中处理注解

定义一个方法setName(),用来处理Name注解,并以使用了Name注解的对象作为参数;在构造方法中调用这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Name("小蓝")
public class Xiaolan {

private String name;

public Xiaolan() {
setName(this);
}

public void sayHi() {
System.out.println("hello, my name is " + name);
}

public void setName(Xiaolan xiaolan) {
Class clazz = this.getClass();
if (clazz.isAnnotationPresent(Name.class)) {
Name annotation = (Name) clazz.getAnnotation(Name.class);
xiaolan.name = annotation.value();
return;
}
}
}

测试一下

如下例子会打印出:
hello, my name is 小蓝

1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloTest {

@Autowired
NameConfig nameConfig;

@Test
public void HelloXiaolanTest() {
Xiaolan xiaolan = new Xiaolan();
xiaolan.sayHi();
}
}

tips: 实际使用过程中建议抽象一个超类,使用超类的构造方法来处理注解。

总结

沿着上文的思路我们可以发现,自定义注解的重点在于 根据注解要达到的目的,考虑如何处理注解。

例如: 本文的需求是通过注解来注入一个属性,考虑到属性可以为static,所以通过Spring配置加反射的方式; 如果属性不能为static,则可以通过构造方法来实现。

在实际开发中,处理注解还有其他多种方式,各有优缺点。

例如:

  • 在Bean的后置处理器中处理注解
    通过实现Bean的后置处理器方法,配合Java反射。
    这种注解可以使用在类、方法、属性上。
    局限性是这种注解只能在Spring Bean中使用。
    参考SpringBoot之自定义注解(基于BeanPostProcessor接口实现)

  • 通过Spring AOP处理
    原理是通过注解来定义切点。
    优点是编写简单。
    但这种注解也只能使用在Spring Bean中,且只能作用于方法上
    参考SpringBoot之自定义注解(基于AOP实现)

  • 通过AspectJ切面处理
    通过SspectJ切面编程实现,类似SPring AOP切面。而已没有只能作用在方法上的局限

  • 通过Spring配置类处理
    通过配置类实现在Spring启动过程中处理注解,配置Java反射机制来注入静态变量。
    局限性是只能注入静态属性。
    这也是本文所使用的方法。