一、什么是依赖注入
所谓依赖注入通常是指在运行期,由spring容器动态地将依赖对象注入到应用中。当spring容器启动后,spring容器初始化,创建并管理bean对象,以及销毁它。应用本身是不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的。这样控制权就由应用转移到了spring容器,控制权的转移就是所谓的反转。所以,我们常常看到 IOC(Inverse of Control)翻译为“控制反转”,但大多数人都习惯将它称为“依赖注入”。
二、为什么需要使用依赖注入
首先,在平时的编程中如果想要实现代码之间的松耦合可能我们的通常的想法是通过分层架构来实现代码之间的耦合。其实上分层架构并没有实现彻底的松耦合,下面我们来看一个比较常见的示例:
public class ProductServiceImpl implements ProductService{
ProductDao productDao;
public void setProductsDao() {
this.productDao = new ClothesDao(); //此处产生紧耦合
}
@Override
public List<Product> getLatest() {
return productDao.getLatest();
}
在上述的程序代码的段的注释中标注在程序中产生了紧耦合或许你会比较困惑,下面让我们来看整个分层架构中的各层之间的依赖关系:
由上面的依赖关系图可以看出,所有的层都依赖了数据访问层,如果修改数据访问层,则领域逻辑层,和表现层都需要进行相应的修改。这将是多么令人痛苦的噩梦。但是,不要太过于悲观,因为Spring 已经很好的为我们解决上面的困扰。
三、依赖注入的实现原理是什么
Spring的IOC的实现原理利用的就是Java的反射机制,Spring还充当了工厂的角色,我们不需要自己建立工厂类。Spring的工厂类会帮我们完成配置文件的读取、利用反射机制注入对象等工作,我们可以通过bean的名称获取对应的对象。为了更好的理解Spring容器中依赖注入的原理,下面我们参考Spring容器源码实现一个自己的Spring容器。
1、定义一个PropertyDefinition类 ,描述配置xml 中property 属性
public class PropertyDefinition {
private String name;
private String ref;
public PropertyDefinition(String name, String ref) {
this.name = name;
this.ref = ref;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
}
2、定义一个 BeanDefinition类 ,用于描述XML文件中Bean
class BeanDefinition {
private String id;
private String className;
//通过一个集合,来存放property的信息
private List<PropertyDefinition> propertys = new ArrayList<PropertyDefinition>();
public BeanDefinition(String id, String className) {
this.id = id;
this.className = className;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public List<PropertyDefinition> getPropertys() {
return propertys;
}
public void setPropertys(List<PropertyDefinition> propertys) {
this.propertys = propertys;
}
}
3、实现XML配置文件的读取,用反射机制初始化实例对象,为bean 的属性对象注入值
/**
* 实现一个自己的Spring容器
*/
public class MyClassPathXMLApplicationContext {
private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();
// 存放bean实例
private Map<String, Object> sigletons = new HashMap<String, Object>();
public MyClassPathXMLApplicationContext(String filename) {
// 模拟内部的实现,首先要读取配置文件,可以用dom4j
this.readXML(filename);
// 读取完bean之后,Spring要对bean进行实例化,怎么实现实例化呢? 通过反射机制就很容易做到
this.instanceBeans();
this.injectObject();
}
/**
* 实现bean的实例化
*/
private void instanceBeans() {
for (BeanDefinition beanDefinition : beanDefines) {
try {
if (beanDefinition.getClassName() != null
&& !"".equals(beanDefinition.getClassName().trim()))
sigletons.put(beanDefinition.getId(), Class.forName(
beanDefinition.getClassName()).newInstance());
} catch (Exception e) {
// 通过反射技术把bean都创建出来
e.printStackTrace();
}
}
}
/**
* 为bean对象的属性注入值
*/
private void injectObject() {
for (BeanDefinition beanDefinition : beanDefines) {
Object bean = sigletons.get(beanDefinition.getId());
if (bean != null) {
try {
PropertyDescriptor[] ps = Introspector.getBeanInfo(
bean.getClass()).getPropertyDescriptors();
//Introspector通过这个类可以取得bean的定义信息
for (PropertyDefinition propertyDefinition : beanDefinition
.getPropertys()) {
for (PropertyDescriptor properdesc : ps) {
if (propertyDefinition.getName().equals(properdesc.getName())) {
Method setter = properdesc.getWriteMethod();// 获取属性的setter方法
// ,private
if (setter != null) {//属性可能没有set方法,所以这里要判断一下
Object value = sigletons
.get(propertyDefinition.getRef());
setter.setAccessible(true);//如果set方法是私有的话,要设置它允许被访问
setter.invoke(bean, value);// 把引用对象注入到属性
}
break;
}
}
}
} catch (Exception e) {
}
}
}
}
/**
* 读取xml配置文件
*/
private void readXML(String filename) {
SAXReader saxReader = new SAXReader();
Document document = null;
try {
URL xmlpath = this.getClass().getClassLoader()
.getResource(filename);
document = saxReader.read(xmlpath);
Map<String, String> nsMap = new HashMap<String, String>();
nsMap.put("ns", "http://www.springframework.org/schema/beans");// 加入命名空间
XPath xsub = document.createXPath("//ns:beans/ns:bean");// 创建beans/bean查询路径
xsub.setNamespaceURIs(nsMap);// 设置命名空间
@SuppressWarnings("unchecked")
List<Element> beans = xsub.selectNodes(document);// 获取文档下所有bean节点
for (Element element : beans) {
String id = element.attributeValue("id");// 获取id属性值
String clazz = element.attributeValue("class"); // 获取class属性值
BeanDefinition beanDefine = new BeanDefinition(id, clazz);
XPath propertysub = element.createXPath("ns:property");
propertysub.setNamespaceURIs(nsMap);// 设置命名空间
List<Element> propertys = propertysub.selectNodes(element);
for (Element property : propertys) {
String propertyName = property.attributeValue("name");
String propertyref = property.attributeValue("ref");
System.out.println(propertyName + " = " + propertyref);
PropertyDefinition propertyDefinition = new PropertyDefinition(
propertyName, propertyref);
beanDefine.getPropertys().add(propertyDefinition);
}
beanDefines.add(beanDefine);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取bean实例
*/
public Object getBean(String beanName) {
return this.sigletons.get(beanName);
}
}
四、依赖注入的实现方式有那些
在spring学习中我们通常用的依赖注入方式主要为:构造方法注入 和Set方法注入。下面我们来详细了解一下这两种注入方式。
1、构造方法注入
public class ProductSevice{
private String productName;
publicUser(String productName) {
this.productName=productName;
}
}
XML 配置文件中bean
<bean id="productSevice"class="com.ywendeng.spring.action.ProductSevice">
<constructor-argvalue constructor-argvalue="productName"></constructor-arg>
</bean>
备注:当构造函数中有两个参数时,需要用index指定参数顺序 例如:
<constructor-argvalue constructor-argvalue="productName" index="0">
2、set方法注入
分别为对象赋值和普通属性赋值的set注入示例
public class GoodsAction extends BaseAction{
GoodsService goodsService;
private String goodsName;
public void setGoodsService(GoodsService goodsService) {
this.goodsService = goodsService;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
}
为了实现上述的注入方法,对应XML配置文件中Bean的配置方式
<bean id="goodsAction" class="com.ywendeng.ecs.action.GoodsAction" scope="prototype">
<property name="goodsService" ref="goodsService"></property>
<property name="goodsName" value="apple"></property> //在实际开发中普通属性值,一般都是由用户提交的请求来设置
</bean>