1장 오브젝트와 의존 관계

soplia080 gyp·2022년 4월 22일
0

토비의 스프링

목록 보기
1/4
post-thumbnail

1.1 초난감한 DAO(난감하지 않게 해주기 위한 여정)

User.java

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.java

public class UserDao {

    public void add(User user) throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3307/problem", "root", "1234");
        PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values (?,?,?)");
        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());
        ps.executeUpdate();

        ps.close();
        c.close();
    }

    public User get(String id) throws ClassNotFoundException, SQLException{
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3307/problem", "root", "1234");
        PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
        ps.setString(1, id);

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

        rs.close();
        ps.close();
        c.close();

        return user;
    }
}

Main.java

public class Test {
    public static void main(String[] args) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        UserDao dao = new UserDao();

        User user = new User();
        user.setId("아이디12");
        user.setName("이름");
        user.setPassword("비밀번호");

        dao.add(user);

        System.out.println(user.getId() + " 등록 성공");

        User user2 = dao.get(user.getId());
        System.out.println(user2.getName());

        System.out.println(user2.getPassword());

        System.out.println(user2.getId() + "조회 성공");
    }
}

1.2 DAO의 분리

1.2.1 관심사를 알아내서 분리 시킴(변화에 유연)

UserDao.java

public class UserDao {

   public void add(User user) throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException {
       Connection c = getConnection();
       PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values (?,?,?)");
       ps.setString(1, user.getId());
       ps.setString(2, user.getName());
       ps.setString(3, user.getPassword());
       ps.executeUpdate();

       ps.close();
       c.close();
   }

   public User get(String id) throws ClassNotFoundException, SQLException{
       Connection c = getConnection();
       PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
       ps.setString(1, id);

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

       rs.close();
       ps.close();
       c.close();

       return user;
   }

   // 중복 코드의 메소드 추출 -> 관심사의 분리
   public Connection getConnection() throws ClassNotFoundException, SQLException{
       Class.forName("com.mysql.cj.jdbc.Driver");
       Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3307/problem", "root", "1234");
       return c;
   }
}

1.2.3 여기서 상속을 통한 확장으로 더 변화에 유연하게 분리

