Spring/Spring 이야기

[Spring framework] 공부를 해보자 4탄

seungdols 2016. 8. 30. 16:47

웹 어플리케이션의 컨텍스트 구성 방법

3가지 정도가 존재한다. 첫 번째는 컨텍스트 계층 구조를 만드는 것이고, 나머지 두 가지 방법은 컨텍스트를 하나만 사용하는 방법이다. 첫 번째와 세번째는 스프링 웹 기능을 사용하는 것이고, 두 번째는 스프링 웹 기술을 사용하지 않을 때 적용 가능한 방법이다.

  • 서블릿 컨텍스트와 루트 어플리케이션 컨텍스트 계층 구조
    웹 관련 빈들은 서블릿의 컨텍스트에 두고, 나머지는 루트 어플리케이션 컨텍스트에 등록한다. 루트 컨텍스트는 모든 서블릿 레벨 컨텍스트의 부모 컨텍스트가 된다.
    스프링 이외에 프레젠테이션 프레임워크 혹 AJAX 등 여러 외부 라이브러리 , 프레임워크를 사용 할 수 있다.

  • 루트 어플리케이션 컨텍스트 단일 구조
    스프링 웹은 사용 하지 않고, 서드 파티 웹 프레임워크 혹 서비스 엔진만 사용해서 프레젠테이션 계층을 만든다면, 스프링 서블릿을 둘 필요가 없다.

  • 서블릿 컨텍스트 단일구조
    스프링 웹 기술을 사용하면서 스프링 외의 프레임워크나 서비스 엔진에서 스프링의 빈을 이용 할 생각이 아니라면, 루트 어플리케이션 컨텍스트를 생략할 수도 있다.
    대신, 서블릿에서 만들어지는 컨텍스트에 모든 빈을 등록해야 한다. 이 상황에서 서블릿 안에 만들어지는 어플리케이션 컨텍스트가 부모 컨텍스트를 갖지 않기 때문에 스스로 루트 컨텍스트가 된다. 그러나, 웹 어플리케이션 레벨에 두는 공유 가능한 루트 컨텍스트와는 전혀 다르다.

웹 어플리케이션 레벨에 만들어지는 루트 웹 어플리케이션 컨텍스트를 등록 하는 가장 쉬운 방법은 서블릿의 이벤트 리스너를 이용하는 것이고, ServletContextListner를 이용한다. 스프링은 이러한 기능을 가진 리스너인 ContextLoaderListener를 제공한다. 이를 이용하고자 하면, web.xml에 리스너 선언을 넣어주기만 하면 된다.

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

ContextLoaderListener는 웹 어플리케이션이 시작 할 때, 자동으로 루트 어플리케이션 컨텍스트를 만들고 초기화 해준다. 다만, 별다른 파라미터를 지정하지 않으면, 디폴트로 설정된 값이 설정된다.

  • 어플리케이션 컨텍스트 클래스
    • XmlWeblApplicationContext
  • XML 설정파일 위치
    • /WEB-INF/applicationContext.xml
      applicationContext 파일을 기본 설정 파일로 사용한다.
      컨텍스트 클래스와 설정 파일 위치는 서블릿 컨텍스트 파라미터를 선언해서 변경 할 수 있다. ContextLoaderListener가 이용할 파라미터를 <context-param>항목 안에 넣어주면 디폴트 설정 대신 파라미터로 준 값이 적용이 된다.
ConextConfigLocation

디폴트 XML 설정 파일 위치는 파라미터를 선언해주는 것만으로도 바꿀 수 있다.

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:config/spring/context/context-*.xml</param-value>
    </context-param>

만약 파일 위치가 여러개라면, 여러 줄에 걸쳐서 넣어주거나 공백으로 분리하면 된다.

서블릿 어플리케이션 컨텍스트

스프링의 웹 기능을 지원하는 프론트 컨트롤러 서블릿은 DispatcherServlet이다.

<servlet>
        <servlet-name>spring-web</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

서블릿 컨텍스트를 위해 등록하는 프론트 컨트롤러 서블릿이며, 이 때 중요한것은 servlet-name이다. 이게 중요한 이유는 결국 서블릿 컨텍스트 파일명이 되기 때문이다. spring-web이 서블릿 이름이라면, spring-web-servlet.xml로 만들어야 하기 때문이다.

