Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Improve Productivity With RelProxy for Java

DZone's Guide to

Improve Productivity With RelProxy for Java

An introduction to the open source RelProxy tool to enrich your Java experience by modifying user code in production without reloading.

· Java Zone
Free Resource

Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code! Brought to you in partnership with ZeroTurnaround.

The purpose of RelProxy is to enrich your development experience in two non-excluding ways:

  • To be able of modify user code in production with no need of reloading the entire application.

  • Improve your productivity in development time avoiding time expensive application reloading with zero performance impact in production.

Both objectives are achieved in certain conditions just adding some RelProxy code to your application usually when registering the typical listeners/callbacks required by your application. This is the "intrusive" way.

If you are a developer of a Java framework, or an autonomous Java service module in general, you can built-in RelProxy Java in your framework to transparently provide code autoreload to end user code using your framework without explicit use of the RelProxy API in your user code beyond some required configuration.

There are two kind of APIs for using the Java part of RelProxy:

  1. JProxy class and related: mainly static methods
  2. Java Scripting API: based on interfaces

The second option is preferred to embed RelProxy Java in your Java framework because it is based on interfaces, no public class of RelProxy is needed to expose in your API, because bootstrap can be executed into your framework. We are going to use the simplified version of the API using JProxyScriptEngineFactory.create().

The JProxyScriptEngine has been designed to provided the same functionality than JProxy, that is, the same methods, but in this case using a pure interface.

A simple example is the best way to show how to embed RelProxy, this example, RelProxyBuiltin (project relproxy_builtin_ex), is included in the RelProxy Examples repository. It defines two listeners to be implemented and registered by end user code, one listener is to show the options and the other to execute the corresponding selected action.

This mini framework and use example is developed using NetBeans and Maven.

There are two packages:

  1. com.innowhere.relproxy_builtin_ex : the mini framework. The subpackage com.innowhere.relproxy_builtin_ex.impl contains the only non-public class of the framework.
  2. com.innowhere.relproxy_builtin_ex_main : a simple use example.

The mini framework (public class and interfaces):

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


Now the implementation details, this class shows how simple is to built-in 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);
        }
    }
}


These three methods are enough to explain how to bootstrap RelProxy Java engine and how easy is to instrument listener registering hot reloadable:

RelProxyBuiltinImpl.java (partial)

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


The public method RelProxyBuiltin.getJProxyScriptEngine() must be called in starting time to configure RelProxy. If RelProxy is not configured and enabled there is no performance penalty.


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.

The Java Zone is brought to you in partnership with ZeroTurnaround. Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code!

Topics:
relproxy ,java ,hot class reload

Published at DZone with permission of Jose Maria Arranz. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}