728x90

개방 폐쇄 원칙

이전에 알아본 내용이지만 다시 간단하게 알아보자면

변경에 개방적이어야 하는 코드들은 수정과 확장에 열려있어야 하고

변경되지 말아야 하는 코드들은 닫혀있어야 하는 원칙이다.

템플릿

개방 폐쇄 원칙을 적용하여 효과적으로 활용할 수 있도록 하는 방법으로

바뀌는 성질이 다른 코드 중 변경이 거의 없고 일정한 패턴으로 유지되는 부분을

자유롭게 변경되는 부분으로부터 독립시키는 방법이다.

 

즉, 이전에 학습했던 객체지향적인 리팩토링처럼

변하지 않고 많은 곳에서 중복되는 코드와 자주 변경되는 코드를 분리하는 작업이다.

 

템플릿 적용하기

1. 변하는 성격이 다른 것 찾기

데이터베이스를 조작하는 코드에서 예시를 든다면

조작을 위한 SQL문과 전체적인 데이터베이스 로직을 처리하는 반복적인 코드가 있다.

 

여기서 SQL문은 상황에 따라 달라지는 자주 변경되는 코드이고

데이터베이스의 전체적인 로직을 처리하는 코드는 공통적으로 사용되는 코드로

SQL문과 관련된 코드가 변하는 성격이 다른 것이라고 볼 수 있다.

2. 코드에서 분리하기

Connection c = null;
PreparedStatement ps = null;

try {
    c = dataSource.getConnection();

    ps = c.prepareStatement("delete from users"); // 변하는 부분

    ps.executeUpdate();
} catch (SQLException e) {
    throw e;
} finally {
    if (ps != null) { try { ps.close(); } catch (SQLException e) {} }
    if (c != null) { try {c.close(); } catch (SQLException e) {} }
}

위의 코드에서 변하는 부분인 SQL문을 분리하는 방법 중에서는

여러가지 방법이 있지만 기존 리팩토링 과정에서 써봤던

메서드 추출이라는 방법이 있다.

try {
    c = dataSource.getConnection();

    ps = makeStatement(c); // 변하는 부분

    ps.executeUpdate();
}

private PreparedStatement makeStatement(Connection c) throws SQLException {
    PreparedStatement ps;
    ps = c.prepareStatement("delete from users");
    return ps
}

자주 바뀌는 SQL 부분의 코드를 메서드로 분리시켰지만

이로 인해 별다른 이득을 보는 것은 없고 그냥 코드를 분리하기만 했다.

 

실질적으로 재사용되는 코드는 SQL 코드가 아니라 

데이터베이스 작업을 처리하는 코드이기 때문이다.

3. 템플릿 메서드 패턴 적용하기

템플릿 메서드 패턴은 상속을 통해 기능을 확장해서 사용한다.

 

변하지 않는 부분은 슈퍼 클래스에, 변하는 부분은 추상 메서드로 정의하여

서브 클래스에서 자신에 맞게 오버라이드하여 정의하여 쓰게 한다.

private PreparedStatement makeStatement(Connection c) throws SQLException {
    PreparedStatement ps;
    ps = c.prepareStatement("delete from users");
    return ps;
}

위의 메서드는 변하는 부분에 속하는 코드이니 해당 메서드를 추상 메서드로 선언한다.

abstract public class UserDao {
	abstract protected PreparedStatement makeStatement(Connection c) throws SQLException;
}

이제 필요에 따라 해당 추상 클래스를 상속 받는 서브 클래스에

추상 메서드를 구현하여 자유롭게 사용할 수 있다.

public class UserDaoDeleteAll extends UserDao {
    protected PreparedStatement makeStatement(Connection c) throws SQLException {
        PreparedStatement ps = c.prepareStatement("delete from users");
        return ps;
    }
}

하지만 DAO 로직마다 상속을 통해 새로운 클래스를 매번 만들어야 하기 때문에 좋은 방법이 아니다.

4. 전략 패턴 적용하기

템플릿 메서드 패턴의 장점을 가지면서도 유연하고 확장성이 뛰어난 패턴으로

 

컨텍스트(변하지 않는 부분) 오브젝트와 전략 오브젝트(변하는 부분)를 아예 둘로 분리하고

이 둘의 오브젝트를 인터페이스를 통해서만 의존하도록 하는 디자인 패턴이다.

 

즉, 컨텍스트 - 전략 인터페이스 - 전략(전략 인터페이스의 구현 클래스) 구조로 생겼다고 생각할 수 있다.

public interface StatementStrategy {
	PreparedStatement makePreparedStatement(Connection c) throws SQLException; 
}

위의 코드처럼 전략 인터페이스를 생성하고 이를 구현하는 전략 클래스를 만든다.

public class Delete implements StatementStartegy {
	PreparedStatement makePreparedStatement(Connection c) throws SQLException {
    	PreparedStatement ps = c.prepareStatement("delete from users");
        return ps;
    }
}

만들어진 전략 클래스를 기존의 컨텍스트에서 사용하면 된다.

try {
    c = dataSource.getConnection();

    StatementStartegy startegy = new Delete();
    ps = startegy.makePreparedStatement(c);

    ps.executeUpdate();
}

하지만 이는 컨텍스트에 어떤 전략을 적용할지 직접적으로 지정하여

고정된 상태이기 때문에 결코 좋은 방식이 아니다.

5. 클라이언트와 컨텍스트를 분리하기

컨텍스트가 자신이 어떤 전략을 사용할지 모르게 하고

클라이언트가 어떤 전략을 사용할지 정해주어 결합을 낮춰야 하는데

이러한 방식은 1장을 공부할 때 사용했던 DI와 같다.

StatementStartegy startegy = new Delete();

위의 코드는 컨텍스트가 아닌 클라이언트가 가지고 있어야 할 코드이기 때문에

해당 부분을 클라이언트에게 넘겨야 한다.

public void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
	try {
        c = dataSource.getConnection();
        
        ps = startegy.makePreparedStatement(c);

        ps.executeUpdate();
    }
}

이렇게 코드를 수정하면 컨텍스트가 호출될 때 클라이언트로부터 전략을 전달 받기 때문에

핵심적인 내용만 잘 가지고 있는 코드다.

public void deleteAll() throws SQLException {
	StatementStartegy = st = new Delete();
    jdbcContextWithStatementStrategy(st);
}

이제 해당 컨텍스트를 호출하는 메서드를 클라이언트라고 볼 수 있는데

클라이언트 메서드에서는 사용할 전략을 선택하여 생성하고

이를 컨텍스트 메서드에 파라미터로 넘겨주면

컨텍스트 메서드는 전달 받은 전략에 맞는 작업을 수행한다.

 

컨텍스트와 클라이언트를 다른 클래스로 서로 분리하진 않았지만

관심사와 의존관계, 책임을 분리하는 것은 끝났다.

 

'Back-End > Spring' 카테고리의 다른 글

