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 或更高的版本,这个版本在嵌入方式上做了改进。
共同学习,写下你的评论
评论加载中...
作者其他优质文章