使用RelProxy提高Java开发效率

jopen 6年前

RelProxy 旨在通过下列两种方式提高开发效率:

可以在生产环境下修改用户代码,而不需要重新加载整个应用。

提高开发效率,避免花费过多的时间加载应用且对性能不会有影响。

两个目标都要求在你的应用中增加一些 RelProxy 代码,注册成一种典型的监听、回调模式。这是一种“侵入”的方式。

如果你是一名Java 框架或独立 Java 通用服务模块的开发者,可以将 RelProxy Java 嵌入到你的框架中,这样能透明地为框架的终端用户提供代码自动加载功能,只需要进行一些必要的配置,而无需调用 RelProxy API。

对使用 Java 版的 RelProxy,有两种 API 可供调用:

JProxy 及其相关类:主要是静态方法

Java 脚本 API:基于接口

第二种方式更适合将 RelProxy 嵌入到你的 Java 框架中,这种方式是基于接口的,在你的 API 中无需暴露公共 RelProxy 类,因为在框架中会执行启动程序。我将使用比较简单的 API:JProxyScriptEngineFactory.create()。

JProxyScriptEngine 的功能与 Jproxy 相同,也就是说具有相同的方法。只是这种情况下,只需要使用接口。

一个简单的例子是演示如何嵌入 RelProxy 的最好方式。这个例子是 RelProxy 的示例仓库中包含的 RelProxyBuiltin(relproxy_builtin_ex 项目中)。它定义了两个监听器来实现注册用户端的代码,一个监听器显示选项(option),另一个执行选择的行为。

这个迷你框架和示例使用 NetBeans 和 Maven 开发完成。

有两个包:

com.innowhere.relproxy_builtin_ex :迷你框架。子包 com.innowhere.relproxy_builtin_ex.impl 只包含一个非公共的类。

com.innowhere.relproxy_builtin_ex_main :一个简单的使用示例。

迷你框架(公共类和接口):

RelProxyBuiltinRoot.java

package com.innowhere.relproxy_builtin_ex;  import com.innowhere.relproxy_builtin_ex.impl.RelProxyBuiltinImpl;  public class RelProxyBuiltinRoot  {      private final static RelProxyBuiltinImpl SINGLETON = new RelProxyBuiltinImpl();      public static RelProxyBuiltin get()      {          return SINGLETON;      }  }

RelProxyBuiltin.java

package com.innowhere.relproxy_builtin_ex;  import com.innowhere.relproxy.jproxy.JProxyScriptEngine;  import java.io.InputStream;  import java.io.PrintStream;  public interface RelProxyBuiltin  {      public JProxyScriptEngine getJProxyScriptEngine();      public void addOutputListener(OutputListener listener);      public void removeOutputListener(OutputListener listener);      public int getOutputListenerCount();      public void addCommandListener(CommandListener listener);      public void removeCommandListener(CommandListener listener);      public int getCommandListenerCount();      public void runLoop(InputStream in,PrintStream out);  }

OutputListener.java

package com.innowhere.relproxy_builtin_ex;  import java.io.PrintStream;  public interface OutputListener  {      public void write(PrintStream out);  }

CommandListener.java

package com.innowhere.relproxy_builtin_ex;  import java.io.PrintStream;  public interface CommandListener  {      public void execute(String command,String input,PrintStream out);  }

现在看一下实现细节,该类演示了怎样简单地内嵌 RelProxy:

RelProxyBuiltinImpl.java