컨텍스트와 DI  (0) 2023.06.13
전략 패턴 최적화 하기  (0) 2023.06.13
스프링에 테스트 적용하기  (0) 2023.06.08
JUnit 자세히 알아보기  (0) 2023.06.08
테스트  (0) 2023.06.07
728x90

JUnit은 각각의 테스트독립적으로 존재할 수 있게 하기 위해

테스트 메서드마다 서로 다른 오브젝트를 만들어서 실행되게 한다.

 

하지만 이 경우가 스프링에서는 문제가 될 수 있는데

애플리케이션 컨텍스트를 테스트 오브젝트마다 반복적으로 만들기 때문에

빈이 많아지고 설정이 복잡해지는 경우에는 컨텍스트 생성에 시간이 많이 소요된다.

 

또한 여러 테스트가 함께 참조해야할 애플리케이션 컨텍스트를

오브젝트 레벨에 저장해두면 각각 다른 컨텍스트를 참조하게 되어 곤란하다.

 

이를 위해서 JUnit은 테스트 클래스 전체에서 딱 한 번만 실행되는

@BeforeClass 스태틱 메서드라는 기능을 제공한다.

 

물론 이 기능으로도 가능하지만 스프링에서 제공하는 더 좋은 방법이 있다.

스프링 테스트 컨텍스트 프레임워크

적용하기

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class UserDaoTest {
	@Autowired
	ApplicationContext context;
	//생략
	@Before
	public void setUp() {
		this.dao = this.context.getBean("userDao", UserDao.class);
		//생략
	}
}

@RunWith 어노테이션JUnit 테스트 실행 방법을 확장할 때 사용하는 어노테이션으로

SpringJUnit4ClassRunner라는 확장 클래스를 지정하여 해당 클래스가

테스트 중에 사용할 애플리케이션 컨텍스트를 만들고 관리해준다.

 

@ContextConfiguration 어노테이션은 애플리케이션 컨텍스트의 설정파일의 위치를 지정한다.

 

이렇게 간단하게 하나의 테스트 클래스 내에서 하나의 테스트용 애플리케이션 컨텍스트를 공유할 수 있다.

 

또한 @ContextConfiguration 어노테이션설정파일이 같은 경우에는

같은 애플리케이션 컨텍스트로 보고 서로 다른 테스트 클래스 간이라도

하나의 애플리케이션 컨텍스트를 공유하게 된다.

 

즉, 설정 파일의 종류만큼만 애플리케이션 컨텍스트를 생성하는 것이다.

@Autowired

해당 어노테이션은 변수 타입과 일치하는 컨텍스트 내의 빈을 찾아서 일치하는 경우

별도의 DI 설정 없이 변수에 주입해주는데, 이러한 방법을 타입에 의한 자동 와이어링이라 한다.

 

하지만 위의 코드에서는 애플리케이션 컨텍스트 타입의 변수에 해당 어노테이션을 적용하여

제 3자로서 의존관계를 주입하던 애플리케이션 컨텍스트가 역으로 의존관계 주입이 되버렸다.

 

이는 애플리케이션 컨텍스트도 빈이기 때문인데

애플리케이션 컨텍스트는 초기화할 때 자기 자신도 빈으로 등록한다.

 

애플리케이션 컨텍스트를 받을 수 있다는 것은 컨텍스트에 등록된 모든 빈들을

getBean을 사용하지 않고도 직접 의존관계 주입을 받을 수도 있다는 것이다.

 

하지만 타입과 일치하는 빈이 두 개 이상인 경우에는 사용 할 수 없다.

의존관계 주입과 테스트

효율적인 테스트를 손쉽게 만들기 위해서는 작은 단위의 대상에 대해 독립적으로 테스트를 해야 하는데

이를 위해서는 테스트에도 DI를 적용할 수 있다.

1. 테스트 코드에 의한 DI

수정자 메서드는 테스트 코드에서 얼마든지 호출하여 사용할 수 있기 때문에

이를 이용해서 테스트 코드에서 직접 의존관계를 주입할 수 있는데,

테스트 코드에서 의존관계를 변경할 수 있게 된다는 말이다.

 

설정파일을 수정하지 않고도 의존관계를 상황에 맞게 수정할 수 있지만

한 번 바꾼 의존관계는 남아있는 모든 테스트에 적용되기 때문에 잘 사용해야 한다.

 

하지만 이러한 문제를 해결하기 위해 @DirtiesContext 어노테이션을 사용하여

의존관계를 바꾸는 테스트가 수행을 마친 후에

새로운 애플리케이션 컨텍스트를 생성하여 나머지 테스트들을 수행하게 할 수 있다.

2. 테스트용 DI 설정하기

위의 방법은 매번 새로운 애플리케이션 컨텍스트를 만들어야 하기 때문에 효과적이지 못하다.

 

그래서 애초에 실제 서버에서 사용될 설정파일과 테스트용으로 사용될 설정파일을

각각 따로 만들어서 사용하는 것이 효과적이다.

3. 스프링 컨테이너 사용하지 않고 DI 테스트 하기

초반에 작성했던 코드처럼 테스트 코드에서

상황에 맞게 직접 의존관계를 주입하여 테스트 할 수 있다.

 

DI를 통해 오브젝트들의 관심사를 분리하고 의존성을 낮췄기 때문에

이렇게 애플리케이션 컨텍스트를 만드는 번거로움을 없애 테스트 시간을

절약할 수 있는 테스트 코드를 짤 수도 있다.

테스트 방법 선택하기

위의 세 가지 테스트 방법들은 각각의 장단점이 있기 때문에

모든 방법을 상황에 맞춰 유용하게 사용할 수 있다.

 

항상 우선적으로는 테스트 수행 속도가 가장 빠르고 간결한

스프링 컨테이너를 사용하지 않는 테스트 방법을 고려하는 것이 좋다.

 

하지만 여러 오브젝트와 복잡한 의존관계를 갖고 있는 오브젝트의

테스트를 수행해야 하는 경우에는 스프링의 설정을 이용한

DI 방식의 테스트를 사용하는 것이 편리하다.

 

예외적인 의존관계를 강제로 구성해서 테스트를 해야하는 경우에는

수동으로 의존관계를 주입하는 방식을 사용하는 것이 좋다.

'Back-End > Spring' 카테고리의 다른 글

전략 패턴 최적화 하기  (0) 2023.06.13
템플릿 적용하기  (0) 2023.06.13
JUnit 자세히 알아보기  (0) 2023.06.08
테스트  (0) 2023.06.07
XML 설정  (0) 2023.06.07
728x90

JUnit 테스트 실행하기

IDE

자바 IDE에서는 대부분 내장된 JUnit 테스트 지원 도구가 존재한다.

 

JUnit을 메인 메서드를 만들지 않고도 테스트를 진행할 수 있게 해주며,

테스트 정보를 표시해주는 다양한 뷰 기능도 제공한다.

빌드 툴

메이븐, 그래들 같은 빌드 툴에서 제공하는 JUnit 플러그인과 테스트가 존재한다.

 

