As a leader of the London Java Community and as a developer in several firms I have noticed one trait across Java developers. We are spoilt! We have nice APIs, abstractions, IDEs and build systems which means that often we don't appreciate some of the lower levels of what is actually happening within the Java ecosystem. Most people can get by without this knowledge, however in the situation that there is an outage or a problem and we simply have no idea why something has started to fail, it's not the best time to start reading up on advanced Java topics.
Sometimes we think we have enough information, because in a topic like class loading it might be enough to say "It's how Java loads classes". In my opinion this lack of knowledge is fairly dangerous, so in the Advanced Java course I recently taught with Mallon Associates I wanted to build an example that would capture the attention of the delegates. It covers four topics that we may be forgiven for forgetting Java is capable of.
The topics that I was aiming to teach amongst others were:
- NIO2 file watch service API
- Compilation of code at runtime
The example I came up with was the WatchingClassLoader. The WatchingClassLoader is a toy example which watches a folder for the arrival of a new file. When a file lands in the inbox it checks to see whether it is a .class file or .java file. If it is a .class file the class loader will load it, if it is a .java file it first compiles it and then loads the compiled version. The main thread also holds a loop waiting for input into the terminal, this allows the user to specify a class and see via reflection the methods of the now loaded class within system. As the class loader is hierarchical if it is asked for a class it doesn't know about it defers to the parent. It works with simple classes that are in the default package (i.e. no package) - though it could be extended to be smarter.
The WatchService is a neat API that allows you to register a Path to monitor for a particular file based event to occur. You can specify the type of event that you are interested in using the StandardWatchEventKinds class. Using the .take() method on the watcher blocks until that event occurs. In the final example this runs on a separate thread.
It is possible to write your own class loader in Java. By default the class loader in Java looks on the classpath specified when running the java command - but more often this is provided from your build system specifying the path to all the library jars your running application will require. Class loading is lazy, so a class is only loaded into the system when it is first required - this is why your code can sometimes run for years and then when it goes down a spurious code path you suddenly find yourself with a ClassNotFoundException.
Writing a custom class loader gives you the opportunity to change this default behaviour. Examples of class loaders might be that when a class is requested it is loaded from a remote network location or your class files are stored in an encrypted format and the class loader is responsible for decrypting the file. In this case I just want to load the class in from where I am watching for new files to be added. To create your own class loader you need to extend the ClassLoader abstract class.
At minimum you need to be looking to override the loadClass method and basically get a byte array of the contents of class file to load. From here you need to call resolveClass and defineClass to correctly load the class into the virtual machine. My system works by checking if the class aiming to be loaded is visible in the directory I am watching - otherwise it defers to the parent class loader to load the class. Class loaders are hierarchical in nature so this is an acceptable way of delegating upwards the loading of the class. The situation that this occurs in my mini example is when the user types something like java.lang.String into the console. A cutdown example of how to do this is below.
Compiling on the Fly
One feature that surprised many of the people who took my course was that it is possible to invoke the Java Compiler from within an executing Java process. ToolProvider allows us to get hold of the JavaCompiler and from there we can pass in a CompilationTask which contains the .java file we are looking to compile. This is best shown by example:
The final stage is reflection, which allows the user to look at the .java file or .class file they have added to the running system. In a demo this is the closest we get to the wow moment :). Reflection is the Java mechanism that allows inspection of a class at runtime. Most people are using reflection, even if they don't know they are doing so, as it is heavily used in frameworks such as Spring. In the example I just print out the methods as this is enough to prove the point, though on the actual course we use reflection for many other purposes including to invoke methods and generally prove what is possible. The simple snippet for allowing reflection on our classes via a custom class loader looks like this:
The Full Project
You can find the full toy example that you can run and play around with on GitHub. As this is a teaching example, there are plenty of places that this could be taken further - so I welcome any pull requests or tidy up to the code. It was actually something originally produced via live coding in front of the class, which has been a fairly tricky learning curve to get used to. That said now I wouldn't run a course without live coding, as it's where you get the most questions and although it's massively stressful it makes for a great session.