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
1
2
3
4
5
6
7
8
9
10
<code>package</code> <code>com.innowhere.relproxy_builtin_ex;</code>
<code>import</code> <code>com.innowhere.relproxy_builtin_ex.impl.relproxybuiltinimpl;</code>
<code>public</code> <code>class</code> <code>relproxybuiltinroot</code>
<code>{</code>
<code> </code><code>private</code> <code>final</code> <code>static</code> <code>relproxybuiltinimpl singleton =</code><code>new</code> <code>relproxybuiltinimpl();</code>
<code> </code><code>public</code> <code>static</code> <code>relproxybuiltin get()</code>
<code> </code><code>{</code>
<code> </code><code>return</code> <code>singleton;</code>
<code> </code><code>}</code>
<code>}</code>
relproxybuiltin.java
11
12
13
14
15
<code>import</code> <code>com.innowhere.relproxy.jproxy.jproxyscriptengine;</code>
<code>import</code> <code>java.io.inputstream;</code>
<code>import</code> <code>java.io.printstream;</code>
<code>public</code> <code>interface</code> <code>relproxybuiltin</code>
<code> </code><code>public</code> <code>jproxyscriptengine getjproxyscriptengine();</code>
<code> </code><code>public</code> <code>void</code> <code>addoutputlistener(outputlistener listener);</code>
<code> </code><code>public</code> <code>void</code> <code>removeoutputlistener(outputlistener listener);</code>
<code> </code><code>public</code> <code>int</code> <code>getoutputlistenercount();</code>
<code> </code><code>public</code> <code>void</code> <code>addcommandlistener(commandlistener listener);</code>
<code> </code><code>public</code> <code>void</code> <code>removecommandlistener(commandlistener listener);</code>
<code> </code><code>public</code> <code>int</code> <code>getcommandlistenercount();</code>
<code> </code><code>public</code> <code>void</code> <code>runloop(inputstream in,printstream out);</code>
outputlistener.java
<code>public</code> <code>interface</code> <code>outputlistener</code>
<code> </code><code>public</code> <code>void</code> <code>write(printstream out);</code>
commandlistener.java
<code>public</code> <code>interface</code> <code>commandlistener</code>
<code> </code><code>public</code> <code>void</code> <code>execute(string command,string input,printstream out);</code>
現在看一下實作細節,該類示範了怎樣簡單地内嵌 relproxy:
relproxybuiltinimpl.java
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<code>package</code> <code>com.innowhere.relproxy_builtin_ex.impl;</code>
<code>import</code> <code>com.innowhere.relproxy.jproxy.jproxyscriptenginefactory;</code>
<code>import</code> <code>com.innowhere.relproxy_builtin_ex.commandlistener;</code>
<code>import</code> <code>com.innowhere.relproxy_builtin_ex.outputlistener;</code>
<code>import</code> <code>com.innowhere.relproxy_builtin_ex.relproxybuiltin;</code>
<code>import</code> <code>java.util.linkedhashset;</code>
<code>import</code> <code>java.util.scanner;</code>
<code>public</code> <code>class</code> <code>relproxybuiltinimpl</code><code>implements</code> <code>relproxybuiltin</code>
<code> </code><code>protected</code> <code>jproxyscriptengine jproxyengine =</code><code>null</code><code>;</code>
<code> </code><code>protected</code> <code>linkedhashset<outputlistener> outlisteners =</code><code>new</code> <code>linkedhashset<outputlistener>();</code>
<code> </code><code>protected</code> <code>linkedhashset<commandlistener> commandlisteners =</code><code>new</code> <code>linkedhashset<commandlistener>();</code>
<code> </code><code>@override</code>
<code> </code><code>public</code> <code>jproxyscriptengine getjproxyscriptengine()</code>
<code> </code><code>if</code> <code>(jproxyengine ==</code><code>null</code><code>) jproxyengine = (jproxyscriptengine)jproxyscriptenginefactory.create().getscriptengine();</code>
<code> </code><code>return</code> <code>jproxyengine;</code>
<code> </code><code>public</code> <code>jproxyscriptengine getjproxyscriptengineifconfigured()</code>
<code> </code><code>if</code> <code>(jproxyengine ==</code><code>null</code> <code>|| !jproxyengine.isenabled())</code>
<code> </code><code>return</code> <code>null</code><code>;</code>
<code> </code><code>public</code> <code>void</code> <code>addoutputlistener(outputlistener listener)</code>
<code> </code><code>jproxyscriptengine jproxy = getjproxyscriptengineifconfigured();</code>
<code> </code><code>if</code> <code>(jproxy !=</code><code>null</code><code>)</code>
<code> </code><code>{</code>
<code> </code><code>listener = jproxy.create(listener,outputlistener.</code><code>class</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>outlisteners.add(listener);</code>
<code> </code><code>public</code> <code>void</code> <code>removeoutputlistener(outputlistener listener)</code>
<code> </code><code>outlisteners.remove(listener);</code>
<code> </code><code>public</code> <code>int</code> <code>getoutputlistenercount()</code>
<code> </code><code>return</code> <code>outlisteners.size();</code>
<code> </code><code>public</code> <code>void</code> <code>addcommandlistener(commandlistener listener)</code>
<code> </code><code>listener = jproxy.create(listener,commandlistener.</code><code>class</code><code>);</code>
<code> </code><code>commandlisteners.add(listener);</code>
<code> </code><code>public</code> <code>void</code> <code>removecommandlistener(commandlistener listener)</code>
<code> </code><code>commandlisteners.remove(listener);</code>
<code> </code><code>public</code> <code>int</code> <code>getcommandlistenercount()</code>
<code> </code><code>return</code> <code>commandlisteners.size();</code>
<code> </code><code>public</code> <code>void</code> <code>runloop(inputstream in,printstream out)</code>
<code> </code><code>scanner scanner =</code><code>new</code> <code>scanner(in);</code>
<code> </code><code>while</code><code>(</code><code>true</code><code>)</code>
<code> </code><code>out.print(</code><code>"enter phrase:"</code><code>);</code>
<code> </code><code>string input = scanner.nextline();</code>
<code> </code><code>out.println(</code><code>"command list:"</code><code>);</code>
<code> </code><code>for</code><code>(outputlistener listener : outlisteners)</code>
<code> </code><code>listener.write(out);</code>
<code> </code><code>out.print(</code><code>"enter command (or quit):"</code><code>);</code>
<code> </code><code>string command = scanner.nextline();</code>
<code> </code><code>if</code> <code>(</code><code>"quit"</code><code>.equals(command))</code>
<code> </code><code>break</code><code>;</code>
<code> </code><code>for</code><code>(commandlistener listener : commandlisteners)</code>
<code> </code><code>listener.execute(command,input,out);</code>
這三個方法足以解釋怎樣啟動 relproxy java 引擎,怎樣簡單地使用指令監聽器來注冊熱加載。
relproxybuiltinimpl.java (部分)
<code>@override</code>
<code> </code><code>public</code> <code>jproxyscriptengine getjproxyscriptengine()</code>
<code> </code><code>{</code>
<code> </code><code>if</code> <code>(jproxyengine ==</code><code>null</code><code>) jproxyengine = (jproxyscriptengine)jproxyscriptenginefactory.create().getscriptengine();</code>
<code> </code><code>return</code> <code>jproxyengine;</code>
<code> </code><code>}</code>
<code> </code><code>public</code> <code>jproxyscriptengine getjproxyscriptengineifconfigured()</code>
<code> </code><code>if</code> <code>(jproxyengine ==</code><code>null</code> <code>|| !jproxyengine.isenabled())</code>
<code> </code><code>return</code> <code>null</code><code>;</code>
<code> </code><code>@override</code>
<code> </code><code>public</code> <code>void</code> <code>addoutputlistener(outputlistener listener)</code>
<code> </code><code>jproxyscriptengine jproxy = getjproxyscriptengineifconfigured();</code>
<code> </code><code>if</code> <code>(jproxy !=</code><code>null</code><code>)</code>
<code> </code><code>{</code>
<code> </code><code>listener = jproxy.create(listener,outputlistener.</code><code>class</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>outlisteners.add(listener);</code>
公共方法 relproxybuiltin.getjproxyscriptengine() 必須在啟動時執行,用于配置 relproxy。如果沒有配置,relproxy 就不起作用。
請記住,通過 create(…) 建立的代理對象需要能正确的執行 hashcode() 方法和 equals(object) 方法,監聽器集合、監聽記錄依賴這兩個方法來差別監聽器對象。
這是基于控制台的示例代碼(名稱與 junit 類似,但确實不是 junit 的測試示例):
main.java
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<code>package</code> <code>com.innowhere.relproxy_builtin_ex_main;</code>
<code>import</code> <code>com.innowhere.relproxy.relproxyonreloadlistener;</code>
<code>import</code> <code>com.innowhere.relproxy.jproxy.jproxy;</code>
<code>import</code> <code>com.innowhere.relproxy.jproxy.jproxycompilerlistener;</code>
<code>import</code> <code>com.innowhere.relproxy.jproxy.jproxyconfig;</code>
<code>import</code> <code>com.innowhere.relproxy.jproxy.jproxydiagnosticslistener;</code>
<code>import</code> <code>com.innowhere.relproxy.jproxy.jproxyinputsourcefileexcludedlistener;</code>
<code>import</code> <code>com.innowhere.relproxy_builtin_ex.relproxybuiltinroot;</code>
<code>import</code> <code>java.io.file;</code>
<code>import</code> <code>java.lang.reflect.method;</code>
<code>import</code> <code>java.net.url;</code>
<code>import</code> <code>java.util.arrays;</code>
<code>import</code> <code>java.util.list;</code>
<code>import</code> <code>javax.tools.diagnostic;</code>
<code>import</code> <code>javax.tools.diagnosticcollector;</code>
<code>import</code> <code>javax.tools.javafileobject;</code>
<code>public</code> <code>class</code> <code>main</code>
<code> </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args)</code><code>throws</code> <code>exception</code>
<code> </code><code>new</code> <code>main();</code>
<code> </code><code>public</code> <code>main()</code>
<code> </code><code>// note: netbeans console window works bad (no input) with maven test tasks http://stackoverflow.com/questions/3035351/broken-console-in-maven-project-using-netbeans</code>
<code> </code><code>// this is why is not a really junit test.</code>
<code> </code><code>setup();</code>
<code> </code><code>try</code>
<code> </code><code>maintest();</code>
<code> </code><code>finally</code>
<code> </code><code>teardown();</code>
<code> </code><code>system.exit(</code><code>0</code><code>);</code>
<code> </code><code>public</code> <code>void</code> <code>setup()</code>
<code> </code><code>url res =</code><code>this</code><code>.getclass().getresource(</code><code>"/"</code><code>);</code><code>// .../target/classes/</code>
<code> </code><code>// use example of relproxy in development time:</code>
<code> </code><code>string inputpath = res.getfile() +</code><code>"/../../src/main/java/"</code><code>;</code>
<code> </code><code>if</code> <code>(</code><code>new</code> <code>file(inputpath).exists())</code>
<code> </code><code>system.out.println(</code><code>"relproxy to be enabled, development mode detected"</code><code>);</code>
<code> </code><code>else</code>
<code> </code><code>system.out.println(</code><code>"relproxy disabled, production mode detected"</code><code>);</code>
<code> </code><code>return</code><code>;</code>
<code> </code><code>jproxyinputsourcefileexcludedlistener excludedlistener =</code><code>new</code> <code>jproxyinputsourcefileexcludedlistener()</code>
<code> </code><code>@override</code>
<code> </code><code>public</code> <code>boolean</code> <code>isexcluded(file file, file rootfolderofsources)</code>
<code> </code><code>{</code>
<code> </code><code>string abspath = file.getabsolutepath();</code>
<code> </code><code>if</code> <code>(file.isdirectory())</code>
<code> </code><code>{</code>
<code> </code><code>return</code> <code>abspath.endswith(file.separatorchar +</code><code>"relproxy_builtin_ex"</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>else</code>
<code> </code><code>return</code> <code>abspath.endswith(file.separatorchar + main.</code><code>class</code><code>.getsimplename() +</code><code>".java"</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>};</code>
<code> </code><code>string classfolder =</code><code>null</code><code>;</code><code>// optional</code>
<code> </code><code>iterable<string> compilationoptions = arrays.aslist(</code><code>new</code> <code>string[]{</code><code>"-source"</code><code>,</code><code>"1.6"</code><code>,</code><code>"-target"</code><code>,</code><code>"1.6"</code><code>});</code>
<code> </code><code>long</code> <code>scanperiod =</code><code>1000</code><code>;</code>
<code> </code><code>relproxyonreloadlistener proxylistener =</code><code>new</code> <code>relproxyonreloadlistener() {</code>
<code> </code><code>public</code> <code>void</code> <code>onreload(object objold, object objnew, object proxy, method method, object[] args) {</code>
<code> </code><code>system.out.println(</code><code>"reloaded "</code> <code>+ objnew +</code><code>" calling method: "</code> <code>+ method);</code>
<code> </code><code>jproxycompilerlistener compilerlistener =</code><code>new</code> <code>jproxycompilerlistener(){</code>
<code> </code><code>public</code> <code>void</code> <code>beforecompile(file file)</code>
<code> </code><code>system.out.println(</code><code>"before compile: "</code> <code>+ file);</code>
<code> </code><code>public</code> <code>void</code> <code>aftercompile(file file)</code>
<code> </code><code>system.out.println(</code><code>"after compile: "</code> <code>+ file);</code>
<code> </code><code>jproxydiagnosticslistener diagnosticslistener =</code><code>new</code> <code>jproxydiagnosticslistener()</code>
<code> </code><code>public</code> <code>void</code> <code>ondiagnostics(diagnosticcollector<javafileobject> diagnostics)</code>
<code> </code><code>list<diagnostic<?</code><code>extends</code> <code>javafileobject>> diaglist = diagnostics.getdiagnostics();</code>
<code> </code><code>int</code> <code>i =</code><code>1</code><code>;</code>
<code> </code><code>for</code> <code>(diagnostic diagnostic : diaglist)</code>
<code> </code><code>system.err.println(</code><code>"diagnostic "</code> <code>+ i);</code>
<code> </code><code>system.err.println(</code><code>" code: "</code> <code>+ diagnostic.getcode());</code>
<code> </code><code>system.err.println(</code><code>" kind: "</code> <code>+ diagnostic.getkind());</code>
<code> </code><code>system.err.println(</code><code>" line number: "</code> <code>+ diagnostic.getlinenumber());</code>
<code> </code><code>system.err.println(</code><code>" column number: "</code> <code>+ diagnostic.getcolumnnumber());</code>
<code> </code><code>system.err.println(</code><code>" start position: "</code> <code>+ diagnostic.getstartposition());</code>
<code> </code><code>system.err.println(</code><code>" position: "</code> <code>+ diagnostic.getposition());</code>
<code> </code><code>system.err.println(</code><code>" end position: "</code> <code>+ diagnostic.getendposition());</code>
<code> </code><code>system.err.println(</code><code>" source: "</code> <code>+ diagnostic.getsource());</code>
<code> </code><code>system.err.println(</code><code>" message: "</code> <code>+ diagnostic.getmessage(</code><code>null</code><code>));</code>
<code> </code><code>i++;</code>
<code> </code><code>relproxybuiltin rpbroot = relproxybuiltinroot.get();</code>
<code> </code><code>jproxyscriptengine engine = rpbroot.getjproxyscriptengine();</code>
<code> </code><code>jproxyconfig jpconfig = jproxy.createjproxyconfig();</code>
<code> </code><code>jpconfig.setenabled(</code><code>true</code><code>)</code>
<code> </code><code>.setrelproxyonreloadlistener(proxylistener)</code>
<code> </code><code>.setinputpath(inputpath)</code>
<code> </code><code>.setjproxyinputsourcefileexcludedlistener(excludedlistener)</code>
<code> </code><code>.setscanperiod(scanperiod)</code>
<code> </code><code>.setclassfolder(classfolder)</code>
<code> </code><code>.setcompilationoptions(compilationoptions)</code>
<code> </code><code>.setjproxycompilerlistener(compilerlistener)</code>
<code> </code><code>.setjproxydiagnosticslistener(diagnosticslistener);</code>
<code> </code><code>engine.init(jpconfig);</code>
<code> </code><code>system.out.println(</code><code>"relproxy running"</code><code>);</code>
<code> </code><code>public</code> <code>void</code> <code>teardown()</code>
<code> </code><code>engine.stop();</code>
<code> </code><code>system.out.println(</code><code>"relproxy stopped"</code><code>);</code>
<code> </code><code>public</code> <code>void</code> <code>maintest()</code>
<code> </code><code>testlistener listener =</code><code>new</code> <code>testlistener();</code>
<code> </code><code>rpbroot.addoutputlistener(listener);</code>
<code> </code><code>asserttrue(rpbroot.getoutputlistenercount() ==</code><code>1</code><code>);</code>
<code> </code><code>rpbroot.removeoutputlistener(listener);</code>
<code> </code><code>asserttrue(rpbroot.getoutputlistenercount() ==</code><code>0</code><code>);</code>
<code> </code><code>commandlistener commandlistener = listener.getcommandlistener();</code>
<code> </code><code>rpbroot.addcommandlistener(commandlistener);</code>
<code> </code><code>asserttrue(rpbroot.getcommandlistenercount() ==</code><code>1</code><code>);</code>
<code> </code><code>rpbroot.removecommandlistener(commandlistener);</code>
<code> </code><code>asserttrue(rpbroot.getcommandlistenercount() ==</code><code>0</code><code>);</code>
<code> </code><code>rpbroot.runloop(system.in,system.out);</code>
<code> </code><code>private</code> <code>static</code> <code>void</code> <code>asserttrue(</code><code>boolean</code> <code>res)</code>
<code> </code><code>if</code> <code>(!res)</code><code>throw</code> <code>new</code> <code>runtimeexception(</code><code>"unexpected error"</code><code>);</code>
看一下這段代碼:
main.java (部分)
<code>url res =</code><code>this</code><code>.getclass().getresource(</code><code>"/"</code><code>);</code><code>// .../target/classes/</code>
<code> </code><code>// use example of relproxy in development time:</code>
<code> </code><code>string inputpath = res.getfile() +</code><code>"/../../src/main/java/"</code><code>;</code>
<code> </code><code>if</code> <code>(</code><code>new</code> <code>file(inputpath).exists())</code>
<code> </code><code>{</code>
<code> </code><code>system.out.println(</code><code>"relproxy to be enabled, development mode detected"</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>else</code>
<code> </code><code>system.out.println(</code><code>"relproxy disabled, production mode detected"</code><code>);</code>
<code> </code><code>return</code><code>;</code>
<code> </code><code>jproxyinputsourcefileexcludedlistener excludedlistener =</code><code>new</code> <code>jproxyinputsourcefileexcludedlistener()</code>
<code> </code><code>@override</code>
<code> </code><code>public</code> <code>boolean</code> <code>isexcluded(file file, file rootfolderofsources)</code>
<code> </code><code>{</code>
<code> </code><code>string abspath = file.getabsolutepath();</code>
<code> </code><code>if</code> <code>(file.isdirectory())</code>
<code> </code><code>{</code>
<code> </code><code>return</code> <code>abspath.endswith(file.separatorchar +</code><code>"relproxy_builtin_ex"</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>else</code>
<code> </code><code>return</code> <code>abspath.endswith(file.separatorchar + main.</code><code>class</code><code>.getsimplename() +</code><code>".java"</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>};</code>
我們擷取并注冊應用源代碼的根目錄,該代碼“可能”會被重新加載。
我們需要排除架構代碼,因為這顯然不是使用者的代碼(不需要重新加載)。此外,還需要排除 main.java
檔案,該檔案包含了測試代碼,也不需要重新加載,隻有 testlistener.java 類(與 main.java
在同一檔案夾下)需要(必需)重新加載。
最後 testlistener.java 類包含兩個監聽器,commandlistener 的實作采用匿名内部類的方式,主要目的是為了示範。
testlistener.java
<code>public</code> <code>class</code> <code>testlistener</code><code>implements</code> <code>outputlistener</code>
<code> </code><code>public</code> <code>void</code> <code>write(printstream out)</code>
<code> </code><code>out.println(</code><code>"uppercase"</code><code>);</code>
<code> </code><code>out.println(</code><code>"lowercase"</code><code>);</code>
<code> </code><code>public</code> <code>commandlistener getcommandlistener()</code>
<code> </code><code>return</code> <code>new</code> <code>commandlistener()</code>
<code> </code><code>public</code> <code>void</code> <code>execute(string command,string text,printstream out)</code>
<code> </code><code>if</code> <code>(</code><code>"uppercase"</code><code>.equals(command))</code>
<code> </code><code>out.println(text.touppercase());</code>
<code> </code><code>else</code> <code>if</code> <code>(</code><code>"lowercase"</code><code>.equals(command))</code>
<code> </code><code>out.println(text.tolowercase());</code>
<code> </code><code>out.println(</code><code>"unknown command:"</code> <code>+ command);</code>
先預定義可選項,然後執行 main 類。為了校驗 relproxy 是否起作用,可以在不停止程式的運作的基礎上增加一個新的可選項“same”。
<code> </code><code>out.println(</code><code>"same"</code><code>); </code><code>// new</code>
<code> </code><code>else</code> <code>if</code> <code>(</code><code>"same"</code><code>.equals(command))</code><code>// new</code>
<code> </code><code>out.println(text);</code><code>// new</code>
下一篇文章中将處理包含目前“same”的行為,不需要停止控制台應用。
注意:使用 relproxy 0.8.7 或更高的版本,這個版本在嵌入方式上做了改進。
來源:51cto