我要造轮子之IoC和依赖注入

简介: <div class="markdown_views"><h1 id="1前言">1、前言</h1><p>因为这是我设想要写的一系列文章的第一篇。所以我先说明一下我为什么要重复造轮子。</p><p>在这里造轮子的目的不是为了造出比前人更出色的轮子来,而是通过造轮子,学习轮子内部的结构及相关原理。甚至去模仿前人轮子上的优点,吸收这些优点。</p><p>这一系列文章初

1、前言

因为这是我设想要写的一系列文章的第一篇。所以我先说明一下我为什么要重复造轮子。

在这里造轮子的目的不是为了造出比前人更出色的轮子来,而是通过造轮子,学习轮子内部的结构及相关原理。甚至去模仿前人轮子上的优点,吸收这些优点。

这一系列文章初步估计应该包括:IoC和依赖注入、AOP、ORM、Servlet容器(tomcat)等。

2、IoC和依赖注入的概念

Inverse of Control,控制反转。

IoC主要功能是依赖关系的转移。应用的本身不负责依赖对象的创建和维护,而是由第三方容器负责。控制权就由应用转移到了外部容器。

IoC的主要功能由控制反转来解释并不是很好理解。所以提出了新的概念Dependency Injection.

DI依赖注入,调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以转移对某一接口实现类的依赖。也就是在运行期,由外部容器动态地将所依赖的对象注入到组件中去。

最常用的IoC和DI框架没有之一:Spring。
Spring IoC的介绍和使用: http://blog.csdn.net/jrainbow/article/details/9091577

3、源码结构

本文中使用的是通过注解(annotation)的方式来对需要进行IoC和DI的类进行管理。

这里写图片描述
我要看大图:http://img.blog.csdn.net/20160218111901359

4、实现过程

4.1 工具类

首先我们需要两个工具类:类加载器ClassLoader 和反射工厂类:

  • 类加载器
    此类加载器通过getClassSet(String packageName)方法获取到packageName路径下及Jar包中的类的集合
package xyz.letus.framework.ioc;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 类加载器
 * @ClassName: ClassLoader
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月14日
 *
 */
public class ClassLoader {
    public static final Logger LOGGER = LoggerFactory.getLogger(ClassLoader.class);

