概述
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。另外由于时间比较仓促,代码设计上有不合理的地方还请包涵,后面会抽时间对代码进行重构。