首页 > 技术文章 > jmeter系列一(jmeter界面相关无TestBean)

liliqiang 2014-12-13 11:37 原文

这系列文章是自己学习研究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                 }

 

推荐阅读