주제

  • 내용 : Standlone java application 환경에서 Java SpringFramework 의 Autowired Annotation 사용할 수 있도록 구현해보기
  • 계획 :
    1. Java SpringFramework의 Bean 관리가 어떻게 이뤄지는 지 개념 이해하기
    2. Java SpringFramework Autowired기능을 구현하기 위해 필요한 Library 준비하기
    3. 실전! 프로젝트 만들어보기
  • 이슈사항 : Autowired Annotation을 우선적으로 구현해보겠습니다.

본격적으로 알아보자

Chapter 1) Java SpirngFramework의 Bean 관리가 어떻게 이뤄지는 지 개념 이해하기

ApplicationContext는 javaspring에서 오브젝트에 대한 생성과 관계설정을 담당한다. IoC컨테이너라고 하기도 한다.

  1. ApplicationContext는 앞의 @Configuration이 붙은 ContextLoader와 ServiceFactory를 설정정보로 등록하고, Bean 목록을 만든다.
  2. 클라이언트가 ApplicationContext의 getBean 메소드를 호출하면 Bean 목록에서 요청한 이름이 있는지 찾는다.
  3. 있다면 Bean을 생성하는 메소드를 호출해서 오브젝트를 생성시킨 후 클라이언트에게 리턴해 준다.

Chapter 2) Java SpringFramework Autowired기능을 구현하기 위해 필요한 Library 준비하기

spring-core,spring-beans,spring-context dependency를 pom.xml에 추가해준다.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>3.1.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>3.1.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.1.1.RELEASE</version>
</dependency>

여기까지 dependency를 준비하면 아래와 같은 오류가 발생한다. 테스트 한 spring3버전에서는 cglib가 포함되어있지 않아서 발생하는 오류이다.

java.lang.IllegalStateException: CGLIB is required to process @Configuration classes. Either add CGLIB to the classpath or remove the following @Configuration bean definitions: [serviceFactory]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:327)
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:222)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:681)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:620)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:446)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:73)

cglib 오류를 해결하기 위해서 cglib dependency를 추가해준다.

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>

Chapter 3) 실전! 프로젝트 만들어보기

1. 개발환경

환경 버전
Java 1.8.0_121
OS Window 10
javaspring 3.1.1.RELEASE

2. Configuration 클래스 설정

Bean객체의 설정 정보를 컨트롤 하는 Configuration 클래스 생성하자.

tomo.config.ContextLoader.java

@Configuration 어노테이션을 추가하여 Application이 수행될 때 가장 먼저 호출하여 Bean 오브젝트에 대한 설정 정보를 읽어올 수 있도록 한다.

@Configuration
public class ContextLoader {
  ...
}

ApplicationContext를 생성해서 Bean 객체에 대한 정보를 인터페이스에 담아야 하는데, 자바 @Configuration 어노테이션이 붙은 클래스를 설정 정보 이용하기 위해서 AnnotationConfigApplicationContext 구현체를 활용한다. AnnotationConfigApplicationContext의 파라미터로 등록된 Bean을 알려준다.

...
    private static ApplicationContext context;
	
    static{
        try {
            if( context == null ) {
                context = new AnnotationConfigApplicationContext(ServiceFactory.class);
            }
        } catch (Exception e) { }
    }
...

context.getBean(..)은 ApplicationContext가 컨트롤하는 객체를 요청하는 메서드. 등록된 Bean의 이름을 파라미터로 넘기면 해당 객체를 리턴해서 넘겨준다.

...
    public static Object getBean(String beanName) {
        ApplicationContext context = getContext();
        return (context == null) ? null : context.getBean(beanName);
    }
...

3. Bean 정보를 가진 Factory클래스

tomo.config.factory.ServiceFactory.java

AnnotationConfigApplicationContext 클래스로 JavaConfig를 사용하기 위해서는 @Configuration 어노테이션을 붙여줘야 한다. @Bean어노테이션으로 Bean을 하나씩 생성하는 방법도 있지만, @ComponentScan어노테이션을 활용해서 패키지로 Bean객체를 읽어오도록 설정해준다.

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "tomo.app")
public class ServiceFactory {

}

