• 自己动手实现一个简单的 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框架中的相关代码也会更加的事半功倍,希望本文对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。

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

     

  • 相关阅读:
    【转】VS2013编译libjpeg库
    玩转百度地图(二)之画圆,高德地图、搜搜地图、搜狗地图等稍微修改即可
    JAVA自动生成正则表达式工具类
    S2SH商用后台权限系统第三讲
    自定义表单验证指令
    关于input/textarea提交内容空格回车转换问题,以及ng-model去除空格问题
    angular ui-router 缓存问题
    ionic 发送请求返回一直都是404
    ionic中获取坐标方法
    ionic的scroll的使用出现的问题
  • 原文地址:https://www.cnblogs.com/dafanjoy/p/13905264.html
Copyright © 2020-2023  润新知