Introduction

A small yet powerful interception library that lets you manipulate existing objects and classes behavior runtime, It’s achieving this by using javassist to do bytecode manipulation,

Interesting features

  • Easy to use
  • Gives you Java super powers
  • Well tested

Using Proxy can allow you to architecturally implement your code completely differently with features like:

  • Building complex object from small objects.
  • Dynamically change an interface of an object (duck typing)
  • Replacing an existing object with modified version.
  • Mixin / multiple inheritance functionality. (Not present in Java by default)
  • AOP like features.
  • Create simple java beans objects without providing an implementation.

Examples

This chapter will present some usage examples of this library. This list is in no way a full list of what you can do with this library.

Example: Building aggregate delegation objects from small implementations

Variant with a fluent style

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package examples;
  
import static com.ericsson.commonlibrary.proxy.Proxy.with;
  
import java.lang.reflect.Method;
  
import com.ericsson.commonlibrary.proxy.Interceptor;
import com.ericsson.commonlibrary.proxy.Invocation;
  
public class BuildingObjectsFluentExample {
  
    public static void main(String[] args) throws SecurityException, NoSuchMethodException {
  
        Algorithm algorithm = with(Algorithm.class).delegate(new Sort1(), new Reverse1()).get();
        System.out.println(algorithm);
        System.out.println(algorithm.algorithmReverse("fluent"));
        System.out.println(algorithm.algorithmSort("fluent"));
  
        Method method = Algorithm.class.getMethod("algorithmSort", String.class);
  
        with(algorithm).interceptAll(new StringInterceptor()).interceptMethod(new StringInterceptor(), method);
        System.out.println(algorithm);
        System.out.println(algorithm.algorithmReverse("fluent"));
        System.out.println(algorithm.algorithmSort("fluent"));
    }
  
    public static class StringInterceptor implements Interceptor {
  
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            if (invocation.getMethod().getReturnType() == String.class) {
                return invocation.invoke() + "Intercepted";
            }
            return invocation.invoke();
        }
    }
  
    public interface Algorithm {
  
        String algorithmSort(String arg);
  
        String algorithmReverse(String arg);
    }
  
    public static class Sort1 {
  
        public String algorithmSort(String arg) {
            return "sort1: " + arg;
        }
    }
  
    public static class Reverse1 {
  
        public String algorithmReverse(String arg) {
            return "reverse1: " + arg;
        }
    }
}

Variant with a typical static style

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package examples;
  
import com.ericsson.commonlibrary.proxy.Proxy;
  
public class BuildingObjectsExample {
  
    public static void main(String[] args) {
        //Building all possible combinations of Algorithm. this would not be possible with inheritance without duplication
        Algorithm obj1 = Proxy.delegate(Algorithm.class, new Sort1(), new Reverse1());
        Algorithm obj2 = Proxy.delegate(Algorithm.class, new Sort1(), new Reverse2());
        Algorithm obj3 = Proxy.delegate(Algorithm.class, new Sort2(), new Reverse1());
        Algorithm obj4 = Proxy.delegate(Algorithm.class, new Sort2(), new Reverse2());
  
        System.out.println(obj2);
        System.out.println(obj2.algorithmReverse("obj2"));
        System.out.println(obj2.algorithmSort("obj2"));
        System.out.println("-----------");
  
        System.out.println(obj4);
        System.out.println(obj4.algorithmReverse("obj4"));
        System.out.println(obj4.algorithmSort("obj4"));
        System.out.println("-----------");
  
        //Object methods are handled a bit special. Here it will use the toString() impl in ToString class even if all the other delegate objects contain a toString() method inherited from class Object.
        Algorithm objWithSpecialToString = Proxy.delegate(Algorithm.class, new Sort2(), new ToString(), new Reverse1());
        System.out.println(objWithSpecialToString);
        System.out.println(objWithSpecialToString.algorithmReverse("objWithSpecialToString"));
        System.out.println(objWithSpecialToString.algorithmSort("objWithSpecialToString"));
        System.out.println("-----------");
  
        //You can replace specific parts of existing implementations
        Algorithm changeExistingImplClass = Proxy.delegate(AlgorithmImpl.class, new Sort2());
        System.out.println(changeExistingImplClass);
        System.out.println(changeExistingImplClass.algorithmReverse("changeExistingImplClass"));
        System.out.println(changeExistingImplClass.algorithmSort("changeExistingImplClass"));
        System.out.println("-----------");
  
        //You can even replace specific parts of existing objects
        Algorithm changeExistingImplObject = Proxy.delegate(new AlgorithmImpl(), new Reverse1());
        System.out.println(changeExistingImplObject);
        System.out.println(changeExistingImplObject.algorithmReverse("changeExistingImplObject"));
        System.out.println(changeExistingImplObject.algorithmSort("changeExistingImplObject"));
    }
  