4. tomo.app 패키지 하위에 @Component, @Service 등의 어노테이션을 가진 클래스를 생성한다.

tomo.app.user.service.UserServiceImpl.java

DeptService 클래스를 @Autowired 시켜 멤버 변수로 가진 UserServiceImpl을 작성한다.

package tomo.app.user.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import tomo.app.dept.service.DeptService;

@Service
public class UserServiceImpl implements UserService {

	@Autowired
	private DeptService deptService;
	
	public void printUserName() {
		String username = "Ruby";
		System.out.println( String.format("My name is %s.", username) );
		
		deptService.printDeptName(username);
	}

}

5. Main클래스 작성하기

tomo.config.MainApp.java

ApplicationContext로 생성된 Bean이 아니면 @Autowired어노테이션을 이용하여 자동 주입시킬 수 없다. 이럴 때는 ContextLoader의 context를 이용해서 직접 Bean 객체를 가져와야 한다. ApplicationContext에 등록되어 있는 Bean객체에서는 미리 생성된 Bean 객체가 자동 주입되므로 UserService, DeptService에서는 어노테이션 사용이 가능하다.

public class MainApp {

	private static UserService userService = ContextLoader.getContext().getBean(UserService.class);
	
	public static void main(String[] args) {
		System.out.println("[--------- main start! ----------]");
		userService.printUserName();
		System.out.println("[---------- main end! -----------]");
	}

}

마무리

이 예제는 web application이 아닌, java application 환경에서 javaspring의 어노테이션 기능을 어떻게 사용할 수 있는 지 간략한 예제를 다뤄본 글입니다. ApplicationContext가 javaspring framework에서 Bean의 생성과 관계연결 어떻게 하는지 이해할 있었던 경험이었습니다.

저는 java, javaspring을 꾸준히 배우고 있는 주니어 개발자입니다. 혹시 틀린 점이나 보완해야할 부분이 있다면 언제든지 코멘트 남겨주세요!


전체 소스가 궁금하다면 https://github.com/uunnaa/standalone-java-application-spring 에서 확인하면 됩니다.


참고 사이트 (reference)


주제

book

  • 내용 : 자바 성능 튜닝 이야기(이상민 저) 내용 리뷰
  • 계획 : 자바 성능 튜닝 이야기를 보며, 모르는 내용을 찾아보고, 중요하다고 생각하는 내용을 정리하여 리뷰하겠습니다.

내용

아래 블로그에 내용 정리하였습니다. https://chaberry.tistory.com/3


주제

book

  • 내용 : 테스트 주도 개발(켄트 백 저) 내용 리뷰
  • 계획 : 전체가 테스트 주도 개발 예제와 파이썬을 활용한 xUnit 구현으로 나뉘어있음. 우선 샘플 예제를 전부 작성하고 리뷰한 후, 추가적으로 Java를 활용하여 JUnit을 구현해보겠습니다.
  • 이슈사항 : 파이썬으로 작성된 코드에 대해 분석하고.. 자바로 구현을 해야할텐데 파이썬을 몰라서.. 켄트백이 작성한 다른 문서를 참고할 수도 있어요ㅠ

내용

Chapter 0. 들어가는 글 및 시나리오

현재 회사에서는 달러 화폐에 대한 채권 포트폴리오 관리 시스템을 판매하고 있다.

그러던 중 새로운 고객으로 부터 "새로운 채권 펀드를 시작하려 하는데 다른 화폐로 채권을 다룰 수 있도록 커스터마이징 해주세요"라는 요청을 받았다.

이를 반영하기 위해 TDD를 이용하여 리팩토링을 진행해보자.

이 책은 한가지 화폐에 대한 기능만을 가진 시스템을 다중 화폐를 지원하도록 리팩토링 하는 과정을 TDD를 통해 풀어내며 TDD의 반복 주기를 학습하는 과정을 담고 있다.

그리고 이 과정을 통해 알게된 테스팅 프레임워크를 파이썬으로 구현하는 과정을 진행하는데 테스팅 프레임워크는.. 파이썬을 모르므로 자바로 진행할 계획인데.. 우선 해보자..ㅎㅎ

  • 아무래도 책보고 정리하는 내용이다보니 주관적인 생각이 들어갈 수 있으므로… 참고만 하시고 자세한 공부는 따로 꼭 책을 읽어보시는걸 추천합니다.
  • 또한 책은… JUnit을 사용하고 있으나 경우에 따라서는 assertJ를 활용하니 참고하시기 바랍니다.

Chapter 1. 다중 통화를 지원하는 Money 객체

종목 가격 합계
IBM 1000 25 25000
GE 400 100 40000
    합계 65000

위와 같은 보고서에서 만약 다중 통화를 지원하려면 아래와 같이 통화 단위를 추가해야 한다.

종목 가격 합계
IBM 1000 25USD 25000USD
GE 400 150CHF 60000CHF
    합계 65000USD

또한 위와 같은 값을 산출할 환율 정보도 추가 되어야한다.

기준 변환 환율
CHF USD 1.5

이를 구현하기 위한 To Do List를 작성해두고 현재 진행 중인 작업은 굵은 글씨로 표기하도록 하겠다.

$5 + 10CHF = $10(환율이 2:1 인 경우)
$5 * 2 = $10

우선 위의

우선 위의 기능(Operation 혹은 Method)을 구현하기에 앞서 테스트 케이스에 대해 작성해야하는데 어떠한 기능이 필요한지 그 기능에 대한 인터페이스를 우선 고민해보는 습관이 필요하다.

기존의 Dollar 객체에 2를 곱하는 테스트를 작성하면 아래와 같다.

import org.junit.Test;
import static org.assertj.core.api.Assertions.*;

public class DollarTest {
    @Test
    public void testMuliplication(){
        Dollar five = new Dollar(5);
        five.times(2)
        assertThat(five.amount).isEqualTo(10);
    }
}

위의 테스트를 위해 필요한 사항을 리스트로 적어보자

  1. Dollar 클래스(생성자)
  2. times Method
  3. amount 변수(int)

그리고 위의 3가지 사항을 틀만 구현하여 컴파일에러 상태부터 벗어나자.물론 아주 간단한 기능을 하는 함수이므로 바로 구현가능하겠지만.. 우선은 TDD를 연습하는 목적이므로 아주 작은 단위로 작업을 진행하도록 권장한다.

위의 세가지 리스트 대로 “틀만 완성된” Dollar Class는 아래와 같다.

package Dollar;

public class Dollar {
    public int amount;
    
    public Dollar(int dollar) {}

    public void times(int times) {}
}

틀만 완성되었기 때문에 테스트에는 아직도 빨간색의 막대가 떠있다.

이를 최소한의 구현(하드코딩…)을 통해 통과시켜보자.

package Dollar;

public class Dollar {
    public int amount = 10;

    public Dollar(int dollar) {

    }

    public void times(int times) {
    }
}

하지만 현재로서는 만약 처음 객체를 생성할 때 입력한 값을 바꾸면 다시 테스트는 빨간색의 막대를 보여주기 때문에 약간의 수정을 더 진행해보자.


Chapter 1. Interface

tds_book

학습목표

앞으로 실습을 통해 다음의 3가지 학습 목표를 갖는다.

자료구조

  • 자바 컬렉션 프레임워크(Java Collection Framework, JFC) 구조로 시작하여, List, Map, 과 같은 자료구조를 사용과 동작 방법

알고리즘 분석

  • 코드를 분석하고 이 코드가 얼마나 빠른 동작을 하고 얼마나 많은 공간(메모리)를 필요한지 예측

