模拟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注解的引入。