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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

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
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

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • AOT Compilation Make Java More Power
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World
  • How Spring and Hibernate Simplify Web and Database Management

Trending

  • Caching 101: Theory, Algorithms, Tools, and Best Practices
  • Designing Fault-Tolerant Messaging Workflows Using State Machine Architecture
  • ITBench, Part 1: Next-Gen Benchmarking for IT Automation Evaluation
  • Data Lake vs. Warehouse vs. Lakehouse vs. Mart: Choosing the Right Architecture for Your Business
  1. DZone
  2. Coding
  3. Java
  4. Java Dynamic Proxies

Java Dynamic Proxies

A lot of modern Java frameworks use dynamically generated code. This article is designed to demystify a little of what is happening behind the scenes.

By 
Alan Hohn user avatar
Alan Hohn
·
Dec. 26, 15 · Tutorial
Likes (18)
Comment
Save
Tweet
Share
23.8K Views

Join the DZone community and get the full member experience.

Join For Free
When I've worked with new users of the Spring framework and Java EE, I've seen a lot of initial confusion over why injection works in some places but not others, or at some times but not others. My theme when teaching this material is to repeat, "there is no magic". Wherever there is an annotation that "magically" injects some object or manages a transaction, there is some piece of code somewhere doing the dirty work. Annotations by themselves are inert; they don't do anything. Instead, another class somewhere uses Java reflection to find the annotation and take some action based on it.

In this article I want to show one of the behind-the-scenes techniques: creating dynamic proxies using classes built into the standard Java library. A proxy is a class that replaces or sits in between some regular class to add some extra behavior. For example, let's say we have a requirement to create an audit log of all the calls to certain services. We could modify every service to update the audit log, but we don't really want to mix in auditing behavior with the behavior of the real service. Instead, we can write a single class that does the auditing consistently, then use a proxy approach to apply this behavior to lots of services.

So we might have a class that looks like:

public class Auditor {

  public void audit(String service, String extraData) {
    // ... Do the auditing
  }

}

Let's say we had a real service. For the purposes of this article I'll use a calculator, which is what I like to use when I can't be bothered to think of something useful:

public interface Calculator {
  int add(int left, int right);
}

public class CalculatorImpl implements Calculator {
  public int add(int left, int right) {
    return left + right;
  }
}

Note that we create an interface; this makes it easier to write a proxy, and for the dynamic proxies built into the Java standard library it is an essential step.

We can now make a very simple proxy that provides auditing for our calculator:

public class AuditingCalculator implements Calculator {
  private Calculator inner;
  private Auditor auditor;
  public AuditingCalculator(Calculator inner, Auditor auditor) {
    this.calculator = calculator;
    this.auditor = auditor;
  }
  public int add(int left, int right) {
    auditor.audit("calculator", "before add");
    int result = inner.add(left, right);
    auditor.audit("calculator", "after add");
    return result;
  }
}

As long as they have a way to get the correct instance (e.g. a factory or dependency injection), users of the calculator do not need to know whether they are using the real service directly or the proxied version.

Unfortunately, looking at this example reveals some important flaws. We have to write a separate class for every service we are auditing, so we really are not saving any lines of code. Additionally, our proxy is tightly coupled to the interface it is proxying, so every time a service has to change we have multiple places to make code changes.

This is where the need for a dynamic class comes in. When we write Java source code and compile it, the compiler writes class files containing bytecode, which is a pseudo-assembly language designed to be run on the Java Virtual Machine (JVM). This bytecode is read dynamically by the JVM when a class is loaded, while the program is running. Of course, there is no reason the bytecode has to exist in files on the disk. When we write in scripting languages (e.g. Groovy) on the JVM, the bytecode generation is saved until runtime. Similarly, when using a dynamic class, the JVM is dynamically generating bytecode, then loading it in as it does any other class.

To make it easier to dynamically generate bytecode, the JVM provides static methods in the class java.lang.reflect.Proxy. These methods can generate a dynamic class that implements one or more interfaces. Real frameworks, like Spring, Java EE, or Blueprint, use this capability; however, in some cases they must dynamically generate proxies for concrete classes, not just interfaces, so they also use third-party libraries like CGLib and ASM.

In our case, we can use a dynamic proxy to save ourselves the work of writing an auditing proxy for every service we need to audit. We start by creating an "invocation handler", which is a class that can accept any method call and take some action based on it:

public class AuditingInvocationHandler implements InvocationHandler {
  private final Auditor auditor;
  private final Object target;
  public AuditingInvocationHandler(Auditor auditor, Object target) {
    this.auditor = auditor;
    this.target = target;
  }
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    auditor.audit(target.getClass().getName(), "before " + method.getName());
    Object returnObject = method.invoke(target, args);
    auditor.audit(target.getClass().getName(), "after " + method.getName());
    return returnObject;
  }
}

This code is a little more complicated, because it has to be able to handle any kind of method on any object. This example does not make very good audit logs, but note that we can pass this invocation handler any data through its constructor, so if we need to improve the behavior we can.

To use this invocation handler, we will dynamically generate a proxy that pretends to implement the Calculator interface, but really calls the invocation handler. The invocation handler audits, then delegates to some "real" calculator:

Auditor auditor = ...;
Calculator real = new CalculatorImpl();
InvocationHandler handler = new AuditingInvocationHandler(auditor, real);

Calculator proxy = (Calculator) Proxy.newProxyInstance(
  ClassLoader.getSystemClassLoader(), new Class[] { Calculator.class }, handler);

real.add(2, 2); // Will not be audited
proxy.add(2, 2); // Will be audited

The key is the newProxyInstance() method, which dynamically generates a class that claims to implement Calculator. When any method is called on the dynamically generated class, that method call is routed to the invocation handler. Note that this way of doing things means that we can use the same invocation handler for many different dynamic proxies, each implementing a different interface. So we can write this code once and use it to audit many services.

The last two lines of the above code illustrate a very important point about using proxies and especially dynamic proxies. There is no magic, so in order for the service call to be audited, the proxy must actually be invoked! If we allow non-proxied instances to get passed around and used, we will not get the proxied behavior. I have seen this confusion many times when using transaction support in Spring and Java EE, where someone will directly instantiate a class annotated as @Transactional but not get transactional behavior. The transactional behavior is performed by a proxy, so only proxied instances will work right. In general, for both Spring and Java EE, if the container injects the object, it will contain all the right proxy logic as needed.

Of course, there is much more complexity with a dependency injection framework, including scanning for Java annotations, matching constructors to instantiate classes using reflection, and matching dependencies by type for dependency injection. Hopefully this small example has given some insight into a key piece of how these frameworks are able to provide functionality where the implementation is hidden from users of the framework.

Java (programming language) Java virtual machine Spring Framework

Opinions expressed by DZone contributors are their own.

Related

  • AOT Compilation Make Java More Power
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World
  • How Spring and Hibernate Simplify Web and Database Management

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!