    /**
     * 获取线程上的类加载器
     * @Title: getClassLoader
     * @Description: TODO
     * @param @return    
     * @return java.lang.ClassLoader    
     * @throws
     */
    public static java.lang.ClassLoader getClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }
    /**
     * 加载类
     * @Title: loadClass
     * @Description: TODO
     * @param @param className
     * @param @param isInitialized
     * @param @return    
     * @return Class<?>    
     * @throws
     */
    public static Class<?> loadClass(String className,boolean isInitialized){
        Class<?> clazz = null;
        try {
            clazz = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException e) {
            LOGGER.error("load class failure", e);
            throw new RuntimeException(e);
        }

        return clazz;
    }
    /**
     * 获取包下所有的类
     * @Title: getClassSet
     * @Description: TODO
     * @param @param packageName
     * @param @return    
     * @return Set<Class<?>>    
     * @throws
     */
    public static Set<Class<?>> getClassSet(String packageName){
        Set<Class<?>> classes = new HashSet<Class<?>>();

        try {
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));

            while(urls.hasMoreElements()){
                URL url = urls.nextElement();
                if(url != null){
                    String protocol = url.getProtocol();
                    if(protocol.equals("file")){
                        String packagePath = url.getPath().replace("%20", " ");
                        addCommonClass(classes, packagePath, packageName);
                    }else if(protocol.equals("jar")){
                        addJarClasses(classes, url);
                    }
                }
            }

        } catch (IOException e) {
            LOGGER.error("get class set failure", e);
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        return classes;
    }
    /**
     * 把jar包中所有的类加入集合
     * @Title: addJarClasses
     * @Description: TODO
     * @param @param classes
     * @param @param url
     * @param @throws IOException    
     * @return void    
     * @throws
     */
    private static void addJarClasses(Set<Class<?>> classes, URL url)
            throws IOException {
        JarURLConnection connetion = (JarURLConnection) url.openConnection();
        if(connetion != null){
            JarFile jar = connetion.getJarFile();
            if(jar != null){
                Enumeration<JarEntry> enties = jar.entries();
                while(enties.hasMoreElements()){
                    JarEntry entry = enties.nextElement();
                    String entryName = entry.getName();
                    if(entryName.endsWith(".class")){
                        String className = entryName.substring(0, entryName.lastIndexOf('.')).replaceAll("/", ".");
                        doAddClass(classes, className);
                    }
                }
            }
        }
    }

    /**
     * 把文件夹中的所有类加入集合
     * @Title: addCommonClass
     * @Description: TODO
     * @param @param classes
     * @param @param packagePath
     * @param @param packageName    
     * @return void    
     * @throws
     */
    private static void addCommonClass(Set<Class<?>> classes,String packagePath,String packageName){
        File[] files = new File(packagePath).listFiles(new FileFilter() {

            public boolean accept(File file) {
                // TODO Auto-generated method stub
                return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
            }
        });

        for(File file : files){
            String fileName = file.getName();
            if(file.isFile()){
                String className = packageName + '.' +  fileName.substring(0, fileName.lastIndexOf('.'));
                doAddClass(classes, className);
            }else{
                String subPackagetPath = packagePath + "/" + fileName;
                String subPackagetName = packageName + "/" + fileName;
                addCommonClass(classes, subPackagetPath, subPackagetName);
            }
        }
    }
    /**
     * 加入类到集合
     * @Title: doAddClass
     * @Description: TODO
     * @param @param classes
     * @param @param className    
     * @return void    
     * @throws
     */
    private static void doAddClass(Set<Class<?>> classes,String className){
        classes.add(loadClass(className, false));
    }
}
  • 反射工厂

    此工厂的功能是:
    1、在运行期创建类对象
    2、运行期为对象的属性赋值
    3、运行期调用对象的方法

package xyz.letus.framework.ioc;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 反射工厂
 * @ClassName: ReflectionFactory
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月19日
 *
 */
public class ReflectionFactory {
    public static final Logger LOGGER = LoggerFactory.getLogger(ReflectionFactory.class);
    /**
     * 创建实例
     * @Title: newInstance
     * @Description: TODO
     * @param @param clazz
     * @param @return    
     * @return Object    
     * @throws
     */
    public static Object newInstance(Class<?> clazz){
        Object instance = null;
        try {
            instance = clazz.newInstance();
        } catch (Exception e) {
            LOGGER.error("new instatnce failure", e);
            throw new RuntimeException(e);
        } 

        return instance;
    }

    /**
     * 调用方法
     * @Title: invokeMethod
     * @Description: TODO
     * @param @param obj
     * @param @param method
     * @param @param args
     * @param @return    
     * @return Object    
     * @throws
     */
    public static Object invokeMethod(Object obj,Method method,Object...args){
        Object result = null;
        try {
            method.setAccessible(true);
            result = method.invoke(obj, args);
        } catch (Exception e) {
            LOGGER.error("invoke method failure", e);
            throw new RuntimeException(e);
        } 
        return result;
    }
    /**
     * 设置成员变量值
     * @Title: setField
     * @Description: TODO
     * @param @param obj
     * @param @param field
     * @param @param value    
     * @return void    
     * @throws
     */
    public static void setField(Object obj,Field field,Object value){
        try {
            field.setAccessible(true);
            field.set(obj, value);
        } catch (Exception e) {
            LOGGER.error("set field failure", e);
            throw new RuntimeException(e);
        } 
    }
}

4.2 注解类

声明两个注解类:Component和Inject

  • Component
    这是一个类注解,加上这样注解的类会在运行期交由IoC容器扫描并自动创建实例。
