中国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
Bytecode Generation Tips and Tricks @ JDJ
作者:未知 时间:2005-08-10 18:15 出处:Java频道 责编:chinaitpower
              摘要:Bytecode Generation Tips and Tricks @ JDJ

This article introduces readers to bytecode generation and shows how to inject generated bytecode into a JVM runtime. After reading this article generating a Java class won't be any harder than creating an XML document with the DOM API.

Over last couple of years, bytecode generation has gained significant momentum. Many tools generate bytecode instead of source code to obviate the compilation step and simplify the injection of generated code at runtime.

There's a number of bytecode-generation libraries with BCEL (Byte Code Generation Library) being the most renown and probably most powerful. It's used by tools such as Xalan's stylesheet compiler and Mercury Interactive's Topaz J2EE Probe.

Bytecode vs Source Generation
You may ask: "Why bother with bytecode generation if I can produce Java source?" Ultimately, it's your call what technique to use. For example, some people create XML documents with println(), others use the DOM API. Keep the following considerations in mind:

  • With Jad, Jadclipse or DJ Java Decompiler (see Resources) a .class file is an open book as .java file. But read-only. So there's no need in (and actually no place for) formidable headers like "Generated by XXX - Do not edit!"
  • There is an additional compilation step if you produce source instead of bytecode. Compilation at runtime is a real big headache, especially if you don't control the target JVM.
  • The amount of Java code you have to write to produce Java source won't be significantly less than the amount of Java code for bytecode generation. Actually, it may be more.
  • Using different template engines has its own vices. First of all generation logic gets distributed between the Java code and template code, which makes the whole solution less manageable and more fragile, because templates aren't easy to debug. Second, as you start adding more and more to your templates they will soon become unreadable. Take a look at Xdoclet templates - they look like cuneiform on the Hammurabi stella.
Use Scenarios
BCEL can be used for code generation (this is what XSLTC and SQLC do) and for code modification (this is what the Mercury Topaz J2EE Probe does). This article covers the first scenario - code generation. In MDA parlance it describes how to build the T in PIM+T -> PSM equation where PSM is Java bytecode and PIM is a XSL stylesheet in the case of XSLTC and SQL statements in the case of SQLC.

Truth to tell BCEL API is a bit cumbersome...elaborate... I've created several helper classes to make life easier.

Runtime Generation
Classes can be generated at build time and runtime. The first case is a trivial one - generated classes can be used like any other Java classes.

Runtime code generation is a more interesting theme. When you generate classes at runtime you need to make them available to JVM. This can be done with com.pavelvlasov.InjectingClassLoader:


1.Interface myInterface=
2.	new Interface(
3.		"public interface com.myorg.myapp.
MyGeneratedInterface extends java.io.Serializable", 
4.		"My Generated interface", 
5.		null);
6....
7.// Injecting
8.ClassLoader parentClassLoader = ... 
9.InjectingClassLoader icl=new InjectingClassLoader(parentClassLoader);
10.icl.consume(myInterface);
11....

We injected generated classes and/or interfaces in the Java runtime. How to use them? If generated classes implement interfaces or extend classes known at compile time then the answer is obvious - instantiate and cast.

What if generated classes don't comply with the statement above? How to use classes not known at compile time? Well, the answer is that information about generated classes can be obtained through reflection. Scripting environments such as JSP, JSTL, Velocity, and script interpreters won't distinguish injected classes from any other.

An important note about runtime generation: BCEL isn't thread-safe. If you're going to generate classes in multithreaded environment then each generating thread should have its own classloader for BCEL classes. This will result in a bigger memory footprint because BCEL classes will be presented in memory once per generating thread.

Generating Interfaces
Generating interfaces is the simplest task because interfaces' methods are all abstract.