두번째로는 load-on-startup이다. 이는 서블릿 컨테이너가 등록된 서블릿을 언제 만들고 초기화할지, 그 순서는 어떻게 되는지를 지정하는 정수 값이다.

생략하거나 음수로 지정하면, 서블릿 컨테이너가 임의로 생성하고 초기화한다. 0 이상의 값을 넣으면 웹 어플리케이션이 시작 되는 시점에서 만들고 초기화 한다. 여러 개의 서블릿이 등록되어 있으면 작은 값을 가진 서블릿이 우선권을 가진다. 보통 1을 넣는다. DispatcherServlet은 서블릿의 초기화 작업 중에 스프링 컨텍스트를 생성한다. 그러므로 이 때, 컨텍스트 설정/환경에 문제가 있으면 확인이 가능하다.

IoC컨테이너가 오브젝트 생성을 어떻게 하는가?
이는 바로 BeanDefinition을 참조하여 오브젝트를 생성하는데, BeanDefinition은 XML문서, 어노테이션, Java Code를 전용 리더가 해석 한 뒤, BeanDefinition 타입의 오브젝트로 변환하고 컨테이너는 이를 참조하여 오브젝트를 생성할 수 있게 되는 것!!

Q) BeanDefinition을 만들려면 결국, 스프링 프레임워크는 XML, @, code 상 정보를 취합해야 하는데, 그럼 문자열 파싱에 대한 파싱 작업을 해야하는데, 성능상에 문제는 없는 것인지..

빈 등록 방법
  • 태그 - xml
  • XML : 네임스페이스 + 전용 태그
  • stereo type annotation + bean scanner
    @Component 어노테이션 또는 @Component를 메타 어노테이션으로 가진 어노테이션이 부여된 클래스를 빈 스캐너의 디폴트 필터는 인식하고, 해당 클래스를 빈으로 등록하게 된다. 디폴트 필터에 적용 되는 어노테이션을 스프링에서는 스테레오 타입 어노테이션이라고 부른다.
  • @Configuration 클래스의 @Bean 메소드
  • 일반 빈 클래스의 @Bean 메소드
    @Configuration이 붙지 않은 @Bean 메소드는 @Configuration 클래스의 @Bean과 미묘한 차이점이 있다.
  • 메타정보 구성
    • XML 단독
    • XML + Bean scanning
    • Bean Scanning

빈 의존 관계 설정 방법

XML : <property>, <constuctor-arg>

XML : 자동 와이어링

XML : 네임스페이스와 전용 태그

어노테이션
@Resource
@Autowired/@Inject
@Autowired/getBean()

해당 항목에서 가장 중요한 것은 표준 어노테이션과 스프링 전용 어노테이션의 혼용은 금지해야 한다는 것이다. 특히나, @Autowired를 사용할 경우 @Inject 어노테이션은 사용하지 않는 것이 더 좋다.

특히, @Autowired를 사용 할 때에는 같은 타입 빈이 여러개 인 경우 에러가 날 수 있으므로, @Qualified를 같이 혼용해야 예외 상황을 처리 할 수 있다.

포틀릿
(이 무엇인지 좀 더 문서를 확인 해야 한다. 포틀릿과 spring 간 관계도 아직 모르겠다.) 어쨌거나 포탈 서버 위에 재사용이 가능한 단위(포틀릿)을 조합하여 개발하는데 스프링에서 포틀릿에 대해서도 지원 해준다. (정확한 사용은 스프링 레퍼런스 참조)

portlet

A portlet is a Web-based component that will process requests and generate dynamic content. The end-user would essentially see a portlet as being a specialized content area within a Web page that occupies a small window in the portal page. Depending on the content nature of the Web site providing the portlet you could use this area to receive different types of information such as travel information, business news, or even local weather. The portlet provides users with the capability to customize the content, appearance and position of the portlet.
To create a portlet application, you should be a J2EE developer with a background in JavaServer Pages (JSP), JavaScript and HTML, and have a knowledge of Enterprise Java Beans.

스코프

스코프의 종류는 싱글톤, 프로토 타입 외에 요청, 세션, 글로벌 세션, 어플리케이션으로 4가지 스코프를 기본적으로 제공한다.

요청 스코프

요청 스코프 빈은 하나의 웹 요청 안에서 만들어지고 해당 요청이 끝날 때 제거 된다.
하나의 웹 요청을 처리하는 동안 참조하는 요청 스코프 빈은 항상 동일한 오브젝트를 보장한다.

세션 스코프, 글로벌 세션 스코프

