월간 보관물: 2014 1월

Dependency Injection, Dependency lookup, Provider

일반적으로 스프링을 사용할 때 @Component 또는 @Service 등의 어노테이션이 명시된 클래스의 객체 ( 또는 설정 xml 의 bean 노드에 정의된 ) 는 application context 와 생명주기를 함께하는 싱글톤 객체라고 볼 수 있다. 이런 객체들을 사용하는 곳에서는 @Autowired 어노테이션만 이용하면 해당 객체를 스프링 프레임워크로 부터 주입 받아 사용할 수 있다. 이것이 일반적으로 스프링에서 사용하는 의존성 주입 (DI) 이다. 어떻게 해당 객체를 만드는지에 대해서는 개발자는 신경쓰지 않아도 된다.

DI 을 사용하지 않는 객체들 중에는 메소드 내부 변수처럼 한번만 쓰고 버리는 임시 변수 같은 객체들도 분명히 존재한다. 이러한 객체들은 스프링 프레임워크에서 관리하지 않고 개발자가 직접 new 를 사용하여 객체를 생성하고 관리하게 된다. 하지만 이러한 임시 객체에서 스프링으로부터 의존성을 주입받아야 하는 bean들이 필요하면 어떻게 할까? @Autowired 를 명시해 놓는다고 DI 를 받을 수 있을까?

DL (dependency lookup) 은 일반적으로 프레임워크에서 생성한 객체들을 프레임워크가 직접 주입해주는것이 아니라 개발자가 개발 코드상에서 해당 객체를 요청하여 받아오는 방식을 말한다. application context 나 bean factory 같은것을 사용하여 프레임워크에서 생성하는 객체들을 받아올 수 있다. 이 때는 생성된 임시 객체에서 사용할 각종 서비스빈들을 프레임워크에서 DI 를 받을 수 있다.

이를 위해서 추천할만한 방법으로 JSR-330 에 정의된 Provider 를 이용하는 방법이다. Provider 는 스프링프레임워크에 기본적으로 포함되어 있지 않기 때문에 아래의 dependency 를 추가한다.

<!-- JSR 330 -->

<dependency>

<groupId>javax.inject</groupId>

<artifactId>javax.inject</artifactId>

<version>1</version>

</dependency>

Provider 를 이용한 코드의 예제는 다음과 같다.

@Autowired

private Provider<MyTask> taskProvider;

public void test(){
     MyTask task = taskProvider.get();
}

Provider 는 인테페이스인데 이 인터페이스에 대한 실제 구현은 스프링에서 알아서 바인딩해준다. 또한 위에서 로컬 변수처럼 해당 scope 내에서 생성되었다가 소멸되는 객체의 경우는 scope을 prototype 으로 지정해줘야 한다.

찾아보면서 만들었더니 대충 아래와 같은 모양의 class가 되었다.

@Component
@Scope (“prototype”)

public class MyTask {

@Autowired

MyService myService;
}

reference
http://toby.epril.com/?p=947
http://toby.epril.com/?p=971
http://whiteship.tistory.com/2533

HashMap 내부 구현 및 동작

Java 에서 자료를 저장하고 검색하기 위해 가장 많이 사용하는 자료구조인 HashMap 이 내부적으로 어떻게 구현 되고 동작하는지 알아보자. 모든 부분은 설명하지 않고 중점적으로 사용되는 부분만을 볼 예정.

먼저 HashMap 에 저장되는 가장 기본적인 자료구조는 key, value 값을 저장하고 있는 Entry 가 있다.


Entry<K,V>  {

int hash;   //hashMap 내부에서 계산된 hash 값

final K key; // 입력된 key

V value;  // 입력된 value

Entry<K,V> next;  // linkedList 형태

}

그리고 각 entry 들을 저장하기 위한 array 인 table 이 있다.

Entry<K,V> [] table;

그 외 중요 변수로는

final float loadFactor;

int threshold;

int modCount;

1. put 