정보 검색

  • 자료구조와 알고리즘을 활용한 간단한 웹 검색 엔진을 만들어 봄

List Interface

앞으로 몇가지 실습을 통해 List Interface를 분석하고 학습하려 한다.

JFCJava Framework Collection는 List 라는 interface를 통해 ArrayList와 LinkedList라는 두 구현 클래스를 제공한다.

List interface를 구현하는 클래스는 약 20가지 메서드를 포함한 특정 메서드를 구현해야한다.

따라서 List Interface를 구현하는 ArrayList와 LinkedList는 모두 동일한 메서드를 제공하므로 상호 교환할 수 있다는 것이다.

그렇다면 이 두 클래스의 차이점은 무엇일까?

ArrayList vs LinkedList

jfc

두 클래스의 차이는 다음의 링크에 자세히 소개되고 있다. ArrayList와 LinkedList 차이

쉽게 정리하자면 다음과 같다.

list

생활코딩의 리스트 비교

하나의 건물(메모리)회사(List의 요소)가 입주해있다.

ArrayList

Array List는 그림과 같이 건물에 나란히 회사가 위치해있는 형태이다. 회사의 사무실이 이렇게 나란히 붙어있기 때문에 사무실의 위치를 찾기가 굉장히 수월하다.

하지만 이러한 방식은 빈 공간을 허용하지 않기 때문에, 중간의 하나의 사무실에 공실이 발생할 경우 전체 사무실이 빈 공간을 채우려 이동해야 한다.

LinkedList

이와달리 Linked List는 사무실이 산발적으로 연결(Linked)된 방식이다.

우리는 사무실의 위치를 찾기 위해 각각의 사무실을(예: 201호 - 304호 - 408호 등) 방문하여 다음 사무실이 어디인지를 물어봐야한다.

예를들면, 201호를 먼저 방문하여 직원에게 다음 사무실이 어딘지 물어봐야 다음 사무실이 304호 라는 것을 알 수 있다는 것이다.

어떤가, 벌써 찾아가기가 번거롭다.

하지만 이 방식은 나란히 있는 것이 아니기 때문에 중간에 사무실에 공실이 발생하거나 추가되어도 전혀 상관없다. 어차피 우리는 각자의 위치(주소)를 갖고 있기 때문이다.

따라서 다음의 특징을 갖는다.

List성능비교


실습1

본론으로 돌아와 List Interface를 살펴보자.

List Interface 사용하기

ArrayList와 LinkedList는 List를 구현(implements)하여 사용하는 클래스이다.

따라서 다음과 같은 구조로 사용할 수 있다.

실습 코드

public class ListClientExample {

    @SuppressWarnings("rawtypes")
    private List list;

    @SuppressWarnings("rawtypes")
    public ListClientExample() {
        list = new LinkedList();
    }

    @SuppressWarnings("rawtypes")
    public List getList() {
        return list;
    }

    public static void main(String[] args) {
        ListClientExample lce = new ListClientExample();
        @SuppressWarnings("rawtypes")
        List list = lce.getList();
        System.out.println(list);
    }
}

테스트 코드

public class ListClientExampleTest {
	/**
	 * Test method for {@link ListClientExample}.
	 */
	@Test
	public void testListClientExample() {
		ListClientExample lce = new ListClientExample();
		@SuppressWarnings("rawtypes")
		List list = lce.getList();
		assertThat(list, instanceOf(ArrayList.class) );
	}
}

테스트 실행결과

failed_test

첫번째 빨간불을 맛보았다.

오류 내용을 살펴보면, ArrayList의 instance를 기대했는데, But LinkedList 라는 것이다.

getList()로 부터 획득하는 리스트의 인스턴스가 문제가 있는 모양인데, 거슬러 올라가면 getList()의 리스트 인스턴스는

    @SuppressWarnings("rawtypes")
    public ListClientExample() {
        list = new LinkedList();
    }

이처럼 LinkedList()로 초기화된 모습이다.

