DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Building Scalable Real-Time Apps with AstraDB and Vaadin
Register Now

Trending

  • Comparing Cloud Hosting vs. Self Hosting
  • Microservices With Apache Camel and Quarkus
  • Competing Consumers With Spring Boot and Hazelcast
  • RBAC With API Gateway and Open Policy Agent (OPA)

Trending

  • Comparing Cloud Hosting vs. Self Hosting
  • Microservices With Apache Camel and Quarkus
  • Competing Consumers With Spring Boot and Hazelcast
  • RBAC With API Gateway and Open Policy Agent (OPA)
  1. DZone
  2. Coding
  3. Java
  4. Proxies Done Right with Guava's AbstractInvocationHandler

Proxies Done Right with Guava's AbstractInvocationHandler

Tomasz Nurkiewicz user avatar by
Tomasz Nurkiewicz
CORE ·
Dec. 26, 13 · Interview
Like (0)
Save
Tweet
Share
5.30K Views

Join the DZone community and get the full member experience.

Join For Free

Not too often but sometimes we are forced to write custom dynamic proxy class usingjava.lang.reflect.Proxy. There is really no magic in this mechanism and it's worth knowing even you will never really use it - because Java proxies are ubiquitous in various frameworks and libraries.

The idea is quite simple: dynamically create an object that implements one or more interfaces but every time any method of these interfaces is called our custom callback handler is invoked. This handler receives a handle to a method that was called (java.lang.reflect.Method instance) and is free to behave in any way. Proxies are often used to implement seamless mocking, caching, transactions, security - i.e. they are a foundation for AOP.

Before I explain what the purpose ofcom.google.common.reflect.AbstractInvocationHandler from the title, let's start from a simple example. Say we want to transparently run methods of given interface asynchronously in a thread pool. Popular stacks like Spring (see: 27.4.3 The @Async Annotation) and Java EE (see: Asynchronous Method Invocation) already support this using the same technique.

Imagine we have the following service:

public interface MailServer {
void send(String msg);
int unreadCount();
}

Our goal is to run send() asynchronously so that several subsequent invocations are not blocking but queue up and are executed in external thread pool concurrently rather than in calling thread. First we need factory code that will create a proxy instance:

public class  AsyncProxy {
public static <T> T wrap(T underlying, ExecutorService pool) {
final ClassLoader classLoader = underlying.getClass().getClassLoader();
final Class<T> intf = (Class<T>) underlying.getClass().getInterfaces()[0];
return (T)Proxy.newProxyInstance(
classLoader,
new Class<?>[] {intf},
new AsyncHandler<T>(underlying, pool));
}
}

Code above makes few bold assumptions, for example that an underlying object (real instance that we are proxying) implements exactly one interface. In real life a class can of course implement multiple interfaces, so can proxies - but we simplify this a bit for educational purposes. Now for starters we shall create no-op proxy that delegates to underlying object without any added value:

class AsyncHandler<T> implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(AsyncHandler.class);
private final T underlying;
private final ExecutorService pool;
AsyncHandler1(T underlying, ExecutorService pool) {
this.underlying = underlying;
this.pool = pool;
}
@Override
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
return method.invoke(underlying, args);
}
}

ExecutorService pool will be used later. The last line is crucial - we invoke method onunderlying instance with the same args. At this point we can:

  • invoke underlying or not (e.g. if given call is cached/memoized)
  • change arguments (i.e. for security purposes)
  • run code before/after/around/on exception
  • alter result by returning different value (it must match the type ofmethod.getReturnType())
  • ...and much more

In our case we will wrap method.invoke() with Callable and run it asynchronously:

class AsyncHandler<T> implements InvocationHandler {
private final T underlying;
private final ExecutorService pool;
AsyncHandler(T underlying, ExecutorService pool) {
this.underlying = underlying;
this.pool = pool;
}
@Override
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
final Future<Object> future = pool.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return method.invoke(underlying, args);
}
});
return handleResult(method, future);
}
private Object handleResult(Method method, Future<Object> future) throws Throwable {
if (method.getReturnType() == void.class)
return null;
try {
return future.get();
} catch (ExecutionException e) {
throw e.getCause();
}
}
}

Extra handleResult() method was extracted in order to properly handle non-voidmethods. Using such a proxy is straightforward:

final MailServer mailServer = new RealMailServer();
final ExecutorService pool = Executors.newFixedThreadPool(10);
final MailServer asyncMailServer = AsyncProxy.wrap(mailServer, pool);

Now even if RealMailServer.send() takes a second to complete, invoking it twice viaasyncMailServer.send() takes no time because both invocations run asynchronously in background.

Broken equals(), hashCode() and toString()

Some developers are not aware of potential issues with default InvocationHandlerimplementation. Quoting the official documentation:

An invocation of the hashCode, equals, or toString methods declared injava.lang.Object on a proxy instance will be encoded and dispatched to the invocation handler's invoke method in the same manner as interface method invocations are encoded and dispatched, as described above.In our case case this means that for example toString() is executed in the same thread pool as other methods of MailServer, quite surprising. Now imagine you have a local proxy where every method invocation triggers remote call. Dispatching equals(),hashCode() and toString() via network is definitely not what we want.

Fixing with AbstractInvocationHandler

AbstractInvocationHandler from Guava is a simple abstract class that correctly deals with issues above. By default it dispatches equals(), hashCode() and toString() toObject class rather than passing it to invocation handler. Refactoring from straightInvocationHandler to AbstractInvocationHandler is dead simple:

import com.google.common.reflect.AbstractInvocationHandler;
class AsyncHandler<T> extendsAbstractInvocationHandler {
//...
@Override
protected Object handleInvocation(Object proxy, finalMethod method, finalObject[] args) throwsThrowable {
//...
}
@Override
public String toString() {
return"Proxy of "+ underlying;
}
}
That's it! I decided to override toString() to help debugging. equals() andhashCode() are inherited from Object which is fine for the beginning. Now please look around your code base and search for custom proxies. If you were not usingAbstractInvocationHandler or similar so far, chances are you introduces few subtle bugs.
Thread pool Interface (computing) Java EE Object (computer science) security Java (programming language) Factory (object-oriented programming) Cache (computing) Documentation

Published at DZone with permission of Tomasz Nurkiewicz, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Trending

  • Comparing Cloud Hosting vs. Self Hosting
  • Microservices With Apache Camel and Quarkus
  • Competing Consumers With Spring Boot and Hazelcast
  • RBAC With API Gateway and Open Policy Agent (OPA)

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: