토비의 스프링 1장

메이도·2023년 3월 5일

주요 용어

  • 리팩토링: 기능에는 영향을 주지 않으며 코드의 구조만 변경하는 것
  • 디자인 패턴: 소프트웨어 설계 시 자주 나타나는 문제를 해결하기 위해 사용하는 재사용 가능한 솔루션
  • 의존 관계(링크, 런타임 사용관계): 한쪽의 변화가 다른 쪽에 영향을 주는 것.
  • 의존관계 주입: 오브젝트의 레퍼런스가 외부로부터 주입되고 이를 통해 다른 오브젝트와의 의존관계가 다이내믹하게 만들어지는 것.
  • IoC(제어의 역전): 모든 제어 권한이 다른 대상에게 위임되어 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다.
  • 자바빈: 다음 두 관례를 따라 만들어진 오브젝트
    • 디폴트 생성자: 자바빈은 프레임워크에서 오브젝트를 생성하기에 파라미터가 없는 디폴트 생성자 필수
    • 프로퍼티: 자바빈이 노출하는 이름을 가진 속성(setter와 getter을 통해 수정 가능)
  • 결합도: 결합도가 낮으면 다른 오브젝트 또는 모듈과 느슨한 연결을 유지한다. 하나의 오브젝트가 변경이 일어날 때 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도
  • 응집도: 응집도가 높을수록 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다. 따라서 기능의 변경이 일어날 때 해당하는 모듈에서의 변화가 가장 크다.
  • 추상클래스 vs 인터페이스
    • 추상클래스: 부모 클래스의 기능을 확장한다.
    • 인터페이스: 함수의 구현을 강제하는 역할을 한다.
  • 스프링 빈: 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트
  • 빈 팩토리: 빈 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트
  • 스프링 컨테이너/애플리케이션 컨텍스트=> 스프링 런타임 엔진: 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC 엔진
  • 빈 스코프: 스프링이 관리하는 오브젝트의 생성/적용 범위. 스프링 컨테이너가 존재하는 동안 유지된다.

1.2 관심사 분리

코드의 변경을 쉽게 하고 변경이 다른 문제를 일으키지 않게 하기 위해 분리와 확장을 고려해 설계한다.
ex) DAO 접속 암호를 변경하기 위해 모든 DAO class를 변경해야 하는 경우
관심이 같은 것은 하나의 객체로 모으고, 관심이 다른 것은 떨어져 영향을 주지 않도록

  1. 중복 코드 메소드 추출:
    db 커넥션을 가져오기 위한 부분을 함수로 따로 분리한다.
    하나의 함수 안에서 db 정보를관리할 수 있음
public class UserDao{
	public void add(User user) throws ClassNotFoundException, SQLException{
    	Connection c = getConnetion();
        ...
    }
    
	private Connection getConnection() throws ClassNotFoundException, SQLException{
		Class.forName("com.mysql.cj.jdbc.driver")
    	Connection c = DriverManager.getConnection("url", "user", "password");
    	return c;
	}
}
  1. 상속을 통한 확장(abstract)
    UserDao 안에서 getConnection을 분리하기
    -> abstract class UserDao를 생성하고
public abstract class UserDao{
	public void add(User user) throws ClassNotFoundException, SQLException{
    	Connection c = getConnetion();
        ...
    }
    
	public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}

다시 UserDao를 상속받아 getConnetction을 구현하는 DUserDao를 만든다.(오버라이딩)

public class DUserDao extends UserDao{
	public Connection getConnection() throws ClassNotFoundException, SQLException{
    	Class.forName("com.mysql.cj.jdbc.driver")
    	Connection c = DriverManager.getConnection("url", "user", "password");
    	return c;
    }
}

그러면 UserDao의 add()와 get() 함수를 갖고 있지만 DUserDao에서 자체적으로 getConnection()을 구현해서 사용할 수 있다.
but 클래스는 상속이 하나만 가능하기 때문에 다른 용도로 상속을 적용하기 힘듦

디자인 패턴

  • 템플릿 메소드 패턴: 부모클래스에 변하지 않는 기능을 구현하고, 자주 변경되어 확장할 기능은 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만든 뒤 서브클래스에서 메필요에 맞게 구현해서 사용하도록 하는 것(템플릿을 미리 생성해서 사용하는 기능)
    hook method: 선택적으로 오버라이드 할 수 있도록 부모 클래스에 만들어놓은 메소드
  • 팩토리 메소드 패턴: 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것(자식클래스에서 객체 생성 후 반환)
    오브젝트 생성 방법을 슈퍼클래스의 기본 코드에서 독립시킨다.