HTTP 세션은 사용자별로 만들어지고, 브라우저를 닫거나 세션 만료 될 때까지는 유지 되어 로그인 정보나 사용자별 선택 옵션을 저장 하기에 유용하다.

참고로, HttpSession 오브젝트를 다른 계층에 넘겨서 사용하는 것은 좋지 않다. 왜냐하면, 웹 환경에 종속적이기 때문에 문제가 될 요소가 많다.
글로벌 세션 스코프는 포틀릿에만 존재하는 글로벌 세션에 저장되는 빈이다.

어플리케이션 스코프

서블릿 컨텍스트에 저장되는 빈 오브젝트다. 어플리케이션 스코프는 컨텍스트가 존재하는 동안 유지 되는 싱글콘 스코프와 비슷한 존재 범위를 갖는다. 드물지만, 웹 어플리케이션과 어플리케이션 존재 범위 차이가 생기기 때문에 어플리케이션 스코프를 사용한다.

스코프 빈 사용

사용하고자 하는 빈에 @Scope(“session”)등을 넣어주면 된다. 해당 키워드처럼 하면 세션 스코프로 생성 된다.

또한, 임의의 스코프를 만들어 사용할 수도 있다.

초기화 메소드

빈 오브젝트 생성 후 DI까지 된 이후에 빈 초기화 메소드가 실행 된다.

  1. 초기화 콜백 인터페이스
  2. init-method 지정
  3. @PostConstruct
  4. @Bean(init-method)

이 중에서 3번인 @PostConstruct를 권장한다고 한다.

제거 메소드
  1. 제거 콜백 인터페이스
    DisposableBean 인터페이스의 destroy() 메소드를 구현한다.
  2. destroy-method
    태그에 destroy-method를 넣어 제거 메소드를 지정한다.
  3. @PreDestroy
    컨테이너가 종료 될 때, 실행될 메소드에 @PreDrestroy를 붙이면 된다.
  4. @Bean(destroyMethod)
    @Bean 어노테이션의 destroyMethod 엘리먼트를 이용해 제거 메소드를 지정 할 수 있다.

팩토리 빈

빈 오브젝트를 만들어 준다. 다만, 자신은 빈 오브젝트로 사용되지 않는다.

  1. FactoryBean 인터페이스
  2. 스태틱 팩토리 메소드
  3. 인스턴스 팩토리 메소드
    팩토리 빈이 바로 팩토리 빈 오브젝트의 메소드를 이용해 빈 오브젝트를 생성 하는 대표 방법이다.
  4. @Bean 메소드

jsp 파일을 왜 하필 WEB-INF 폴더 하위에 두는지 궁금했었다. 이제서야 해당 궁금증을 풀 수 있었다. 이유는 현 웹 개발은 MVC 모델 2방식이며, 이는 사용자가 접근함으로써 JSP파일을 실행하는 것이 아니라 만에 하나라도 접근을 하게 될 경우를 방지하면서 내부적인 스프링 로직에 의해 실행 될 수 있도록 하기 위함이다.

컨트롤러

서블릿이 넘겨주는 HTTP요청은 HttpServletRequest 오브젝트에 담겨져 있다. 컨트롤러가 이를 이용해 사용자의 요청을 파악하려면 클라이언트 호스트, 포트, URI,쿼리 스트링, 폼 파라미터, 쿠키, 헤더, 세션을 비롯해서 서블릿 컨테이너가 요청 어트리뷰트로 전달해주는 것까지 매우 다양한 정보를 참고하면 된다.

또한, 요청에 대한 추출만 하는 것이 아닌 바른 요청인지, 해당 요청에 대한 검증을 또한 하는 것이 컨트롤러의 일이다.

핸들러 매핑

HTTP요청 정보를 이용해 이 요청을 처리할 핸들러 객체인 컨트롤러를 찾아주는 기능을 가진 DispatcherServlet의 전략.

보통 다섯가지가 있는데 이에 대해 알아보자.

BeanNameUrlHandlerMapping
디폴트 핸들러 매핑의 하나다. 빈의 이름에 들어 있는 Url을 HTTP 요청의 URL과 비교해서 일치하는 빈을 찾아준다. 가장 직관적이고 가장 사용하기 쉽다. 특히 URL에 ANT 패턴( * ?)등과 같은 와일드 카드를 사용 하는 패턴을 넣을 수 있다.

