8.7. Spring-MVC Integration

1.1부터 스프링 DM은 스프링 MVC 프레임워크와 밀접하게 통홥되었다. 이번 섹션에서 어떻게 스프링 MVC 애플리케이션을 OSGi 환경에서 실행할 수 있는지 살펴보겠다.

OSGi 플랫폼에서 제대로 사용되려면, 스프링 MVC를 새로운 환경으로 이전해야 한다. 스프링 DM은 OSGi를 인식하는 application context를 제공한다.(OsgiBundleXmlWebApplicationContext) 이것은 스프링 MVC의 XmlApplicationContext와 동일한 역할을 한다. application context는 웹 애플리케이션 BundleContext를 알고 있고 따라서 OSGi 영역에 있는 자원들을 읽고 OSGi 서비스를 공개하고 BundleContextAware를 지원하고 클래스패스에 포함되어있는 번들들을 컴포넌트스캔할 수 있다.

이 애플리케이션 컨텍스트를 사용하려면 스프링의 ContextLoaderListener의 contextClass라는 파라미터와 DispatcherServlet을 사용하면 된다.

<context-param>
  <param-name>contextClass</param-name>                                                                   (1)
  <param-value>
org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext
</param-value>    (2)
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>                        (3)
</listener>

<servlet>
  <servlet-name>petclinic</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>                              (4)
  <load-on-startup>2</load-on-startup>
  <init-param>
    <param-name>contextClass</param-name>                                                             (5)
    <param-value>
org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext
</param-value>  (2)
  </init-param>
</servlet>

(1) 스프링의 ContextLoaderListner가 최상위 웹 애플리케이션 컨텍스트 타입을 알기 위한
context-param 이름
(2) OSGi를 인식하는 web application context 풀네임
(3) 스프링 설정 읽어들이는 리스너
(4) 스프링 MVC 젤 앞단 컨트롤러
(5) 스프링의 DispatcherServlet이 웹 애플리케이션 컨텍스트 타입을 알기 위한 init-param

위와같이 설정하면,  스프링 DM 번들은 구동중인 BundleContext를 가져다 사용할 수 있으며 OSGi 환경을 인식할 수 있다.

노트

물론 스프링 MVC 애플리케이션에는 적절한 import 문들이 들어가있어야 한다. 즉 WAR도 번들이기 때문에 manifest 파일에 적절하게 설정되어 있지 않으면, 클래스패스가 맞지 않게되고 따라서 제대로 동작하지 않을 것이다.

8.6. OSGi-ready libraries and web development

불행히도, 현재 대부분의 웹 개발용 라이브러리들은 OSGi 번들이 아니다. 즉 그 라이브러리들을 OSGi 공간에서 사용하려면 다른 번들에 내장된 채로 사용하는 수밖에 없다. 이 문제를 해결하기 위해서, 스프링 DM 프로젝트는 흔히 사용하는 라이브러리들을 OSGi에서 사용할 수 있는 형태로 바꾼 것들을 Maven 리파지토리에 올려서 제공하고 있다.

8.6.1. Deploying web containers as OSGi bundles

스프링 DM은 웹 컨테이너를 OSGi 서비스로 설치하는 것이 가능하게 한다. Tomcat 이나 Jetty 배포판이 그런 일을 하지는 않기 떄문에 스프링 DM은 간단하지만 유용한 두 개의 OSGi Activator를 스프링 DM 리파지토리에서 제공한다. 어떤게 설치되면, 프로그래밍적으로 기본 설정(커스터마이징 할 수 있다. activator는 공용이지만 설정은 쉽게 변경할 수 있다.)에 따라 적절한 웹 컨테이너를 시작시킨다.

8.6.1.1. Tomcat 5.5.x/6.0.x

아파치 톰캣 버전 5.5.x 와 6.0.x는 OSGi 구성물 형태로 catalina.osgi artifactId를 가지고 있다. 이 jar들은 오직 commons-logging, JMX, Servlet/JSP 라이브러리만을 필요로 한다.

