캠핑과 개발


tiles2는 Spring MVC와 연동시 그 뷰 네임을 definition 네임으로 하기 때문에 각각의 뷰마다 각각의 definition이 있어야 하므로 번거롭기 짝이없다. tiles2기반으로 프로젝트를 하는 사람들이 이를 간과할리 없다. 찾아보니 Dynamic-tiles2라는 간단한 라이브러리가 그것을 도와준다.

Dynamic-tiles2는 David Winterfeldt라는 사람이 http://www.springbyexample.org/ 운영하면서 만든 라이브러리이다. Spring의 2개의 클래스를 확장하고 1개의 클래스로 처리하는 비교적 초간단 라이브러리이다.(소스코드를 보면 어렵지 않게 이해할수 있다.) 사이트는 이외에도 간단한 예제들이 많이 있어 Spring을 처음 접하는 사람에게 유용하리라 생각된다.

기본에 이어서 설명한다ViewResolver를 아래와 같이 변경한다.

[기존]

 <bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView" />
 </bean>

[신규]

<bean id="tilesViewResolver" class="org.springbyexample.web.servlet.view.tiles2.TilesUrlBasedViewResolver">
        <property name="viewClass" value="org.springbyexample.web.servlet.view.tiles2.DynamicTilesView" />
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
        <property name="tilesDefinitionName" value="default" />
     <property name="tilesBodyAttributeName" value="body" />
     <property name="tilesDefinitionDelimiter" value="." />
    </bean>

tilesDefinitionName 은 기본 tiles의 Definition 이름이고, tilesBodyAttributeName 은 해당 바뀔 속성의 이름이다. tilesDefinitionDelimiter 상속시 구분자이다. 본 라이브러리를 보면 알겠지만 각각의 Spring UrlBasedViewResolver 와 TilesView를 확장하여 간단히 확장한 소스이다.(이게 spring의 장점중에 하나이다.)

어쨌든 이와 같이 ViewResolver를 설정하고 tiles2 설정을 아래와 같이 변경한다..

[기존]

 <definition name=".default" template="/WEB-INF/templates/main.jsp">
  <put-attribute name="title" value="Simple Tiles 2 Example" type="string" />
  <put-attribute name="header" value="/WEB-INF/templates/header.jsp" />
  <put-attribute name="footer" value="/WEB-INF/templates/footer.jsp" />
  <put-attribute name="menu" value="/WEB-INF/templates/menu.jsp" />
  <put-attribute name="body" value="/WEB-INF/templates/blank.jsp" />
 </definition>
 <definition name="index" extends="default">
  <put-attribute name="body" value="/WEB-INF/jsp/index.jsp" />
 </definition>
 <definition name="member/list" extends="default">
  <put-attribute name="body" value="/WEB-INF/jsp/member/list.jsp" />
 </definition>

 [신규]

 <definition name=".default" template="/WEB-INF/templates/main.jsp">
  <put-attribute name="title" value="Simple Tiles 2 Example" type="string" />
  <put-attribute name="header" value="/WEB-INF/templates/header.jsp" />
  <put-attribute name="footer" value="/WEB-INF/templates/footer.jsp" />
  <put-attribute name="menu" value="/WEB-INF/templates/menu.jsp" />
  <put-attribute name="body" value="/WEB-INF/templates/blank.jsp" />
 </definition>

