• openpnp - src - 配置文件载入过程的初步分析


    openpnp - src - 配置文件载入过程的初步分析

    概述

    从openpnp - dev - 2022_0801那天最后的代码, 编译除了一份可执行程序给自己用.
    用官方打包的发布版 dev - 2022_0801运行, 居然读取我自己版本的配置文件失败.
    将自己打包的那份dev-2022_0801, 再将版本退一个版本, 也会读取配置文件失败. 估计官方打包没用dev-2022_0801那天的最后版本.

    通过git版本比对, 结合报错信息, 大概知道是新版本有个类, 添加了一个新属性, 导致旧版本读取不了新版本增加的属性时, 因为没有那个旧类没有那个新增加的属性, 导致抛出异常.

    作为新手, 想弄清为啥读取配置文件报错, 错在哪里. 顺便看一下openpnp序列化配置文件的过程. 多一些了解, 总是没错的…

    笔记

    自己编译用的git版本

    在这里插入图片描述
    不太理解为啥官方打包的版本是2022/8/1下午的时间, 但是却没采用2022/8/1那天最新的实现. 而且提交时间只差了3分钟, 还是同一个人提交的, 迷惑…

    编译openpnp工程的过程已经做了笔记(openpnp - 软件调试环境搭建).

    报错截图

    先用官方版本的dev-2022/8/1的发布包标定的设备.

    后来发现openpnp有点bug, 修正了, 此时正常用, 但是配置文件被新版本(比官方版本晚3分钟的git版本, 修正后, 重新编译)改了, machine.xml中的视觉方案类中多了一个类属性.

    偶然用官方发布的dev-2022_0801实验, 才发现报错如下:
    在这里插入图片描述
    根据报错提示, 可以知道 machine.xml中有个属性 settle-test-move-mm 在类org.openpnp.machine.referene.solutions.VisionSolutions中没有, 导致了抛异常报错.

    看到这个报错后, 有2个想法:

    • 具体报错点在哪里? (等以后出现版本和配置文件不匹配时, 可以迅速定位到发生异常的类, 还可以知道报错前, 哪里是抛异常的第一现场?)
    • openpnp配置文件有4个xml, 这些配置文件读取的流程大致是从哪里到哪里?

    用了一天, 将上面2个问题搞定了. 心里舒坦多了.

    问题1 - 怎么在调试状态下, 定位到抛异常的第一现场?

    我是第一次调试这个问题, 不断的下条件断点. 最后可以2步就到达报错现场. 如果不设置条件断点, 那基本就是不可调试的.

    在这里插入图片描述
    在这里插入图片描述
    F8步过loadMachine(file), 如果没有其他条件断点, 就会直接进入437行的抛异常实现, 就会看到报错对话框.
    F7步入loadMachine(file), 先F8, 如果哪步会导致进入437行报错断点, 再F7那个函数. 结合条件断点, 就能定位到抛异常的第一现场.
    然后 CTRL + ALT + 向左的箭头, 就可以回到最后一次F8的代码实现. 然后停掉程序, 看看是不是第一现场, F7, F8, 直到找到第一现场.

    当断住后, 如果不是我们要的调试代码, 按F9, 让程序继续跑, 命中我们后续的断点继续调试. 如果确定断点没用了, 就在一个断点上右击, 进入断点编辑器, 禁止掉不用的断点(不用删除, 可能找调试痕迹还有用).

    在这里插入图片描述
    这里就是找到的抛异常的第一现场.
    2个条件断点都一样, 如下:

    ((null != name) && (19 == name.length()) && (0x73 == name.value[0]))
    
    • 1

    这个条件断点, 是找name == “visual-solutions”, 为了简单, 值判断了name的长度和第一个字符, 已经可以正确断住了. 如果只判断一个字符值不好使, 那就再挑一个字符值, 看情况, 条件写的越少越好.

    在IDEA中下字符串的条件断点的方法已经做了笔记(java - IDEA IDE - 设置字符串断点).

    因为报错的第一现场是在第三方jar包中, 只读的无法添加调试代码, 所以下条件断点能捕获到调试点就行.

    结合单步调试找到的现场, 来分析报错的原因

    第一现场的代码如下:

       private void readAttribute(InputNode node, Object source, Section section, LabelMap map) throws Exception {
          String name = node.getName(); // 取节点名称
          String path = section.getAttribute(name); // 取属性的path
          // map这里是类实际的属性值的集合
          // path是xml中指定的这个类的属性名称
          Label label = map.getLabel(path); // 拿属性的path去取属性的值
          
          if(label == null) {
          	// 这里是没取到属性的值, 也就是旧版本openpnp读取新版本配置文件, 因为具体的类没有这个属性, 自然无法将xml中规定的类属性设置到类的到属性成员中.
          	// 这里就是抛异常的第一现场
             Position line = node.getPosition(); // 报错的行数, 这里取的值不准, 没有参考价值. 文件是应该包含该属性的类, 行数(line)根本不对. 不怪IDEA, 因为这个属性就不存在, 用属性的代码行数自然也不对.
             Class expect = context.getType(type, source); // 好像是发生错误的类名称
    
    		// 如果map是要求严格匹配(类的属性和xml中必须严格对应), 缓存也是要求严格匹配的, 就抛出异常报错.
             if(map.isStrict(context) && revision.isEqual()) {   
             	// 然后就进入这里, 抛异常, 异常的内容就在报错框中被我们看到           
                throw new AttributeException("Attribute '%s' does not have a match in %s at %s", path, expect, line);
             }            
          } else {
             readInstance(node, source, label);
          }         
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    openpnp配置文件读取的流程

    只看用户空间的代码(主干函数), 进了第三方库的实现就不看了.

    从main函数开始
    D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\Main.java

        public static void main(String[] args) {
    
    
            monkeyPatchBeansBinding();
            
            for (String s : args) {
                if (s.equals("--version")) {
                    System.out.println(getVersion());
                    System.exit(0);
                }
            }
            
            // http://developer.apple.com/library/mac/#documentation/Java/Conceptual/Java14Development/07-NativePlatformIntegration/NativePlatformIntegration.html#//apple_ref/doc/uid/TP40001909-212952-TPXREF134
            System.setProperty("apple.laf.useScreenMenuBar", "true");
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            }
            catch (Exception e) {
                throw new Error(e);
            }
    
            File configurationDirectory = new File(System.getProperty("user.home"));
            configurationDirectory = new File(configurationDirectory, ".openpnp2");
    
            if (System.getProperty("configDir") != null) {
                configurationDirectory = new File(System.getProperty("configDir"));
            }
    
            configurationDirectory.mkdirs();
    
            configureLogging(configurationDirectory);
    
            Configuration.initialize(configurationDirectory);
            final Configuration configuration = Configuration.get();
            Locale.setDefault(Configuration.get().getLocale());
    
            ThemeInfo theme = configuration.getThemeInfo();
            new ThemeSettingsPanel().setTheme(theme, configuration.getFontSize(), configuration.isAlternateRows());
            ThemeDialog.getInstance().setOldTheme(theme);
    
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    try {
                        MainFrame frame = new MainFrame(configuration); // 进入MainFrame实现读配置
                        frame.setVisible(true);
                        Logger.info(String.format("Bienvenue, Bienvenido, Willkommen, Hello, Namaskar, Welkom, Bonjour to OpenPnP version %s.", Main.getVersion()));
                        configuration.getScripting().on("Startup", null);
                    }
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\gui\MainFrame.java

    public MainFrame(Configuration configuration) {
    // 巨大的一个函数, 只看相关实现
    // ...
            registerBoardImporters();
    
            addComponentListener(componentListener);
            
            boolean configurationLoaded = false;
            while (!configurationLoaded) {
    	        try {
    	            configuration.load(); // 这里载入配置文件(4个.xml), 如果配置文件过不去, 就会抛一场.
    	            scriptFileWatcher = new ScriptFileWatcher(configuration.getScripting());
    	            scriptFileWatcher.setMenu(mnScripts);
    	            
    	            if (Configuration.get().getMachine().getProperty("Welcome2_0_Dialog_Shown") == null) {
    	                Welcome2_0Dialog dialog = new Welcome2_0Dialog(this);
    	                dialog.setSize(750, 550);
    	                dialog.setLocationRelativeTo(null);
    	                dialog.setModal(true);
    	                dialog.setVisible(true);
    	                Configuration.get().getMachine().setProperty("Welcome2_0_Dialog_Shown", true);
    	            }
    	            configurationLoaded = true;    
    	        }
    	        catch (Exception e) {
    	        	// 这里就是用户看到报错提示框的地方
    	            e.printStackTrace();
    	            if (!MessageBoxes.errorBoxWithRetry(this, "Configuration Load Error", //$NON-NLS-1$
    	                    "There was a problem loading the configuration. The reason was:

    "
    //$NON-NLS-1$ + e.getMessage() + "

    "
    //$NON-NLS-1$ + "Please check your configuration files and try again. They are located at: " //$NON-NLS-1$ + configuration.getConfigurationDirectory().getAbsolutePath() + "

    "
    //$NON-NLS-1$ + "If you would like to start with a fresh configuration, just delete the entire directory at the location above.

    "
    //$NON-NLS-1$ + "Retry loading (else openpnp will exit) ?")) { //$NON-NLS-1$ System.exit(1); } } } splitWindows(); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    D:\my_openpnp\openpnp_github\src\main\java\org\openpnp\model\Configuration.java

    	// 用户空间读取配置的函数
    	// .xml和具体的类是对应的, 就是序列化xml文件到类的属性
        public synchronized void load() throws Exception {
            boolean forceSave = false;
            boolean overrideUserConfig = Boolean.getBoolean("overrideUserConfig");
    
    		// 1. 读取packages.xml到具体类的属性
            try {
                File file = new File(configurationDirectory, "packages.xml");
                if (overrideUserConfig || !file.exists()) {
                    Logger.info("No packages.xml found in configuration directory, loading defaults.");
                    file = File.createTempFile("packages", "xml");
                    FileUtils.copyURLToFile(ClassLoader.getSystemResource("config/packages.xml"), file);
                    forceSave = true;
                }
                loadPackages(file);
            }
            catch (Exception e) {
                String message = e.getMessage();
                if (e.getCause() != null && e.getCause().getMessage() != null) {
                    message = e.getCause().getMessage();
                }
                throw new Exception("Error while reading packages.xml (" + message + ")", e);
            }
    
    		// 2. 读取parts.xml到具体类的属性
            try {
                File file = new File(configurationDirectory, "parts.xml");
                if (overrideUserConfig || !file.exists()) {
                    Logger.info("No parts.xml found in configuration directory, loading defaults.");
                    file = File.createTempFile("parts", "xml");
                    FileUtils.copyURLToFile(ClassLoader.getSystemResource("config/parts.xml"), file);
                    forceSave = true;
                }
                loadParts(file);
            }
            catch (Exception e) {
                String message = e.getMessage();
                if (e.getCause() != null && e.getCause().getMessage() != null) {
                    message = e.getCause().getMessage();
                }
                throw new Exception("Error while reading parts.xml (" + message + ")", e);
            }
    
    		// 3. 读取vision-settings.xml到具体类的属性
            try {
                File file = new File(configurationDirectory, "vision-settings.xml");
                if (overrideUserConfig || !file.exists()) {
                    Logger.info("No vision-settings.xml found in configuration directory, loading defaults.");
                    file = File.createTempFile("visionSettings", "xml");
                    FileUtils.copyURLToFile(ClassLoader.getSystemResource("config/vision-settings.xml"), file);
                    forceSave = true;
                }
                loadVisionSettings(file);
            }
            catch (Exception e) {
                String message = e.getMessage();
                if (e.getCause() != null && e.getCause().getMessage() != null) {
                    message = e.getCause().getMessage();
                }
                throw new Exception("Error while reading vision-settings.xml (" + message + ")", e);
            }
    
    		// 4. 读取machine.xml到具体类的属性
    		// 这次调试, 就能看到是在读取machine.xml配置文件时, 抛异常.
            try {
                File file = new File(configurationDirectory, "machine.xml");
                if (overrideUserConfig || !file.exists()) {
                    Logger.info("No machine.xml found in configuration directory, loading defaults.");
                    file = File.createTempFile("machine", "xml");
                    FileUtils.copyURLToFile(ClassLoader.getSystemResource("config/machine.xml"), file);
                    forceSave = true;
                }
                loadMachine(file); // 这里具体都配置文件
            }
            catch (Exception e) {
                String message = e.getMessage();
                if (e.getCause() != null && e.getCause().getMessage() != null) {
                    message = e.getCause().getMessage();
                }
                throw new Exception("Error while reading machine.xml (" + message + ")", e);
            }
    
            loaded = true;
    
            // Tell all listeners the configuration is loaded. Use a snapshot of the list in order to tolerate new
            // listener additions that may happen through object migration.
            for (ConfigurationListener listener : new ArrayList<>(listeners)) {
                listener.configurationLoaded(this);
            }
    
            if (forceSave) {
                Logger.info("Defaults were loaded. Saving to configuration directory.");
                configurationDirectory.mkdirs();
                save();
            }
    
            for (ConfigurationListener listener : listeners) {
                listener.configurationComplete(this);
            }
        }
    
    	// 下面是保存配置文件的函数
        public synchronized void save() throws Exception {
            LocalDateTime now = LocalDateTime.now();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
        private void loadMachine(File file) throws Exception {
            Serializer serializer = createSerializer();
            // serializer.read 已经离开用户代码, 去了第三方库中.
            // 如果不是调试代码, 就不用去看了.
            MachineConfigurationHolder holder = serializer.read(MachineConfigurationHolder.class, file);
            machine = holder.machine;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    END

  • 相关阅读:
    状态管理Pinia
    0143__c++filt(1) command
    C++类模板中如何调用其基类模板中的函数
    Go语言中开启和退出协程
    使用oracle虚拟机添加新硬盘
    十四、Docker的基本操作
    [激光原理与应用-28]:《激光原理与技术》-14- 激光产生技术 - 激光的主要参数与指标
    结合Elementplus源码讲解BEM的使用
    除了SD Web UI 或comfyUI,还有更简单的运行SDXL的方法吗?
    面试 - react - Hooks
  • 原文地址:https://blog.csdn.net/LostSpeed/article/details/134096650