diy实现spring依赖注入
【README】
本文diy代碼實現了 spring 依賴注入,一定程度上揭示了依賴注入原理;
【1】控制反轉-Inversion of Control
是一種編碼思想,簡而言之就是 應用程序A可以使用組件B,但A無法控制B的生命周期(如創建,內部屬性賦值,銷毀(若需)等等),而交由第三方控制,如容器;
- 這里的容器不僅僅是spring容器,spring容器只是一種實現方式; 還有其他容器,如 PicoContainer,參見文末引用資料;
- 這里的組件指的是 封裝了屬性和方法的java類實例,如業務邏輯處理類實例,dao層實例,數據源,日志發送器,http客戶端,kafka客戶端,各種客戶端等等很多;
這樣做的原因 是可以把 應用程序A 與 組件B 解耦,1可以簡化代碼開發,2提高代碼復用,3代碼易于維護;
補充:控制反轉,反轉的是 組件或javabean的創建,存儲與管理權到第三方容器;by wikipedia? https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC
【早期控制反轉例子】
spring容器出現前的IOC例子(即 IOC 在spring之前就已經提出來了,IOC并非是spring提出,而是 spring使用了IOC的編程思想):
關于命令行用戶界面的主控權:命令行用戶界面交互的主控權由應用程序來控制,應用程序可以設計一系列命令,如linux上的zk,kafka客戶端命令等,應用程序逐條輸出信息和給出響應;如 window命令行界面,linux vim編輯器,zk客戶端,kafka客戶端等;
而在圖形用戶界面環境下(包括CS,BS),UI框架將負責執行一個主循環監聽事件,應用程序只需要提供處理函數即可(代碼適配瀏覽器),無需關心界面交互方式(界面IO,事件等),它也沒法關心,因為每個瀏覽器有自己特有的交互方式(有少許差別,這才會產生瀏覽器兼容性問題), 這樣的UI框架如瀏覽器。顯然,前端應用程序如html,js無法控制界面響應(只能適配),而由底層UI框架來控制;這時,交互控制權發生了反轉,從應用程序轉移到了UI框架;
雖然應用程序無法控制界面響應,但它可以使用UI組件渲染前端;
小結:可以理解 在IoC的編程思想下,應用程序對使用的組件只有使用權,沒有所有權,所有權由第三方容器或框架控制*;
如何理解所有權? 即 當應用程序消亡時,其使用的組件并沒有隨它而消亡,而是繼續存在;因為前者對后者沒有所有權,無法管理后者的生命周期;
【1.1】實現控制反轉主要有兩種方式:依賴注入和依賴查找。
1)依賴注入DI(被動接受屬性對象賦值):在類A實例a的創建過程中同時創建了類A依賴對象b(僅創建對象,沒有賦值),然后第三方容器通過類型或名稱把 b 注入(賦值)給類A實例的屬性;這個過程叫做依賴注入;
【代碼1-依賴注入代碼示例】
A a = new A(); // 主類A實例 步驟1 B b = new B(); // 依賴實例b 步驟2 a.setB(b); // 第三方容器注入bean或賦值給實例a的屬性 步驟3, // 以上3步均由 第三方容器通過反射+工廠來完成這里反轉的是 類A實例對依賴屬性對象b的創建,存儲和管理權利(或生命周期權),而由第三方容器來管理;因為按照傳統編程方式,類A依賴屬性對象b,那屬性b就應該由類A來創建和賦值;
2)依賴查找(主動索取對象):主動索取相應類型的對象,獲得依賴對象的時間也可以在代碼中自由控制。
【1.2】自動裝配 Autowire
表示第三方容器通過類型或名稱把創建的依賴對象 賦值給主類實例的屬性對象的過程,前提是屬性對象被Autowire注解標識;
【小結】
上文詳細闡述了? IoC, DI, autowire 的概念,這是spring核心概念,應該是比較清楚了;
- IoC:是一種編程模型,講的是 依賴對象不由 使用者創建,交由 第三方容器來創建和管理;
- DI:是IoC思想的一種實現, 講的是? 使用者實例,依賴對象實例都由 第三方容器來創建,實例創建完成后, 容器通過類型或名稱把依賴對象賦值給 使用者實例的屬性對象,以便建立依賴關系的過程;
- autowire:是DI過程的一部分,講的是?? 容器通過類型或名稱把依賴對象賦值給 使用者實例的屬性對象的過程;
【2】diy代碼實現 spring? 依賴注入
【2.1】自定義4個注解
// 標識 controller后的步驟 @Documented @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyStep {public String value() default ""; }// 標識 dao bean @Documented @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyRepository {public String value() default ""; }// 標識bean @Documented @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyBean {public String value() default ""; }// 標識自動注入bean @Documented @Inherited @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAutowire {public String value() default ""; }【2.2】自定義容器
/*** @Description bean工廠 * @author xiao tang* @version 1.0.0* @createTime 2021年12月16日*/ public interface BeanFactory {<T> T getBean(Class<T> clazz);<T> T getBean(String beanName); } /*** @Description spring容器 * @author xiao tang* @version 1.0.0* @createTime 2021年12月16日*/ public class SpringContext implements BeanFactory {/** 類路徑根目錄 */private static String appRoot = "D:\\workbench_idea\\study4vw\\vwstudy10\\target\\classes";private static SpringContextSupport springContextSupport = new SpringContextSupport(appRoot);public static void insertBean(String key, Object value) {SpringContextSupport.container.put(key, value);}@Overridepublic <T> T getBean(Class<T> clazz) {return (T) SpringContextSupport.container.get(clazz.getName());}@Overridepublic <T> T getBean(String beanName) {return (T) SpringContextSupport.container.get(beanName);} } /*** @Description spring容器支持類,掃描注解,使用反射創建bean,注入bean* @author xiao tang* @version 1.0.0* @createTime 2021年12月19日*/ public class SpringContextSupport {/** clazz名稱列表 */List<String> allClazzNameList;/** 自定義注解映射 */Map<String, Class> defineAnnotationMap;/** bean clazz名稱列表 */List<String> beanClazzNameList;/** spring容器 */static ConcurrentMap<String, Object> container = new ConcurrentHashMap<>();public SpringContextSupport(String appRootPath) {// 掃描所有文件的clazzthis.allClazzNameList = scanAllFileClazz(appRootPath);// 掃描所有自定義注解this.defineAnnotationMap = scanAnnotation(allClazzNameList);// 剔除注解類clazzthis.beanClazzNameList = allClazzNameList.stream().filter(x->!defineAnnotationMap.containsKey(x)).collect(Collectors.toList());this.loadBean();}/*** @description 加載bean* @author xiao tang* @date 2021/12/25*/private void loadBean() {// 掃描被注解修飾的類,并添加到容器for (String x : beanClazzNameList) {try {Class beanClazz = Class.forName(x);for (Class defineAnnoClazz : defineAnnotationMap.values()) {if (beanClazz.isAnnotationPresent(defineAnnoClazz)) { // bean是否被定義的注解修飾Annotation defineAnnoBean = beanClazz.getAnnotation(defineAnnoClazz);try {// 獲取注解的value方法值(value方法值==bean名稱) ,可以認為這里約定注解的value()值為beanNameString beanName = (String)defineAnnoBean.getClass().getDeclaredMethod("value").invoke(defineAnnoBean);// 若value方法值為空,則取類名首字母小寫beanName = !BusiStringUtils.isBlank(beanName) ? beanName : BusiStringUtils.lowerFirstChar(beanClazz.getSimpleName());// 若bean在容器中存在,直接breakif (container.containsKey(beanName)) break;// 實例化bean之前先掃描是否有屬性bean,若有被 MyAutowired修飾的屬性,則注入Object beanObj = beanClazz.newInstance();// 裝配bean屬性this.autowireField(beanClazz, beanObj);// 注入beanSpringContext.insertBean(beanName, beanObj);SpringContext.insertBean(beanClazz.getName(), beanObj);break;} catch(Exception e) {e.printStackTrace();}}}} catch (Exception e) {throw new IllegalStateException("類" + x+ "實例化失敗", e);}}}/*** @description 裝配屬性bean* @param beanClazz bean class對象* @author xiao tang* @date 2021/12/25*/private void autowireField(Class beanClazz, Object beanObj) {for (Field field : beanClazz.getDeclaredFields()) {if (field.isAnnotationPresent(MyAutowire.class)) { // 若屬性被 MyAutowire 修飾String beanNameOfMyAutowire = field.getAnnotation(MyAutowire.class).value(); // 獲取 MyAutowire的value方法值beanNameOfMyAutowire = !BusiStringUtils.isBlank(beanNameOfMyAutowire) ? beanNameOfMyAutowire : field.getType().getName(); // 獲取autowire的value方法值// 裝配beantry {// 若容器不存在該bean,則創建并放入容器if (!container.containsKey(beanNameOfMyAutowire)) {Object beanObjOfAutowire = field.getType().newInstance();// 注入屬性beanSpringContext.insertBean(beanNameOfMyAutowire, beanObjOfAutowire);SpringContext.insertBean(field.getClass().getName(), beanObjOfAutowire);}field.setAccessible(true); // 設置可以訪問field.set(beanObj, container.get(beanNameOfMyAutowire)); // 注入bean} catch (Exception e) {}}}}/*** @description 掃描所有注解* @param clazzNameList 類名列表* @return 注解集合* @author xiao tang* @date 2021/12/19*/public static Map<String, Class> scanAnnotation(List<String> clazzNameList) {/** 自定義注解名稱 */Map<String, Class> annotationMap = new HashMap<>();for (String x : clazzNameList) {try {Class clazz = Class.forName(x);if (Class.forName(x).isAnnotation()) { // 若為注解,添加到緩存annotationMap.put(clazz.getName(), clazz);}} catch (ClassNotFoundException e) {}}return annotationMap;}/*** @description 掃描所有文件* @param appRoot 根目錄* @return 類名列表* @author xiao tang* @date 2021/12/17*/public static List<String> scanAllFileClazz(String appRoot) {// class文件列表List<String> clazzFileList = new ArrayList<>();// 文件夾隊列LinkedList<File> dirList = new LinkedList<>();dirList.add(new File(appRoot));// 遍歷文件夾while (!dirList.isEmpty()) {File[] files = dirList.removeFirst().listFiles();for (File tempFile : files) {// 文件夾if (tempFile.isDirectory()) {dirList.add(tempFile);} else if (tempFile.getName().endsWith(".class")) {// 非文件夾,且以 .class 結尾,添加到文件列表clazzFileList.add(prcFilePath(tempFile.getAbsolutePath()));}}}return clazzFileList;}/*** @description 加工文件名,獲取全限定類名* @param filePath 文件絕對路徑* @return 全限定類名* @author xiao tang* @date 2021/12/25*/public static String prcFilePath(String filePath) {String flag = "target\\classes\\";return filePath.substring(filePath.indexOf(flag)+flag.length(), filePath.lastIndexOf(".class")).replace('\\', '.');} }【2.3】工具 ?
/*** @Description 字符串工具 * @author xiao tang* @version 1.0.0* @createTime 2021年12月17日*/ public class BusiStringUtils {private BusiStringUtils(){}public static String lowerFirstChar(String raw) {raw.charAt(0);char[] charArr = raw.toCharArray();charArr[0] += 32;return new String(charArr);}/** * @description 判斷字符串是否為空* @param * @return * @author xiao tang * @date 2021/12/25 */public static boolean isBlank(String raw) {if (raw ==null) return true;for (int i = 0; i < raw.length(); i++) {if (!Character.isWhitespace(raw.charAt(i))) return false;}return true; } }【2.4】 使用以上自定義容器
【2.4.1】自定義 step
/*** @Description 查詢參數* @author xiao tang* @version 1.0.0* @createTime 2021年12月25日*/ @MyStep("VWPAMQRY") public class VWPAMQRY {@MyAutowireParamDAO paramDAO;/*** @description 業務邏輯 * @param key 鍵* @return 響應報文* @author xiao tang * @date 2021/12/19 */public String doBusi(String key) {// 其他邏輯 XXX// 調用dao層api查詢參數值 return paramDAO.qryValueByKey(key);} }【2.4.2】dao 層
/*** @Description dao層 * @author xiao tang* @version 1.0.0* @createTime 2021年12月19日*/ @MyRepository("diyParamDao") public class ParamDAO {private static Map<String, String> params = new HashMap<>();static {params.put("k1", "v1");params.put("k2", "v2");}/*** @description 根據key查詢value* @param key 鍵* @return 值* @author xiao tang* @date 2021/12/19*/public String qryValueByKey(String key) {return params.get(key);} }?
【3】main程序 拉起整個應用
/*** @Description spring ioc 實現機制* @author xiao tang* @version 1.0.0* @createTime 2021年12月16日*/ public class Topic16Main {public static void main(String[] args) {SpringContext springContext = new SpringContext();// 通過clazz 獲取ParamDAO paramDAO = springContext.getBean(ParamDAO.class);String value = paramDAO.qryValueByKey("k1");System.out.println(value); // v1// 通過 name 獲取paramDAO = springContext.getBean("diyParamDao");value = paramDAO.qryValueByKey("k1");System.out.println(value); // v1// 從容器獲取 stepVWPAMQRY vwpamqry = springContext.getBean(VWPAMQRY.class);System.out.println(vwpamqry.doBusi("k1"));// v1System.out.println(vwpamqry.doBusi("k2")); // v2} }打印結果:
v1
v1
v1
v2
【Reference】
IoC容器和Dependency Injection模式 - Thoughtworks洞見https://insights.thoughtworks.cn/injection/依賴注入和控制反轉的理解,寫的太好了。_路在腳下-CSDN博客_依賴注入和控制反轉的區別學習過Spring框架的人一定都會聽過Spring的IoC(控制反轉) 、DI(依賴注入)這兩個概念,對于初學Spring的人來說,總覺得IoC 、DI這兩個概念是模糊不清的,是很難理解的,今天和大家分享網上的一些技術大牛們對Spring框架的IOC的理解以及談談我對Spring Ioc的理解。一、分享Iteye的開濤對Ioc的精彩講解 首先要分享的是Iteye的開濤這位技術牛人https://blog.csdn.net/bestone0213/article/details/47424255
總結
以上是生活随笔為你收集整理的diy实现spring依赖注入的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网页设计怎么添加gif(网页设计怎么添加
- 下一篇: java socket实现简单即时通讯