Spring REST 로깅

  • Spring Web RestTemplate을 이용한 HTTP 통신시 Interceptor를 활용하여 요청과 응답 로그를 남긴다.
  • 주의할 점은, ResponseEntity의 Body는 Stream 이므로 로깅 인터셉터에서 Body Stream을 읽어 소비가되면 실제 비즈니스 로직에서는 Body가 없어진다.
  • 이를 해결하기 위해 RestTemplate Bean 설정에서 requestFactory에 BufferingClientHttpRequestFactory를 세팅해줘야 한다.

설정

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    ...

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder
                // 로깅 인터셉터에서 Stream을 소비하므로 BufferingClientHttpRequestFactory 을 꼭 써야한다.
                .requestFactory(() -> new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
                // 타임아웃 설정 (ms 단위)
                .setConnectTimeout(30000)
                .setReadTimeout(30000)
                // UTF-8 인코딩으로 메시지 컨버터 추가
                .additionalMessageConverters(new StringHttpMessageConverter(Charset.forName("UTF-8")))
                // 로깅 인터셉터 설정
                .additionalInterceptors(new RestTemplateLoggingRequestInterceptor())
                .build();
    }

    ...
}

RestTemplateLoggingInterceptor

/**
 * Spring RestTemplate 로깅 인터셉터
 *
 * @author Hoonmaro
 */
@Slf4j
public class RestTemplateLoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    /**
     * <pre>
     * RestTemplate 로깅 Interceptor
     *
     * <pre>
     *
     * @param request HttpRequest
     * @param body Request Body
     * @param execution ClientHttpRequestExecution
     * @throws IOException
     */
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {

        // request log
        URI uri = request.getURI();
        traceRequest(request, body);

        // execute
        ClientHttpResponse response = execution.execute(request, body);

        // response log
        traceResponse(response, uri);
        return response;
    }

    /**
     * <pre>
     * RestTemplate Request 로깅
     *
     * <pre>
     * @param request HttpRequest
     * @param body Request Body
     */
    private void traceRequest(HttpRequest request, byte[] body) {
        StringBuilder reqLog = new StringBuilder();
        reqLog.append("[REQUEST] ")
        .append("Uri : ").append(request.getURI())
        .append(", Method : ").append(request.getMethod())
        .append(", Request Body : ").append(new String(body, StandardCharsets.UTF_8));
        log.info(reqLog.toString());
    }

    /**
     * <pre>
     * RestTemplate Response 로깅
     *
     * <pre>
     * @param response ClientHttpResponse
     * @throws IOException
     */
    private void traceResponse(ClientHttpResponse response, URI uri) throws IOException {
        StringBuilder resLog = new StringBuilder();
        resLog.append("[RESPONSE] ")
        .append("Uri : ").append(uri)
        .append(", Status code : ").append(response.getStatusCode())
        .append(", Response Body : ").append(StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8));
        log.info(resLog.toString());
    }
}

RestTemplate

RestTemplate은 HTTP 클라이언트 라이브러리를 통해 높은 수준의 API를 제공한다. REST 엔드포인트를 코드 한줄로 호출하기 쉽게 해준다. 오버로드된 메소드들은 다음과 같다.

 

RestTemplate methods