put() 메소드를 통해 key, value 값이 저장되면 내부적으로 아래와 같은 모습으로 저장된다.

스크린샷 2013-10-09 오후 9.47.40

이미지 출처 : https://mkbansal.wordpress.com/2010/06/24/hashmap-how-it-works/

저장되는 순서는 아래와 같다.

1) key 객체의 hashCode() 값을 이용하여 내부적으로 새로운 hash 값을 구한다.

2) 새로운 hash 값을 이용하여 table 배열에서 몇번째 index 에 저장할지 index 를 찾는다. ( indexFor 함수 )

3-1) 구해진 index 에 entry 가 이미 존재하면 linked-list 형태로 연결한다.

3-2) 구해진 index 에 entry 가 없으면 그냥 assign 한다.

4) 만약 key 가 기존에 존재하던 값이면 value 만 replace 하고 이전 value 값을 리턴한다.

2. doubling 

위에서도 볼 수 있듯이 hashMap 에서는 entry 를 저장할 수 있는 table 배열의 크기가 정해져 있기 때문에 많은 수의 entry 가 입력되면 collision 이 자주 발생하게 되고 이는 성능저하를 가져오게 된다. 이를 방지하기 위해서 HashMap 은 table 배열의 사이즈를 doubling 하게 된다. ( 배열의 크기는 항상 2의 제곱수를 유지한다 )

1) entry 저장 시 entry 개수가 threshold ( table.size * loadFactor ) 를 넘어서면 table 을 doubling

2) table 사이즈가 커졌기 때문에 기존에 저장된 모든 entry 에 대해서  rehash 를 수행하여 entry 분산

3) table.size 가 maximum_capacity 를 넘어가면 doubling 하지 않음.

doubling 은 기존 배열의 두배 크기의 배열을 새롭게 만들고 기존의 entry 를 rehash 하며 옮겨오는 방식으로 진행된다.

3. indexFor 

앞서 설명했듯이 내부적으로 새롭게 계산된 hash 값을 이용하여 table 에 저장된 index 값을 구하게 되는데 이를 위해 indexFor 함수를 호출한다. 일반적으로 table size 만큼 entry 를 분산해야 하기 때문에 modular 방식을 생각할 수 있겠으나 좀 더 효율적인 분산을 위해 아래와 같은 방법으로 구현되어 있다.

static int indexFor ( int h, int length ){

return h & ( length -1 );

}

h 는 hash 값, length 는 table 배열의 size 이다.

table 의 length 는 2의 제곱수이기 때문에 length – 1 은 모든 비트가 1로 채워진 수가 된다. 그 값과 h 값을 & 연산을 통해서 index 값을 구하게 된다. 이런 방식을 취하면 length 값이 두배로 증가하였을때는 상위 하나의 비트가 더 추가된 값과 h 값이 & 연산을 하게 된다. 그 결과는 반드시 기존 index 값과 같거나 doubling 으로 새롭게 추가된 영역의 index 만을 가지게 되기 때문에 좀 더 효율적인 entry 의 분산을 기대할 수 있다.

스크린샷 2013-10-30 오전 12.29.01
4. ConcurrentModificationException

HashMap 은 thread-safe 한 자료구조가 아니다. 즉, 특정 thread 가 자료를 읽는 중에도 다른 thread 가 map 내부의 자료를 삭제할 수 있다. 이는 multi-thread 환경에서 분명 문제가 된다. 이같은 문제 때문에 HashMap 에서는 entry 들을 iteration 하는 도중 자료구조에 변경이 일어나면 ConcurrentModificationException 을 발생시켜 해당 문제를 알려준다.

이를 위해서 HashMap 내부에 modCount 라는 변수를 두고 HashMap 의 모든 업데이트의 발생시 modCount 를 증가시켜 준다. 그리고 entry 의 iteration 을 시작하기 전에 modCount 값을 다른 임시 변수에 복사 해 두고 매번 next 호출때 마다 두 변수 값을 비교하여 차이가 발생하면 ConcurrentModificationException 을 발생시킨다

