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