    public interface Algorithm {
  
        String algorithmSort(String arg);
  
        String algorithmReverse(String arg);
    }
  
    public static class AlgorithmImpl implements Algorithm {
  
        @Override
        public String algorithmSort(String arg) {
            return "sort3: " + arg;
        }
  
        @Override
        public String algorithmReverse(String arg) {
            return "reverse3: " + arg;
        }
    }
  
    public static class Sort1 {
  
        public String algorithmSort(String arg) {
            return "sort1: " + arg;
        }
    }
  
    public static class Sort2 {
  
        public String algorithmSort(String arg) {
            return "sort2: " + arg;
        }
    }
  
    public static class Reverse1 {
  
        public String algorithmReverse(String arg) {
            return "reverse1: " + arg;
        }
    }
  
    public static class Reverse2 {
  
        public String algorithmReverse(String arg) {
            return "reverse2: " + arg;
        }
    }
  
    public static class ToString {
  
        @Override
        public String toString() {
            return "special toString";
        }
    }
}

Example: Altered behavior with recursive nature

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*
Copyright (c) 2018 Ericsson
  
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
  
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
  
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package examples;
  
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
  
import com.ericsson.commonlibrary.proxy.Interceptor;
import com.ericsson.commonlibrary.proxy.Invocation;
import com.ericsson.commonlibrary.proxy.Proxy;
  
  
public class CountDownCollectionRecursionExample {
    public static void main(String[] args) {
        //Anonymous class, should usually be put in a separate class.
        Interceptor countDown = new Interceptor() {
  
            @Override
            public Object intercept(Invocation invocation) throws Throwable {
                if (invocation.getMethodName().contains("add")) {
                    invocation.invoke(); //invoke original
                    Integer val = (Integer) invocation.getParameter0();
                    if (val > 0) {
                        Collection list = (Collection) invocation.getThis();
                        return list.add(val - 1); //invoke proxy again.
                    }
                    return true;
                }
                return invocation.invoke(); //invokes method as normal
            }
        };
  
        List<Integer> list = Proxy.intercept(new ArrayList<Integer>(), countDown);
        list.add(4);
        list.add(6);
        list.add(3);
        System.out.println(list); // [4, 3, 2, 1, 0, 6, 5, 4, 3, 2, 1, 0, 3, 2, 1, 0]
  
        Set<Integer> set = Proxy.intercept(new HashSet<Integer>(), countDown);
        set.add(4);
        System.out.println(set); // [0, 1, 2, 3, 4]
        set.add(6);
        System.out.println(set); // [0, 1, 2, 3, 4, 5, 6]
        set.add(3);
        System.out.println(set); // [0, 1, 2, 3, 4, 5, 6]
  
    }
}

Example: Compact interception with lambdas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package examples;
  
import static com.ericsson.commonlibrary.proxy.Proxy.with;
  
public class LambdaInterceptionExample {
  
    public static class SomeImpl { //sample class
  
        public void log(String log) {
            System.out.println(log);
        }
    }
  
    public static void main(String[] args) throws SecurityException, NoSuchMethodException {
  
        //lambda on class
        SomeImpl obj = with(SomeImpl.class)
                .interceptAll(i -> {
                    System.out.println("before method: " + i.getMethodName() + " param: " + i.getParameter0());
                    return i.invoke();
                }).get();
        obj.log("123");
        //Console output:
        //        before method: log param: 123
        //        123
  
        //lambda on object
        SomeImpl obj2 = with(new SomeImpl())
                .interceptAll(i -> {
                    Object result = i.invoke();
                    System.out.println("after method: " + i.getMethodName() + " param: " + i.getParameter0());
                    return result;
                }).get();
        obj2.log("321");
        //Console output:
        //        321
        //        after method: log param: 321
  
        //lambda without return.
//        SomeImpl obj3 = with(SomeImpl.class)
//                .interceptAll((i) -> System.out.println("Replace method invocation: " + i.getMethodName()))
//                .get(); //TODO why does maven have problem with compiling this? 
//        obj3.log("12345");
        //Console output:
        //        Replace method invocation: log
    }
}

