Spring框架在项目中可以说是非常常见了,一直以来都只停留在会用的程度上,对原理、概念都了解的很模糊,因此很想找个机会好好梳理一下。
在Spring中有几个比较常见的概念,先理清楚,将会对我们理解框架有非常大的帮助。
一、依赖注入和控制反转
如果稍微有接触过spring,应该知道依赖注入(Inversion of Control,IOC)是Spring的核心机制,控制反转(Dependency Injection)往往会被同时提及,这两者是什么含义,有什么区别呢?
首先,理解一下什么是依赖。
我们知道Java是一门面向对象的语言,在那些年我们又枯燥又彷徨、有时却又有点小骄傲的编程过程中,应该会经常遇到A对象中调用B对象的情形,这种情形就被Spring称之为依赖关系,即A对象依赖B对象。这种互相调用的关系,就是Spring中所说的依赖关系。
这种时候本不必举个栗子,但是还是……举一个吧。
现在有一个Food类
package com.yolanda.fun.entity;
public class Food {
public String print() {
return "It is delicious.";
}
}
有一个Person类,调用了Food类
package com.yolanda.fun.entity;
public class Person {
private Food food;
public Food getFood() {
System.out.println("I want to eat something.");
System.out.println(food.print());
return food;
}
public void setFood(Food food) {
this.food = food;
}
}
好,这两段代码中并没有创建一个实例。如果没有用Spring框架,可能会怎样做呢?
如果答案是new一个的话,下面将引用书中的一段原话
“如果读者发现自己还在经常采用这种方式创建Java对象,那表明你对Java掌握的相当不够”。
嘿嘿(邪魅一笑)
这样调用者直接使用new关键字创建被调用对象(被依赖对象)的Java实例,程序会高度耦合。
那如何才能解除这种大量存在又无法避免的高度耦合现象呢?噔噔噔噔~~~这就是Spring的价值所在啦。
上述的这种原始的方式,是调用者主动获取被依赖的对象,而使用Spring框架,调用者只要被动的接受Spring容器为调用者的成员变量赋值。调用者获取被依赖对象的方式从原来的主动获取,变成了被动接受,这个概念就称为控制反转。
用一种更容易理解的说法就是,主动创建实例的原始方式,就好像我们人类饿了就去寻找食物,打猎呀、摘果子呀、点外卖呀,用Spring框架就是一种理想的共产主义,饿了,Spring容器就把食物发给你。
而从Spring容器的角度来看,Spring容器把被依赖对象赋值给调用者的成员变量,简单来说,就是为调用者注入它依赖的对象的实例,这个概念就称为依赖注入。
说到这里就豁然开朗了,有木有!有木有!其实依赖注入和控制反转的含义是完全相同的,只是两个人取了不一样的名字而已。
(还有一种工厂模式比直接new的方式好点,我先懒得写,后面补,明天就补)
二、Bean
Bean就是Spring中的一个基本单元了,在Spring当中,所有的Java对象都是Bean,注意不是Java Bean。
Spring容器就是管理这些Bean之间的关系。
三、注入
前面说了那么多概念,Spring容器是怎么来管理Bean的呢?
自问自答~~~
Spring用XML配置文件来管理Bean.
下面介绍一种一秒懒人快速创建Spring项目的方法,连需要什么jar包都不用管
方法就是!
——直接在eclipse里新建一个Spring Project.哈哈哈,让我叉会腰先~~~
好,继续~
项目里自动生成了一个spring配置文件,application-config.xml,那我们来注入这两个bean。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="person" class="com.yolanda.fun.entity.Person">
<property name="food" ref="food"></property>
</bean>
<bean id="food" class="com.yolanda.fun.entity.Food"></bean>
</beans>
下面就来解释一下,上面的配置文件做了些啥~
在配置文件中,Spring配置Bean实例通常会指定两个属性
一个是id,id是Bean的唯一标识,Spring根据此id值来管理Bean,程序通过id值来访问Bean实例。
另一个就是class,class指定了Bean的实现类,而且这个实现类不能是接口,必须到接口的实现类,Spring会使用XML解析器读取这个值,然后通过反射来创建该实现类的实例。
<bean id="person"
class="com.yolanda.fun.entity.Person">
这一句代码,声明了一个名为person的Bean,它的实现类是
com.yolanda.fun.entity.Person
这个class属性一定要带上完整的包名。
Spring的底层解析代码
String idStr = "Person";
String classStr = "com.yolanda.fun.entity.Person";
Class clazz = Class.forName(classStr);
Object obj = clazz.newInstance();
// Spring容器container
container.put(idStr, obj);
就是说Spring通过反射根据class指定的类名创建了一个Java对象,以id值作为key,放到了容器里。这个Java对象就是Spring容器里的一个Bean。
每个<Bean.../>
元素默认驱动Spring容器调用这个类的无参数构造器来创建实例,并将该实例作为Spring容器中的Bean.
同理下面这句话就是把名为food的Java对象放在容器里啦。
<bean id="food" class="com.yolanda.fun.entity.Food"></bean>
那再来解释这个啦,
<bean id="person" class="com.yolanda.fun.entity.Person">
<property name="food" ref="food"></property>
</bean>
这个的Spring底层就是酱紫滴~
String nameStr = "food";
String refStr = "food";
String setterName = "set" + nameStr.substring(0,1).toUpperCase() + name.substring(1);// setFood
// 这一步是把刚才放进去的Bean取出来
Object paramBean = container.get(refStr);
// 这个clazz上一段解释过啦
Method setter = clazz.getMethod(setterName, paramBean.getClass());
// 这个obj就是前面那段的obj
setter.invoke(obj, paramBean);
每个<property.../>
元素默认驱动Spring调用一次setter方法。
四、测试
最后再写个test类测试一下
public class TestSpring {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/spring/application-config.xml");
Person person = ctx.getBean("person", Person.class);
person.getFood();
}
}
输出:
I want to eat something.
It is delicious.