这系列文章是自己学习研究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 @Override2 public JPopupMenu createPopupMenu() {3 return MenuFactory.getDefaultControllerMenu();4 }
看MenuFactory类
有个static块,调用了静态方法initializeMenus
1 ListguiClasses = 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 this11 */12 if (name.endsWith("JMeterTreeNode") // $NON-NLS-1$13 || name.endsWith("TestBeanGUI")) { // $NON-NLS-1$14 continue;// Don't try to instantiate these15 }16 17 if (elementsToSkip.contains(name)) { // No point instantiating class18 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 testing44 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 ListlistClasses = 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 annotations10 commands = new HashMap >(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 commandObjects = commands.get(commandName);20 if (commandObjects == null) {21 commandObjects = new HashSet ();22 commands.put(commandName, commandObjects);23 }24 commandObjects.add(command);25 }26 }
搜集实现了Command接口的各种类,这些类就是响应菜单单击事件的类,他们都在org.apache.jmeter.gui.action包下,当然我们可以实现我们自己的Command,
简单的看其中一个 AboutCommand
1 static { 2 HashSetcommands = new HashSet (); 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 avoided10 * if at all possible.11 */12 @Override13 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 @Override23 public Set getActionNames() {24 return AboutCommand.commandSet;25 }
doAction方法处理单击事件的地方
getActionNames方法是ActionRouter用来调用的,
ActionRouter中收集Command的代码
1 for (String commandName : command.getActionNames()) {2 SetcommandObjects = commands.get(commandName);3 if (commandObjects == null) {4 commandObjects = new HashSet ();5 commands.put(commandName, commandObjects);6 }7 commandObjects.add(command);8 }