IndexController는 변함이 없다.

 @Autowired
 private SampleService sampleService;
 
 @Autowired
 private TilesUrlBasedViewResolver tilesViewResolver;
 
 
 /**
  * Tiles를 이용한 일반적인 요청입니다.
  */
 @RequestMapping("/sample/index.do")
 public ModelAndView index1(HttpServletRequest request, HttpServletResponse response){
  
  //ModelMap modelMap = new ModelMap();
  //modelMap.addAttribute("documentList", sampleService.index());
  //service call
  sampleService.index();
  request.setAttribute("name", "name");
  return new ModelAndView("sample/index");
 }
 
 /**
  * Tiles의 Default layout가 아닌 body layout를 요청한 예입니다.
  */
 @RequestMapping("/index.do")
 public void index2(){
  System.out.println("/index.do");
  //다른 페이지로 이동하려고 하면 이렇게 합니다.
  tilesViewResolver.setTilesDefinitionName("body");
 }
 
 /**
  * Tiles를 거치지 않고 redirect로 페이지를 이동합니다.
  * @param request
  * @param response
  * @return
  */
 @RequestMapping("/sample/redirect.do")
 public String redirect(HttpServletRequest request, HttpServletResponse response){
  tilesViewResolver.setRedirectContextRelative(true);
  return "redirect:/jsp/sample/redirect.jsp";
 }
 /**
  * Tiles를 거치지 않고 forward로 페이지를 이동합니다.
  * @param request
  * @param response
  * @return
  */
 @RequestMapping("/sample/forward.do")
 public String forward(HttpServletRequest request, HttpServletResponse response){  
  tilesViewResolver.setRedirectContextRelative(true);
  request.setAttribute("name", "name!!!");
  return "sample/forward";
 } 
 
 /**
  * Tiles를 거치지 않고 void 처리합니다.
  * @param request
  * @param response
  * @return
  */
 @RequestMapping("/sample/voidcall.do")
 public void voidcall(HttpServletRequest request, HttpServletResponse response) throws Exception{  
  tilesViewResolver.setRedirectContextRelative(true);
  response.getWriter().print("voidcall!!!");
 }

요약하면, 기존의 tiles2 설정에서 viewresolver를 Dynamic-tiles2 라이브러리 함수로 바꾸고 tiles 설정파일을 기존의 각각의 definition 네임를 제거하면 된다.


출처 : http://yunsunghan.tistory.com/259


Tiles는 기존에 UI Layout에서 인기있었던 라이브러리였다. 비록 각각의 뷰 파일을 만들어야 하는 번거러움이 있음에도 불구하고, include를 제거할수 있는 좋은 대안으로 인기가 있었다. 그후 Sitemesh 라는게 유행했는데 뷰마다 각각의 파일을 만들필요가 없어서 인기가 있었다. 최근까지도 나는 이걸 이용했었다. 다시또 유행하는게 Tiles 2 인데 이놈은 Spring Reference에 나올 정도로 인기가 급상승 중이다. 이런 Layout 라이브러리(또는 프레임웍)는 왼만해서는 새로 변경하지 않는데, Sitemesh의 불편한점(누구나 처음 쓸때 겪은 Sitemesh의 곤란함)을 털어보고자 Tiles2에 도전해 본다.

일반적인 Controller 를 이용한 매핑 예제를 알아본다.(Tiles는 Annotaion과 무관함으로 annotation 기반이라고 해서 특별히 설정할것은 없다.)

Spring MVC 설정에서 ViewResolver는 보통 아래와 같다.

 <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/jsp/" />
  <property name="suffix" value=".jsp" />  
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
 </bean>

이것은 Controller를 Annotation기반으로 할때도 변함없다. 그러나 Tiles2는 UrlBasedViewResolver를 사용하고 뷰클래스도 TilesView를 사용한다.

 <bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView" />
 </bean>

물론 Sitemesh처럼 설정파일도 있다.
Sitemesh와 설정의 차이점은 설정이 Spring Bean으로 Integration되었다는것이다. TilesConfigurer를 아래와 같이 설정한다.

 <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
  <property name="definitions">
   <list>
    <value>/WEB-INF/tiles-defs/templates.xml</value>
   </list>
  </property>
 </bean>

이것이 Spring과 연동하는 끝이다. 두개의 Bean 설정만으로 연동이 끝이다.

나머지 Tiles2 사용법은 Sitemesh를 사용해봤다면 크게 다를만한 것이 없다. 간단히 개념만 정리한다면 Controller View Name만 Tiles2 definition과 일치하면 해당 layout으로 랜더링된다. 이해하기 쉽게 순서적으로 나열해 보면 먼저 Controller를 다음과 같이 만든다.

 @RequestMapping("/index.do")
 public void index(ModelMap model){
  model.addAttribute("msg","Donkey!!");
 }