UserDao.java

  /**
     * 상속을 통한 확장방법을 제공
     * @return
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public abstract Connection getConnection() throws ClassNotFoundException, SQLException;

Main.java

public class SubClass {

    static class NUserDao extends UserDao {
        @Override
        public Connection getConnection() throws ClassNotFoundException, SQLException {
            // N사의 DB Connection 생성 코드
            return null;
        }
    }

    static class DUserDao extends UserDao {
        @Override
        public Connection getConnection() throws ClassNotFoundException, SQLException {
            // D사의 DB Connection 생성 코드
            return null;
        }
    }

    public static void main(String[] args) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//        UserDao dao = new DUserDao();
        UserDao dao = new NUserDao();

        User user = new User();
        user.setId("아이디12");
        user.setName("이름");
        user.setPassword("비밀번호");

        dao.add(user);

        System.out.println(user.getId() + " 등록 성공");

        User user2 = dao.get(user.getId());
        System.out.println(user2.getName());

        System.out.println(user2.getPassword());

        System.out.println(user2.getId() + "조회 성공");
    }
}

여기서 "상속으로 기능을 확장" 하는 2가지 디자인 패턴을 알려주신다.

1. 템플릿 메소드 패턴 - 슈퍼 클래스에서 어떤 기능을 실행하는 로직(템플릿 메소드)에서 변화가 필요한 부분(강제적 - 추상 메소드, 선택적 - 훅 메소드)을 알잘딱으로 구현해서 사용하도록 하는 방법

public abstract class Super {
	/// 템플릿 메소드 
    public void templateMethod(){
        hookMethod();
        abstractMethod();
    }
    // 선택적으로 오버라이드 가능한 훅 메소드
    protected void hookMethod() {}

    // 서브클래스에서 반드시 구현해야되는 추상 메소드
    public abstract void abstractMethod();
}

public class Sub1 extends Super{

    @Override
    protected void hookMethod() {
        super.hookMethod();
    }

    @Override
    public void abstractMethod() {

    }
}

2. 팩토리 메소드 패턴 - 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것
UserDao.java

  /**
     * 상속을 통한 확장방법을 제공
     * @return
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public abstract Connection getConnection() throws ClassNotFoundException, SQLException;

public class SubClass {

    static class NUserDao extends UserDao {
        @Override
        public Connection getConnection() throws ClassNotFoundException, SQLException {
            // N사의 DB Connection 생성 코드
            return null;
        }
    }

    static class DUserDao extends UserDao {
        @Override
        public Connection getConnection() throws ClassNotFoundException, SQLException {
            // D사의 DB Connection 생성 코드
            return null;
        }
    }
  • 서브클래스의 getConnection()을 통해 만들어진 Connection 오브젝트의 종류가 다르거나 같다.
    	> 여기서는 Connection 인터페이스를 상속 받는 클래스들이거나 인터페이스의 객체들
  • DUserDao, NUserDao의 입장에선 어떤식으로 Connection 기능을 제공할지만 관심대상이다.
  • 상 하위 클래스의 각자의 관심 사항을 분리 시키기 위해 쓴다고 할 수 있다.

하지만 여기에는 한계가 있따..

  1. 상속을 사용했다는 점
    • 자바는 다중 상속을 허용하지 않는다.
      - extends의 상속 구조인 경우 구현체를 가진 이름이 똑같은 똑같은 추상 메소드를 만났을때 어떤 놈을 써야 될지 멘붕이 온다.
    • 커넥션 객체를 가져오려고 상속 구조를 만들어 버리면 그 이후에 다른 목적의 상속을 적용하기 힘들다.
  2. 여전히 상속을 통한 상하위 관계는 밀접하다.
  3. 추상 메소드 구현체의 중복

1.3 DAO의 확장

1.3.1 독립적인 클래스로 분리

SimpleConnectionMaker.java

public class SimpleConnectionMaker {
    public Connection makeNewConnection() throws ClassNotFoundException, SQLException{
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3307/problem", "root", "1234");
        return c;
    }
}

UserDao.java

public class UserDao {

    private SimpleConnectionMaker simpleConnectionMaker;

    public UserDao() {
        this.simpleConnectionMaker = new SimpleConnectionMaker();
    }
    public void add(User user) throws ... {
    	Connection c = simpleConnectionMaker.makeNewConnection();
    	...
    }
    public void get(String id) throws ... {
    	Connection c = simpleConnectionMaker.makeNewConnection();
    	...
    }
}
  • 이번엔 UserDao 코드의 수정 없이 DB 커넥션 생성 기능을 변경할 방법이 없다.
    - Connection 인터페이스를 쓰는 메소드가 천개면 다 바꿔야됨.
  • SimpleConnectionMaker에 완전히 종속됨.

이래선 상속을 이용한 방법만도 못한거 아닌가 싶다...


1.3.2 인터페이스의 도입

  • 이때 필요한게 서로 긴밀하게 연결되지 않도록 중간에 추상적인 느슨한 연결고리 필요

ConnectionMaker.java

public interface ConnectionMaker {
    public Connection makeConnection() throws ClassNotFoundException, SQLException;
}

DConnectionMaker.java (여기서 커넥션을 여기서 구현)

public class DConnectionMaker implements ConnectionMaker{
    @Override
    public Connection makeConnection() throws ClassNotFoundException, SQLException {
        // D 사가 족자적인 방법으로 Connection 을 생성하는 코드
        return null;
    }
}
public class UserDao {

    private ConnectionMaker connectionMaker;

    public UserDao() {
        // 그래도 여기는 클래스 이름이 나오네?
        this.connectionMaker = new DConnectionMaker();
    }
    public void add(User user) throws ... {
    	Connection c = connectionMaker.makeConnection();
    	...
    }
    public void get(String id) throws ... {
    	Connection c = connectionMaker.makeConnection();
    	...
    }
}

그래도 생성자의 코드는 제거되지 않고 남아았다.

  • 결국 UserDao의 생성자 메소드를 직접 수정해야 확장을 할 수 있다.

1.3.3 관계설정 책임의 분리

  • 여기서 토비님은 클라리언트 오브젝트에 관한 이야기를 해주신다.

    사용되는 오브젝트를 서비스, 사용하는 오브젝트를 클라이언트

  • 위 코드의 생성자에서 ConnectionMaker 인터페이스 구현 클래스의 관계를 결정해주는 기능을 분리해서 두기 적당한 장소가 UserDao를 사용하는 클라이언트 오브젝트이다.

  • 클래스 사이의 관계를 설정해주는 것이 아닌, 오브젝트와 오브젝트의 동적인 관계를 설정해줘야 된다는 게 핵심 -> 다형성을 이용하라는 말씀이시다.

ConnectionMaker.java

public interface ConnectionMaker {
    public Connection makeConnection() throws ClassNotFoundException, SQLException;
}

DConnectionMaker.java

public class DConnectionMaker implements ConnectionMaker{
    @Override
    public Connection makeConnection() throws ClassNotFoundException, SQLException {
        // D 사가 족자적인 방법으로 Connection 을 생성하는 코드
        return null;
    }
}

UserDao.java

public class UserDao {

    private ConnectionMaker connectionMaker;

	public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
    public void add(User user) throws ... {
    	Connection c = connectionMaker.makeConnection();
    	...
    }
    public void get(String id) throws ... {
    	Connection c = connectionMaker.makeConnection();
    	...
    }
}
public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        ConnectionMaker connectionMaker = new DConnectionMaker();
//        ConnectionMaker connectionMaker = new NConnectionMaker();
		UserDao dao = new UserDao(connectionMaker);

  • ConnectionMaker의 구현체의 오브젝트 간 관계를 맺는 책임을 최종 클라이언트 UserDaoTest(UserDao를 사용하는 클라이언트)에 떠넘겨 버리면서 모든 관심사를 독립적으로 분리했다.
  • 이렇게함으로써 UserDao에는 전혀 손대지 않고 모든 고객이 만족스럽게 DB연결 기능을 확장하여 사용이 가능해졌다.

1.3.4 원칙과 패턴

개방 패쇄 원칙(OCP, Open-Close Principle)

클래스와 모듈은 확장에는 열려있고, 변경에는 닫혀있다.

  • UserDao는 DB연결 기능을 확장하는 데에는 열렸고, UserDao의 핵심코드는 그런 변화에 영향을 받지 않고 유지할 수 있다.

높은 응집도와 낮은 결합도

  • 응집도 凝集度
    凝 (엉길 응) 集 (모을 집) 度 (법도 도, 헤아릴 탁, 살 택)

    • 높은 응집도
      변화가 일어날때 해당 모듈에서 많은 부분이 함께 바뀐다.
      응집도가 높다는 건 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되있다고 볼 수 있다.
  • 결합도 結合度 - 하나의 오브젝트가 변할때 관계를 맺은 다른 오브젝트에게 변화를 요구하는 정도
    結 (맺을 결, 상투 계) 合 (합할 합, 쪽문 합, 홉 홉) 度 (법도 도, 헤아릴 탁, 살 택)

    • 낮은 결합도
      관계를 맺고 있는 다른 오브젝트에게 변경에 대한 요구가 전파되지 않음을 의미 - 느슨한 연결

전략 패턴(Strategy Pattern)

  • 변경이 필요한 알고리즘(독립적인 책임으로 분리가 가능한 기능) 인터페이스 를 통해 분리시키고, 이를 구현한 구체 클래스를 필요에 따라 바꿔서 사용 가능한 패턴
  • 여기서 전략이라 불리는 것은 구체 클래스를 전략이라고 보고 이 구체 클래스를 바꿔가면서 사용할 수 있음이라고 생각하면 된다.
  • 초난감 DAO로 치면 아래 경우다.

    ConnectionMaker connectionMaker = new DConnectionMaker() --> 전략;

여기서 토비님이 슬슬 스프링에 대한 이야기를 할 때가 됐다고 말하시는데...


1.4 제어의 역전(IoC)

1.4.1 오브젝트 팩토리

  • 이번엔 엉겹결에 UserDao로부터 어떤 구현 클래스를 사용할지를 결정하는 기능을 떠맡은 UserDaoTest의 관심사를 분리해서 리펙토링 해보자.

팩토리

  • 객체의 생성을 담당하고, 만들어진 오브젝트를 돌려주는 역할을 하는 오브젝트
    (추상 팩토리 패턴, 팩토리 메소드 패턴과는 다르니 혼동 ㄴㄴ)
  • 단지 오브젝트를 생성하는 쪽과 오브젝트를 사용하는 쪽의 역할과 책임을 분리하려는 목적으로 사용
  • 오브젝트를 구성하고 그 관계를 정의하는 책임

DaoFactory.java

public class DaoFactory {
    public UserDao userDao(){
        DConnectionMaker connectionMaker = new DConnectionMaker();
        UserDao userDao = new UserDao(connectionMaker);
        return userDao;
    }
}

UserDaoTest.java

public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        UserDao dao = new DaoFactory().userDao();
        ...
   	}
}
  • 이제 UserDaoTest는 UserDao에게 객체를 받아 자신의 관심사인 테스트만 하면 된다.

설계도로서의 팩토리

  • UserDao, ConnectionMaker는 애플리케이션의 핵심, 기술 로직을 담당하는 컴포넌트
  • DaoFactory는 컴포넌트의 구조와 관계를 정의한 설계도

두 역할의 오브젝트를 분리함


1.4.2 오브젝트 팩토리의 활용

  • 여기서 다른 DAO의 생성 기능을 넣으면?
public class DaoFactory {
    public UserDao userDao(){
        return new UserDao(new DConnectionMaker());
    }
    
    public AccountDao accountDao(){
        return new AccountDao(new DConnectionMaker());
    }

    public MessageDao messageDao(){
        return new MessageDao(new DConnectionMaker());
    }
}
  • new DConnectionMaker()가 메소드마다 반복 -> ConnectionMaker의 구현 클래스를 바꿀 때마다 모든 메소드를 일일히 수정해야 됨.
public class DaoFactory {
    public UserDao userDao(){
        return new UserDao(connectionMaker());
    }

    public AccountDao accountDao(){
        return new AccountDao(connectionMaker());
    }

    public MessageDao messageDao(){
        return new MessageDao(connectionMaker());
    }
    
    public ConnectionMaker connectionMaker(){
        return new DConnectionMaker();
    }
}
  • ConnectionMaker의 구현 클래스를 결정하고 오브젝트를 만드는 별도의 메소드로 뽑기

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

제어의 역전
간단히 프로그램의 제어 흐름 구조가 뒤빠뀌는 것

일반적인 프로그램의 흐름

  • 모든 종류의 작업을 사용하는 쪽에서 제어한다.
public class UserDao {

    private ConnectionMaker connectionMaker;

    public UserDao() {
        // UserDao가 자신이 사용할 ConnectionMaker 구현체를 결정하는 부분
        this.connectionMaker = new DConnectionMaker();
    }
     public void add(User user) throws ClassNotFoundException, SQLException    {
        Connection c = connectionMaker.makeConnection();
        ...
     }
     public User get(String id) throws ClassNotFoundException, SQLException{
        Connection c = connectionMaker.makeConnection();
     }
     
}
  • UserDao가 자신이 사용할 ConnectionMaker의 구현 클래스를 자신이 결정하고, 그 오브젝트를 필요한 시점에 생성해 각 메소드에서 이것을 사용한다.

저 윗 부분이 제어의 역전이라는 개념을 도입하면

public class UserDao {

    private ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
    public void add(User user) throws ClassNotFoundException, SQLException    {
        Connection c = connectionMaker.makeConnection();
        ...
     }
     public User get(String id) throws ClassNotFoundException, SQLException{
        Connection c = connectionMaker.makeConnection();
     }
}
  • UserDao도 이제 능동적이 아니라 수동적인 존재가 되었다.
  • UserDao 자신도 어떤 ConnectionMaker 구현체가 사용될지를 모르고, 자신이 사용할 오브젝트도 DaoFactory가 공급해주는 대로 사용해야 된다.
public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        UserDao dao = new DaoFactory().userDao();
	}
}
  • UserDaoTest도 DaoFactory가 공급해주는 ConnectionMaker를 사용해야 된다.

제어의 역전과 프레임워크

  • 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택, 생성하지 않는다.
  • 또한 자신(UserDao)도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다.
  • 모든 제어 권한을 자신이 아닌 다른 대상에게 위임한다.
  • 제어의 역전 개념이 적용되어야 프레임워크라고 불린다.
  • 제어의 역전에서는 프레임워크 또는 컨테이너와 같이 애플리케이션 컴포넌트의 생성과 관계설정, 사용, 생명주기 관리 들을 관장하는 존재가 필요하다.

1.5 스프링의 IoC

1.5.1 오브젝트 팩토리를 이용한 스프링 IoC

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

  • bean
    - 스프링에서는 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트를 Bean이라고함.
    - 스프링 컨테이너가 생성, 관계 설정, 사용 등을 제어해주는 IoC이 적용된 오브젝트
  • bean factory
    - 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트
    - 이것을 좀 더 확장한 것이 애플리케이션 컨텍스트

여기서 토비님은 앞으로 책 내에서 용어적 약속을 하셨는데

  • 책에서 빈팩토리라고 말할 때는
    - 빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점을 맞춤
  • 애플리케이션 컨텍스트라고 말할 때는
    - 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC엔진

DaoFactory를 사용하는 애플리케이션 컨텍스트

DaoFactory.java

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

    @Bean
    public ConnectionMaker connectionMaker(){
        return new DConnectionMaker();
    }
}
  • @Configuration : 오브젝트 설정을 담당하는 클래스
  • @Bean : 오브젝트를 만들어주는 메소드에 붙임
  • 자바 코드의 탈을 쓰고 있지만, XML과 같은 스프링 전용 설정정보라고 보는 것이 좋다.

UserDaoTest.java

public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
        UserDao dao = context.getBean("userDao", UserDao.class);
        ...
        dao.add(user);
   	}
}
  • getBean(빈의 이름, 리턴 타입)

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

  • 애플리케이션 컨텍스트는 Ioc 컨테이너, 스프링 컨테이너, 빈 팩토리라고도 불림

1) DaoFactory 클래스를 설정정보로 등록
2) @Bean이 붙은 메소드의 이름을 가져와 빈 목록을 만듬
3) 클라이언트가 애플리케이션 컨텍스트의 getBean() 메소드를 호출
4) 자신의 빈 목록에서 요청한 이름이 있는지 탐색, 있으면 빈을 생성하는 메소드를 호출
5) 오브젝트를 생성시킨 후 클라이언트에게 돌려줌

그럼 굳히 앞에서 만든 DaoFactory같은 오브젝트 팩토리보다 스프링을 적용하면 더 우월한 점이 무엇일까?

애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점

  • 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
    - 사용자가 필요할 때마다 팩토리 오브젝트를 생성해야 된다. 애플리케이션 컨텍스트를 사용하면 일관된 방식으로 오브젝트를 가져올 수 있다 또한 자바 코드보다 XML로 더 단순하게 IoC 설정 정보를 만들 수 있다.
  • 애플리케이션 컨텍스트는 종합 IoC 서비스를 제공해준다.
    - 단지 오브젝트 생성, 다른 오브젝트와의 관계설정만이 전부가 아니다. 오브젝트를 활용할 수 있는 다양한 기능을 제공한다. ex) 자동 생성, 인터셉팅, 오브젝트 후처리 등
  • 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.
    - 빈의 이름으로 빈을 찾거나 타입, 애노테이션으로 빈을 찾을 수 있다.

1.5.3 스프링 IoC의 용어 정리

  • : 스프링이 직접 IoC방식으로 생성과 제어를 담당하는 오브젝트
  • 빈 팩토리 : 스프링의 IoC 를 담당하는 핵심 컨테이너, 빈을 관리(등록, 생성, 조회, 반환)한다.
  • 애플리케이션 컨텍스트 : 빈 팩토리를 확장한 IoC 컨테이너. 빈 팩토리 + 스프링이 제공하는 애플리케이션 지원 기능
  • 설정 정보/ 설정 메타정보 : 애플리케이션 컨텍스트(빈 팩토리)가 IoC를 적용하기 위해 사용하는 메타정보
  • 컨테이너 또는 IoC 컨테이너 : IoC 방식으로 빈을 관리한다.

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

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

  • 동일성 : == (메조리 주소가 같음)
  • 동등성 : equals() (정보가 같음)

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

애플리케이션 컨텍스트는 IoC 컨테이너면서 싱글톤을 저장, 관리하는 싱글톤 레지스트리(singleton registry)다. 스프링은 default로 빈 오브젝트를 모두 싱글톤으로 생성한다.

서버 애플리케이션 싱글톤

싱글톤으로 만드는 이유

  • 스프링이 주로 적용되는 대상이 자바 엔터프라이즈 기술을 사용하는 "서버"환경.
  • 수많은 요청에 따른 오브젝트의 생성은 서버의 부하를 일으킴.
  • 서블릿은 멀티 스레드환경에서 싱글톤으로 동작하는 엔터프라이즈 기술의 기본이 되는 서비스 오브젝트

싱글톤 패턴의 한계

public class UserDao{
	private static UserDao INSTANCE;
   	... 
    private UserDao(ConnectionMaker connectionMaker){
    this.connectionMaker = connectionMaker;
	}
    public static synchronized UserDao getInstance(){
    	if (INSTANCE == null) INSTANCE = new UserDao(...);
        return INSTANCE;
    }
}
  • private 생성자를 갖고있기 때문에 상속 X
    - 다른 생성자가 없는 경우 상속과 다형성을 적용 X
    - 상속과 다형성이 적용되지 않는 static 필드, 메소드를 사용
  • 싱글톤은 테스트가 어렵다.
    - 싱글톤을 만드는 방식이 제한적이므로, MOCK 오브젝트로 대체가 힘듬
    - 초기화 과정에서 사용할 오브젝트를 동적으로 주입하기도 힘들기 때문
  • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
    - 서버에서 클래스 로더를 어떻게 구현하냐에 따라 싱글톤이라도 하나 이상의 오브젝트가 만들어질 수 있다.
    - 여러 JVM이 분산돼서 설치 되는 경우에도 각각 독립적으로 오브젝트가 생긴다.
  • 싱글톤의 사용은 전역 상태를 만들 ㅜ수 있기 때문에 바람직하지 못하다.
    - 싱글톤의 static method를 이용해 언제든지 쉽게 접근 가능 -> 전역상태로 유지
    - 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향에서 권장되지 않음.

싱글톤 레지스트리

  • 스프링은 서버환경에서 싱글톤이 만들어져서 서비스 오브젝트로 사용되는것을 적극 지지함.
  • 자바의 싱글톤 패턴 구현 방식은 단점이 많음. -> 그래서 스프링에서 직접 만듬.

스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공 > 싱글톤 레지스트리

싱글톤 레지스트리의 장점

  1. static method와 private 생성자를 사용해야 하는 비정상적인 클래스가 아닌 평범한 자바 클래스를 싱글톤으로 활용하게 해줌.
  2. 싱글톤으로 사용될 애플리케이션 클래스라도 public 생성자를 가질 수 있다.
    • 싱글톤으로 사용되야 할 상황이면 간단히 오브젝트를 생성해서 사용 가능
    • 테스트도 수월하고, mock 오브젝트로 대체가 가능하다.
  3. 스프링이 지지하는 객체지향적인 설계 방식, 원칙, 디자인 패턴(싱글톤 제외)을 자유롭게 적용 가능.

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

  • 싱글톤은 멀티스레드 환경에서 상태 관리에 주의해야한다.
  • 싱글톤이 멀티스레드 환경에서 사용되는 경우 상태정보를 내부에 갖고 있지 않은 무상태(stateless) 방식으로 만들어져야 한다.
  • 읽기 전용의 값이라면 초기화 시점에서 인스턴수 변수에 저장해두고 공유하는 것은 괜찮다.

상태가 없는 방식으로 클래스를 만들면 각 요청에 대한 정보는 어떻게 다뤄야 할까?

  • 이때는 파라미터, 로컬 변수, 리턴 값을 이용한다.
  • 메소드안에서 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어기 때문에 싱글톤이라도 여러 스레드가 변수의 값을 덮어쓸 일이 없다.

example

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

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

UserDao를 멀티스레드 환경에서 이렇게 사용하면 안되는 예시

public class UserDao {
	// 읽기 전용
    private ConnectionMaker connectionMaker;
    private Connection connection; // 매번 새로운 값으로 바뀜 -> 심각한 문제 발생
    private User user; // 매번 새로운 값으로 바뀜 -> 심각한 문제 발생

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

    public void add(User user) throws ClassNotFoundException, SQLException    {
        this.connection = connectionMaker.makeConnection();
       	...
    }

    public User get(String id) throws ClassNotFoundException, SQLException{
        this.connection = connectionMaker.makeConnection();
        this.user = new User();
        this.user.setId(re.getString("id"));
        ...
        return user;
    }
}
  • ConnectionMaker 인터페이스 타입의 connectionMaker는 인스턴스 변수를 사용해도 무방하다.
    • 읽기 전용의 정보이며, DaoFactory에서 @Bean으로 설정되어 스프링에서 한개의 오브젝트만 만들어진다.
    • 스프링이 한 번 초기화해주고 나면 이후에는 수정되지 않기 때문에 멀티스레드 환경에서도 안전하다.
  • connection, user 같은 경우는 심각한 문제가 발생한다. 따라서 기존의 UserDao처럼 개별적으로 바뀌는 정보는 로컬 변수나, 파라미터로 주고 받는다.

기존의 UserDao처럼 개별적으로 바뀌는 정보는 로컬 변수, 파라미터로 주고 받으면서 사용

public class UserDao {

    private ConnectionMaker connectionMaker;

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

    public void add(User user) throws ClassNotFoundException, SQLException    {
        Connection c = connectionMaker.makeConnection(); // <- 이게 맞다.
        ...
    }

    public User get(String id) throws ClassNotFoundException, SQLException{
        Connection c = connectionMaker.makeConnection(); // <- 이게 맞다.
        PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
        ps.setString(1, id);

        ResultSet rs = ps.executeQuery();
        rs.next();
        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("Name"));
        user.setPassword(rs.getString("password"));
        return user;
    }

1.6.3 스프링 빈의 스코프

  • 스프링이 관리하는 빈이 생성, 존재, 적용되는 범위 -> 빈의 스코프(scope)

싱글톤 스코프

  • 빈의 기본 스코프는 싱글톤이다.(대부분의 빈이 싱글톤이다.)
  • 싱글톤 스코프는 컨테이너 내에 1개의 오브젝트만 만들어져, 제거하지 않는 이상 스프링 컨테이너가 존재하는 동안에는 계속 유지된다.

프로토(prototype) 타입 스코프

  • 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 만들어준다.

요청(request) 스코프

  • 웹을 통해 새로운 HTTP 요청이 생길때마다 생성

세션(session) 스코프

  • 웹의 세션과 유사한 스코프


1.7 의존관계 주입(DI)

1.7.1 제어의 역전과 의존관계 주입

DI 라는 용어가 생긴 이유

  • IoC 는 소프트웨어에서 자주 등장하는 일반적인 개념이다.
    - IoC 라는 용어 자체가 매우 느슨하게 정의되어 폭넓게 사용 된다.
  • 스프링을 IoC 컨테이너라고만 하기엔 스프링이 제공하는 기능의 특징을 자세히 설명하지 못한다.
  • 스프링이 서블릿 컨테이너처럼 서버에서 동작하는 서비스 컨테이너인지..
  • 아니면 단순히 IoC 개념을 적용된 템플릿 메소드 패턴을 이용하여 만들어진 프레임워크인지
  • 아니면 또 다른 IoC 특징을 지닌 기술인지... 파악이 힘들다.

그래서 스프링이 제공하는 IoC 방식의 핵심을 짚어주는 "DI"라는 의도가 드러나는 용어가 탄생됨.

  • 스프링 IoC 기능의 대표적인 동작 원리는 주로 DI다.
  • 그래서 초기에는 IoC 컨테이너라고 불리는 스프링이 요새는 DI 컨테이너라고 많이 불린다.
  • DI는 오브젝트 레퍼런스를 외부로부터 제공(주입)받아 이를 통해 다른 오브젝트와 동적으로 의존관계가 만들어진다.

1.7.2 런타임 의존관계 설정

의존 관계

  • 항상 방향성을 부여(누가 누구에게 의존하는 관계)
  • 한쪽의 변화가 다른 쪽에 영향을 주는 것(B가 변하면 A에게 영향을 미친다)

UserDao의 의존관계

모델링 시점의 의존관계

  • ConnectionMaker 인터페이스가 변한다면 그 영향을 UserDao가 직접적으로 받게 됨.
  • DConnectionMaker에서 변화가 일어나도 UserDao에 영향을 주지 않음.
  • 인터페이스에 대해서만 의존관계를 만들어두면 인터페이스 구현 클래스와의 관계는 느슨해져 변화에 영향을 덜 받는 상태가 됨(인터페이스를 통해 의존관계 제한하면 변경에서 자유로움.)

런타임 시점의 의존관계

  • 인터페이스를 통해 설계 시점에서 느슨한 의존관계를 갖는 경우에는 런타임 시에 사용할 오브젝트가 어떤 클래스로 만든 것인지 알 수가 없다.
  • 프로그램이 시작되고 UserDao 오브젝트가 만들어지고 런타임 시에 의존 관계를 맺는 대상, 실제 사용대상인 오브젝트를 의존 오브젝트라고 한다.
  • 의존관계 주입은 구체적인 의존 오브젝트와 그것을 사용할 주체(클라이언트라고 부르는 오브젝트)를 런타임시에 연결해주는 작업

의존관계 주입

  • 클래스 모델, 코드에는 런타임 시점의 의존 관계가 드러나지 않음(인터페이스로만 의존하고 있음)
  • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3의 존재가 결정
  • 의존 관계는 사용할 오브젝트에 대한 참조를 외부에서 주입해줌으로써 만들어짐.

설계 시점에서는 몰랏던 두 오브젝트의 관계를 맺도록 도와주는 제3의 존재의 유무 -> DI의 핵심

  • 제 3의 존재는 관계설정 책임을 가진 코드
    - 애플리케이션 컨텍스트, 빈 팩토리, IoC 컨테이너 등

UserDao의 의존관계 주입

관계설정의 책임 분리 전 UserDao 클래스

 public UserDao() {
        this.connectionMaker = new DConnectionMaker;
    }
  • 사용할 구체적인 클래스를 알고 있어야 함.
  • 이 코드의 문제는 이미 런타임 시의 의존관계가 코드 속에서 미리 다 결정되 있음.

관계설정의 책임 분리 후 UserDao 클래스

  public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
  • IoC 방식을 써서 UserDao에서 런타임 의존관계를 드러내는 코드를 제거
  • 제 3의 존재에게 런타임 의존관계 결정 권한을 위임

    보통 DI는 그 근간이 되는 IoC와 함께 사용해서 IoC/DI 컨테이너라는 식으로 많이 엮이는 개념이다.

여기서 정리를 하자면

  • DI컨테이너(스프링)에 의해 런타임 시, 의존 오브젝트를 사용할 수 있도록 그 래퍼런스를 전달받는 과정이 마치 위에서 설명했던 생성자 혹은 메소드를 통해 UserDao에게 오브젝트 주입해는 것과 같다고 해서 이를 의존관계 주입이라고 부름

1.7.3 의존관계 검색과 주입

  • 스프링이 제공하는 IoC 방법에는 의존관계 주입 뿐만 있는 것이 아니다.
  • 의존관계를 맺는 방법이 외부로부터 주입 외에 의존관계 검색도 있다.
  • 의존관계 검색은 런타임 시 의존관계를 맺을 오브젝트를 결정, 생성 작업은 외부 컨테이너에게 IoC로 맡기지만, 가져올때는 메소드나 생성자를 통한 주입 대신 스스로 컨테이너에게 요청하는 방법을 사용.
private ConnectionMaker connectionMaker;

public UserDao() {
		DaoFactory daoFactory = new DaoFactory();
        this.connectionMaker = daoFactory.connectionMaker;
    }
  • UserDao는 여전히 어떤 ConnectionMaker 오브젝트를 사용할지 모른다.(IoC 개념)
  • 외부로부터의 주입이 아닌 스스로 IoC 컨테이너인 DaoFactory에게 요청하는 것
  • 위의 DaoFactory의 경우 미리 준비된 메소드(connectionMaker)를 호출하는 것일 뿐이지만, 스프링의 애플리케이션 컨텍스트라면 이미 정해놓은 이름을 전달 -> 그 이름에 해당하는 오브젝트를 찾게 된다.

이해가 안될테니 코드로 보자

 AnnotationConfigApplicationContext context = 
 		new AnnotationConfigApplicationContext(DaoFactory.class);
 UserDao dao = context.getBean("connectionMaker", ConnectionMaker.class);
  • 이 메소드가 의존관계 검색에 사용되는 것

의존관계 주입 VS 의존관계 검색

  • 의존관계 주입이 코드가 더 깔끔하다.
  • 검색 방법은 오브젝트 팩토리 클래스 or 스프링 API가 등장한다.
  • 관심사가 다른 코드가 섞여 있을수도 있으므로 의존관계 주입 방식을 사용하는 편이 낫다.

그럼 의존관계 검색은 언제 쓰는가?

  • 애플리케이션 기동 시점에서 한번은 써야한다.
  • static method인 main()에서는 DI를 이용해 오브젝트를 주입받을 방법이 없다.
  • 서버는 main()같은 기동 메서드는 없지만, 아용자의 요청을 받을 때마다 main()과 비슷한 역할을 하는 서블릿에서 스프링 컨테이너에 담긴 오브젝트를 사용하려면 검색 방식을 통해 가져와야한다.

그럼 2개의 차이점이 뭔가요?

  • 의존관계 방식에서는 검색하는(UserDao) 오브젝트는 자신이 꼭 스프링 빈일 필요는 없다.
public class UserDao {

    private ConnectionMaker connectionMaker;

    public UserDao() {
        AnnotationConfigApplicationContext context = 
        		new AnnotationConfigApplicationContext(DaoFactory.class);
        this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
    }
    ...
}
  • UserDao는 굳이 스프링 빈일 필요가 없다( new UserDao()로 사용해도 된다. )
  • 이 경우는 ConnectionMaker 만 스프링 빈이면 된다.

반면 의존관계 주입에서는..

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

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

    private ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
    ...
}
  • UserDao와 ConnectionMaker 사이에 DI가 적용되러면 UserDao도 컨테이너가 만드는 빈이여야 한다.
  • UserDao가 빈이여야 컨테이너가 IoC 방식으로 UserDao에 대한 생성과 초기화 권한을 갖을 수 있다.

DI를 원하는 오브젝트는 먼저 자기가 컨테이너가 관리하는 빈이여야 한다.

DI 받는다.

DI의 동작 방식은 외부로부터의 주입이다.
그러면 외부에서 파라미터로 오브젝트를 넘겨주면 DI인가?

  • 주입 받는 메소드 파라미터가 특정 클래스 타입으로 고정되어 있다면 DI가 일어날 수 없다.
    - 이건 단순 오브젝트 주입이다.
  • 인터페이스 타입의 파라미터여야 동적으로 구현 클래스를 제공할 수 있다.
    - DI의 개념을 따르는 주입이다.

1.7.4 의존관계 주입의 응용

DI 기술의 장점

  • 코드에는 런타임 클래스에 대한 의존관계가 나타나지 않고, 인터페이스를 통해 결합도가 낮은 코드를 만듬
    - 다른 책임을 가진 사용 의존관계에 있는 대상이 변경되도 자신은 영향을 받지 않음.
    - 변경을 통한 다양한 확장 방법에는 자유롭다.

부가기능 추가(데코레이션 패턴)

  • EX) 위 코드에서 DB 연결 횟수를 카운팅하는 기능을 추가해야 한다.
  • 모든 DAO에 DB 연결 횟수를 카운팅하는 메소드를 넣는 방법 대신에 더 좋은 방법이 있다.

DI를 이용하는 방법

  • DAO와 DB 커넥션 사이에 오브젝트를 추가하면 된다.
  • 컨테이너가 사용하는 설정 정보만 수정해서 런타임 의존관계만 새롭게 정의한다.

CountingConnectionMaker.java

public class CountingConnectionMaker implements ConnectionMaker{
    
    int counter = 0;
    private ConnectionMaker realConnectionMaker;

    public CountingConnectionMaker(ConnectionMaker realConnectionMaker) {
        this.realConnectionMaker = realConnectionMaker;
    }

    @Override
    public Connection makeConnection() throws ClassNotFoundException, SQLException {
        this.counter++; // 카운팅
        return realConnectionMaker.makeConnection();
    }
    
    public int getCount(){
        return this.counter;
    }
}

DaoFactory.java

@Configuration
public class DaoFactory {
    @Bean
    public UserDao userDao() {
        return new UserDao(connectionMaker());
    }
    @Bean
    public ConnectionMaker connectionMaker() {
        return new CountingConnectionMaker(realConnectionMaker());
    }
    @Bean
    public ConnectionMaker realConnectionMaker(){
        return new DConnectionMaker();
    }
}
  • UserDao는 ConnectionMaker의 인터페이스만 의존하기에 ConnectionMaker 인터페이스를 구현하고 있다면 어떤 것이든 DI가 가능하다.
  • DConnectionMaker 대신 CountingConnectionMaker 오브젝트로 바꿔치기 한다.
  • UserDao가 DB커넥션을 가져올 때마다 CountingConnectionMaker 의 makeConnection이 실행되어 카운팅을 한다.
  • CountingConnectionMaker는 실제 DB 커넥션인 DConnectionMaker 오브젝트를 그대로 전달하여 기존 DAO들에 코드 변경 없이 빈 이름이 connectionMaker 인 상태로 작동된다.
public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
        CountingConnectionMaker dao = context.getBean("connectionMaker", CountingConnectionMaker.class);
        
        dao.getCount(); // DB 커넥션 갯수
        ....
	}
}

DI의 장점은 관심사의 분리를 통해 얻어지는 높은 응집도에서 나온다.

  • 모든 DAO가 직접 의존해서 사용할 ConnectionMaker 타입 오브젝트는 DaoFactory의 connectionMaker()에서 결정된다(한마디로 쟤만 바꾸면 모든 DAO의 return 오브젝를 한번에 바꾼다.)

1.7.5 메소드를 이용한 의존관계 주입

  • 의존관계 주입 시 반드시 생성자를 사용해야 하는 것은 아니다.

일반 메소드를 이용해 의존 오브젝트와의 관계 주입 방법

  1. 수정자(setter) 메소드를 이용한 주입
  2. 일반 메소드를 이용한 주입
    • 수정자 메소드처럼 set으로 시작해야하고, 한개의 파라미터만 가져야 된다는 제약이 싫다면 DI용으로 일반 메소드를 만들수 있다.

스프링은 생성자, 수정자 메소드, 초기화 메소드를 이용한 방법 외에도 다양한 의존관계 주입 방법을 지원한다.


1.8 XML을 이용한 설정

자바 코드로 일일이 오브젝트 사이의 의존정보를 설정하는것은 번거로우니 XML로 DI 의존관계 설정 정보를 만들어보자.

  • XML의 장점
    • XML은 단순한 텍스트 파일이며, 자바 파일과는 달리 컴파일과 같은 별도의 빌드 작업이 없다.
    • 환경이 달라져 오브젝트의 관계가 바뀌는 경우에도 빠르게 변경사항을 반영할 수 있다.
    • DTD와 스키마를 이용해서 정해진 포맷을 따라 작성 됐는지 확인이 용이하다.
      • DTD와 스키마
        • xml 문서는 미리 정해진 구조를 따라서 작성 됐는지 검사가능하다.
        • xml 문서의 구조를 정의 하는 방법에는 DTD와 스키마가 있으며, 스프링의 xml 설정파일은 2가지 방법을 모두 지원한다.
        • DTD보다는 스키마를 많이 쓰며 더 자세한건 검색하자.

1.8.1 XML 설정

ConnectionMaker() 전환

@Configuration
public class DaoFactory {
	@Bean
    public ConnectionMaker connectionMaker(){
        return new DConnectionMaker();
    }
}
<beans>
        <bean id="connectionMaker" class="패키지명.DConnectionMaker"></bean>
</beans>
  • @Configuration => <beans>
  • @Bean => <bean id="methodName">
  • return new BeanClass(); => <bean class="a.b.c...BeanClass" >
    • <bean> 태그의 class attribute에 지정하는 것은 자바 메소드에서 오브젝트를 만들때 사용하는 클래스 이름이다. 메소드의 리턴 타입을 class attribute에 사용 ㄴㄴ하다.

UserDao() 전환

  • 수정자 메소드가 많이 쓰였던 이유 중 하나가 XML로 의존관계 정보를 만들때 편리하는 점도 있다. (자바빈의 관례를 따라서 수정자 메소드는 프로퍼티가 된다.)

    • setConnectionMaker()라는 이름의 메소드가 있다면 connectionMaker라는 프로퍼티를 갖는다고 할 수 있다.
@Configuration
public class DaoFactory {
    @Bean
    public UserDao userDao(){
        UserDao userDao = new UserDao();
        userDao.setConnectionMaker(connectionMaker());
        return userDao;
    }
}
<bean id="userDao" class="패키지명.UserDao">
        <property name="connectionMaker" ref="connectionMaker"/>
</bean>
  • name은 property의 이름, ref는 수정자 메소드를 통해 주입해줄 오브젝트의 이름(빈의 ID)이다.

XML의 의존관계 주입 정보

별 내용은 아니고 빈의 이름이 바뀌는 경우 주의사항정도의 내용이다.

<beans>
	<bean id="connectionMaker" class="패키지명.DConnectionMaker"></bean>
    <bean id="userDao" class="패키지명.UserDao">
        <property name="connectionMaker" ref="connectionMaker"/>
    </bean>
</beans>
  • bean 이름이 변경되는 경우,(여기서는 connectionMaker) 해당 빈을 참조하는 property태그의 ref도 변경해야된다.
<beans>
	<bean id="myConnectionMaker" class="패키지명.DConnectionMaker"></bean>
    <bean id="userDao" class="패키지명.UserDao">
        <property name="connectionMaker" ref="myConnectionMaker"/>
    </bean>
</beans>

1.8.2 XML을 이용하는 애플리케이션 컨텍스트

applicationContext.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.xsd">
    <bean id="connectionMaker" class="DConnectionMaker"></bean>
    <bean id="userDao" class="UserDao">
        <property name="connectionMaker" ref="connectionMaker"/>
    </bean>
</beans>
public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
	}
}

1.8.3 DataSource 인터페이스로 변환

DataSource 인터페이스 적용

public interface ConnectionMaker {
    Connection makeConnection() throws ClassNotFoundException, SQLException;
}
  • 지금까지의 예시로 사용했던 ConnectionMaker는 DB 커넥션을 생성해주는 매우 단순한 인터페이스이다.
  • 자바에는 DB 커넥션을 가져오는 오브젝트의 기능을 추상화한 더 좋은 DataSource라는 인터페이스가 존재한다. DataSource로 ConnectionMaker를 대체하자.

UserDao.java

public class UserDao {
    
    private DataSource dataSource;

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

    public void add(User user) throws SQLException {
        Connection = dataSource.getConnection();
		...
    }
    ...
}
  • 다음은 DataSource의 구현 클래스가 필요하다.
  • SimpleDriverDataSource라고 테스트 환경에서 간단히 사용할 수 있는 스프링이 제공해주는 DataSource 구현클래스가 있다.

자바 코드 빈 설정 방식

DaoFactory.java

@Configuration
public class DaoFactory {
    @Bean
    public UserDao userDao(){
        UserDao userDao = new UserDao();
        userDao.setDataSource(dataSource());
        return userDao;
    }
    @Bean
    public DataSource dataSource() {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
        dataSource.setUrl("jdbc:mysql://localhost:3307/problem");
        dataSource.setUsername("root");
        dataSource.setPassword("1234");
        return dataSource;
    }
}

XMl 설정 방식(아직 property값은 없는 상태)


<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.xsd">
    <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
  		아직 DB 접속 정보가 없다.
  	</bean>
    <bean id="userDao" class="UserDao">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

1.8.4 프로퍼티 값의 주입

값 주입

  • dataSource() 를 보면 빈 오브젝트의 레퍼런스가 아닌 단순 정보(텍스트나 단순 오브젝트)도 오브젝트를 초기화 하는 과정에서 수정자 메소드에 넣는것을 볼 수 있다.
  • DB 접속 아이디가 바뀌어도 클래스 코드는 수정해줄 필요가 없다는 점에서 이것도 일종의 DI라 볼 수 있다.
  • <propery>를 이용해 주입할 정보를 지정할 때 레퍼런스인 경우는 <propery ref="">, 단순 값인 경우는 <propery value="">를 사용한다.
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost/problem"/>
        <property name="username" value="root"/>
        <property name="password" value="1234"/>
</bean>

value 값의 자동 변환

url, username, password은 모두 String 타입이지만 driverClass같은 경우는 java.lang.Class 타입이다. Class 타입의 파라미터를 갖는 수정자 메소드에 어떻게 사용이 가능한걸까...

  • 이런 설정이 가능한 이유는 스프링이 property값을 메소드의 파라미터 값을 참고해서 자바 타입으로 변환한다.
  • 내부적으로 reflection을 사용한다고 보면된다.
Class driverClass = Class.forName("com.mysql.cj.jdbc.Driver");
dataSource.setDriverClass(driverClass);
  • 해당 코드를 볼 수 있는 링크이다.

1.9 정리

1장의 등장 단어들

  • 관심사의 분리, 리팩토링
  • 전략 패턴
  • 개방 패쇄 원칙
  • 낮은 결합도, 높은 응집도
  • IoC
  • 싱글톤 레지스트리
  • DI
  • 생성자 주입과 수정자 주입
  • XML 설정

스프링의 관심은 "오브젝트와 그 관계" 이며, 그 오브젝트를 설계, 분리, 의존관계 결정은 개발자의 몫이다.

profile
천방지축 어리둥절 빙글빙글 돌아가는

2개의 댓글

comment-user-thumbnail
2022년 4월 26일

3줄요약

1개의 답글