테스트를 통과시키기 위해선 ArrayList가 필요하다. 이 초기화 작업만 바꾸어 주면 될 것 같다.

    @SuppressWarnings("rawtypes")
    public ListClientExample() {
        list = new ArrayList();
    }

success_test

이 클래스의 구성은 유용하진 않지만, List를 캡슐화하는 클래스의 필수 요소를 가지고 있다.

위 클래스는 생성자를 통해서만 LinkedList를 초기화하고, getList를 통해 List객체애 대한 참조를 반환한다.

경우에 따라 ArrayList를 사용하고자 한다면, 생성자를 통한 초기화만 바꾸어주면 된다.

이와 같은 방식을 인터페이스 프로그래밍이라고 하는데, 필요한 경우가 아니라면 LinkedList나 ArrayList를 직접 구현하지 않고 생성자를 통해 구현함으로써, 필요에 따라 생성자의 초기화만 변경하여 유연한 대처를 할 수 있다.

여기서 말하는 ‘인터페이스’는 자바의 인터페이스가 아닌, 일반적인 의미의 인터페이스를 말한다.

첫 실습은 비교적 싱겁게(?) 끝났지만, 앞으로 다루게 될 List Interface에 대한 내용을 다루어 보았다.

이 장에서 기억해야 할 것은,

  • ArrayList와 LinkedList가 분명하게 컨셉이 다르다는 것
  • interface에 대한 개념(어떤 클래스를 초기화하느냐에 따라 유연하게 사용 가능)

그러면 다음 장에서 본격적으로 List에 대해서 파해쳐보자서

생각해보기

  1. 앞선 ListClientExample 클래스 생성자에서 ArrayList객체를 List 인터페이스로 교체하면 어떻게 될까?
    • 생성자에서 인스턴스를 생성하지 않고, 인터페이스로만 선언한다면 List의 실체(인스턴스)가 없으므로 컴파일이 제대로 되지 않는다.
  2. 왜 List 인터페이스로는 인스턴스가 생성되지 않을까?
    • 자바의 interface는 인스턴스를 생성하는 객체로 사용되지 않는다. interface를 사용해서 어떠한 클래스를 디자인할 것인지 명세(또는 목차)만을 제공한다.

    따라서 List가 interface로 설계된 이유 역시, List라는 전체 구조를 필요애 따라 구현(ArrayList or LinkedList)할 수 있도록 가이드라인만 제공해준 것이 아닐까.

[참고]

  • https://www.holaxprogramming.com/2014/02/12/java-list-interface/
  • http://www.nextree.co.kr/p6506/
  • https://opentutorials.org/module/1335/8821

@Meet Up

  • 일시 : 2019.1.13
  • 장소 : 종로 3가, 다리미 커피
  • 참여자 : Bryan, Bread, Jay, Judy, Ruby, Steve
  • 주제 : 2019 스터디 계획

Issue

  • 2019년 스터디 계획 [2018년 스터디 진행의 한계]
    • 주중/주말 출장 또는 업무가 많아 코딩 또는 공통 과제를 수행하기 어려움
    • 주말 모임(토요일 오전 11시) 역시 개인 일정이 생기며 6명 전체 인원 모이기가 어려움
    • 2018년(약 11개월) 진행했지만, 실제로 눈에 보이는 성과/결과물이 남지 않았음

Opinion

  • 무엇을 하던지, 결과물이 남았으면 좋겠음
  • 현실적으로 6명이 실현/참여 가능한 스터디를 진행 필요

Result

  • 월 1회 주제 하나 이상의 주제를 선정하여 해당 주제를 정리/업로드
  • 월말(해당 월의 마지막 주) 오프라인/온라인 모임을 통해 자신이 조사한 내용 공유

TODO

  • 블로그 collaborator 등록
  • 블로그 업로드 환경 구축 및 git push 테스트
  • 1회차 1월 주제 선정 내용 블로그 업로드

@Next Meet Up : 2019.1.19