1.3 클래스 분리

connection 생성을 위해 SimpleConnectionMaker 클래스를 생성하고 이 클래스 안에 커넥션 생성 함수인 makeNewConnection()을 만든다.

public class UserDao{
	private SimpleConnectionMaker simpleConnectionMaker;
    
    public UserDao(){
    	simpleConnectionMaker = new SimpleConnectionMaker();
    }
    
    public void add(User user) throws ClassNotFoundException, SQLException{
    	Connection c = simpleConnectionMaker.makeNewConnection();
        ...
    }
}

단점
1. dao가 늘어날수록 add함수와 같은 db 접근 함수 안의 makeNewConnection과 같은 함수를 전부 수작업으로 고쳐야 한다.(고객사는 openConnection이라는 이름의 연결 함수를 쓸 수도 있음)
2. UserDao가 db 커넥션 용 클래스를 명확하게 알아야 한다.

인터페이스 도입

UserDao와 SimpleConnectionMaker 사이에 인터페이스를 넣어 추상적인 느슨한 연결고리를 만든다.
추상화: 공통적인 성격을 뽑아내 따로 분리하는 것으로 인터페이스를 사용한다.
인터페이스: 어떤 일을 하겠다는 기능만을 정의해놓은 것으로 구현 방법은 인터페이스를 상속해서 구현하는 자식 클래스에서 정해진다.
인터페이스를 통해 자신이 구현한 클래스에 대한 구체적 정보를 모두 감춘다.

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


public class DConnectionMaker implements ConnectionMaker{
    public Connection makeConnection() throws ClassNotFoundException, SQLException{
    ... 연결 코드 구현
}


public class UserDao {
    ConnectionMaker connectionMaker;//어떤 클래스를 사용하는지 알지 못함

    public UserDao(){
    	connectionMaker = new DConnectionMaker();//클래스 이름이 직접적으로 보임
    }
    
