RelProxy был представлен в предыдущей статье здесь, на Java DZone, с RelProxy вы можете изменять во время выполнения подмножество вашего исходного кода Java без ограничений, чтобы автоматически перезагружаться с минимальной площадью, избегая повторных развертываний и утомительной перезагрузки контекста.
Перезагружаемый исходный код может быть расположен в стандартном месте для RelProxy, чтобы помочь вам только во время разработки или ниже папки WEB-INF / для загрузки в производство, как и любой другой динамический язык (php, ruby, phyton …).
В этом уроке мы увидим, как RelProxy может помочь вам повысить производительность в этом случае только во время разработки, чтобы показать эту функцию, мы создадим базовый проект GWT-RPC в Eclipse и будем изменять, чтобы представить RelProxy и получить автоматический перегрузка классов.
шаги:
- Скачайте и установите Eclipse (предполагается, что Luna v4.4).
- Загрузите и установите плагин Google для Eclipse (только GWT, никакой App Engine или Android не требуется).
- Выберите в Eclipse пункт меню «Создать» / «Другой» / «Google» / «Проект веб-приложения», чтобы создать пример проекта GWT-RPC (не нажимайте в Google App Engine, в этом нет необходимости). Нет необходимости отключать перезагрузку контекста, она уже отключена во внутренней Jetty, используемой GWT.
- Загрузите дистрибутив RelProxy и скопируйте `relproxy-xyzjar` в` / war / WEB-INF / lib / `.
В нашем примере мы создали проект с именем relproxy_ex_gwt и пакетом com.innowhere.relproxyexgwt, это структура сгенерированного исходного кода:
relproxy_ex_gwt (root folder of project)
src
com
innowhere
relproxyexgwt
client
GreetingService.java
GreetingServiceAsync.java
Relproxy_ex_gwt.java
server
GreetingServiceImpl.java
shared
FieldVerifier.java
Relproxy_ex_gwt.gwt.xml
В RelProxy есть «два мира»: перезагружаемый и не перезагружаемый.
В GWT-RPC только классы, выполняемые на сервере, могут быть загружены повторно, то есть классы в папке «server» (есть только один класс, GreetingServiceImpl.java). Классы в папке «client» не могут быть перезагружены в горячем режиме RelProxy, потому что этот код Java выполняется в клиенте (предварительно скомпилированном в JavaScript), то же самое применяется к «shared», общие классы могут использоваться в обеих сторонах, клиенте и сервере, изменение и перезагрузка общего класса действует только на стороне сервера и игнорируется на стороне клиента, поэтому не рекомендуется.
Единственный перезагружаемый класс — это GreetingServiceImpl.java, и он будет нашим фокусом.
Это сгенерированный код плагином GWT:
package com.innowhere.relproxyexgwt.server;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.innowhere.relproxyexgwt.client.GreetingService;
import com.innowhere.relproxyexgwt.shared.FieldVerifier;
/**
* The server side implementation of the RPC service.
*/
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService {
public String greetServer(String input) throws IllegalArgumentException {
// Verify that the input is valid.
if (!FieldVerifier.isValidName(input)) {
// If the input is not valid, throw an IllegalArgumentException back to
// the client.
throw new IllegalArgumentException("Name must be at least 4 characters long");
}
String serverInfo = getServletContext().getServerInfo();
String userAgent = getThreadLocalRequest().getHeader("User-Agent");
// Escape data from the client to avoid cross-site script vulnerabilities.
input = escapeHtml(input);
userAgent = escapeHtml(userAgent);
return "Hello, " + input + "!<br><br>I am running " + serverInfo + ".<br><br>It looks like you are using:<br>" + userAgent;
}
/**
* Escape an html string. Escaping data received from the client helps to
* prevent cross-site script vulnerabilities.
*
* @param html the html string to escape
* @return the escaped string
*/
private String escapeHtml(String html) {
if (html == null) {
return null;
}
return html.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
}
}
Этот класс представляет собой сервлет, созданный для получения запросов RPC от клиента в соответствии с шаблоном интерфейса интерфейса GreetingService, совместно используемого кодом клиента и сервера. Мы не собираемся пытаться перезагрузить этот сервлет, потому что жизненный цикл этого класса контролируется контейнером сервлета. Нам нужен перезагружаемый синглтон, реализующий интерфейс, зарегистрированный в JProxy (ключевой класс RelProxy для перезагрузки класса Java), поэтому мы глубоко преобразуем GreetingServiceImpl:
package com.innowhere.relproxyexgwt.server;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.innowhere.relproxy.RelProxyOnReloadListener;
import com.innowhere.relproxy.jproxy.JProxy;
import com.innowhere.relproxy.jproxy.JProxyConfig;
import com.innowhere.relproxy.jproxy.JProxyDiagnosticsListener;
import com.innowhere.relproxy.jproxy.JProxyInputSourceFileExcludedListener;
import com.innowhere.relproxy.jproxy.JProxyCompilerListener;
import com.innowhere.relproxyexgwt.client.GreetingService;
/**
* The server-side implementation of the RPC service.
*/
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements
GreetingService {
protected GreetingServiceDelegate delegate;
public void init(ServletConfig config) throws ServletException {
super.init(config);
ServletContext context = config.getServletContext();
String inputPath = context.getRealPath("/") + "/../src/";
JProxyInputSourceFileExcludedListener excludedListener =
new JProxyInputSourceFileExcludedListener()
{
@Override
public boolean isExcluded(File file, File rootFolder) {
String absPath = file.getAbsolutePath();
if (file.isDirectory())
{
return absPath.endsWith(File.separatorChar + "client") ||
absPath.endsWith(File.separatorChar + "shared");
}
else
{
return absPath.endsWith(GreetingServiceDelegate.class.getSimpleName() + ".java") ||
absPath.endsWith(GreetingServiceImpl.class.getSimpleName() + ".java");
}
}
};
String classFolder = null; // Optional: context.getRealPath("/") + "/WEB-INF/classes";
Iterable<String> compilationOptions =
Arrays.asList(new String[]{"-source","1.6","-target","1.6"});
long scanPeriod = 300;
RelProxyOnReloadListener proxyListener =
new RelProxyOnReloadListener() {
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()
{
public void onDiagnostics(DiagnosticCollector<javax.tools.JavaFileObject> diagnostics)
{
List<Diagnostic<? extends JavaFileObject>> diagList = diagnostics.getDiagnostics();
int i = 1;
for (Diagnostic<? extends JavaFileObject> 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++;
}
}
};
JProxyConfig jpConfig = JProxy.createJProxyConfig();
jpConfig.setEnabled(true)
.setRelProxyOnReloadListener(proxyListener)
.setInputPath(inputPath)
.setJProxyInputSourceFileExcludedListener(excludedListener)
.setScanPeriod(scanPeriod)
.setClassFolder(classFolder)
.setCompilationOptions(compilationOptions)
.setJProxyCompilerListener(compilerListener)
.setJProxyDiagnosticsListener(diagnosticsListener);
JProxy.init(jpConfig);
this.delegate = JProxy.create(new GreetingServiceDelegateImpl(this),
GreetingServiceDelegate.class);
} // init
public String greetServer(String input) throws IllegalArgumentException
{
try
{
return delegate.greetServer(input);
}
catch(IllegalArgumentException ex)
{
ex.printStackTrace();
throw ex;
}
catch(Exception ex)
{
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
public HttpServletRequest getThreadLocalRequestPublic()
{
return getThreadLocalRequest();
}
}
Давайте рассмотрим этот JProxy-готовый класс. GreetingServiceImpl на практике является одиночным, потому что это сервлет, поэтому этот атрибут:
protected GreetingServiceDelegate delegate;
которые содержат перезагружаемый синглтон, зарегистрированный на:
this.delegate = JProxy.create(new GreetingServiceDelegateImpl(this),
GreetingServiceDelegate.class);
Как вы можете видеть, мы создали Java-файл GreetingServiceDelegateImpl.java для хранения класса синглтона, подлежащего перезагрузке, и моста / двери в перезагружаемый мир, реализующего интерфейс GreetingServiceDelegate. JProxy возвращает прокси-объект, реализующий интерфейс GreetingServiceDelegate, доступный для мира без перезагрузки.
Посмотрите на этого слушателя:
JProxyInputSourceFileExcludedListener excludedListener =
new JProxyInputSourceFileExcludedListener()
{
@Override
public boolean isExcluded(File file, File rootFolder) {
String absPath = file.getAbsolutePath();
if (file.isDirectory())
{
return absPath.endsWith(File.separatorChar + "client") ||
absPath.endsWith(File.separatorChar + "shared");
}
else
{
return absPath.endsWith(GreetingServiceDelegate.class.getSimpleName() + ".java") ||
absPath.endsWith(GreetingServiceImpl.class.getSimpleName() + ".java");
}
}
};
Зарегистрирован на:
.setJProxyInputSourceFileExcludedListener(excludedListener)
Этот слушатель фильтрует исходные файлы Java, которые RelProxy / JProxy должен игнорировать, даже если они изменены.
Поскольку JProxy создает новый ClassLoader и перезагружает на нем все классы с возможностью горячей перезагрузки, когда кто-то изменяется, классы внутри папок client / и shared / не должны быть перезагружаемыми, поскольку не имеют смысла в GWT.
Когда папка внутри объявленной исходной папки перезагружаемых классов, указанных в конфигурации, будет проверена на наличие измененных классов, вызывается метод isExcluded, чтобы проверить, должна ли быть исключена вся папка, это очень полезно для больших проектов с большим количеством перезагружаемые файлы. В этом случае классы внутри клиента / или совместно используемые / исключаются. Если папка не исключена, файлы и папки в этой папке запрашиваются для исключения вызова isExcluded, класс GreetingServiceImpl не может быть перезагружен, поскольку он является сервлетом и не может быть зарегистрирован в JProxy, поскольку он уже используется системой сервлетов JavaEE. Наконец, GreetingServiceDelegate.java не может быть перезагружен, потому что интерфейс открыт для мира без перезагрузки.
В итоге, только сервер / классы могут быть перезагружены, за исключением класса сервлета GreetingServiceImpl.java и GreetingServiceDelegate.java.
Это код GreetingServiceDelegate:
package com.innowhere.relproxyexgwt.server;
public interface GreetingServiceDelegate {
public String greetServer(String input) throws IllegalArgumentException;
}
И код GreetingServiceDelegateImpl.java, в основном копия / вставка исходного кода сервлета:
package com.innowhere.relproxyexgwt.server;
import com.innowhere.relproxyexgwt.shared.FieldVerifier;
public class GreetingServiceDelegateImpl implements GreetingServiceDelegate
{
protected GreetingServiceImpl parent;
public GreetingServiceDelegateImpl() // needed by JProxy
{
}
public GreetingServiceDelegateImpl(GreetingServiceImpl parent)
{
this.parent = parent;
}
public String greetServer(String input) throws IllegalArgumentException {
// Verify that the input is valid.
if (!FieldVerifier.isValidName(input)) {
// If the input is not valid, throw an IllegalArgumentException back to
// the client.
throw new IllegalArgumentException("Name must be at least 4 characters long");
}
String serverInfo = parent.getServletContext().getServerInfo();
String userAgent = parent.getThreadLocalRequestPublic().getHeader("User-Agent");
// Escape data from the client to avoid cross-site script vulnerabilities.
input = escapeHtml(input);
userAgent = escapeHtml(userAgent);
return "Hello, " + input + "!<br><br>I am running " + serverInfo
+ ".<br><br>It looks like you are using:<br>" + userAgent;
}
/**
* Escape an html string. Escaping data received from the client helps to
* prevent cross-site script vulnerabilities.
*
* @param html the html string to escape
* @return the escaped string
*/
private String escapeHtml(String html) {
if (html == null) {
return null;
}
return html.replaceAll("&", "&").replaceAll("<", "<")
.replaceAll(">", ">");
}
}
Запустите этот пример (Запуск от имени / режим веб-приложения GWT Super Dev), откройте этот URL-адрес
http://127.0.0.1:8888/Relproxy_ex_gwt.html в своем браузере, и появится следующий экран:

Нажмите на Отправить на сервер:

Нажмите на кнопку Закрыть.
Теперь мы собираемся на лету изменить код Java GreetingServiceDelegateImpl, просто измените «Hello» на «Hello <b> BROTHER </ b> » и сохраните:
return "Hello <b>BROTHER</b>, " + input + "!<br><br>I am running " + serverInfo + ".<br><br>It looks like you are using:<br>" + userAgent;
Вернуться в браузер, снова нажмите «Отправить на сервер»:

Как вы можете видеть, в этом случае перезагрузка страницы не требовалась, так как необходимо вызвать прокси-метод для перезагрузки классов, этот вызов был сделан вызовом AJAX / RPC.
В этом примере мы сделали очень простое изменение метода, добавление большего количества методов не является проблемой, но большую часть времени вам потребуется добавлять новые поля, связанные с новыми классами, потому что GreetingServiceDelegateImpl — это одиночный объект, который мы не можем добавлять, удалять или изменять имена и типы полей этого класса, чтобы преодолеть это серьезное ограничение, создайте новые классы, избегая одноэлементного шаблона, и перенесите код в них. Код примерно так:
public String greetServer(String input)
throws IllegalArgumentException {
return new GreetingServiceProcessor(this).greetServer(input);
}
Объявленные поля GreetingServiceProcessor могут меняться без проблем, поскольку этот класс можно перезагружать и создавать его при любом вызове GreetingServiceDelegateImpl.greetServer () со свежими данными.
Вы можете найти полный исходный код этого примера здесь:
https://github.com/jmarranz/relproxy_examples/tree/master/relproxy_ex_gwt
Наслаждаться!!