HTML이나 텍스트 파일 형식으로 보기 좋게 테스트 결과를 만들어주기 때문에

개인별로는 IDE를 통해서 테스트를 실행하지만 통합 빌드 후의 테스트는

빌드 툴을 통한 테스트 결과를 파일로 전달 받는다.

일관된 테스트 결과

테스트는 외부의 상태에 따라 결과가 달라지는 일이 발생하지 않고

코드의 변화가 있지 않는한 항상 같은 결과가 나와야 한다.

 

이를 위해서는 테스트한 결과는 실제로 반영되지 않아야 하는데

테스트를 실행한 후에는 결과만 확인하고 초기화하는 것이 좋다.

public void andAndGet() throws SQLException {
    //데이터 삭제를 통한 초기화
    //초기화가 제대로 된지 검증

    //db에 데이터 추가
    //추가가 제대로 되었는지 검증

    //추가하려는 데이터와 추가된 데이터가 일치하는지 마지막으로 검증
}

이렇게 직접 데이터를 삭제하는 메서드와 데이터의 초기화 및 추가 검증 메서드를 구현하여

테스트 메서드에서 사용하는 방법도 있지만 스프링은 더 편리한 기능을 제공한다.

 

하지만 아직 스프링의 기능을 충분히 배우지 못했기 때문에 나중에 다시 알아보겠다.

 

중요한 것은 테스트의 결과는 항상 일관되야 한다는 것이다.

포괄적인 테스트

테스트는 항상 한 가지 결과만 검증하는 것에서 끝나는 것이 아니라 다양한 상황을 염두하고

테스트를 진행해야 하는데, 테스트가 성공하지 말아야 하는 경우에 성공하는 것을 방지하기 위함이다.

 

한 가지 더 주의해야 할 점은 JUnit은 테스트의 실행 순서를 보장해주지 않기 때문에

테스트 코드를 작성할 때는 항상 실행 순서와 상관 없이 올바른 결과가 나오고

다른 테스트와 독립적으로 존재하게 작성해야 한다.

예외 조건에 대한 테스트

테스트 실행 도중 예외가 발생한 경우에는 특정 값을 리턴하거나 예외를 던지게 할 수 있다.

 

현재까지 작성한 코드는 리턴값이 있는 경우

assertThat 메서드를 통해 리턴값이 예상 결과와 일치하는지 확인하여

테스트의 성공여부를 알 수 있었다.

 

이번에는 예외를 사용하여 테스트 성공여부를 알 수 있는 방법을 알아볼 것인데,

예외는 assertThat을 통해서 비교를 할 수가 없지만

JUnit에서는 이러한 예외조건 테스트를 위한 기능을 제공한다.

@Test(expected=EmptyResultDataAccessException.class)
public void getUserFailure() throws SQLException {
    dao.deleteAll();
    assertThat(dao.getCount(), is(0));

    dao.get("unknown_id");
}

위의 코드와 같이 @Test 어노테이션의 element 요소로 해당 테스트의 실행 중에

발생할 것이라 기대하는 예외 클래스를 넣어주면 되는데

기대하는 예외가 발생했을 때 테스트가 성공했음을 알려준다.

테스트 주도 개발 (Test Driven Development)

말그대로 테스트가 개발을 주도한다는 것이고 개발이 테스트에 이끌려 간다는 것이다.

 

실제 코드를 작성한 후에 코드가 정상적으로 실행되는지 테스트를 통해 검증하는 것이 아니라

테스트를 먼저 작성한 후에 이를 바탕으로 실제 기능을 구현하는 것으로

테스트에 맞춰 개발이 진행되는 것이다.

기능설계를 위한 테스트

테스트는 어떤 조건을 가지고 어떤 동작을 했을 때 어떤 결과가 나오는지에 대해 검증하는 행위인데

이는 메서드의 기능 정의서와 다를게 없는 구성이다.

 

보통의 프로그램이 기능설계 > 구현 > 테스트라는 순서로 이루어졌다면

테스트가 기능설계의 역할도 담당하고 있는 것이다.

 

즉,  테스트 코드를 의사 코드라고 볼 수 있다.

 

테스트 코드로 간단한 틀을 만들고 테스트에 필요한 메서드들을 구현하여

테스트를 수행하는 경우 성공했다면 기능이 정상적으로 구현이 된 것이고,

실패했다면 기능에 결함이 있는 것을 바로 알 수가 있는 것이 테스트 주도 개발의 장점이다.

 

요약하면 테스트를 먼저 만들고 테스트가 성공할 때까지 가능한 빠르게 개발하는 방법이다.

테스트 코드 리팩토링 하기

테스크 코드는 기능에 영향을 주지는 않으니 굳이 보기 좋고 효율적으로 리팩토링 할 필요가

있는지 의문을 가질 수도 있지만 모든 코드는 보기 쉽고 변경이 용이해서 나쁠 것이 없다.

 

반복적인 부분을 뽑아내는 메서드 추출 리팩토링 방법도 있지만

JUnit에서 이러한 반복되는 준비 작업을 별도의 메서드로 분리하고 테스트 실행 전에

자동으로 먼저 호출해주는 기능이 있다.

@Before

어노테이션의 이름부터 누가봐도 어떤 상황의 전에 실행될거만 같이 생겼다.

 

이 어노테이션은 실제로 @Test 메서드가 실행되기 전에 먼저 실행되어야 하는

선행 메서드를 정의할 때 사용되는 어노테이션이다.

@Before
public void setUp() {
    ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
    this.dao = context.getBean("userDao", UserDao.class);

    this.user1 = new User("아이디1", "이름1", "비밀번호1");
    this.user2 = new User("아이디2", "이름2", "비밀번호2");
    this.user3 = new User("아이디3", "이름3", "비밀번호3");
}

위의 코드와 같이 반복적으로 실행되는 코드를 해당 어노테이션이 붙은 메서드에 넣어주면

테스트 메서드들이 실행될 때마다 항상 해당 메서드가 실행된 후에 실행된다.

 

@Before 어노테이션을 보면 어떤 생각이 들 수도 있는데

Before가 있으면 당연히 정반대의 역할을 수행하는 @After 어노테이션도 존재한다.

픽스처

위의 코드에서 dao 같은 경우를 픽스처라고 할 수 있는데

테스트를 수행할 때 필요한 정보나 오브젝트를 가리키는 말이다.

'Back-End > Spring' 카테고리의 다른 글

템플릿 적용하기  (0) 2023.06.13
스프링에 테스트 적용하기  (0) 2023.06.08
테스트  (0) 2023.06.07
XML 설정  (0) 2023.06.07
DI(Dependency Injection)  (0) 2023.06.07
728x90

복잡한 엔터프라이즈 애플리케이션을 효과적으로 개발하기 위해서는

객체지향과 테스트 기술을 유연하게 다룰 줄 알아야한다.

 

코드가 정확히 동작하는지를 확인하며 코드나 설계의 결함을 파악하여 제거해가는
디버깅을 통해 최종적으로 모든 결함을 제거하여 개발한 코드를 확신할 수 있게 해주는 작업

 