Method group Description
getForObject GET method를 통해 representation을 조회
getForEntity GET method를 통해 ResponseEntity를 조회 (status, headers, body)
headForHeaders HEAD method를 통해 리소스의 모든 헤더들을 조회
postForLocation POST method를 통해 새로운 리소스를 생성하고 응답에 Location 헤더를 리턴
postForObject POST method를 통해 새로운 리소스를 생성하고 응답에 representation을 리턴
postForEntity POST method를 통해 새로운 리소스를 생성하고 응답에 representation을 리턴
put PUT method를 통해 리소스를 새로 생성하거나 수정
patchForObject PATCH method를 통해 리소스를 수정하고 응답의 representation을 리턴.
주의할 것은 JDK HttpURLConnection은 PATCH 를 지원하지 않고, Apache HttpComponents와 다른 것들은 지원함
delete DELETE method를 통해 특정 URI의 리소스를 삭제
optionsForAllow ALLOW method를 통해 리소스가 허용하는 HTTP method들을 조회
exchange 위의 method들 보다 더 일반화되고, 덜 선택적인 버전으로 필요한 경우 추가적인 유연성을 제공함
HTTP 메서드, URL, 헤더 및 본문을 포함한 RequestEntity를 입력받아 ResponseEntity를 리턴
이러한 방법을 사용하면 Class 대신 ParameterizedTypeReference를 사용하여 제네릭 타입 응답을 지정할 수 있음
execute 요청을 수행하는 가장 일반화된 방법으로, 콜백 인터페이스를 통한 요청 준비 및 응답 추출에 대한 완벽한 제어가 가능

 

초기화

기본 생성자는 java.net.HttpURLConnection 라이브러리를 사용한다. ClientHttpRequestFactory를 사용하여 다른 HTTP 라이브러리로 바꿀 수 있다. 스프링은 Apache HttpComponents, Netty, OkHttp를 지원한다.

  • 예)
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

ClientHttpRequestFactory는 기본 HTTP 클라이언트 라이브러리(예: 자격 증명, 연결 풀링 등)에 대한 구성 옵션을 제공한다. 주의할 점은 java.net 패키지의 HTTP 구현체를 사용할 경우 401 상태를 가진 응답 객체에 접근할 때 익셉션이 발생할 수 있으므로, 이럴 경우 다른 HTTP 라이브러리 사용해야 한다.

 

URIs

대부분의 RestTemplate 메소드는 String vararg 또는 Map<String, String>을 통해 URI 템블릿과 URI 템플릿 변수를 허용한다.

/* String vararg */
String result = restTemplate.getForObject(
        "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");

/* Map<String, String> */
Map<String, String> vars = Collections.singletonMap("hotel", "42");

