Petclinic을 통해 스프링 3.0 주요기능 살펴보기

Spring Framework 3.0 M2 released

스프링 3.0 m2가 배포됐습니다. 유겐 휄러가 주요 기능을 잘 정리해 뒀네요. 아쉬운건 아직도 레퍼런스 업데이트가 되지 않았다는 겁니다. 어쩔 수 없이 아쉬운 사람이 우물 판다고 예제 코드를 뒤적거릴 수밖에 없더군요.

EL 지원에 관한 예제는 petclinic-servlet.xml에서 볼 수 있습니다.

    <bean id=”visits” class=”org.springframework.samples.petclinic.web.VisitsAtomView”/>

    <bean id=”vets” class=”org.springframework.web.servlet.view.xml.MarshallingView”>
        <property name=”contentType” value=”application/vnd.springsource.samples.petclinic+xml”/>
        <property name=”marshaller” ref=”marshaller”/>
    </bean>

    <bean class=”org.springframework.web.servlet.view.ContentNegotiatingViewResolver”>
        <property name=”mediaTypes”>
            <map>
                <entry key=”xml” value=”#{vets.contentType}”/>
                <entry key=”atom” value=”#{visits.contentType}”/>
            </map>
        </property>
        <property name=”order” value=”0″/>
    </bean>

이렇게 visits와 vets 빈의 contentType 속성에 들어있는 값을 #{vets.contentType}, #{visits.contentType} 이런식으로 참조하여 다른 빈에 주입할수 있습니다. visits에는 contentType속성이 없는 것 같지만 이 속성은 AbstractView에 있는 속성이고 VisitsAtomView 클래스가 그것을 상속했기 때문에 기본값을 가지게 될 겁니다.

다음으로 RestTemplate 기능은 컨트롤러에서 볼 수 있습니다.

@Controller
@RequestMapping(“/owners/{ownerId}/pets/new”)
@SessionAttributes(“pet”)
public class AddPetForm {

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@PathVariable(“ownerId”) int ownerId, Model model) {
        Owner owner = this.clinic.loadOwner(ownerId);
        Pet pet = new Pet();
        owner.addPet(pet);
        model.addAttribute(“pet”, pet);
        return “pets/form”;
    }

}

이런 식으로 URL의 일부를 매서드 매개변수로 바인딩 해줍니다. 바인딩 할 필요가 없는 경우 와일드 카드를 이용할 수도 있습니다.

