Вы находитесь на странице: 1из 7

Fro m C o d e to Arc hit e ctur e : Th e A ct o f Cla s sl o a din g a n d In sta n c e Cre ati o n

written by Jamel TAYEB

INTRODUCTION
The hardest part (to me at least) about software architecture in the small is translating its vocabulary into concrete and fathomable developer/programmer code-speak. If there is any hope to bring this discipline to the mere mortal, it should perhaps go through this path. In this article, I speak about one architectural mechanism hidden behind the innocent methods java.lang.ClassLoader.loadClass() and java.lang.Class.newInstance().

THE BASIC STEPS


The ability to load a class, creat object instances of that class and invoke known or unknown methods on those objects yields tremendous parameterization power. The mechanism is ubiquitous in all frameworks so let us see the four basic practical steps to achieve this 1. 2. 3. 4. Obtaining a ClassLoader. Creating a Class object from the class name pased as a String. Creating an object instance from the Class object. Invoking a known method.

There are three main methods to obtain the ClassLoader from inside any class. The first is the Thread.getContextClassLoader() which gives the ClassLoader of the current thread we are in. This might have been set by an earlier Thread.setContextClassLoader() executed by the thread that created our current thread. If this is null, we revert to the ClassLoader that was used to load our own call (whose code we are exeuting). Finally, we might also use directly the system ClassLoader using the static method ClassLoader.getSystemClassLoader(). The code snippet below shows the first two steps.
// Three ways to obtain a ClassLoader. cl1 = Thread.currentThread().getContextClassLoader(); cl2 = this.getClass().getClassLoader(); cl3 = ClassLoader.getSystemClassLoader(); // Creating the Class object. try { Class clazz = classLoader.loadClass("my.package.MyClass"); } catch (ClassNotFoundException cnfe) { clazz = null; }

The Class object is created from the String giving the full package name of the target class using the static method ClassLoader.loadClass(). This method loads the bytes of the . class file and creates a corresponding Class object inside the Java virtual machine. It is this critical step that transforms a String holding only a symbolic name into a usable object in the JVM. Of course, the .class file corresponding to the class name must reside in the Classpath otherwise a ClassNotFoundException is raised.