String result = restTemplate.getForObject(
        "http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

URI 템플릿은 자동으로 인코딩된다.

restTemplate.getForObject("http://example.com/hotel list", String.class);

// 요청 url: "http://example.com/hotel%20list"

RestTemplateuriTemplateHandler 속성을 사용하여 URI 인코딩 방법을 지정할 수 있다 또는 java.net.URI를 사용하여 이를 사용하는 RestTemplate 메소드에 인자로 전달할 수 있다.

 

Headers

exhcnage() 메소드를 사용하여 요청 헤더를 지정할 수 있다.

String uriTemplate = "http://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);

RequestEntity<Void> requestEntity = RequestEntity.get(uri)
        .header(("MyRequestHeader", "MyValue")
        .build();

ResponseEntity<String> response = template.exchange(requestEntity, String.class);

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();

ResponseEntity를 반환하는 RestTemplate 의 여러 메소드를 통해 응답 헤더를 얻을 수 있다.

 

Body

RestTemplate 메서드에 전달되고 반환되는 객체는 HttpMessageConverter의 도움으로 raw content로 변환된다.

POST 메서드에서 입력된 객체는 요청 body에 직렬화된다.

URI location = template.postForLocation("http://example.com/people", person);

요청의 Content-Type 헤더를 명시적으로 설정할 필요는 없다. 대부분의 경우 소스 객체 타입에 따라 호환되는 메시지 컨버터를 찾을 수 있고, 선택한 메시지 컨버터가 적절히 content-type을 설정한다. 필요한 경우 exchange 메서드를 사용하여 Content-Type 요청 헤더를 명시적으로 제공할 수 있으며, 이는 선택된 메시지 컨버터에 영향을 미칠 수 있다.

GET 메서드에서는 응답의 body가 출력 객체로 역직렬화된다.

Person person = restTemplate.getForObject("http://example.com/people/{id}", Person.class, 42);

요청의 Accept 헤더는 명시적으로 설정할 필요가 없다. 대부분의 경우 Accept 헤더를 예상 응답 유형에 따라 호환되는 메시지 컨버터를 찾을 수 있다. 이 컨버터는 Accept 헤더를 채우는데 도움이 된다. 필요한 경우 exchange 메서드를 사용하여 Accept헤더를 명시적으로 제공할 수 있다.

기본적으로 RestTemplate은 선택적인 변환 라이브러리가 있는지 확인하는데 도움이 되는 클래스패쓰 검사에 따라 모든 내장 메시지 컨버터들을 등록한다. 또, 명시적으로 메시지 컨버터를 설정할 수 있다.

 

Message Conversion

spring-web 모듈은 InputStreamOutputStream을 통해 HTTP 요청 및 응답의 body를 읽고 쓰는 역할을 맡은 HttpMessageConverter를 포함한다. HttpMessageConverter는 클라이언트 측(예, RestTemplate)과 서버 측 (예, Spring MVC REST controllers)에서 사용된다.

주요 미디어(MIME) 유형에 대한 구체적인 구현은 프레임워크에서 제공되며 기본적으로 클라이언트 측 RestTemplate 및 서버측 RequestMethodHandlerAdapter에 등록된다. (메시지 구성 참조)

HttpMessageConverter의 구현은 다음 섹션에 기술되어있다. 모든 컨버터들이 기본 미디어 타입을 사용하지만 supportedMediaTypes 빈 프로퍼티 설정으로 오버라이드 할 수 있다.

 

HttpMessageConverter 구현체 목록

MessageConverter Description
StringHttpMessageConverter HTTP 요청 및 응답으로부터 문자열(String)을 읽고 쓸 수 있는 구현체
기본적으로 모든 텍스트 미디어 타입들 (text/*)을 지원하며, text/plain Content-Type으로 작성한다.
FormHttpMessageConverter HTTP 요청 및 응답으로부터 form 데이터를 읽고 쓸 수 있는 구현체
기본적으로 application/x-www-form-urlencoded 미디어 타입을 지원한다.
Form 데이터는 MultiValueMap<String, String>읽고 작성된다.
ByteArrayHttpMessageConverter HTTP 요청 및 응답으로부터 바이트 배열을 읽고 쓸 수 있는 구현체
기본적으로 모든 미디어 타입 (*/*)을 지원하며, application/octet-stream Content-Type으로 작성한다.
이 컨버터는 supportedMediaTypes 속성을 설정하고 getContentType(byte[])를 오버라이드하여 재정의할 수 있다
MarshallingHttpMessageConverter org.springframework.oxm 패키지의 스프링의 마샬러(Marshaller)와 언마샬러(Unmarshaller) 추상화를 사용하여 XML을 읽고 쓸 수 있는 구현체
이 컨버터는 사용하기 전에 마샬러와 언마샬러를 필요로 한다.
이들은 생성자 또는 빈 프로퍼티를 통해 주입될 수 있다.
기본적으로 text/xmlapplication/xml을 지원한다
MappingJackson2HttpMessageConverter Jackson 라이브러리의 ObjectMapper를 사용하여 JSON을 읽고 쓸 수 있는 구현체
JSON 매핑은 필요에 따라 Jackson이 제공하는 어노테이션을 통해 커스터마이징 될 수 있다
추가 제어가 필요할 경우 특히, 특정 유형에 대해 커스텀 JSON 직렬화/역직렬화를 제공해야 하는 경우 ObjectMapper 속성을 통해 커스텀 ObjectMapper를 주입할 수 있다.
기본적으로 application/json을 지원한다
MappingJackson2XmlHttpMessageConverter Jackson XML의 XmlMapper를 사용하여 XML을 읽고 쓸 수 있는 구현체
Jackson이 제공하는 어노테이션을 또는 JAXB를 사용하여 필요에 따라 XML 매핑은 커스터마이징 될 수 있다
추가 제어가 필요한 경우, 특히 특정 유형에 대해 커스텀 XML 직렬화/역직렬화를 제공해야하는 경우 ObjectMapper 속성을 통해 커스텀 XmlMapper를 주입할 수 있다
기본적으로 application/xml을 지원한다
SourceHttpMessageConverter HTTP 요청 및 응답으로부터 javax.xml.transform.Source를 읽고 쓸 수 있는 구현체
DOMSource, SAXSource, StreamSource만 지원된다.
기본적으로 text/xmlapplication/xml를 지원한다
BufferedImageHttpMessageConverter HTTP 요청 및 응답으로부터 java.awt.image.BufferedImage를 읽고 쓸 수 있는 구현체
Java I/O API에서 지원하는 미디어 타입을 읽고 쓴다

 

Jackson JSON Views

객체 속성의 일부만 직렬화하도록 Jackson JSON View를 지정할 수 있다.

  • 예)
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);