package xyz.letus.framework.ioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 组件描述注解
 * @ClassName: Component
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月19日
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}
  • Inject
    这是一个属性注解,加上此注解的属性,会在运行期由IoC容器进行DI操作,为属性赋值。
package xyz.letus.framework.ioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 依赖注入注解
 * @ClassName: Controller
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月19日
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
    String value() default "";
}

4.3 扫描托管的类

ClassFactory类查找所有basePackage包下的类,并判断这些类是否有标注@Component注解。带此注解的类是需要我们的IoC容器管理的类。

这里我们获取到的类集合是个一Map。如果用户在使用@Component注解的时候,有指定value值,我们后面在DI的时候会根据此值去查询并赋值。如果没指定value,则会使用缺省的类名。

package xyz.letus.framework.ioc;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import xyz.letus.framework.ioc.annotation.Component;

/**
 * 类操作助手
 * @ClassName: ClassHelper
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月19日
 *
 */
public class ClassFactory {

    /**
     * 获取交由容器管理的类
     * @Title: getBeanClasses
     * @Description: TODO
     * @param @return    
     * @return Map<String, Class<?>>    
     * @throws
     */
    public static  Map<String, Class<?>> getBeanClasses(String basePackage){
        Map<String, Class<?>> annotationClasses = new HashMap<String, Class<?>>();
        Set<Class<?>> classes = ClassLoader.getClassSet(basePackage);
        for(Class<?> clazz : classes){
            if(clazz.isAnnotationPresent(Component.class)){
                Component component = clazz.getAnnotation(Component.class);
                String name = clazz.getSimpleName();
                String value = component.value();
                if(value.length() > 0){
                    name = value;
                }
                classes.add(clazz);
                annotationClasses.put(name, clazz);
            }
        }
        return annotationClasses;
    }
}

4.4 创建实例及属性赋值

我们通过ClassFactory获取到所有托管的类后,我们可以ReflectionFactory来创建所有类的实例。

Object obj = ReflectionFactory.newInstance(entry.getValue());
package xyz.letus.framework.ioc;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;


/**
 * Bean助手类
 * @ClassName: BeanHelper
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月19日
 *
 */
public class BeanFactory {
    private static final Map<String, Object> BEAN_MAP = new HashMap<String, Object>();

    /**
     * 创建所有托管的实例
     * @Title: createInstance
     * @Description: TODO
     * @param @param packages    
     * @return void    
     * @throws
     */
    public static void createInstance(List<String> packages){
        for (String packagePath : packages) {
            Map<String, Class<?>> beanClasses = ClassFactory
                    .getBeanClasses(packagePath);
            for (Entry<String, Class<?>> entry : beanClasses.entrySet()) {
                Object obj = ReflectionFactory.newInstance(entry.getValue());

                BEAN_MAP.put(entry.getKey(), obj);
            }
        }

        IocHelper.inject(BEAN_MAP);
    }


