Packaging Swing apps with integrated JavaFX content
- by igor
JavaFX provides a lot of interesting capabilities for developing rich client applications in Java, but what if you are working on an existing Swing application and you want to take advantage of these new features? Maybe you want to use one or two controls like the LineChart or a MediaView. Maybe you want to embed a large Scene Graph as an initial step in porting your application to FX. A hybrid Swing/FX application might just be the answer.
Developing a hybrid Swing + JavaFX application is not terribly difficult, but until recently the deployment of hybrid applications has not simple as a "pure" JavaFX application. The existing tools focused on packaging FX Applications, or Swing applications - they did not account for hybrid applications.
But with JavaFX 2.2 the tools include support for this hybrid application use case.
Solution
In JavaFX 2.2 we extended the packaging ant tasks to greatly simplify deploying hybrid applications. You now use the same deployment approach as you would for pure JavaFX applications. Just bundle your main application jar with the fx:jar ant task and then generate html/jnlp files using fx:deploy. The only difference is setting toolkit attribute for the fx:application tag as shown below:
<fx:application id="swingFXApp" mainClass="${main.class}" toolkit="swing"/>
The value of ${main.class} in the example above is your application class which has a main method. It does not need to extend JavaFX Application class.
The resulting package provides support for the same set of execution modes as a package for a JavaFX application, although the packages which are created are not identical to the packages created for a pure FX application. You will see two JNLP files generated in the case of a hybrid application - one for use from Swing applet and another for the webstart launch.
Note that these improvements do not alter the set of features available to Swing applications. The packaging tools just make it easier to use the advanced features of JavaFX in your Swing application. The same limits still apply, for example a Swing application can not use JavaFX Preloaders and code changes are necessary to support HTML splash screens.
Why should I use the JavaFX ant tasks for packaging my Swing application?
While using FX packaging tool for a Swing application may seem like a mismatch at face value, there are some really good reasons to use this approach. The primary justification for our packaging tools is to simplify the creation of your application artifacts, and to reduce manual errors. Plus, no one should have to write JNLP by hand.
Some specific benefits include:
Your application jar will include a launcher program. This improves your standalone launch by:
checking for the JavaFX runtime
guiding the user through any necessary installations
setting the system proxy for Java
The ant tasks will generate JNLP and HTML files for your swing app:
avoids learning unnecessary details about JNLP, and eliminates the error-prone hand editing of JNLP files
simplifies using advanced features like embedding JNLP and signing jars as BLOBs to improve launch performance.you can also embed the signing certificate details to improve the user's experience
allows the use of web page templates to inject the generated code directly into your actual web page instead of being forced to copy/paste the generated code snippets.
What about native packing?
Absolutely! The very same ant task can generate a native bundle for a Swing application with JavaFX content. Try running one of these sample native bundles for the "SwingInterop" FX example: exe and dmg. I also used another feature on these examples: a click-through license agreement for .exe installers and OS X DMG drag installers.
Small Caveat
This packaging procedure is optimized around using the JavaFX packaging tools for your entire Swing application. If you are trying to embed JavaFX content into existing project (with an existing build/packing process) then you may need to experiment in order to find the best way to integrate the JavaFX packaging steps into your existing build procedure.
As long as you can use ant in your build process this should be a workable approach.
It some cases solution could be less than ideal. For example, you need to use fx:jar to package your main jar file in order to produce a double-clickable jar or a native bundle. The jar will be created from scratch, but you may already be creating the main jar file with a custom manifest. This may lead to some redundant steps in your build process. Hopefully the benefits will outweigh the problems.
This is an area of ongoing development for the team, and we will continue to refine and improve both the tools and the process. Please share your experiences and suggestions with us. You can comment here on the blog or file issues to JIRA.
Sample code
Here is the full ant code used to package SwingInterop. You can grab latest JavaFX samples and try it yourself:
<target name="-post-jar">
<taskdef resource="com/sun/javafx/tools/ant/antlib.xml"
uri="javafx:com.sun.javafx.tools.ant"
classpath="${javafx.tools.ant.jar}"/>
<!-- Mark application as Swing-based -->
<fx:application id="swingFXApp" mainClass="${main.class}" toolkit="swing"/>
<!-- Create doubleclickable jar file with embedded launcher -->
<fx:jar destfile="${dist.jar}">
<fileset dir="${build.classes.dir}"/>
<fx:application refid="swingFXApp" name="SwingInterop"/>
<manifest>
<attribute name="Implementation-Vendor" value="${application.vendor}"/>
<attribute name="Implementation-Title" value="${application.title}"/>
<attribute name="Implementation-Version" value="1.0"/>
</manifest>
</fx:jar>
<!-- sign application jar. Use new self signed certificate -->
<delete file="${build.dir}/test.keystore"/>
<genkey alias="TestAlias"
storepass="xyz123" keystore="${build.dir}/test.keystore"
dname="CN=Samples, OU=JavaFX Dev, O=Oracle, C=US"/>
<fx:signjar keystore="${build.dir}/test.keystore"
alias="TestAlias" storepass="xyz123">
<fileset file="${dist.jar}"/>
</fx:signjar>
<!-- generate JNLPs, HTML and native bundles -->
<fx:deploy width="960" height="720" includeDT="true"
nativeBundles="all"
outdir="${basedir}/${dist.dir}" embedJNLP="true"
outfile="${application.title}">
<fx:application refId="swingFXApp"/>
<fx:resources>
<fx:fileset dir="${basedir}/${dist.dir}" includes="SwingInterop.jar"/>
</fx:resources>
<fx:permissions/>
<info title="Sample app: ${application.title}"
vendor="${application.vendor}"/>
</fx:deploy>
</target>