控制反转,即Inversion of Control(IoC),是面向对象中的一种设计原则,可以用有效降低架构代码的耦合度,从对象调用者角度又叫做依赖注入,即Dependency Injection(DI),通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的容器,将其所依赖的对象的引用传递给它,也可以说,依赖被注入到对象中,这个容器就是我们经常说到IOC容器。Sping及SpringBoot框架的核心就是提供了一个基于注解实现的IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。

这篇文章我们自己动手实现一个基于注解的简单IOC容器,当然由于是个人实现不会真的完全按照SpringBoot框架的设计模式,也不会考虑过多的如循环依赖、线程安全等其他复杂问题,  整个实现原理很简单,扫描注解,通过反射创建出我们所需要的bean实例,再将这些bean放到集合中,对外通过IOC容器类提供一个getBean()方法,用来获取ean实例,废话不多说,下面开始具体设计与实现。

1、定义注解

@Retention(RetentionPolicy.RUNTIME)
public @interface SproutComponet {
String value() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SproutRoute {
RouteEnum value();
}

2、实现jar包扫描类

根据传入jar包,扫描与缓存jar包下所有指定注解的class<?>类对象

public class ClassScanner {

    private static Set<Class<?>> classSet = null;

    private static Map<String, Class<?>> componetMap = null;

    /**
* 获取指定包名下所有class类
* @param packageName
* @return
* @throws Exception
*/
public static Set<Class<?>> getClasses(String packageName) throws Exception { if (classSet == null){
classSet = ReflectUtils.getClasses(packageName); }
return classSet;
} /**
* 缓存所有指定注解的class<?>类对象
* @param packageName
* @return
* @throws Exception
*/
public static Map<String, Class<?>> getBean(String packageName) throws Exception { if (componetMap == null) {
Set<Class<?>> clsList = getClasses(packageName); if (clsList == null || clsList.isEmpty()) {
return componetMap;
} componetMap = new HashMap<>(16);
for (Class<?> cls : clsList) { Annotation annotation = cls.getAnnotation(SproutComponet.class);
if (annotation == null) {
continue;
} SproutComponet sproutComponet = (SproutComponet) annotation;
componetMap.put(sproutComponet.value() == null ? cls.getName() : sproutComponet.value(), cls); }
}
return componetMap;
} }

基于ClassScanner,扫描并缓存加有注解的Method对象,为后面实现方法路由提供支持

public class RouterScanner {

    private String rootPackageName;

    private static Map<Object, Method> routes = null;

    private List<Method> methods;

    private volatile static RouterScanner routerScanner;

    /**
* get single Instance
*
* @return
*/
public static RouterScanner getInstance() {
if (routerScanner == null) {
synchronized (RouterScanner.class) {
if (routerScanner == null) {
routerScanner = new RouterScanner();
}
}
}
return routerScanner;
} private RouterScanner() {
} public String getRootPackageName() {
return rootPackageName;
} public void setRootPackageName(String rootPackageName) {
this.rootPackageName = rootPackageName;
} /**
* 根据注解 指定方法 get route method
*
* @param queryStringDecoder
* @return
* @throws Exception
*/
public Method routeMethod(Object key) throws Exception {
if (routes == null) {
routes = new HashMap<>(16);
loadRouteMethods(getRootPackageName());
} Method method = routes.get(key); if (method == null) {
throw new Exception();
} return method; } /**
* 加载指定包下Method对象
*
* @param packageName
* @throws Exception
*/
private void loadRouteMethods(String packageName) throws Exception {
Set<Class<?>> classSet = ClassScanner.getClasses(packageName); for (Class<?> sproutClass : classSet) {
Method[] declaredMethods = sproutClass.getMethods(); for (Method method : declaredMethods) {
SproutRoute annotation = method.getAnnotation(SproutRoute.class);
if (annotation == null) {
continue;
}
routes.put(annotation.value(), method);
}
} } }

3、定义BeanFacotry对象工厂接口

接口必须具备三个基本方法:

  • init()  初始化注册Bean实例
  • getBean() 获取Bean实例
  • release() 卸载Bean实例
public interface ISproutBeanFactory {

    /**
* Register into bean Factory
*
* @param object
*/
void init(Object object); /**
* Get bean from bean Factory
*
* @param name
* @return
* @throws Exception
*/
Object getBean(String name) throws Exception; /**
* release all beans
*/
void release(); }

4、实现BeanFacotry对象工厂接口

BeanFactory接口的具体实现,在BeanFacotry工厂中我们需要一个容器,即beans这个Map集合,在初始化时将所有的需要IOC容器管理的对象实例化并保存到 bean 容器中,当需要使用时只需要从容器中获取即可,

解决每次创建一个新的实例都需要反射调用 newInstance() 效率不高的问题。

public class SproutBeanFactory implements ISproutBeanFactory {

