Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5주차] (김동하, 이승철, 이세원) #2

Open
sunwootest opened this issue Jul 25, 2023 · 3 comments
Open

[5주차] (김동하, 이승철, 이세원) #2

sunwootest opened this issue Jul 25, 2023 · 3 comments
Assignees
Labels

Comments

@sunwootest
Copy link
Collaborator

  • 5주차
    • 1장
    • 2장
    • 3장
@d11210920
Copy link

d11210920 commented Jul 30, 2023

토비의 스프링 2장

테스트

  • 테스트는 관심사의 분리라는 원리를 적용하여 테스트 하고자 하는 대상에만 집중해서 테스트하는 것이 좋다. (Unit Test)
  • 테스트는 코드에 대해 확신을 갖게 해준다.
  • 테스트코드는 자동으로 수행하는 코드를 작성하는 것이 좋다.
    사람의 눈으로 확인하는 과정이나, 사람이 일일이 설정을 해줘야 한다면 실수할 가능성도 있다.

테스트 검증의 자동화.

아래 코드로 작성했을 때, 우리는 화면에 표시된 정보들이 다 맞는지 눈으로 확인을 해봐야만 맞는지를 테스트할 수 있었다.

수동 검증

System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getUd() + " 조회 성공);

하지만 아래 코드처럼 자동으로 검증하도록 바꾸면 우리는 테스트 성공인지 실패인지만 확인해 보면 된다.

자동 검증

if(!user.getName().equals(user2.getName())) {
       System.out.println("테스트 실패 (name)");
}
else if(!user.getPassword().equals(user2.getPassword())) {
       System.out.println("테스트 실패 (password)");
}
else{
       System.out.println("조회 테스트 성공");
}


JUnit framework

  • JUnit 프레임워크가 요구하는 조건은 두가지가 있다.
  1. 메소드는 public으로 선언되어야 한다.
  2. 메소드에 @test라는 어노테이션을 붙여줘야 한다.

위에서 잠깐 보았던

if(!user.getName().equals(user2.getName())) { . . . }

이 코드를 JUnit이 제공해주는 assertThat이라는 스태틱 메소드를 이용하여 다음과 같이 변경할 수 있다.

assertThat(user2.getName(), is(user.getName()));
  • JUnit은 예외가 발생하거나 assertThat에서 실패하지 않고 메소드의 실행이 완료되면 테스트가 성공했다고 인식한다.
    만약 실패한다면 OK 대신 FAILURES!! 라는 내용이 출력되고, 수행한 테스트 중에서 몇 개의 테스트가 실패했는지 보여준다.
    출력된 메시지를 살펴보면 실패한 이유도 알려준다.
  • JUnit은 테스트 메소드의 실행 순서를 보장해 주지 않는다. 따라서 테스트의 결과가 테스트 실행 순서에 영향을 받는다면, 예를들어 어느 메소드에서 등록한 정보를 다른 테스트에서 활용하는 식으로 테스트를 만든다면 그는 테스트를 잘못 만든것이다.

JUnit Test 순서 지정 (책 이외의 내용)

기본적으로 Junit은 테스트 메소드의 순서가 랜덤이지만, 지난 스터디때 배운 기억이 있어서 정리를 해보았다.

  1. 메소드 이름(알파벳) 순서로 실행
    테스트 클래스에 @TestMethodOrder 어노테이션에 MethodOrderer.MethodName 지정하면 메소드 이름(알파벳) 순서로 실행 시킬 수 있다.
MethodName 코드

@TestMethodOrder(MethodOrderer.MethodName.class)
public class MethodNameOrderTest {
    @Test
    void bear() {
        System.out.println("bear");
    }
    @Test
    void ant() {
        System.out.println("ant");
    }
    @Test
    void cat() {
        System.out.println("cat");
    }
    @Test
    void dog() {
        System.out.println("dog");
    }
}

  1. 지정된 @order에 의한 실행 순서
    @TestMethodOrder에 MethodOrderer.OrderAnnotation 을 사용하면, @order 어노테이션에서 순서를 명시할 수 있다
@order 코드

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class JUnit5Test {

    @Test
    @Order(3)
    void bear() {
        System.out.println("bear");
    }

    @Test
    @Order(4)
    void ant() {
        System.out.println("ant");
    }

    @Test
    @Order(2)
    void cat() {
        System.out.println("cat");
    }

    @Test
    @Order(1)
    void dog() {
        System.out.println("dog");
    }
}


  • 예외가 예상이 된다면 다음과 같은 방법을 사용할 수 있다.
  1. null과 같은 특별한 값을 리턴한다.
  2. 해당하는 정보를 찾을 수 없다고 예외를 던진다.

