[토비의 스프링 3.1] 1주차 스터디 - 오브젝트와 의존관계

리리·2025년 1월 24일
0

토비의 스프링

목록 보기
3/6

스터디 날짜 24.07.05.
스터디 범위 1장. 오브젝트와 의존관계

스프링이 가장 관심을 많이 두는 대상은 오브젝트다. 스프링을 이해하려면 먼저 오브젝트에 깊은 관심을 가져야 한다. 애플리케이션에서 오브젝트가 생성되고 다른 오브젝트와 관계를 맺고, 사용되고, 소멸하기까지의 전 과정을 진지하게 생각해볼 필요가 있다.



POJO 프로그래밍

💬 스프링은 자바의 기술이 복잡해지면서 잃어버린 객체지향 언어의 본질과 장점을 되살릴 수 있도록 도와주는 도구이다. 스프링은 가장 단순한 객체지향적인 개발 모델, POJO 프로그래밍을 주장한다.

진정한 POJO란,
1 png

  • 객체지향적인 원리에 충실하면서
  • 특정 환경과 기술에 종속되지 않고
  • 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다.

공통 프로그래밍 모델

  1. IoC/DI
    오브젝트의 생명주기와 의존관계에 대한 프로그래밍 모델이다.
    유연하고 확장성이 뛰어난 코드를 만들 수 있게 도와주는 객체지향 설계 원칙과 디자인 패턴의 핵심 원리를 담고 있으며 스프링 프레임워크의 근간이 된다.

  2. PSA (서비스 추상화)

    구체적인 기술과 환경에 종속되지 않도록 유연한 추상 계층을 두어 이식성이 뛰어나며 유연한 애플리케이션을 만들 수 있게 된다.

  3. AOP

    애플리케이션 코드에서 부가적인 기능을 독립적으로 모듈화하는 프로그래밍 모델이다.


1.1 초난감 DAO

💁 DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다.

💬 사용자 정보를 저장할 때는 자바빈 규약을 따르는 오브젝트를 이용하면 편리하다.