    /**
     * 获取Bean实例
     * @Title: getBean
     * @Description: TODO
     * @param @param name
     * @param @return    
     * @return T    
     * @throws
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name){
        if(!BEAN_MAP.containsKey(name)){
            throw new RuntimeException("can not get bean by className:"+name);
        }

        return (T) BEAN_MAP.get(name);
    }
}

实例化所有类后,由IocHelper类来判断这些对象中是否有带@Inject注解的属性,然后同样通过ReflectionFactory来为这些属性进行依赖注入(DI赋值)。

package xyz.letus.framework.ioc;

import java.lang.reflect.Field;
import java.util.Map;

import xyz.letus.framework.ioc.annotation.Inject;

/**
 * 依赖注入助手类
 * @ClassName: IocHelper
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月19日
 *
 */
public class IocHelper {
    /**
     * 注入属性
     * @Title: inject
     * @Description: TODO
     * @param     
     * @return void    
     * @throws
     */
    public static void inject(Map<String, Object> beanMap){
        /**
         * 为类中的属性注入值
         */
        for(Map.Entry<String, Object> entry : beanMap.entrySet()){
            String name = entry.getKey();
            Object beanInstance = entry.getValue();

            Field[] beanFields = beanInstance.getClass().getDeclaredFields();

            for(Field field : beanFields){
                if(field.isAnnotationPresent(Inject.class)){
                    Inject inject = field.getAnnotation(Inject.class);
                    if(inject.value().length() > 0){
                        name = inject.value();
                    }
                    Class<?> fieldClazz = field.getType();
                    Object fieldInstance = beanMap.get(name);
                    if(fieldInstance != null){
                        ReflectionFactory.setField(beanInstance, field, fieldInstance);
                    }
                }
            }
        }
    }
}

4.4 资源管理器

虽然我们使用的是annotation的方式来进行管理配置信息,但像Spring一样,简单的资源文件可以使用框架更便捷与快速地工作。

我们这里使用的是Java资源文件来作为配置文件,当然如果我们有层次比较分明的配置信息时,我们也可以像Spring框架那样使用XML文件。

ResourceFactory对资源文件解析比较简单,我们现在仅仅解析scanPackage资源。这个资源告诉我们的框架需要托管的类放在哪个包路径下。当然,如果没有这个说明,我们也可以扫描整个项目下的类文件,这样效率明显比较低。

package xyz.letus.framework.context;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

/**
 * 资源管理器
 * @ClassName: ResourceFactory
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2016年2月17日
 *
 */
public class ResourceFactory {
    private static final List<String> SCAN_PACKAGES = new ArrayList<String>();

    /**
     * 解析资源文件(配置文件)
     * @Title: parse
     * @Description: TODO
     * @param @param fileName
     * @param @throws FileNotFoundException
     * @param @throws IOException    
     * @return void    
     * @throws
     */
    public static void parse(String fileName) throws FileNotFoundException, IOException {
        InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);  
        Properties properties = new Properties();
        properties.load(stream);
        Enumeration<?> e = properties.propertyNames();// 得到配置文件的名字
        while (e.hasMoreElements()) {
            String key = (String) e.nextElement();
            String value = properties.getProperty(key);

            if("scanPackage".equals(key)){
                SCAN_PACKAGES.add(value);
            }
        }
    }
    /**
     * 获取自动描述的包路径
     * @Title: getPackages
     * @Description: TODO
     * @param @return    
     * @return List<String>    
     * @throws
     */
    public static List<String> getPackages(){
        return SCAN_PACKAGES;
    }

}

4.5 应用容器

ApplicationContext 通过资源管理器ResourceFactory 获取到所以需要托管类的包路径。然后交由BeanFactory创建所有的类实例,并为其属性赋值。

ApplicationContext 提供了getBean(String name),此方法用户可以通过之前定义的名称为默认的名称来获取类实例。

package xyz.letus.framework.context;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import xyz.letus.framework.ioc.BeanFactory;

/**
 * 应用容器
 * @ClassName: IocContext
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2016年2月17日
 *
 */
public class ApplicationContext {
    private String path;

    private ApplicationContext(String path){
        this.path = path;
        init();
    }