객체지향 기술을 통해 확장과 변화를 고려한 프로그래밍을 할 수 있다면

테스트를 통해 이러한 프로그래밍 결과를 검증할 수 있게 해준다.

기존 테스트 방식의 문제점

기존의 웹 프로그램에서 테스트를 하기 위해서는 해당 기능과 관련된 모든 기능들을

약식으로라도 구현해두고 웹 화면을 띄워 입출력을 실행한 후 관련 기능들이

모두 제대로 동작하는지 일일히 확인을 해야했다.

 

일부 기능만 테스트하기 위한 작업치고는 해야할 일이 너무 많을 뿐만 아니라

다수의 코드가 참여하는 테스트이기 때문에 어떤 기능에서 오류가 발생했는지,

어떤 요인들이 영향을 미치는지 식별하기가 쉽지 않다.

효율적으로 테스트 하기

작은 단위부터 시작하기 : 단위 테스트

위와 같은 문제들을 방지하기 위해서는 테스트하기 위한 대상에만 집중하는 것이 좋고

이를 위해서는 가능하면 작은 단위로, 관심사가 같은 것끼리 하는 것이 바람직하다.

 

이러한 작은 단위의 코드에 대해 수행하는 것은 단위 테스트라고 하며

여기서의 단위는 하나의 관심에 집중하여 효율적으로 테스트할 수 있는 범위이다.

 

단위 테스트는 주로 개발자가 만든 코드를 스스로 즉각적으로 확인하기 위해 사용되는데

이렇게 한 단위의 개발을 마칠 때마다 단위 테스트를 진행하면 해당 단위에 대해서는

완벽하게 알 수 있기 때문에 나중에 몰아서 긴 테스트를 하는 경우보다도

예외와 오류를 찾고 수정하기 훨씬 편해진다.

자동수행 테스트 코드

기존 웹 프로그램 테스트에서는 테스트를 수행하기 위해 웹 페이지를 실행한 후에

직접 값을 입력하고 전송하는 과정을 거쳐야 했지만

코드를 통해서 자동으로 테스트 데이터를 제공하면 이러한 번거로움이 줄어든다.

 

번거로운 작업이 없고 테스트를 빠르게 실행할 수 있기 때문에 자주 반복할 수 있다.

테스트는 지속적이고 점진적으로 하기

애플리케이션의 모든 로직을 다 작성한 후에 테스트를 시작하려하면

엄청난 양의 오류가 발생할 것이고 이를 그때 몰아서 찾으려하면 머리 아픈 작업이 될 것이다.

 

단위 테스트를 지속적으로 활용하여 점진적으로 코드의 완성도를 높여 나가는 것이 좋은데

속도가 느리다고 생각할 수도 있지만 이미 개발한 코드에 확신을 갖게 되고

나중에 몰아서 오류를 수정하는 것보다 훨씬 더 적은 시간으로 개발이 가능하다.

기존 테스트 코드의 문제점

public static void main(String[] args) throws ClassNotFoundException, SQLException {
    //user 추가하는 코드

    //user가 제대로 추가되었는지 확인하는 코드
    User user2 = dao.get(user.getId());
    System.out.println(user2.getName());
    System.out.println(user2.getPassword());

    System.out.println(user2.getId());
}

직접 눈으로 결과를 확인해야 함

콘솔창에 출력된 테스트 코드의 실행결과가 자신이 예상한 결과와 일치하는지

개발자가 직접 눈으로 콘솔창을 봐가며 잘못된 부분이 있는지 확인해야 한다.

일일히 테스트를 실행해야 함

테스트를 할 때마다 메인 메서드를 계속 추가한다면 상당히 번거롭고 난잡해진다.

문제점 개선하기

if (!user.getName().equals(user2.getName())) {
	//이름 테스트 실패 출력
} else if (!user.getName().equals(user2.getName())) {
	//비밀번호 테스트 실패 출력
} else {
	//테스트 성공 출력
}

코드로 검증하기

기존 테스트 코드에서 직접 눈으로 결과를 확인하교 비교해야하는 단점은

테스트 수행뿐만 아니라 결과를 검증하는 코드까지 작성하여 검증을 자동화하면 해결된다.

 

테스트의 실패는 테스트 중에 발생하는 런타임 에러결과값이 다른 에러가 있고

런타임 에러는 콘솔창에 에러 메시지가 출력되기 때문에 확인이 쉽지만

예상과 결과값이 다른 경우는 별도의 확인 작업이 필요하다.

 

분기별로 테스트의 결과를 처리해주는 등의 방법으로 결과를 직접 확인하지 않고

코드를 통해서 테스트의 성공 여부만 확인하면 된다.

테스트를 더 편하게 사용하기

메인 메서드로 만드는 테스트는 테스트 코드로서의 기능은 수행하지만

개발자들은 항상 더욱 편리한 것을 필요로 한다.

 

이를 위해서 일정한 패턴의 테스트를 생성하고, 많은 테스트를 간단히 실행하고,

종합적인 결과를 보거나 실패 원인을 빠르게 찾을 수 있는 테스트 지원 도구가 존재한다.

JUnit 프레임워크

이름에서 부터 알 수 있듯이 자바의 단위 테스트 지원 프레임워크다.

 

프레임워크의 기본 동작 원리는 IoC이기 때문에 

오브젝트를 생성하고 실행하는 일은 모두 프레임워크가 처리할 것이고

개발자는 테스트를 위해 main 메서드나 오브젝트 같은 것을 직접

만들어서 실행시키는 수고를 할 필요가 없다.

테스트 메서드로 전환하기

테스트를 메인 메서드를 통해 실행한다는 것은 제어권을 직접 가진다는 의미

이러한 제어권을 프레임워크로 넘겨줘야 한다.

@Test 
public void andAndGet() throws SQLException {
    ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
    UserDao dao = context.getBean("userDao", UserDao.class);

    //유저 정보 추가를 위한 정보 작성 코드

    dao.add(user);
}

@Test 어노테이션을 통해 테스트 메서드임을 알려주고 테스트 내용을 작성하면 되는데

테스트 메서드는 접근제어자를 반드시 public으로 선언해야 한다.

 

기존에는 조건문으로 테스트 데이터들을 비교하여 결과를 만들었다면

JUnit이 제공하는 assertThat 메서드를 사용하여 대체할 수 있다.

@Test 
public void andAndGet() throws SQLException {
    //유저 정보 추가 코드 생략
    User user2 = dao.get(user.getId());

    assertThat(user2.getName(), is(user.getName()));
    assertThat(user2.getPassword(), is(user.getPassword()));
}

 

assertThat 메서드는 첫 번째 파라미터 값이 두 번째 파라미터 값과 일치하는지 비교하여

일치하면 계속 진행(continue)하고 일치하지 않으면 테스트를 실패하게 한다.

테스트 실행하기

