中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档 | 网通镜像
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 程序开发 > 编程语言 > Java > Java与XML
Using PatchExpert to Extend Your Code More Easily
作者:未知 时间:2005-08-10 18:18 出处:Java频道 责编:chinaitpower
              摘要:Using PatchExpert to Extend Your Code More Easily

It's often very difficult to apply extensions or patches to an existing application. Consider the steps required. First, a software developer has to modify some source code. Then a platform engineer rebuilds and repackages the system, creating a detailed installation guideline. Finally, a deployment engineer redeploys the software at customer site. Hopefully, it all works. Is there any method to make these tasks simpler?

PatchExpert is a simple tool to satisfy this requirement. It can insert software extensions or patches through some predefined extension points.

Problem

Let's look at an example first to see how difficult it is to apply a patch to an existing application.

TestMain is a simple application used to print out an onscreen triangle of asterisk ("*") characters. It has a Calculator class to calculate the level of triangle according to the given integer and asks TrianglePrint to print out the triangle.

Here's the source code for a basic implementation. Calculator uses the Singleton pattern, and is used to figure out how many asterisks to print out. This implementation simply doubles the given argument.

public class Calculator {
    static Calculator instance = new Calculator();

    public static Calculator getInstance() {
        return instance;
    }

    protected Calculator() {
    }

    public int calc(int num) {
        return num * 2;
    }
}

TrianglePrint prints out the triangle line by line, according to the given level.

public class TrianglePrint {
    int level;

    public TrianglePrint(int level) {
        this.level = level;
    }

    public void print() {
        for (int i = 1; i <= level; i++)
            System.out.println(getLine(i));
    }

    protected String getLine(int num) {
        StringBuffer sb = new StringBuffer();
        for(int i = 1; i <= num; i++)
            sb.append("*");
        return sb.toString();
    }

    public int getLevel() {
        return level;
    }
}

Finally, TestMain exercises the code by getting a Calculator instance, calling calc(), and sending the result on to TrianglePrint.

public class TestMain {
    public static void main(String[] args) {
        Calculator c = Calculator.getInstance();
        int num = c.calc(4);
        TrianglePrint printer = new TrianglePrint(num);
        printer.print();
    }
}

The application will print out an ASCII art triangle like this:

*
**
***
****
*****
******
*******
********

Now imagine that after the application has been deployed on the customer site, the requirements change: both the Calculator algorithm and TrianglePrint print behavior need to be changed. A patch should be applied to the software. Below are the tasks typically taken to achieve that goal.

  • Software developer's task

    The developer uses inheritance to minimize the changes applied directly to the existing source code. He needs to add two new classes and modify two of the original classes.

    He creates ModernCalculator, a subclass of Calculator. It overrides calc(int) to provide a new algorithm that simply adds 1 to the given integer number instead of doubling it.

    public class ModernCalculator
                        extends Calculator {
       public int calc(int num) {
           return num + 1;
       }
    }
    
    

    A new FancyTrianglePrint subclasses TrianglePrint. It overrides getLine(int) to generate a new string for the given line.

    public class FancyTrianglePrint
                      extends TrianglePrint {
       public FancyTrianglePrint(int level) {
           super(level);
       }
    
       protected String getLine(int num) {
           int level = getLevel();
           StringBuffer sb = new StringBuffer();
           for (int t = 1; t <= level - num; t++)
               sb.append(" ");
           for(int t = 1; t <= num; t++) {
               sb.append("*");
               if (t != num)
                   sb.append(" ");
           }
           for (int t = 1; t <= level - num; t++)
               sb.append(" ");
           return sb.toString();
       }
    }
    
    

    Now the new implementation classes are ready. But how to integrate them into the existing application? TestMain needs to be modified to use FancyTrianglePrint instead of TrianglePrint.

    public class TestMain {
       public static void main(String[] args) {
           Calculator c = Calculator.getInstance();
           int num = c.calc(4);
    //TrianglePrint printer = new TrianglePrint(num);
           TrianglePrint printer = new FancyTrianglePrint(num);
           printer.print();
       }
    }
    
    
    Since Calculator uses a Singleton pattern, it should be modified to use a singleton instance of ModernCalculator instead of Calculator.
    public class Calculator {
    //static Calculator instance = new Calculator();
       static Calculator instance = new ModernCalculator();
       ...
    }
    
    
  • Platform engineer's task

    With changes to the project source, the platform engineer needs to add the new files to the project, rebuild the whole system, re-package it (for a big project, it is most likely that some modules need to be rebuilt and re-packaged) and ship it to the deployment engineer with a detailed patch installation guide.

  • Deployment engineer's task

    The deployment engineer needs to install the patch on the customer site. The installation involves backing up old files and deleting, adding, or replacing files according to the patch installation guide.

