Javaのライブラリを作成してからMavenセントラルリポジトリに登録するまで
Cacoo4JというJavaのライブラリを作成したので、オープンソースとして公開してMavenセントラルリポジトリに登録するまで一通りの作業をやってみました。
そこで備忘録的にJavaのライブラリを開発してからMavenセントラルリポジトリに登録するまでに私がやった作業の流れを残しておきたいと思います。
作業の流れ
ライブラリの作成
これをやらないと話が始まりません。私はCacooというサービスのAPIを簡単に呼び出せるJavaのライブラリを作成しました。たしかJava Day Tokyo 2013当たりのツイートでCacoo4J無いよねってのを見たのと、昔CacooのOpenSocialガジェットを作った事があってCacooのAPIの知識があったので簡単に作れるんじゃないかなと思ったのがきっかけです。
作ったライブラリは最終的にMavenのセントラルリポジトリで公開したいのでNetBeansのMavenプロジェクトとして作成しました。
ライセンスやコメントの記載
自分で使う分にどんなソースでも良いのですが、オープンソースとしてライブラリを公開するにはライセンスやコメントの整理を行わないといけません。私はオープンソースで公開する時はだいたいMITライセンスを選択するのですが、今回はTwitter4Jに合わせてApache License 2.0.にする事にしました。プロジェクトフォルダにLICENSE.txtを配置してソース全部のヘッダにライセンスの表記を追記しました。
あと本当はJavaDocのコメントとか丁寧に書くんですが、日本語で書くのもあれだなとか英語苦手だなと思っていたら結局何も書いてません。すみません。使い方そんなに難しくないので勘弁して下さい。
ドメインの購入
ドメインの購入は必須では無いですがライブラリ用のサイトを作成したかったのと、MavenのgroupIdにドメイン名を使いたかったので、ドメイン「cacoo4j.org」を購入する事にしました。お名前.comでドメイン購入。1年間で980円なのでまあ安い買い物です。
GitHub Pagesでサイト作成
GitHubにはGitHub Pagesというものがあって開発者のサイトやリポジトリのサイトのホスティングもやってくれるんですね。素敵すぎます。GitHub PagesのAutomatic Page GeneratorでREADME.mdをまるっとサイト化して購入したcacoo4j.orgドメインを設定しました。
Mavenセントラルリポジトリに登録
ソースも公開してサイトも作成したので最後にMavenセントラルリポジトリへの登録です。Mavenセントラルリポジトリへの登録はいろいろと手順やルールがあってややめんどくさかったですが大まかには以下のような流れで作業しました。
・Sonatypeのアカウントを作成する
・jarファイル署名用にpgpで鍵を作成(pgpはMacPortsでインストールしました)
・SonatypeにIssueを作成する(このチケットでリポジトリ作成のやりとりをする)
・Sonatypeにモジュールのデプロイ
・Issueに完了報告
・問題無ければMavenリポジトリに反映される
以下のサイトを参考に試行錯誤しました。
http://samuraism.jp/diary/2012/05/03/1336047480000.html
https://sites.google.com/site/yasuda/maven/appendex-mavensentoraruripojitorini-gong-kaisuru
一番問題になるところはpom.xmlにいろいろと情報や設定を書く必要があるのでその点が苦労するのかなと思います。
私はJavaDocやソースのjarもリリースする必要がある事に気づかずにそのままモジュールだけリリースしそうになりました。maven-source-pluginやmaven-javadoc-pluginを追加してJavaDocとソースもリリースすする必要があります。
参考までにcacoo4jのpom.xmlは
https://github.com/den2sn/cacoo4j/blob/master/pom.xml
Issueのチケットは
https://issues.sonatype.org/browse/OSSRH-6861
です。
ということで
一度は一通り流れとしてやっておきたかったのでちょうど良い機会になりました。
Cacoo4jをMavenセントラルリポジトリに登録しました
Cacoo4jをMavenセントラルリポジトリに登録しました。
これでMavenを利用しているアプリケーションから簡単にCacoo4jが利用出来るようになりました。
pom.xmlに以下を追加して下さい。
pom.xml
<dependency> <groupId>org.cacoo4j</groupId> <artifactId>cacoo4j</artifactId> <version>1.0.0</version> </dependency>
JavaからCacooのAPIが簡単に呼び出せるのでCacooのサービスを利用していて興味がある方は使ってみて下さい。
Cacoo4j
http://cacoo4j.org/
Cacoo
https://cacoo.com/
ここがイイヨ!! Java EE 講座でインタビューして頂きました
会社員になってからほとんどJavaの開発をやっている私ですが、ここ1,2年は特にJavaの情報発信やセミナーへ参加するように心がけています。
その活動のおかげでOracle様よりお声掛けいただいてJava EE 講座というWebの特集記事でインタビューをして頂きました。
ここがイイヨ!! Java EE 講座
第3回:アイエックス・ナレッジ株式会社の加藤田益嗣氏に聞くJava EE のおススメ!!
http://www.oracle.com/technetwork/jp/articles/java/ja-topics/java-articles-1981229-ja.html
なかなか自分がいままでやってきたことを振り返る機会は無いのでとても良い機会になりました。
仕事ではJavaに限らず他の言語も使用した開発も行っていますが、一番長くやってきただけにJavaには一段と思い入れがあります。
今後もJavaがさらに良いものになるように、わずかながらでも協力できればと思っています。
JSF2.2のステートレスビュー
JSF2.2のビックチケットの1つとしてステートレスビューという機能が導入されています。
これは今まで常識だったJSFはステートフルという概念を覆す機能です。
今までJSFでは初回リクエストから次のリクエストまでの間に
画面のコンポーネントツリーをセッションスコープもしくは
クライアントのhiddenに保持する必要がありました。
しかし今回導入されたステートレスビューでは
サーバに再リクエストが来た際にコンポーネントツリーを再作成するという方法で
コンポーネントツリーを保有する必要がなくなっています。
そのためサーバメモリーをまったく使用せずにJSFを利用することができるようになりました。
ステートレスビューを有効にするには画面全体を
transient="true"属性を付けたf:viewタグで囲むだけです。
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core"> <f:view transient="true"> <h:head> <title>title</title> </h:head> <h:body> <h1>JSF Stateless mode</h1> </h:body> </f:view> </html>
生成されるHTML内のViewState値には「stateless」という固定の文言が出力されるようになります。
<input type="hidden" name="javax.faces.ViewState" value="stateless" />
マネージドビーンをステートフルに設計してしまうと結局
サーバのセッションを使用してしまう事になるので、
完全にサーバをステートフルに保つということは難しいかもしれませんが、
コンポーネントツリーだけでもステートレスになる意味は少なくないかと思います。
今までJSFはステートフルが必須だったために
サーバをステートレスに保ちたい人たちに嫌われる傾向がありましたが
この機能の導入によりそれが解消されていくかもしれません。
パフォーマンスの面からみても向上するような結果が出ているようです。
・Stateless JSF ? high performance, zero per request memory overhead
http://www.industrieit.com/blog/2011/11/stateless-jsf-high-performance-zero-per-request-memory-overhead/
ただし注意点として、ステートを保有するようなコンポーネントが使用できなくなってしまったり
マネージドビーンの@ViewScopedが使用できなくなってしまうという弊害があるようなので
利用には十分注意が必要です。
JSF 2.2ではf:eventのpreRenderViewではなくf:viewActionを使う?
JSF 2.2でf:viewActionというコンポーネントが追加されています。
基本はJSF 2.0で初期画面表示時に処理を動作させる時に使っていた
f:eventのpreRenderViewと同じなのですが微妙に違うところがあります。
まずは単純に呼び出された回数をカウントするバッキングビーンを作ります。
package sample; import javax.faces.view.ViewScoped; import javax.inject.Named; @Named @ViewScoped public class ViewAction { private int count = 0; public int getCount() { return count; } public void init() { count++; } }
そして画面
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core"> <h:head> <title>ViewAction Sample</title> </h:head> <h:body> <f:metadata> <f:viewAction action="#{viewAction.init()}"/> </f:metadata> <div>Count: #{viewAction.count}</div> <h:form> <h:commandButton value="Click" /> </h:form> </h:body> </html>
f:viewActionのactionでバッキングビーンのinitメソッドを呼び出しています。
では実際に動かしてみます。
画面を表示するとinitメソッドが動作してカウントが1と表示されます。
ボタンを押してもカウントは増えません。
f:eventのpreRenderViewでは画面表示の度に処理が実行されていましたが
f:viewActionでは初期表示時に1回しか動作しないようになっています。
再表示時に処理を動作させたい場合はonPostbackにtrueを指定します。
<f:viewAction action="#{viewAction.init()}" onPostback="true"/>
するとクリックを押す度にカウントが増えて行きます。
また、f:viewActionでは処理を実行するフェーズを指定する事ができます。
指定出来るフェーズはフェーズ2から5のAPPLY_REQUEST_VALUES, PROCESS_VALIDATIONS,
UPDATE_MODEL_VALUES, INVOKE_APPLICATIONの4種類です。
たとえばAPPLY_REQUEST_VALUESフェーズで処理を実行させたい場合は
<f:viewAction action="#{viewAction.init()}" phase="APPLY_REQUEST_VALUES"/>
と指定します。
f:eventのpreRenderViewはRenderer Responseフェーズで動作すると思いますが、
f:viewActionでは指定できないのでもしかすると使い分けが必要になってくるかもしれません。
f:viewActionはアクションというだけあってoutcome値を返すことも出来ます。
バッキングビーンで
public String init() { return "actionView"; }
とするとactionViewに遷移します。
f:viewParamと組み合わせることでパラメータによって画面分岐等も行うことができるようになっています。
最後におまけですが実は上記サンプルプログラムは現状のGlassFish4.0では動作させることができません。
実はネームスペースのバグがあって
http://xmlns.jcp.org/jsf/core
とすると動作しないようです。
2015/4/26 追記 GlassFish4.1で修正されました。
http://java.sun.com/jsf/core
を指定するとひとまず動作させる事ができます。
JSF 2.2 で待ちに待ったFileUploadを試す
JSF 2.2 でやっと導入されたFileUploadを試してみました。
まずサーバ側のBackingBeanを作成します。
package sample; import javax.inject.Named; import javax.enterprise.context.RequestScoped; import javax.servlet.http.Part; @Named @RequestScoped public class UploadPage { private Part file; public Part getFile() { return file; } public void setFile(Part file) { this.file = file; } }
JSFすごい。もはや処理すらありません。
fileのゲッターセッターを準備します。
ファイルはServlet3.0のファイルアップロードと同様にjavax.servlet.http.Partで受け取れます。
では送信するFecelets側の処理を書いてみましょう。
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"> <h:head> <title>Upload Test</title> </h:head> <h:body> <h:form enctype="multipart/form-data"> <h:inputFile id="file" value="#{uploadPage.file}" /><br /> <h:commandButton value="Upload" /><br /> <h:outputText id="fileName" value="#{uploadPage.file == null ? '' : uploadPage.file.submittedFileName}" /> </h:form> </h:body> </html>
ファイルアップロードなのでもちろんformにenctype="multipart/form-data"を指定します。
あとはh:inputFileを配置するだけです。
ファイルが存在している場合はServlet3.1で追加されたsubmittedFileNameを呼び出してファイル名を表示しています。
最後にこのままだと日本語のファイル名が文字化けしてしまうのでglassfish-web.xmlにdefault-charsetでUTF-8を指定しておきます。NetBeansで自動生成したものに1行追加。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd"> <glassfish-web-app error-url=""> <class-loader delegate="true"/> <jsp-config> <property name="keepgenerated" value="true"> <description>Keep a copy of the generated servlet class' java code.</description> </property> </jsp-config> <parameter-encoding default-charset="UTF-8" /> </glassfish-web-app>
では実行してみましょうファイルを添付して
Uploadボタンを押すと
無事ファイルがアップロードされてファイル名が表示されました。
では今度は処理をAjaxでのファイルアップロードに修正してみましょう。
<h:form enctype="multipart/form-data"> <h:inputFile id="file" value="#{uploadPage.file}"></h:inputFile><br /> <h:commandButton value="Upload"> <f:ajax execute="@this" render="fileName" /> </h:commandButton><br /> <h:outputText id="fileName" value="#{uploadPage.file == null ? '' : uploadPage.file.submittedFileName}" /> </h:form>
では同じように実行してみましょうファイルを添付して
Uploadボタンを押すと
こちらも無事ファイルがアップロードされてファイル名が表示されました。AjaxによりoutputTextの部分しか更新されていないため入力部分のファイルのパスが消えていません。
実は現状AjaxのファイルアップロードはIEではもう一度実行しようとするとエラーになってしまいます。また、ChromeやFirefox等の別のブラウザでは見えてはいけないiFrameが表示されて処理が途中で終了してしまいます。
Java EE 7のTransactionalアノテーションを試してみる
Java EE 7からの新機能 JTA 1.2で追加されたTransactionalアノテーションを試してみた。
まずmessageというフィールドをもった単純なEntityクラスを作成して
@Entity public class Test implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String message; // GetterとSetterは長いので省略 }
次にデータを追加するクラスを作成するのだけれども今まで作成していたSessionBeanはこんな感じ。
@Stateless public class TestFacade { @PersistenceContext(unitName = "TESTPU") private EntityManager em; public void test() throws Exception { Test t = new Test(); t.setMessage("test"); em.persist(t); } }
でも今回はこれをTransactionalを付加したCDIのクラスに修正する。
@RequestScoped public class TestFacade { @PersistenceContext(unitName = "TESTPU") private EntityManager em; @Transactional public void test() throws Exception { Test t = new Test(); t.setMessage("test"); em.persist(t); } }
TestFacadeをインジェクトしてtestメソッドを実行するともちろんデータが増える
ではデータ登録後にExceptionを発生させるように修正する
@Transactional public void test() throws Exception { Test t = new Test(); t.setMessage("test"); em.persist(t); throw new Exception(); //追加 }
実行するとExceptionがスローされるがチェック例外なのでやっぱりデータは追加される。
ではExceptionではなくてRuntimeExceptionを発生させる。
@Transactional public void test() throws Exception { Test t = new Test(); t.setMessage("test"); em.persist(t); throw new RuntimeException(); //変更 }
実行すると今度は非チェック例外なのでjavax.transaction.TransactionalExceptionがスローされて追加処理はロールバックされる。
ではTransactionalアノテーションのrollbackOnにExceptionクラスを指定したうえでExceptionを発生させてみる
@Transactional(rollbackOn = Exception.class) public void test() throws Exception { Test t = new Test(); t.setMessage("test"); em.persist(t); throw new Exception(); }
実行するとチェック例外であってもデータはロールバックされて追加されない。エラーはもちろんTransactionalExceptionになる。
ではExceptionを継承したClassNotFoundExceptionを発生させてみる
@Transactional(rollbackOn = Exception.class) public void test() throws Exception { Test t = new Test(); t.setMessage("test"); em.persist(t); throw new ClassNotFoundException(); }
実行するとバグなのか仕様なのかわからないけれど処理はロールバックされずにコミットされた。継承クラスじゃ駄目なのかな。
(バグっぽい:https://java.net/jira/browse/GLASSFISH-20533)
2015/4/26 追記:GlassFish4.1ではロールバックするように修正されていました。
基本的にはSessionBeanのトランザクション制御と大きな大差は無い感じ。今後はSessionBeanはあまり使わずにこっちがメインになるかもね。