自己动手实现一个简单的 IOC容器

控制反转,即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框架中的相关代码也会更加的事半功倍,希望本文对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。

 

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

 

posted @ 2020-10-31 19:03  DaFanJoy  阅读(2928)  评论(0编辑  收藏  举报