Getting started with dynamic proxies in Java

Java Dynamic Proxy

A proxy class is a class that acts as an intermediate layer between the client and the target object. In the case of dynamic classes, they have generated automatically at runtime. Dynamic proxies are specifically useful to generate type-safe reflective dispatch of invocations on objects. In this article, we explain how to get started with dynamic proxies in Java.

A rudimentary dynamic proxy example

Let say in an application, we have a set of service classes that implement the Service interface. Then we would like to add some logs before each method execution of classes that implement the Service interface.

One obvious approach is to go and modify all our service class methods and add the method execution logging. But that’s not a sustainable approach and breaks the separation of concerns design principle.

Another approach is to use interceptors by relying on libraries such as Spring AOP. In the real world scenarios, usually, interceptors should be used.

But do you know how Spring AOP works underneath? It uses Dynamic Proxies. For the sake of this example, we want to go low-level. So we use dynamic proxies instead. This way you will learn even how Spring AOP underneath works as well.

You must keep in mind that the objective of dynamic proxies is not to add the functionality of the target object. But it is to control access to the object.

Diagram of what to implement

Dynamic proxy implementation

To implement our example, we start by creating the Service interface as follows,

package com.madadipouya.sample.dynamic.proxy;

public interface Service {
}

Since this is a very rudimentary example, the Service interface has no methods define and merely acts as a marker interface.

The next step is to implement our services. We use the library application metaphor here. Let say we have three services:

  • BookService
  • AuthorService
  • LibraryService

The following section shows the implementation of each.

First, the BookService implementation,

package com.madadipouya.sample.dynamic.proxy.book.service;

import com.madadipouya.sample.dynamic.proxy.Service;
import com.madadipouya.sample.dynamic.proxy.book.entity.Book;

import java.util.List;

public interface BookService extends Service {

    void getBookById(long id);

    List<Book> getBooks();

    Book addBook(Book book);

    boolean deleteBook(long bookId);
}
package com.madadipouya.sample.dynamic.proxy.book.service;

import com.madadipouya.sample.dynamic.proxy.book.entity.Book;
import java.util.List;

public class DefaultBookService implements BookService {

    @Override
    public void getBookById(long id) {
        // Implementation details
        System.out.println(String.format("Found Book with Id = %s", id));
    }

    @Override
    public List<Book> getBooks() {
        // Implementation details
        System.out.println("Querying to get all books");
        return List.of();
    }

    @Override
    public Book addBook(Book book) {
        // Implementation details
        System.out.println("Book added");
        return new Book();
    }

    @Override
    public boolean deleteBook(long bookId) {
        // Implementation details
        System.out.println(String.format("Book %s deleted", bookId));
        return true;
    }
}

Then AuthorService interface,

package com.madadipouya.sample.dynamic.proxy.author.service;

import com.madadipouya.sample.dynamic.proxy.Service;
import com.madadipouya.sample.dynamic.proxy.author.entity.Author;

import java.util.List;

public interface AuthorService extends Service {

    List<Author> getAllAuthors();

    Author addAuthor(Author author);
}

The AuthorService implementation:

package com.madadipouya.sample.dynamic.proxy.author.service;

import com.madadipouya.sample.dynamic.proxy.author.entity.Author;
import java.util.List;

public class DefaultAuthorService implements AuthorService {

    @Override
    public List<Author> getAllAuthors() {
        // Implementation details
        System.out.println("Getting list of all authors");
        return List.of();
    }

    @Override
    public Author addAuthor(Author author) {
        // Implementation details
        System.out.println("Adding the author!");
        return new Author();
    }
}

Lastly, the LibraryService interface,

package com.madadipouya.sample.dynamic.proxy.library.service;

import com.madadipouya.sample.dynamic.proxy.Service;
import com.madadipouya.sample.dynamic.proxy.library.entity.Library;

public interface LibraryService extends Service {

    Library searchForLibraryByLatitudeLongitude(long latitude, long longitude);
}

The LibraryService implementation:

package com.madadipouya.sample.dynamic.proxy.library.service;

import com.madadipouya.sample.dynamic.proxy.library.entity.Library;

public class DefaultLibraryService implements LibraryService {

    @Override
    public Library searchForLibraryByLatitudeLongitude(long latitude, long longitude) {
        System.out.println("Searching for a library based on the given lat and lon");
        // Implementation details
        return new Library();
    }
}

Now that we have all the services of the library app up and running, we need to implement the dynamic proxy of the Service interface. This proxy just logs a message before the method execution of any classes that implement the Service interface.

package com.madadipouya.sample.dynamic.proxy.proxy;

import com.madadipouya.sample.dynamic.proxy.Service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MethodExecutorLogger implements InvocationHandler {

    private Service service;

    public MethodExecutorLogger(Service service) {
        this.service = service;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(String.format("Executing %s method", method.getName()));
        return method.invoke(service, args);
    }
}

As you can see the log Executing [name of method] method is added before calling each invoke method, which is the delegate of the real implementation execution.

Now let’s test the example by creating an instance of each service and run a method of each.

package com.madadipouya.sample.dynamic.proxy;

import com.madadipouya.sample.dynamic.proxy.author.entity.Author;
import com.madadipouya.sample.dynamic.proxy.author.service.AuthorService;
import com.madadipouya.sample.dynamic.proxy.author.service.DefaultAuthorService;
import com.madadipouya.sample.dynamic.proxy.book.service.BookService;
import com.madadipouya.sample.dynamic.proxy.book.service.DefaultBookService;
import com.madadipouya.sample.dynamic.proxy.library.service.DefaultLibraryService;
import com.madadipouya.sample.dynamic.proxy.library.service.LibraryService;
import com.madadipouya.sample.dynamic.proxy.proxy.MethodExecutorLogger;

import java.lang.reflect.Proxy;

public class Application {

    public static void main(String[] args) {
        // Get proxy of author service
        AuthorService authorServiceProxy = getProxyOf(new DefaultAuthorService(), AuthorService.class);
        authorServiceProxy.addAuthor(new Author());

        // Get proxy of book service
        BookService bookServiceProxy = getProxyOf(new DefaultBookService(), BookService.class);
        bookServiceProxy.getBooks();

        // Get proxy of library service
        LibraryService libraryServiceProxy = getProxyOf(new DefaultLibraryService(), LibraryService.class);
        libraryServiceProxy.searchForLibraryByLatitudeLongitude(10, 20);

    }

    private static <T extends Service> T getProxyOf(T instanceImplementation, Class<? extends Service> interfaceClass) {
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] {interfaceClass},
                new MethodExecutorLogger(instanceImplementation));
    }
}

If we run the project, we get the below output that indicates the dynamic proxy is working as expected.

Executing addAuthor method
Adding the author!
Executing getBooks method
Querying to get all books
Executing searchForLibraryByLatitudeLongitude method
Searching for a library based on the given lat and lon

Conclusion

In this tutorial, we demonstrate how to get started with dynamic proxies in Java by providing a simple example. Understanding the dynamic proxy helps one to have a better grip of aspect oriented libraries such as Spring AOP and its underneath mechanics. As always, the fully functional demo is available on GitHub.

Resources

Inline/featured images credits