RequestEntity<MappingJacksonValue> requestEntity =
    RequestEntity.post(new URI("http://example.com/user")).body(value);

ResponseEntity<String> response = template.exchange(requestEntity, String.class);

 

Multipart

multipart 데이터를 전송하기 위해 multipart 내용을 대표하는 Objects 또는 내용과 헤더를 대표하는 HttpEntity를 값으로 가지는 MultiValueMap<String, ?>를 제공할 필요가 있다. MultipartBodyBuilder는 multipart 요청을 만들어주는 편리한 API를 제공한다.

MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));

MultiValueMap<String, HttpEntity<?>> parts = builder.build();

대부분의 경우 각 part에 Content-Type를 지정할 필요가 없다. Content-Type은 직렬화 하도록 선택한 HttpMessageConverter 또는 파일 확장자에 기반한 Resource의 경우에 자동으로 결정된다. 필요한 경우 오버로드된 builder 메소드를 통해 각 part에 사용할 MediaType을 명시적으로 제공할 수 있다.

MultiValueMap이 준비되면, RestTemplate에 인자를 넘겨줄 수 있다.

MultipartBodyBuilder builder = ...;
template.postForObject("http://example.com/upload", builder.build(), Void.class);

MultiValueMap이 일반적인 form 데이터를 나타낼수 있는(예: application/x-www-form-urlencoded) String이 아닌 값을 가질 경우, Content-Typemultipart/form-data로 지정할 필요 없다. 이는 ㅇ항상 HttpEntity 래퍼를 보장하는 MultipartBodyBuilder를 사용하는 경우에 해당된다.

 

참고

 

스프링 프레임워크 정적 자원 버전 관리




HTTP 캐싱

  • 네트워크를 통해 자원을 가져오는 작업은 느리고 비용이 발생한다. 특히, 크기가 크면 그만큼 브라우저가 화면을 표시하는데 오래 걸려 사용자 경험이 나빠진다.
  • 가져온 자원을 캐시했다가 재활용하는 것은 성능 최적화에 중요하다.
  • 모든 브라우저는 HTTP 캐시 구현 기능이 포함되어 있다.
  • 개발자는 각 서버 응답이 브라우저에 응답을 캐시할 수 있는 시점과 그 기간을 지시하게 위한 HTTP Header를 올바르게 제공해야한다.

정적 자원 버전 관리

  • 브라우저는 한 번 방문한 사이트의 정적 자원은 캐시가 되어 만료 될 때까지 사용한다.
  • 이때문에 정적 자원이 수정된 후에 사이트를 방문한 사용자는 최신 정적 자원을 사용하는 반면, 이전에 방문했던 사용자는 과거 정적 자원을 사용하게 되어 이슈가 발생할 수 있다.
  • 예) 이미지가 다르게 보임, 수정된 자바스크립트가 실행이 안되거나 동작이 달라짐
  • 따라서 정적 자원의 버전관리를 통해 수정된 정적 자원일 경우 새로운 버전이 제공되어 사용자들이 서버에서 최신 버전 정적 자원을 받을 수 있도록 해야한다.

정적 자원 캐시 만료되는 경우

  • 사용자 직접 삭제
  • max-age 또는 expires로 정해진 기한이 지난 경우
  • 정적 자원의 URL을 변경한 경우

