概述
IOC(Inversion of Control)“控制反转”,不过更流行的叫法是“依赖注入”(DI - Dependency Injection)。
什么是“控制反转”呢?其实就是将控制权(创建对象和对象之间的依赖关系的权利)交给Spring容器。以前我们写代码的需要某个对象的时候直接使用 new XXXImpl();,有了Spring IOC容器之后,它负责对象的创建和依赖注入,当我们需要某个对象时直接跟Spring IOC容器要就好了。
IOC 听起来很高大上,其实实现起来并不复杂。本文主要介绍基于XML配置的方式来实现一个IOC容器,后面会有单独的一章介绍如何通过注解的方式来实现IOC容器。
用法
具体用法与Spring IOC类似,如下:
1、beans.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="userDao" class="com.ricky.ioc.sample.dao.UserDaoImpl" scope="singleton" init-method="init" ></bean>
<bean id="userService" class="com.ricky.ioc.sample.service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userController" class="com.ricky.ioc.sample.controller.UserController">
<property name="userService" ref="userService"></property>
</bean>
</beans>
2、添加maven依赖
<dependency>
<groupId>com.ricky.framework</groupId>
<artifactId>ioc</artifactId>
<version>1.0.0</version>
</dependency>
3、加载bean配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
//通过id获取Bean
UserController userController =(UserController)ctx.getBean("userController");
userController.login("ricky","123");
//通过Class获取Bean
UserService userService = ctx.getBean(UserService.class);
System.out.println(userService);
userService.login("ricky","abc");
ctx.close();
运行结果如下:
UserController login name->ricky,password->123
UserServiceImpl login name->ricky,password->123
UserDaoImpl find name->ricky
com.ricky.ioc.sample.service.UserServiceImpl@214c265e
UserServiceImpl login name->ricky,password->abc
UserDaoImpl find name->ricky
container close…
具体实现
思路:
解析beans.xml获取Bean列表以及相互之间的依赖关系,然后通过反射技术构造出Bean实例,并根据Bean之间的依赖关系进行Bean装配。
首先,看看ApplicationContext类,代码如下:
package com.ricky.framework.ioc;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.lang3.StringUtils;
import com.ricky.framework.ioc.model.BeanDefinition;
import com.ricky.framework.ioc.model.PropertyDefinition;
import com.ricky.framework.ioc.util.ReflectionUtils;
public abstract class ApplicationContext {
public abstract Object getBean(String id);
public abstract <T> T getBean(Class<T> clazz);
public abstract void close();
protected abstract BeanDefinition getBeanDefinition(String id);
protected Object createBean(BeanDefinition bd) {
try {
Object bean = ReflectionUtils.newInstance(bd.getClassName());
if(StringUtils.isNotEmpty(bd.getInitMethodName())){
ReflectionUtils.invokeMethod(bean,bd.getInitMethodName());
}
return bean;
} catch (ClassNotFoundException | InstantiationException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException("create bean error,class->"+bd.getClassName(),e);
}
}
protected void injectBeanProperties(Object bean,BeanDefinition beanDefinition){
try {
PropertyDescriptor[] ps = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
for(PropertyDefinition propertyDefinition : beanDefinition.getProperties()){
for(PropertyDescriptor propertyDescriptor:ps){
if(propertyDescriptor.getName().equals(propertyDefinition.getName())){
Method setter = propertyDescriptor.getWriteMethod();
setter.setAccessible(true);
setter.invoke(bean,getBean(propertyDefinition.getRef()));
}
}
}
} catch (SecurityException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| IntrospectionException e) {
throw new RuntimeException("inject bean properties error",e);
}
}
}
在这涉及到两个关键类:BeanDefinition 和 PropertyDefinition,它们分别是用来描述 javabean的定义和javabean 属性的定义,一个BeanDefinition 可以有1个或多个PropertyDefinition,它们之间是1:N的关系。代码如下:
BeanDefinition.java
package com.ricky.framework.ioc.model;
import java.util.List;
public class BeanDefinition {
private String id;
private String className;
private String scope; //singleton|prototype
private String initMethodName;
private List<PropertyDefinition> properties;
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 String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getInitMethodName() {
return initMethodName;
}
public void setInitMethodName(String initMethodName) {
this.initMethodName = initMethodName;
}
public List<PropertyDefinition> getProperties() {
return properties;
}
public void setProperties(List<PropertyDefinition> properties) {
this.properties = properties;
}
@Override
public String toString() {
return "BeanDefinition [id=" + id + ",className=" + className
+ ",scope=" + scope + ",initMethodName=" + initMethodName
+ ",properties=" + properties + "]";
}
}
PropertyDefinition.java
package com.ricky.framework.ioc.model;
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;
}
@Override
public String toString() {
return "PropertyDefinition [name=" + name + ",ref=" + ref + "]";
}
}
2、接下来是ClassPathXmlApplicationContext类,代码如下:
package com.ricky.framework.ioc;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import com.ricky.framework.ioc.model.BeanDefinition;
import com.ricky.framework.ioc.parser.BeanXmlConfigParser;
import com.ricky.framework.ioc.util.BeanScope;
import com.ricky.framework.ioc.util.ReflectionUtils;
public class ClassPathXmlApplicationContext extends ApplicationContext {
private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<String,BeanDefinition>();
protected Map<String,Object> beanInstanceMap = new HashMap<String,Object>();
public ClassPathXmlApplicationContext(String xmlFilePath) {
System.out.println("****************container init begin****************");
readXml(xmlFilePath);
initBeans();
injectBeans();
System.out.println("****************container init end****************");
}
private void readXml(String xmlFilePath) {
BeanXmlConfigParser beanXmlConfigParser = new BeanXmlConfigParser();
List<BeanDefinition> bean_def_list = null;
try {
bean_def_list = beanXmlConfigParser.parse(xmlFilePath);
} catch (FileNotFoundException e) {
throw new RuntimeException("not found bean xml,file->"+xmlFilePath,e);
} catch (DocumentException e) {
throw new RuntimeException("bean xml format error,e);
}
for (BeanDefinition beanDefinition : bean_def_list) {
if(StringUtils.isEmpty(beanDefinition.getId()) || StringUtils.isEmpty(beanDefinition.getClassName())){
throw new IllegalArgumentException("bean definition is empty!");
}
if (beanDefinitionMap.containsKey(beanDefinition.getId())) {
throw new IllegalArgumentException(
"duplicated bean id,id->"
+ beanDefinition.getId());
}
beanDefinitionMap.put(beanDefinition.getId(),beanDefinition);
}
}
private void initBeans() {
for (Map.Entry<String,BeanDefinition> me : beanDefinitionMap.entrySet()) {
BeanDefinition bd = me.getValue();
if(StringUtils.isEmpty(bd.getScope()) || bd.getScope().equals(BeanScope.SINGLETON)){
try {
Object bean = createBean(bd);
beanInstanceMap.put(bd.getId(),bean);
} catch (Exception e) {
throw new IllegalArgumentException("create bean error,class->"+bd.getClassName(),e);
}
}
}
}
private void injectBeans() {
for (Map.Entry<String,BeanDefinition> me : beanDefinitionMap.entrySet()) {
BeanDefinition beanDefinition = me.getValue();
//判断有没有注入属性
if (beanDefinition.getProperties() != null && beanDefinition.getProperties().size()>0) {
Object bean = beanInstanceMap.get(beanDefinition.getId());
try {
injectBeanProperties(bean,beanDefinition);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public Object getBean(String id) {
// System.out.println("get bean by id:"+id);
if (StringUtils.isEmpty(id)) {
return null;
}
if (beanDefinitionMap.containsKey(id)) {
BeanDefinition bd = beanDefinitionMap.get(id);
if(StringUtils.isEmpty(bd.getScope()) || bd.getScope().equals(BeanScope.SINGLETON)){
return beanInstanceMap.get(id);
}
Object bean = null;
try {
bean = createBean(bd);
injectBeanProperties(bean,bd);
beanInstanceMap.put(bd.getId(),bean);
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
throw new IllegalArgumentException("unknown bean,id->" + id);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getBean(Class<T> clazz) {
// System.out.println("get bean by type:"+clazz.getName());
for(Map.Entry<String,BeanDefinition> me : beanDefinitionMap.entrySet()){
BeanDefinition bd = me.getValue();
Class<?> beanClass = null;
try {
beanClass = ReflectionUtils.loadClass(bd.getClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if(beanClass!=null && clazz.isAssignableFrom(beanClass)){
// System.out.println("find bean by type,class->"+clazz.getName());
return (T) getBean(bd.getId());
}
}
return null;
}
@Override
protected BeanDefinition getBeanDefinition(String id) {
return beanDefinitionMap.get(id);
}
@Override
public void close() {
System.out.println("container close...");
// release resource
beanDefinitionMap.clear();
beanDefinitionMap = null;
beanInstanceMap.clear();
beanInstanceMap = null;
}
}
在ClassPathXmlApplicationContext类中,主要负责三大功能:解析XML配置文件、通过反射构建Bean实例以及对Bean进行装配。
小结
以上所有代码均已上传到GitHub上,欢迎大家fork。另外由于时间比较仓促,代码设计上有不合理的地方还请包涵,后面会抽时间对代码进行重构。