이것은 index.do를 요청하면 index() 메서드를 호출하고 msg변수를 model에 담아 index viewName과 매핑한다. 만약 Tiles2 설정파일(templates.xml)이 아래와 같다면,

 <definition name="default" template="/WEB-INF/templates/layout1.jsp">
  <put-attribute name="header" value="/WEB-INF/templates/header.jsp" />
  <put-attribute name="footer" value="/WEB-INF/templates/footer.jsp" />
  <put-attribute name="body" value="/WEB-INF/templates/body.jsp" />
 </definition>
 <definition name="index" extends="default">
  <put-attribute name="body" value="/WEB-INF/jsp/index.jsp" />
 </definition>


definition name이 index인것과 매핑되여 해당 /WEB-INF/jsp/index.jsp 파일과 랜더링 된다. 물론 default definition을 상속했으니 해당 layout의 특성을 가져오고 body속성(attribute)만 index.jsp로 바뀐다.

이렇게 일일히 모든 view를 definition 테그로 만든다는것은 별로 좋은생각이 아닌것 같다. 이런 설정을 줄여줄수 있는 방법이 있다. 거기에 대해서 다음글에 포스트하기로 한다.

출처 : http://yunsunghan.tistory.com/258

Spring2.5 annotation 설정

없으면 에러 낼거야
이건 2.5이전부터 있던건데 그냥 한번 짚고 넘어 갑니다.
@Required 가 붙은 setter 메소드는 반드시 XML 설정에 의존성 삽입이 정의 되어 있어야 합니다.
(@Autowired도 안됩니다.)
XML 설정에 명시적인 의존성 삽입이 없다면 런타임 예외가 던져 집니다.
이 애노테이션을 처리하기 위한 후처리기는 다음과 같습니다.
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>


자동으로 묶어 줄게
@Autowried 는 타입에 일치하는 bean을 의존성 삽입 해줍니다.
필드에 사용 할 경우 setter가 존재하지 않아도 됩니다.
@Autowried
private Foo foo;

여러개의 인자를 가진 메소드에 사용 할 수 있습니다.
@Autowried
public void setUp(Foo foo, Bar bar) {
    this.foo = foo;
    this.bar = bar;
}

@Autowired의 속성인 required의 기본값이 true이기 때문에 발견되는 bean이 없다면 예외가 던져 집니다.
발견 되는 bean이 없더라도 예외가 던져지지 않기를 원하면 required 속성을 fasle로 설정하면 됩니다.
@Autowried(required=false)
private Foo foo;

일치하는 타입을 찾기 때문에 발견되는 bean이 여러개인 경우 예외가 던져 집니다.
이를 방지 하기 위해 <bean /> 요소의 추가된 속성인 primary를 설정 합니다.
발견되는 bean이 여러개 일지라도 primary가 설정된 bean이 참조 됩니다.
<bean id="foo" class="example.Foo" primary="true" />
<bean id="foo2" class="example.Foo"/>

이 애노테이션을 처리하기 위한 후처리기는 다음과 같습니다.
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>


정확한 이름을 알려줄게
@Autowired을 사용 할 때 타입으로 bean을 찾기 때문에 여러개의 bean이 발견되면 primary로 해결 했습니다.  하지만 @Qualifier를 이용해 찾으려는 bean의 id를 넘길 수 있습니다.
@Autowried
@Qualifier("foo")
private Foo foo;

필드에만 사용 가능 하기 때문에 메소드에 적용시 다음과 같이 설정 합니다.
@Autowried
public void setUp(@Qualifier("foo") Foo foo, @Qualifier("foo2") Foo foo2) {
    this.foo = foo;
    this.foo2 = foo2;
}

이 애노테이션을 처리하기 위한 후처리기는 @Autowried의 후처리기와 동일 합니다.


JSR-250 애노테이션 지원
-@Resource
-@PostConstruct
-@PreDestroy
위 세가지 애노테이션을 사용하기 위해서는 후처리기를 등록 해야 합니다.
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>

@Resource
JNDI의 리소스 및 Spring 컨테이너의 bean을 찾아 autowring 합니다.
name 속성을 생략 할 경우 해당 필드명으로 찾고 해당 bean이 발견되지 않는다면 타입으로 찾습니다.

필드에 사용 할 경우 setter가 존재하지 않아도 됩니다.
@Resource(name="dataSource")
private DataSource dataSource;

한개의 인자를 가진 setter 메소드만 적용 할 수 있습니다.
@Resource(name="dataSource")
public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
}