<bean name = "/s*" class="sp..." />

이렇게 되어 있는 경우 /s로 시작하는 매핑을 찾아 준다. /**일 경우 하나 이상의 경로를 지정할 때 사용한다.

<bean name = "/root/**/sub" class="sp..." />

단, 사용하기 편하고, 빠르게 URL을 매핑할 수는 있으나, 컨트롤러 수가 많아질 수록 URL정보가 XML 빈선언이나 클래스의 어노테이션등에 분산 되어 관리하기 어렵다.

ControllerBeanNameHandlerMapping

빈의 아이디나 빈의 이름을 이용해 매핑해준다. 단 bean의 id에 문자 사용의 제약이 있어 URL 시작 기호인 /는 사용할 수 없다. 단, 나중에 ControllerBeanNameHandlerMapping이 자동으로 /를 붙여준다.

<bean id="hello" class="sp..." />

위 처럼 XML을 사용하던가, 스테레오 타입 어노테이션을 이용할 수도 있다.

@Component("hello")
public class MyController implements Controller{

}

대신 이 핸들러 매핑은 빈 이름 앞 뒤에 prefix, suffix를 지정할 수 있는 장점이 있다.

<bean class="org.springframework.web.servlet.mvc.support.ControllerBeanNameHandlerMapping"> 
    <property name ="urlPrefix" value="/app/sub" />
</bean>

디폴트 핸들러 매핑전략이 아니므로 전략 빈으로 등록을 해주어야 하며, 이렇게 등록하면 디폴트 매핑 전략은 모두 무시 된다.

ControllerClassNameHandlerMapping

빈 이름 대신 클래스 이름을 URL에 매핑해 주는 핸들러 매핑 클래스다. 즉, 다음과 같은 컨트롤러 클래스는 /hello에 매핑 된다.

public class HelloController implements Controller{...}

보통은 클래스 명이 모두 URL에 매핑 되지만, Controller인 경우 해당 Controller를 뺀 나머지 이름으로 매핑이 된다.

SimpleUrlHandlerMapping

해당 매핑 전략은 URL과 매핑 정보를 한 곳에 모아 놓을 수 있다.

<baen class = "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name = "mappings">
        <props>
            <prop key = "/hello">helloController</prop>
            ? , * , **와 같은 와일드 카드를 사용 할 수 있다. 
         </props>
    </property>
</bean>

<bean id="helloController" .... />

DefaultAnnotationHandlerMapping

@RequestMapping이라는 어노테이션을 컨트롤러 클래스나 메소드에 직접 부여하고, 이를 이용해 매핑하는 전략이다. 또한, HTTP 메소드, HTTP 헤더 정보까지 매핑에 활용 가능하다.

핸들러 인터셉터

서블릿 필터와 유사하며, 디스패처 서블릿이 동작하기 전/후에 요청 응답 오브젝트를 가공 할 수 있다. 또한, 디스패처 서블릿으로부터 매핑 요청을 받으면, HandlerExecutionChain을 리턴하는데 이는 하나 이상의 핸들러 인터셉터를 거쳐 컨트롤러를 실행하도록 한다. 또한, HttpServletRequest / HttpServletResponse뿐 아니라 실행 될 컨트롤러 빈 오브젝트, 컨트롤러가 돌려주는 ModelAndView, 발생한 예외등을 제공 받을 수 있기 때문에 서블릿 필터보다 더 강략한 기능을 가지고 있다.

HandlerInterceptor

핸들러 인터셉터 인터페이스를 구현해서 만드는데 3개의 메소드가 정의 되어 있다.

preHandle, postHandle, afterCompletion이 존재하며, preHandle을 false로 리턴하면 나머지, postHandle 및 남은 인터셉터들은 무시된다.

preHandle()은 인터셉터가 등록된 순서대로 실행되며, postHandle(), afterCompletion()은 등록 된 반대 순서로 실행 된다.

컨트롤러가 작업을 마친 뒤, 뷰 정보를 ModelAndView 타입 오브젝트에 담아서 DispatcherServlet에 돌려주는 방법이 2가지가 있다.

  1. View 타입의 오브젝트로 반환
  2. 뷰 이름을 리턴

뷰 이름을 돌려줄 때는 실제 사용할 뷰를 결정해주는 뷰 리졸버가 필요하다.

뷰 리졸버

핸들러 매핑이 URL로부터 컨트롤러를 찾아주는 것처럼, 뷰 이름으로 사용할 뷰 객체를 찾아 주는 역할을 한다.

