巴黎的灯光下 发表于 2017-6-22 10:28:05

使用 java 动态加载机制模拟脚本语言的效果

缘由有时我们需要测试某个方法,需要频繁的修改这个方法,但又不想重新去run整个程序,怎么做呢?
一个正常的appium测试用例package com.dynamicclassloader;

import java.io.File;
import java.net.URL;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;
import com.dynamicclassloader.DynamicEngine;
import com.my.Utils;

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.remote.AndroidMobileCapabilityType;
import io.appium.java_client.remote.AutomationName;
import io.appium.java_client.remote.MobileCapabilityType;

public class CommonTest {

    private AndroidDriver<?> driver;
    public DesiredCapabilities capabilities = new DesiredCapabilities();
    public String location = "C:/Users/test.apk";
    public String pkgName = "";
    public String activityName = "";

    public String getSN() {
      String sn = Utils.runCMD(new String[]{"adb", "devices"}).split("\n").split("\t");

      return sn;
    }

    public void updatePkgActivity() {
      String pkgInfo = Utils.runCMD(new String[]{"aapt", "dump", "badging", location});

      String pkgStart = "package: name='";
      pkgName = pkgInfo.substring(pkgInfo.indexOf(pkgStart) + pkgStart.length(), pkgInfo.indexOf("' version"));

      String activityStart = "launchable-activity: name='";
      activityName = pkgInfo.substring(pkgInfo.indexOf(activityStart) + activityStart.length(), pkgInfo.indexOf("'label=''"));

    }

    public void updateCapabilites() {
      File app = new File(location);

      capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, getSN());

      updatePkgActivity();
      capabilities.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, pkgName);
      capabilities.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, activityName);

      capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 300);

      if (Utils.runCMD(new String[]{"adb", "shell", "pm", "path", pkgName}).equals("")) {
            capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
      }

      capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2);
    }

    @Before
    public void setUp() throws Exception {
      updateCapabilites();
      driver = new AndroidDriver<WebElement>(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);

    }

    @Test
    public void stepTest() {
      TouchAction ta = new TouchAction(driver);
      ta.tap(306, 1852).perform();
    }

    @After
    public void tearDown() throws Exception {
      driver.quit();
    }

}
当我修改stepTest后,重新运行时,就会发现appium日志又刷刷地从头开始创建driver了,虽然driver已经创建完成了
动态加载方法
Utils类中访问获取程序内部资源的方法
    public String getStep(String fileName) {
      String step = "";

      InputStream is = getClass().getResourceAsStream(fileName);
      BufferedReader br = new BufferedReader(new InputStreamReader(is));
      String s = "";
      try {
            while((s = br.readLine()) != null) {
                step += s;
//            System.out.println(s);
            }
      } catch (IOException e) {
            e.printStackTrace();
      }

      return step;
    }
修改后的stepTest方法
@Test
    public void stepTest() {

      while (true) {
            String source;
            DynamicEngine de = DynamicEngine.getInstance();

            source = new Utils().getStep("AndroidStep.txt");

            try {
                System.out.println("test");
                Class clazz =de.javaCodeToObject("com.carl.AndroidStep", source);
                clazz.getMethod("runStep", AndroidDriver.class).invoke(clazz, driver);
            } catch (Exception e) {
                e.printStackTrace();
            }
      }
    }
package com.dynamicclassloader;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.tools.*;


/**
* 在Java中最好的方法是使用StandardJavaFileManager类。
* 这个类可以很好地控制输入、输出,并且可以通过DiagnosticListener得到诊断信息,
* 而DiagnosticCollector类就是listener的实现。
* 使用StandardJavaFileManager需要两步。
* 首先建立一个DiagnosticCollector实例以及通过JavaCompiler的getStandardFileManager()方法得到一个StandardFileManager对象。
* 最后通过CompilationTask中的call方法编译源程序。
*/
public class DynamicEngine {
    //单例
    private static DynamicEngine ourInstance = new DynamicEngine();

    public static DynamicEngine getInstance() {
      return ourInstance;
    }
    private URLClassLoader parentClassLoader;
    private String classpath;
    private DynamicEngine() {
      //获取类加载器
      this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();

      //创建classpath
      this.buildClassPath();
    }

    /**
   * @MethodName    : 创建classpath
   */
    private void buildClassPath() {
      this.classpath = null;
      StringBuilder sb = new StringBuilder();
      for (URL url : this.parentClassLoader.getURLs()) {
            String p = url.getFile();
            sb.append(p).append(File.pathSeparator);
      }
      this.classpath = sb.toString();
      //System.out.println("classpath:" + this.classpath);
    }

    /**
   * @MethodName    : 编译java代码到Object
   * @Description    : TODO
   * @param fullClassName   类名
   * @param javaCode类代码
   * @return Object
   * @throws Exception
   */
    public Class javaCodeToObject(String fullClassName, String javaCode) throws Exception {
      long start = System.currentTimeMillis(); //记录开始编译时间
//      Object instance = null;
      //获取系统编译器
      JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
      // 建立DiagnosticCollector对象
      DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

      // 建立用于保存被编译文件名的对象
      // 每个文件被保存在一个从JavaFileObject继承的类中
      ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));

      List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
      jfiles.add(new StringSourceJavaObject(fullClassName, javaCode));

      //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合
      List<String> options = new ArrayList<String>();
      options.add("-encoding");
      options.add("UTF-8");
      options.add("-classpath");
      options.add(this.classpath);

      JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
      // 编译源程序
      boolean success = task.call();

      Class clazz = null;
      if (success) {
            //如果编译成功,用类加载器加载该类
            JavaClassObject jco = fileManager.getJavaClassObject();
            DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
            //String source = "package com.carl.test;public class Sourceee { public static void main(String[] args) {System.out.println(\"Hello World!\");} }";
            clazz = dynamicClassLoader.loadClass(fullClassName, jco);
//            instance = clazz.newInstance();

            for (Method m: clazz.getDeclaredMethods()) {
                System.out.println("method name: " + m.getName());
//                Class<?> classType = Class.forName("java.lang.String");
//                m.invoke(clazz, Array.newInstance(classType, 0));
            }
      } else {
            //如果想得到具体的编译错误,可以对Diagnostics进行扫描
            String error = "";
            for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                error += compilePrint(diagnostic);
            }
      }
      long end = System.currentTimeMillis();

      return clazz;