package com.innowhere.relproxy_builtin_ex.impl;  import com.innowhere.relproxy.jproxy.JProxyScriptEngine;  import com.innowhere.relproxy.jproxy.JProxyScriptEngineFactory;  import com.innowhere.relproxy_builtin_ex.CommandListener;  import com.innowhere.relproxy_builtin_ex.OutputListener;  import com.innowhere.relproxy_builtin_ex.RelProxyBuiltin;  import java.io.InputStream;  import java.io.PrintStream;  import java.util.LinkedHashSet;  import java.util.Scanner;  public class RelProxyBuiltinImpl implements RelProxyBuiltin  {      protected JProxyScriptEngine jProxyEngine = null;      protected LinkedHashSet<OutputListener> outListeners = new LinkedHashSet<OutputListener>();      protected LinkedHashSet<CommandListener>  commandListeners = new LinkedHashSet<CommandListener>();      @Override      public JProxyScriptEngine getJProxyScriptEngine()      {          if (jProxyEngine == null) jProxyEngine = (JProxyScriptEngine)JProxyScriptEngineFactory.create().getScriptEngine();          return jProxyEngine;      }      public JProxyScriptEngine getJProxyScriptEngineIfConfigured()      {          if (jProxyEngine == null || !jProxyEngine.isEnabled())              return null;          return jProxyEngine;      }      @Override      public void addOutputListener(OutputListener listener)      {          JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();          if (jProxy != null)          {              listener = jProxy.create(listener,OutputListener.class);          }          outListeners.add(listener);      }      @Override      public void removeOutputListener(OutputListener listener)      {          JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();          if (jProxy != null)          {              listener = jProxy.create(listener,OutputListener.class);          }          outListeners.remove(listener);      }      @Override      public int getOutputListenerCount()      {          return outListeners.size();      }      @Override      public void addCommandListener(CommandListener listener)      {          JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();          if (jProxy != null)          {              listener = jProxy.create(listener,CommandListener.class);          }          commandListeners.add(listener);      }      @Override      public void removeCommandListener(CommandListener listener)      {          JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();          if (jProxy != null)          {              listener = jProxy.create(listener,CommandListener.class);          }          commandListeners.remove(listener);      }      @Override      public int getCommandListenerCount()      {          return commandListeners.size();      }      @Override      public void runLoop(InputStream in,PrintStream out)      {          Scanner scanner = new Scanner(in);          while(true)          {              out.print("Enter phrase:");              String input = scanner.nextLine();              out.println("Command list:");              for(OutputListener listener : outListeners)                  listener.write(out);              out.print("Enter command (or quit):");              String command = scanner.nextLine();              if ("quit".equals(command))                  break;              for(CommandListener listener : commandListeners)                  listener.execute(command,input,out);          }      }  }

这三个方法足以解释怎样启动 RelProxy Java 引擎,怎样简单地使用指令监听器来注册热加载。

RelProxyBuiltinImpl.java (部分)

  @Override      public JProxyScriptEngine getJProxyScriptEngine()      {          if (jProxyEngine == null) jProxyEngine = (JProxyScriptEngine)JProxyScriptEngineFactory.create().getScriptEngine();          return jProxyEngine;      }      public JProxyScriptEngine getJProxyScriptEngineIfConfigured()      {          if (jProxyEngine == null || !jProxyEngine.isEnabled())              return null;          return jProxyEngine;      }      @Override      public void addOutputListener(OutputListener listener)      {          JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();          if (jProxy != null)          {              listener = jProxy.create(listener,OutputListener.class);          }          outListeners.add(listener);      }

公共方法 RelProxyBuiltin.getJProxyScriptEngine() 必须在启动时执行,用于配置 RelProxy。如果没有配置,RelProxy 就不起作用。

请记住,通过 create(…) 创建的代理对象需要能正确的执行 hashCode() 方法和 equals(Object) 方法,监听器集合、监听记录依赖这两个方法来区别监听器对象。

这是基于控制台的示例代码(名称与 JUnit 类似,但确实不是 JUnit 的测试示例):

Main.java

