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(二)》