JUnit 프레임워크로 만든 테스트 코드도 마찬가지로 자바 프로그램이기 때문에

따로 메인 메서드를 통해 실행을 시켜줘야 실행이 된다.

public static void main(String[] args) {
	JUnit.main("@Test 메서드를 가진 클래스");
}

@Test 어노테이션이 적용된 메서드를 가진 클래스를 사용하여 실행시킨다.

'Back-End > Spring' 카테고리의 다른 글

스프링에 테스트 적용하기  (0) 2023.06.08
JUnit 자세히 알아보기  (0) 2023.06.08
XML 설정  (0) 2023.06.07
DI(Dependency Injection)  (0) 2023.06.07
싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
728x90

XML

애플리케이션 컨텍스트 생성에 사용할 설정정보를 만드는 것은 반복적인 작업이 계속해서 이뤄진다.

DI 구성이 바뀔 때마다 이런 반복적인 작업을 수행하는 것은 번거롭기 때문에

다양한 방법을 통해 이러한 설정정보를 만들 수 있는데 대표적인 방법이 XML이다.

 

XML은 단순한 텍스트 파일이기 때문에 이해하기 쉽고 별도의 빌드 작업이 필요 없고

오브젝트의 관계 변경 사항에 대한 반영이 쉽고 빠르다.

XML 설정

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
						http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="빈의 이름" class="빈 생성에 사용할 클래스">
		<property name="set을 뺀 수정자 메서드명" ref="수정자 메서드의 파라미터" />
	</bean>
</beans>

<beans> : @Configuration, 루트 엘리먼트로 사용

<bean> : @Bean, 빈을 정의(빈의 이름, 어떤 클래스를 사용해서 만들지, 의존 오브젝트)

<property> : 빈의 의존관계 정의

애플리케이션 컨텍스트 생성하기 : XML 이용

기존에는 설정정보를 사용하여 애플리케이션 컨텍스트를 생성하기 위해

AnnotationConfigApplicationContext 구현 클래스를 사용하였으니,

XML을 사용하여 생성하기 위해서는 GenericXmlApplicationContext 구현 클래스를 사용한다.

// 기존의 설정파일을 통한 애플리케이션 컨텍스트 생성
AnnotationConfigApplicationContext context = 
	new AnnotationConfigApplicationContext(DaoFactory.class);
    
UserDao dao = context.getBean("userDao", UserDao.class);

// XML을 통한 애플리케이션 컨텍스트 생성
ApplicationContext context = new GenericXmlApplicationContext("XML 설정파일 위치");

DataSource 인터페이스

DataSource 인터페이스는 DB 커넥션을 가져오는 메서드 이외에도 여러 기능을 갖고 있어서

직접 구현해서 사용하기는 어렵다.

 

하지만 이미 해당 인터페이스의 구현 클래스들이 존재하기 때문에 이를 사용하면 된다.

private DataSource dataSource;
	
public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
}

public void add(User user) throws SQLException {
    Connection c = this.dataSource.getConnection();
}

프로퍼티 값의 주입

DB 연결 정보들도 마찬가지로 XML을 통해 설정할 수 있는데

다른 빈 오브젝트의 레퍼런스를 주입하는 것이 아닌 단순한 값을 주입하기 때문에

ref 속성값 대신 value 속성값을 사용한다.

@Bean
public DataSource dataSource() {
    SimpleDriverDataSource dataSource = new SimpleDriverDataSource ();

    dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
    dataSource.setUrl("jdbc:mysql://localhost/springbook?characterEncoding=UTF-8");
    dataSource.setUsername("spring");
    dataSource.setPassword("book");

    return dataSource;
}

위의 코드와 같이 XML을 사용해서 DB의 연결 정보들을 직접 설정해주는 경우에는

연결 정보가 바뀌는 경우에는 코드를 직접 수정해야 하지만

XML을 통해 설정하는 경우에는 코드가 아닌 XML 파일만 수정하면 된다.

<bean id="myConnectionMaker" class="springbook.user.dao.DConnectionMaker">
    <property name="driverClass" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost/springbook?characterEncoding=UTF-8" />
    <property name="username" value="spring" />
    <property name="password" value="book" />
</bean>

value 속성값은 타입을 지정해주지 않아도 상관없는데

스프링이 이 값을 수정자 메서드의 파라미터 타입을 참고해서 형변환 해주기 때문이다.

'Back-End > Spring' 카테고리의 다른 글

JUnit 자세히 알아보기  (0) 2023.06.08
테스트  (0) 2023.06.07
DI(Dependency Injection)  (0) 2023.06.07
싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
스프링 IoC  (0) 2023.06.05
728x90

의존관계와 의존관계 주입

서로 의존하고 있는 관계라는 것은 한 쪽의 변화가 다른 한 쪽에 영향을 끼친거나

한 쪽이 다른 한 쪽의 멤버를 사용하는 경우 등이 의존관계에 있다고 볼 수 있다.

 

의존관계는 설계 모델에서의 의존 관계런타임 시에 만들어지는 의존관계가 있는데

스프링에서의 의존관계 주입은 이런 런타임 시의 의존관계가 있고,

이러한 런타임 시의 의존관계를 오브젝트 의존관계라고 한다.

 

즉, 실제 사용대상인 의존 오브젝트와 그것을 사용하는 주체(클라이언트) 오브젝트

런타임 시에 연결해주는 작업을 해주는 것을 의존관계 주입(DI)이라고 한다.

 

의존관계 주입은 아래와 같은 세 가지 조건을 만족해야 한다.

  • 클래스 모델 및 코드에는 런타임 시점의 의존관계가 드러나지 않게, 클래스와 클래스 간의 의존이 아닌 인터페이스를 통해서만 의존하고 있어야 함
  • 런타임 시점의 의존관계는 컨테이너와 팩토리 같은 제 3자가 결정
  • 의존관계는 사용할 오브젝트(의존 오브젝트)에 대한 참조를 외부에서 주입하여 만들어짐

쉽게 정리하자면 개발자가 코드를 설계하고 작성하는 시점에는 오브젝트 간의 연결(의존)이 없지만

런타임 시(서버가 동작할 때)에 오브젝트 간에 연결되는 것이다.

 

이전 게시글부터 현재까지 코드를 수정하는 과정을 살펴보면 

코드를 작성할 때는 인터페이스를 통해 느슨한 연결을 유지하는 방향으로 작성하고

애플리케이션 컨텍스트를 통해 런타임 시의 의존관계를 주입하였는데

이러한 방식을 스프링의 의존관계 주입이라고 이해할 수 있다.

의존관계 검색과 주입

스프링이 제공하는 IoC 방법에는 의존관계 주입말고도 의존관계 검색이라는 방법이 있는데

의존관계를 외부로부터 주입 받는 것이 아니라 스스로 검색을 이용하는 방법이다.

 

자신이 필요로 하는 의존 오브젝트를 능동적으로 검색하는 기능으로

검색만 할 뿐이지 주입까지 하는 기능은 아니다.

 

