模拟Spring IoC(一)Component及Autowired注解

下面的文章内容是对Spring的IoC部分的简单模仿,但并不是完全一样,有很多的不足之处

依赖注入解释:

IoC(Inversion of Control)即控制反转,与依赖注入DI(Dependency Injection)意义相同。
下面举个例子说明一下什么是依赖注入:

public class TwoClass {
	
	public TwoClass() {
	}
	
	@Override
	public String toString() {
		return "这是一个TwoClass的对象";
	}
	
}

public class OneClass {
	private TwoClass two;
	
	public OneClass() {
	}

	public void doOneThing() {
		System.out.println(two);
	}
	
}

public class Demo {

	public static void main(String[] args) {
		OneClass one = new OneClass();
		one.doOneThing();
	}

}

Demo类的主函数执行结果,输出为null。这是必然的,因为OneClass类的two成员并没有初始化。
如果想这么一种办法:对于OneClass类中的two成员,通过一种工具,自动地完成对two成员的初始化(注入),那么,Demo类的主函数的结果就不再是null了!从外部对two成员的初始化过程就是注入过程。

分析

将某应用所涉及的类及其对象,都集中存储到一个集合(池子)中;凡是在这个集合中的类,尤其是这些类的成员类型也在这个池子中,则,需要注入的成员的初始化都可以从池子中的对象给予!

整理一下:整个过程就是构建一个容器(上下文),在这个容器中存储类及其对象;在使用这些类的对象时,基本上都是从这个容器中获取的;这些类的成员,若其类型也在容器中,则,它们将被自动初始化,且用容器中的对象完成初始化。需要说明的是,对于类中的成员的初始化选择,应该由用户决定。
在进行初始化时,需要确定被初始化成员,Spring提供了两种方式,注解XML文件配置,我选用注解方式进行下面的模拟。

注解

先给出三个注解,Compontent、Autowired、Bean

Compontent:作用于类上,主要用来标记要存放到容器中的类

Autowired:作用于成员或者setter方法上,用于标记需要被注入的成员,作用于setter方法也是为了可以标记需要被注入的成员。

Tip:当Autowired作用于成员上时,会破坏成员的安全性,此时成员的private权限就被破坏了,所以最好的方式是作用在对应的setter方法之上。

Bean:作用于方法上,用法之后会有例子专门解释

@Retention(RUNTIME)
@Target(TYPE)
public @interface Component {
	boolean singleton () default true; //是否单例
}

@Retention(RUNTIME)
@Target({ FIELD, METHOD })
public @interface Autowired {

}

@Retention(RUNTIME)
@Target(METHOD)
public @interface Bean {

}

对类及类类型等进行封装

根据上面的思路,要将类和对象进行封装处理

//封装类和对象
public class BeanDefinition {
	private Class<?> klass;
	private Object object;
	private boolean singleton;//判断是否单例
	private boolean inject;//判断是否被注入
	
	BeanDefinition() {
		this.inject = false;//默认未注入
		this.singleton = true;//默认单例
	}

	Class<?> getKlass() {
		return klass;
	}

	void setKlass(Class<?> klass) {
		this.klass = klass;
	}

	Object getObject() {
		return object;
	}

	void setObject(Object object) {
		this.object = object;
	}

	boolean isSingleton() {
		return singleton;
	}

	void setSingleton(boolean singleton) {
		this.singleton = singleton;
	}

	boolean isInject() {
		return inject;
	}

	void setInject(boolean inject) {
		this.inject = inject;
	}

}

将类和对象等存储到容器中

由于之后要将模拟Spring作为工具应用于其他项目,需要获取项目中的类,所以需要使用到包扫描技术,包扫描技术会通过扫描包,获取包中的类,在下面的方法中我会使用到曾经制作的包扫描小工具,在获取类之后,对其进行处理。

ps:包扫描工具只需要提供包名,即可获得包中的类,而该工具是一个抽象类,提供了一个抽象方法dealClass(),这个方法是提供给用户的,由他们在该方法中对获取的类进行处理。

