模拟Spring实现方法注入

模拟Spring实现方法的注入

此篇是模拟Spring框架依赖注入的第一篇文章,主要是先实现对成员,方法的对应的类的注入工作。

基本思路:
将应用所涉及到的类及其对象,都存储到一个集合中,这个集合中的类对应的成员也存储在这个池子(集合)中,成员的初始化由集合中的类对象所给予;以后需要用到这些类的对象,直接从这个集合(池子)中取得。
也就是说,我们需要构建一个容器(上下文),这个容器中存储类及其对象,要用到时直接在容器中获取;若这些类的成员的类型也在容器中,则成员将被容器中的对象完成自动初始化。

首先提供两个注解,Compoent和Autowired注解:

@Retention(RUNTIME)
@Target(TYPE)
public @interface Compoent {
	boolean singleton() default true;
}
@Retention(RUNTIME)
@Target({ FIELD, METHOD })
public @interface Autowired {
}

Compoent注解作用于类,即凡是有Compoent注解的类,在之后的扫描中会将这个类及其对象存储到我们准备的容器中;Autowired注解作用于成员和方法,即凡是有Autowired注解的类成员或方法,若成员类型或方法参数类型也在容器中,我们需要对其进行成员的初始化或者方法的调用工作。

再者,提供两个类,满足我们需要验证的条件:

package com.mec.springioc.demo;

import com.mec.springioc.core.Autowired;
import com.mec.springioc.core.Compoent;

@Compoent
public class OneClass {
	@Autowired
	private TwoClass two;
	
	public OneClass() {
	}

	@Autowired
	public void setTwo(TwoClass two) {
		this.two = two;
	}
	
	public void doSomething() {
		System.out.println(two);
	}
	
}

package com.mec.springioc.demo;

import com.mec.springioc.core.Compoent;

@Compoent
public class TwoClass {
	
	public TwoClass() {
	}

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

从上述两个类的代码看出,我们希望OneClass和TwoClass两个类及其对象都被存储到容器中,并实现OneClass类的成员的注入,也可通过方法注入。

这时,就可以准备容器了,提供一个Map,已类名称为键,BeanDefinition为值,相应的一系列操作在BeanFactory类中完成。
这个BeanDefinition是类对应的对象和其他属性及其操作的一个封装类。

package com.mec.springioc.core;

public class BeanDefinition {
	private Class<?> klass;
	private Object object;
	private boolean singleton;  //这个类及其对象是否是“单例”的;
	private boolean inject;       //这个类及其对象是否已经注入容器;
	
	BeanDefinition() {
		this.singleton = true;
		this.inject = false;
	}

	boolean isSingleton() {
		return singleton;
	}

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

	boolean isInject() {
		return inject;
	}

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

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

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

	Object getObject() {
		return object;
	}

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

而对于容器,首先需要通过包扫描来填充这个“池子”,

	public static void scanPackage(String packageName) {
		new PackageScanner() {			
			@Override
			public void dealClass(Class<?> klass) {
				if (klass.isPrimitive()
						|| klass == String.class
						|| klass.isAnnotation()
						|| klass.isArray()
						|| klass.isInterface()
						|| klass.isEnum()
						|| !klass.isAnnotationPresent(Compoent.class)) {
					return;
				}
				//将这个类实例化,并放到classBeanPool中去;
				try {
					Object object = null;
					Compoent compoent = klass.getAnnotation(Compoent.class);
					boolean singleton = compoent.singleton();
					if (singleton) {
						object = klass.newInstance();
					}
					
					BeanDefinition beanDefinition = new BeanDefinition();
					beanDefinition.setSingleton(singleton);
					beanDefinition.setKlass(klass);
					beanDefinition.setObject(object);
					
					classBeanPool.put(klass.getName(), beanDefinition);
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}.scanPackage(packageName);;
	}

包扫描工具

这个方法的功能也很明确,扫描用户提供的包,将带有Compoent注解的类实例化,注入到容器中。当然,这是不完善的,比如对于不能加Compoent注解的类应该怎么做,由于只是对上述OneClass和TwoClass这两个简单类的注入,所以更加完善的操作将在后续文章中介绍。

提供供用户调用的两个方法,getBean,即,通过类名称获取类的对象:

	public <T> T getBean(Class<?> klass) {
		return getBean(klass.getName());
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getBean(String className) {
		BeanDefinition bean = classBeanPool.get(className);
		if (bean == null) {
			System.out.println("这个[" + className + "]不存在!");
			return null;
		}
		//这个Bean如果是单例的,直接获取object即可;
		//如果是非单例的,就需要重新set进去;
		Object object = null;
		if (bean.isSingleton()) {
			object = bean.getObject();
		} else {
			Class<?> klass = bean.getKlass();
			try {
				object = klass.newInstance();
				bean.setObject(object);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		
		//注入工作之所以放在getBean中,而不是包扫描之后,
		//是因为可能存在多次包扫描或者XML文件解析;
		//因此在没有扫描完之前,不能确定依赖关系的完整性;
		//在这里实现注入工作,是比较 “保险” 的;
		if (!bean.isInject() || !bean.isSingleton()) {
			bean.setInject(true);
			//这里完成对object需要注入的成员的初始化工作;
			inject(bean);
		}
		
		return (T) object;
	}

接下来就是完成容器中所包含的成员类型的成员初始化工作:

	private void inject(BeanDefinition bean) {
		Class<?> klass = bean.getKlass();
		Object object = bean.getObject();
		
		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;
			}
			Class<?> fieldType = field.getType();
			Object value = getBean(fieldType);
			field.setAccessible(true);
			try {
				field.set(object, value);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void injectMethod(Class<?> klass, Object object) {
		Method[] methods = klass.getDeclaredMethods();
		for (Method method : methods) {
			String methodName = method.getName();
			int paraCount = method.getParameterCount();
			int modifier = method.getModifiers();
			if (!methodName.startsWith("set")
					|| paraCount != 1
					|| !Modifier.isPublic(modifier)
					|| !method.isAnnotationPresent(Autowired.class)) {
				continue;
			}
			//对这个方法进行反射调用;
			Class<?> paraType = method.getParameterTypes()[0];
			Object value = getBean(paraType);
			
			try {
				method.invoke(object, new Object[] {value});
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		}
	}

这里需要说明的是,对于Autowired注解作用的方法实现类对象的初始化,这里为了简单起见做了一些限定:必须“set”开头,参数个数只能是一个并且参数类型在容器中,以及是public修饰的方法。

到这里,对于第一步类及其对象,以及成员和方法注入工作基本完成,根据前面提供的两个类,我们可以测试一下注入结果:

public class Test {

	public static void main(String[] args) {
		BeanFactory.scanPackage("com.mec.springioc.demo");
		BeanFactory beanFactory = new BeanFactory();
		OneClass one = beanFactory.getBean(OneClass.class);
		one.doSomething();
	}

}
com.mec.springioc.demo.OneClass
com.mec.springioc.demo.TwoClass
----------------------------
这是一个TwoClass类的对象!

因为我在BeanFactory中写了一个输出classBeanPool中所有类名称的方法,所以才有运行结果的前两行,而能够输出“这是一个TwoClass类的对象!”这句话,说明注入工作是成功的。

下一篇将继续完善模拟Spring实现依赖注入,以及Bean注解的引入。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值