    public static ApplicationContext getContext(String path){
        return new ApplicationContext(path);
    }
    /**
     * 初始化
     * @Title: init
     * @Description: TODO
     * @param     
     * @return void    
     * @throws
     */
    public void init(){
        try {
            ResourceFactory.parse(path);
            List<String> packages = ResourceFactory.getPackages();
            BeanFactory.createInstance(packages);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 根据别名获取一个对象
     * @Title: getBean
     * @Description: TODO
     * @param @param name
     * @param @return    
     * @return T    
     * @throws
     */
    public <T> T getBean(String name)  {
        return BeanFactory.getBean(name);
    }
}

5、框架使用

现在我们使用本文编写的IoC框架进行编程。

  • 首先我们需要一个Dao类及一个Service类。
    Dao类比较简单,一个say()方法,并加上@Component将由IoC容器管理。
    Service和Dao不同的是,它有一个未初始化的dao属性,并由@Inject告诉IoC容器,需要在运行时,为此属性初始化。
package xyz.letus.demo;

import xyz.letus.framework.ioc.annotation.Component;

@Component
public class Dao {
    public void say(){
        System.out.println("Dao say something.");
    }
}
package xyz.letus.demo;

import xyz.letus.framework.ioc.annotation.Component;
import xyz.letus.framework.ioc.annotation.Inject;

@Component
public class Service {
    @Inject
    private Dao dao;

    public void say(){
        dao.say();
        System.out.println("Service say something.");
    }
}
  • 资源文件context.properties
scanPackage=xyz.letus.demo
  • 启动IoC容器,并获取service实例
ApplicationContext context = ApplicationContext.getContext("context.properties");
Service service = context.getBean("Service");
service.say();
  • 输出结果
Dao say something.
Service say something.

当然我们上面使用的注解都是缺省模式的。我们也可以为托管的对象加上别名:

@Component("a")
public class Dao {
@Inject("a")
private Dao dao;
@Component("b")
public class Service {
Service service = context.getBean("b");

得到的结果是一样的。

6、我们还要继续

就这样,我们完成了最简单的IoC及DI框架。当然说是简单,因为它离Spring等框架的IoC功能还很远很远,包括为接口注入实现的实例、单例模式及多例模式的实现等等。

我们还要继续造轮子。

7、源码下载

https://github.com/benben-ren/wheel/tree/d389e62b1f1380b45bb38b098c5ed0ce8123c9dd

注:源码中只有ioc构架源码,不包含使用源码。

目录
相关文章
|
9天前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
3月前
|
消息中间件 Java Kafka
SpringBoot中的@Bean之谜:揭秘依赖注入的魔法与陷阱
【8月更文挑战第29天】这段内容介绍了在分布式系统中起到异步通信与解耦作用的消息队列,并详细探讨了三种流行的消息队列产品:RabbitMQ、RocketMQ 和 Kafka。RabbitMQ 是一个基于 AMQP 协议的开源消息队列系统,支持多种消息模型,具有高可靠性及稳定性;RocketMQ 则是由阿里巴巴开源的高性能分布式消息队列,支持事务消息等多种特性;而 Kafka 是 LinkedIn 开源的分布式流处理平台,以其高吞吐量和良好的可扩展性著称。文中还提供了使用这三种消息队列产品的示例代码。
24 0
|
5月前
|
Java 调度 容器
心得经验总结:控制反转(IoC)
心得经验总结:控制反转(IoC)
36 0
面试被问了几百遍的 IoC 和 AOP,还在傻傻搞不清楚?
首先声明:IoC & AOP 不是 Spring 提出来的,它们在 Spring 之前其实已经存在了,只不过当时更加偏向于理论。Spring 在技术层次将这两个思想进行了很好的实现。
|
XML Java 数据库
|
XML 存储 缓存
小白也看得懂的 Spring IoC 核心流程介绍
小白也看得懂的 Spring本文将用最通俗易懂的文字介绍 Spring IoC 中的核心流程,主要用于帮助初学者快速了解 IoC 的核心流程,也可以用作之前源码分析文章的总结。本着简单的初衷,本文会省略掉大量流程,只介绍最重要的步骤。 IoC 核心流程介绍
517 0
小白也看得懂的 Spring IoC 核心流程介绍
|
XML JavaScript Java
阅读Spring源码:IOC控制反转前的处理
阅读Spring源码:IOC控制反转前的处理
123 0
阅读Spring源码:IOC控制反转前的处理
|
设计模式 Java jenkins
别再面向 for 循环编程了,Spring 自带的观察者模式就很香!
1. 概述 在设计模式中,观察者模式是一个比较常用的设计模式。维基百科解释如下:
别再面向 for 循环编程了,Spring 自带的观察者模式就很香!
|
测试技术 容器
三分钟搞懂依赖注入
三分钟搞懂依赖注入
205 0
|
C# 容器
控制反转_依赖注入简明教程
控制反转_依赖注入简明教程
120 0