To complete the four steps, we use the following example. We created a very simple utility class shown below that only provides the mathematical logarithm function.
package my.framework.consumer; /** * My utility class. It has lots of useful functions. * @version 1.0 */ public class MyPersonalUtils { public static double logarithm(double x) { return Math.log(x); }

Note that it is part of the my.framework.consumer package. We then write a very simple class that performs the two steps above then creates a MyPersonalUtils object and invokes its logarithm() method. Here is the code below
package my.first.framework; import my.framework.consumer.MyPersonalUtils; /** * Some tests on Class loading and oject instantiation. * @version 1.0 */ public class ClassLoaderTest { public ClassLoaderTest() {} public static void main(String[] args) { ClassLoader cl; ClassLoaderTest tstCL = new ClassLoaderTest(); MyPersonalUtils utils=null; Class clazz; // Get the ClassLoader. cl = Thread.currentThread().getContextClassLoader(); if (cl==null) cl = tstCL.getClass().getClassLoader(); try { // Load the Class object from the ful package name. clazz = cl.loadClass("my.framework.consumer.MyPersonalUtils"); // Instantiate an object. if (clazz!=null) utils = (MyPersonalUtils) clazz.newInstance(); // Invoke a static method. double y = utils.logarithm(456); System.out.println("log(10000)="+y); } catch (ClassNotFoundException cnfe) { System.out.println("ClassNotFoundException ENCOUNTERED !!! "+cnfe); } catch (IllegalAccessException iae) { System.out.println("IllegalAccessException ENCOUNTERED !!! "+iae); } catch (InstantiationException ine) { System.out.println("InstantiationException ENCOUNTERED !!! "+ine); } System.out.println("It works !!!");

As you can see, once we have our Class object, we call its newInstance() method. We need to cast to the real concrete object we have created if we are to call its methods. This cast might throw the java.lang.ClassCastException which is a runtime exception if it fails. After this, we have our precious object that lives like any other object of our code inside the virtual machine. We conclude by invoking the static logarithm() method which delegates to Math.log(). We have the following output.
log(10000)=6.1224928095143865 It works !!!

Although nothing is apparently extraordinary with this piece of code, we succeeded a most trivial case of parameterized computation. Starting from a class name, we could dynamically have acess to the logarithm computation on a class that is 'outside our universe'. The example is very trivial because 1. We know the concrete class name 2. We know the method name 3. We know the method parameters. Nothing impressive; especially that we have even an explicit import statement so we really did not need this level of indirection in the first place but the example served to illustrate accessing computation from the mere name of class (passed as String).

ONE MORE LEVEL OF INDIRECTION


The next step is to exploit the power of polymorphism to load a class whose name we do not know (no import statement) but which implements an interface we know; or even better, an interface we have designed!. In this case, 1. 2. 3. 4. We do NOT know the concrete class. We know the interface or base class We know the method name We know the method parameters.

Let us define a new interface for a car object which we call ICar in our 'framework' as follows
package my.first.framework; /** * The interface for a car. * We are interested only in washing it :). * @version 1.0 */ public interface ICar { public void wash(); }

The framework user then, imports the framework classes or interfaces he needs and defines his own car class implementing the ICar interface from the framework. Here is an example
package my.framework.consumer; import my.first.framework.ICar; /** * A concrete car class implementing the ICar interface * @version 1.0 */ public class MyFavouriteCar implements ICar { public MyFavouriteCar() {} public void wash() { long washTime = 100000000, wash=0; System.out.println("Your favourite car is being washed ... "); for (long l=0; l < washTime; l++) wash++; System.out.println("Your car is washed; thanks for your patience.");

Note the import statement at the beginning of the code. The framework's main class will then dynamically load this concrete implementation and execute the wash() method. We reuse the class loading and object instantiation mechanism illustrated above.
package my.first.framework; /** * Some tests on Class loading and oject instantiation. * @version 1.0 */ public class ClassLoaderTest { public ClassLoaderTest() {} public static void main(String[] args) { ClassLoader cl; ClassLoaderTest tstCL = new ClassLoaderTest(); ICar yourCar=null; Class clazz; // Get the ClassLoader. cl = Thread.currentThread().getContextClassLoader(); if (cl==null) cl = tstCL.getClass().getClassLoader(); try { // Load the Class object from the ful package name. clazz = cl.loadClass("my.framework.consumer.MyFavouriteCar"); // Instantiate an object; cast to the interface type. if (clazz!=null) yourCar = (ICar) clazz.newInstance(); // Invoke a method. yourCar.wash(); } catch (ClassNotFoundException cnfe) { System.out.println("ClassNotFoundException ENCOUNTERED !!! "+cnfe); } catch (IllegalAccessException iae) { System.out.println("IllegalAccessException ENCOUNTERED !!! "+iae); }

catch (InstantiationException ine) { System.out.println("InstantiationException ENCOUNTERED !!! "+ine); } "); } } System.out.println("I washed your car without knowing its brand !!!

We used the interface as an example link between framework code and user code, but it could also be an abstract class from which user code inherits or even an implicit relationship whereby user code defines a concrete class MyClassImpl that contains impementations of all the methods of a framework interface or abstract class without the explicit 'implements' or 'extends' relationship. In this case, the only link is the mechanism by which the concrete user class names are passed to the framework's ClassLoader.loadClass() code fragments; usually through custom framework configuration files or java system properties. Each interface whose corresponding instance class is loaded through the ClassLoader could be seen as one parameter of the framework. It adds one degree of freedom to the framework user. It is a parameter, if we may say, of a higher order whose values are passed during runtime through the class loading mechanism. This is illustrated in Figure 1 below.

Figure 1. The communication points between framework designer and framework user through class implementation and dynamic class loading The typical framework has a driver class with a main() method illustrated symbolically with the Framework.main() method in Figure 1. It makes use of a whole lot of internal framework classes and interfaces that are not visible to the framework user. This is illustrated by the green rectangle in the figure. It also defines some interfaces (or abstract classes) that concern directly the framework user. These are illustrated in Figure 1 with the blue squares entitles I1 and I2. These are the real communication points between framework designer and framework user. For some such interfaces, implementing a concrete class will be necessary to fill the missing parts of the framework's mechanism. If an interface implementation is not necessary for a concrete execution of a framework instance, then it could be seen as a possible extension point. Two concrete classes implementing I1 and I2 are illustrated in Figure 1 as belonging to the framework user's space.

EXAMPLE
The Figure below demonstrates the concept of framework extension and implementation through dynamically loaded user classes that implement framework interfaces or classes. The example is that of the Struts framework.

Figure 2. User and framework classes in Struts The Struts framework for web development implements the front controller servlet design pattern wherein its central ActionServlet class implements that javax.servlet.HttpServlet interface and drives the whole web application framework through its process() method. According to the URL invoked by the view pages (usually JSP pages) delegates the request processing to an object loaded at runtime that extends the org.apache.struts.action.Action abstract class. This is illustrated in Figure 2 with the user class UsrAction. Once the correct object instace is created, the Action.execute() method is invoked directly. In Struts, the complete package name of the UsrAction class is read during servlet initialization from the Struts configuration file. Then during execution, code identical to the one shown in the examples above is used to load and instantiate the corresponding Action object. The same mechanism is used with the second framework class org.apache.struts.action.ActionForm. If there is an HTML form in the web page, its data is wrapped in a concrete object whose class extends ActionForm. The framework then executes the reset() and validate() methods on the form object. Finally, it is passed as an argument to the Action.execute() method (among other stuff). This is illustrated in Figure 2 above by the UsrForm user class. Again, inside the Struts framework's code, the complete package name of the form class is read during the servlet initialization phase from Struts' specific configuration file and a concrete object is instantiated during the servlet execution phase. The very methods exemplified above are used by the framework to achieve this goal.

CONCLUSION
To illustrate how innocent code can mean a lot from a software architecture or design pattern perspective, we started with a discussion on class loading and instantiation methods in the core Java library and moved toward a discussion on framework building, framework usage and framework extension mechanisms. We illustrated with a diagram that the act of class loading and instantiation should be seen as a communication point between framework designer and user. Finally, we demonstrated the ideas with a realworld framework called Struts for building web applications. The act of dynamic class loading and object instantiation has an architectural value and significance that we hope the reader is now more aware of.

Author
Jamel TAYEB is a software engineer working for a software services firm based in Paris. He's been with Java, Server-side and J2EE technologies since 1999. He holds a Bachelor of Science and a Master of Science degrees in Computer Science from Bilkent University (Ankara) and had some research experience in the database and information systems laboratory of the University of Illinois at Chicago. e-mail jtay205@yahoo.fr Web Site http://membres.lycos.fr/jamel

Вам также может понравиться