우선, 2번을 살펴보겠다.

@Test(expected=EmptyResultDataAccessException.class)

이와 같이 테스트 어노테이션을 작성해 준다면, 보통 테스트와는 반대로 정상적으로 테스트 메소드를 마치면 실패하고, epected에서 지정한 예외가 던져지면 테스트가 성공한다. 즉, 예외가 반드시 발생해야 하는 경우를 테스트하고 싶을때 유용하게 쓸 수 있다.


테스트가 이끄는 개발

이 책에서 나온 과정은 테스트를 먼저 만들고, 테스트가 실패하는 것을 보고 실제 코드에 손을 대기 시작한다.
이런 순서를 따라 개발을 진행하는 전략이 실제로 존재한다. 이를 테스트 주도 개발 TDD (Test Driven Development) 라고 한다.
이러한 개발의 장점은 다음과 같다.

  • 개발을 하다보면 테스트를 만들기 귀찮아지거나, 성의없는 테스트를 작성하게 될 염려가 있지만, 테스트를 먼저 만들다보니 절대 빼먹지 않는다는 장점이 있다.
  • 자연스럽게 단위 테스트를 작성할 수 있다.

테스트 코드 개선

  1. 중복 코드 제거
  • 이전에는 중복되는 코드를 제거할 때 별도의 메서드로 추출하는 방법을 적용하였지만, JUnit이 제공하는 기능을 활용하면 테스트를 실행할 때마다 반복되는 준비 작업을 별도의 메소드에 넣게 해주고, 이를 매번 테스트 메소드를 실행하기 전에 먼저 실행시켜주는 기능이 있다.

Junit의 테스트 수행 방식

  1. 테스트 클래스에서 @test가 붙은 public이고 파라미터가 없는 테스트 메소드를 모두 찾는다.
  2. 테스트 클래스의 오브젝트를 하나 만든다.
  3. @before가 붙은 메소드가 있으면 실행한다.
  4. @test가 붙은 메소드 하나를 호출하고 테스트 결과를 저장해둔다.
  5. @after가 붙은 메소드가 있으면 실행한다.
  6. 나머지 호출이 안된 @test가 붙은 메소드에 대해 2~5번을 반복한다.
  7. 모든 테스트의 결과를 종합하여 돌려준다.

책에서 나온대로 간단히 정리해보면 이와같은 방식으로 테스트가 수행이 된다.

JUnit의 테스트 메소드 실행 방법 그림

image


어플리케이션 컨텍스트 관리

@Runwith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml)
public class testClass{
      @Autowired
      private ApplicationContext context;

      . . .

}
  1. @RunWith
    • 이 어노테이션은 JUnit 프레임워크의 테스트 실행방법을 확장할때 사용하는 어노테이션이다.
    • 위 코드와 같이 SpringJUnit4ClassRunner라는 Junit용 테스트 컨텍스트 프레임워크 확장 클래스를 지정해주면 JUnit이 테스트를 진행하는 중에 테스트가 사용할 어플리케이션 컨텍스트를 만들고 관리하는 작업을 진행해준다.
  2. @ContextConfiguration
    • ContextConfiguration은 자동으로 만들어줄 어플리케이션 컨텍스트의 설정파일 위치를 지정한다.
  • 이렇게 지정해두면 매 테스트 메소드가 실행될 때마다 서로 다른 context를 만드는 것이 아니라 하나의 context가 사용이 된다.
  • 서로 다른 클래스에 위 코드와 같이 작성해두면 서로 다른 클래스 내에서도 동일한 context만이 만들어져 사용된다.
  • 이와 같이 한개의 어플리케이션 컨텍스트만 만들어서 사용하면 성능이 대폭 상승한다.

DI와 테스트