1.Interface myInterface=new Interface("public interface com.myorg.myapp.
MyGeneratedInterface extends
 java.io.Serializable", "My Generated interface", null);

Adding a method is as straightforward as creating an interface itself:


1.myInterface.addMethod("void setMyValue(java.lang.String str)", 
null, "My generated method");

If the method parameters aren't known at coding time then they can be supplied in the second parameter of addMethod(), which will either be null or a collection of com.pavelvlasov.util.Parameter implementations.

Adding a field is also a one-liner:


1.myInterface.addField("MY_CONSTANT", "java.lang.String");

But fields in interfaces are static final and shall be initialized in the static initializer:


1.InstructionList il=new InstructionList();
2.il.append(new LDC(myInterface.getClassGen().getConstantPool().addString
("My constant value")));
3.il.append(myInterface.createPutField("MY_CONSTANT"));
4.il.append(new RETURN());
5.myInterface.addStaticInitializer(il, "Initializes myInterface");

Once you've created an interface and added methods you should either save it to a file for future use or inject it into the runtime. To save the interface you can invoke its save(File) method or obtain BCEL JavaClass object using getJavaClass() method and do whatever you want with that object.

In case the generated interface needs to be injected into Java runtime com.pavelvlasov.codegen.InjectingClassLoader comes into play.


1.ClassLoader parentClassLoader = ...;
2.InjectingClassLoader icl=new InjectingClassLoader(parentClassLoader);
3.icl.consume(myInterface);
4.Class myInterfaceClass=icl.loadClass("com.myorg.myapp.MyGeneratedInterface");
5....

Generating Classes
Generating classes is a bit more complicated than generating interfaces since you need to generate method implementations.

My advice on code generation is to:

  • Minimize the amount of code to be generated by moving the functionality to the superclass and helper classes
  • Create a template method in Java and compile it.
  • Run org.apache.bcel.util.Class2-HTML to generate class HTML documentation.
  • Run org.apache.bcel.util.BCELifier. It will create code, which will generate a template class. There's a bug in BCELifier shipped in BCEL 5.1 and it doesn't work on all classes. See Resources below for a download link to the fixed version.
  • Use instructions produced by BCELifier as a starting point. You can also copy bytecode instructions from generated HTML documentation to your generator method; comment them out and then write code using commented instructions as guidelines.
  • Run org.apache.bcel.verifier.Verifier or com.pavelvlasov.codegen.ClassGeneratorBase.verify() on the generated classes.
  • Use Jad, Jadclipse or DJ Java Decompiler to decompile generated files and verify method logic. I recommend that you set the "annotate" option so you can see the bytecode instruction as comments.
Writing linear bytecode is very simple as we've seen from the previous section. Branches (if, while,...) and exception handlers are the things that require attention. Let's see how to generate code that has both branches and exception handlers using the advice above. This is the code we're going to generate:


1.public int templateMethod(String str) {
2.	if (str==null) {
3.		return 0;
4.	} else {
5.		try {
6.			return Integer.parseInt(str);
7.		} catch (NumberFormatException e) {
8.			return -1;
9.		}
10.	}
11.}

This is the output of BCELifier:


1.InstructionList il = new InstructionList();
2.MethodGen method = new MethodGen(ACC_PUBLIC, Type.INT, new Type[] 
{ Type.STRING }, new String[] { "arg0" }, "templateMethod",
 "com.pavelvlasov.codegen.samples.TemplateClass", il, _cp);
3.
4.InstructionHandle ih_0 = il.append(_factory.createLoad(Type.OBJECT, 1));
5.BranchInstruction ifnonnull_1 = _factory.createBranchInstruction
(Constants.IFNONNULL, null);
6.il.append(ifnonnull_1);
7.InstructionHandle ih_4 = il.append(new PUSH(_cp, 0));
8.il.append(_factory.createReturn(Type.INT));
9.InstructionHandle ih_6 = il.append(_factory.createLoad(Type.OBJECT, 1));
10.il.append(_factory.createInvoke("java.lang.Integer", "parseInt", 
Type.INT, new Type[] { Type.STRING }, Constants.INVOKESTATIC));
11.InstructionHandle ih_10 = il.append(_factory.createReturn(Type.INT));
12.InstructionHandle ih_11 = il.append(_factory.createStore(Type.OBJECT, 2));
13.InstructionHandle ih_12 = il.append(new PUSH(_cp, -1));
14.InstructionHandle ih_13 = il.append(_factory.createReturn(Type.INT));
15.ifnonnull_1.setTarget(ih_6);
16.method.addExceptionHandler(ih_6, ih_10, ih_11, new ObjectType
("java.lang.NumberFormatException"));
17.method.setMaxStack();
18.method.setMaxLocals();
19._cg.addMethod(method.getMethod());
20.il.dispose();

Now, we change it to be less cryptic:


1.MethodPrototype mp=new MethodPrototype(myClass, "public int getMyInt
(java.lang.String str)", null);
2.InstructionList il = new InstructionList();
3.ExceptionHandler eh=new ExceptionHandler
("java.lang.NumberFormatException");
4.il.append(mp.createVariableLoad("str"));
5.BranchInstruction ifnonnull = InstructionFactory.createBranchInstruction
(Constants.IFNONNULL, null);
6.il.append(ifnonnull);
7.il.append(new ICONST(0));
8.il.append(mp.createReturn());
9.InstructionHandle ih = il.append(mp.createVariableLoad("str"));
10.ifnonnull.setTarget(ih);
11.eh.setFrom(ih);
12.il.append(myClass.createInvoke("java.lang.Integer",
"int parseInt(java.lang.String)", null, Constants.INVOKESTATIC));
13.eh.setTo(il.append(mp.createReturn()));
14.eh.setHandler(il.append(InstructionFactory.createStore(Type.OBJECT, 2)));
15.il.append(new ICONST(-1));
16.il.append(mp.createReturn());
17.Collection ehc=new ArrayList();
18.ehc.add(eh);
19.mp.addMethod(il, ehc, "My generated method");

After that we run org.apache.bcel.verifier.Verifier to verify the generated class. Then we run Jad and compare the decompiled code with the original. Once you become familiar with bytecode, you won't ned BCELifier. I use only Jadclipse with annotations.

Generating Documentation
If you generate classes that implement some interface then you probably don't need to document them. For example, Xalan XSLTC produces a bunch of classes but you don't need documentation for these classes, all you need to know is the class name. On the other hand, SQLC (see the Resources) generates classes and interfaces from SQL statements and in this case documentation is necessary. The good thing about classes from the com.pavelvlasov.codegen package is that if you use them to generate bytecode then the work to generate documentation is close to zero. You need to do is to use the class com.pavelvlasov.codegen.HtmlDocConsumer. You can also use xml.DocConsumer to produce XML documentation and then style it. Stylesheets are available in the SQLC sample application.


1.HtmlDocConsumer consumer=
2.	new HtmlDocConsumer(
3.		new File("generated"), 
4.		new File("generated_doc"));
5.
6.com.pavelvlasov.codegen.Class myClass=
7.	new com.pavelvlasov.codegen.Class(
8.		"public class com.myorg.myapp.MyGeneratedClass", 
9.		"My Generated class", 
10.		consumer.getListener());
11....
12.// Saving to file.
13.consumer.consume(myClass.getJavaClass());
14.consumer.shutdown();

Conclusion
I hope that after reading this article bytecode generation will become part of your skillset.

There are many cases when there's a model/data structure in a non-Java software system that should be exposed to Java. It can be an XML schema, a mainframe map, database metadata... There's probably no reason to write a generator for an XML schema because it's already done many times, just select an XML-Java binding solution that fits your needs. But for special cases bytecode engineering is a good way to generate bridges.

Resources

  • Source code: www.pavelvlasov.com/articles/bcg/source.zip. You need to download the PvCommons library (see below) to run the samples.
  • BCEL: http://jakarta.apache.org/bcel/
  • BCEL with fixed bug: www.pavelvlasov.com/bcel-5.1-fixed.zip
  • JVM instructions reference: http://cat.nyu.edu/~meyer/jvmref/
  • Antlr: (www.antlr.org) - Parser generator.
  • Jad (http://kpdus.tripod.com/jad.html) - fast Java decompiler
  • Jadclipse (http://sourceforge.net/projects/jadclipse/) - Eclipse plugin for Jad
  • DJ Java Decompiler (http://members.fortunecity.com/neshkov/dj.html) - GUI for Jad
  • PvCommons (www.pavelvlasov.com/pv/content/Products/Common/products.common.html) - my library of common classes
  • com.pavelvlasov.codegen package - Code generation classes.
  • com.pavelvlasov.sqlc package - SQL compiler. Uses codegeneration classes to compile SQL statements to Java classes.
  • com.pavelvlasov.cache.sql - Example of classes compiled by SQLC
  • 关闭本页
     
    首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
    Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有