控制反轉,即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框架中的相關代碼也會更加的事半功倍,希望本文對大家能有所幫助,其中如有不足與不正確的地方還望指出與海涵,
關注微信公眾號,查看更多技術文章,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/197460.html
標籤:Java
上一篇:大廠面試系列(五):Dubbo和Spring Cloud
下一篇:Java語言的演進
