The Java API is a new addition to Substrate, but it is based on the same workflow as the C APIs that have been in use for years. Developers can use these APIs to add hooks programmatically, making it possible to achieve use cases such as "hook all methods in a package that take an integer as an argument".
First, a developer must get a reference to the Java class containing the code they wish to modify. They can either do this directly using .class
, indirectly using Object.getClass, using standard Java reflection as with Class.forName, or using MS.hookClassLoad to wait until the class is loaded.
Second, methods on the class can be found using Java reflection, and then hooked using MS.hookMethod. This API takes a replacement implementation in the form of a small object which implements an interface with a single method called "invoked".
Finally, developers normally need the ability to "proceed" to the original implementation (the one that existed before your modifications), possibly at a different time, or passing different arguments. This is accomplished by using an "invoke" method provided by Substrate on the hook object.
Sometimes, the design of a Java package makes it difficult to make modifications, as many of the fields, methods, or even classes and interfaces, are marked either private or protected. At the JNI level—as would be use with the C API—these restrictions do not exist, but they can be painful in Java.
These situations can be worked around with Substrate in Java by marking classloader as loading classes that exist outside of the security model using the C API MSJavaBlessClassLoader; however, this API is not directly accessible (at least currently) using the Java API.
However, when Substrate itself loads the code for your extension, it first blesses the classloader it uses. This allows you to get these benefits, isolated to only the classes that you ship with your extension, without having to drop to the level of C. For more information, please read our guide on Java Access Control.
One key thing to realize while developing in Java is that it includes a robust solution to code library dependency issues: even though libraries are carefully namespaced, that is not relied upon for purposes of disambiguating multiple loaded copies of similar code.
As an example, it is possible for a program to load plug-ins from third-parties, each of which comes with its own version of the same library for its own internal use. As this library, over time, would have maintained the same API, the name and package namespace of its classes would now conflict.
However, if these plug-ins were loaded by different class loaders (and they should be), each copy of these conflicting classes will be considered unrelated for purposes of the VM. In fact, if you tried to cast one to the other, you'd get a confusing error saying "a.b.C cannot be cast to type a.b.C".
Developers thereby need to be aware that hooks installed by MS.hookClassLoad might be run multiple times with different Class arguments, even in the same process, even inside of the same VM, even as part of the same high-level application.
If you write code, therefore, which modifies code that is not loaded from the system classloader, you will not be able to directly refer to these types from your code unless you first reload your extension under a new ClassLoader that is below the loader that loader that class.
As this is a common use case for Substrate, as the code for individual applications will be loaded in their own ClassLoader that has the application's package as its path, an API is provided to make this process easier: MS.moveUnderClassLoader.