✅ 자바빈 규약의 규칙

  1. 자바빈은 기본 패키지 이외의 특정 패키지에 속해 있어야 한다.

  2. 기본 생성자가 존재해야 한다.

  3. 멤버변수의 접근제어자는 private로 선언되어야 한다.

  4. 멤버변수에 접근 가능한 getter와 setter 메서드가 존재해야 한다. (3번의 규칙을 지키기 위함)

  5. getter와 setter는 접근제한자가 public으로 선언되어야 한다.

  6. 직렬화 되어 있어야 한다. (선택사항)

    • 직렬화란?
      참고 링크

      • Serialize,
        직렬화를 위한 인터페이스로 객체를 파일에 저장하거나, 다른 서버로 보내거나 받거나 등의 일을 하기 위해 구현한다.

      • 자바 객체를 serialize하기 위해서는,
        java.io.Serializable 인터페이스를 구현하도록 한다. 해당 인터페이스는 멤버변수나 메서드가 존재하지 않는 maker interface이다.

      • Serialization 특징

      1. 부모 클래스가 Serializable 인터페이스를 구현하면 자식 클래스는 자동으로 Serializable하다.
      2. non-static 변수만 Serialization으로 저장될 수 있다.
      3. 보안 등의 이유로 어떤 멤버 변수가 Serialization되지 않기를 원한다면 해당 데이터를 transient 데이터로 지정하도록 한다.
        class Member implements Serializable {
        	transient String password;
        	String name;
        	...
        }
      4. 해당 객체가 deserialized 될 때 해당 객체의 생성자는 호출되지 않는다.
      5. Serializable한 객체와 연관되어 있는 객체 또한 Serializable 인터페이스를 반드시 구현해야 한다.
        class ObjectA implements Serializable {  
        	//ObjectB는 반드시 Serializable을 구현해야 함  
        	ObjectB oj = new ObjectB();
        }

        Serializable을 implement하여 직렬화가 가능하다. 해당 객체에 영속성을 부여하기 위해서 사용되는 매커니즘이다.

    • marker interface란?
      참고 링크

      💁 인터페이스 내부에 아무 것도 없는 인터페이스로, 객체의 타입과 관련된 정보를 제공해 준다. 컴파일러와 JVM은 이 마커 인터페이스를 통해 객체에 대한 추가적인 정보를 얻을 수 있다.

      • ❓객체의 타입과 관련된 정보를 제공해준다
        NotSerializableException

        import java.io.*;
        
        public class MarkerInterfaceTest {
            public void serializableTest() throws IOException {
                File f = new File("test.txt");
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
        
                // 이 부분이 객체를 파일로 저장하는 부분
                objectOutputStream.writeObject(new SomeObject("kjhoon", "kjhoon0330@snu.ac.kr"));
            }
        
            public static void main(String[] args) {
                MarkerInterfaceTest t = new MarkerInterfaceTest();
                try {
                    t.serializableTest();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        
        // 인터페이스를 구현하지 않은 임의의 오브젝트
        class SomeObject {
            private String name;
            private String email;
        
            public SomeObject(String name, String email) {
                this.name = name;
                this.email = email;
            }
        }
        • 해결 방법
        class SomeObject implements Serializable {
            // Serializlble 인터페이스 구현
            // 내부 생략
        }
        • 해결된 이유
          아래 사진은 ObjectOutputStreamwriteObject 함수 내부를 보여준다. objSerializable의 인스턴스인 경우 NotSerializableException이 발생되지 않도록 처리하고 있다. 이는 Serializable이라는 마커 인터페이스가 객체에 추가적인 정보를 주입시키고 있음을 이야기한다.

          2 png

1.2 DAO의 분리

개발자가 객체를 설계할 때 가장 염두에 둬야 할 사항은 바로 미래의 변화를 어떻게 대비할 것인가이다. 객체지향 설계와 프로그래밍이 이전의 절차적 프로그래밍 패러다임에 비해 초기에 좀 더 많은, 번거로운 작업을 요구하는 이유는 객체지향 기술 자체가 지니는, 변화에 효과적으로 대처할 수 있다는 기술적인 특징 때문이다.


💁 템플릿 메소드 패턴

  • 슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든다.
  • 서브클래스에서는 추상 메소드를 구현하거나, 훅 메소드를 오버라이드하는 방법을 이용해 기능의 일부를 확장한다.
    • 슈퍼클래스에서 디폴트 기능을 정의해두거나 비워뒀다가 서브클래스에서 선택적으로 오버라이드할 수 있도록 만들어둔 메소드를 말한다.

💁 팩토리 메소드 패턴

  • 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 방법
  • 주로 인터페이스 타입으로 오브젝트를 리턴하므로 서브클래스에서 정확히 어떤 클래스의 오브젝트를 만들어 리턴할지는 슈퍼클래스에서는 알지 못하며 관심도 없다.

3 png

💆‍ 관심사의 분리가 가능해졌지만, 상속을 사용하는 단점이 존재한다.

  • 상속을 통한 상하위 클래스의 관계는 생각보다 밀접하다.
  • 서브클래스는 슈퍼클래스의 기능을 직접 사용할 수 있다. 즉, 슈퍼클래스 내부의 변경이 있을 때 모든 서브클래스를 함께 수정하거나 다시 개발해야 할 수도 있다.

*디자인 패턴을 공부할 땐, 패턴을 적용할 상황, 해결해야 할 문제, 솔루션의 구조와 각 요소의 역할과 함께 핵심 의도가 무엇인지를 기억해둬야 한다.

1.3 DAO의 확장

1.3.1 클래스의 분리

상속을 사용하는 방식 대신, 관심사가 다른 두 부분을 클래스로 분리한다.
다만 이 경우 아래의 문제가 재차 발생한다.

-> 하나의 클래스가 다른 클래스와 그 코드에 종속적이면 자유로운 확장이 힘들다. 어떤 클래스에서 어떤 메서드를 가져와야하는지 일일이 지정하지 않아도 괜찮을 순 없을까?

1.3.3 관계설정 책임의 분리

  • 클래스 사이에 존재하는 불필요한 의존관계를 끊어내자.
  • 어떤 클래스를 사용할 것인지는 클라이언트의 책임으로 전가해서 런타임 시점에 다이내믹한 오브젝트 관계를 가지게끔 하는 것이다.

1.3.4 원칙과 패턴

개방 폐쇄 원칙

  • 클래스나 모듈은 얼마든지 기능을 확장할 수 있다. (확장에는 열려있고,)
  • 동시에 자신의 핵심 기능을 구현한 코드는 그런 변화에 영향을 받지 않고 유지할 수 있다. (변경에는 닫혀있음을 의미)

높은 응집도와 낮은 결합도

  • 응집도가 높다는 건 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다는 뜻이다.
  • 책임과 관심사가 다른 오브젝트 또는 모듈과는 낮은 결합도, 즉 느슨하게 연결된 형태를 유지하는 것이 바람직하다.

1.4 제어의 역전(IoC, Inversion Of Control)

팩토리

  • 객체의 생성 방법을 결정하고 생성한 오브젝트를 돌려주는 일을 하는 오브젝트
  • 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔하게 분리하려는 목적으로 사용한다

제어권의 이전을 통한 제어관계 역전

  • 제어의 역전에서는 프레임워크 또는 컨테이너와 같이 애플리케이션 컴포넌트의 생성과 관계설정, 사용, 생명주기 관리 등을 관장하는 존재가 필요하다. → 교재에서의 예시: DaoFactory

1.5 스프링의 IoC

💬 스프링의 핵심을 담당하는 건, 바로 빈 팩토리 또는 애플리케이션 컨텍스트라고 불리는 것이다. 이 두가지는 우리가 만든 DaoFactory가 하는 일을 좀 더 일반화한 것이라고 설명할 수 있다.

애플리케이션 컨텍스트와 설정정보

빈, Bean

  • 오브젝트 단위의 애플리케이션 컴포넌트
  • 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트
  • 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트

빈 팩토리, Bean Factory

  • 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트
  • IoC의 기본 기능에 초점을 맞췄을 때 주로 빈 팩토리라는 용어를 사용한다.

애플리케이션 컨텍스트

  • IoC 방식을 따라 만들어진 일종의 빈 팩토리
  • 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC엔진이라는 의미가 좀 더 부각된다.

애플리케이션 컨텍스트가 IoC 방식의 기능을 제공할 때 사용할 설정정보

@Configuration
public class DaoFactory {
	@Bean
	public UserDao userDao() {
		return new UserDao(connectionMaker());
	}
	
	@Bean
	public ConnectionMaker connectionMaker(){
		return new DConnectionMaker();
	}
}
  • @Configuration 어노테이션
    • 스프링이 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식한다.
    • 애플리케이션 컨택스트가 활용하는 IoC 설정정보이다.
  • @Bean 어노테이션
    • 오브젝트를 만들어주는 메소드에 해당 어노테이션을 붙인다.
public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, 
				 SQLException {
			ApplicationContext context = 
				new AnnotationConfigApplicationContext(DaoFactory.class);
			UserDao dao = context.getBean("userDao", UserDao.class);
			...
}
  • getBean()의 첫 번째 파라미터, “userDao”
    • DaoFactory에서 @Bean 어노테이션을 userDao 이름의 메소드에 붙였기 때문에, 이 메소드 이름이 바로 빈의 이름이 된다.
    • userDao라는 이름의 빈을 가져온다는 것은 DaoFactory의 userDao() 메소드를 호출해서 그 결과를 가져온다고 생각하면 된다.
  • getBean()의 두 번째 파라미터
    • getBean()은 기본적으로 Object 타입을 리턴하기 때문에 매번 오브젝트를 다시 캐스팅해줘야 하는 부담이 있다.
    • 자바 5 이상의 제네릭 메소드 방식을 사용해 getBean()의 두 번째 파라미터에 리턴 타입을 주면 캐스팅 코드를 추가로 작성하지 않아도 된다.

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

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

  • 동일성 == 연산자
  • 동등성 equals() 메서드
    • 두 개의 각기 다른 오브젝트의 정보가 동등한 경우를 말한다.
    • 동일한 오브젝트는 동등하지만, 그 반대는 항상 참은 아니다.
    • 단, Object의 equals() 메서드는 두 오브젝트이 동일성을 비교한다.

스프링 컨텍스트로부터 가져온 오브젝트는 매번 동일한 값을 돌려준다.

1.6.1 싱글톤 레지스트리로서의 애플리케이션 컨텍스트

애플리케이션 컨텍스는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이다.

왜 스프링은 싱글톤으로 빈을 만들까?

  • 스프링이 주로 적용되는 대상이 자바 엔터프라이즈 기술을 사용하는 서버환경이기 때문이다.
  • 매번 클라이언트에서 요청이 올 때마다 각 로직을 담당하는 오브젝트를 새로 만들어서 사용한다고 하면 부하가 매우 클 것이고, 서버가 감당하기는 힘들어진다.
  • 이런 이유로 서블릿은 서비스 오브젝트로, 대부분 멀티스레드 환경에서 싱글톤으로 동작한다. 서블릿 클래스당 하나의 오브젝트만 만들어두고, 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.

싱글톤 패턴의 한계

  • 생성자는 private으로 제한되기 때문에 다른 생성자가 없다면 상속이 불가하다.
    • 객체지향의 장점인 상속과 이를 이용한 다형성을 적용할 수 없고, 객체지향적인 설계의 장점을 적용하기 어렵게 만든다.
  • 싱글톤은 테스트하기 어렵다.
    • 싱글톤은 만들어지는 방식이 제한적이므로 테스트에서 목 오브젝트로 대체하기 힘들다.
    • 생성자를 통해 사용할 오브젝트를 다이내믹하게 주입하기도 어렵다.
  • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
    • 서버에서 클래스 로더를 어떻게 구성하냐에 따라 하나 이상의 오브젝트가 만들어질 수 있다.
    • 여러 개의 JVM에 분산돼서 설치되는 경우에도 각각 독립적으로 오브젝트가 생기므로 싱글톤으로서의 가치가 떨어진다.
  • 싱글톤의 사용은 전역 상태를 만들 수 있다.

대신 스프링은 싱글톤 레지스트리를 제공한다

  • 자바의 기본적인 싱글톤 구현 패턴 방식의 단점을 극복하기 위해, 스프링이 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다.
  • 스태틱 메서드와 private 생성자를 사용하지 않아도 평범한 자바 클래스를 싱글톤으로 활용할 수 있게 된다.

1.6.2 싱글톤과 오브젝트의 상태

싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 상태정보를 내부에 갖고 있지 않은 무상태 방식으로 만들어져야 한다.

  • 상태가 없는 방식으로 클래스를 만들면서 각 요청에 대한 정보를 다루기 위해서는 파라미터와 로컬 변수, 리턴 값 등을 이용하면 된다.
  • 메소드 파라미터나, 메소드 안에서 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라고 해도 여러 스레드가 변수의 값을 덮어쓸 일이 없다.

읽기 전용 속성의 정보라면 싱글톤에서 인스턴스 변수로 사용해도 좋다.
물론 단순한 읽기전용 값이라면 static final 이나 final로 선언하는 편이 낫다.

  • static final
    • 클래스의 모든 인스턴스에 대해 동일한 값을 갖는다.
  • final
    • 개별 인스턴스마다 고유의 값을 갖는다.
    • 인스턴스는 new로 힙에 새로 생성될 때 각 인스턴스에서 설정한 값으로 초기화되기 떄문이다.

0개의 댓글

관련 채용 정보