    /**
* 对象map
*/
private static Map<Object, Object> beans = new HashMap<>(8); /**
* 对象map
*/
private static List<Method> methods = new ArrayList<>(2); @Override
public void init(Object object) {
beans.put(object.getClass().getName(), object);
} @Override
public Object getBean(String name) {
return beans.get(name);
} public List<Method> getMethods() {
return methods;
} @Override
public void release() {
beans = null;
}
}

5、实现bean容器类

IOC容器的入口及顶层实现类,声明bena工厂实例,扫描指定jar包,基于注解获取 Class<?>集合,实例化后注入BeanFacotry对象工厂

public class SproutApplicationContext {

    private SproutApplicationContext() {
} private static volatile SproutApplicationContext sproutApplicationContext; private static ISproutBeanFactory sproutBeanFactory; public static SproutApplicationContext getInstance() {
if (sproutApplicationContext == null) {
synchronized (SproutApplicationContext.class) {
if (sproutApplicationContext == null) {
sproutApplicationContext = new SproutApplicationContext();
}
}
}
return sproutApplicationContext;
} /**
* 声明bena工厂实例,扫描指定jar包,加载指定jar包下的实例
*
* @param packageName
* @throws Exception
*/
public void init(String packageName) throws Exception {
//获取到指定注解类的Map
Map<String, Class<?>> sproutBeanMap = ClassScanner.getBean(packageName); sproutBeanFactory = new SproutBeanFactory(); //注入实例工厂
for (Map.Entry<String, Class<?>> classEntry : sproutBeanMap.entrySet()) {
Object instance = classEntry.getValue().newInstance();
sproutBeanFactory.init(instance);
} } /**
* 根据名称获取获取对应实例
*
* @param name
* @return
* @throws Exception
*/
public Object getBean(String name) throws Exception {
return sproutBeanFactory.getBean(name);
} /**
* release all beans
*/
public void releaseBean() {
sproutBeanFactory.release();
} }

6、实现方法路由

提供方法,接受传入的注解,通过RouterScanner与SproutApplicationContext 获取对应Method对象与Bean实例,调用具体方法,从而实现方法路由功能。

public class RouteMethod {

    private volatile static RouteMethod routeMethod;

    private final SproutApplicationContext applicationContext = SproutApplicationContext.getInstance();

    public static RouteMethod getInstance() {
if (routeMethod == null) {
synchronized (RouteMethod.class) {
if (routeMethod == null) {
routeMethod = new RouteMethod();
}
}
}
return routeMethod;
} /**
* 调用方法
* @param method
* @param annotation
* @param args
* @throws Exception
*/
public void invoke(Method method, Object[] args) throws Exception {
if (method == null) {
return;
}
Object bean = applicationContext.getBean(method.getDeclaringClass().getName());
if (args == null) {
method.invoke(bean);
} else {
method.invoke(bean, args);
}
} /**
* 根据注解调用方法
* @param method
* @param annotation
* @param args
* @throws Exception
*/
public void invoke(RouteEnum routeEnum, Object[] args) throws Exception {
Method method = RouterScanner.getInstance().routeMethod(routeEnum);
if (method == null) {
return;
}
Object bean = applicationContext.getBean(method.getDeclaringClass().getName());
if (args == null) {
method.invoke(bean);
} else {
method.invoke(bean, args);
}
} }

7、具体使用

到这里IOC容器的主要接口与实现类都以基本实现,我们看下具体的使用

首先初始化IOC容器,这里根据main方法扫描应用程序所在包下的所有类,把有注解的bean实例注入实例容器

    public void start() {
try { resolveMainClass();
if(mainClass!=null) {
SproutApplicationContext.getInstance().init(mainClass.getPackage().getName());
} }catch (Exception e) {
// TODO: handle exception
}
}
/**
* 查询main方法的class类
*
*/
private Class<?> resolveMainClass() {
try {
if(!StringUtils.isEmpty(config().getRootPackageName())) {
mainClass = Class.forName(config().getRootPackageName());
}else {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
mainClass = Class.forName(stackTraceElement.getClassName());
break;
}
}
} } catch (Exception ex) {
// ignore this ex
}
return mainClass;
}

获取bead实例,并调用方法

    /**
* 根据注解调用方法
* @param method
* @param annotation
* @param args
* @throws Exception
*/
public void invoke(RouteEnum routeEnum, Object[] args) throws Exception {
Method method = RouterScanner.getInstance().routeMethod(routeEnum);//基于IOC实现的方法路由
if (method == null) {
return;
}
Object bean = applicationContext.getBean(method.getDeclaringClass().getName()); // 通过Bean容器直接获取实例
if (args == null) {
method.invoke(bean);
} else {
method.invoke(bean, args);
}
}

8、总结

在上面内容中我们围绕“反射”+“缓存”实现了一个最基础的IOC容器功能,整体代码简单清晰,没有考虑其他复杂情况,适合在特定场景下使用或学习, 同时也可以让你对IOC的定义与实现原理有一个初步的认知,后续去深入学习sping框架中的相关代码也会更加的事半功倍,希望本文对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。

关注微信公众号,查看更多技术文章。