이러한 의존관계 검색을 해주는 메서드를 이전에 사용해본 적이 있는데

바로 빈 팩토리가 제공하는 getBean 메서드다.

 

일반적인 의존관계 주입의 코드를 살펴보면

public UserDao() {
	DaoFactory daoFactory = new DaoFactory();
    this.connectionMaker = daoFactory.connectionMaker;
}

이렇게 메서드나 생성자를 통해 의존관계를 주입해주지만

아래와 같이 검색, 즉 컨테이너(애플리케이션 컨텍스트)에게 요청하여 처리할 수도 있다.

AnnotationConfigApplicationContext context = 
	new AnnotationConfigApplicationContext(DaoFactory.class);
    
this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);

애플리케이션 컨텍스트를 통해 getBean 메서드를 사용하여 의존관계를 검색해서 주입할 수 있다.

어떤 방식을 사용하는 것이 좋은가?

의존관계 검색을 통해 의존관계를 주입하는 방법은 코드에 스프링 API가 직접적으로 나타나기에

애플리케이션 컴포넌트가 서버와 관련된 성격이 다른 오브젝트에 의존하게 되어 좋은 방법이 아니라서

보통 애플리케이션 컴포넌트에 의존관계를 주입하는 경우에는 일반적인 의존관계 주입을 사용한다.

 

하지만 의존관계 검색이 쓸 필요가 없었다면 알아보지도 않았을 것인데, 이러한 방법도 다 사용하는 곳이 있다.

 

애플리케이션의 기동 시점에는 최소한 한 번은 의존관계 검색을 사용해 오브젝트를 가져와야 하는데

static 메서드인 메인 메서드, 즉 스프링이 실행되는 메서드와 서버는

의존성 주입을 이용해 오브젝트를 주입 받을 방법이 없기 때문이다.

 

또한 의존관계 주입은 주입 받으려는 대상 모두가 빈 오브젝트여야 하지만

의존관계 검색은 빈 오브젝트가 아닌 경우에서도 사용할 수 있다.

 

의존관계 주입은 특정 클래스 타입으로 고정된 것은 주입 받을 수 없는데

의존관계 주입이 런타임 시에 다이나믹하게 결정해서 제공받아야 하기 때문이다.

 

즉, 클래스를 주입 받는 것이 아닌 인터페이스를 주입 받아야만 한다.

의존관계 주입의 장점과 응용

지금까지 알아본 스프링, IoC, DI 같은 모든 것들이 객체지향 설계를 지향하는 방식이라고 했듯이

이러한 의존관계 주입을 적용하면 객체지향 설계로 얻을 수 있는 장점들을 모두 챙길 수 있다.

 

간단하게 요약하면 결합도가 낮은 코드책임을 철저하게 분리하여 코드의 수정, 확장 등이 자유로워진다.

 

이러한 장점을 통해 여러가지 다양한 응용을 할 수 있다.

 

우선 이전의 내용 중에서 초난감 DAO 코드를 수정하면서 알 수 있었듯이

데이터베이스의 설정을 바꿔야 하는 경우 인터페이스를 통해 느슨하게 연결되어 있기 때문에

해당 인터페이스를 구현한 데이터베이스의 설정을 모두 사용할 수 있다는 것이다.

 

서버에서 사용될 설정, 개발시에 사용될 설정, 테스트 시에 사용될 설정들을

하나의 인터페이스를 통해 복잡한 코드 수정 없이 모두 사용할 수 있고,

이것은 객체지향 설계의 장점인 다형성과 같다고 볼 수 있다.

 

새로운 기능을 추가하고 싶은 경우도 마찬가지로

기능을 사용하고자 하는 오브젝트와 같은 인터페이스를 구현하는

새로운 오브젝트를 추가하여 의존관계를 추가/수정해주기만 하면 된다.

메서드를 이용하여 의존관계 주입하기

메서드를 이용하여 의존관계를 주입할 수 있는데 생성자를 통한 의존관계 주입보다 자주 사용된다.

 

수정자 메서드와 일반 메서드를 이용한 주입 방법이 있는데

수정자 메서드를 이용한 주입 방법부터 살펴보겠다.

 

수정자 메서드를 이용한 주입

수정자 메서드를 이용한 주입은 외부에서 내부의 속성 값을 변경할 때

주로 사용되는데 여기서 수정자는 Setter를 말한다.

 

수정자 메서드는 파라미터로 값을 전달 받을 수 있기 때문에

외부로부터 제공받은 오브젝트 참조를 저장해뒀다

내부 메서드에서 사용하는 의존관계 주입 방식에서 활용하기 좋지만

하나의 파라미터만 가질 수 있다는 단점이 있다.

private ConnectionMaker connectionMaker;

// 기존 생성자 사용
public UserDao(ConnectionMaker connectionMaker) {
    this.connectionMaker = connectionMaker;
}

// 수정자 메서드 사용
public void setConnectionMaker(ConnectionMaker connectionMaker) {
    this.connectionMaker = connectionMaker;
}

 

@Bean //기존
public UserDao userDao() {
    UserDao dao = new UserDao(connectionMaker());
    return dao;
}

@Bean //수정자 메서드
public UserDao userDao() {
    UserDao dao = new UserDao();
    dao.setConnectionMaker(connectionMaker());
    return dao;
}

일반 메서드를 이용한 주입

수정자 메서드와 비슷하지만 수정자 메서드의 단일 파라미터 단점을 보완한

여러 개의 파라미터를 받기 위해 수정자 메서드 방식 대신 사용하는 방법이다.

 

보통은 수정자 메서드를 가장 많이 사용하며, 이외에도 다양한 의존관계 주입 방법이 있다.

 

'Back-End > Spring' 카테고리의 다른 글

테스트  (0) 2023.06.07
XML 설정  (0) 2023.06.07
싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
스프링 IoC  (0) 2023.06.05
IoC(Inversion of Control, 제어의 역전)  (0) 2023.06.05
728x90

오브젝트의 동일성과 동등성

동일성

오브젝트의 주소가 같다는 의미로 == 연산자를 통해서 비교한다.

즉, 서로 동일한 오브젝트라면 해당 오브젝트는 두 개가 아니라 하나만 존재하고 있다는 것

동등성

오브젝트의 정보가 같다는 내용으로 equals 메서드를 통해서 비교한다.

즉, 서로 동등한 오브젝트라면 오브젝트가 가지고 있는 내용은 같지만 주소는 다르므로

동등한 오브젝트는 하나가 아니라 두 개가 같은 내용으로 존재하고 있다는 것

 

equal 메서드를 따로 구현하지 않은 경우에는 Object 클래스에 있는 것을 사용하여 동일성을 비교함

팩토리와 애플리케이션 컨텍스트의 동일성 차이

스프링을 적용하지 않은 기존의 팩토리를 통해 오브젝트를 생성하는 경우에는

오브젝트가 생성될 때마다 항상 새로운 오브젝트가 생성 되어

주소값이 다른, 동일성이 같지 않은 오브젝트가 생성된다.

 

