Статьи

Горячая перезагрузка класса Java с RelProxy в режиме разработки, пример GWT

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

Наслаждаться!!