토비의 스프링 3.1 - 1장_오브젝트와 의존관계

Roeniss Moon·2020년 6월 21일
1

토비의 스프링 3.1

목록 보기
2/6
post-thumbnail

기초 세팅

DTO

DTO (Data Transfer Object) : 자바빈 스타일을 준수하는 것이 활용에 편리하다.

🤔 사견 : DTO를 그냥 자바빈이라고 부르기도 하는 것 같다.

// User DTO example
public class User{
    String id, pw;
    public String getId(){ return id; }
    public void setId(String id){ this.id = id; }
    public String getPw(){ return pw; }
    public void setPw(String pw){ this.pw = pw; }
}

"JSP 프로그래밍에는 DTO(Data Transfer Object)나 DAO(Data Access Object) 클래스의 객체를 JSP페이지에서 사용하기 위해 사용합니다.
자바빈을 이용하여 프로그래밍을 하면 클래스의 객체 선언과 비즈니스 로직 등을 <% %> 스크립틀릿 영역에서 작성하지 않아서 가독성이 좋습니다."

(https://developer-syubrofo.tistory.com/21)

*좀 더 좋은 DTO/DAO 예시 코드 : https://all-record.tistory.com/105

DAO

유저 데이터를 조회 또는 추가 (INSERT, SELECT) 하기 위해선 다음과 같은 코드가 필요하다.

public class UserDao{
    public void add(User user) { // throws 코드 생략
        // jdbc 드라이버 조회
        // DB connection 객체 획득 w/db_url/_name/_password
        // (Prepared)Statement 객체 활용해 SQL문 작성
        // 쿼리 실행
        // (Prepared)Statement 객체 close
        // DB connection 객체 close
    }
    
    public User get(String id) {
    	// SQL 작성까지는 동일
        // 쿼리 실행 후 값을 ResultSet 객체에 저장
        // 새 User 객체 만들기
        // setter들 이용해서 ResultSet 객체 속 값들을 User 객체 속으로 복사
        // (Prepared)Statement 객체 close
        // DB connection 객체 close
        // ResultSet 객체 close
        // 유저 리턴
    }
}

관심의 분리

리팩토링 - 중복 코드를 제거 (extract method)

기존 코드의 커넥션 부분은 중복되므로 분리 가능

private Connection getConnection(){
    // jdbc 드라이버 조회
    // DB connection 객체 획득 w/db_url/_name/_password
    return connection;
}

상속을 통한 확장 (템플릿 메소드 패턴)

N사와 D사가 커넥션 생성 방법을 각각의 방식대로 변경하려고 하는 상황.

// springbook/user/dao/UserDao.java
public abstract class UserDao { // 이 클래스를 상속해서 사용해야 함
    public void add(User user) { // throws 코드 생략
        // ...
    }

    public User get(String id) {
        // ..
    }
    
    public abstract Connection getConnection(){ // 이 메소드를 상속해야 함
        // ..
    }
}

// springbook/user/dao/NUserDao.java
public class NUserDao extends UserDao {
    public Connection getConnection(){
        // N사 스타일의 connection 생성
    }
}
    

Template method pattern : "알고리즘의 구조를 변경하지 않고 알고리즘의 특정 단계들을 다시 정의할 수 있게 해준다"

https://ko.wikipedia.org/wiki/템플릿_메소드_패턴

Factory method pattern : "객체를 생성하기 위한 인터페이스를 정의하고, 인스턴스 생성은 서브클래스가 결정하게 한다."
"아니 그런 자세한 건 모르겠구요 그냥 열나고 기침나오는데 듣는 약으로 주세요"

https://johngrib.github.io/wiki/factory-method-pattern
https://youtu.be/q3_WXP9pPUQ?t=367

상속의 단점

  • 만약 getConnection()은 필요 없고, 다른 기능은 상속하고 싶다면? : 자바는 단일 상속..

  • 서브클래스가 슈퍼클래스의 기능을 직접 사용한다면? : 슈퍼클래스 내부 변화가 있을 때 서브클래스들도 다 수정해야 함.

  • 또다른 DAO 클래스가 나타난다면? : 그 쪽에도 getConnection()을 만들어 주어야 함.

인터페이스를 통한 확장 (전략 패턴)

🤔 사견 : 이 부분은 LSP 원칙과 밀접한 관련이 있지 않나, 하는 생각이 듦

// springbook/user/dao/ConnectionMaker.java
public interface ConnectionMaker {
    public Connection makeNewConnection();
}

// springbook/user/dao/NConnectionMaker.java
public class NConnectionMaker implements ConnectionMaker{
    public Connection makeNewConnection(){
        // ...
    }
}

// springbook/user/dao/UserDao.java
public class UserDao { 
    private Connection connection
    public void UserDao(Connection connection){
        this.connection = connection;
    }
    
    // ...
}

// springbook/user/dao/UserDaoTest.java (main class)
public class UserDaoTest {
    public static void main(String[] args) {
        ConnectionMaker ncm = new NConnectionMaker();
        UserDao dao = new UserDao(ncm);
        // ...
    }
}

상속을 사용한 경우보다 훨씬 유연하다. 위에서 언급한 세 가지 문제를 한 번에 해결함.

OCP : "High coherence (응집도) & Low coupling (결합도)"

스프링 프레임워크

IoC

현재는 테스트를 위한 UserDaoTest.main()에서 어떤 ConnectionMaker를 사용할지를 결정하고 있다. 이것은 책임이 둘이라는 뜻이므로, 관심의 분리가 필요하다.

(여기서의 Factory는 Factory method pattern과 무관함을 책에서 명시함)

// springbook/user/dao/DaoFactory
public class DaoFactory {
    public UserDao userDao() {
        ConnectionMaker ncm = new NConnectionMaker();
        UserDao userDao = new UserDao(ncm); 
        return userDao;
    }
}

// springbook/user/dao/UserDaoTest
// ...
    UserDao dao = new UserDaoFactory().userDao();
// ...

🤔 사견 : 메소드 이름과 리턴할 객체 명을 일부러 통일시키는 것 같다.

이렇게 만들어진 DaoFactory를 설계도, 나머지 클래스들을 컴포넌트라고 말할 수 있다. 컴포넌트 중 Client 클래스(UserDaoTest)에서 설계도를 이용해 객체를 뽑으려고 시도하고, 설계도는 다른 컴포넌트들을 생성하고 조합하여 이를 리턴하면, Client에서 이를 활용하는 방식.

이것이 IoC, 제어의 역전의 기본 구조이다. 오브젝트(Client)는 자신이 사용할 오브젝트를 스스로 선택하지도, 생성하지도 않는다. 모든 제어 권한은 다른 특별한 오브젝트(설계도)에게 위임되어 있다.

  • UserDao는 어떤 ConnectionMaker를 사용할지가 DaoFactory에 의해 정해진다.

  • UserDaoTest는 DaoFactory가 만들고 초기화하여 리턴해주는 dao만 사용할 수 있다.

스프링 프레임워크는 이러한 IoC를 극한까지 적용하는 프레임워크이다.

스프링의 IoC - 빈 팩토리와 애플리케이션 컨텍스트

Bean : 스프링이 생성과 제어를 직접 담당하는 Object

Bean Factory : Bean에 대한 제어를 담당하는 IoC Object

Application Context : BeanFactory를 extends한 컨테이너. 스프링의 각종 부가 서비스를 빈 팩토리에 얹었다고 보면 된다. 위의 DaoFactory(설계도)가 여기에 해당함. (같은 용어들 : IoC 컨테이너, 스프링 컨테이너, 빈 팩토리, 스프링, 싱글톤 레지스트리)

"같은 용어라고 알고 있되, 빈 팩토리라고 말할 때는 "빈을 생성하고 제어(관계를 설정)"하는 IoC의 기본 기능에 초점을 맞춘 것이고, 애플리케이션 컨텍스트라고 말할 때는 애플리케이션 전반에 걸쳐 모든 구성요소들의 제어 작업을 담당하는 IoC 엔진이라는 의미에 초점을 맞췄다고 생각할 것"

어노테이션(annotation)을 사용해 DaoFactory를 App context 소스로 사용할 수 있다.

// springbook/user/dao/DaoFactory
@Configuration
public class DaoFactory {
    @Bean
    public UserDao userDao() {
        return new UserDao(connectionMaker()); 
    }
    
    @Bean
    public ConnectionMaker connectionMaker(){
        return new NConnectionMaker();
    }
}

// springbook/user/dao/UserDaoTest
// ...
        ApplicationContext daoContext = new AnnotationConfigApplicationContext(DaoFactory.class);
        UserDao dao = daoContext.getBean("userDao", UserDao.class); // 2nd param : for casting
// ...

작동 방식 : Client가 getBean()으로 요청하면 ApplicationContext 객체 내에서 빈 목록을 조회한다. 별다른 옵션이 없다면 싱글톤 객체를 리턴한다.

장점 : Client가 Factory 클래스를 몰라도 된다. 즉, 일관된 방식으로 원하는 Object를 가져올 수 있다.

싱글톤 레지스트리로서의 스프링

Singleton Pattern : "인스턴스의 수를 제한하는 패턴. 대개 하나"

단점들 :

  • private 생성자 (= 상속 불가)
  • 상태를 확인하기 어려움 (= 테스트 어려움)
  • production 환경에서 1개만 있도록 통제하기가 어려움
  • 어디서나 접근할 수 있으므로 global state로 사용됨 (anti pattern)

사용시 주의점: stateless하게 운용할 것

스프링 프레임워크는 싱글톤 레지스트리의 기능을 한다. 즉, 평범한 자바 클래스를 싱글톤으로 사용하면서 싱글톤 패턴의 장점만 취할 수 있게 해준다.

DI

스프링이 제공하는 IoC 방식 == DI (Dependency Injection)

의존 관계에는 방향성이 있다. (클래스 다이어그램(UML)의 화살표가 '가리키는 쪽'이 누가 '누구에게' 의존하는지를 의미함)

인터페이스에 대해서 의존 == 인터페이스를 구현한 구현 클래스와의 관계(런타임 시의 의존관계)가 느슨

클래스 다이어그램에는 런타임 의존관계가 드러나지 않음

즉, DI는 다음 세 조건을 충족해야 함

  • 클래스 다이어그램 및 코드에는 런타임 시점의 의존관계가 드러나지 않는다 (by 인터페이스로 의존관계 구축)

  • 런타임 시점의 의존관계는 제3자가 결정한다. (ex. 컨테이너, 팩토리)

  • 의존관계는 외부에서 주입함으로써 만들어진다. (ex. 컨테이너, 팩토리)

핵심 : "설계 시점에는 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제3의 존재가 있다."

DL

의존관계 검색(DL; Dependency Lookup)은 능동적으로 자기에게 필요한 dependent object를 찾는 방식이다. 일반적으로는 DI 쓰는게 좋다. 하지만 애플리케이션이 작동하는 시점에 최소 1번은 DL이 필요하다. 예를 들면 main() 메소드에서는 제일 먼저 실행되는데, 그러면 DI를 당할 수가 없다.

DI와 DL의 차이점 : DI 받기 위해선 본인도 (컨테이너의 관리를 받는) @Bean이어야 한다.

DI 구현 방법

  1. 생성자 사용 : 생성자에 파라미터를 만들어두고 이를 통해 요구되는 Object의 reference를 내부로 넘겨줌

  2. 메소드 사용 :
    a. setter 사용 : DI 받는 클래스 안쪽에 set[객체명] 형태의 메소드를 만들고 내부 인스턴스 변수로 저장
    b. 일반 메소드 사용 : 여러 개의 파라미터를 가질 수 있음. 'set'으로 시작하지 않아도 됨. 생성자와 다르게, 필요한 만큼의 파라미터만 사용할 수 있다.

XML 사용

DaoFactory 대신 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="connectionMakerId" class="springbook.user.dao.NConnectionMaker"/>

    <bean id="userDao" class="springbook.user.dao.UserDao">
        <property name="connectionMaker" ref="connectionMakerId"/>
    </bean>
</beans>

(원래 connectionMakerId가 아니라 connectionMaker인데 ref가 무엇인지 확실히 하려고 변경함)

XML 문서 구조를 정의할 때 DTD 방식과 스키마 방식이 있는데 위에 나온대로 후자를 권장.

ConnectionMaker를 이미 존재하는 DataSource 인터페이스로 변경하면 다음과 같은 XML이 만들어진다.

(직접 만든 ConnectionMaker 대신 DataSource 인터페이스를 활용해서, DaoFactory 안의 Bean을 수정하는 방식은 생략)

<?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="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="db_url"/>
        <property name="username" value="db_user_name"/>
        <property name="password" value="db_password"/>
    </bean>

    <bean id="userDao" class="springbook.user.dao.UserDao">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

IntelliJ라서 그런건지 macOS라서 그런건지 모르겠는데, 'com.mysql.jdbc.Driver'를 사용하면 경고를 왕창 보여준다.

ref : Bean을 가져올 때 사용
value : 단순 값 (스프링이 property.name을 통해 parameter type을 확인하고 value의 텍스트를 적절하게 캐스팅)

정리

  • 관심사의 분리, 리팩토링

  • 전략 패턴 - 바뀔 수 있는 클래스는 인터페이스를 구현. 다른 클래스에서는 이 인터페이스를 통해서만 접근

  • OCP - 자신의 책임 자체가 변경되는 경우가 아니라면, 변화가 없어야 한다. 만약 변화가 있다면 수정할 필요가 있다는 뜻.

  • low coupling, high coherence - 위랑 같다

  • IoC - 오브젝트(Bean)가 생성되고, 다른 오브젝트들과 관계를 맺는 작업의 '제어권'을 별도의 오브젝트 팩토리에게 위임. 이것을 일반화한게 IoC 컨테이너. 이를 통해 "오브젝트(Client)는 자신이 사용할 대상(Bean)의 생성과 선택에 대한 책임이 없어졌다"

  • 싱글톤 레지스트리 (= IoC 컨테이너)

  • DI - 설계 시점에 & 코드 상에는 클래스-인터페이스 간 loose dependency, 런타임 시점에 & 실제 사용은 구체적인 오브젝트들의 dependency. By 제3자 (= DI 컨테이너 (= 스프링))

  • DI 방식 (의존 주입 방식) - 생성자, setter 메소드, 일반 메소드

  • XML - DI 설정정보 만들기. ref와 value의 차이

profile
기능이 아니라 버그예요

0개의 댓글