java反射模拟spring依赖注入

本文介绍了一种自定义依赖注入机制,通过反射、注解和配置文件实现类似于Spring的功能,包括面向接口编程、实例化管理及系统升级时的代码复用。通过实例演示了如何在类中注入依赖,以及如何使用注解来简化注入过程。

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

首先,这种模拟依赖注入所用的知识是反射。
其次,依赖注入有什么好处呢?他可以将创建实例的工作交给第三方完成,而不用交由调用方完成(这一般依靠配置文件来实现),这可以降低类与类之间的耦合度。
第三,仅仅将创建实例的工作交给第三方完成还不够。比如A调用B,A中始终包含一个B类的定义(B b),然后B的实例化由容器完成,将来如果我们需要用C类的功能替换B的功能,则需要修改A类的引用定义(C c),违反开闭原则。因此,对于一个可扩展、易维护的系统,应该尽量面向接口编程,将功能尽量抽象成方法,利用java的多态编程。
一般而言,系统升级时,B类和C类的实现功能是一致的,可能只是底层接口变动(如mysql数据库替换成oracle数据库),或jar包升级,或第三方接口变动,但A类需要的业务功能依然不变。所以系统设计之初,需要定义一个接口,接口中的方法覆盖A类所需的功能。然后B类实现接口I。此时A类调用B类时,可以写成(I i),然后向上转型,依靠容器将B的实例注入A中,类似:I i = new B();。当以后升级时,我们只需要定义一个类C实现接口I,然后修改配置文件,让C类成为I的引用对象即可。
最后,上面的过程其实就是spring的基本功能,面向接口编程,将类之间的实例化调用写在配置文件applicationContext.xml中,然后所有类的实例化由容器完成。

接下来,就开始实现依赖注入:
1.现在又两类类A、B,A类对B类完成调用,需要在A类中写:B b = new B();
我们想像spring一样只定义一个B b,然后生成b的get、set方法就能完成B的实例化,那么需要一个方法,传入A类本身,然后获得以set开头的方法,并调用此方法,完成,传入一个new B()实例,完成B的赋值。方法如下: 
public static void diObj(Object obj) {
		try {
			// 获取本类自定义的所有方法
			Method[] ms = obj.getClass().getDeclaredMethods();
			for (Method m : ms) {
				String mn = m.getName();
				// 如果方法名称以set开头
				if (mn.startsWith("set")) {
					// 截断方法名,获取properties中配置的dao名称
					mn = mn.substring(3);
					// 将首字母小写
					mn = mn.substring(0, 1).toLowerCase() + mn.substring(1);
					// 根据peoperties中配置的名称字符串
					String fullName = PropertiesUtil.getProperty(mn);
					// 通过字符串生成类实例
					Object instance = Class.forName(fullName);
					// 调用setXXX()方法完成实例的注入
					m.invoke(obj, instance);
				}
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
说明:因为通过字符串生成实例(Class.forName(name)),需要的name参数是类的全路径,所以我们需要将此全路径配置在properties文件中。并通过properties文件操作工具获取。(spring是写在xml中)
2.上面方法有了,如何注入呢?那就需要在每个需要注入的类中调用一下此方法,并将类本身传入。如在A中调用:diObj(this);
3.这种方法有一个问题,就是如果A类中还有其他的set方法,但是我们并不需要注入,就可能抛出异常。因此,我们需要在要自定义一个Annotation类,这个Annotation类标签,放在属性前,或放在set方法前都行(下面的栗子,放在set方法前),反正最终要实现的功能都一样:有Annotation的我们才注入。
1)定义一个Annotation,如下:
<span style="text-indent: 24px; white-space: pre; background-color: rgb(240, 240, 240);">package com.edu.mybatis.common.annotation;</span>

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface DaoDi {
	// String abc() default "";
	String value() default "";
}
2)修改diObj(Object obj)公共方法:
</pre><pre name="code" class="java">public static void diObj(Object obj) {
		try {
			// 获取本类自定义的所有方法
			Method[] ms = obj.getClass().getDeclaredMethods();
			for (Method m : ms) {
				String mn = m.getName();
				// 如果此方法上包含@DaoDi标签,返回true
				if (m.isAnnotationPresent(DaoDi.class)) {
					DaoDi daoDi = m.getAnnotation(DaoDi.class);
					// 获取DaoDi的value值
					String value = daoDi.value();
					// 如果annotation中传入参数(如:@DaoDi("userDao")),则表示注入userDao
					if (value != null && !"".equals(value)) {
						mn = value;
					}
					// 如果annotation不传参数(如:@DaoDi),则表示将setXXX方法的XXX名称最为注入对象
					else {
						// 截断方法名,获取properties中配置的dao名称
						mn = mn.substring(3);
						// 将首字母小写
						mn = mn.substring(0, 1).toLowerCase() + mn.substring(1);
					}
					// 根据peoperties中配置的名称字符串
					String fullName = PropertiesUtil.getProperty(mn);
					// 通过字符串生成类实例
					Object instance = Class.forName(fullName);
					// 调用setXXX()方法完成dao注入
					m.invoke(obj, dao);
				}
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

3)在setXXX方法上加上Annotation,如:
@DaoDi("addressDao")
    public void setAddrDao(IAddressDao addressDao) {
         this.addressDao = addressDao;
    }
三部完成,代码可以正常工作了。
4.有人可能会觉得这种注入,需要在每个类中初始化时都写上一句diObj(this)是一件非常无趣的事情。那么我们就需要根据具体业务具体分析了。如果是一个基本B/S项目,我们可能会分层,比如dao层、service层、action层等。以dao层为例:
我们本来就需要为dao抽象出一些公共的操作,并封装成一个dao的父类(这样做是必要的,以后更改dao接口也更方便,更灵活)。当业务中需要定义一个dao类时,我们就继承此dao父类。此时机会就出现了,我们将注入语句写在dao父类的构造方法中。那么凡是在dao需要注入的属性,在继承dao父类后,都会通过父类的构造方法完成。
而至于service层、action层也一样,我们需要为该层定义一个父类,并在构造方法中调用注入语句。



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值