手动实现一个 IOC/DI 容器
- 第一章为源码解析。
- 第二章为实现一个简单的 IOC 容器。
- 第三章进阶 Spring 插件开发。
手动实现一个 IOC/DI 容器
上一篇文章里我们已经对 Spring 的源码有了一个大概的认识,对于 Spring 中的一些组件,比如 Aware,PostProcessor,ApplicationContext 有了一定的认识,下面我们就来手动实现一个 IOC/DI 容器。
项目整体用 maven 构建,里面有两个模块,MySpring 为 IOC/DI 的核心,Demo 为测试项目。
先来看看整体的项目结构,目前为第一个版本,好多需要完善的地方。最近好忙。

首先我们把几个重要的注解定义出来。
@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 "";
}
首先我们也定义一个 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); }
定义一个 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();
} }
在看容器的最终实现类之前,我们先把 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>
容器的最终实现类,基于注解的 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();
} }在 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();
}
}测试结果:
// 容器中的 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)。
项目源码
手动实现一个 IOC/DI 容器的更多相关文章
- StructureMap经典的IoC/DI容器
StructureMap是一款很老的IoC/DI容器,从2004年.NET 1.1支持至今. 一个使用例子 //创建业务接口 public interface IDispatchService { } ...
- String框架搭建的基本步骤,及从 IOC & DI 容器中获取 Bean(spring框架bean的配置)--有实现数据库连接池的链接
Spring框架的插件springsource-tool-suite-3.4.0.RELEASE-e4.3.1-updatesite(是一个压缩包)导入步骤: eclipse->help-> ...
- 深入理解IoC/DI
------------------------------------------------------------------------ 理解IoC/DI 1.控制反转 --> 谁控制谁 ...
- IoC/DI基本思想的演变
---------------------------------------------------------------------------------- (1)IoC/DI的概念 IoC ...
- 工厂方法模式与IoC/DI
IoC——Inversion of Control 控制反转 DI——Dependency Injection 依赖注入 1:如何理解IoC/DI 要想理解上面两个概念,就必须搞清 ...
- spring--学习之IOC DI
2.1.1 IoC是什么 Ioc-Inversion of Control,即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器 ...
- spring ioc DI 理解
下面是我从网上找来的一些大牛对spring ioc和DI的理解,希望也能让你对Spring ioc和DI的设计思想有更进一步的认识. 一.分享Iteye的开涛对Ioc的精彩讲解 Ioc—Inversi ...
- 工厂方法模式与IoC/DI控制反转和依赖注入
IoC——Inversion of Control 控制反转 DI——Dependency Injection 依赖注入 要想理解上面两个概念,就必须搞清楚如下的问题: 参与者都有谁? 依赖:谁 ...
- IoC/DI
From:http://jinnianshilongnian.iteye.com/blog/1471944 我对IoC/DI的理解 博客分类: spring杂谈 IoCDI IoC IoC: Inv ...
随机推荐
- [实时更新]jquery完整版下载
jquery-2.1.0 注!不再支持IE 6/7/8 直接引用地址: 开发版地址1: <script src="http://code.jquery.com/jquery-2. ...
- hdu2083 简易版之最短距离
点A和点B之间随意一点到A的距离+到B的距离=|AB|,而AB外的一点到A的距离+到B的距离>|AB|: #include<math.h> #include<stdio.h&g ...
- 机器学习 vs. 深度学习
1. bias/vairance Trend # 1:Scale driving Deep Learning process. 2. feature learning 的方式 A common com ...
- hdu 2037 这个夏天不AC (java)
问题: 这个题为项贪心算法.我们的想法是在第一时间每个周期根据结束排序(按结束越早穿越,更多的程序), 然后从第一个节目开始.假设下一个节目的开始时间大于一个节目的开始时间,是进行程序,依次递推. 输 ...
- react学习(6)——关于组件生命周期的问题
在项目开发的过程中,遇到了一个问题: 父组件请求后台数据,收到后将数据以props传给子组件,子组件根据收到数据的不同,显示不同的内容,同时,子组件自身可以根据click操作改变根据父组件的数据显示的 ...
- PHP获取月末时间
用php获取月末 $a_date = "2009-11-23"; echo date("Y-m-t", strtotime($a_date)); //第二种 ( ...
- Windows静态库和动态库的创建和使用(VS2005)
偶们在实际的编程开发中,经常会遇到运行时无法找到某个DLL文件或者链接时无法找到某个LIB文件.然后,我们就开始乱GOOGLE一下,然后将VS2005的设置改变一下,或许就Ok了,我们将别人开发的DL ...
- DevExpress XtraTreeList TreeList复选框选择
权限管理涉及复选框多勾选. 1.控件属性设置 TreeList.OperationView.ShowCheckBoxes=true;用于显示CheckBox: TreeList.OperationBe ...
- 【全面解禁!真正的Expression Blend实战开发技巧】第三章 从最常用ButtonStyle开始 - TextButton
原文:[全面解禁!真正的Expression Blend实战开发技巧]第三章 从最常用ButtonStyle开始 - TextButton 在实际项目中,使用blend做的最多的一定是各种自定义But ...
- .NET MVC 在action中,过滤器中,或视图中,如何分别获取 当前请求的 控制器/视图/区域 的名字
1)过滤器中的: public class CMSAttribute : FilterAttribute, IAuthorizationFilter { public void OnAuthoriza ...