模拟Spring框架之IOC和DI(一)

1.前言

什么是Spring IOC?DI?
IOC(Inversion of Control)控制反转控制反转不是一种新的技术而是一种设计思想。控制反转指的是创建对象的控制权反转了,以前是创建对象的主动权和创建时机是由自己把控的,该对象的依赖对象也需要手动去创建、注入,现在这个控制权交给了Spring容器,由Spring容器去管理,去创建对象,同时对象之间的依赖关系也没有了,他们都依赖于Spring容器,通过Spring容器去建立他们之间的关系;
DI(Dependency Injection)依赖注入,组件之间的依赖关系由容器在运行期间决定,即有容器动态的将某个依赖关系注入到组件中;依赖注入的目的不在于为软件系统提供更多的功能,它的主要目的在于提升组件重用的频度,并为软件搭建一个灵活,可扩展的平台,通过依赖注入,我们只需要简单的配置,不需要任何代码就可以指定目标资源,完成自身的业务逻辑,不需要关心具体的资源来自何处有谁实现。
上述概念来自:https://www.jianshu.com/p/a78880df6687

总之,IOC是一种思想,DI是一种设计模式,DI基于IOC容器的,也就是说依赖注入(DI)是对IOC这种设计思想的实现。

2.简单模拟Spring IOC

通过上面概念的讲述,我们对Spring IOC有了初步的了解,但可能你还是不理解具体是怎么样的,下面我们就用一个简单的例子介绍一下,请看代码:

先给出两个类OneClass和TwoClass:

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

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

}

public class TwoClass {

	public TwoClass() {
	}

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

再给出一个测试类:

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

}

测试结果为:null。这是必然的,也很简单,我们可以看到OneClass中的two成员没有进行初始化。如果想这么一种办法:对于OneClass类中的two成员,通过一种工具,自动地,悄悄地完成对two成员的初始化(注入),那么,Demo类的主函数执行结果就完美了!
接下来我们修改一下代码:

