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
Наслаждаться!!