DEV Community

Deniss Sudak
Deniss Sudak

Posted on

Java proxy for overlapping interfaces

If you are writing in Java and need to convert a DTO (data transfer object) to JSON before sending it out, there are libraries that will do this task for you. Jackson is one of them. It’ll use Java reflection to look for getters to figure out what data to write to JSON.

Let’s say you have a few of these DTOs and some parts of them overlap, that is they have the same fields in common. You don’t need many overlapping DTOs before you end-up with code duplication that you can’t overcome with inheritance alone. Does it matter? DTOs are just lightweight data containers. There is no business logic in them. What duplication are we talking about?

Fine. But a DTO doesn’t have to be just a POJO when it has the potential to be so much more :) For example, it can represent your integration with an accounting system:

class AccountingDTO {
   List<LineItem> getProfitLoss();
   List<ListItem> getBalanceSheet();
   List<Invoice> getInvoices();
}
Enter fullscreen mode Exit fullscreen mode

Imagine that these getters fetch data from Xero.

Now let’s say you only need to P&L and balance sheet converted to JSON to serve the first request. Another request requires P&L and invoices, and the third needs only invoices and, again, the balance sheet. Giving the entire AccountingDTO to Jackson will serialise everything making unnecessary requests to that accounting system as well as sending back JSON larger than is needed. If you split into 3 DTOs then, for example, the getProfitLoss method would have to appear in two of them and that’s duplication.

The solution I came up with is to create three interfaces that list only the getters required for their corresponding requests while keeping all the business logic in AccountingDTO.

Class diagram

For each of these interfaces, create what I call a super‑interface proxy — a proxy instance that exposes only the required subset of methods, while every call is passed through to the full AccountingDTO implementation. Giving one of these proxies to Jackson generates JSON that contains only what the interface (and not the underlying implementation) specifies.

public class SuperInterfaceProxyFactory<Super, T extends Super> {
    private final Class<Super> superClass;

    public SuperInterfaceProxyFactory(Class<Super> superClass) {
        this.superClass = requireNonNull(superClass);
    }

    public Super newProxy(T obj) {
        checkNotNull(obj);
        return (Super) Proxy.newProxyInstance(
                superClass.getClassLoader(),
                new Class[]{superClass},
                new PassThroughInvocationHandler(obj)
        );
    }

    private static class PassThroughInvocationHandler implements InvocationHandler {
        private final Object target;

        private PassThroughInvocationHandler(Object target) {
            this.target = requireNonNull(target);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return method.invoke(target, args);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Here your Super could be AccountingDTO1 interface, and T the implementation that contains all the methods. The object
returned by newProxy will generate JSON that you need. Have a look at the code example: https://github.com/denissudak/java-super-interface-proxy

I hope you find it useful. Enjoy!

Top comments (0)