控制反轉,即Inversion of Control(IoC),是面向對象中的一種設計原則,能夠用有效下降架構代碼的耦合度,從對象調用者角度又叫作依賴注入,即Dependency Injection(DI),經過控制反轉,對象在被建立的時候,由一個調控系統內全部對象的容器,將其所依賴的對象的引用傳遞給它,也能夠說,依賴被注入到對象中,這個容器就是咱們常常說到IOC容器。Sping及SpringBoot框架的核心就是提供了一個基於註解實現的IoC容器,它能夠管理全部輕量級的JavaBean組件,提供的底層服務包括組件的生命週期管理、配置和組裝服務、AOP支持,以及創建在AOP基礎上的聲明式事務服務等。設計模式
這篇文章咱們本身動手實現一個基於註解的簡單IOC容器,固然因爲是我的實現不會真的徹底按照SpringBoot框架的設計模式,也不會考慮過多的如循環依賴、線程安全等其餘複雜問題, 整個實現原理很簡單,掃描註解,經過反射建立出咱們所須要的bean實例,再將這些bean放到集合中,對外經過IOC容器類提供一個getBean()方法,用來獲取ean實例,廢話很少說,下面開始具體設計與實現。緩存
@Retention(RetentionPolicy.RUNTIME) public @interface SproutComponet { String value() default ""; }
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SproutRoute { RouteEnum value(); }
根據傳入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); } } } }
接口必須具有三個基本方法:架構
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(); }
BeanFactory接口的具體實現,在BeanFacotry工廠中咱們須要一個容器,即beans這個Map集合,在初始化時將全部的須要IOC容器管理的對象實例化並保存到 bean 容器中,當須要使用時只須要從容器中獲取便可,app
解決每次建立一個新的實例都須要反射調用 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; } }
IOC容器的入口及頂層實現類,聲明bena工廠實例,掃描指定jar包,基於註解獲取 Class<?>集合,實例化後注入BeanFacotry對象工廠ide
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(); } }
提供方法,接受傳入的註解,經過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); } } }
到這裏IOC容器的主要接口與實現類都以基本實現,咱們看下具體的使用this
首先初始化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); } }
在上面內容中咱們圍繞「反射」+「緩存」實現了一個最基礎的IOC容器功能,總體代碼簡單清晰,沒有考慮其餘複雜狀況,適合在特定場景下使用或學習, 同時也可讓你對IOC的定義與實現原理有一個初步的認知,後續去深刻學習sping框架中的相關代碼也會更加的事半功倍,但願本文對你們能有所幫助,其中若有不足與不正確的地方還望指出與海涵。
關注微信公衆號,查看更多技術文章。