自己动手实现一个简单的 IOC容器的更多相关文章

  1. .NET实现一个简单的IOC容器

    目录 1.主要细节 2.具体示例 参考及示例代码下载 shanzm-2020年3月17日 20:06:01 1.主要细节 使用反射程序集的方式获取对象的类型 通过反射的方式获取指定类型的的所有公共属性 ...

  2. laravel学习:php写一个简单的ioc服务管理容器

    php写一个简单的ioc服务管理容器 原创: 陈晨 CoderStory 2018-01-14 最近学习laravel框架,了解到laravel核心是一个大容器,这个容器负责几乎所有服务组件的实例化以 ...

  3. 几句代码简单实现IoC容器

    前言 最近在调试EasyNetQ代码的时候发现里面有一段代码,就是IoC容器的简单实现,跟着他的代码敲了一遍,发现了奇妙之处.当然也是因为我才疏学浅导致孤陋寡闻了.他的思路就是通过动态调用构造函数生成 ...

  4. IoC原理-使用反射/Emit来实现一个最简单的IoC容器

    从Unity到Spring.Net,到Ninject,几年来陆陆续续用过几个IoC框架.虽然会用,但也没有一直仔细的研究过IoC实现的过程.最近花了点时间,下了Ninject的源码,研究了一番,颇有收 ...

  5. 【最简单IOC容器实现】实现一个最简单的IOC容器

    前面DebugLZQ的两篇博文: 浅谈IOC--说清楚IOC是什么 IoC Container Benchmark - Performance comparison 在浅谈IOC--说清楚IOC是什么 ...

  6. 手写一个最简单的IOC容器,从而了解spring的核心原理

    从事开发工作多年,spring源码没有特意去看过.但是相关技术原理倒是背了不少,毕竟面试的那关还是得过啊! 正所谓面试造火箭,工作拧螺丝.下面实现一个最简单的ioc容器,供大家参考. 1.最终结果 2 ...

  7. (2)自己写一个简单的servle容器

    自己写一个简单的servlet,能够跑一个简单的servlet,说明一下逻辑. 首先是写一个简单的servlet,这就关联到javax.servlet和javax.servlet.http这两个包的类 ...

  8. 比Spring简单的IoC容器

    比Spring简单的IoC容器 Spring 虽然比起EJB轻量了许多,但是因为它需要兼容许多不同的类库,导致现在Spring还是相当的庞大的,动不动就上40MB的jar包, 而且想要理解Spring ...

  9. 一个简单的servlet容器

    [0]README 0.1)本文部分文字转自 “深入剖析Tomcat”,旨在学习  一个简单的servlet容器  的基础知识: 0.2)for complete source code, pleas ...

随机推荐

  1. Cookies题解

    来源:<算法竞赛进阶指南> Describe: 有M块饼干要分给N个孩子.当有k个孩子分到的饼干数比第i个孩子分到的多时,会产生g[i]*k的贡献.求最小的贡献及任意一种方案. Solut ...

  2. 提交 linux kernel 补丁流程备忘录

    1. 订阅 linux 邮件列表 linux 邮件列表 Kernel Mailing Lists 是所有 linux kernel 开源贡献者协同工作的平台,可以通过向 VGER.KERNEL.ORG ...

  3. Azure Storage 系列(七)使用Azure File Storage

    一,引言 今天我们开始介绍 Storage 中的最后一个类型的存储----- File Storage(文件存储),Azure File Storage 在云端提供完全托管的文件共享,这些共享项可通过 ...

  4. java安全编码指南之:异常处理

    目录 简介 异常简介 不要忽略checked exceptions 不要在异常中暴露敏感信息 在处理捕获的异常时,需要恢复对象的初始状态 不要手动完成finally block 不要捕获NullPoi ...

  5. CSS实现图片圆角显示

    问题描述 在自定义博客园侧边栏公告时,想增加博客头像,但图片默认显示成是方形的,不是很好看,想着改成圆角显示会漂亮些 解决方案 增加css样式 border-radius:25px; 上面的像素值根据 ...

  6. (转载)IO模型

    本文转载自网络. 如有侵权,请联系处理! 简介 参考<UNIX Network Programming Volume 1, ThirdEdition [Electronic resources] ...

  7. mysql-16-variables

    #变量 /* 系统变量: 全局变量 会话变量 自定义变量: 用户变量 局部变量 */ # 一.系统变量 #由系统提供,属于服务器层面 #1.查看所有的系统变量 show global variable ...

  8. makefile实验三 理解make工作的基本原则

    代码简单,但测试花样多,若能回答对本博客的每个步骤的预期结果,可以说对makefile的基础掌握是扎实的. 一,当前的makefile代码 root@ubuntu:~/Makefile_Test# r ...

  9. 03 . Docker数据资源管理与网络

    Docker数据卷 在容器中管理数据主要有两种方式 # 数据卷(Data volumes) # 数据卷容器(Data volume containers) # 数据卷是一个可供一个或多个容器使用的特殊 ...

  10. Java知识系统回顾整理01基础01第一个程序03Eclipse下载安装

    Eclipse是最流行的java 集成开发环境IDE(Integrated Development Environment) 下载安装Eclipse两种方式 一.方式1:Eclipse官网下载安装 链 ...