まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:jdk

Java のアプリ開発をしていて、こんな実行時エラーに遭遇することがあります:
java.lang.UnsatisfiedLinkError: C:\IBM\Notes\nlsxbe.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform


最近は Windows といえば 64bit 版が普通になってきました(よね?)。自分の開発環境も 64bit 版 Windows で、Java 開発環境も(JDK も JRE も Eclipse も)64bit 版を使っています。ここまでは珍しくないと思っています。

しかし開発するアプリケーションによっては外部ライブラリ(JAR ファイル)を指定する必要があり、その JAR ファイルが 32bit のネイティブライブラリを使っていたりするなど、64bit の JDK で 32bit 前提のコードを実行しなければならなくなった場合に上記のようなエラーが発生するのでした。

典型例としては、IBM Notes の Java API を使ったアプリケーション開発が挙げられます。2017/Oct/01 現在、Windows 版の IBM Notes は 32bit 版しか存在していません。Java で IBM Notes の API を実行するには IBM Notes 同梱の Notes.jar 他をインポートして使う必要がありますが、この Notes.jar は 32bit の DLL を参照するため、64bit Java から実行すると上記のエラーが発生します。


エラーの原因が分かったところで、どのように回避すればいいでしょうか? これも解決策としてはシンプルで 32bit Java(JRE) から実行すればよいことになります。32bit JRE をダウンロードして実行してもいいですが、上記のような IBM Notes の JAR を使った場合であれば、IBM Notes が使っている Java をそのまま指定して実行すればよいことになります。

ちとややこしいのが Eclipse のような IDE を使っている場合の指定方法です。まず Java - Installed JREs の中に Standard VM として Notes Java を追加しておく必要があります。Notes Java は(ノーツをインストールしたディレクトリ)/jvm にあるので、このディレクトリを指定して追加します:
2017100101


そして Eclipse の Java プロジェクトの中で JRE System Library として、上記で設定した Notes Java を指定します。これでこのプロジェクト内で作成したアプリを実行する際には Notes Java 内の java.exe が利用されるようになるので、上記のエラーが回避できるようになります:
2017100102



 

IBM Watson の API を使っていたら、ある日を境にこんなエラーが出るようになりました:
javax.net.ssl.SSLException: java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 64

2016110901


エラーが出ているのは URL を指定して REST API を実行する瞬間の箇所です。GET でも POST でも発生しました。

原因を調べるために Watson API Explorer を使って、同じ API を実行してみたところ・・・今度はエラーなく実行できてしまいました:
2016110902


なんだこれは!? というわけで、エラーメッセージをググって調べてみたところ、こんな StackOverflow にこんなスレッドを見つけました:
Receiving “javax.net.ssl.SSLException: java.lang.ArrayIndexOutOfBoundsException” while connecting to “https:” site


ここに書かれている内容が原因であるとすると、暗号化アルゴリズムである TLS のバージョンの変更が原因らしいとのこと。またいくつかの対処方法があるが、クライアント側だけで対応するには JDK のバージョンを 1.8 にしろ、とのことらしいです。

確かに今回エラーが出るようになったシステムでは JDK 1.6 が使われているので、このエラーが発生する条件は満たしています。また Watson API Explorer 側では JDK 1.8 か、Java 以外のアプリケーションサーバーが使われていると仮定すればエラーなしに成功することになります。なので、今回のような結果になっても説明は付くことになります。
2016110903



原因が分かったところで、問題は対処方法です。今回はとあるシステム内からこの API を実行したかったという背景があります。そのシステムに組み込まれている JDK のバージョンが 1.6 である以上、ここを勝手に変更するわけにはいきません。 一方で、クライアントが JDK 1.8 未満である以上はこのエラーを回避するにはサーバー側を変更する必要があります。API 提供されているサーバー設定を変更してもらえる保証もありません。そもそもセキュリティ強化のためのサーバー設定変更が背景にあるわけですが、仮にセキュリティに一旦目をつぶるとして、JDK 1.6 のままでどうにかする方法はないでしょうか?


その方法の1つとして考えられるのが、下図のように JDK 1.8(または他のプラットフォームでもよい)など Watson API を実行できる環境でプロクシー的なものを作って、JDK 1.8 未満のシステムからはこのプロクシーを経由してリクエストを実行し、レスポンスを得る、という仕組みです:
2016110904

こうすると JDK 1.8 未満のシステムからはプロクシーまでアクセスできればよく、またプロクシー側の設定等にも自分である程度手を加えることも可能になります。そして Watson からは JDK 1.8 からのリクエストとして API を実行して結果を返す、という仕組みを作ればうまくいくのではないか、と考えました。まあ2つの仕組みを作ってメンテナンスしなければならない、という点はありますが、技術的にはクリアできる目処が立ちそうです。