테스트를 잘 활용하려면 자동으로 실행 가능하며 빠르게 동작 하도록 테스트 코드를 만들어야 한다.
그러기 위해서는 가능한 한 작은 단위의 대상에 국한해서 테스트해야 하는데, DI는 테스트가 작은 단위의 대상에 대해 독립적으로 만들어지고 실행되게 하는 데 중요한 역할을 한다.

  • 테스트시에 DB의 정보를 이용하면 안되므로 DI를 이용하여 테스트 중에 DAO가 사용할 DataSource 오브젝트를 바꿔주는 방법

    • 테스트용 DB에 연결해주는 DataSource를 테스트 내에서 직접 만들 수 있다.
  • 테스트 코드 내에서 의존관계를 강제로 변경하는 방법

@DirtiesContext // 테스트 메소드에서 어플리케이션 컨텍스트의 구성이나 상태를 변경한다는 것을 테스트 컨텍스트 프레임워크에 알려준다.
public class UserTest {
    @Autowired
    UserDao dao;

    @Before
    public void beforeTest() {
        . . .
        DataSource dataSource = new SingleConnectionDataSource(
                             "jdbc:mysql:/localhost/testdb". "spring", "book", true); 
//테스트에서 UserDao가 사용할 DataSource 오브젝트를 직접 생성한다

        dao.setDataSource(dataSource);
    }
}

이 방법의 장점은 XML설정파일을 수정하지 않고도 테스트 코드를 통해 오브젝트 관계를 재구성 할수 있다.
이때, 원래 스프링 테스트 컨텍스트 프레임워크를 적용했다면 어플리케이션 컨텍스트는 테스트 중에 딱 한개만 만들어지고 모든 테스트에서 공유해서 사용하는데, 이 코드에서는 의존관계를 강제로 변경하기에 나머지 모든 테스트를 수행하는 동안 변경된 어플리케이션 컨텍스트가 계속 사용된다.

따라서 위 코드에서는 @DirtiesContext 어노테이션을 추가해서 해당 클래스의 테스트에서 어플리케이션 컨텍스트의 상태를 변경한다는 것을 알려줬다. 이 어노테이션이 붙은 클래스에는 어플리케이션 컨텍스트 공유를 허용하지 않는다.

하지만 이와같은 방법으로도 조금 찜찜하다.

  • 테스트 코드전용의 설정파일을 따로 만들어두는 방법
    이렇게 하면 설정파일을 하나 더 작성하고 테스트에 맞게 수정해주는 수고만으로 테스트에 적합한 오브젝트 의존관계를 만들어 사용할수 있게 됐는데, DI를 할 수 있도록 준비해둔 덕분이다.

  • 컨테이너 없는 DI 테스트

public class UserTest {
    UserDao dao;

    @Before
    public void beforeTest() {
        . . .
        UserDao dao = new UserDao()
        DataSource dataSource = new SingleConnectionDataSource(
                             "jdbc:mysql:/localhost/testdb". "spring", "book", true); 

        dao.setDataSource(dataSource);
    }
}

이렇게 하면 어플리케이션 컨텍스트가 만들어지는 번거로움이 없으니 그만큼 테스트시간도 절약할 수 있지만, 매번 새로운 테스트 오브젝트를 만들기 때문에 UserDao가 매번 새롭게 만들어진다는 단점이 있다.

여기서 중요한점은 DI를 위해 컨테이너가 반드시 필요한 것은 아니다.
DI 컨테이너나 프레임워크는 DI를 편하게 적용하도록 도움을 줄 뿐, 컨테이너가 DI를 가능하게 해주는 것은 아니다.


학습 테스트

개발자가 자신이 만든 코드가 아닌 다른사람이 만든 코드와 기능에 대해 작성한 테스트를 학습 테스트라고 한다.

  • 학습 테스트의 장점
    1. 다양한 조건에 따른 기능을 손쉽게 확인해 볼 수 있다
    2. 학습 테스트 코드를 개발 중에 참고할 수 있다
    3. 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와준다
    4. 테스트 작성에 대한 좋은 훈련이 된다
    5. 새로운 기술을 공부하는 과정이 즐거워진다

학습테스트의 예로 다음과같은 테스트를 책에서 소개하고 있다.

  1. JUnit에 대한 학습 테스트
  2. 스프링 테스트 컨텍스트에 대한 학습 테스트

버그 테스트

버그테스트란 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트이다.
버그테스트는 실패하도록 만들어야 한다. 즉, 버그가 원인이 되서 테스트가 실패하는 코드를 만드는 것이다.
그 후 버그 테스트가 성공할 수 있도록 어플리케이션 코드를 수정하고 테스트가 성공한다면 버그가 해결된 것이다.

  • 버그 테스트 장점
    1. 테스트의 완성도를 높여준다
    2. 버그의 내용을 명확하게 분석할 수 있게 해준다
    3. 기술적인 문제를 해결하는 데 도움이 된다