스프링의 애플리케이션 컨텍스트를 통해 생성된 오브젝트

항상 같은 주소값을 가진 오브젝트를 반환하여 동일성이 보장된다.

 

이렇게 주소값이 같은 동일한 하나의 오브젝트만 생성하여 사용하는 것을

싱글톤 패턴이라고 하고 이런 싱글톤 방식으로 오브젝트를 만드는

IoC 컨테이너인 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는

싱글톤 레지스트리라고 한다.

 

싱글톤 레지스트리와 싱글톤 패턴은 엄연히 다른 개념이다.

싱글톤으로 빈을 생성하는 이유

스프링은 자바 엔터프라이즈 기술을 사용하는 서버환경에서 주로 사용된다.

 

서버에서는 클라이언트의 요청을 받아 작업을 수행해야하는데

클라이언트의 요청이 올 때마다 오브젝트를 계속 생성이 된다면

서버에 과부하가 걸려 감당하기가 힘들다.

 

그렇기 때문에 사용자의 요청을 담당하는 여러 스레드에서

하나의 오브젝트를 공유하여 동시에 사용한다.

싱글톤 패턴의 단점

싱글톤으로 만드려는 생성자의 접근 제어자를 private으로 하여

외부에서 오브젝트를 더 이상 생성할 수 없게 제한하고

해당 클래스 안에서 정적 멤버로 오브젝트를 생성한 후에

getter를 통해 해당 싱글톤 오브젝트를 사용할 수 있게 한다.

 

싱글톤을 구현하는 코드는 아래와 같다.

public class Single{
    private static Single INSTANCE;

    private Single() {}
    
    public Single getSingle() {
    	return INSTANCE;
    }
}

오브젝트를 클래스 자신만 생성할 수 있게 생성자를 private으로 제한하기 때문에 상속할 수 없고,

static 멤버를 사용하기 때문에 객체지향의 가장 큰 특징인 상속과 다형성을 사용할 수 없다.

 

오브젝트가 생성되는 방식도 제한이 되어있기 때문에

테스트를 하기 위해 오브젝트를 생성하거나 초기화 혹은 값을 주입하는 작업이 힘들다.

 

서버환경에서 싱글톤이 하나만 생성되는 것이 보장되지 않기도 하는데.

서버의 클래스 로더가 어떻게 구성되었는지에 따라, 혹은 서버가 여러 개의 JVM에 분산되어 설치되는 등에

따라 여러개의 싱글톤 오브젝트가 생기기 때문에 완벽한 싱글톤 오브젝트라고 볼 수 없기도 하다.

 

또한 싱글톤은 정적 멤버로 만들기 때문에 언제 어디서든 접근할 수 있어서

무분별한 접근, 수정, 공유의 문제가 생길 수 있다.

싱글톤 레지스트리

위와 같이 자바의 기본적인 싱글톤 패턴의 구현 방식은 큰 단점들이 있어서

스프링은 싱글톤 레지스트리라는 직접 싱글톤을 만들고 관리하는 기능이 존재한다.

 

private 생성자와 스태틱 메서드를 사용하는 클래스가 아닌

평범한 자바 클래스를 싱글톤으로 활용할 수 있게 해준다.

 

스프링에서는 오브젝트의 생성, 관계설정, 사용 등에 대한 모든 제어권을

애플리케이션 컨텍스트에게 넘기기 때문에 스태틱과 private 생성자를 쓰지 않아도

애플리케이션 컨텍스트가 자동으로 관리해주기 때문에 가능한 것이다.

 

즉, 애플리케이션 컨텍스트 덕분에 스태틱을 사용하지 않고 public 생성자도 사용할 수 있어져서

자바의 객체지향적인 방식들을 모두 사용할 수 있는 단점이 없는 싱글톤 패턴을 사용할 수 있게 된다.

싱글톤과 오브젝트의 상태

싱글톤은 여러 스레드, 즉 여러 요청이 들어오면 동시에 접근해서 사용할 수 있기 때문에

서비스 형태의 오브젝트로 사용되는 무상태 방식으로 만들어져야 한다.

 

무상태 방식이란 인스턴스 필드의 값을 변경하고 유지하지 않는 방식으로

상태정보를 내부에 갖고 있지 않는 방식을 말하는데

이러한 방식을 사용하는 이유는 만약에 두 명의 사용자가 동시에

A라는 값을 변경했다면 가장 마지막으로 값을 변경한 사용자의 변경사항만

저장되는 상황들을 방지하기 위해서다.

 

이는 싱글톤 오브젝트, 즉 하나의 객체를 모든 사용자가 동시에 사용하기 때문이다.

 

상태를 저장하지 않으면 요청에 대한 결과나 데이터베이스와 서버 등으로부터 생성된 정보를

저장할 공간이 없지만 이런 경우에는 파라미터와 로컬 변수 혹은 리턴 값처럼 한 번 사용되고

버려지는 정보를 사용하여 처리하면 동시성 문제와 메모리 문제를 해결할 수 있다.

 

따라서 개별적으로 바뀌는 정보는 로컬 변수, 파라미터로 주고 받는 것이 좋고

싱글톤 빈을 저장하거나 읽기 전용 정보 같은 경우는 그대로 사용해도 상관 없다.

스프링 빈의 스코프

스프링에서 빈이 생성되고, 존재하고, 적용되는 범위

기본 스코프는 싱글톤 상태이다.

 

싱글톤 스코프는 컨테이너가 존재하는 동안, 즉 서버가 유지되는 동안 계속 유지된다.

 

대부분의 경우 스코프는 싱글톤 스코프이지만, 상황에 따라 바뀌기도 하는데

빈을 요청할 때마다 새로운 오브젝트를 만드는 프로토타입 스코프와

사용자로부터 HTTP 요청이 온 경우에 생성되는 요청 스코프, 세션 스코프 등이 있다.

'Back-End > Spring' 카테고리의 다른 글

XML 설정  (0) 2023.06.07
DI(Dependency Injection)  (0) 2023.06.07
스프링 IoC  (0) 2023.06.05
IoC(Inversion of Control, 제어의 역전)  (0) 2023.06.05
초난감 DAO와 객체지향  (0) 2023.06.05
728x90

DaoFactory를 통해 간단하게 IoC가 무엇인지 알게 되었고

이제 이 팩토리를 스프링의 도움을 받아 IoC를 적용해본다.

 

스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트

오브젝트 단위의 애플리케이션 컴포넌트

IoC가 적용된 오브젝트

빈 팩토리

빈의 생성, 관계설정 등의 제어를 담당하는 IoC 오브젝트

이를 더 확장한 애플리케이션 컨텍스트를 주로 사용

애플리케이션 컨텍스트

애플리케이션 설정정보를 바탕으로 빈의 생성과 관계설정을 총괄하는 IoC 엔진 같은 역할

애플리케이션 컨텍스트 적용하기 : 설정정보 생성하기

팩토리는 애플리케이션의 설계도 같은 것이라고 했는데