public static void main(String[] args) {
		OneClass one = new OneClass();
		Class<?> klass = OneClass.class;
		try {
			Field field = klass.getDeclaredField("two");
			field.setAccessible(true);
			field.set(one, new TwoClass());
			
			one.doOneThing();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}

这时我们可以看结果为:

这是一个TwoClass的对象

3.深入理解Spring IOC

通过上面的例子我们对依赖注入有了初步的认识,下面我们就来深入理解一下。首先我们考虑这样一种思路:将某应用所涉及的类及其对象,都集中存储到一个集合(池子)中;凡是在这个集合中的类,尤其是这些类的成员类型也在这个池子中,则这些成员的初始化都已池子中的对象还给予。从另一个角度思考上述问题:构建一个容器(上下文),在这个容器中存储类及其对象;在使用这些类的对象时,基本上都是从这个容器中获取的;这些类的成员,若其类型也在容器中,则,它们将被自动初始化,且用容器中的对象完成初始化。需要说明的是,对于类中的成员的初始化选择,应该由用户决定。
选择权的实现有两种具体的方法:
1、通过XML配置映射关系;
2、通过注解配置映射关系。
这里我们运用注解的方式进行配置。

先给出两个注解Component和Autowried:

@Retention(RUNTIME)
@Target(TYPE)
//该注解用在类上
public @interface Component {
	//这个方法是用来表示带有@Component注解的类是否是单例的,这里默认是单例的,具体由用户决定
	boolean singleton() default true;
}
@Retention(RUNTIME)
//该注解用在成员和方法上
@Target({ FIELD, METHOD })
public @interface Autowried {

}

这里要说明的是当Autowried注解用于方法时,则使用者应该保证使用在setter方法上,且该方法时public的,因为后续需要通过反射机制对该方法注入。setter方法有如下基本特征:1.一个参数;2.无返回值;当然也可以返回本类型对象。接下来我们继续用上面的OneClass和TwoClass类,对它们进行注解:

@Component
public class OneClass {
	@Autowried //这里需要注入
	private TwoClass two;
	
	public OneClass() {
	}
	
	@Autowried
	public void setTwo(TwoClass two) {
		this.two = two;
	}

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

@Component(singleton = false)
public class TwoClass {

	public TwoClass() {
	}

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

我们通过包扫描,扫描出带有@Component注解的类,将这些类及其对象存入到一个容器中。在构建容器之前,我们需要建立一个类来封装那些要存入容器中的类及其对象,以及它们是否是单例的,是否已经注入,方便以后使用。BeanDefinition类就是完成这个功能的。

BeanDefinition类:

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 this.singleton;
	}
	
	boolean isInject() {
		return this.inject;
	}
	//下面是成员的Getter和Setter方法,我就不贴出来了
}

下面就是IOC的重点部分,BeanFactory类(详细解释请看注释):

public class BeanFactory {
	//用于存放类及其对象的容器beanPool,以类名称为键,BeanDefinition为值来存放到一个Map中
	private static final Map<String, BeanDefinition> beanPool
			= new HashMap<String, BeanDefinition>();
	
	public BeanFactory() {
	}
	
	//这里运用了包扫描,详细情况请看上一篇文章Java包扫描(工具)
	//通过包扫描,扫描出带有@Component注解的类,将类及其对象存入beanPool中
	//如果该类是单例的,生成该类对象,否则先不生成,对象为null
	public static void scanPackage(String packageName) {
		new PackageScanner() {
			@Override
			public void dealClass(Class<?> klass) {
				/判断该类是否带@Component注解,若不是,不予处理
				if (klass.isPrimitive() //是八大基本类型
						|| klass == String.class //是String类
						|| klass.isAnnotation() //是注解类
						|| klass.isInterface() //是接口类
						|| klass.isEnum() //是枚举类
						|| klass.isArray() //是数组类
						|| !klass.isAnnotationPresent(Component.class)) { //不带@Component注解
					return;
				}
				try {
					Object object = null;
					Component component = klass.getAnnotation(Component.class);
					boolean singleton = component.singleton();
					
					BeanDefinition beanDefinition = new BeanDefinition();
					//若该类是单例的。则生成对象
					if (singleton) { 
						object = klass.newInstance();
					}
					//得到的类,对象以及是否为单例设置进去
					beanDefinition.setSingleton(singleton);
					beanDefinition.setKlass(klass);
					beanDefinition.setObject(object);
					//存入容器中
					beanPool.put(klass.getName(), beanDefinition);
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}.scanPackage(packageName);
		
	}
	
	//用来获得容器中类的对象
	public <T> T getBean(Class<?> klass) {
		//这里调用了以类名称为参数的getBean方法
		return getBean(klass.getName());
	}
	
	//用来对类的成员进行注入
	private void injectField(Class<?> klass, Object object) {
		//通过类来获取它的成员
		Field[] fields = klass.getDeclaredFields();
		//对成员进行遍历,如果带有@Autowried注解,进行注入;否则不予处理!
		for (Field field : fields) {
			if (!field.isAnnotationPresent(Autowried.class)) {
				//continue是跳出本次循环,return是跳出整个循环
				continue;
			}
			//获得成员的类类型
			Class<?> fieldType = field.getType();
			//调用getBean方法获得对象,这里用到了递归
			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 modify = method.getModifiers();//获得方法的修饰符
			//判断该方法是否为setter方法,修饰符是否为public。
			if (!methodName.startsWith("set")
					|| paraCount != 1
					|| !Modifier.isPublic(modify)
					|| !method.isAnnotationPresent(Autowried.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();
			}
		}
	}
	
	//用来注入成员和方法的,调用了上面两个方法(injectField和injectMethod方法)
	private void inject(BeanDefinition bean) {
		Object object = bean.getObject();
		Class<?> klass = bean.getKlass();
		injectField(klass, object);
		injectMethod(klass, object);
	}
	
	//获得容器中类的对象的,如果类是单例的,直接从容器中获取;
	//否则,生成一个对象,设置到bean中,存放到池子中。
	@SuppressWarnings("unchecked")
	public <T> T  getBean(String className) {
		BeanDefinition bean = beanPool.get(className);
		if (bean == null) {
			System.out.println(className + "不存在!");
		}
		Object object = null;
		if (bean.isSingleton()) {
			object = bean.getObject();
		} else {
			try {
				Class<?> klass = bean.getKlass();
				object = klass.newInstance();
				bean.setObject(object);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		
		if (!bean.isInject() || !bean.isSingleton()) {
			bean.setInject(true);
			//进行“注入”工作!
			inject(bean);
		}
		
		return (T) object;
	}
	
}

通过XML方式,也可以配置Bean和注入;XML方式能实现的,注解方式也能实现;
XML配置的优点:不侵害源代码,保证了“开闭原则”;
注解配置的优点:程序可读性强,无需额外代码(开发效率高)。

通过上述代码可以看出,我们将“注入”工作延迟到GetBean()时,而不是在包扫描时急于完成注入工作,这可以称之为懒汉模式!

接下来我们测试一下:

public static void main(String[] args) {
		BeanFactory.scanPackage("com.mec.mpring.demo");
		BeanFactory beanFactory = new BeanFactory();
		
		TwoClass two = beanFactory.getBean(TwoClass.class);
		System.out.println(two);
		
		OneClass one = beanFactory.getBean(OneClass.class);
		one.doOneThing();
		
		String str = beanFactory.getBean(String.class);
		System.out.println(str);
	}

测试结果为:

这是一个TwoClass的对象
这是一个TwoClass的对象
java.lang.String不存在!
Exception in thread "main" java.lang.NullPointerException
	at com.mec.mpring.core.BeanFactory.getBean(BeanFactory.java:120)
	at com.mec.mpring.core.BeanFactory.getBean(BeanFactory.java:58)
	at com.mec.mpring.test.Test.main(Test.java:20)

在包扫描时,不对String类进行处理,所以池子里没有这个类,结果输出java.lang.String不存在!也无法得到其对象,所以会报错!
由于篇幅原因,我们先说到这,后续处理请看《模拟Spring框架之IOC和DI(二)》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值