使用RelProxy提高Java開發效率
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