CLOVER🍀

That was when it all began.

Web層との連携Bundleの作成

今度は、前回作成したWeb層と連携するOSGi Bundleを作成してみたいと思います。Web層でSTSを使ったOSGi Bundle Projectの作成で散々な目に遭ったので、今回は最初からMavenを利用します。

まずは、Projectの作成。

$ mvn archetype:generate -DgroupId=my.service.bundle -DartifactId=my-service-bundle
$ cd my-service-bundle/
$ mvn eclipse:eclipse

できたら、STSにインポートして、「Maven」→「Enable Dependency Management」とSpring Tools」→「Add OSGi Bundle Project Nature」。デフォルトで作成されている、不要なJavaソースは削除。Javaのビルド設定が何故かJ2SE 1.5になっているので、1.6まで引き上げておきます。あと、Targeted Runtimesに「Virgo Web Server」を設定しておきましょうね。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>my.service.bundle</groupId>
  <artifactId>my-service-bundle</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <name>my-service-bundle</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>org.springframework.spring-library</artifactId>
	    <type>libd</type>
	    <version>3.0.0.M3</version>
		<scope>provided</scope>
	</dependency>
  </dependencies>

	<repositories>
		<repository>
		    <id>com.springsource.repository.bundles.snapshot</id>
		    <name>SpringSource Enterprise Bundle Repository - SpringSource Bundle Snapshot</name>
			<url>http://repository.springsource.com/maven/bundles/snapshot</url>
		</repository>
		<repository>
		    <id>com.springsource.repository.bundles.release</id>
		    <name>SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases</name>
			<url>http://repository.springsource.com/maven/bundles/release</url>
		</repository>
		<repository>
			<id>com.springsource.repository.bundle.external</id>
			<name>SpringSource Enterprise Bundle Repository - External Bundle Releases</name>
			<url>http://repository.springsource.com/maven/bundles/external</url>
		</repository>
		<repository>
			<id>com.springsource.repository.bundle.milestone</id>
		    <name>SpringSource Enterprise Bundle Repository - SpringSource Bundle Milestones</name>
			<url>http://repository.springsource.com/maven/bundles/milestone</url>
		</repository>

		<repository>
		    <id>com.springsource.repository.libraries.release</id>
		    <name>SpringSource Enterprise Bundle Repository - SpringSource Library Releases</name>
		    <url>http://repository.springsource.com/maven/libraries/release</url>
		</repository>
		<repository>
		    <id>com.springsource.repository.libraries.external</id>
		    <name>SpringSource Enterprise Bundle Repository - External Library Releases</name>
		    <url>http://repository.springsource.com/maven/libraries/external</url>
		</repository>
		<repository>
		    <id>com.springsource.repository.libraries.milestone</id>
		    <name>SpringSource Enterprise Bundle Repository - Milestone Library Releases</name>
		    <url>http://repository.springsource.com/maven/libraries/milestone</url>
		</repository>
		<repository>
		    <id>com.springsource.repository.libraries.snapshot</id>
		    <name>SpringSource Enterprise Bundle Repository - Snapshot Library Releases</name>
		    <url>http://repository.springsource.com/maven/libraries/snapshot</url>
		</repository>
	</repositories>
</project>

Mavenリポジトリの設定は、greenpages.parentから全部持ってきてしまいました(笑)。

公開するインターフェースの作成。
src/main/java/my/service/bundle/HelloService.java

package my.service.bundle;

public interface HelloService {
	public String getVersion();
	
	public int getCount();
}

実装クラス。インターフェースとは別パッケージに作成します。
src/main/java/my/service/bundle/internal/HelloServiceImpl.java

package my.service.bundle.internal;

import java.util.concurrent.atomic.AtomicInteger;

import my.service.bundle.HelloService;

import org.springframework.stereotype.Service;

// @Component("helloService")でもOK
@Service("helloService")
public class HelloServiceImpl implements HelloService {
	private AtomicInteger counter = new AtomicInteger();
	
	@Override
	public String getVersion() {
		return "1.0";
	}

	@Override
	public int getCount() {
		return counter.incrementAndGet();
	}
}

MANIFESTの作成。「src/main/resources」ディレクトリがない場合は、作成した上でソースフォルダに設定してください。
src/main/resources/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Export-Package: my.service.bundle;version="1.0.0"
Bundle-Version: 1.0.0
Bundle-Name: My Service Bundle
Bundle-ManifestVersion: 2
Import-Package: org.springframework.stereotype;version="[3.0,3.1)"
Bundle-SymbolicName: my.service.bundle

外部に公開するパッケージは、「my.service.bundle」のみで実装クラスが所属するパッケージは公開しません。

