Struts1ユーザにGrailsをオススメする6の理由 - The search is over -
Struts1のEOLがアナウンスされました。 最後のリリースから長く時間が経過しており、実質開発は終了している状態でしたが、このタイミングでのアナウンスとなりました。
アナウンスの中では、次の乗り換え先として、Struts2・Spring Web MVC・Grails・Stripesといったフレームワークがお薦めされていますが、私はダンチな生産性を提供するGrailsをお勧めします。
私のフレームワーク遍歴は、Struts1、Wicket、Seasar2、Springと色々渡り歩いてきましたが、ここ1年はがっつり業務でGrailsを使用しています。 1年がっつり使ってみた経験から、Struts1ユーザにGrailsをお勧めする理由をいくつか考えてみました。
Grailsは既存Javaフレームワークの延長線上にある
Grailsを支える基盤は、Spring・Hibernateといった、Javaの世界でよく知られ、また広く使われているフレームワークです。 ビューはSpring MVCをベースとしており、モデルはHibernateをベースとしています。 このため、既存のJavaシステムで、これらフレームワークの知識を有している場合は、そのノウハウを思う存分活用できます。
Struts1ユーザからみてGrailsをお勧めする理由の1つは、Grailsのコントローラがリクエスト駆動ベースであることです。 WicketやJSFなどのように、コンポーネントベースではありません。 このため、Struts1でActionクラスを実装してしてきたユーザにとって、GrailsのControllerは非常に馴染みやすいはずです。
さらに、Java EE互換である点も重要です。
Grailsでは、単にgrails war
とコマンドを実行するだけでwarファイルを生成できます。
このwarファイルは、Tomcatなど今までStrutsアプリケーションをデプロイしてきたアプリケーションサーバにデプロイ可能です。
既存のアプリケーションサーバが使用できるため、今までの運用ノウハウも引き続き活用できます。
GroovyはJava開発者のための言語である
Ruby、Pythonといった言語に比べて、Javaコードの冗長さは、よく批判の対象になります。 Groovyの目標は、常に、このJavaの定型的な冗長さを排除し、Java開発者に異次元の生産性を提供することです。
GroovyはJava言語の延長にある、Java開発者のための言語です。 Java開発者のための言語である証拠に、一部の例外を除き、Javaのコードは、Groovyのコードとして実行可能です。 これは、Groovyのシンタックスは、Javaのシンタックスと互換があるということです。 そのため、Javaプログラマであれば、今すぐにでもGroovyのコードが書き始められます! 世界中の多くのJavaエンジニアは、潜在的なGroovyエンジニアといっても過言ではないでしょう。 若干言い過ぎたかも知れませんが、Javaエンジニアにとって非常に学びやすい言語であることは間違いありません。
もちろん、Javaコードとの親和性だけでなく、GroovyにはJava開発者に異次元の生産性を提供する多くの機能が含まれています。 型宣言の省略、クロージャ、便利なコレクション操作、メタプログラミング、演算子のオーバーロードなど、Rubyや他言語で羨ましかった機能、またはそれ以上の機能がGroovyで使用できます。
Grailsは、このGroovyでコードを書きます。 Struts1ユーザにとっては、このGroovyが逆に障壁になるかと思います。 いくらJavaとの親和性が高いとはいえ、言語が変わるというのは大きな話です。 しかし、新たなフォースを手に入れるには常に痛みが付き物です。 Java開発者にとって、比較的少ない痛みで、異次元の生産性が手に入るのはGroovy以外にありません。
既存のJava資産をそのまま活用できる
GroovyとJavaの親和性の高さも、既存のJava開発者にとって大きな魅力の1つです。 Groovyからは、既存のJavaのライブラリ・フレームワークといった、Javaのコードを簡単に呼び出すことができます。 これは、Javaのコード内でJavaのコードを呼ぶように、Groovyのコード内でJavaコードを呼び出せます。
また、GrailsでもJavaライブラリや、Javaのコードと連携する仕組みが予め用意されています。
jar形式のJavaライブラリは、単にlib
ディレクトリにファイルを置くだけで、簡単にアプリケーションの依存関係として追加できます。
Javaコードには、デフォルトでsrc/java
というディレクトリがGrailsによって用意されており、このディレクトリでJavaコードをすぐに書き始めることができます。
もし、Strutsアプリケーションで使用してた、ライブラリや、ビジネスロジックなどの資産がある場合は、 Grails上でもその資産を活用できます。
フレームワークの連携で悩むことはありません
Struts1を単独で使用していたユーザもいるかも知れませんが、SpringやHibernateといった、他のフレームワークと組み合わせて使用していたユーザも多くいると思います。 これらフレームワークを組み合わせて使用する場合は、自分で連携の設定をしなければなりません。 この連携の設定を調べるために、インターネットを彷徨い、気がつくと1日2日経過していた、という話は珍しくありません。 一度連携できたら、使い回すだけでしょ?と思いがちですが、フレームワークのバージョンに伴い設定方法が変更になり、また1日2日インターネットにダイブする羽目になります。
たかが数日と思うかもしれませんが、迅速な開発が求められる昨今においては、これは非常に足かせになります。ぐぐってる暇なんてありません。
Grailsはフルスタックフレームワークです。create-app
とコマンドを打てば、数秒で全てのフレームワーク連携が完了した雛形が手に入ります。
Struts1のように、フレームワークの連携で悩むことは、Grailsではありません。
進化し続けるフレームワーク
Struts1ユーザにとって、乗り換えたフレームワークが、数年で使い物にならなくなる自体はあまり嬉しくないでしょう。 そのため、現在Grailsの開発が活発に行われているか、今後もメンテンナスが続くかは関心の1つかと思います。
現在Grailsは、VMwareとEMCとの合併会社Pivotalの配下にある、SpringSourceで開発が行われています。 開発自体はオープンに行われており、ベンダー依存を気にする必要は今のところ無いかと思います。 ソースコードもGithubで公開されています。
開発は活発に行われており、世の中の動向に合わせて新しい機能が今なお追加されています。 ただ、新しい機能がどんどん追加されているため、正直枯れているとは言えない状況です。
Sturts1ユーザにとっては、安定性が心配になるかと思いますが、商用レベルでの使用に十分耐えるレベルではあるので、安心してください。
学習環境
Grailsのドキュメントは非常にしっかりと書かれており、日本語版も鋭意翻訳中です。
まとめ
GrailsはJava開発者のための、フレームワークです。 Java開発者が魅力を感じないとしたら、ほんと訴求先がないと言っていいかもしれません(言語、フレームワークは素晴らしいのは間違いないのですが、Java開発者以外には訴求力が弱い)。
ということでSturts1ユーザの皆さん、一緒にGrailsやりましょう!
GrailsでアノテーションベースでBeanを登録する
Grailsではgrails-app/service
ディレクトリ配下などにクラスを置くと自動的にSpringのbeanとして認識されますが、src/groovy
やsrc/java
といったディレクトリでは自動的にはbeanとして登録されません。
src/groovy
、src/java
配下のクラスをbeanとして登録したい場合はSpring Bean DSLを使用して登録することができますが、もう一つの方法としてSpringのcomponent-scan
を使用する方法がGrailsでも提供されています。
component-scan
を使用すると指定したパッケージ配下のクラスに対してアノテーションベースでbean登録ができるようになります。
設定の準備
Grailsでcomponent-scan
を使用するにはConfig.groovyでgrails.spring.bean.packages
を指定します。
grails.spring.bean.packages = ["grails.example"]
これであとはgrails.example
配下にアノテーションベースで定義したクラスを置くことで自動的にbean登録されます。
アノテーションベースでbeanを定義する
Grails特有のルールというのは基本的になくSpringのルールに従うだけです。詳細はSpringのドキュメントを参照してくだい。
いくつかサンプルを紹介します。
Springのアノテーションを使用して登録する
Springの@Component
、@Autowired
、@Qualifier
などを使用して登録します。
package grails.example import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component @Component class MyBean { @Autowired MyBeanHoge myBeanHoge @Autowired @Qualifier("myPiyo") def piyo @Autowired @Qualifier("grailsApplication") def grailsApplication String toString() { "This is MyBean" } } @Component class MyBeanHoge { String toString() { "This is MyBeanHoge" } } @Component("myPiyo") class MyBeanPiyo { String toString() { "This is MyBeanPiyo" } }
JSR330を使って登録する
JSR330の@Inject
、@Named
を使用して登録します。
package grails.example import javax.inject.Inject import javax.inject.Named @Named class NamedBean { @Inject NamedBeanHoge namedBeanHoge @Inject @Named("namedPiyo") def piyo @Inject @Named("grailsApplication") def grailsApplication String toString() { "This is NamedBean" } } @Named class NamedBeanHoge { String toString() { "This is NamedBeanHoge" } } @Named("namedPiyo") class NamedBeanPiyo { String toString() { "This is NamedBeanPiyo" } }
JSR330のアノテーションを使用するには依存ライブラリの追加が必要です。
dependencies { ... compile 'javax.inject:javax.inject:1' }
JSR250系も使える
package grails.example import javax.annotation.PostConstruct import javax.annotation.PreDestroy import javax.inject.Named @Named class PostConstructAndPreDestroyBean { def number @PostConstruct def init() { number = 100 } @PreDestroy def destroy() {} }
Spring 3.1 新機能 - AnnotationConfigContextLoader
3.0 から Javaconfig という XML でなく Java のコードで Spring の設定が記述できる機能が追加されていましたが、この機能が Test でも使えるようになりました。
従来 3.0 のテストケースは @ContextConfiguration を使用してこんな定義をしていました。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "/META-INF/spring/*.xml") public class AccountRepositoryTest {
3.1 からは AnnotationConfigContextLoader を指定すると Javaconfig をロード可能なります。面白いのは内部クラスで @Configuration が付いているものを自動でロードしてくれることです。
こんな使い方が出来ます。
package org.yamkazu.spring31; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import javax.inject.Inject; import javax.sql.DataSource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; import org.springframework.transaction.annotation.EnableTransactionManagement; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = AnnotationConfigContextLoader.class) public class AccountRepositoryWithJavaConfigTest { @Configuration @EnableTransactionManagement @ComponentScan(basePackages = "org.yamkazu.spring31", excludeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = TestConfig.class) }) static class TestConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); emf.setDataSource(dataSource()); return emf; } @Bean public DataSource dataSource() { // @formatter:off return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2) .addScript("classpath:/schema.sql") .addScript("classpath:/test-data.sql") .build(); // @formatter:on } @Bean public JpaTransactionManager transactionManager() { return new JpaTransactionManager(); } } @Inject AccountRepository repository; @Test public void test() throws Exception { Account account = new Account(); account.setName("aaa"); repository.persist(account); Account find = repository.find(account.getId()); assertThat(find.getName(), is(equalTo("aaa"))); } }
テスト対象の周辺クラスを、モックに差し替えるといったテストのコンテキストで色々小回りが効きそうな印象です。
ちょっとわからなかったのが、@ComponentScanを使用する場合、かつ自身のテストクラスのパッケージが含まれている場合(テストクラスはテスト対象と同じパッケージにするのでかぶること多いじゃないかなぁ)、@ComponentScan と AnnotationConfigContextLoader が 同じBeanを登録しようとしてコンフリクトしてしまうことです。excludeFiltersを設定することで回避できましたが、これが解なのかは不明。
任意の@Configurationのクラスしてする場合は@ContextConfigurationのclassesに指定すれば良いです。
Spring 3.1がやってくる
先日 Spring 3.1 の RC1 が出ました。
新機能は以下のとおり。
http://static.springsource.org/spring/docs/3.1.0.RC1/spring-framework-reference/html/new-in-3.1.html
3.1 GA release を11月末にリリース予定で、その間にRC2を出すとのことです。
Spring 3.1 新機能 - Bean Definition Profiles と Environment XML版
3.1からProfileという仕組みを使ってBeanの定義を簡単に切り替えられました。RailsとかGrailsだとかSeasar2とかにも似たのがありますね。それです。
やり方はXMLで定義する方法と、Javaのクラスに対してアノテーションを設定する方法がありますが、まずはXMLの方から見ていきます。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"> <context:property-placeholder location="classpath*:META-INF/spring/*.properties" /> <context:component-scan base-package="org.yamkazu.spring31" /> <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory"> <property name="persistenceUnitName" value="persistenceUnit" /> <property name="dataSource" ref="dataSource" /> </bean> <beans profile="production"> <bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource"> <property name="driverClassName" value="${database.driverClassName}" /> <property name="url" value="${database.url}" /> <property name="username" value="${database.username}" /> <property name="password" value="${database.password}" /> <property name="testOnBorrow" value="true" /> <property name="testOnReturn" value="true" /> <property name="testWhileIdle" value="true" /> <property name="timeBetweenEvictionRunsMillis" value="1800000" /> <property name="numTestsPerEvictionRun" value="3" /> <property name="minEvictableIdleTimeMillis" value="1800000" /> </bean> </beans> <beans profile="dev"> <jdbc:embedded-database id="dataSource" type="H2" /> </beans> </beans>
profileはbeansの属性として定義します。ここではproductionとdevという二つのProfileを定義して実行時のProfile情報を元にdataSourceが切り替わるようにしています。beansは上の様にbeansの中に入れ子にしてもいいですし(ネストで書けるようになったのは3.1からです)、従来通りファイル自体に切り離して、rootエレメントとして定義しても問題ありません。
あとは実行時にこのProfileを指定して実行するだけです。この実行時のProfileを指定するのがEnvironmentというしくみです。テストから見てみます。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:/META-INF/spring/*.xml") @ActiveProfiles({ "dev" }) public class AccountRepositoryTest { @Inject AccountRepository accountRepository; @Test public void 一件登録して取得する() { Account account = new Account(); account.setName("aaa"); accountRepository.persist(account); Account find = accountRepository.find(account.getId()); assertThat(find.getName(), is(equalTo("aaa"))); } }
従来のSpringのJUnitでテストをする設定に加えて@ActiveProfilesというアノテーションを指定してProfile情報を定義します。分かりやすいようにわざと配列形式で指定しましたが、有効にしたいProfileは複数定義することが可能です。実はProfileの定義も複数指定できます。例えば
<beans profile="dev1,dev2"> <jdbc:embedded-database id="dataSource" type="H2" /> </beans>
のような形です。
次はプログラム内でEnvironmentを指定する方法を見ていきます。
public class AccountRepositoryXmlTest { @Test public void 一件登録して取得する() { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.getEnvironment().setActiveProfiles("dev"); ctx.load("classpath:/META-INF/spring/*.xml"); ctx.refresh(); AccountRepository accountRepository = ctx.getBean(AccountRepository.class); Account account = new Account(); account.setName("aaa"); accountRepository.persist(account); Account find = accountRepository.find(account.getId()); assertThat(find.getName(), is(equalTo("aaa"))); } }
contextのgetEnvironment().setActiveProfiles("xx")を呼び出して設定します。
実行時のシステムプロパティーに設定する方法もあります。
public class AccountRepositoryXmlTest { @Test public void 一件登録して取得する() { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:/META-INF/spring/*.xml"); ctx.refresh(); AccountRepository accountRepository = ctx.getBean(AccountRepository.class); Account account = new Account(); account.setName("aaa"); accountRepository.persist(account); Account find = accountRepository.find(account.getId()); assertThat(find.getName(), is(equalTo("aaa"))); } }
上記のコードを
-Dspring.profiles.active="dev"
というspring.profiles.activeに有効にしたいprofileを指定することが出来ます。
ちなみにWebアプリケーションの場合はweb.xmlからEnvironmentを指定できます。
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>spring.profiles.active</param-name> <param-value>production</param-value> </init-param> </servlet>
Spring Data JPA で遊んでみる まとめ
いろいろ紹介してきましたが、全部以下に書いてあるので、ざっと目を通すといいかなぁと思います。
色々触ってみた感想は、Spring で JPA 使うなら1番目の選択肢として考えていいじゃないかと思います。
メソッドだけでクエリを定義できるといった高級な機能から、独自実装ももてるといった、幅広いカスタマイズ性も持ちあわせており、守備範囲が広く良く出来てるなぁといった印象です。
では、そんな感じで。
Spring Data JPA で遊んでみる 〜その11〜
ここまで紹介てきたクエリの発火方法で、恐らくほとんどのことができるんじゃないかと思います。ただ、どうしても自分で実装を持ちたくなるような時もあります。
Spring Data JPA では定義したリポジトリのインタフェースに対し、独自の拡張クラスを作成することができます。
まず拡張したいメソッドを定義したインタフェースを用意します。
public interface EmpRepositoryCustom { String echo(String message); }
拡張メソッドだけを定義したインタフェースを個別に起こしているところに注意してください。
これをリポジトリのインタフェースに継承させます。
public interface EmpRepository extends EmpRepositoryCustom {
インタフェースの準備はこれで終わりです。実装くらすはEmpRepositoryCustomを実装しつつクラス名をリポジトリ名+Implとなるようにします。このルールは変えられますし、独自にSpringのBeanとして個別に定義することもできます。詳しくはマニュアルを参照してください。
実装クラスはこんなんです。
public class EmpRepositoryImpl implements EmpRepositoryCustom { @Override public String echo(String message) { return message; } }
ちょっとインタフェースの定義が、独自メソッドだけ切り出してインタフェースを別に起こさないといけないなど、手間な感じが最初はしたのですが、一貫して基底となるインタフェースで、他のインタフェースを継承することで機能拡張が行えるというスタイルな感じがして、これはこれでmixinポイ感じで統一感があるのかなぁと思いました。これはJpaRepositoryを継承すればJPAの機能が、JpaSpecificationExecutorを継承すればSpecificationの機能が、QueryDslPredicateExecutorを継承すればQueryDslの機能が、独自のインタフェースを継承すれば独自の機能がという感じです。
サンプル
https://github.com/yamkazu/springdata-jpa-example/tree/customimpl