探索java中的DI实现原理

本文介绍了DI(依赖注入)的概念,它是解决代码分层开发中解耦问题的关键。通过Java的反射和注解技术,展示了如何实现DI,并提供了一个简单的DI示例,包括注解的定义和使用,以及DI的执行过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是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的反射,我目前并没有想到其他简便的方法,如果有同学了解其他实现机制的,还请不吝赐教,在此感谢。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值