控制反转,即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. Regression trees树回归 以及其他

    https://www.cnblogs.com/wuliytTaotao/p/10724118.html 选 weighted variance 最小的 但是weighted variance是怎么计 ...

  2. Java知识系统回顾整理01基础03变量08表达式

    一.以 ; 结尾的一段代码,即为一个表达式 表达式是由变量.操作符以及方法调用所构成的结构.如下所示: int i = 5; System.out.println(5); 都是表达式 public c ...

  3. Windows下安装VScode,并使用,以及中文配置

    转载:https://blog.csdn.net/x15011238662/article/details/85094006 首先明确一点,VScode是开发Go应用的基础编辑器,是Microsoft ...

  4. 【题解】 [EZEC-4]求和

    对于百分之十的数据:随便过. 下面推式子: \[\sum_{i=1}^n\sum_{j=1}^n\gcd(i,j)^{i+j} \] \[=\sum_{d=1}^n\sum_{i=1}^n\sum_{ ...

  5. 14.深入k8s:kube-proxy ipvs及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 这一篇是讲service,但是基础使用以及基本概念由于官方实在是写的 ...

  6. linux下的echo

    echo命令用于在shell中打印shell变量的值,或者直接输出指定的字符串.linux的echo命令,在shell编程中极为常用, 在终端下打印变量value的时候也是常常用到的,因此有必要了解下 ...

  7. 项目使用eslint

    今天eslint版本更新了,然后昂,有些奇奇怪怪的错误提示了,然后想,这我得 1.配置一个保存时根据eslint规则自动修复 2.欸,之前编码遇到未使用的变量都会有标记黄线,我很好定位,这会怎么没了 ...

  8. 《流畅的Python》第二部分 数据结构 【序列构成的数组】【字典和集合】【文本和字节序列】

    第二部分 数据结构 第2章 序列构成的数组 内置序列类型 序列类型 序列 特点 容器序列 list.tuple.collections.deque - 能存放不同类型的数据:- 存放的是任意类型的对象 ...

  9. devops-jenkins-Pipeline基础语法

    1. jenkins-Pipeline基础语法  1) jenkins-Pipeline总体介绍 • Pipeline,简而言之,就是一套运行与jenkins上的工作流框架,将原本独立运行于单个或多个 ...

  10. 多测师讲解自动化测试 _接口面试题(001)_高级讲师肖sir

    1.为什么要做接口测试(必要性)1.可以发现很多在页面上操作发现不了的bug2.检查系统的异常处理能力3.检查系统的安全性.稳定性4.前端随便变,接口测好了,后端不用变5.可以测试并发情况,一个账号, ...