@leesewon00
Copy link
Member

leesewon00 commented Jul 31, 2023

3장 템플릿


  • 상황1
    : b 제외 a, c 는 동일 행위

  • 문제점
    : a,c 는 동일한데 계속 작성해주고 있음

class TemplateClass {
    void test1() {
        System.out.println("a");
        System.out.println("b1");
        System.out.println("c");
    }

    void test2() {
        System.out.println("a");
        System.out.println("b2");
        System.out.println("c");
    }
}

public class Main {
    public static void main(String[] args) {
        TemplateClass templateClass = new TemplateClass();
        templateClass.test1();
        templateClass.test2();
    }
}
  • 상황 2
    : 중복되는 부분 메소드로 추출

  • 문제점
    : 비슷한 메소드인 test3()가 추가되면 계속 b3, b4 코드를 작성해야한다.
    즉, TemplateClass 가 영향을 직접적으로 받는다

class TemplateClass {
    void a() {
        System.out.println("a");
    }

    void c() {
        System.out.println("c");
    }

    void test1() {
        a();
        System.out.println("b1");
        c();
    }

    void test2() {
        a();
        System.out.println("b2");
        c();
    }
}

public class Main {
    public static void main(String[] args) {
        TemplateClass templateClass = new TemplateClass();
        templateClass.test1();
        templateClass.test2();
    }
}
  • 상황 3
    : 전략패턴 적용 (b3,b4 추가되어도 TemplateClass가 영향X , 단 매번 Class 생성)

  • 전략 패턴
    디자인 패턴 중 하나로 객체가 할 수 있는 행위들 각각을 전략으로 만들어 동적으로 전략 수정이 가능한 패턴
    동일 계열의 알고리즘군(a,b,c)을 정의하고 각 알고리즘을 캡슐화(strategy)하며 이들을 상호교환이 가능하도록 만든 것

class TemplateClass {
    void a() {
        System.out.println("a");
    }

    void c() {
        System.out.println("c");
    }

    void test(B b) {
        a();
        b.b();
        c();
    }
}

interface B {
    void b();
}

class B1 implements B {
    @Override
    public void b() {
        System.out.println("b1");
    }
}

class B2 implements B {
    @Override
    public void b() {
        System.out.println("b2");
    }
}

public class Main {
    public static void main(String[] args) {
        TemplateClass templateClass = new TemplateClass();
        B1 b1 = new B1();
        B2 b2 = new B2();
        templateClass.test(b1);
        templateClass.test(b2);
    }
}
  • 상황 4
    : 템플릿-콜백 패턴 적용 (b3,b4 추가되어도 TemplateClass가 영향X , 매번 Class 생성X)
class TemplateClass {
    void a() {
        System.out.println("a");
    }

    void c() {
        System.out.println("c");
    }

    void test(B b) {
        a();
        b.b();
        c();
    }
}

interface B {
    void b();
}

public class Main {
    public static void main(String[] args) {
        TemplateClass templateClass = new TemplateClass();
        templateClass.test(() -> System.out.println("b1")); // 람다 사용
        templateClass.test(new B() {
            @Override
            public void b() {
                System.out.println("b2");
            }
        });
    }
}


  • 템플릿-콜백 패턴
    전략패턴의 변형된 형태로, 변화되는 부분을 매번 클래스로 만들지 않고, ‘익명내부클래스’로 바로 생성
    템플릿 : 정해져있는 틀, 전략 패턴의 컨텍스트 (TemplateClass)
    콜백 : 익명 내부 클래스로 만들어지는 오브젝트, 정해진 틀 안에서 변화되는 부분만 유동적으로 받아서 수행 (B)
    ex) JdbcTemplate

  • 장점
    별도의 전략 클래스 없이, 전략을 사용하는 메소드에 매개변수값으로 전략 로직을 넘겨 실행하기 때문에
    전략 클래스를 일일히 만들 필요가 없음
    전략 패턴은 객체와 전략 객체 간의 의존성을 설정해 주는 팩토리 객체가 필요한데,
    템플릿 콜백 패턴은 팩토리 객체 없이 해당 객체를 사용하는 메소드에서 인터페이스의 전략을 선택
    따라서 외부에서 어떤 전략을 사용하는지 감추고 중요한 부분에 집중 가능

  • 단점
    스프링 프레임워크에서 DI를 사용하지 않게 되면, Bean으로 등록되지 않아 싱글톤 객체가 되지 않음
    인터페이스를 사용하지만, 실제 사용할 클래스를 직접 선언하기 때문에 결합도 증가