5. get

인자로 받은 key 에 매핑되는 value 값을 찾는 방식은 put 의 방식과 유사하다.

1) key 객체의 hashCode() 값을 이용하여 내부적으로 새로운 hash 값을 구한다.

2) 새로운 hash 값을 이용하여 table 배열에서 몇번째 index 에 저장되어 있는지 찾는다.  ( indexFor 함수 )

3-1) 구해진 index 에 entry 의 key 와 동일한 객체인지 equals 를 이용하여 비교하여 동일하면 entry 의 value 를 리턴한다.

3-2) equals 결과가 false 이면 entry 의 next 링크를 계속 따라가며 동일한 key 를 가진 entry 를 찾는다.

일반적인 hash 의 자료구조는 위와 같은 방식으로 구현되어 있다.

주의해서 알아야 할점은 entry 의 개수가 많아지면 doubling 이 자주 일어나고 이 때 새로운 table 을 위한 memory 공간 확보, 기존의 모든 entry 에 대해서 rehash 등으로 인한 오버헤드가 발생한다.  HashMap 생성시 entry 의 개수를 미리 예측할 수 있다면 doubling 으로 인한 오버헤드를 줄일 수 있다. 또한 doubling 으로 인한 memory 낭비가 있을 수 있기 때문에 정적인 데이터의 경우 loadFactor 값을 높게 조정해 볼 수도 있을것이다.

@ModelAttribute 사용시 오류 내용 확인

@ModelAttribute 를 사용하면 request parameter 로 넘어오는 데이터를 이용하여  ModelAttribute 어노테이션으로 선언한 객체를 생성하여 값을 세팅할 수 있다. 이때 객체 바인딩에 오류가 생기면 400 을 리턴하고 함수 진입 조차 하지 못하는데 이 경우 도무지 오류의 내용을 알기 어려운 경우가 있었다.


@RequestMapping( value="edit", method=RequestMethod.POST )

public ModelAndView editMyData ( ModelAndView mav, @ModelAttribute MyData mydata ){

......

}

이 경우 함수의 인자로 BindingResult 를 함께 받아서 출력해보면 오류의 내용을 확인할 수 있다.


@RequestMapping( value="edit", method=RequestMethod.POST )

public ModelAndView editMyData ( ModelAndView mav, @ModelAttribute MyData mydata, BindingResult errors  ){

if ( errors.hasErrors() ){

logger.error( errors.getAllErrors() );

}

}

RestTemplate 을 이용해서 API 호출

스프링 웹 프로젝트에서 rest api 를 단순히 호출하거나 호출한 결과를 이용하고 싶을 때 아파치 commons 의 HttpClient 말고 스프링 웹 프로젝트에서 제공하는 RestTemplate 을 사용할 수 있다.

public class ApiCallService {

private SimpleClientHttpRequestFactory requestFactory;

@PostConstruct

public void setup(){

requestFactory = new SimpleClientHttpRequestFactory();

requestFactory.setConnectTimeout(500);

requestFactory.setReadTimeout(10000);

}


public Map<String, Object> callApi ( String url ) {

try {

Map<String, Object> apiResult = new RestTemplate(requestFactory).getForObject(url, Map.class);
//또는 
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.getForEntity(url + "?param1={param1}&param2={param2}", null, param1_value, param2_value);

} catch (RestClientException e) {

....

}

}

추가적으로 RestTemplate 의 결과를 Map 이나 기타 다른 자료구조로 받아오기 위한 몇가지 설정이 필요하다.
여기서는 API 의 결과가 json 이고 그것을 Map 으로 받길 원한다면

1. spring 설정에 아래 내용 추가.

    <bean id="jackson2HttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>

2. Jacson 사용을 위해서 pom.xml 에 디펜던시 추가.

    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-databind</artifactId>
       <version>2.2.3</version>
    </dependency>

구지 이러한 기능이 필요없다면 그냥 String.class 로 rest 결과를 받으면 된다.