게다가 저장소에는 톰캣 Activaor도 있는데 이들의 이름은 catalina.osgi.start로 되어있다. activator는 톰캣 XML 설정을 이해하며 서버를 localhost, 8080 포터에서 실행하기위한 기본 설정을 가지고 있다. 이런 기본 설정은 conf/server.xml 을 톰캣 activator에 Fragment로 붙여서 기본 설정을 재정의할 수 있다.

fragment를 붙이려면, 다음과 같이 manifest에 추가한다.

Fragment-Host: org.springframework.osgi.catalina.start.osgi

8.6.1.2. Jetty 6.1.8+/6.2.0

Jetty는 기본적으로 OSGi에서 사용할 수 있는 형태다. 따라서 어떤 변형을 하지 않아도 OSGi 플랫폼에 설치할 수 있다. 하지만, activator가 없기 때문에 스프링 DM이 톰캣 activator같은 걸 하나 제공해준다. 이 activator는 jetty.start.osgi라는 이름을 가지고 있다. 톰캣 activator와 마찬가지로 이녀석도 localhost에 8080 포트로 기본 설정을 포함하고 있다. 이 기본 설정을 바꾸려면 etc/jetty.xml 에 설정을 한다음 fragment로 붙이면 된다.

Fragment-Host: org.springframework.osgi.jetty.start.osgi

extender처럼, 각각의 activator들은 사용자가 아무것도 제공하지 않으면 기본값을 사용한다.

8.6.2. Common libraries

Servlet, JSP, Standard Taglib, Commons-EL 및 기타 라이브러리들을 스프링 DM 저장소에서 이용할 수 있다.

8.5. Customizing the standard deployers

디플로이어는 시작시에 필요한 서비스들을 룩업한다. 필수 요소인 경우 디플로이어는 해당 서비스를 참조할 수 있을 때까지 기다리고, 만약 시간이 지나도 참조할 수 없을 때는 예외를 발생시킨다. 만약에 기본 타임아웃 설정이나 서비스 룩업 필터가 정의되어 있지 않으면, 스프링 DM의 reference 엘리먼트를 사용하여 설정할 수 있다.

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
   xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
   xmlns:osgi=”http://www.springframework.org/schema/osgi”
   xmlns:p=”http://www.springframework.org/schema/p”                                     (4)
   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”>

    <osgi:reference id=”myTomcatServer”                                                  (1)
        interface=”org.apache.catalina.Service”
        filter=”(environment=testing)”
        cardinality=”0..1″/>
        
    <bean id=”warDeployer”                                                               (2)
        class=”org.springframework.osgi.web.deployer.tomcat.TomcatWarDeployer”
        p:service-ref=”myTomcatServer”/>                                                 (3)

</beans>

1    사용자 정의 OSGi 서비스 룩업
2    디플로이어 정의Deployer definition (이름이 중요하다.)
3    서비스 속성 할당(p namespace 사용)
4    스프링의 p 네임스페이스 선언

위의 설정에 필요한 패키지를 import 해야 한다는 것을 주의 하자. 그리고 위는 웹 Extender에서 사용해야 하기 때문에 Fragment로 설정한다. 우선 import 해야 하는 패키지는 Catalina 패키지고, 그안에 있는 Service  인터페이스는 다른 패키지에 있는 Connector라는 것의 의존하고 있다. 따라서 다음과 같이 설정한다. 그러지 않으면, ClassNotFoundException 이나 NoClassDefFoundException이 발생한다.

# Catalina packages
Import-Package: org.apache.catalina,org.apache.catalina.connector
# Spring-DM Web Extender
Fragment-Host: org.springframework.bundle.osgi.web.extender

8.4. web extender 설정하기