Example: Remove all Java bean implementation and stick with interfaces

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package examples;
  
import com.ericsson.commonlibrary.proxy.Proxy;
  
public class JavaBeanExample {
  
    public static void main(String[] args) {
        //Normally without the proxy library.
        JavaBean bean = new JavaBeanImpl(); // requires you to create JavaBeanImpl with implementation.
        bean.setName("bean");
        System.out.println("bean name:" + bean.getName());
  
        //"Proxy.javaBean" will dynamically create a class that acts exactly like JavaBeanImpl.
        //This means that JavaBeanImpl is no longer needed can be removed.
        JavaBean proxyBean = Proxy.javaBean(JavaBean.class);
        proxyBean.setName("proxy");
        System.out.println("bean proxy name:" + proxyBean.getName());
    }
  
    public interface JavaBean {
  
        String getName();
  
        void setName(String name);
    }
  
    //NOT needed when using the proxy solution
    public static class JavaBeanImpl implements JavaBean {
  
        private String name;
  
        @Override
        public String getName() {
            return name;
        }
  
        @Override
        public void setName(String name) {
            this.name = name;
        }
    }
}

Example: Alter existing objects in Java, like the console.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package examples;
  
import java.io.PrintStream;
  
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
import com.ericsson.commonlibrary.proxy.Interceptor;
import com.ericsson.commonlibrary.proxy.Invocation;
import com.ericsson.commonlibrary.proxy.Proxy;
  
public class RedirectConsoleExample {
  
    public static void main(String[] args) {
        //Anonymous interceptor class, should usually be put in a separate class.
        Interceptor redirectPrintStream = new Interceptor() {
  
            @Override
            public Object intercept(Invocation invocation) throws Throwable {
                if (invocation.getMethod().getName().contains("println")) {
                    String classNameWhereItWasCalled = Thread.currentThread().getStackTrace()[1]
                            .getClassName();
                    Logger logger = LoggerFactory.getLogger(classNameWhereItWasCalled);
                    logger.info("Console: " + invocation.getParameter0());
  
                    //The console would be empty because the println was intercepted. removing "return null;" in the interceptor would mean
                    //that the original println would be called and 123 would appear in both slf4j and in the console.
                    return null;
                }
                return invocation.invoke(); //invokes other methods as normal
            }
        };
  
        //add the interceptor to the current System.out
        PrintStream newOut = Proxy.intercept(System.out, redirectPrintStream);
        System.setOut(newOut);
  
        //you can use the interceptor on multiple object.
        PrintStream newErr = Proxy.intercept(System.err, redirectPrintStream);
        System.setErr(newErr);
  
        System.out.println("123");
        System.err.println("some error");
    }
}

Example: MDC logging, add contextual metadata to your logging.

See more info at: MDC logging

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package examples;
  
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
import com.ericsson.commonlibrary.proxy.Proxy;
  
public class MdcExample {
  
    public static void main(String[] args) {
        SomeImpl objectToLog = new SomeImpl();
        objectToLog = Proxy.mdcLogging(objectToLog, "id", "id001");
        objectToLog = Proxy.mdcLogging(objectToLog, "instance", "inst01");
        objectToLog.log("Hello World");
  
        SomeImpl objectToLog2 = new SomeImpl();
        objectToLog2 = Proxy.mdcLogging(objectToLog2, "id", "id001");
        objectToLog2 = Proxy.mdcLogging(objectToLog2, "instance", "inst02");
        objectToLog2.log("Hello World2");
  
        //Expected output:
        //2013-06-25 10:40:17,374 Logger:com.ericsson.commonlibrary.proxy.examples.MdcExample$SomeImpl [id001.inst01] Hello World
        //2013-06-25 10:40:17,376 Logger:com.ericsson.commonlibrary.proxy.examples.MdcExample$SomeImpl [id001.inst02] Hello World2
        //log4j configured: log4j.appender.A1.layout.ConversionPattern=%d{ISO8601} Logger:%c [%X{id}.%X{instance}] %m\n
    }
  