정리

고정된 작업흐름을 가지고 여기저기 자주 반복되는 코드가 있다면, 중복되는 코드를 분리할 방법을 생각
중복된 코드를 먼저 메소드로 분리하는 간단한 시도
일부 작업을 필요에 따라 바꾸어 사용해야 한다면? 인터페이스를 사이에 두고 분리해서 전략 패턴을 적용
바뀌는 부분이 한 애플리케이션 안에서 동시에 여러 종류가 만들어질 수 있다면 템플릿/콜백 패턴을 적용

@tmdcheol
Copy link

1장 오브젝트와 의존관계

모든 것은 어떠한 목적을 위해 발명된다.
스프링의 목적은 '객체지향설계' 이다. 스프링은 framework 형태로 개발자들의 객체지향설계를 인도한다.

나의 생각 => 스프링은 미래의 변화에 대비할 수 있는 코드를 짜도록 도와준다.

1장 목표 : 스프링이 관심을 갖는 object의 설계, 구현, 동작원리에 초점.

과정 설명 -> 리팩토링 및 테스트 코드 작성

User.class

public class User {
    String id;
    String name;
    String password;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

UserDao.class

public class UserDao {

    public void add(User user) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
        PreparedStatement pstmt = conn.prepareStatement(
                "insert into users(id, name, password) value(?,?,?)");

        pstmt.setString(1, user.getId());
        pstmt.setString(2, user.getName());
        pstmt.setString(3, user.getPassword());
        pstmt.executeUpdate();

        pstmt.close();
        conn.close();
    }


    public User get(String id) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book");
        PreparedStatement pstmt = conn.prepareStatement(
                "select * from users where id = ?");
        pstmt.setString(1, id);

        ResultSet rs = pstmt.executeQuery();
        rs.next();
        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));

        rs.close();
        pstmt.close();
        conn.close();

        return user;
    }
}

대체로 변화는 집중된 한가지 관심. 한가지 관심이 한 군데에 집중되게

관심사의 분리 : ex) DB 커넥션

  1. 커넥션을 가져오는 함수 getConnection()을 만들기 => 중복 코드의 메소드 추출
    private Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/springbook","spring","book");
        return conn;
    }

상속을 통한 확장

   public abstract Connection getConnection();

상속의 단점

상하위 클래스의 관계가 생각보다 밀접. => 슈퍼 클래스가 변동이 있으면, 서브 클래스는 영향을 받게 된다.
다중 상속을 허용하지 않는다. (다이아몬드)


클래스의 분리

public class UserDao {
    private final SimpleConnectionMaker simpleConnectionMaker; // 관계 존재

    public UserDao() {
        simpleConnectionMaker = new SimpleConnectionMaker();
    }

    public void add(User user) throws ClassNotFoundException, SQLException {
        Connection conn = simpleConnectionMaker.getConnection();

하지만, Connection 변경 시, UserDao 변경 사항이 발생함.
구체 클래스 의존 중. 변경사항이 발생하면 UserDao 수정 해야함.


인터페이스의 도입

public interface ConnectionMaker {
    Connection getConnection() throws ClassNotFoundException, SQLException;
}
public class UserDao {
    private final ConnectionMaker connectionMaker;

    public UserDao() {
        connectionMaker = new SimpleConnectionMaker(); // 아직도 의존
    }

UserDao는 인터페이스만 의존한다. 다형성을 활용한 것.
그러나 원점이다. 결국 구체 클래스를 명시 해야한다.


관계설정 책임의 분리

public class UserDao {
    private final ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }

외부에서의 주입
관계를 설정(하나의 관심사)하는 역할을 하는 클래스를 만들자.
UserDao 내에서 ConnectionMaker를 생성하는 코드는 이제 존재하지 않는다.
외부로 위임한다. 제어의 역전이 일어난 것이다.


원칙과 패턴

OCP : 개방 폐쇠 원칙

클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

ex) Connection 변경이 이루어진 상황이다.
UserDao 수정은 발생할 필요가 없다. => 변경에 닫혀있다.
ConnectionMaker의 구체 클래스를 새로 생성한다. => 확장에 열려 있다.

외부에서 주입하는 관계 설정 클래스가 따로 존재하기 때문에 가능한 것.


높은 응집도와 낮은 결합도

높은 응집도

connection 기능을 하나의 클래스로 응집도를 높이자.
ex) db connection 관련해서 변동이 있으면 connectionMaker 구현 클래스만 새로 만들고, 그 클래스만 테스트하면 된다.
응집도가 낮다면? connection 관련한 코드가 흩어져, 모든 코드들을 수정해야 한다.

낮은 결합도

느슨하게 연결된 형태를 유지하는 것이 바람직하다. 서로 독립적이고 알 필요도 없게 만들어 주는 것.
ex) ConnectionMaker (interface) 를 통해서 결합도를 낮춘다.
A 클래스가 B 인터페이스를 의존한다. A 클래스 (클라이언트는) B 인터페이스의 정보만 알고, 구체 클래스가 어떤 일을 수행하는지 관심이 없다.