ViewResolver 인터페이스를 구현하여 만들어진다. 또한, 디스패처 서블릿의 디폴트 뷰 리졸버는 InternalResourceViewResolver이다.

InternalResourceViewResolver는 JSP를 사용할 때 주로 사용하는 리졸버이며, 디폴트 상태보다 명시적으로 뷰 리졸버를 빈으로 등록 했을 때, prefix, suffix를 지정 할 수 있으므로 명시적으로 뷰 리졸버를 등록해주도록 한다.

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

핸들러 예외 리졸버

핸들러 예외 리졸버는 컨트롤러 작업 중 발생한 예외를 어떻게 처리할지 결정하는 전략이다.

총 4개의 핸들러 예외 리졸버 구현 전략을 제공하고 있다. 그 중 3개는 디폴트로 등록 되도록 설정 되어 있다.

AnnotationMethodHandlerExceptionResolver
예외가 발생한 컨트롤러 내의 메소드 중에서 @ExceptionHandler 어노테이션이 붙은 메소드를 찾아 예외처리를 맡겨주는 핸들러 예외 리졸버이다.

ResponseStatusExceptionResolver
예외를 특정 HTTP 응답 상태 코드로 전환해주는 것이다.

@ResponseStatus(value = HttpStatus.SERVICE_UNVAILABLE, reason = "서비스 일시 중지")
public class NotInServiceException extends RuntimeException{

}

위와 같이 하면, 해당 응답 코드를 클라이언트에 전달하고, 사용자는 503 에러와 함께 서비스 일시 중지라는 메시지를 볼 수 있다.

DefaultHandlerExceptionResolver
스프링에서 내부저긍로 발생하는 주요 예외를 처리해주는 표준 예외처리 로직을 담당하고 있다.
SimpleMappingExceptionResolver
web.xml의 와 비슷하게 예외를 처리할 뷰를 지정할 수 있게 해준다.

멀티 파트 리졸버

파일 업로드를 하려면 멀티 파트 처리를 할 수 있도록 돕는 멀티 파트 리졸버를 등록 해주어야 한다.

<bean id = "multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="100000" />
</bean>

위와 같이 빈을 등록 해주어야 하며, 사이즈 지정을 위해 프로퍼티로 설정해야 한다.

지난번에 Mybatis 연동할 때 추가했던 라이브러리 중 DBCP 관련 글은 D2 - Commons DBCP를 참조하면 된다.


DI는 결론적으로 전략 패턴의 장점을 일반적으로 사용 할 수 있도록 만든 것이고, 가장 중요한 개념은 제 3자의 도움을 통해 두 오브젝트 사이의 유연한 관계가 설정 되도록 만든다.

동일성 ==

JVM에 정의된 개념. 두 객체는 같은 메모리 위치를 가르키고 있다면 동일한 객체이다.

동등성 .equals(Object object)

서로 다른 두 객체가 같은 값을 갖는것을 말한다.
참고로, 스프링의 정수는 DI와 함께 패턴인 것 같다. 기본이 되는 디자인 패턴에 대한 지식을 먼저 학습하는 것이 좋을 듯 하다. 특히 DI에 대해서는 스프링의 원리에도 녹아 있으며, 사용할때에도 DI를 직접 적용하는 방법도 필요로 하다고 하니 중요한 것 같다. 특히나, 기본인 되는 동등성(Equality)과 동일성(Identity)는 헷갈리지 말자.
어플리케이션 컨텍스트는 루트를 가질 수 있어 계층 구조로 표현이 가능하다. 즉, parent를 가질 수 있는데, child 어플리케이션 컨텍스트에 빈이 없으면, parent에서 찾는다.


또한, 부모와 자식 컨텍스트에 동일한 빈이 존재할 경우에는 자식 컨텍스트가 우선 된다.
스프링은 Front Controller Pattern을 사용하는데, 이는 J2EE에서 사용하던 패턴이었다. 이 패턴으로 만들어 진 것이 Spring의 ㅇDispatcherServlet이다.

Dispatchservletn이 모든 요청에 대해 요청을 받고 나서, 필요한 일들(매핑, 핸들러, ViewResolver 등)에 대해 각 처리기에 위임을 하고, 처리를 한다. 즉, 중앙 집중식의 요청/응답을 하는 컨트롤러가 DispatcherServlet이다.


반응형