Now the application with patch is ready to work. It prints out the triangle like this:



    *
   * *
  * * *
 * * * *
* * * * *

Introducing PatchExpert

Framework

The java.net project PatchExpert is a simple tool to make extending and patching software easier. It allows the developer to define some extension points in the target application. Through these extension points, it can insert implementation at runtime or by configuration. Figures 1 and 2 demonstrate the relationship among these elements.

The relationship among elements
Figure 1. The relationship among elements

The relationship among elements
Figure 2. The relationship among elements

The software architect decides where the software can be extended. At these extension points, the developer makes a call to PatchExpert, providing a name for the extension point.

There can be many different implementations to be inserted into the extension point. Of course, these implementations should have something in common. They often have the same superclass or implement the same interface.

An external XML configuration file is used to map the named extension point to a specific implementation. Of course, this means it is possible to change implementations without touching the original code.

PatchExpert API

ObjectFactory is the main class of PatchExpert. Let's check the methods in this class one by one.

  • public static ObjectFactory getInstance()

    This is the factory method. It always returns the singleton instance of ObjectFactory.

  • public Object newObject(String module, String name)

    This method creates an implementation instance for a specific extension point. The parameter module is the module name of the extension point. It acts like a namespace to avoid name collision. The parameter name specifies the extension point in the module. This method is suitable for an implementation class with an accessible default constructor.

  • public Object newObject(String module, String name, ClassLoader cl)

    This method is almost the same as newObject(String module, String name). The only difference is that it uses cl to specify which class loader is used to load the implementation class.

  • public Object newObject(String module, String name, Class[] paramTypes, Object[] paramValues)

    This method is used when the implementation class has a non-default accessible constructor. The parameter paramTypes is used to specify the constructor signature, while paramValues specifies the parameters for the constructor.

  • public Object newObject(String module, String name, Class[] paramTypes, Object[] paramValues, ClassLoader cl)

    This method is almost the same as newObject(String module, String name, Class[] paramTypes, Object[] paramValues), except that it uses the given class loader cl to load the implementation class.

    These newObject() methods only specify the extension point and how to initialize the implementation instance. They have no knowledge about which implementation class will be used for that extension point. It is the external configuration file's responsibility to combine a specific implementation class with the extension point.

  • public void putClassConfig(String moduleName, String name, String className)

    This method is used to change the combination of implementation class and extension point at runtime. The parameter className gives the full name of the implementation class. The effect of this method will overwrite the setting in the external configuration file.

  • public void refresh()

    This method is used to refresh the combination of implementation class and extension point at runtime. It can be called after the external configuration file has changed.

Configuration File

The configuration file is where you specify implementation classes for extension points. The default configuration file name is classfactory.xml. If needed, this name can be changed through the system property org.jingle.patchexpert.configname. The configuration file should be placed in the META-INF directory, which can be accessed by class path or .jar file.

Below is a template of the configuration file.

<?xml version="1.0" encoding="UTF-8"?>
<application>
<module name="moduleName">
  <extPoint className="class.full.name" name="pointName"/>
  ...
  <extPoint .../>
</module>
...
<module ...>
...
</module>

<patch name="patchfile.xml"/>
...
<patch .../>
</application>

  • The configuration file should have a root tag application.
  • There can be zero or more module tags and patch tags under the application tag.
  • The module tag acts like a namespace. It contains zero or more extPoint sub-elements.
  • The extPoint tag combines the extension point with an implementation class name to define an extension point.
  • The patch tag is used to specify the patch configuration file location, which is relative to the META-INF directory. The patch configuration file has the same format and its settings will overwrite the original settings.
There may be more than one accessible configuration file in an application. When the ObjectFactory is initialized, it loads all of the accessible configuration files, according to the opposite order of the class path. Configuration file settings loaded later will overwrite previous ones.

An Example of Using PatchExpert

Now let's apply PatchExpert to the previous example. The code has only a few changes.

The TrianglePrint instance creation is an extension point. Instead of creating an explicit TrianglePrint instance, we create an implementation instance for the extension point printer in the module triangle. The implementation class should have an accessible constructor with an int parameter.

