下面的文章内容是对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