How to prune a Java program
Let's me start from what I want to do then raising some questions I have.
I want to develop a general Java program which is a superset of a number of programs (let's call them program variants). In particular, the general program has methods which are only used by one or more program variants (but not all). Given a particular configuration, I want to remove unnecessary methods and just keep the smallest set of methods for one program variant.
For example, I have a general program as below:
public class GeneralProgram {
// this method is common for all variants
public void method1() {};
// this method is specific to variant 1
public void method2() {};
// this method is specific to variant 2
public void method3() {};
}
Then after pruning the program based on configuration for variant 1, the r开发者_运维技巧esult is
public class GeneralProgram {
// this method is common for all variants
public void method1() {};
// this method is specific to variant 1
public void method2() {};
}
It doesn't matter if the resulting class name is the same as the original one or not. I just want to prune the content of the class.
So, here are my questions:
Do you have any idea how to realize this except low level text processing?
I know that I can use aspectJ to disable/enable specific methods at runtime but what I really want to do is performing this task before deploying the program. Is there any technique in Java for this purpose?
It seems to me that the right solution here is to use some object oriented programming and layer your program:
base.jar contains:
package foo.base;
class GeneralProgram {
public void method1(){ }
}
var1.jar contains:
package foo.var1;
import foo.base.GeneralProgram;
class GeneralProgramVar1 extends GeneralProgram {
public void method2(){ }
}
var2.jar contains:
package foo.var2;
import foo.base.GeneralProgram;
class GeneralProgramVar2 extends GeneralProgram {
public void method3(){ }
}
Some deployments will have both base.jar and var1.jar, others will have base.jar and var2.jar. You'll have to mess with the classpaths a bit to resolve the dependencies.
If you can separate your variants well enough so that there are truly unused functions then you can use a compression utility like ProGuard to remove unused methods from the classes. You might find, however, that the effort required to reap the benefits of ProGuard are the same as the structure I recommend above.
@Mark Elliot's answer gives you a "right way" to do this.
There are a number of reasons why your way is not a good idea in general, and for Java applications in particular:
Java does not support this. Specifically, it does not support conditional compilation.
While source code preprocessors are sometimes used, mainstream Java tool chains don't support them. (Same for (hypothetical?) tools that operate at the bytecode level ... though that's not what you seem to be talking about.)
With conditionally compilation variants, it is easier for a change made in one variant to break another. (By contrast, a good O-O design will isolate variant-specific code to particular classes where they can't affect the behaviour of other variants.)
A codebase with rampant conditional compilation is much harder to understand.
Conditional compilation variants make testing more complicated. You basically have to treat each variant as a separate application that has to be tested separately. This makes writing tests more complicated, and running tests more expensive. (And testing the variants IS important because of the fragility of code bases that rely on conditional compilation; see previous.)
Test coverage analysis is harder / more work with variants because of tool issues; see previous.
In a comment the OP writes:
So it is not effective if I deploy unnecessary resources (e.g. methods, classes specific to other variants) for a particular variant.
What do you mean by "not effective"?
In most cases it simply does not matter that a code base includes functionality that is not used in certain use-cases or on certain platforms. Java applications use lots of memory, and code size is generally not the major cause of this. In short, in most cases it is "effective" to deploy code that won't be used: it does the job and the overheads don't really matter.
If you have one of those unusual applications where JAR file size or code memory usage is really significant (and not just a hypothetical issue), you still don't need to resort to conditional compilation or bytecode hacking.
If JAR file size is the critical issue, then there are tools that will strip out classes and methods that the tool determines will not be used; e.g. assuming that the application is started from a specified
main
method.If memory usage is the critical issue, you can structure your code so that it uses dynamic loading to load variant, platform or even use-case specific classes.
精彩评论