//      System.out.println("javaCodeToObject use:"+(end-start)+"ms");
//      return instance;
    }

巴黎的灯光下 发表于 2017-6-22 10:28:38

/**
   * @MethodName    : compilePrint
   * @Description    : 输出编译错误信息
   * @param diagnostic
   * @return
   */
    private String compilePrint(Diagnostic diagnostic) {
      System.out.println("Code:" + diagnostic.getCode());
      System.out.println("Kind:" + diagnostic.getKind());
      System.out.println("Position:" + diagnostic.getPosition());
      System.out.println("Start Position:" + diagnostic.getStartPosition());
      System.out.println("End Position:" + diagnostic.getEndPosition());
      System.out.println("Source:" + diagnostic.getSource());
      System.out.println("Message:" + diagnostic.getMessage(null));
      System.out.println("LineNumber:" + diagnostic.getLineNumber());
      System.out.println("ColumnNumber:" + diagnostic.getColumnNumber());
      StringBuffer res = new StringBuffer();
      res.append("Code:[" + diagnostic.getCode() + "]\n");
      res.append("Kind:[" + diagnostic.getKind() + "]\n");
      res.append("Position:[" + diagnostic.getPosition() + "]\n");
      res.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
      res.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
      res.append("Source:[" + diagnostic.getSource() + "]\n");
      res.append("Message:[" + diagnostic.getMessage(null) + "]\n");
      res.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
      res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
      return res.toString();
    }
}


package com.dynamicclassloader;

import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
/**
* 将输出流交给JavaCompiler,最后JavaCompiler将编译后的class文件写入输出流中
*/
public class JavaClassObject extends SimpleJavaFileObject {

    /**
   * 定义一个输出流,用于装载JavaCompiler编译后的Class文件
   */
    protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();

    /**
   * 调用父类构造器
   * @param name
   * @param kind
   */
    public JavaClassObject(String name, Kind kind) {
      super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
    }

    /**
   * 获取输出流为byte[]数组
   * @return
   */
    public byte[] getBytes() {
      return bos.toByteArray();
    }

    /**
   * 重写openOutputStream,将我们的输出流交给JavaCompiler,让它将编译好的Class装载进来
   * @return
   * @throws IOException
   */
    @Override
    public OutputStream openOutputStream() throws IOException {
      return bos;
    }

    /**
   * 重写finalize方法,在对象被回收时关闭输出流
   * @throws Throwable
   */
    @Override
    protected void finalize() throws Throwable {
      super.finalize();
      bos.close();
    }
}

package com.dynamicclassloader;

import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

public class StringSourceJavaObject extends SimpleJavaFileObject {
    private String content = null;

    public StringSourceJavaObject(String name, String content) throws URISyntaxException {
      super(URI.create("string:///" + name.replace(".", "/") + JavaFileObject.Kind.SOURCE.extension), Kind.SOURCE);
      this.content = content;
    }

    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
      return content;
    }
}

package com.dynamicclassloader;

import java.net.URL;
import java.net.URLClassLoader;

public class DynamicClassLoader extends URLClassLoader {
    public DynamicClassLoader(ClassLoader parent) {
      super(new URL, parent);
    }

    public Class findClassByClassName(String className) throws ClassNotFoundException {
      return super.findClass(className);
    }

    public Class loadClass(String fullName, JavaClassObject jco) {
      byte[] classData = jco.getBytes();
      return super.defineClass(fullName, classData, 0, classData.length);
    }
}

package com.dynamicclassloader;


import java.io.IOException;
import javax.tools.*;

/**
* 类文件管理器
* 用于JavaCompiler将编译好后的class,保存到jclassObject中
*/
public class ClassFileManager extends ForwardingJavaFileManager {

    /**
   * 保存编译后Class文件的对象
   */
    private JavaClassObject jclassObject;

    /**
   * 调用父类构造器
   * @param standardManager
   */
    public ClassFileManager(StandardJavaFileManager standardManager) {
      super(standardManager);
    }

    /**
   * 将JavaFileObject对象的引用交给JavaCompiler,让它将编译好后的Class文件装载进来
   * @param location
   * @param className
   * @param kind
   * @param sibling
   * @return
   * @throws IOException
   */
    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling)
            throws IOException {
      if (jclassObject == null)
            jclassObject = new JavaClassObject(className, kind);
      return jclassObject;
    }

    public JavaClassObject getJavaClassObject() {
      return jclassObject;
    }
}

package com.carl;

import io.appium.java_client.TouchAction;
import io.appium.java_client.android.AndroidDriver;

public class AndroidStep {

    public static void runStep(AndroidDriver driver) {
      TouchAction ta = new TouchAction(driver);
      ta.tap(306, 1852).perform();
    }

}

主要原理
动态加载一个java类,并反射调用
参考书籍
《Java深度历险》

乐哈哈yoyo 发表于 2017-6-22 10:41:24

学习了!

巴黎的灯光下 发表于 2017-6-22 10:43:19

乐哈哈yoyo 发表于 2017-6-22 10:41
学习了!

:)
页: [1]
查看完整版本: 使用 java 动态加载机制模拟脚本语言的效果