Статьи

Повышение производительности с RelProxy для Java

Цель  RelProxy  — обогатить ваш опыт разработки двумя не исключающими способами:

  • Чтобы иметь возможность изменять пользовательский код в процессе производства без необходимости перезагрузки всего приложения.

  • Повышайте производительность во время разработки, избегая дорогостоящей перезагрузки приложений с нулевым воздействием на производительность в процессе производства.

Обе цели достигаются в определенных условиях, просто добавляя некоторый код RelProxy в ваше приложение, обычно при регистрации типичных слушателей / обратных вызовов, требуемых вашим приложением. Это «навязчивый» способ.

Если вы являетесь разработчиком фреймворка Java или автономного сервисного модуля Java в целом, вы можете встроить RelProxy Java в свой фреймворк, чтобы прозрачно обеспечить автозагрузку кода для кода конечного пользователя, используя вашу фреймворк, без явного использования API RelProxy в вашем приложении. пользовательский код за пределами необходимой конфигурации.

Существует два вида API для использования Java-части RelProxy:

  1. Класс JProxy и связанные с ним: в основном статические методы
  2. Java Scripting API: на основе интерфейсов

Второй вариант предпочтителен для встраивания RelProxy Java в вашу инфраструктуру Java, поскольку он основан на интерфейсах, поэтому для представления в вашем API не требуется публичный класс RelProxy, потому что загрузчик может быть выполнен в вашей среде. Мы собираемся использовать упрощенную версию API JProxyScriptEngineFactory.create().

Он JProxyScriptEngineбыл разработан, чтобы обеспечить ту же функциональность JProxy, что и те же методы, но в данном случае с использованием чистого интерфейса.

Простой пример — лучший способ показать, как внедрить RelProxy, этот пример, RelProxyBuiltin (проект relproxy_builtin_ex), включен в репозиторий RelProxy examples. Он определяет двух слушателей, которые должны быть реализованы и зарегистрированы кодом конечного пользователя, один слушатель должен показать опции, а другой — выполнить соответствующее выбранное действие.

Этот мини-фреймворк и пример использования разработан с использованием NetBeans и Maven.

Есть два пакета:

  1. com.innowhere.relproxy_builtin_ex: мини рамки. Подпакет com.innowhere.relproxy_builtin_ex.implсодержит единственный непубличный класс фреймворка.
  2. 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);
        }
    }
}

Этих трех методов достаточно, чтобы объяснить, как загрузить Java-движок RelProxy и как просто настроить прослушиватель, регистрирующий горячую перезагрузку:

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 не настроен и не включен, производительность не снижается.

Remember that the proxy object returned by create(…) method correctly calls the method hashCode() and equals(Object) in the internal true listener object for identity purposes required by the listener collection/registry.

This is the example code of this console based program (names are inspired on JUnit but is not really a JUnit test example):

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");
    }
}

Take a look to this piece of code:

Main.java (partial)

        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");
                }
            }
        };

We get and later register the root folder of the source code of our application, this code «may be» reloadable.

We need to exclude the source code of the framework because is not obviously end user code (not reloadable), and the Main.java file containing the test code, this class is not reloadable, only the class TestListener.java (in the same folder than Main.java) will be (and must be) reloadable. We could simplify this isExcluded method just excluding anything different to the file TestListener.java, but excluding whole folders is recommended to speed the traversing of the source code file tree because otherwise all not reloadable files, into not reloadable folders, are checked.

Finally the class TestListener.java containing both listeners, the CommandListener implemented is forced to be an anonymous inner class just for demonstrative purposes:

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);
            }
        };
    }
}

Execute the Main class and try the predefined options. To check if RelProxy is working fine try to add a new option «same» without stopping the program:

    @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);
            }
        };
    }
}

The next phrase to process includes now the «same» action with no need of stopping the console application.

ItsNat web framework is perhaps the first including RelProxy with this technique (as of v1.4).

Note: use RelProxy 0.8.7 or upper, this release adds an improvement when embedding by this way.