什么是DI
DI是Dependency Injection的简称,翻译过来就是“依赖注入”。熟悉java开发的同学都会接触到这个概念,在spring、guice等框架中均是一个基本概念。依赖注入所作的事情就是,在程序运行时为类的变量赋予实例。DI解决了代码分层开发,层次之间的代码解耦问题。与DI相伴生的概念是IOC,他们是同一事物的不同方面的阐述。
如何实现DI
如果我们编写的代码中,没有为类的变量创建实例,那么就要想办法在程序运行时为变量赋值。我目前能想到的方法就是利用java的reflect机制,利用反射在类加载过程中为变量赋值。对于一个项目工程,我们设计时知道哪些类的变量需要注入实例,而选择在类加载时为其创建指定的实例并赋值给特定的变量,但是这样会出现因人而异,导致代码设计的混乱,代码的质量太差而且不可复用。如何让程序自己知道哪些类的哪些变量需要进行DI,最好的方法就是做标记,java提供的Annotation技术能够很好的完成这个任务。利用注解,我们可以为变量打上标记,java程序能够识别这些注解,java的反射机制提供了获取注解的API,我们可以很好利用这点。利用java的反射和注解技术,我们可以在程序运行时对指定的package路径下的被标记的一些类进行加载,在加载过程中创建实例对象赋给被标记的变量。这种方式,将其完善就可以复用,其就形成一套框架技术。
示例
简单的代码模拟实现java的DI。
简单的注解:
@Target(ElementType.TYPE)
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
}
不了解java注解的同学,这里需要知道两个关键注解:
- @Target注解表示注解的作用范围,它可以是类型、方法、字段等等,可以查看java.lang.annotation.ElementType这个API,查看其允许的所有范围。
- @Retention注解表示注解的保留时长,它有三种策略SOURCE、CLASS、RUNTIME,分别表示:只存在于源码中,编译时丢弃;编译时保留,jvm运行时丢弃;保留至程序运行时。默认是CLASS策略。我们要利用注解,在程序运行时为变量注入实例,因此注解要保留至程序运行时,所以策略一定选择RUNTIME。
注解的使用:
//简单的接口
public interface People {
public void whereIFrom();
}
//简单的实现
@MyComponent
public class Chinese implements People {
@Override
public void whereIFrom() {
System.out.println("I'm from China.");
}
}
//需要DI的地方
public class Printer {
@Inject
private People p;
public void print() {
p.whereIFrom();
}
}
从上面代码可以看到,Printer类的成员p没有创建实例,在print方法中我使用到了变量p,这里p就需要注入一个实例,程序才能正确运行。@Inject注解是JDK提供的注解,它已经被spring及guice等框架所使用。被@Inject注解标示的字段、方法、构造函数,其变量或参数的值是需要运行时注入的。
DI的简单实现:
public class Test {
//存放创建好的实例对象
private static List<Object> instances = new ArrayList<Object>();
public static void main(String[] args) {
try {
/*扫描指定的package路径下的class,
对使用了MyComponent注解的类创建实例,
并将其存入instances列表中保存在内存以供使用。
这里使用了guava的api。*/
ClassPath cp = ClassPath.from(Test.class.getClassLoader());
ImmutableSet<ClassInfo> set = cp.getTopLevelClasses("com.sun.app.sample");
for(ClassInfo ci : set) {
Class c = Class.forName(ci.getName());
MyComponent mc = (MyComponent)c.getAnnotation(MyComponent.class);
if(mc != null) {
instances.add(c.newInstance());
}
}
Printer p = new Printer();
//为p对象里的成员变量p注入实例
Class printerC = p.getClass();
Field[] fields = printerC.getDeclaredFields();
for(Field field :fields) {
Inject ij = field.getAnnotation(Inject.class);
if(ij != null) {
Class fieldC = field.getType();
if(fieldC.isInterface()) {
field.setAccessible(true);
//从内存中instance列表中找到People接口的实现类的实例对象
for(Object o:instances) {
Class[] ifs = o.getClass().getInterfaces();
for(Class i:ifs) {
if(i.equals(fieldC)) {
//注入实例
field.set(p, o);
break;
}
}
}
}
}
}
p.print();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行程序,验证效果:
至此,一个简单的DI实现示例就完成了。除了java的反射,我目前并没有想到其他简便的方法,如果有同学了解其他实现机制的,还请不吝赐教,在此感谢。