2012年8月2日木曜日
特定のURLをフックしてアプリを起動させる(暗黙的インテント)
・特定のURLクリック時にアプリをインストールしている場合はアプリケーション選択ダイアログが出て、ブラウザで遷移するかアプリを起動するか選べる
・アプリをインストールしていない場合は普通にページ遷移
具体的に言うと、オンエアー中のアニメのタイムラインを見に行こうとするとアプリケーションに移動するか、スマフォサイトで用意したページに行くか選べるようにしたいってこと。
まず、AndroidManifest.xmlを変更
<activity
android:name=".Test"
android:label="@string/test.title">
<intent-filter>
<action
android:name="android.intent.action.VIEW" />
<category
android:name="android.intent.category.DEFAULT" />
<category
android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="http"
android:host="test.tsubuani.com"
android:pathPrefix="/live" />
</intent-filter>
</activity>
intent-filterにaction.View,category.DEFAULT,cateogry.BROWSABLEを追加。
dataにscheme,host,pathPrefixを追加。
schemeやhostとかはそれぞれ依存しているので注意が必要。
pathPrefixの他にも、pathやpathPatternなんてのも指定できる。
細かい仕様は下記参照。
http://www.techdoctranslator.com/android/guide/manifest/data-element
今回の例で言うと
http://test.tsubuani.com/live*******
っていうURLにアクセスしようとするとTestActivityが起動するという感じになる。
ActivityはonNewIntentに処理を書いてやればよい
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Intent intent = getIntent();
if( intent != null ) {
/** リンク先のURLを取得する。 */
String data = intent.getDataString();
if(data != null){
//urlを元にごにょごにょ
}
}
}
これで、アプリをインストールしている人はアプリで実況TLを見ることが出来るし、
そうでない人はスマフォサイトで用意したページに遷移させることが出来る。
Androidはこんな感じでアプリ同士の連携がすごい簡単に出来るのが素敵。
AndroidManifest.xmlに記述したの取得方法
strings.xmlで管理する方法もありますがどちらがいいのでしょうか?
とりあえず、AndroidManifest.xmlに以下のように<meta-data>を記述します。
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".TestDroid"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data android:value="test_value" android:name="test_key" />
</application>
10行目がmeta-dataの記述です。
この値をプログラム側で取得する方法です。
ApplicationInfo appliInfo = null;
try {
appliInfo = getPackageManager().getApplicationInfo(getPackageName(),
PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {}
Log.d("MetaData", appliInfo.metaData.getString("test_key"));
このように記述します。
ちなみに、例はActivityの中での使用方法なので、
Contextのメソッドの「getPackageManager」や「getPackqgeName」は適時インスタンス変数からの呼び出しに変更してください。
ウィジェットの作り方
Home Screen上にAppWidgetを作成するための基本的な方法を説明します。
(開発にはAndroidSDK 1.6を使用しています)
まずは、「HelloWorld!」と表示するだけの簡単なAppWidgetを作成してみます。
■AndroidSDKリファレンス(サイト)
http://developer.android.com/intl/ja/reference/packages.html
■AndroidSDKリファレンス
android-sdk-windows-1.6_r1.zip
解凍後のフォルダ内
docs/reference/packages.html
プロジェクトの作成
「ファイル」→「新規」→「Androidプロジェクト」
プロジェクト名、アプリケーション名、パッケージ名を入力、ターゲット名を選択します。
デフォルトで「Create Activity」にチェックがされていますが、
今回は使用しないためチェックをはずします。
ファイルが自動生成され、ビルドが行われると、このようなファイルが作成されているのが確認できます。
「ウィンドウ」→「ビューの表示」→「ナビゲーター」で確認できるようにします。
自動生成された各ファイルの詳細については、「HelloWorld!」のページを参考にしてください。
AppWidgetの作成
AppWidgetを作成するためには次の工程が必要になります。
・AppWidgetのレイアウト作成
・AppWidgetの設定ファイル作成
・AppWidgetの処理を記述
・AndroidManifest.xmlへの登録
AppWidgetのレイアウト作成
今回は「HelloWorld!」と表示するだけですので、 自動生成されたレイアウトをそのまま使用します。
HelloWorldWidget/res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#000000">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>
表示を分かりやすくするため、
android:background="#000000"
で、背景色のみ新規に設定しています。
AppWidgetの設定ファイル作成
AppWidgetの設定を記述するXMLを作成します。
「HelloWorldWidget/res」以下に「xml」フォルダを作成し、
右クリックメニューより、「新規」→「Android XML file」を選択。
ファイル名:任意(ここではappwidget.xml)
リソースの種類:AppWidget Provider
を入力し、完了ボタンをクリックするとxmlの雛型が出来ますので、下記を参考に設定を記述します。
HelloWorldWidget/res/xml/appwidget.xml
<?xml version="1.0" encoding="UTF-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="72dip"
android:minHeight="72dip"
android:initialLayout="@layout/main"
android:updatePeriodMillis="0"
/>
android:minWidth
ウィジェットの幅(dip)
android:minHeight
ウィジェットの高さ(dip)
android:initialLayout
ウィジェットの初期レイアウト
android:updatePeriodMillis
ウィジェットの更新間隔(ms)
"0"の場合は更新しない
ウィジェットのサイズ
ウィジェットサイズは、自由には決められず、画面を4×4に分割したセル単位で指定します。
指定には、DIP(density-independent pixels)という単位を用い、これにより画面サイズの違う端末でも同じ様に表示することができます。
分割された1セルのサイズは74dipです。
ただし、実サイズ計算時に丸め誤差が発生するため、実際には次の計算式でdipを算出します。
dip = (セル数 * 74dip) - 2dip
今回は1×1セルのサイズで作成するため、72dipを指定します。
http://android-developers.blogspot.com/2009/04/introducing-home-screen-widgets-and.html
ウィジェットの更新間隔
AndroidSDK 1.6よりウィジェットの更新間隔に制限が掛かり、
30分以下は30分として扱われる様になりました。
updatePeriodMillisでの更新では、ウィジェットがバックグラウンドにいる場合にも更新が発生し、
他アプリの動作やバッテリー消費に影響があるための制限です。
時計やタイマー等、短い間隔での更新が必要な場合には、Serviceを使用した方法などがあります。
今回は表示するのみなので、0を指定します。
http://code.google.com/p/android/issues/detail?id=3990
AppWidgetの処理を記述
「src/android/sample」以下に、AppWidgetProviderクラスを継承した任意のクラスを作成します。
(ここではHelloAndroidWidgetProviderとします)
src/android/sample/HelloAndroidWidgetProvider.java
public class HelloAndroidWidgetProvider extends AppWidgetProvider {
@Override
public void onEnabled(Context context) {
Log.v("HelloAndroidWidget", "onEnabled");
super.onEnabled(context);
}
@Override
public void onUpdate(Context context, AppWidgetManager
appWidgetManager,
int[] appWidgetIds) {
Log.v("HelloAndroidWidget", "onUpdate");
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
Log.v("HelloAndroidWidget", "onDeleted");
super.onDeleted(context, appWidgetIds);
}
@Override
public void onDisabled(Context context) {
Log.v("HelloAndroidWidget", "onDisabled");
super.onDisabled(context);
}
@Override
public void onReceive(Context context, Intent intent) {
Log.v("HelloAndroidWidget", "onReceive");
super.onReceive(context, intent);
}
}
AppWidgetProviderの以下の処理をオーバーライドすることで、AppWidgetの処理を記述できます。
onEnabled()
AppWidgetが作成される際に呼ばれます。
同じAppWidgetを複数起動した際には、初回のみ呼ばれます。
全体的な初期化処理が必要な場合はここに記述します。
onUpdate()
AppWidgetが更新される際に呼ばれます。
updatePeriodMillis等で更新間隔を設定していれば、そのタイミングで呼ばれます。
また、AppWidgetを起動した際にも一度呼ばれます。
onDeleted()
AppWidgetが削除された際に呼ばれます。
終了処理が必要な場合はここに記述します。
onDisabled()
AppWidgetが全て削除された際に呼ばれます。
全体的な終了処理が必要な場合はここに記述します。
onReceive()
アクションを受け取り、AppWidgetProviderの各メソッドの呼び出しを処理します。
今回は特に処理はありませんが、動作確認のため各メソッドにログを入れています。
ログ
ログ表示にはLogクラスを使用します。
詳細はリファレンスを参照してください。
http://developer.android.com/intl/ja/reference/android/util/Log.html
Logの確認にはLogCatビューを使用します。
表示されていない場合は、eclipseの下記メニューより表示出来ます。
「ウィンドウ」→「ビューの表示」→「その他」→「Android」→「LogCat」
稀にログが出力されなくなることがありますが、eclipseの再起動もしくは
Android開発プラグインの再インストールで回避出来るようです。
AndroidManifest.xmlへの登録
AppWidgetをAndroidManifest.xmlへ登録します。
<application>タグ内に<receiver>タグを追加し、AppWidgetの情報を記述します。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="andriod.sample"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<receiver android:name=".HelloAndroidWidgetProvider"
android:label="@string/app_name">
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/appwidget" />
</receiver>
</application>
<uses-sdk android:minSdkVersion="4" />
</manifest>
<reciver>
android:nameにAppWidgetProviderを継承したクラスを".(ドット)クラス名"で記述します。
<intent-filter>
<action>タグにAPPWIDGET_UPDATEを記述し、更新イベントを受け取れることを示します。
<meta-data>
android:resourceにAppWidgetの設定ファイルを指定します。
ウィジェットの実行
eclipseメニューより「実行」→「実行構成」を選択し、実行構成ダイアログを表示します。
「AndroidApplication」を右クリック、「新規」を選択し、名前とプロジェクトを指定します。
実行ボタンクリックでAndroidエミュレータが起動します。
MENUボタンでHomeScreenへ移動します。
動作を確認するために、作成したウィジェットをHomeScreenに登録します。
HomeScreen画面を長押しすると、メニューが表示されます。
Widgetsを選択し、ウィジェット選択画面を表示。
先程作ったウィジェットを選択します。
ウィジェットの削除
ウィジェットを長押しすると、画面下部にゴミ箱アイコンが現れるので、
そこへドラッグ&ドロップします。
2012年8月1日水曜日
Antでjarファイルを作るときにmanifestファイルをClass-Pathごと自動生成する方法
<jar jarfile="${生成するjarファイルの名前}" basedir="${classファイルのあるディレクトリ}">
<path id="libraries">
<fileset dir="${外部ライブラリ用ディレクトリ}" includes="*.jar"/>
</path>
<pathconvert property="classpath" refid="libraries" targetos="unix" pathsep=" ">
<map from="${basedir}\${外部ライブラリ用ディレクトリ}\"
to="${実行時のjarファイルから外部ライブラリへのパス}"/>
<map from="\" to="/"/>
</pathconvert>
<manifest>
<attribute name="Main-Class" value="${メインクラス}"/>
<attribute name="Class-Path" value="${classpath}"/>
</manifest>
</jar>
外部ライブラリを置いてあるディレクトリからjarファイルの一覧を取得。
各jarファイルの絶対パスを置き換えて、半角スペース区切りの文字列にする。
manifestファイルにメインクラスと一緒に指定。
という感じ。
以下にEclipseで使っているAnt用build.xmlのテンプレートを載せておく。
<?xml version="1.0" encoding="UTF-8"?>
<project name="build" default="build" basedir=".">
<!-- 生成するjarファイル名(拡張子を含まない) -->
<property name="jar.name" value=""/>
<!-- 実行するメインクラス名(パッケージ名を含む) -->
<property name="main" value=""/>
<!-- 生成されたjarファイルの配置先 -->
<target name="deploy" depends="jar">
<copy file="${jar.filepath}" todir=""/>
</target>
<!-- jarファイルの配置先でライブラリを置くパス -->
<property name="dst.lib.dir" value="../lib/java/"/>
<!-- ソースファイルを置くディレクトリ -->
<property name="src.dir" value="src"/>
<!-- 外部ライブラリを置くディレクトリ -->
<property name="lib.dir" value="lib"/>
<!-- クラスファイルを置くディレクトリ -->
<property name="classes.dir" value="bin"/>
<!-- javadocを出力するディレクトリ -->
<property name="javadoc.dir" value="docs"/>
<!-- 生成されたjarファイルを置くパス -->
<property name="jar.filepath" value="${lib.dir}/${jar.name}.jar"/>
<!-- デフォルトターゲット -->
<target name="build">
<antcall target="javadoc"/>
<antcall target="deploy"/>
<delete file="${jar.filepath}"/>
</target>
<!-- jarファイルの生成 -->
<target name="jar" depends="compile">
<!-- ライブラリパスを生成 -->
<path id="libraries">
<fileset dir="${lib.dir}" includes="*.jar"/>
</path>
<pathconvert property="classpath" refid="libraries" targetos="unix" pathsep=" ">
<map from="${basedir}\${lib.dir}\" to="${dst.lib.dir}"/>
<map from="\" to="/"/>
</pathconvert>
<jar jarfile="${jar.filepath}" basedir="${classes.dir}">
<manifest>
<attribute name="Main-Class" value="${main}"/>
<attribute name="Class-Path" value="${classpath}"/>
</manifest>
</jar>
</target>
<!-- コンパイル -->
<target name="compile">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}"/>
</target>
<!-- Javadocの生成-->
<target name="javadoc">
<javadoc sourcepath="${src.dir}" extdirs="${lib.dir}" destdir="${javadoc.dir}"
encoding="UTF-8" additionalparam="-J-Dfile.encoding=UTF-8"/>
</target>
</project>
Assuming Ant 1.7 or above, you can use the manifestclasspath task.
<path id="dep.runtime">
<fileset dir="./lib">
<include name="**/*.jar" />
</fileset>
</path>
<property name="dep_cp" value="${toString:dep.runtime}" />
<target name="default">
<manifestclasspath property="manifest_cp" jarfile="myjar.jar">
<classpath refid="dep.runtime" />
</manifestclasspath>
<echo message="Build Classpath: ${dep_cp}" />
<echo message="Manifest Classpath: ${manifest_cp}" />
</target>
日本通信のイオンSIM、LTE端末に対応+microSIMタイプも提供
月額980円/最大100Kbps、月額2980円/最大400Kbps、月額4980円/最大数Mbpsという3つのプランに変更はなく、対応機種を拡大させることが今回の狙い。SIMカード自体は"LTE対応"だが、下り最大75MbpsのLTE通信が可能になるわけではなく、通信速度(ベストエフォート)は従来のイオンSIMと変わらない。
新タイプのイオンSIMは、8月1日に本州と四国、8月3日に北海道、九州、沖縄にて申し込みを受け付ける。既存ユーザーについては、8月中旬からMy b-mobile Page(https://mypage.bmobile.ne.jp/)で申し込めば、LTE対応のSIMに交換したり、microSIMに交換したりできる。交換にかかる手数料は2100円。イオン店頭での受け付けは現在検討中とのこと。既存のイオンSIMは新しいイオンSIMと入れ替わる形となり、今後は提供されなくなる。
2012年7月30日月曜日
A Look At The Java Class Loader
Class Loaders
In a Java Virtual Machine (JVM), each and every class is loaded by some instance of a java.lang.ClassLoader. The ClassLoader class is located in the java.lang package and you can extend it to add your own functionality to class loading.
When a new JVM is started by java HelloWorld, the bootstrap class loader is responsible for loading key java classes like java.lang.Object and other runtime code into memory. The runtime classes are packaged inside jre/lib/rt.jar file. We cannot find the details of the bootstrap class loader in the java language specification, since this is a native implementation. For this reason the behavior of the bootstrap class loader will differ across JVMs.
Maze Behind Class Loaders
All class loaders are of the type java.lang.ClassLoader. Other than the bootstrap class loader all class loaders have a parent class loader. These two statements are different and are important for the correct working of any class loaders written by a developer. The most important aspect is to correctly set the parent class loader. The parent class loader for any class loader is the class loader instance that loaded that class loader.
We have two ways to set the parent class loader:
public class CustomClassLoader extends ClassLoader{
public CustomClassLoader(){
super(CustomClassLoader.class.getClassLoader());
}
}
Or
public class CustomClassLoader extends ClassLoader{
public CustomClassLoader(){
super(getClass().getClassLoader());
}
}
The first constructor is the preferred one, because calling the method getClass() from within a constructor should be discouraged, since the object initialization will be complete only at the exit of the constructor code. Thus if the parent class loader is correctly set, whenever a class is requested out of a ClassLoader instance using loadClass(String name) method, if it cannot find the class, it should ask the parent first. If the parent cannot find the class, the findClass(String name) method is invoked. The default implementation of findClass(String name) will throw ClassNotFoundException and developers are expected to implement this method when they subclass java.lang.ClassLoader to make custom class loaders.
Inside the findClass(String name) method, the class loader needs to fetch the byte codes from some arbitrary source. The source may be a file system, a network URL, another application that can spit out byte codes on the fly, or any similar source that is capable of generating byte code compliant with the Java byte code specification. Once the byte code is retrieved, the method should call the defineClass() method, and the runtime is very particular about which instance of the ClassLoader is calling the method. Thus if two ClassLoader instances define byte codes from the same or different sources, the defined classes are different.
For example, lets say Ive a main class called MyProgram. MyProgram is loaded by the application class loader, and it created instances of two class loaders CustomClassLoader1 and CustomClassLoader2 which are capable of finding the byte codes of another class Student from some source. This means the class definition of the Student class is not in the application class path or extension class path. In such a scenario the MyProgram class asks the custom class loaders to load the Student class, Student will be loaded and Student.class will be defined independently by both CustomClassLoader1 and CustomClassLoader2. This has some serious implications in java. In case some static initialization code is put in the Student class, and if we want this code to be executed one and only once in a JVM, the code will be executed twice in the JVM with our setup, once each when the class is separately loaded by both CustomClassLoaders. If we have two instances of Student class loaded by these CustomClassLoaders say student1 and student2, then student1 and student2 are not type-compatible. In other words,
Student student3 = (Student) student2;
will throw ClassCastException, because the JVM sees these two as separate, distinct class types, since they are defined by different ClassLoader instances.
The Need For Your Own Class loader
For those who wish to control the JVMs class loading behavior, the developers need to write their own class loader. Let us say that we are running an application and we are making use of a class called Student. Assuming that the Student class is updated with a better version on the fly, i.e. when the application is running, and we need to make a call to the updated class. If you are wondering that the bootstrap class loader that has loaded the application will do this for you, then you are wrong. Javas class loading behavior is such that, once it has loaded the classes, it will not reload the new class. How to overcome this issue has been the question on every developers mind. The answer is simple. Write your own class loader and then use your class loader to load the classes. When a class has been modified on the run time, then you need to create a new instance of your class loader to load the class. Remember, that once you have created a new instance of your class loader, then you should make sure that you no longer make reference to the old class loader, because when two instances of the same object is loaded by different class loaders then they are treated as incompatible types.
Writing Your Own Class loader
The solution to control class loading is to implement custom class loaders. Any custom class loader should have java.lang.ClassLoader as its direct or distant super class. Moreover you need to set the parent class loader in the constructor. Then you have to override the findClass() method. Here is an implementation of a custom class loader.
import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; public class CustomClassLoader extends ClassLoader { public CustomClassLoader(){ super(CustomClassLoader.class.getClassLoader()); } public Class loadClass(String className) throws ClassNotFoundException { return findClass(className); } public Class findClass(String className){ byte classByte[]; Class result=null; result = (Class)classes.get(className); if(result != null){ return result; } try{ return findSystemClass(className); }catch(Exception e){ } try{ String classPath = ((String)ClassLoader.getSystemResource(className.replace('.',File.separatorChar)+".class").getFile()).substring(1); classByte = loadClassData(classPath); result = defineClass(className,classByte,0,classByte.length,null); classes.put(className,result); return result; }catch(Exception e){ return null; } } private byte[] loadClassData(String className) throws IOException{ File f ; f = new File(className); int size = (int)f.length(); byte buff[] = new byte[size]; FileInputStream fis = new FileInputStream(f); DataInputStream dis = new DataInputStream(fis); dis.readFully(buff); dis.close(); return buff; } private Hashtable classes = new Hashtable(); }</i> Here is how to use the CustomClassLoader. <i>public class CustomClassLoaderTest { public static void main(String [] args) throws Exception{ CustomClassLoader test = new CustomClassLoader(); test.loadClass(com.test.HelloWorld); } }
Summary
Many J2EE application servers have a hot deployment capability, where we can reload an application with a new version of class definition, without bringing the server VM down. Such application servers make use of custom class loaders. Even if we dont use an application server, we can create and use custom class loaders to fine-control class loading mechanisms in our Java applications. So have fun with custom loaders.
Inside Class Loaders
This series of articles started when I wanted to write a weblog about the impact of class loaders in a J2EE server. But the log entry grew, due the fact that a few basic rules still can provide a complex system, as you see in physics, where a few basic components and forces can build up something like our universe with all of the stars, black holes, pulsars, galaxies, and planets.
In this part, I want to lay the groundwork on which we can start a discussion about dynamic and modular software systems. Class loaders may seem to be a dry topic, but I think it is one of the topics that separate the junior from the senior software engineer, so bear with me for an exciting journey into the darker corners of Java.
Now you may ask yourself, "Why should I deal with multiple class loaders and their limitations and problems?" The short answer is that you probably have to, one way or the other. Even when you write a simple servlet or JSP program and deploy within a servlet container, your code is loaded by your very own class loader, preventing you from accessing other web applications' classes. In addition, many "container-type" applications such as J2EE servers, web containers, NetBeans, and others are using custom class loaders in order to limit the impact of classes provided by a component, and thus will have an impact on the developer of such components.
As we will see later, even with dynamic class loading, there can only be one class loaded in a particular JVM. Additional class loaders enable a developer to partition the JVM so that the reduced visibility of a class makes it possible to have multiple, different definitions of the same class loaded.
The class loaders work like the federal bank of each country, issuing their own currency. The border of each country defines the visibility and usability of the currency and makes it possible to have multiple currencies in the world.
First we need to explain some definitions:
CL: | Class loader. |
Initial CL: | The CL that initiated the loading of the class. |
Effective CL: | The CL that actually loaded the class. |
Class type: | The fully qualified class name (package plus class name). |
Class: | A combination of the class type and effective class loader. |
java.lang.Class : | A class in the JDK that represents a class (name, fields, methods, etc.). |
Symbolic Link: | A class type used within the source code, such as superclasses, extended interfaces, variables, parameters, return values, instanceofs, and upcasts. |
Class loaders and their usage follow a few simple rules:
- Class loaders are hierarchically organized, where each one has a parent class loader, except the bootstrap class loader (the root).
- Class loaders should (practically: must) delegate the loading of a class to the parent, but a custom class loader can define for itself when it should do so.
- A class is defined by its class type and the effective class loader.
- A class is only loaded once and then cached in the class loader to ensure that the byte code cannot change.
- Any symbolic links are loaded by the effective class loader (or one of its ancestors), if this is not already done. The JVM can defer this resolution until the class is actually used.
- An upcast of an instance to another class fails when the class of the instance and the class of the symbolic link do not match (meaning their class loaders do not match).
Now I want to put on some meat to these bare-bone rules to provide better understanding.
Class Loader Organization and Delegation
Before we start, let's look at a typical class loader hierarchy, as illustrated by Figure 1:
Figure 1. Class loader hierarchy example
As shown in Figure 1, the bootstrap class loader (BS) loads the classes from the JVM, as well as extensions to the JDK. The system class loader (CP) loads all of the classes provided by the CLASSPATH
environment variable or passed using the -classpath
argument to the java
command. Finally we have several additional class loaders, where A1-3 are children of the CP, and B1-2 are children of A3. Every class loader (except BS) has a parent class loader, even if no parent is provided explicitly; in the latter case, the CP is automatically set as the parent.
That alone does not mean much but has a big impact on class-loading delegation. The Javadoc of java.lang.ClassLoader
specifies that any class loader must first delegate the loading of a class to the parent, and only if this fails does it try to load the class itself. Actually, the class loader does not care about which one gets the bytes of the class, but rather which one calls defineClass()
. In this final method, an instance of classjava.lang.Class
is created and cached in the class loader so that the byte code of a class cannot change on a following request to load the class. This method also checks that the given class name matches the class name in the byte code. Because this method is final
, no custom class loader can change this behavior.
As previously mentioned, a class loader must delegate the loading of a class (although a developer can override loadClass()
and change this behavior). On one hand, if loading of system classes is not delegated, an application could provide malicious code for JDK classes and introduce a ton of problems. On the other hand, all classes at least extendjava.lang.Object
, and this class must be resolved, too. Thus the custom class loader has to load this class by itself, otherwise the load fails with a linkage error. These two facts imply that a custom class loader has to delegate class loading. In JDK 1.4, two of the three versions of defineClass()
throw a SecurityException
if the given class name starts with "java", while the third version is deprecated due to these security concerns.
I want to stress the fact here that there is a difference between the class loader that starts the process of loading the class and the one that actually loads (defines) the class. Assuming that in our example no class loader delegates the loading of a class to one of its children, any class is either loaded by the Initial CL or by one of its ancestors. Let us assume that a class A contains a symbolic link to class B that in turn contains a symbolic link to class C. The class loader of C can never be a child of the class loader of B or of A. Of course, one should never say "never," and yes, it is possible to break this rule, but like multiple inheritance in C++, this is "black belt" programming.
A more prominent exception of the JDK delegation model of "delegating first" is the class loader for a web container described in the servlet specification. This one tries to load a class first by itself before it delegates to the parent. Nevertheless, some classes, such as java.*
, javax.*
, org.xml.sax.*
and others, are delegated first to the parent. For more information, please check out the Tomcat 5.0 documentation.
Class Linking
After a class is defined with defineClass()
, it must be linked in order to be usable by the final resolveClass()
method. Between this method call and the first usage of a symbolic link, the class type is loaded by the class loader of the containing class as Initial CL. If any linked class (type) cannot be loaded, the method will throw a linkage error (java.lang.NoClassDefFoundError
). Keep in mind that the resolution of symbolic links is up to the JVM and can be done anywhere between the loading of the containing class (eager resolution or C-style) and the first actual usage of the symbolic link (lazy resolution). It can happen that a symbolic link is in a class and if it is never used, the linked class will never be loaded such as in this example with JDK 1.4.2 on Windows 2000:
public class M { // In JDK 1.4.2 on W2K this class can be used // fine even if class O is not available. public O mMyInstanceOfO; }
whereas this class will fail with a linkage error if the class O
cannot be loaded:
public class M { // In JDK 1.4.2 and W2K the creation of an // instance of M will FAIL with // a NoClassDefFoundError if class O is not // available public O mMyInstanceOfO = new O(); }
and to make matters a little bit more complicated, it only fails when an instance is created:
// Fine because in JDK 1.4.2 on W2K class // linking is done lazy Class lClassM = Class.forName("M"); // Fails with NoClassDefFoundError Object lObject = lClassM.newInstance();
For more information, please read Chapter 12: "Execution" in the Java Language Specification.
Class Definition
To a beginner, a class is identified solely by the class type. As soon as you start to deal with class loaders, this is no longer the case. Provided that class type M is not available to CP, A1 and A2 could load the same class type M with different byte code. Even when the byte code would be the same from a Java point of view, these classes are different, no matter if the byte code is the same or not. To avoid ambiguities, a class is identified by its class type as well as the Effective CL, and I will use the notation <Class Name>-<Class Loader>
. So for this case, we have classes M-A1
and M-A2
. Imagine we also have another class, Test-A1
, with a method upcastM()
that looks like this:
public void upcastM(Object pInstance) throws Exception { M lM = (M) pInstance; }
Because the class Test
is loaded by A1
, its symbolic link M
is also loaded by A1
. So we are going to upcast a given object to M-A1
. When this method is called with an instance of the class M-A1
as an argument, it will return successfully, but if it is called with an instance of M-A2
, it will throw a ClassCastException
because it is not the same class, according to the JVM. Even with reflection this rule is enforced, because both java.lang.Class.newInstance()
andjava.lang.reflect.Constructor.newInstance()
return an instance of class java.lang.Object-BS
. Unless only reflection is used during the lifetime of this object, the instance has to be upcast at some point. In the case of only using reflection to avoid conflicts, any arguments of a method still be subject to an upcast to the class of the method signature and therefore the classes must match, otherwise you get a java.lang.IllegalArgumentException
due to the ClassCastException
.
Test
The sample code may help the reader to better understand the concepts described above and, later, to do their own investigations. In order to run the sample code, just extract it in the directory of your choice and execute the ant build script in the classloader.part1.basics directory.
It has three directories: main, version_a, and version_b. The main directory contains the startup class Main.java
as well as the custom class loader that will load classes from a given directory. The other two directories both contain one version of M.javaand Test.java. The class Main
will first create two custom class loaders each loading classes, after delegating to the parent class loader, from either the version_a or version_b directories. Then it will load the class M
by each of these two class loaders and create an instance through reflection:
// Create two class loaders: one for each dir. ClassLoader lClassLoader_A = new MyClassLoader( "./build/classes/version_a" ); ClassLoader lClassLoader_B = new MyClassLoader( "./build/classes/version_b" ); // Load Class M from first CL and // create instance Object lInstance_M_A = createInstance( lClassLoader_A, "M" ); // Load Class M from second CL and // create instance Object lInstance_M_B = createInstance( lClassLoader_B, "M" );
In order to test an upcast, I need a class where the Effective CL is one of the custom class loaders. I then use reflection in order to invoke a method on them because I cannot upcast them because Main
is loaded by the CP:
// Check the upcast of a instance of M-A1 // to class M-A1. This test must succeed // because the CLs match. try { checkUpcast( lClassLoader_A, lInstance_M_A ); System.err.println( "OK: Upcast of instance of M-A1" + " succeeded to a class of M-A1" ); } catch (ClassCastException cce) { System.err.println( "ERROR: Upcast of instance of M-A1" + " failed to a class of M-A1" ); } // Check the upcast of a instance of M-A2 to // class M-A1. This test must fail because // the CLs does not match. try { checkUpcast( lClassLoader_A, lInstance_M_B ); System.err.println( "ERROR: upcast of instance of M-A2" + " succeeded to a class of M-A1" ); } catch (ClassCastException cce) { System.err.println( "OK: upcast of instance of M-A2 failed" + " to a class of M-A1" ); }
The checkUpcast()
loads the class Test
through reflection and calls the Test.checkUpcast()
method, which makes a simple upcast:
private static void checkUpcast( ClassLoader pTestCL, Object pInstance ) throws Exception { try { Object lTestInstance = createInstance( pTestCL, "Test" ); Method lCheckUpcastMethod = lTestInstance.getClass().getMethod( "checkUpcast", new Class[] { Object.class } ); lCheckUpcastMethod.invoke( lTestInstance, new Object[] { pInstance } ); } catch( InvocationTargetException ite ) { throw (ClassCastException) ite.getCause(); } }
Afterwards, there are some tests that do the same thing, but check the upcast restriction against reflection to ensure that reflection cannot compromise the rules posted at the beginning of the article. The last test checks the linking of symbolic links. On Windows 2000 and JDK 1.4.2, it will also show the lazy loading of classes because the loading of the class succeeds, whereas the creation of the instance eventually fails:
// Load a class N that has a symbolic link to // class O that was removed so that the class // resolving must fail try { // Upcast ClassLoader to our version in // order to access the normally protected // loadClass() method with the resolve // flag. Even the resolve flag is set to // true the missing symbolic link is only // detected in W2K and JDK 1.4.2 when the // instance is created. Class lClassN = ( (MyClassLoader) lClassLoader_A).loadClass( "N", true ); // Finally when the instance is created // any used symbolic link must be resolved // and the creation must fail lClassN.newInstance(); System.err.println( "ERROR: Linkage error not thrown even" + "class O is not available for" + " class N" ); } catch( NoClassDefFoundError ncdfe ) { System.err.println( "OK: Linkage error because class O" + " could not be found for class N" ); }
Please note that in the directory version_a there is a class named O.java
, because in order to compile the class N.java
, this class is needed. However, the ant
build script will remove the compiled class O.class
before the test is started.
Conclusion
As long as a Java developer does not deal with his or her own class loader, all of the classes are loaded by the bootstrap and system class loader, and there will never be a conflict. Thus, it seems that a class is defined only by the fully qualified class name. As soon as there are sibling class loaders -- neither a parent of the other -- a class type can be loaded multiple times with or without different byte code. The class loader also defines the visibility of a class type because any upcast checks against the class name as well as its class loaders.
To use the currency analogy, this is expressed by the fact that you can have several currencies in your wallet, but as soon as you want to use one, the cashier will check if your money is of the local currency. Still, you can carry these currencies in your pocket wherever you go, and likewise, you can carry around instances of classes even when they are unknown or not compatible in a particular class, as long as the class of the reference is compatible there. Luckily in Java, java.lang.Object
is the superclass of all instances and is loaded by the BS, which is the parent of all class loaders no matter what. This means a reference of a class java.lang.Object
is always compatible. I think of this as a "tunneling through" of classes from one compatible island to the next -- something that is very important in J2EE, as will be shown in a future installment.
My analogy with the currencies is very simplified, because it implies that all classes have the same visibility due to the single border of a country. The analogy is based on the two-dimensional world map, whereas with Java class loaders, each level within the hierarchy of the class loaders is adding a new layer and building up a three-dimensional space.
Additional class loaders enable a Java developer to write modular applications where the visibility of classes is restricted, and therefore, multiple class types can be loaded and managed. Nevertheless, it requires effort to understand the used class loaders and the organization of the classes and class loaders. As with threads, class loading is a runtime behavior that is not obviously visible to the developer, and requires experience and testing to understand and utilize.
Now that the groundwork is laid, we can finally delve into the usage of class loaders. In the next article, we will see how class loaders can be used in a J2EE application server to manage deployments and what the effects are on invocations through local or remote interfaces. Afterwards, we will see how advanced class loaders make it possible to drop class types or massage the code in order to add "Advices" (AOP) at runtime without changing or recompiling your code.