    public void add(User user) throws ClassNotFoundException, SQLException{
    	Connection c = connetionMaker.makeConnection();
        ...사용자 저장 코드
    }

add()에서 인터페이스의 makeConnection 함수를 구현한 DConnectionMaker 객체를 사용해 연결함으로 고객사가 ConnectionMaker을 상속받아 자체적으로 커넥션 클래스를 생성해도 makeConnection()이 다른 이름으로 변경되지 않는다.(변경에는 닫혀있다)

관계 설정의 책임 분리

UserDao가 어떤 ConnectionMaker의 구현 클래스를 사용할지 모르게 설정하자!

객체지향의 다형성에 의해 클래스 B에 대해 알지 못해도 B가 A 인터페이스를 구현한 클래스라면 B의 오브젝트를 인터페이스 타입(A)로 받아서 사용할 수 있다.

public class UserDao {
    ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker){//의존관계 주입 부분
    	this.connectionMaker = connectionMaker;
    }
    
    
//UserDaoTest 클래스의 main 함수
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao dao = new UserDao(connectionMaker);

UserDao의 클라이언트에게 ConnectionMaker 구현 클래스를 결정하는 책임이 전가됨.

개방 폐쇄 원칙

클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.
인터페이스를 통해 구현하게 되면 확장을 위해서는 개방돼 있지만 불필요한 변화는 폐쇄돼 있다.

SOLID(객체지향 설계 원칙): 특별한 상황에서 발생하는 문제에 대한 구체적인 해결책인 디자인 패턴에 비해 일반적인 상황에서 객체 지향의 특징을 잘 살려 적용 가능한 설꼐 원칙

+) UserDao와 ConnectionMaker은 응집도가 높고 서로의 연결이 느슨함으로 결합도가 낮다.

전략 패턴

변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 구현 클래스를 필요에 따라 바꿔서 사용할 수 있는 디자인 패턴.
UserDao는 ConnectionMaker을 인터페이스로 정의해 구현 클래스를 바꿔가며 사용할 수 있는 전략패턴이다.

1.4 IoC: 제어의 역전

팩토리

팩토리란 객체의 생성 방법을 결정하고 생성된 오브젝트를 돌려주는 것.(오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 분리하는 목적)

public class DaoFactory {//팩토리를 통해 객체를 생성해서 반환함으로 UserDaoTest에서 ConnectionMaker을 결정하는 것을 막음
    public UserDao userDao(){
        ConnectionMaker connectionMaker = new DConnectionMaker();
        UserDao userDao = new UserDao(connectionMaker);
        return userDao;
    }

다른 dao객체를 생성하는 함수를 DaoFactory 안에 만들 시 new DConnectionMaker() 가 중복되게 된다.

public class DaoFactory {
    public UserDao userDao(){
        return new UserDao(connectionMaker());
    }//new DConnectionMaker() 중복이 제거됨

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

라이브러리는 애플리케이션의 코드를 직접 제어하지만 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용된다.
처음에는 ConnectionMaker의 구현 클래스를 결정하고 오브젝트를 생성하는 제어권은 UserDao에게 있었지만 지금은 DaoFactory에게 있다.(UserDaoTest도 DaoFactory가 공급하는 ConnectionMaker을 사용하는 수동적 클래스가 됐다.

스프링 빈 팩토리 설정정보

빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식하게 하기 위해 @Configuration 애노테이션 추가

@Configuration: 애플리케이션 컨텍스트가 사용할 설정정보
@Bean: 오브젝트 생성을 담당하는 IoC용 메소드

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
        this.dao = context.getBean("userDao", UserDao.class);

userDao라는 이름의 빈을 가져온다는 것은 DaoFactory의 userDao()를 호출해 그 결과를 가져오는 것과 동일하다.
애플리케이션 컨텍스트에 DaoFactory 클래스를 설정정보로 등록 -> @Bean 메소드 이름을 가져와 빈 목록 생성 ->getBean() 호출 시 빈 목록에서 빈 이름 찾음-> 빈 생성 메소드 호출해 오브젝트 생성 후 클라이언트에게 반환

애플리케이션 컨텍스트의 장점
1. 클라이언트는 구체적인 팩토리 클래스를 알 필요 없다(DaoFactory를 사용할 것이다!)
2. 종합적인 IoC 서비스 제공(오브젝트 후처리, 시점 등)
3. 빈 검색하는 다양한 방법을 제공한다(타입/애노테이션 기반 검색)

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

동일성: 완전히 같은 동일한 오브젝트 ==, Object의 equals()메서드
동등성: 동일한 정보를 담고 있는 오브젝트 equals()

  • 스프링은 여러 번 빈을 요청하더라도 동일한 오브젝트(싱글톤)를 돌려준다.
  • 애플리케이션 컨텍스트는 직접 싱글톤 오브젝트를 만들어 저장하고 관리하는 싱글톤 레지스트리이다.(public 생성자 사용 가능)

싱글톤 패턴: 하나만 만들어지는 클래스의 오브젝트는 애플리케이션 내에서 전역적으로 접근이 가능하다.

싱글톤의 단점

  • 어느 클래스에서도 접근 수정이 가능한 전역 상태가 된다.
  • 싱글톤은 자기 자신의 생성자에서만 객체를 생성할 수 있어 다형성을 사용할 수 없다(private 형태의 constructor만 갖을 수 있음)
  • 오브젝트 생성이 어려워 테스트가 힘들다.
  • 서버 환경에서는 싱글톤이 하나만 만들어짐을 보장 하지 못한다.

싱글톤은 인스턴스 필드 값을 변경하고 유지하는 상태유지(stateful) 방식으로 만들지 않는다. 싱글톤 패턴은 private Connection c와 같이 상태가 변하는 변수를 갖으면 하나의 객체가 공유되어 c의 정보에 혼란이 생길 수 있다. 따라서 자신이 사용하는 다른 싱글톤 빈을 저장하는 읽기 전용 인스턴스 변수는 사용 가능하다.

1.7 의존 관계 주입

A가 B를 의존한다 = A가 B에 정의된 메소드를 호출해서 사용한다.

  • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로써 만들어진다.
  • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3의 존재가 결정
  • 코드에서 런타임 시점의 의존관계가 드러나지 않으려면 인터페이스에만 의존해야 한다.

getBean()을 통해 의존관계를 검색해 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾는다. getBean()을 사용하는 클래스는 스프링의 빈이 아니어도 된다. getBean()에서 검색하는 빈 이름만 등록돼있으면 된다.

의존관계에 주입에는 두 가지 방법이 있다.
1. 수정자 메소드(setter)을 이용해 주입
파라미터로 전달된 값을 내부 인스턴스 변수에 저장한다.
2. 일반 메소드를 이용해 주입

0개의 댓글