core extender와 마찬가지로, 웹 Extender도 OSGi fragment로 설정될 수 있다. 같은 패턴을 사용해서, 웹 extender는 자신의 번들 영역에있는 META-INF/spring 폴더 밑에 있는 XML 파일들을 찾고 그것들을 하나의 application context로 조립하여 자신의 설정파일로 내부적으로 사용한다. 이런 기본 설정을 재정의 하려면, 아래 표에서 적당한 빈 이름을 찾아서, 그것들을 원하는 대로 정의한 다음 그걸 다음의 코드를 사용하여 fragment로 spring-osgi-web.jar에 붙이면 된다.

Fragment-Host: org.springframework.bundle.osgi.web.extender

다음의 빈들이 현재 Web extender에 의해 인식가능하다.

표생략

8.4.1. 웹 배포자 변경하기

톰캣 배포자에서 제티로 바꾸는 예제는 다음과 같다. META0INF/spring/jetty-deployer.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”>

    <bean id=”warDeployer”                                                               (1)
        class=”org.springframework.osgi.web.deployer.jetty.JettyWarDeployer” />          (2)
</beans>

1    web extender에 의해 사용되고 있는 미리 정의한 빈 이름
2    org.springframework.osgi.web.deployer.WarDeployer 인터페이스를 구현하고 있는 Bean 구현체

파일을 만들었으면, 스프링 DM 웹 Extender 번들d에 OSGi fragment로 붙어있는 번들이 되어야 한다. 따라서 Fragment-Host 헤더를 사용한다.

Fragment-Host: org.springframework.bundle.osgi.web.extender

자 이제 위의 조각(Fragment)이 spring-osgi-web.jar 번들에 끼워졌기 때문에 웹 애플리케이션을 제티에 배포한다.

미리 만들어둔 제티 Fragment를 스프링 DM 메이븐 저장소에서 jetty.extender.fragment.osgi 아티팩트id로 사용할 수 있다.

8.3. WAR classpath in OSGi

서블릿 스펙에는 WAR 내부에 특별한 의미를 지니는 위치와 몇가지 규칙들을 정의하고 있다. 이번 섹션에서는 이것들이 OSGi 환경에서 어떻게 처리되는지 살펴볼 것이다.

8.3.1. Static Recsources

WAR 번들을 설치할 때, static resource들의 경우, 스프링 DM은 번들 영역에서 가용한 것이 무엇인지만 신경쓴다. 이게 무슨 뜻이냐면 번들 jar 내부에 있는 것들과 거기에 붙어있는 fragment만을 사용할 수 있다는 것이다. 서블릿 스펙에 보면, 웹 애플리케이션에 접속한 클라이언트가 아니라 WEB-INF 폴더 이하에 있는 자원들만ServletContext API를 통해 사용 가능하다고 되어있다. 게다가 클래스패스(imported pachages, required bundles 또는 dynamic imports)에 있는 모든 자원은 애플리케이션 코드에서 읽어들여 사용할 수 있지만 그 밖에서는 참조할 수 없다.

8.3.2. 서블릿

기존의 WAR 배포와의 주요 차이점은 서블릿 패키지들을 WAR 번들에서 사용할 수 있도록 명시적으로 import되어야 한다는 것이다. 해당 패지키들을 Import -Package 속성에 추가한다.

Import-Package: javax.servlet,javax.servlet.http,javax.servlet.resources

추가적으로, 서블릿 스펙은 WAR의 클래스패스를 몇몇 미리 정의된 위치에 근거하고 있다. 빠르게 살펴보면 다음과 같다:

  • WEB-INF/classes: 모든 리소드들은 WEB-INF/Classes 밑에 있다
  • WEB-INF/lib/*.jar: 모든 jar 파일들은 WEB-INF/lib 밑에 있다.

여기에 추가로, 각각의 컨테이너 구현체들은 WAR 클래스패스에 추가되는 common 라이브러리들을 제공할 수 있다. OSGi 이기 때문에, WAR 클래스패스는 다시 구성한다. 스프링 DM은 미리 정의된 위치는 무시하고 항상 OSGi 클래스패스를 사용한다. 즉 WEB-INF/classes에 포함되어 있거나 WEB-INF/lib에 포함되어 있다 하더라도 WAR 번들이 import한 패키지들만 사용할 수 있다는 것이다. 다시 말하자면, WEB-INF/classes 밑에 있는 모든 클래스들중에 WAR OSGi 클래스패스에서 가용하지 않은 것들은 없는거나 마찬가지다.

미리 정의된 WAR 위치들을 설정하는 가장 쉬운 방법은, 그것들을 번들 클래스패스에 추가하는 것이다.

Bundle-Classpath: .,WEB-INF/classes,WEB-INF/lib/some.jar,WEB-INF/lib/lib.jar

번들 클래스패스에 추가하기 전에 그들을 OSGi 번들로 설치할 수 있는지 없는지 생각해야한다. 그렇게 함으로써 귿르을 다른 WAR에서도 사용할 수 있고 OSGi 버전잉을 할 수 있다. 동일한 VM에 같은 라이브러리의 상이한 버전이 여럿 존재하는 것이 가능해 진다.

8.3.3. JSP

JSP를 처리하려고, 스프링 DM은 Tomcat Jasper2 엔진을 통합했다. 즉 JSP 1.2, 2.0, 2.1을 지원한다는 것이다. OSGi화된 버전들은 스프링 DM 리파지토리에서 가용하다. OSGi 번들이 Jasper 클래스들을 import할 필요가 없다.

8.3.3.1. 태그 라이브러리들

JSP 스펙은 태그 라이브러리 생성하여 “JSP 페이지에서 재사용할 수 있는 기들을을 모듈화 하여 선언할 수 있도록”했다. 또한 재사용 가능한 taglib들은 자바 클래스들(Tag 구현체)와 어떤 태그들을 사용할 수 있는 기술한 파일을 컴포넌트로 가지고 있다. 스프링 DM은 taglib들을 jar 형태로 WEB-INF/lib 밑에 두거나 풀어져있는 상태로 WEB-INF/classes 밑에 두는 JSP 컨벤션을 확장하여, 번들 클래스패스(imported pachages, required bundles)에 있는 모든 taglib들을 찾아낸다.

스프링 DM은 번들 클래스패스 내에서 자동으로 taglib 파일(*.tld)들을 찾아서 그들을 Jasper 엔진에서 사용할 수 있게 해준다. 하지만, tag 정의가 자동으로 찾아지는 반면 tag 클래스들은 그렇지 않다. 여기서도 OSGi 클래스패스가 우선한다. 즉, 태그를 사용하려면, war 번들이 tag에 해당하는 클래스를 import 해야한다. 안그러면 태그를 사용할 수 없다.

많은 태그를 방출하는 라이브리들을 다룰 때, Impoert-Pachage 대신 Required-Bundle 헤더를 사용할 수 있다.

Require-Bundle: org.springframework.osgi.jstl.osgi

위의 헤더를 사용하면, JSP Standard Tag Library(JSTL)이 내보내는 모든 클래스들을 war 번들이 참조할 수 있고 따라서 JSP 내에서 사용할 수 있다.

주의할 것

Require-Bundle을 사용하기 전에 그 용법에 대해 OSGi 스펙 3.13 참조하라.

war 클래스패스에 어떤 매카니즘을 사용하든 상관없이, 여러 WAR들이 그것들을 공유하게 할 수 있다. 각각의 번들들은 오직 패키지만 import 할 수 있고 라이브러리 jar를 통째로 가져오진 못한다. 사실, 다른 번들들이나 Jar에 들어있는 패지키들을 선택적으로 사용하여 원하는 행위를 도출해 낼 수 있다. 매우 막강한 기능으로 웹 애플리케이션 배포를 용이하게 한다.