zoukankan      html  css  js  c++  java
  • 手动实现一个 IOC/DI 容器

    • 第一章为源码解析。
    • 第二章为实现一个简单的 IOC 容器。
    • 第三章进阶 Spring 插件开发。

    手动实现一个 IOC/DI 容器

    上一篇文章里我们已经对 Spring 的源码有了一个大概的认识,对于 Spring 中的一些组件,比如 Aware,PostProcessor,ApplicationContext 有了一定的认识,下面我们就来手动实现一个 IOC/DI 容器。

    项目整体用 maven 构建,里面有两个模块,MySpring 为 IOC/DI 的核心,Demo 为测试项目。

    1. 先来看看整体的项目结构,目前为第一个版本,好多需要完善的地方。最近好忙。

    2. 首先我们把几个重要的注解定义出来。
      @Autowired,自动注入注解,用来实现 DI 功能。
      @Controller,控制层注解,暂时不实现 SpringMVC 相关的功能。后续分析完 SpringMVC 源码后会实现。
      @Service,服务层注解,与 Spring 中的 @Component 作用相同,就是将这个 Bean 交给 Spring 来管理。
      @Repository,持久层注解,将这个 Bean 交给 Spring 来管理。
      暂时先定义这几个注解。

      这几个注解的定义与代码都是一样的,就是将注解定义出来。

       @Target(ElementType.TYPE)
       @Retention(RetentionPolicy.RUNTIME)
       public @interface Service {
       
           String value() default "";
       }
      
    3. 首先我们也定义一个 BeanFactory 工厂方法作为最上层的容器。里面主要有一个 getBean 方法用来从容器中获取 Bean,当然这里面已经包含了 Bean
      的实例化过程。getBean 方法调用抽象的 doGetBean 方法,最后交给子类实现。

       package org.springframework.ioc.factory;
       
       /**
        * 容器对象的工厂类,生产容器对象
        *
        */
       public abstract class BeanFactory {
       
           public Object getBean(String beanName){
               return doGetBean(beanName);
           }
       
           protected abstract Object doGetBean(String beanName);
       
       }    
      
    4. 定义一个 ApplicationContext 继承 BeanFactory,在里面添加 xml
      处理工具类和包的扫描路径。这个类也是一个抽象类。里面包含两个实例参数,配置文件路径和 xml 处理工具。

       package org.paul.springframework.ioc.bean;
       
       import org.paul.springframework.ioc.factory.BeanFactory;
       import org.paul.springframework.ioc.xml.XmlUtil;
       
       public abstract class ApplicationContext extends BeanFactory {
       
           protected String configLocation;
           protected XmlUtil xmlUtil = null;
       
           public ApplicationContext(){
           }
       
           public ApplicationContext(String configLocations){
               this.configLocation = configLocations;
               xmlUtil = new XmlUtil();
           }
       
       }
      
    5. 在看容器的最终实现类之前,我们先把 xmlUtil 和 配置文件的结构给大家看一下。
      xmlUtil 的作用就是解析配置文件获得类的所描路径。

       package org.springframework.ioc.xml;
       
       import org.dom4j.Document;
       import org.dom4j.DocumentException;
       import org.dom4j.Element;
       import org.dom4j.io.SAXReader;
       import java.io.InputStream;
       
       /**
        * 解析容器的配置文件中扫描包的路径
        *
        */
       public class XmlUtil {
           public String handlerXMLForScanPackage(String configuration){
               InputStream ins = this.getClass().getClassLoader().getResourceAsStream(configuration);
               SAXReader reader = new SAXReader();
               try{
                   Document document = reader.read(ins);
                   Element root = document.getRootElement();
                   Element element = root.element("package-scan");
                   String res = element.attributeValue("component-scan");
                   return res;
               }catch (DocumentException e){
                   e.printStackTrace();
               }
               return null;
           }
       }
      
      
       <?xml version="1.0" encoding="UTF-8" ?>
       <beans>
           <package-scan component-scan="com.spring.demo" />
       </beans>
      
    6. 容器的最终实现类,基于注解的 xml 扫描容器配置类。

      • 首先定义两个线程安全的 List 和 一个 ConcurrentHashMap,分别用来保存扫描 类的路径,类和实例对象。
      • 在 AnnotationApplicationContext 的构造函数里,分别实现了以下的功能。
        调用父类的初始化方法,将 xml 工具实例化。
        使用 xmlUtil 和 配置文件路径获取到扫描的包路径。
        获取到包路径后,执行包的扫描操作。
        将里面有对应注解的 Bean 注入到容器中。
        将对象创建出来,先忽略依赖关系。
        执行容器实例管理和对象运行期间的依赖装配。
       package org.springframework.ioc.bean;
       
       import java.io.File;
       import java.io.FileFilter;
       import java.lang.reflect.Field;
       import java.net.URISyntaxException;
       import java.net.URL;
       import java.util.ArrayList;
       import java.util.Collections;
       import java.util.List;
       import java.util.Map;
       import java.util.Map.Entry;
       import java.util.concurrent.ConcurrentHashMap;
       
       import org.springframework.ioc.annotation.Autowired;
       import org.springframework.ioc.annotation.Component;
       import org.springframework.ioc.annotation.Controller;
       import org.springframework.ioc.annotation.Repository;
       import org.springframework.ioc.annotation.Service;
       
       public class AnnotationApplicationContext extends ApplicationContext {
       	
       	//保存类路径的缓存
       	private static List<String> classCache = Collections.synchronizedList(new ArrayList<String>());
       	
       	//保存需要注入的类的缓存
       	private static List<Class<?>> beanDefinition = Collections.synchronizedList(new ArrayList<Class<?>>());
       
       	//保存类实例的容器
       	private static Map<String,Object> beanFactory = new ConcurrentHashMap<>();
       	
       	public AnnotationApplicationContext(String configuration) {
       		super(configuration);
       		String path  = xmlUtil.handlerXMLForScanPackage(configuration);
               System.out.println(path);
               
               //执行包的扫描操作
               scanPackage(path);
               //注册bean
               registerBean();
               //把对象创建出来,忽略依赖关系
               doCreateBean();
               //执行容器管理实例对象运行期间的依赖装配
               diBean();
       	}
       
       
       
       	@Override
       	protected Object doGetBean(String beanName) {
       		return beanFactory.get(beanName);
       	}
       	
       	/**
       	 * 扫描包下面所有的 .class 文件的类路径到上面的List中
       	 * 
       	 */
       	private void scanPackage(final String path) {
       		URL url = this.getClass().getClassLoader().getResource(path.replaceAll("\.", "/"));
       		try {
       			File file = new File(url.toURI());
       			
       			file.listFiles(new FileFilter(){
       				//pathname 表示当前目录下的所有文件
       				@Override
       				public boolean accept(File pathname) {
       					//递归查找文件
       					if(pathname.isDirectory()){
       						scanPackage(path+"."+pathname.getName());
       					}else{
       						if(pathname.getName().endsWith(".class")){
                                   String classPath = path + "." + pathname.getName().replace(".class","");
                                   classCache.add(classPath);
       						}
       					}
       					return true;
       				}
       				
       			});
       		} catch (URISyntaxException e) {
       			e.printStackTrace();
       		}
       		
       		
       	}
       	
       
       	/**
       	 * 根据类路径获得 class 对象
       	 */
       	private void registerBean() {
       		if(classCache.isEmpty()){
       			return;
       		}
       		
       		
       		for(String path:classCache){
       			try {
       				//使用反射,通过类路径获取class 对象
       				Class<?> clazz = Class.forName(path);
       				//找出需要被容器管理的类,比如,@Component,@Controller,@Service,@Repository
       				if(clazz.isAnnotationPresent(Repository.class)||clazz.isAnnotationPresent(Service.class)
       						||clazz.isAnnotationPresent(Controller.class)|| clazz.isAnnotationPresent(Component.class)){
       					beanDefinition.add(clazz);
       				}
       			} catch (ClassNotFoundException e) {
       				// TODO Auto-generated catch block
       				e.printStackTrace();
       			}
       		}
       		
       	}
       
       	
       	/**
       	 * 
       	 * 根据类对象,创建实例
       	 */
       	private void doCreateBean() {
       		if(beanDefinition.isEmpty()){
       			return;
       		}
       		
       
       		for(Class clazz:beanDefinition){
       			try {
       				Object instance = clazz.newInstance();
       				//将首字母小写的类名作为默认的 bean 的名字
       				String aliasName = lowerClass(clazz.getSimpleName());
       				//先判断@ 注解里面是否给了 Bean 名字,有的话,这个就作为 Bean 的名字
       				if(clazz.isAnnotationPresent(Repository.class)){
       					Repository repository = (org.springframework.ioc.annotation.Repository) clazz.getAnnotation(Repository.class);
       					if(!"".equals(repository.value())){
       						aliasName = repository.value();
       					}
       				}	
       				if(clazz.isAnnotationPresent(Service.class)){
       					Service service = (org.springframework.ioc.annotation.Service) clazz.getAnnotation(Service.class);
       					if(!"".equals(service.value())){
       						aliasName = service.value();
       					}
       				}
       				if(clazz.isAnnotationPresent(Controller.class)){
       					Controller controller = (org.springframework.ioc.annotation.Controller) clazz.getAnnotation(Controller.class);
       					if(!"".equals(controller.value())){
       						aliasName = controller.value();
       					}
       				}
       				if(clazz.isAnnotationPresent(Component.class)){
       					Component component = (org.springframework.ioc.annotation.Component) clazz.getAnnotation(Component.class);
       					if(!"".equals(component.value())){
       						aliasName = component.value();
       					}
       				}
       				if(beanFactory.get(aliasName)== null){
       					beanFactory.put(aliasName, instance);
       				}
       				
       				//判断当前类是否实现了接口
       				Class<?>[] interfaces = clazz.getInterfaces();
       				if(interfaces == null){
       					continue;
       				}
       				//把当前接口的路径作为key存储到容器中
       				for(Class<?> interf:interfaces){
       					beanFactory.put(interf.getName(), instance);
       				}
       				
       
       				
       			} catch (InstantiationException e) {
       				e.printStackTrace();
       			} catch (IllegalAccessException e) {
       				e.printStackTrace();
       			}
       		}
       		
       		
       		for (Entry<String, Object> entry : beanFactory.entrySet()) { 
       			  System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); 
       			}
       		
       		
       	}
       	
       	/**
       	 * 对创建好的对象进行依赖注入
       	 */
       	private void diBean() {
       		if(beanFactory.isEmpty()){
       			return;
       		}
       		
       		for(Class<?> clazz:beanDefinition){
       			String aliasName = lowerClass(clazz.getSimpleName());
       			//先判断@ 注解里面是否给了 Bean 名字,有的话,这个就作为 Bean 的名字
       			if(clazz.isAnnotationPresent(Repository.class)){
       				Repository repository = (org.springframework.ioc.annotation.Repository) clazz.getAnnotation(Repository.class);
       				if(!"".equals(repository.value())){
       					aliasName = repository.value();
       				}
       			}	
       			if(clazz.isAnnotationPresent(Service.class)){
       				Service service = (org.springframework.ioc.annotation.Service) clazz.getAnnotation(Service.class);
       				if(!"".equals(service.value())){
       					aliasName = service.value();
       				}
       			}
       			if(clazz.isAnnotationPresent(Controller.class)){
       				Controller controller = (org.springframework.ioc.annotation.Controller) clazz.getAnnotation(Controller.class);
       				if(!"".equals(controller.value())){
       					aliasName = controller.value();
       				}
       			}
       			if(clazz.isAnnotationPresent(Component.class)){
       				Component component = (org.springframework.ioc.annotation.Component) clazz.getAnnotation(Component.class);
       				if(!"".equals(component.value())){
       					aliasName = component.value();
       				}
       			}
       			
       			//根据别名获取到被装配的 bean 的实例
       			Object instance = beanFactory.get(aliasName);
       			try{
       				//从类中获取参数,判断是否有 @Autowired 注解
       				Field[] fields = clazz.getDeclaredFields();
       				for(Field f:fields){
       					if(f.isAnnotationPresent(Autowired.class)){
       						System.out.println("12312312312123");
       						//开启字段的访问权限
       						f.setAccessible(true);
       						Autowired autoWired = f.getAnnotation(Autowired.class);
       						if(!"".equals(autoWired.value())){
       							System.out.println("111111111111111111111");
       							//注解里写了别名
       							f.set(instance, beanFactory.get(autoWired.value()));
       							
       						}else{
       							//按类型名称
       							String fieldName = f.getType().getName();
       							f.set(instance, beanFactory.get(fieldName));
       						}
       					}
       				}
       			}catch(Exception e){
       				e.printStackTrace();
       			}
       			
       		}
       		
       	}
       
       
       	private String lowerClass(String simpleName) {
               char[] chars = simpleName.toCharArray();
               chars[0] += 32;
               return chars.toString();
       	}
       
       }
      
      
      
      
    7. 在 Demo 模块中定义类似与 SpringMVC 的三层架构,并在 Service 层注入 Dao,在 Dao 层我们只打印了一句话,为了验证 DI 成功。

       package org.Demo;
      
       import static org.junit.Assert.assertTrue;
       
       import org.junit.Test;
       import org.springframework.ioc.bean.AnnotationApplicationContext;
       import org.springframework.ioc.bean.ApplicationContext;
       
       import com.spring.demo.Service.BookService;
       
       /**
        * Unit test for simple App.
        */
       public class AppTest 
       {
       	public static void main(String[] args){
       
       		ApplicationContext ctx = new AnnotationApplicationContext("applicationContext.xml");
       		BookService bookService = (BookService) ctx.getBean("bookService");
       		bookService.action();
       	}
       }
      
      
    8. 测试结果:

       // 容器中的 Bean
       Key = bookDao, Value = com.spring.demo.Dao.BookDaoImpl@3d494fbf
       Key = [C@4fca772d, Value = com.spring.demo.controller.BookController@1ddc4ec2
       Key = bookService, Value = com.spring.demo.Service.BookServiceImpl@133314b
       Key = com.spring.demo.Dao.BookDao, Value = com.spring.demo.Dao.BookDaoImpl@3d494fbf
       Key = com.spring.demo.Service.BookService, Value = com.spring.demo.Service.BookServiceImpl@133314b
       
       // service 调用 dao 层的方法,成功打印。
       我在读书
      

      这样一个 IOC/DI 容器就构建成功了,整个项目源码在 github,希望大家 star 一下,一起改进(多提 pull request)。
      项目源码

    讨论学习 qq 群:725758660
  • 相关阅读:
    如何实现基于消息/传输安全验证机制下的windows身份验证过程、无任何验证
    利用WCF的callback机制开发一个简单的多人游戏模型
    WCF常见预绑定协议中各种安全模式(Message Security|Transport Security)所支持的客户端凭证验证类型汇总
    tornado+peeweeasync+peewee+mysql(一)
    IIS 内部运行机制
    IIS 7 托管管道模式 经典模式(Classic) 集成模式(Integrated) 分析与理解
    斐波那契查找算法
    进制间转换
    严版数据结构题集3.15
    (TOJ 4413)IP address
  • 原文地址:https://www.cnblogs.com/paulwang92115/p/11005704.html
Copyright © 2011-2022 走看看