public class TestMain {
    public static void main(String[] args) {
        Calculator c = Calculator.getInstance();
        int num = c.calc(4);
//TrianglePrint printer = new TrianglePrint(num);
        TrianglePrint printer = (TrianglePrint)
                ObjectFactory.getInstance().newObject(
                            "triangle",
                            "printer",
                            new Class[] {Integer.TYPE},
                            new Object[] {new Integer(num)});
        printer.print();
    }
}

There's another extension point where the Calculator singleton instance is created. We create an implementation instance for the extension point cal in the module calculator here. The implementation class should have an accessible default constructor.

public class Calculator {
//static Calculator instance = new Calculator();
    static Calculator instance =
       (Calculator) ObjectFactory.getInstance().newObject(
                                      "calculator", "cal");

    public static Calculator getInstance() {
        return instance;
    }

    protected Calculator() {
    }

    public int calc(int num) {
        return num * 2;
    }
}

Now you need a classfactory.xml file, under the META-INF directory and inside of the target sample.jar file.

 <?xml version="1.0" encoding="UTF-8"?>
  <application>
    <module name="triangle">
      <extPoint className="TrianglePrint" name="printer"/>
    </module>
    <module name="calculator">
      <extPoint className="Calculator" name="cal"/>
    </module>
  </application>

This configuration file connects the actual implementation classes to the extension points.

The target sample.jar file is deployed on the customer site. Now look at how the requirements change is handled when PatchExpert is used.

  • Software developer's task

    The developer provides two new classes, ModernCalculator and FancyTrianglePrint, as before. There is no modification applied to the old source code. This can reduce the risk of introducing new bugs when fixing old ones.

  • Platform engineer's task

    The platform engineer builds the new Java files with the original sample.jar and packages the new Java classes into patch.jar with a classfactory.xml under the META-INF directory.

    <?xml version="1.0" encoding="UTF-8"?>
    <application>
     <module name="triangle">
       <extPoint className="FancyTrianglePrint" name="printer"/>
     </module>
     <module name="calculator">
       <extPoint className="ModernCalculator" name="cal"/>
     </module>
    </application>
    
    
    This is a much lighter workload compared to rebuilding the whole system. And there is no need to re-package the system. Only the newly created patch.jar is shipped to the deployment engineer.

  • Deployment engineer's task

    The deployment engineer just drops the patch.jar file into the library directory and makes sure that it appears before sample.jar in the class path.

That's all. Now the patch works. It is much easier than before, isn't it?

Pros and Cons Compared to an AOP Solution

Aspect-Oriented Programming (AOP) is another commonly used technique to provide extensions and patches to an existing application. The AOP approach and PatchExpert both try to avoid touching the old system in order to reduce the upgrade risk. But at the byte-code level, the AOP compiler will insert some byte code into old class files while compiling the new aspect. Compared to the AOP approach, PatchExpert has the following pros and cons:

Pros:

  • AOP's byte-code-level change may require the rebuilding and repackaging of a whole module or system. And the deployment engineer still has many things to do: backing up old files and deleting, adding, or replacing files according to the patch installation guide. A PatchExpert approach is free of this workload.
  • With the help of PatchExpert, the target application can switch implementations via an edit of the external configuration file. In some cases, it can even switch the implementation at runtime.
  • With PatchExpert, it is very easy to discard the extension or patch and roll back to the original version. No build is required; just delete the add-on patch package.

Cons:

  • For PatchExpert to work, it has to be part of the application framework. This means it should be integrated at the beginning of the software design process. You cannot use PatchExpert to apply a patch to an application that does not already use PatchExpert. In this case, an AOP solution is much more flexible, since an AOP approach can be used regardless of whether the original application uses AOP or not.
  • PatchExpert asks the architect to think carefully in the design phase about where extension points should go. AOP has no such requirement; it can define arbitrary "point cuts" as its extension points.

Conclusion

PatchExpert can simplify the task of applying extensions or patches to an existing application. It just adds the patch package without touching the original system. This reduces the risk of introducing new bugs when fixing old ones. In the meantime, it makes the rolling back easier: it's no more complicated than deleting the add-on patch package.

Of course, PatchExpert can be used in other domains, such as software integration testing. For example, when the integration test depends on external services that are not currently available, it is very easy to continue the test by adding mock services into the test environment without touching the source code. When the external services are available, just remove the mock services and test again.

Reference

Lu Jian is a senior Java architect/developer with four years of Java development experience.

关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有