스프링 프레임워크에서 정적 자원 버전 관리

  • 스프링 프레임워크는 4.1 버전 이후부터 WebMvcConfigureraddResourceHandlers 설정 메서드에서 버전 관리 핸들러를 리졸버를 등록하여 관리할 수 있다.
  • ContentVersionStrategy 통해서 브라우저가 요청한 자원 각 파일의 내용에 따라 MD5 해쉬된 문자열이 요청한 자원 파일명 뒤에 버전처럼 붙는다.
  • 수정된 정적 자원의 경우 파일 내용이 바뀌므로 서버에서 응답한 요청한 자원 파일명에 붙은 MD5 해쉬 문자열이 사용자의 브라우저가 캐시해서 가지고 있던 것과 다르기 때문에 새로 내려받게 된다.

WebMvcConfig 설정

@Configuration
public class MvcConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**", "/webjars/**")
                .addResourceLocations("/resources/", "classpath:/META-INF/resources/webjars/")
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
                .addResolver(new WebJarsResourceResolver());
	}

}

정적 자원 접근 처리 설정

  • 컨트롤러 어드바이스 설정을 ResourceUrlProvider를 모든 응답에 urls라는 이름으로 제공한다.
  • 템플릿에서 urls로 접근이 가능하다.
@ControllerAdvice
public class ResourceUrlAdvice {

    @Autowired
    private ResourceUrlProvider resourceUrlProvider;

    @ModelAttribute("urls")
    public ResourceUrlProvider urls() {
        return this.resourceUrlProvider;
    }
}

JSP에서 사용

<!-- CSS -->
<link rel="stylesheet" href="${urls.getForLookupPath('/resources/sss/style.css')}" />

<!-- JavsScript -->
<script src="${urls.getForLookupPath('/resources/js/common/common.js')}"></script>
<script src="${urls.getForLookupPath('/resources/js/main.js')}"></script>

예시

이전 버전


변경된 버전


흐름



생각해볼 점

  • 하나의 HTTP 요청에 필요한 정적 자원을 하나씩 검사하는 부분이 추가 되므로 극악한 상황의 네트워크, 서버 환경에서는 성능에 악영향을 줄 수도 있을 것 같다.
  • 하지만 일반적으로 문제는 없고, 오히려 버전 관리가 안되서 사용자가 과거 버전의 자원을 가진 채로 사이트가 운영됨으로 발생하는 이슈에 대한 비용이 더 클 것 같다.

참고


스프링의 소개

스프링이란?

  • 자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크
  • EJB(Enterprise Java Beans)의 단점을 보완한 프레임 워크
  • DI(Dependency Injection, 의존성 주입)과 AOP(Aspect-Oriented Programing, 관점 지향 프로그래밍)이 지원되는 경량 컨테이너 & 프레임워크


  • 자바 플랫픔으로서 자바 애플리케이션을 개발하는데 필요한 하부 구조를 포괄적으로 제공한다.
  • 스프링이 하부 구조를 처리하므로 개발자는 애플리케이션 개발에 집중할 수 있다.

경량화

  • 스프링 자체가 가볍거나 작은 규모의 코드로 이루어진 것은 아니다.
  • 오히려 스프링은 20여개의 모듈로 세분화되고 복잡하고 방대한 코드를 가진 프레임워크이다.
  • 경량화가 특징인 이유는 기존 자바 엔터프라이즈 기술의 불필요한 복잡함에 반대되는 개념에서 시작되었다.
  • 주류 기술이었던 EJB는 고가의 무거운 자바 서버(WAS)가 필요했고, 다루기 힘든 설정파일 구조, 패키징, 불편한 배포 등이 단점이었다.
  • 반면, 스프링은 톰캣과 같은 단순한 서버환경에서도 동작하며, 단순한 개발환경으로도 엔터프라이즈 애플리케이션 개발하는데 충분하다.
  • 또 EJB 등의 기존 프레임워크에서 만들어진 코드에 비해 코드량이 적고 단순하기도 하다.
  • 즉, 기존에 비해 빠르고 간편하게 애플리케이션을 개발할 수 있어 생산성이 뛰어난 프레임워크다.

스프링 기능

  • 경량 컨테이너로서 자바 객체를 직접 관리
    • 각각의 객체 생성, 소멸과 같은 라이프 사이클을 관리하며 스프링으로부터 필요한 객체를 얻어올 수 있다.
  • POJO(Plain Old Java Object) 방식의 프레임워크
    • 일반적인 J2EE 프레임워크에 비해 구현을 위해 특정한 인터페이스를 구현하거나 상속을 받을 필요가 없어 기존에 존재하는 라이브러리 등을 지원하기에 용이하고 객체가 가볍다.
  • 제어 반전(또는 제어의 역전)(IoC: Inversion of Control)을 지원
    • 컨트롤의 제어권이 사용자가 아니라 프레임워크에 있어서 필요에 따라 스프링에서 사용자의 코드를 호출한다.
  • 의존성 주입(DI: Dependency Injection)을 지원
    • 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 서로 연결시켜준다.
  • 관점 지향 프로그래밍(AOP: Aspect-Oriented Programming)을 지원
    • 트랙잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 해당 기능을 분리하여 관리할 수 있다.
  • 확장성이 높음
    • 스프링 프레임워크에 통합하기 위해 간단하게 기존 라이브러리를 감싸는 정도로 스프링에서 사용이 가능하다.
    • 수많은 라이브러리가 이미 스프링에서 지원되고 있고 스프링에서 사용되는 라이브러리를 별도로 분리하기도 용이하다.
  • 다른 프레임워크들의 통합

스프링의 구성

  • Spring Core
    • Core 컨테이너 기능
    • Spring 프레임워크의 근간이 되는 IoC (또는 DI) 기능을 지원하는 영역
    • BeanFactory를 기반으로 Bean 클래스들을 제어할 수 있는 기능을 지원한다.
  • Spring Context
    • Spring Core에서 지원하는 기능외에 추가적인 기능들과 좀 더 쉬운 개발이 가능하도록 지원한다.
    • 또, JNDI, EJB 등을 위한 Adaptor들을 포함하고 있다.
  • AOP
    • Spring 프레임워크에 AOP를 지원하는 기능이다.
  • DAO
    • JDBC 기반하의 DAO 개발을 좀 더 쉽고, 일관된 방법으로 개발하는 것이 가능하도록 지원한다.
  • ORM
    • Object-Relational Mapping 프레임워크인 Hibernate, MyBatis, JDO와의 결합을 지원하기 위한 기능이다.
  • Spring Web
    • Web Application 개발에 필요한 Web Application Context와 MultipartRequest 등의 기능을 지원한다.
  • Portlet
    • 포탈 서버에서 돌아가는 독립된 웹 애플리케이션 원격 지원 기능

스프링 창시자

  • 스프링의 창시자인 로드 존슨(Rod Johnson)
    • 2003년 『Expert One-on-One J2EE Design and Development』책을 출간하여 JE22 애플리케이션 설계와 독창적인 개발 전략을 다루었다.
    • “항상 프레임워크 기반으로 접근하라”를 강조하였다.

오픈소스

  • 스프링은 아파치 라이선스 2.0(Apache License Ver 2.0)을 따른다.
    • 영리 목적 및 비공개 프로젝트에 자유롭게 이용가능하다.
    • 스프링을 사용하고 원 저작자를 밝히고 라이선스 정보를 포함시키는 등의 의무사항을 지켜야 한다.
  • 오픈소스는 무료로 이용할 수 있고 소스코드가 모두 공개되어 수많은 다양한 환경의 개발자들이 사용하므로 테스트에 용이하고, 빠른 피드백을 받을 수 있는 장점이 있다.
  • 반면, 오픈소스 프로젝트 팀의 사정 상 개발이 여의치 않을 경우 프로젝트가 중단에 드랍되거나 버그 수정에 많은 시간이 소요될 수도 있다.
  • 특히 스프링은 애플리케이션 프레임워크로 그 규모가 일반 오픈 소스 프로젝트에 비해 크고 엔터프라이즈 개발자들에게는 중요한 프레임워크이므로 오픈 소스의 문제점을 안고 가기에는 부정적이었다.
  • 그래서 로드 존슨과 스프링 오픈소스화의 대표적 공로자인 유겐 흴러(Juergen Hoeller)와 자바 엔터프라이즈에서 최상급 개발자들이 스프링 소스(SpringSource)를 만들어 스프링 프레임워크의 실질적인 개발을 책임지고 있다.
  • 스프링 소스는 2009년 세계적인 IT업체인 VMware가 인수하여 더욱 안정적인 환경에서 개발되고 있다.


Application Context

  • 전체 계층구조에서 최상단에 위치한 컨텍스트
  • 서로 다른 서블릿 컨텍스트에서 공유해야하는 Bean들을 등록해놓고 사용할 수 있다.
  • 웹 애플리케이션 전체에 적용 가능한 프로퍼티, DB 연결, 로깅 기능 등에 이용한다.
  • Servlet Context에 등록된 Bean은 이용할 수 없다.
  • Servlet Context에 동일한 Bean이 있을 경우 Servlet Context Bean이 우선된다.
  • 하나의 컨텍스트에 정의된 AOP 설정은 다른 컨텍스트의 Bean에는 영향을 미치지 않는다.
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:egovframework/spring/com/applicationContext-*.xml</param-value>
  </context-param>

Servlet Context

  • 서블릿에서만 이용되는 컨텍스트
  • 다른 서블릿과 공유하기 위한 Bean들은 Application Context에 등록해놓고 사용해야 한다.
  • DispatcherServlet은 자신만의 컨텍스트를 생성, 초기화하고 동시에 Application Context를 찾아서 자신의 부모 컨텍스트로 사용한다.
<servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/config/egovframework/springmvc/*.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

IoC 컨테이너 구성방법

  1. Application Context만 이용
  2. Servlet Context만 이용
  3. Application ContextServlet Context 하나씩 이용
  4. Application ContextServlet Context여러 개 이용
  5. 여러 Application ContextServlet Context 하나 이용
    • Application Context 설정을 목적에 맞게 분류 (Property, Validation, sqlMap, datasource, aop 등)

Component Scan 사용시 컨텍스트 설정 방법

  • Application Context에 Service, Repository, Servlet Context에 Controller만 등록해야한다.
  • Application Context 설정

    • Service와 Repository는 Include 하고 Controller는 Exclude 한다.
      <context:component-scan base-package="egovframework">
          <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
          <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
      </context:component-scan>
  • Servlet Context 설정

    • Service와 Repository는 Exclude 하고 Controller는 Include 한다.
      <context:component-scan base-package="egovframework">
          <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
      </context:component-scan>
  • 이렇게 설정하는 이유는 스프링이 Transaction을 적용하기 위해서이다.
  • 스프링 트랜잭션은 AOP를 이용해서 해당 빈의 proxy를 만들어서 tx가 적용되는 bean을 바꿔치기 한다. 그러면 원래 @Service(또는 @Transactional) 어노테이션이 붙은 빈은 뒤로 숨고 tx가 적용된 proxy bean이 @Service가 붙은 bean으로 대치된다.
  • 만약 Application ContextServlet Context가 모든 stereotype의 컴포넌트를 풀 스캔 할 경우, tx 설정은 Application Context에만 적용되어 있기 때문에 Application Context의 @Service는 트랜잭션이 적용이 되지만 Servlet Context의 @Service는 트랜잭션이 적용이 안된다.
  • Bean 객체는 Servlet Context가 우선되므로 @Controller가 동작하여 같은 context(Servlet Context)안에서 검색을 하고, @Service가 지정된 bean이 있으므로 이 bean을 사용한다. 이 @Service가 지정된 bean은 트랜잭션 적용이 안 되어 있어 트랜잭션 처리가 안된다.

+ Recent posts