    public static class SomeImpl {
  
        private static final Logger LOG = LoggerFactory.getLogger(MdcExample.SomeImpl.class);
  
        public void log(String log) {
            LOG.info(log);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package examples;
  
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
import com.ericsson.commonlibrary.proxy.Proxy;
  
public class MdcRecursiveExample {
  
    public static void main(String[] args) {
        SomeImpl objectToLog = new SomeImpl();
        objectToLog = Proxy.mdcLogging(objectToLog, "id", "id001");
        objectToLog.log("1");
  
        SomeImpl2 impl = objectToLog.getImpl();
        impl.log("2");
        SomeImpl3 impl2 = impl.getImpl();
        impl2.log("3");
  
    }
  
    public static class SomeImpl {
  
        private final SomeImpl2 impl = new SomeImpl2();
        private static final Logger LOG = LoggerFactory.getLogger(MdcRecursiveExample.SomeImpl.class);
  
        public void log(String log) {
            LOG.info(log);
        }
  
        public SomeImpl2 getImpl() {
            return impl;
        }
    }
  
    public static class SomeImpl2 {
  
        private final SomeImpl3 impl = new SomeImpl3();
  
        private static final Logger LOG = LoggerFactory.getLogger(MdcRecursiveExample.SomeImpl2.class);
  
        public void log(String log) {
            LOG.info(log);
        }
  
        public SomeImpl3 getImpl() {
            return impl;
        }
    }
  
    public static class SomeImpl3 {
  
        private static final Logger LOG = LoggerFactory.getLogger(MdcRecursiveExample.SomeImpl3.class);
  
        public void log(String log) {
            LOG.info(log);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package examples;
  
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
import com.ericsson.commonlibrary.proxy.Proxy;
  
public class MdcDangersExample {
  
    public static void main(String[] args) {
        SomeImpl objectToLog = new SomeImpl();
        objectToLog.log("1");
        objectToLog = Proxy.mdcLogging(objectToLog, "id", "id001");
        objectToLog.log("1");
    }
  
    public static class SomeImpl {
  
        private static final Logger LOG = LoggerFactory.getLogger(SomeImpl.class);
  
        //Not dangerous
        private static final String stringStaticFinal = "hello";
        private final String stringPrivate = "hello";
  
        //dangerous. Will prevent Proxy from from adding mdc
        //    private static String stringStatic;
        //    public static String stringStaticPublic;
        //    public String stringPublic;
        //    public final String stringPublicFinal = "final";
  
        public void log(String log) {
            LOG.info(log);
  
        }
    }
  
}

Example: Simplified explaination on how you can visualize how Proxy actually working.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package examples;
  
import com.ericsson.commonlibrary.proxy.Interceptor;
import com.ericsson.commonlibrary.proxy.Invocation;
import com.ericsson.commonlibrary.proxy.Proxy;
  
public class InterceptionInnerWorkingsExplaination { 
  
    public static class SomeImpl {
  
        public void log(String log) {
            System.out.println(log);
        }
    }
  
    public static class MyInterceptor implements Interceptor {
  
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            //possible do something before calling the original method
            Object returnObject = invocation.invoke();
            //possible do something after calling the original method
            return returnObject;
        }
    }
  
    public static void main(String[] args) {
        SomeImpl proxy = Proxy.intercept(new SomeImpl(), new MyInterceptor());
        proxy.log("hello world");
    }
  
    public static class MyInterceptor2 implements Interceptor {
  
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("Method entered: " + invocation.getMethodName());
            Object returnObject = invocation.invoke(); //original method invocation.
            System.out.println("Method exit: " + invocation.getMethodName());
            return returnObject;
        }
    }
  
    //Roughly what Proxy creates: 
    public class SomeImplCreatedByProxy extends SomeImpl {
  
        @Override
        public void log(String log) {
            //now it's up to the interceptors to decide what this method should do.
            getInterceptorList().invokeAll();//delegation to original "SomeImpl" is the last interceptor
        }
    }
  
    //IGNORE these.
    public static Invoke getInterceptorList() {
        return null;
    }
      
    public interface Invoke {
        void invokeAll();
    }
}