自己動手實現一個簡單的 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框架中的相關程式碼也會更加的事半功倍,希望本文對大家能有所幫助,其中如有不足與不正確的地方還望指出與海涵。
關注微信公眾號,查看更多技術文章。