22.6 注解的应用:DI容器

22.6 注解的应用:DI容器

我们再来看一个简单的DI容器的例子。我们引入两个注解:一个是@SimpleInject;另一个是@SimpleSingleton,先来看@SimpleInject。

1. @SimpleInject

引入一个注解@SimpleInject,修饰类中字段,表达依赖关系,定义为:

1
2
3
4
@Retention(RUNTIME)
@Target(FIELD)
public @interface SimpleInject {
}

我们看两个简单的服务ServiceA和ServiceB, ServiceA依赖于ServiceB,它们的定义如代码清单22-1所示。

代码清单22-1 两个简单的服务ServiceA和ServiceB
1
2
3
4
5
6
7
8
9
10
11
12
public class ServiceA {
@SimpleInject
ServiceB b;
public void callB(){
b.action();
}
}
public class ServiceB {
public void action(){
System.out.println("I'm B");
}
}

ServiceA使用@SimpleInject表达对ServiceB的依赖。

DI容器的类为SimpleContainer,提供一个方法:

1
public static <T> T getInstance(Class<T> cls)

应用程序使用该方法获取对象实例,而不是自己new,使用方法如下所示:

1
2
ServiceA a = SimpleContainer.getInstance(ServiceA.class);
a.callB();

SimpleContainer.getInstance会创建需要的对象,并配置依赖关系,其代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static <T> T getInstance(Class<T> cls) {
try {
T obj = cls.newInstance();
Field[] fields = cls.getDeclaredFields();
for(Field f : fields) {
if(f.isAnnotationPresent(SimpleInject.class)) {
if(! f.isAccessible()) {
f.setAccessible(true);
}
Class<? > fieldCls = f.getType();
f.set(obj, getInstance(fieldCls));
}
}
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

代码假定每个类型都有一个public默认构造方法,使用它创建对象,然后查看每个字段,如果有SimpleInject注解,就根据字段类型获取该类型的实例,并设置字段的值。

2. @SimpleSingleton

在上面的代码中,每次获取一个类型的对象,都会新创建一个对象,实际开发中,这可能不是期望的结果,期望的模式可能是单例,即每个类型只创建一个对象,该对象被所有访问的代码共享,怎么满足这种需求呢?我们增加一个注解@SimpleSingleton,用于修饰类,表示类型是单例,定义如下:

1
2
3
4
@Retention(RUNTIME)
@Target(TYPE)
public @interface SimpleSingleton {
}

我们可以这样修饰ServiceB:

1
2
3
4
5
6
@SimpleSingleton
public class ServiceB {
public void action(){
System.out.println("I'm B");
}
}

SimpleContainer也需要做修改,首先增加一个静态变量,缓存创建过的单例对象:

1
private static Map<Class<? >, Object> instances = new ConcurrentHashMap<>();

getInstance也需要做修改,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static <T> T getInstance(Class<T> cls) {
try {
boolean singleton = cls.isAnnotationPresent(SimpleSingleton.class);
if(! singleton) {
return createInstance(cls);
}
Object obj = instances.get(cls);
if(obj ! = null) {
return (T) obj;
}
synchronized (cls) {
obj = instances.get(cls);
if(obj == null) {
obj = createInstance(cls);
instances.put(cls, obj);
}
}
return (T) obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

首先检查类型是否是单例,如果不是,就直接调用createInstance创建对象。否则,检查缓存,如果有,直接返回,如果没有,则调用createInstance创建对象,并放入缓存中。

createInstance与第一版的getInstance类似,代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static <T> T createInstance(Class<T> cls) throws Exception {
T obj = cls.newInstance();
Field[] fields = cls.getDeclaredFields();
for(Field f : fields) {
if(f.isAnnotationPresent(SimpleInject.class)) {
if(! f.isAccessible()) {
f.setAccessible(true);
}
Class<? > fieldCls = f.getType();
f.set(obj, getInstance(fieldCls));
}
}
return obj;
}

本章介绍了Java中的注解,包括注解的使用、自定义注解和应用示例,示例的完整代码在github上,地址为https://github.com/swiftma/program-logic ,位于包shuo.laoma.dynamic. c85下。

注解提升了Java语言的表达能力,有效地实现了应用功能和底层功能的分离,框架/库的程序员可以专注于底层实现,借助反射实现通用功能,提供注解给应用程序员使用,应用程序员可以专注于应用功能,通过简单的声明式注解与框架/库进行协作

下一章,我们来探讨Java中一种更为动态灵活的机制:动态代理。