이말은 팩토리를 애플리케이션 컨텍스트의 설정정보로 사용할 수 있다는 것이다.

 

@Configuration 어노테이션을 사용하여 DaoFactory를

애플리케이션 컨텍스트에 사용 할 설정정보라는 것을 알려준다.

 

오브젝트를 생성하는 메서드는 오브젝트를 반환하니 당연히 빈으로 등록해줘야하고

@Bean 어노테이션을 사용하여 빈으로 등록할 것이라 알려준다.

@Configuration
public class DaoFactory {
	@Bean
	public UserDao userDao() {
		UserDao dao = new UserDao(connectionMaker());
		return dao;
	}

	@Bean
	public ConnectionMaker connectionMaker() {
		ConnectionMaker connectionMaker = new DConnectionMaker();
		return connectionMaker;
	}
}

즉, 오브젝트를 관리하는 팩토리를 애플리케이션 컨텍스트의 설정정보로 설정하고

오브젝트를 생성하거나 반환하는 메서드라면 빈으로 등록하기만 하면 된다는 것이다.

 

애플리케이션 컨텍스트냉장고라고 생각하면

@Bean 어노테이션이 적용되어 있는 것들은 냉장고에 보관하고 있는 식재료

@Configuration 어노테이션이 적용되어 있는 클래스는

냉장고 안에 보관한 식재료들의 목록을 정리한 문서라고 볼 수 있다.

애플리케이션 컨텍스트 적용하기

ApplicationContext를 구현한 클래스는 여러 종류가 있고

@Configuration 어노테이션을 적용한 설정정보를 사용하여 ApplicationContext를 만들기 위해서는

AnnotationConfigApplicationContext라는 구현 클래스를 사용하면 된다.

AnnotationConfigApplicationContext context = 
new AnnotationConfigApplicationContext(DaoFactory.class);

이렇게 구현 클래스를 사용하여 애플리케이션 컨텍스트를 생성하면

위에서 말한대로 애플리케이션 컨텍스트라는 냉장고에 식재료들이 담겨져 있고

이를 getBean 메서드를 사용하여 저장된 오브젝트(식재료)를 가져올 수 있다.

UserDao dao = context.getBean("userDao", UserDao.class);

getBean 메서드는 애플리케이션 컨텍스트가 관리하는 오브젝트를 요청하는 메서드로

첫 번째 인자로는 등록된 빈의 이름을 받는데 굳이 이름을 지정하는 이유는

해당 오브젝트를 다른 구성으로 생성하는 다른 메서드를 추가할 수 있기 때문이다.

 

두 번째 인자처럼 제너릭 메서드 방식을 사용하면

기존의 Object 타입으로 오브젝트를 가져와 형변환 하여 사용해야 했던 것을 생략할 수 있다.

 

위의 코드는 아래의 코드와 비슷하다고 볼 수 있다.

UserDao dao = new UserDao();

애플리케이션 컨텍스트가 모든 오브젝트를 관리하고

컨텍스트를 통해 오브젝트를 가져와서 사용할 뿐이다.

애플리케이션 컨텍스트의 동작방식

애플리케이션 컨텍스트 인터페이스는 빈 팩토리 인터페이스를 상속 받았기 때문에 빈 팩토리라고 볼 수도 있고,

애플리케이션 컨텍스트를 IoC 컨테이너 혹은 스프링 컨테이너라고도 한다.

 

애플리케이션 컨텍스트는 자동으로 IoC를 적용하고 자신이 관리하는 모든 오브젝트에 대해

오브젝트의 생성과 관계 설정을 처리해준다.

 

하지만 애플리케이션 컨텍스트 스스로 오브젝트를 생성할 수는 없고

위에서 작성한 코드처럼 @Configuration 어노테이션이 붙은 설정정보 클래스의

오브젝트를 생성하여 반환하는 메서드를 사용하여 getBean 메서드를 사용한

클라이언트에게 전달해주기만 할 뿐이다.

 

getBean("userDao") → 애플리케이션 컨텍스트의 빈 목록에서 해당하는 빈 조회

→ 해당하는 빈의 메서드를 실행 → 실행된 메서드가 오브젝트를 생성하고 오브젝트를 반환

→ 애플리케이션 컨텍스트가 클라이언트에 오브젝트 전달

애플리케이션 컨텍스트의 장점

기존의 팩토리 방식은 오브젝트를 추가할 때마다 어떤 팩토리 클래스를 사용할지 알아야하고

필요할 때마다 팩토리 오브젝트를 생성해야하는 번거로움이 있다.

 

애플리케이션 컨텍스트를 사용하면 일관된 방식으로 원하는 오브젝트를 가져올 수 있고,

단순한 방식을 사용해 설정정보를 만들 수 있다.

 

오브젝트 생성, 관계 설정, 오브젝트 생성 방식, 상황에 따른 시점과 전략 수정, 자동생성,

후처리, 정보의 조합, 설정 방식의 다변화, 인터셉팅 등을 포함한 애플리케이션 컨텍스트가

제공해주는 많은 기능뿐만 아니라 빈 팩토리를 상속 받았기 때문에

빈이 사용할 수 있는 기능들도 사용할 수 있다.

 

인텔리제이가 자바 개발을 위한 IDE라면 애플리케이션 컨텍스트는 오브젝트를 위한 IDE다.

 

또한, 빈을 쉽고 다양하게 조회할 수 있는데

getBean 메서드는 빈의 이름을 사용해 빈을 찾을 수 있고,

타입이나 특별한 어노테이션이 적용되어 있는 빈도 조회 가능하다.

용어 정리

스프링이 직접 IoC 방식으로 관리하는 오브젝트

 

빈 팩토리

스프링의 IoC를 담당하는 핵심 컨테이너

빈 등록, 생성, 조회, 부가적인 관리 등을 담당

빈 팩토리를 직접적으로 사용하지는 않고 애플리케이션 컨텍스트를 통해 사용

getBean 메서드 같은 경우가 빈 팩토리에 정의되어 있다.

 

애플리케이션 컨텍스트

빈 팩토리를 확장한 IoC 컨테이너

빈 팩토리 + 스프링의 추가 기능

 

설정정보/설정 메타정보

애플리케이션 컨텍스트/빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보

 

컨테이너/IoC 컨테이너

IoC 방식으로 빈을 관리하는 역할을 수행한다.

역할이 애플리케이션 컨텍스트/빈 팩토리와 같기 때문에

보통 스프링 컨테이너 하면 애플리케이션 컨텍스트와 같다고 본다.

 

스프링 = 스프링 컨테이너 = 애플리케이션 컨텍스트

'Back-End > Spring' 카테고리의 다른 글

DI(Dependency Injection)  (0) 2023.06.07
싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
IoC(Inversion of Control, 제어의 역전)  (0) 2023.06.05
초난감 DAO와 객체지향  (0) 2023.06.05
초난감 DAO 리팩토링하기  (0) 2023.06.05

+ Recent posts