public static final void scanPackage(String packageName) {
	new PackageScanner() {
		@Override
		public void dealClass(Class<?> klass) {
			//klass类若为八大基本类型、注解、数组、接口、String类,则直接返回
			//类上无Component注解直接返回
			if(klass.isPrimitive()			//八大基本类型
					|| klass.isAnnotation()	//注解类
					|| klass.isArray()		//数组
					|| klass.isInterface()	//接口
					|| klass == String.class //String类
					|| !klass.isAnnotationPresent(Component.class)) {
				return;
			}
			
			//完成了对有@Componnet注解的类的实例化,并将其放到BeanPool
				try {
					Object object = null;
					//如果存在Component注解,则返回@Component,否则为null。
					Component component = klass.getAnnotation(Component.class);
					boolean singleton = component.singleton();//@Component注解自定义的singleton()
					
					BeanDefinition beanDefinition = new BeanDefinition();
					if(singleton) {//如果单例的,直接实例化该类
						object = klass.newInstance();
					}
					
					//将该类的属性、类、对象使用beanDefinition封装起来
					beanDefinition.setSingleton(singleton);
					beanDefinition.setKlass(klass);
					beanDefinition.setObject(object);
					
					//以类名为键,beanDefinition对象为值存储到beanPool中
					BeanPool.put(klass.getName(), beanDefinition);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}.scanPackage(packageName);
}		

注入

要进行注入,有两个步骤。一是获取需要注入的Bean,二获取被注入的成员或setter方法。
那么什么时候是最好的注入时机呢?
答曰:为保证获取所有包的依赖关系,不能在包扫描之后就立刻进行注入。最佳注入时机:在使用的时候才进行注入,即执行getBean时进行注入 ,这就是所谓的懒汉模式

获取bean
private static BeanDefinition getBeanObject(String className) {
	//从beanPool中根据类名获取bean
	BeanDefinition bean = beanPool.get(className);
	if (bean == null) {
		return null;
	}
	Object object = null;
	//处理单例和非单例的情况
	//非单例的情况,根据类重新生成新的对象object
	//单例情况,直接返回bean
	if (!bean.isSingleton()) {
		Class<?> klass = bean.getKlass();
		try {
			object = klass.newInstance();
			bean.setObject(object);
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
	return bean;
}
进行注入

注入过程:将Compontent注解的且实例化的被放在beanPool中的bean通过getBean方法获取,再通过bean中的kalss反射机制获取成员或方法的类型,再使用getBean获取成员或setter方法的参数,将获取到的值注入到Aotuwired注解的成员或setter方法的参数

Autowired注解作用于需要注入的成员或者对应的setter方法之上,在获取到标志的成员之后再进行注入操作。这意味着注入时需要考虑两种情况,对成员注入,对方法注入。

public <T> T getBean(String className) {
	BeanDefinition bean = getBeanObject(className);
	if (bean == null) {
		showCircleDependency();//该方法是用于输出循环依赖,之后会给出具体实现
		System.out.println("Bean[" + className + "]不存在!");
		return null;
	}
	Object object = bean.getObject();
	
	//非单例(对象是新生成的)和未注入的情况都需要进行注入
	if(!bean.isInject() || !bean.isSingleton()) {
		//这里完成object中需要注入的成员的初始化工作!
		bean.setInject(true);//设置为已注入
		inject(bean);
	}
	
	return (T) object;
}
/*
	 * 获取Bean
	 */
	private static BeanDefinition getBeanObject(String className) {
		BeanDefinition bean = BeanPool.get(className);
		if (bean == null) {
			return null;
		}
		Object object = null;
		//处理单例和非单例的情况
		//非单例的情况,根据类重新生成新的对象object
		//单例情况,直接返回bean
		if (!bean.isSingleton()) {
			Class<?> klass = bean.getKlass();
			try {
				object = klass.newInstance();
				bean.setObject(object);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		
		return bean;
	}
private void inject(BeanDefinition bean) {
	//获取相关对象和类
	Object object = bean.getObject();
	Class<?> klass = bean.getKlass();
	
	injectField(klass,object);
	injectMethod(klass,object);
}
对成员进行注入
private void injectField(Class<?> klass,Object object) {
	//根据反射机制获取类的所有成员
	Field[] fields = klass.getDeclaredFields();
	for(Field field: fields) {
		if(!field.isAnnotationPresent(Autowired.class)) {
			continue;
		}
		//有注解的类
		//先获取成员的类型,再根据类型从beanFactory中获取存放的该类型的对象
		Class<?> fieldClass = field.getType();
		Object value = getBean(fieldClass);
		field.setAccessible(true);
		try {
			//把需要注入的内容(value)“set”到field,object是field成员所属类的对象
			field.set(object, value);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
}

上述代码中存在一个递归,Object value = getBean(fieldClass),这里之所以有递归是因为:要对成员进行注入,那么就需要获取该成员所属类的相关信息,而所属类极有可能也需要先被注入,这样才能获取到,所以此处有递归。

对方法进行注入

需先判断该方法是否为setter方法
setter方法特征:
1、只有一个参数;
2、无返回值;当然也可以是返回本类型对象

private void injectMethod(Class<?> klass,Object object) {
	Method[] methods = klass.getDeclaredMethods();
	for(Method method:methods) {
		String methodName = method.getName();
		int parameterCount = method.getParameterCount();//获取参数个数
		int modify =  method.getModifiers();//获取方法权限
		
		//以"set"开头;权限为public;只有1个参数;以Autowired为注解
		if(!methodName.startsWith("set") 
				|| !Modifier.isPublic(modify)
				|| parameterCount != 1
				|| !method.isAnnotationPresent(Autowired.class)){
			continue;
		}			
			
		//因为方法只有一个参数,所以直接取下标为0的即可		
		Class<?> paraType =  method.getParameterTypes()[0];
		Object value = getBean(paraType);
		
		try {
			//虽然只有一个参数,new Object[]{}为了程序格式
			method.invoke(object, new Object[] {value});
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
}

这篇文章重点写了Component注解及Autowired注解的相关东西,下一篇文章写Bean注解相关的东西
全部代码请跳转–>GitHub https://github.com/Arrvine/mspring-IoC

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值