这系列文章是自己学习研究jmeter源码后的结果,这篇文章介绍的是jmeter界面以及事件响应相关的东西,写下来分享也利于回头看,
下图是jmeter源码导入neatbeans
涉及到跟gui相关的内容大体都在core里面的gui包下
涉及到的类主要有
AbstractJMeterGuiComponent
GUIFactory
GuiPackage
MainFrame
MenuFactory 动态加载类路径下继承了AbstractJmeterGuiComponent或者实现了TestBean接口的类,他们就是MainFrame中右边的Frame
实现TestBean接口的类会有个BeanInfo于之相对应,这是javabean规范,重点是用到了Introspector
gui.action包下面
Command和各种实现了Command接口的类
ActionRouter 动态加载实现了Command接口的类,通过ActionEvent的name来匹配是哪个类响应MainFrame中左边树的相关事件
gui.tree包下面
MainFrame左边的树,重点是TreeNode存了各种testelement,比如controller、sampler、preprocess、config etc.当运行测试的时候,程序就读取里面的各种testelement元素来执行测试流程
界面涉及相关流程
实例化树
MainFrame实例化的时候,会通过调用makeTree方法传入JMeterTreeModel和JMeterTreeListener构建一个JTree,初始化有两个节点
1 private void initTree(TestElement tp, TestElement wb) { 2 // Insert the test plan node 3 insertNodeInto(new JMeterTreeNode(tp, this), (JMeterTreeNode) getRoot(), 0); 4 // Insert the workbench node 5 insertNodeInto(new JMeterTreeNode(wb, this), (JMeterTreeNode) getRoot(), 1); 6 // Let others know that the tree content has changed. 7 // This should not be necessary, but without it, nodes are not shown when the user 8 // uses the Close menu item 9 nodeStructureChanged((JMeterTreeNode)getRoot()); 10 }
这是JMeterTreeModel中的方法,这个方法会被JMeterTreeModel构造方法调用
1 public JMeterTreeModel() { 2 this(new TestPlanGui().createTestElement(),new WorkBenchGui().createTestElement()); 3 4 }
TestPlanGui和WorkBeanchGui都是继承了AbstractJMeterGuiComponent的类,这是MainFrame中右边界面元素都要继承的类
里面有通用发放,界面执行流程会调用到
tree响应鼠标右键事件弹出菜单
看JMeterTreeListener类,实现了TreeSelectionListener, MouseListener, KeyListener接口
因此响应鼠标右键的事件就是在这处理的
1 @Override 2 public void mousePressed(MouseEvent e) { 3 // Get the Main Frame. 4 MainFrame mainFrame = GuiPackage.getInstance().getMainFrame(); 5 // Close any Main Menu that is open 6 mainFrame.closeMenu(); 7 int selRow = tree.getRowForLocation(e.getX(), e.getY()); 8 if (tree.getPathForLocation(e.getX(), e.getY()) != null) { 9 log.debug("mouse pressed, updating currentPath"); 10 currentPath = tree.getPathForLocation(e.getX(), e.getY()); 11 } 12 if (selRow != -1) { 13 // updateMainMenu(((JMeterGUIComponent) 14 // getCurrentNode().getUserObject()).createPopupMenu()); 15 if (isRightClick(e)) { 16 if (tree.getSelectionCount() < 2) { 17 tree.setSelectionPath(currentPath); 18 } 19 log.debug("About to display pop-up"); 20 displayPopUp(e);//这里 21 } 22 } 23 }
1 private void displayPopUp(MouseEvent e) { 2 JPopupMenu pop = getCurrentNode().createPopupMenu(); 3 GuiPackage.getInstance().displayPopUp(e, pop); 4 }
JMeterTreeNode中的createPopupMenu方法
1 public JPopupMenu createPopupMenu() { 2 try { 3 return GuiPackage.getInstance().getGui(getTestElement()).createPopupMenu(); 4 } catch (Exception e) { 5 log.error("Can't get popup menu for gui", e); 6 return null; 7 } 8 }
最后shift+鼠标来到了JMeterGUIComponent接口,恩好吧,这是IDE
找一个实现了这个接口的类,AbstractControllerGui....
1 @Override 2 public JPopupMenu createPopupMenu() { 3 return MenuFactory.getDefaultControllerMenu(); 4 }
看MenuFactory类
有个static块,调用了静态方法initializeMenus
1 List<String> guiClasses = ClassFinder.findClassesThatExtend(JMeterUtils.getSearchPaths(), new Class[] { 2 JMeterGUIComponent.class, TestBean.class }); 3 Collections.sort(guiClasses); 4 for (String name : guiClasses) { 5 6 /* 7 * JMeterTreeNode and TestBeanGUI are special GUI classes, and 8 * aren't intended to be added to menus 9 * 10 * TODO: find a better way of checking this 11 */ 12 if (name.endsWith("JMeterTreeNode") // $NON-NLS-1$ 13 || name.endsWith("TestBeanGUI")) {// $NON-NLS-1$ 14 continue;// Don't try to instantiate these 15 } 16 17 if (elementsToSkip.contains(name)) { // No point instantiating class 18 log.info("Skipping " + name); 19 continue; 20 } 21 22 boolean hideBean = false; // Should the TestBean be hidden? 23 24 JMeterGUIComponent item; 25 try { 26 Class<?> c = Class.forName(name); 27 if (TestBean.class.isAssignableFrom(c)) { 28 TestBeanGUI tbgui = new TestBeanGUI(c); 29 hideBean = tbgui.isHidden() || (tbgui.isExpert() && !JMeterUtils.isExpertMode()); 30 item = tbgui; 31 } else { 32 item = (JMeterGUIComponent) c.newInstance(); 33 } 34 } catch (NoClassDefFoundError e) { 35 log.warn("Missing jar? Could not create " + name + ". " + e); 36 continue; 37 } catch (Throwable e) { 38 log.warn("Could not instantiate " + name, e); 39 if (e instanceof Error){ 40 throw (Error) e; 41 } 42 if (e instanceof RuntimeException){ 43 if (!(e instanceof HeadlessException)) { // Allow headless testing 44 throw (RuntimeException) e; 45 } 46 } 47 continue; 48 }
会扫描实现了JMeterGUIComponent的接口或者实现了TestBean接口(上面提到的利用了JavaBean规范,BeanInfo哦)的类,
首先Class.forName他们
对于实现接口TestBean的类,会用TestBeanGUI进行包装(我能说这是装饰模式吗?)
然后调用getMenuCategories方法(实现了JMeterGUIComponent接口都会实现这个方法),
这个方法就是告诉当在currentNode上右键鼠标,针对于这个node会弹出的菜单即所谓的PopupMenu
1 if (categories == null) { 2 log.debug(name + " participates in no menus."); 3 continue; 4 } 5 if (categories.contains(THREADS)) { 6 threads.add(new MenuInfo(item, name)); 7 } 8 if (categories.contains(FRAGMENTS)) { 9 fragments.add(new MenuInfo(item, name)); 10 } 11 if (categories.contains(TIMERS)) { 12 timers.add(new MenuInfo(item, name)); 13 } 14 15 if (categories.contains(POST_PROCESSORS)) { 16 postProcessors.add(new MenuInfo(item, name)); 17 } 18 19 if (categories.contains(PRE_PROCESSORS)) { 20 preProcessors.add(new MenuInfo(item, name)); 21 } 22 23 if (categories.contains(CONTROLLERS)) { 24 controllers.add(new MenuInfo(item, name)); 25 } 26 27 if (categories.contains(SAMPLERS)) { 28 samplers.add(new MenuInfo(item, name)); 29 } 30 31 if (categories.contains(NON_TEST_ELEMENTS)) { 32 nonTestElements.add(new MenuInfo(item, name)); 33 } 34 35 if (categories.contains(LISTENERS)) { 36 listeners.add(new MenuInfo(item, name)); 37 } 38 39 if (categories.contains(CONFIG_ELEMENTS)) { 40 configElements.add(new MenuInfo(item, name)); 41 } 42 if (categories.contains(ASSERTIONS)) { 43 assertions.add(new MenuInfo(item, name)); 44 }
然后对应TestElement的gui类中的createPopUp方法就会在这里来取他们对应的菜单项目
弹出菜单项的响应鼠标单击事件
看MenuFactory中的各种makeMenus方法,makeMenuItem是最小项即创建每个弹出菜单最后一项内容
1 public static JMenuItem makeMenuItem(String label, String name, String actionCommand) { 2 JMenuItem newMenuChoice = new JMenuItem(label); 3 newMenuChoice.setName(name); 4 newMenuChoice.addActionListener(ActionRouter.getInstance()); 5 if (actionCommand != null) { 6 newMenuChoice.setActionCommand(actionCommand); 7 } 8 9 return newMenuChoice; 10 }
newMenuChoice.addActionListener(ActionRouter.getInstance());
这句话设置了每个菜单项响应事件的监听器,已经actioncommand即该菜单项的command名字
定位到ActionRouter类,他是一个单例类,看他的方法populateCommandMap
1 List<String> listClasses = ClassFinder.findClassesThatExtend( 2 JMeterUtils.getSearchPaths(), // strPathsOrJars - pathnames or jarfiles to search for classes 3 // classNames - required parent class(es) or annotations 4 new Class[] {Class.forName("org.apache.jmeter.gui.action.Command") }, // $NON-NLS-1$ 5 false, // innerClasses - should we include inner classes? 6 null, // contains - classname should contain this string 7 // Ignore the classes which are specific to the reporting tool 8 "org.apache.jmeter.report.gui", // $NON-NLS-1$ // notContains - classname should not contain this string 9 false); // annotations - true if classnames are annotations 10 commands = new HashMap<String, Set<Command>>(listClasses.size()); 11 if (listClasses.isEmpty()) { 12 log.fatalError("!!!!!Uh-oh, didn't find any action handlers!!!!!"); 13 throw new JMeterError("No action handlers found - check JMeterHome and libraries"); 14 } 15 for (String strClassName : listClasses) { 16 Class<?> commandClass = Class.forName(strClassName); 17 Command command = (Command) commandClass.newInstance(); 18 for (String commandName : command.getActionNames()) { 19 Set<Command> commandObjects = commands.get(commandName); 20 if (commandObjects == null) { 21 commandObjects = new HashSet<Command>(); 22 commands.put(commandName, commandObjects); 23 } 24 commandObjects.add(command); 25 } 26 }
搜集实现了Command接口的各种类,这些类就是响应菜单单击事件的类,他们都在org.apache.jmeter.gui.action包下,当然我们可以实现我们自己的Command,
简单的看其中一个 AboutCommand
1 static { 2 HashSet<String> commands = new HashSet<String>(); 3 commands.add(ActionNames.ABOUT); 4 commandSet = Collections.unmodifiableSet(commands); 5 } 6 7 /** 8 * Handle the "about" action by displaying the "About Apache JMeter..." 9 * dialog box. The Dialog Box is NOT modal, because those should be avoided 10 * if at all possible. 11 */ 12 @Override 13 public void doAction(ActionEvent e) { 14 if (e.getActionCommand().equals(ActionNames.ABOUT)) { 15 this.about(); 16 } 17 } 18 19 /** 20 * Provide the list of Action names that are available in this command. 21 */ 22 @Override 23 public Set<String> getActionNames() { 24 return AboutCommand.commandSet; 25 }
doAction方法处理单击事件的地方
getActionNames方法是ActionRouter用来调用的,
ActionRouter中收集Command的代码
1 for (String commandName : command.getActionNames()) { 2 Set<Command> commandObjects = commands.get(commandName); 3 if (commandObjects == null) { 4 commandObjects = new HashSet<Command>(); 5 commands.put(commandName, commandObjects); 6 } 7 commandObjects.add(command); 8 }