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.
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
- Java Dynamic Proxy by Bob Tarr
- Dynamic Proxy documentation by Oracle