@Controller
@RequestMapping(“/owners/*/pets/{petId}/visits/new”)
@SessionAttributes(“visit”)
public class AddVisitForm {

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@PathVariable(“petId”) int petId, Model model) {
        Pet pet = this.clinic.loadPet(petId);
        Visit visit = new Visit();
        pet.addVisit(visit);
        model.addAttribute(“visit”, visit);
        return “pets/visitForm”;
    }

}

이런 식으로 말이죠. 간단해 보이네요~

다음으로는 AtomView 지원 기능도 살펴봤습니다. 3.0에 추가된 AbstractAtomFeedView 이 클래스를 상속해서 구현하면 되더군요.

public class VisitsAtomView extends AbstractAtomFeedView {

    @Override
    protected void buildFeedMetadata(Map<String, Object> model, Feed feed, HttpServletRequest request) {

    }

    @Override
    protected List<Entry> buildFeedEntries(Map<String, Object> model,
            HttpServletRequest request, HttpServletResponse response) throws Exception {

    }
   
}

구현은 이런 식이며 피드 메타 데이터는 Feed 객체에 Id와 Title을 그리고 Updated에 날짜를 채워줍니다. 코드에 에러가 있어서 Feed 라는 클래스를 자세히 보지 못해서 아쉽네요. 그 아래 매서드에서는 Entry List를 만들어서 반환해줍니다. Entry도 역시 자세히 보고 싶은데 못봤습니다. id, title, updated, summary 정보를 설정하는 코드로 대충 어떤 속성이 있는지 예상할 순 있었습니다.

마지막으로 OXM 기능을 살펴봤는데 끝내주더군요. @_@

    <bean id=”vets” class=”org.springframework.web.servlet.view.xml.MarshallingView”>
        <property name=”contentType” value=”application/vnd.springsource.samples.petclinic+xml”/>
        <property name=”marshaller” ref=”marshaller”/>
    </bean>

    <oxm:jaxb2-marshaller id=”marshaller”>
        <oxm:class-to-be-bound name=”org.springframework.samples.petclinic.Vets”/>
    </oxm:jaxb2-marshaller>

보시다시피 MarshallingView는 스프링 3.0에서 제공하는 클래스고 Vets는 도메인 클래스입니다. OXM을 지원하는 스키마까지 제공하는군요. 저렇게만 설정하면 객체를 알아서 XML로 변환해주느냐???… 아니요 한가지 더 필요합니다.

 @XmlRootElement
public class Vets {

    private List<Vet> vets;

    @XmlElement
    public List<Vet> getVetList() {
        if (vets == null) {
            vets = new ArrayList<Vet>();
        }
        return vets;
    }

}

캬… 뭐 대충 눈으로 보면 뭐하는건지 알 수 있게해주는 직관적인 코드입니다.

자 이정도면 대충 주요 기능은 거의다 조금 살펴본 것 같습니다. 예제 코드좀 돌려보면 좋겠는데 그건 나중에 해봐야겠습니다. 지금은 번역 땜시..-_-;;

메이븐 Maven 2.0.10 배포!

http://maven.apache.org/release-notes.html
자세한 내용은 위 링크에서 참조하세요~

버그 30개 가량 고쳤고 10개 가량의 개선 사항이 있었군요. 기존 빌드에 영향을 줄 수 있는 것 두 가지를 요약해 뒀는데..

한 가지는 settings.xml에서 미러 저장소 설정할 때 문서에 나와있는던 내용과 다르게 맨 마지막에 대응하는 저장소를 사용했었는데 그걸 다시 수정해서 맨 처음에 대응하는 저장소를 사용하도록 고쳤다는 내용이고,

다른 하나는 pom 모델에서 hashmap을 사용하여 의존성 순서 판단 하던것을 linkedHashMap을 사용하도록 변경했다는 내용입니다.

@_@ 둘 다 잘 모르는거네요.

SWF 9장 시스템 설정

9.1. 도입

이번 장에서는 웹 플로우 시스템을 웹 환경에서 사용하기 적당하도록 설정하는 방법을 살펴본다.

9.2. webflow-config.xsd

웹 플로우는 스프링 스키마를 제공하여 여러분이 시스템을 설정할 수 있도록 한다. 이 스키마를 사용하려면 그것을 기반시설 계층의 빈 파일 중 하나에 추가하라.

<beans xmlns=”http://www.springframework.org/schema/beans”
       xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
       xmlns:webflow=”http://www.springframework.org/schema/webflow-config”
       xsi:schemaLocation=”
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/webflow-config
           http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd”>       

    <!– Setup Web Flow here –>
   
</beans>

9.3. 기본 시스템 설정

다음 절에서 웹 플로우 시스템을 여러분 애플리케이션에 설정할 때 필요한 최소한의 설정을 살펴보겠다

9.3.1. FlowRegistry

플로우를 FlowRegistry에 등록하라.

<webflow:flow-registry id=”flowRegistry”>
    <webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml” />
</webflow:flow-registry>

9.3.2. FlowExecutor

플로우를 실행하는 핵심 서비스 FlowExecutor를 배포하라.

<webflow:flow-executor id=”flowExecutor” />

스프링 MVC와 스프링 Faces 부분에서 웹 플로우 시스템을 어떻게 MVC와 JSF에 통합하는지 살펴보라.

9.4. flow-registry 옵션

이번 절은 flow-registry 설정 옵션을 살펴본다

9.4.1. 플로우 위치 명시하기

location 엘리먼트를 사용하여 등록할 플로우 정의 경로를 명시한다. 기본으로 base-path가 정의되어 있지 않다면 플로우는 자신의 파일 이름에서 확장자를 뺀 식별자를 부여받는다.

<webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml” />
        
9.4.2. 커스텀 플로우 식별자 부여하기

id를 명시하여 커스텀 등록 식별자를 플로우에 부여한다.

<webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml” id=”bookHotel” />
           
9.4.3. 플로우 meta-attributes 부여하기

flow-definition-attributes 엘리먼틀르 사용하여 등록한 플로우에 커스텀 meta-attributes를 부여한다.

<webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml”>
    <flow-definition-attributes>
        <attribute name=”caption” value=”Books a hotel” />
    </flow-definition-attributes>
</webflow:flow-location>
           

9.4.4. 위치 패턴으로 플로우 등록하기

flow-location-pattern 엘리먼트를 사용하여 명시한 리소스 위치 패턴에 대응하는 플로우를 등록한다.

<webflow:flow-location-pattern value=”/WEB-INF/flows/**/*-flow.xml” />
           
9.4.5. 플로우 위치 기반 경로

base-path 엘리먼트를 사용하여 애플리케이션의 모든 플로우에 대한 기본 위치를 정의한다. 모든 플로우 위치는 기본 경로를 기준으로 상대 경로가 된다. 기본 경로는 ‘/WEB-INF’같은 리소스 경로 또는 ‘classpath:org/springframework/webflow/samples’ 같은 클래스패스 위치가 될 수 있다.

<webflow:flow-registry id=”flowRegistry” base-path=”/WEB-INF”>
    <webflow:flow-location path=”/hotels/booking/booking.xml” />
</webflow:flow-registry>
           
With a base path defined, the algorithm that assigns flow identifiers changes slightly. Flows will now be assigned registry identifiers equal to the the path segment between their base path and file name. For example, if a flow definition is located at ‘/WEB-INF/hotels/booking/booking-flow.xml’ and the base path is ‘/WEB-INF’ the remaining path to this flow is ‘hotels/booking’ which becomes the flow id.

기본 경로를 정의함으로서 플로우 식별자를 부여하는 알고리즘이 약간 바뀐다. 플로우는 이제 기본 경로와 파일이름 사아의 값을 등록 식별자로 부여받는다. 예를 들어, 플로우가 정의되어 있는 위치가 ‘/WEB-INF/hotels/booking/booking-flow.xml’ 이렇고 기본 경로가
‘/WEB-INF’ 라면 남는 부분인  ‘hotels/booking’가 플로우의 id가 된다.

[팁]    디렉토리 당 플로우 정의
각각의 플로우 정의를 별도의 디렉토리로 묶는 것이 좋은 습관이라는 것을 기억해두자. 이렇게 하면 모듈화를 증진시키고 독립적인 리소스를 프로우 정의와 함께 묶을 수 있다. 또한 규약을 이용할 때 두 개의 플로우가 같은 식별자를 갖는 것을 방지할 수 있다.

만약 기본 경로가 명시되어 있지 않거나 플로우 정의가 기본 경로에 있다면 파일이름(에서 확장자를 뺸)것을 사용하여 id를 대입한다. 예를 들어, 만약 플로우 정의 파일이 ‘booking.xml’라면 플로우 식별자는 간단하게 ‘booking’이 된다.

위치 패턴은 등록 기본 경로와 함께 사용하면 매우 막강하다. 플로우 식별자가 ‘*-flow’가 되지 않고 디렉토리 경로 기반이 된다. 예를 들어..

<webflow:flow-registry id=”flowRegistry” base-path=”/WEB-INF”>
    <webflow:flow-location-pattern value=”/**/*-flow.xml” />
</webflow:flow-registry>
           
위 예제에서 WEB-INF 밑에 /user/login, /user/registration, /hotels/booking, /flights/booking에 위치한 플로우들이 있다고 했을 때 플로우 id는 각각 user/login, user/registration, hotels/booking,  flights/booking가 된다.

9.4.6. FlowRegistry 계층구조 설정하기

Use the parent attribute to link two flow registries together in a hierarchy. When the child registry is queried, if it cannot find the requested flow it will delegate to its parent.

parent 속성을 사용하여 두 플로우 등록을 하나의 계층구조로 묶을 수 있다. 요청받은 플로우를 하위 레지스트리에서 찾지 못하면 parent로 위임한다.

<!– my-system-config.xml –>
<webflow:flow-registry id=”flowRegistry” parent=”sharedFlowRegistry”>
    <webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml” />
</webflow:flow-registry>

<!– shared-config.xml –>
<webflow:flow-registry id=”sharedFlowRegistry”>
    <!– Global flows shared by several applications –>
</webflow:flow-registry>
           
9.4.7. 커스텀 FlowBuilder 서비스 설정하기

flow-builder-services 속성을 사용하여 flow-registry에서 플로우를 만들 때 사용할 서비스와 설정을 커스터마이징할 수 있다. 만약 flow-builder-services 태그가 명시되어 있지 않다면 기본 서비스 구현체를 사용한다. 태그가 정의되어 있다면 커스터마이징 하고 싶은 서비스를 참조하면 된다.

<webflow:flow-registry id=”flowRegistry” flow-builder-services=”flowBuilderServices”>
    <webflow:flow-location path=”/WEB-INF/flows/booking/booking.xml” />
</webflow:flow-registry>

<webflow:flow-builder-services id=”flowBuilderServices” />
           

설정 가능한 서비스는 conversion-service, expression-parser, view-factory-creator다. 이 서비스들은 여러분이 정의한 커스텀 빈을 첨조하도록 설정되었다. 예를 들어..

<webflow:flow-builder-services id=”flowBuilderServices”
    conversion-service=”conversionService”
    expression-parser=”expressionParser”
    view-factory-creator=”viewFactoryCreator” />

<bean id=”conversionService” class=”…” />
<bean id=”expressionParser” class=”…” />
<bean id=”viewFactoryCreator” class=”…” />
           

9.4.7.1. conversion-service

conversion-service 속성을 사용하여 웹 플로우 시스템이 사용할 ConversionService를 커스터마이징할 수 있다. Converter는 플로우 실행도중 어떤 타입을 다른 타입으로 변경할 필요가 있을 때 사용한다. 기본 ConversionService는 숫자, 클래스, Enum같은 기본 객체 타입에 대한 컨버터를 등록한다.

9.4.7.2. expression-parser

expression-parser 속성을 사용하여 웹 플로우 시스템이 사용할 ExpressionParser를 커스터마이징할 수 있다. 기본 ExpressionParser는 클래스패스에서 사용할 수 있다면 Unified EL을 사용하고 그렇지 않다면 OGNL을 사용한다.

9.4.7.3. view-factory-creator

view-factory-creator 속성을 사용하여 웹 플로우 시스템이 사용할 ViewFactoryCreator를 커스터미이징 할 수 있다. 기본 ViewFactoryCreator는 JSP, Velocity, Freemarker를 랜더링 할 수 있는 스프링 MVC ViewFactory를 생성한다.

설정 가능한 설정은 development다. 이 설정은 플로우 생성 과정 중에 적용할 전역 설정 속성이다.

9.4.7.4. development

플로우를 개발 모드로 변경하려면 이것을 true로 설정하라. 개발 모드는 메시지 번들 같은 독립적인 플로우 리소스 변경을 포함하여 플로우 정의 변경 핫-릴로딩을 변경한다.

9.5. flow-executor 옵션

이번 장에서는 flow-executor 설정 옵션을 살펴본다.

9.5.1. 플로우 실행 리스너 부착하기

flow-execution-listeners 엘리먼틀르 사용하여 플로우 실행 생명주기 리스너를 등록한다.

<flow-execution-listeners>
    <listener ref=”securityListener”/>
    <listener ref=”persistenceListener”/>
</flow-execution-listeners>
           
특정 플로우만 관찰할 리스너를 설정할 수도 있다.

<listener ref=”securityListener” criteria=”securedFlow1,securedFlow2″/>
           

9.5.2. FlowExecution 영속성 튜닝하기

flow-execution-repository 엘리먼트를 사용하여 플로우 실행 영속성 설정을 튜닝한다.

<flow-execution-repository max-executions=”5″ max-execution-snapshots=”30″ />
           
9.5.2.1. max-executions

max-executions 속성을 설정하여 사용자 세션당 생성할 수 있는 플로우 실행 수 상한선을 설정한다.

9.5.2.2. max-execution-snapshots

max-execution-snapshots 속성을 설정하여 플로우 실행당 가질 수 있는 히스토리 스냅샷 수 상한선을 정할 수 있다. 스냅샷을 못찍게 하려면 이 값을 0으로 설정하라. 무한대로 스냅샷을 찍으려면 이 값을 -1로 설정하라.