package com.innowhere.relproxy_builtin_ex_main;  import com.innowhere.relproxy.RelProxyOnReloadListener;  import com.innowhere.relproxy.jproxy.JProxy;  import com.innowhere.relproxy.jproxy.JProxyCompilerListener;  import com.innowhere.relproxy.jproxy.JProxyConfig;  import com.innowhere.relproxy.jproxy.JProxyDiagnosticsListener;  import com.innowhere.relproxy.jproxy.JProxyInputSourceFileExcludedListener;  import com.innowhere.relproxy.jproxy.JProxyScriptEngine;  import com.innowhere.relproxy_builtin_ex.CommandListener;  import com.innowhere.relproxy_builtin_ex.RelProxyBuiltin;  import com.innowhere.relproxy_builtin_ex.RelProxyBuiltinRoot;  import java.io.File;  import java.lang.reflect.Method;  import java.net.URL;  import java.util.Arrays;  import java.util.List;  import javax.tools.Diagnostic;  import javax.tools.DiagnosticCollector;  import javax.tools.JavaFileObject;  public class Main  {      public static void main(String[] args) throws Exception      {          new Main();      }      public Main()      {          // Note: NetBeans Console window works bad (no input) with Maven Test tasks http://stackoverflow.com/questions/3035351/broken-console-in-maven-project-using-netbeans          // this is why is not a really JUnit test.          setUp();          try          {              mainTest();          }          finally          {              tearDown();          }          System.exit(0);      }      public void setUp()      {          URL res = this.getClass().getResource("/"); // .../target/classes/          // Use example of RelProxy in development time:          String inputPath = res.getFile() + "/../../src/main/java/";          if (new File(inputPath).exists())          {              System.out.println("RelProxy to be enabled, development mode detected");          }          else          {              System.out.println("RelProxy disabled, production mode detected");              return;          }          JProxyInputSourceFileExcludedListener excludedListener = new JProxyInputSourceFileExcludedListener()          {              @Override              public boolean isExcluded(File file, File rootFolderOfSources)              {                  String absPath = file.getAbsolutePath();                  if (file.isDirectory())                  {                      return absPath.endsWith(File.separatorChar + "relproxy_builtin_ex");                  }                  else                  {                      return absPath.endsWith(File.separatorChar + Main.class.getSimpleName() + ".java");                  }              }          };          String classFolder = null; // Optional          Iterable<String> compilationOptions = Arrays.asList(new String[]{"-source","1.6","-target","1.6"});          long scanPeriod = 1000;          RelProxyOnReloadListener proxyListener = new RelProxyOnReloadListener() {              @Override              public void onReload(Object objOld, Object objNew, Object proxy, Method method, Object[] args) {                  System.out.println("Reloaded " + objNew + " Calling method: " + method);              }          };          JProxyCompilerListener compilerListener = new JProxyCompilerListener(){              @Override              public void beforeCompile(File file)              {                  System.out.println("Before compile: " + file);              }              @Override              public void afterCompile(File file)              {                  System.out.println("After compile: " + file);              }          };          JProxyDiagnosticsListener diagnosticsListener = new JProxyDiagnosticsListener()          {              @Override              public void onDiagnostics(DiagnosticCollector<JavaFileObject> diagnostics)              {                  List<Diagnostic<? extends JavaFileObject>> diagList = diagnostics.getDiagnostics();                  int i = 1;                  for (Diagnostic diagnostic : diagList)                  {                     System.err.println("Diagnostic " + i);                     System.err.println("  code: " + diagnostic.getCode());                     System.err.println("  kind: " + diagnostic.getKind());                     System.err.println("  line number: " + diagnostic.getLineNumber());                     System.err.println("  column number: " + diagnostic.getColumnNumber());                     System.err.println("  start position: " + diagnostic.getStartPosition());                     System.err.println("  position: " + diagnostic.getPosition());                     System.err.println("  end position: " + diagnostic.getEndPosition());                     System.err.println("  source: " + diagnostic.getSource());                     System.err.println("  message: " + diagnostic.getMessage(null));                     i++;                  }              }          };          RelProxyBuiltin rpbRoot = RelProxyBuiltinRoot.get();          JProxyScriptEngine engine = rpbRoot.getJProxyScriptEngine();          JProxyConfig jpConfig = JProxy.createJProxyConfig();          jpConfig.setEnabled(true)                  .setRelProxyOnReloadListener(proxyListener)                  .setInputPath(inputPath)                  .setJProxyInputSourceFileExcludedListener(excludedListener)                  .setScanPeriod(scanPeriod)                  .setClassFolder(classFolder)                  .setCompilationOptions(compilationOptions)                  .setJProxyCompilerListener(compilerListener)                  .setJProxyDiagnosticsListener(diagnosticsListener);          engine.init(jpConfig);          System.out.println("RelProxy running");      }      public void tearDown()      {          RelProxyBuiltin rpbRoot = RelProxyBuiltinRoot.get();          JProxyScriptEngine engine = rpbRoot.getJProxyScriptEngine();          engine.stop();          System.out.println("RelProxy stopped");      }      public void mainTest()      {          RelProxyBuiltin rpbRoot = RelProxyBuiltinRoot.get();          TestListener listener = new TestListener();          rpbRoot.addOutputListener(listener);          assertTrue(rpbRoot.getOutputListenerCount() == 1);          rpbRoot.removeOutputListener(listener);          assertTrue(rpbRoot.getOutputListenerCount() == 0);          rpbRoot.addOutputListener(listener);          CommandListener commandListener = listener.getCommandListener();          rpbRoot.addCommandListener(commandListener);          assertTrue(rpbRoot.getCommandListenerCount() == 1);          rpbRoot.removeCommandListener(commandListener);          assertTrue(rpbRoot.getCommandListenerCount() == 0);          rpbRoot.addCommandListener(commandListener);          rpbRoot.runLoop(System.in,System.out);      }      private static void assertTrue(boolean res)      {          if (!res) throw new RuntimeException("Unexpected Error");      }  }

看一下这段代码:

Main.java (部分)

 URL res = this.getClass().getResource("/"); // .../target/classes/          // Use example of RelProxy in development time:          String inputPath = res.getFile() + "/../../src/main/java/";          if (new File(inputPath).exists())          {              System.out.println("RelProxy to be enabled, development mode detected");          }          else          {              System.out.println("RelProxy disabled, production mode detected");              return;          }          JProxyInputSourceFileExcludedListener excludedListener = new JProxyInputSourceFileExcludedListener()          {              @Override              public boolean isExcluded(File file, File rootFolderOfSources)              {                  String absPath = file.getAbsolutePath();                  if (file.isDirectory())                  {                      return absPath.endsWith(File.separatorChar + "relproxy_builtin_ex");                  }                  else                  {                      return absPath.endsWith(File.separatorChar + Main.class.getSimpleName() + ".java");                  }              }          };

我们获取并注册应用源代码的根目录,该代码“可能”会被重新加载。

我们需要排除框架代码,因为这显然不是用户的代码(不需要重新加载)。此外,还需要排除 Main.java 文件,该文件包含了测试代码,也不需要重新加载,只有 TestListener.java 类(与 Main.java 在同一文件夹下)需要(必需)重新加载。

最后 TestListener.java 类包含两个监听器,CommandListener 的实现采用匿名内部类的方式,主要目的是为了演示。

TestListener.java

package com.innowhere.relproxy_builtin_ex_main;  import com.innowhere.relproxy_builtin_ex.CommandListener;  import com.innowhere.relproxy_builtin_ex.OutputListener;  import java.io.PrintStream;  public class TestListener implements OutputListener  {      @Override      public void write(PrintStream out)      {          out.println("uppercase");          out.println("lowercase");      }      public CommandListener getCommandListener()      {          return new CommandListener()          {              @Override              public void execute(String command,String text,PrintStream out)              {                  if ("uppercase".equals(command))                      out.println(text.toUpperCase());                  else if ("lowercase".equals(command))                      out.println(text.toLowerCase());                  else                      out.println("Unknown command:" + command);              }          };      }  }

先预定义可选项,然后执行 Main 类。为了校验 RelProxy 是否起作用,可以在不停止程序的运行的基础上增加一个新的可选项“same”。

@Override      public void write(PrintStream out)      {          out.println("uppercase");          out.println("lowercase");          out.println("same");  // NEW      }      public CommandListener getCommandListener()      {          return new CommandListener()          {              @Override              public void execute(String command,String text,PrintStream out)              {                  if ("uppercase".equals(command))                      out.println(text.toUpperCase());                  else if ("lowercase".equals(command))                      out.println(text.toLowerCase());                  else if ("same".equals(command)) // NEW                      out.println(text); // NEW                  else                      out.println("Unknown command:" + command);              }          };      }  }

下一篇文章中将处理包含当前“same”的行为,不需要停止控制台应用。

ItsNat web 框架可能是第一个使用 RelProxy 技术的应用(版本 v1.4)。

注意:使用 RelProxy 0.8.7 或更高的版本,这个版本在嵌入方式上做了改进。

原文链接: dzone 翻译: ImportNew.com - paddx
译文链接: http://www.importnew.com/17015.html