近いうちに実際に作ってみた仕組みをまた紹介する予定です。


僕は普段 Java の IDE (統合開発環境)として Eclipse を使ってますが、久しぶりに IDE なしでサーブレットを作る機会がありました。結構ハマったこともあって、普段いかに Eclipse に頼っているのかを痛感しました・・・


今回やりたかったことは、ごくごくシンプルな話で、
 (1) サーブレットの Java ソースファイルを書いて、
 (2) JDK でビルドしてサーブレットの class ファイルを作る
という2つです。要はサーブレットの実行環境が特殊で war ファイルとかをデプロイできないため、 class ファイルをそのまま配置する必要がある、という(IBM Domino とかの)ケースを想定しています。

なお環境は Windows7 で、JDK 1.8 はダウンロード&インストール済みとします。



まず (1) のソースファイルは以下のような感じにしました。パッケージも指定していません。これを UTF-8 で記述して、HelloWorld.java というファイル名で保存します:
//. HelloWorld.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet{
  @Override
  protected void doGet( HttpServletRequest req, HttpServletResponse res ){
    try{
      res.setContentType( "text/plain; charset=UTF-8" );
      res.getWriter().println( "ハローワールド!" );
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}

↑特別なことは何もしてない、ごくごくシンプルな「ハローワールド!」です。


さて、これを (2) JDK でビルドして class ファイルに・・・という段階で何度かつまづきました。

まずは普通にそのままコンパイルしてみると、
C:\tmp>javac HelloWorld.java

HelloWorld.java:11: エラー: この文字は、エンコーディングMS932にマップできません
      res.getWriter().println( "繝上Ο繝シ繝ッ繝シ繝ォ繝会シ?" );

「ハローワールド!」という日本語文字列のエンコードでエラーが出てしまいました。ちゃんとソースコードは UTF-8 にしたつもりだったのに・・・

実は Windows 版の JDK の javac はソースコードが Shift-JIS で書かれているものとして動作するらしいのでした(なんちゅうクソ仕様・・・)。便利とは思えない仕様ですが、このエラーを回避するにはコマンドラインパラメータでソースコードのエンコードを指定必要があるのでした。

というわけで今度は UTF-8 を指定してコンパイル。すると別のエラーが大量に・・・
C:\tmp>javac -encoding utf-8 HelloWorld.java

HelloWorld.java:3: エラー: パッケージjavax.servletは存在しません
import javax.servlet.*;
^
HelloWorld.java:4: エラー: パッケージjavax.servlet.httpは存在しません
import javax.servlet.http.*;
^
HelloWorld.java:6: エラー: シンボルを見つけられません
public class DominoInfo extends HttpServlet{
                                ^
  シンボル: クラス HttpServlet
HelloWorld.java:8: エラー: シンボルを見つけられません
  protected void doGet( HttpServletRequest req, HttpServletResponse res ){
                        ^
  シンボル:   クラス HttpServletRequest
  :
  :

これら全てサーブレットの JAR ライブラリを(指定していないので)見つけられないことが原因のエラーです。これは普段の Eclipse 環境でも指定していることでしたが、すっかり忘れていました。

Tomcat あたりをダウンロード&インストールして、サーブレット JAR ファイル(servlet-api.jar とかいう名前だと思います)を見つけて取り出します(以下の例では c:\arc\servlet-api-3.1.jar というパスで保存しているものとして記述しています)。で、改めてこのファイルを classpath に指定してコンパイル:
C:\tmp>javac -classpath c:\arc\servlet-api-3.1.jar -encoding utf-8 HelloWorld.java
C:\tmp>

無事コンパイルできました。HelloWorld.class が出来ているはずです:
2016102201


・・・こ、こんなに難しかったっけ? (^^;


この応用で、作ったサーブレットを実際に IBM Domino 上で動かすための設定についてはこちら


今まで Tomcat を使う場合は使い慣れた(理由はそれだけじゃないけど) Tomcat6 を使っていました。試しに Tomcat7 を導入する機会があったので、その導入手順を紹介します。環境はいつもの CentOS 6 です。


まずは Java/JDK 環境が必要です。今回は OpenJava JDK 1.7 を導入することにしました:
# yum install java-1.7.0-openjdk-devel

さて Tomcat7 です。実は CentOS 6 の標準リポジトリには Tomcat7 は含まれていません。標準で含まれているのは Tomcat6 ですが、yum でインストールする場合は
# yum install tomcat6

のように、わざわざ「6」を指定して導入する必要があります。この辺りの導入手順の詳細はこちらを参照してください:
CentOS に Apache Tomcat を導入する

さて Tomcat7 です。Tomcat6 は「6」を指定する必要がありました。Tomcat7 も「7」を指定する必要があるかというと、これがありません(笑)。コマンドとしては
# yum install tomcat

で導入されるのですが、普通にそのまま実行しても「見つからない」というエラーになるだけだと思います。Tomcat7 を yum でインストールするには事前に EPEL リポジトリの登録が必要です。つまり、まずはこのコマンドを実行します:
# rpm -Uvh http://ftp.riken.jp/Linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm

その後に yum で install です。tomcat-admin-webapps パッケージを指定して、管理コンソール画面ごとインストールします:
# yum install tomcat tomcat-admin-webapps

管理コンソール画面にアクセスするための設定を追加します。/etc/tomcat/tomcat-users.xml をテキストエディタで編集し、以下の3行を追加します:
<role rolename="admin-gui"/>
<role rolename="manager-gui"/>
<user username="admin" password="P@ssw0rd" roles="admin-gui,manager-gui"/>
   ↑この例だとユーザー名: admin、パスワード: P@ssw0rd でアクセスするユーザーを作成


インストール後は "tomcat" というサービス名で起動したり、再起動したり、自動実行指定ができます:
# /etc/init.d/tomcat start
# chkconfig tomcat on

管理コンソール画面ごとインストールした場合は、http://(IPアドレス:8080)/manager/html で管理画面にアクセスできます。Basic 認証でユーザー名とパスワード(上記設定の場合であれば admin と P@ssw0rd)を指定してログインできます:
2015122801


CentOS6 で Tomcat7 を使う場合は EPEL リポジトリの登録、が肝です。


(2015/07/14 追記)
このエントリではビルドパックを使って Java8 ランタイムを動かす方法を紹介していますが、現在は標準の Liberty Java ランタイムが Java8 対応しています。詳しい手順はこちらを参照ください:
Bluemix で Java8 を使う
(2015/07/14 追記終わり)



新しい環境で、新たに IBM Bluemix 向けのアプリ開発環境を作っていてハマった落とし穴の解説です。同じ穴に引っかかる人を未然に防げれば何より。


新しい PC で新たに Java + Eclipse 環境を普通に構築して、で Java ウェブアプリケーションを1つ作りました。それを IBM Bluemix にデプロイ:
2015030901


問題なく成功:
2015030902


で動作確認・・・ したらエラー!
2015030901


一応、事前にローカルホストで動作確認できていたので、コーディング上のミスは考えにくい。なんだこれは?環境依存??だとしてもどんな環境???

よくわからないので画面のエラーメッセージを読んでみる。サーブレットクラスが corrupted で UnavailableException になってる。"Check that the class file was not renamed after it was compiled" って言われても、war ファイルをデプロイしただけだし・・・

・・・ん?コンパイル??

そういえば・・・、とデプロイ時の画面の標準出力内容を見直し:
2015030903


IBM Bluemix の Liberty Java では IBM Java 1.7.1 が使われていることがわかります。そして自分の開発環境を再確認すると:
2015030902

これだ!
新しい環境には JDK 1.8 しかインストールしてなかったので、JavaSE 1.8 でコンパイルしてた!

IBM Bluemix で Liberty Java を使う場合、少なくとも現時点では JDK 1.7 を導入して使うべきですね。JDK 1.8 をインストールすること自体はいいんですが、Bluemix 用にコンパイルする場合は 1.7 互換(以下)でのビルドが必要になります。

特に新たに開発環境をセットアップするようなケースでは(深く考えずに最新版だけをインストールしちゃうと NG なので)要注意です。


(おまけ)
IBM Bluemix で「どうしても JDK 1.8 / Java 8 を使いたい!」という場合は、CloudFoundry 用の buildpack を使って、以下のように指定すると OpenJDK 1.8 (と Apache Tomcat)が含まれた Java 実行環境をランタイムにすることができます:
# cf push XXX(アプリ名)XXX -p ****.war -b https://github.com/cloudfoundry/java-buildpack.git
(おまけ終わり)


いずれかの方法で、ランタイムとアプリケーションのコンパイルバージョンを合わせることができれば、デプロイしたアプリケーションを IBM Bluemix 上で正しく動かすことができるようになります:
2015030903

↑サンプルはここで紹介したアプリを IBM Bluemix 上で動作確認したものです。
Tomcat でリライト









このページのトップヘ