타입으로 찾지 않길 원한다면 다음과 같은 설정을 합니다.
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
    <property name="fallbackToDefaultTypeMatch" value="false"></property>
</bean>

JNDI 리소스만 참조 하려면 다음과 같은 설정을 합니다.
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
    <property name="alwaysUseJndiLookup" value="true"/>
</bean>

@PostConstruct 와 @PreDestroy
기존에는 생명 주기를 다루는 방법이 2가지가 있었습니다.
1.InitializingBean, DisposableBean을 구현하는 방법
2.XML 설정파일에서 <bean /> 요소의 init-method, destroy-method 속성 설정

2.5에서 추가된 두 애노테이션은 기존의 방식과 동일한 결과를 가져 옵니다.
@PostConstruct
public void init() {
    ...
}
   
@PreDestroy
public void preDestroy() {
    ...
}

만약 3가지 방식 모두 사용할 경우
우선순위는 애노테이션->XML설정->인터페이스 구현 입니다.


이거 하나면 OK
위에서 나온 애노테이션을 사용하기 위해서는 3가지의 BeanPostProcessor를 등록해야 했습니다.
하지만 이거  하나면 3개가 모두 등록 됩니다.
<context:annotation-config />


알아서 찾아줄게
위에서 나온 애노테이션으로 XML설정을 줄이더라도 bean 선언은 반드시 해야 했습니다.
하지만 component-scan 을 이용하면 bean선언 조차 생략 할 수 있습니다.
component-scan의 대상이 되기 위해서는 스테레오타입 애노테이션을 사용해야 합니다.

4개의 스테레오타입 애노테이션
@Component
- 스테레오타입 애노테이션의 조상 입니다.
@Controller
-Spring MVC에서 컨트롤러로 인식 합니다.
@Service
-역할부여 없이 스캔 대상이 되는데 비즈니스 클래스에 사용하면 될 것 같습니다.
@Repository
-DAO에 사용되며 DB Exception을 DataAccessException으로 변환해 줍니다.

간단한 예제 입니다.
테스트에 사용될 2개의 클래스
@Component("bar")
public class Bar {
}

@Component("foo")
public class Foo  {
    @Autowired
    private Bar bar;
   
    public Bar getBar() {
        return bar;
    }
}

지정된 패키지의 하위패키지까지 재귀적으로 스테레오타입이 있는 클래스를 찾습니다.
필터를 이용해 제외 검색 대상에서 제외 시킬 수 있으며
<context:annotation-config /> 까지 자동으로 등록됩니다.
<context:component-scan base-package="example" />

테스트는 통과 합니다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"Beans.xml"})
public class ScanTest {
    @Autowired
    private Foo foo;
   
    @Test
    public void fooTest() {
        assertNotNull(foo.getBar());
    }
}

@Scope를 함께 사용 하여 Scope를 설정 할 수 있습니다.
@Scope("request")
@Component("bar")
public class Bar {
}
singleton인 foo에서 request scope인 bar를 사용 하면 어떻게 될 까요?
역시 scope문제가 발생 합니다.

이 문제를 해결하기 위해 <context:component-scan /> 요소의 scoped-proxy 속성이 존재 합니다.
scoped-proxy 속성은 <aop:scoped-poxy/> 요소처럼 WebApplicationContext 에서만 유효하며
"session", "globalSession", "request" 이외의 scope는 무시 됩니다.

아래 3가지 값을 지정 할 수 있습니다.
no - 디폴트값, proxy를 생성하지 않습니다.
interfaces - JDK Dynamic Proxy를 이용한 Proxy 생성
targetClass - CGLIB를 이용한 Proxy 생성

Bar는 인터페이스 기반이 아니기 때문에 CGLIB를 이용 했습니다.
<context:component-scan base-package="example" scoped-proxy="targetClass"/>

만약 스캔된 bean들 중 @Scope의 값이 "session", "request" 일 때
scoped-proxy 속성 값이 "targetClass", "interfaces" 가 아닐경우 예외가 던져지고 초기화에 실패 합니다.

component-scan을 이용하면 method-lookup도 불가능 하므로 참조되는 bean이 web-scope가 아니고
prototype일 경우의 문제는 해결 할수 없을것 같습니다.

출처 : http://blog.daum.net/calmknight/15785980