Channels ▼
RSS

Tools

Modifying an Application without Modifying Its Source Code


A Tangled Web We Weave

An alternative to modifying the JRE is to modify a Java applications bytecode. If we could effectively modify the bytecode, we wouldnt need to recompile the original source code. Because we are modifying selected portions of bytecode in an application, the changes would be localized and contained -- unlike the previous JRE modification approach. There are several useful and powerful tools available to modify Java bytecode such as Javassist. We chose to leverage AspectJ, the popular aspect-oriented programming framework for Java, for our bytecode modifications. AspectJ is less powerful than a tool like Javassist that lets you directly manipulate bytecode, but it was sufficient for our needs and easy to integrate with our workflow.

Aspect-oriented programming (AOP) evolved from OOP as a means to separate common code fragments that cross-cut a number of classes. Proponents of AOP maintain that encapsulating this common functionality in a single aspect enhances the maintainability of the code. For our purposes, we were less concerned with the pros and cons of aspects and more interested in leveraging tools that weave these aspects across the objects. AspectJ supports the weaving of aspects at load-time, so we dont need to rebuild the application. AspectJ relies upon text pattern matching to identify join points in the application where advice from the aspect can be inserted.

Using AspectJ, we can take an approach that is less like an evil twin and more like a parasite that can be attached when needed. To selectively replace Sockets with our own customized Socket (e.g., CustomSocket), we can use AspectJ to create an aspect that wraps around calls to the Socket constructor. This aspect takes the Socket that is being created in that constructor call and passes it to the CustomSocket constructor. The CustomSocket will delegate to the Socket that has been passed in, injecting behavior to mimic certain properties. For example, to mimic low bandwidth, the CustomSocket will return a modified CustomInputStream that wraps the original InputStream. This CustomInputStream will sleep for a set period of time on each "read" operation, resulting in net data transfer at the desired bandwidth.

A simple AspectJ aspect is shown in Example 2. Its job is to find calls to two kinds of Socket constructors, one with no arguments and one with two arguments, a String and an int. Notice that we are matching against calls to the Socket constructor and not the execution of the Socket constructor. This distinction means that our pointcut will insert bytecode into any class that instantiates a Socket with one of these constructors, rather than inside the Socket class itself. The advantage of not modifying the Socket is that the original vanilla Socket is still available for those classes that need it. This is a useful benefit of our aspect weaving approach, but the dirty little secret is that this was done by necessity rather than by choice.


public aspect SocketSwap
{
  pointcut onCreateSocketNoArgs():
    call (Socket.new()) && !within(SocketSwap);
    pointcut onCreateSocketTwoArgs(String host, int port):
    args(host, port) && call(Socket.new(..)) && !within(SocketSwap);
    Socket around(): onCreateSocketNoArgs()
  {
    return new CustomSocket(new Socket());
  }
    Socket around(String host, int port) throws UnknownHostException, IOException: onCreateSocketTwoArgs(host, port) 
  {
    return new CustomSocket(new Socket(host, port));
  }
}

Example 2: An example aspect that replaces a Socket with a CustomSocket

The downside of this bytecode manipulation approach is that we are modifying the application codes direct access to Sockets only, and indirect invocations will not be modified. Because AspectJ uses a custom classloader to insert aspects, classes loaded via other classloaders (e.g., the bootstrap classloader) cannot be woven. Although some other bytecode manipulation tools are more flexible, similar constraints apply. This is why we could not modify Socket bytecode directly. Leaving Socket unmodified is fine for our purposes, but there is a more ominous implication. The inability to weave into classes on the bootstrap classloader means that we cant modify JRE classes that may internally invoke Socket constructors, such as several classes within the java.rmi package. You could potentially implement customized RMI classes and apply these aspects in the same way that we applied our CustomSocket, but not only would you need to understand and duplicate the non-Socket related internals of the RMI classes, but you would also need to carefully review the implementation of the JRE to capture all Socket invocations. Similar to the modified JRE approach, this would be highly version-specific and similarly brittle.

Conclusion

We have demonstrated two approaches to modifying the behavior of third-party Java applications without rebuilding or otherwise directly modifying the original source code. They both have their pros and cons, but under the right circumstances can provide an interesting means to modify behavior. While not all applications can be successfully modified by these two approaches, there will be many Java applications that can benefit from this post-build modification of behavior.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video