Springを利用したOSGiの設定。
src/main/resources/META-INF/spring/osgi-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        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.xsd
                http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"
        xmlns:osgi="http://www.springframework.org/schema/osgi">

        <osgi:service ref="helloService" interface="my.service.bundle.HelloService" />
</beans>

Contextの設定。
src/main/resources/META-INF/spring/module-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        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.0.xsd
								http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"
        xmlns:context="http://www.springframework.org/schema/context">

        <!--  enables classpath component scanning for this module -->
        <context:component-scan base-package="my.service.bundle.internal" />

</beans>

実装クラスを格納したパッケージを対象とした、コンポーネントスキャンを利用しています。

ここまでやったら、Virgo Web Serverに登録してみます。エラーなくデプロイできて、Virgoの管理コンソールから確認できれば成功です。

続いて、前回作成したWeb Bundleと繋げてみます。ここから先は、前回作成したWeb Bundleの修正です。

依存関係が変わるので、pom.xmlを変更します。依存するプロジェクトの情報を追記してください。
pom.xml

    <dependency>
      <groupId>my.service.bundle</groupId>
      <artifactId>my-service-bundle</artifactId>
      <version>1.0.0</version>
    </dependency>

なお、面倒になったのでService層とリポジトリの設定は一緒にしてしまいました…。

依存パッケージが増えるので、MANIFESTを修正します。
src/main/webapp/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Bundle-SymbolicName: my.web.bundle
Bundle-Version: 1.0.0
Bundle-Name: My Web Bundle
Web-ContextPath: my-web-bundle
Import-Library: org.springframework.spring;version="[3.0,3.1)"
Import-Bundle: com.springsource.org.apache.taglibs.standard;version="[
 1.1.2,1.3)"
Import-Package: my.service.bundle;version="[1.0,2.0)",
 org.eclipse.virgo.web.dm;version="[2.0.0,3.0.0)",
 org.springframework.beans.factory.annotation;version="[3.0,3.1)",
 org.springframework.core.io;version="[3.0,3.1)",
 org.springframework.stereotype;version="[3.0,3.1)",
 org.springframework.web.bind.annotation;version="[3.0,3.1)",
 org.springframework.web.context;version="[3.0,3.1)",
 org.springframework.web.servlet;version="[3.0,3.1)",
 org.springframework.web.servlet.mvc.annotation;version="[3.0,3.1)"

Import-Packageに、「my.service.bundle」を追加しています。

作成したHelloServiceへの参照設定を、applicationContext.xml追加。
src/main/webapp/WEB-INF/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:osgi="http://www.springframework.org/schema/osgi"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
		http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">
	
	<!-- import a service from OSGi implementing the Directory interface and make available as a bean called directory -->
	<osgi:reference id="helloService" interface="my.service.bundle.HelloService" />

</beans>

Controllerを、HelloServiceを利用するように修正します。
src/main/java/my/web/bundle/HelloController.java

package my.web.bundle;

import java.util.concurrent.atomic.AtomicInteger;

import my.service.bundle.HelloService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {
	@Autowired
	private HelloService helloService;
	
	private AtomicInteger counter = new AtomicInteger();
	
	@RequestMapping("/index")
	public ModelAndView index() {
		ModelAndView modelAndView = new ModelAndView();
		modelAndView.setViewName("index");
		modelAndView.addObject("myCount", counter.incrementAndGet());
		modelAndView.addObject("version", helloService.getVersion());
		modelAndView.addObject("count", helloService.getCount());
		
		return modelAndView;
	}
}

Autowiredアノテーションを使って、HelloServiceの実装体をDIするように宣言しています。また、Spring MVCでビューに値を渡す際には、ModelAndViewクラスを利用するようです。どうでもいいですが、すっごい名前だなぁと…。

最後、JSP
src/main/webapp/WEB-INF/jsp/index.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<html>
<body>
<h2>Hello Spring MVC myCount:${fn:escapeXml(myCount)} Service[Version:${fn:escapeXml(version)}, count:${fn:escapeXml(count)}]</h2>
</body>
</html>

実は、JSPを書くのって相当久しぶりなんですよね…。EL式なんて、初めて書きました。

ここまでできたら、Virgo Web Serverにデプロイして動作確認。
http://localhost:8080/my-web-bundle/hello/index

HelloServiceImplが出力しているバージョン番号が確認できますね?また、アクセス回数が増えるごとにmyCount、count両方の値が増加していきます。
※Springの管理するBeanは、デフォルトでSingletonのためです

ここまでくるまで実はいろいろハマっていたのですが、なんとか繋がってよかったです。

あ、ちょっと注意ですがMavenプロジェクトの連結はSTS…というかEclipseに任せているので、プロジェクトをまとめるpom.xmlを書かないとコマンドラインではきっとうまくパッケージングができないと思います…。ローカルリポジトリにインストールしても回避できるかと思いますが、それをするのもちょっとやだなぁ、と。