제어의 역전(IOC)

public class DaoFactory {

    public UserDao userDao() {
        ConnectionMaker connectionMaker = new SimpleConnectionMaker();
        UserDao userDao = new UserDao(connectionMaker);
        return userDao;
    }
}

DaoFactory 클래스를 만든다. DaotFactory에게 제어권이 생긴 것이다.
객체 생성 정의하고 레퍼런스를 리턴한다.

설계도로서의 팩토리

오브젝트 내에서 구조를 결정하는 작업(하나의 관심사)를 분리한 것.

제어의 역전

프로그램의 제어 흐름 구조가 뒤바끼는 것
자신이 사용할 오브젝트를 스스로 선택 X. 제어 권한을 위임받은 특별한 오브젝트에 의해 결정되고 만들어진다.
ex) UserDao는 ConnectionMaker를 생성하지 않는다. UserFactory가 코드의 흐름을 제어한다.

프레임워크는 제어의 역전 개념이 적용된 대표적인 기술


###스프링의 Ioc
스프링은 오브젝트(Bean)을 제어한다. framework 인 것이다.

Bean

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

BeanFactory

등록 생성 조회 관리. 스프링의 Ioc 를 담당하는 핵심 컨테이너.

애플리케이션 컨텍스트

beanFactory를 확장. 스프링이 제공하는 애플리케이션 지원 기능을 모두 포함한다.

설정정보/설정 메타정보

IOC를 적용하기 위해 사용하는 메타정보 : Configuration(청사진)
이 설정 메타정보를 통해서 스프링은 Ioc Container 생성
메타정보는 xml, java code 등 다양한 방식 존재
이 또한 스프링이 interface 를 통해서 가능한 것.
설정 정보가 어떠한 형태로 주어져도, Ioc Container 생성이 가능한 것.

@bean 어노테이션으로 스프링이 관리하는 bean임을 명시
@configuration 어노테이션으로 오브젝트 설정을 담당함을 명시 -> 이 또한 하나의 bean


싱글톤 레지스트리와 오브젝트 스코프

싱글톤

인스턴스를 1개만 생성하여 공유해서 사용

빈 객체는 싱글톤으로 관리가 된다.
물론 스코프 설정을 프로토타입으로 하여, 생성에만 관여하고, 싱글톤으로 spring이 관리하지 않도록 할 수 있다.
싱글톤 레지스트리로서의 애플리케이션 컨텍스트

순수 java로의 싱글톤 패턴의 한계

  • 상속 불가
  • 테스트화가 어렵다 (생성 방식 제한적)
  • 서버환경에서는 하나 만들어지는거 보장 X

스프링을 통한 싱글톤

순수 자바로의 싱글톤 패턴과 달리, 스프링이 지지하는 객체지향적인 설계방식과 디자인 패턴을 적용하는데 아무런 제약이 없다
테스트 코드를 짤 때도, 자유롭게 생성 가능하다.

싱글톤 주의점

  • 상태가 존재하지 않아야 한다
  • 멀티 스레드 환경에서 여러 스레드가 접근 시 문제 발생
  • 지역 변수와 parameter를 이용하여 해결한다.

DI

스프링은 Ioc 컨테이너로 object를 관리한다.
의존 관계를 맺어줄 때는 런타임 때 검색방식으로 이루어진다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants