토비의 스프링3 - 1부 이해 1.6~1.8 끝

2020. 5. 27. 11:37자바 Java/스프링(Spring) 프레임워크

반응형

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

 

앞에서 만들었던 코드에서 

DaoFactory의 userDao() 메소드를 두번 호출해서 리턴되는 UserDao 오브젝트를 비교해보자 

이 두개는 같은 오브젝트일까? 

 

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

동일하다(identical)는 것과 동등하다(equivalent)는 것은 다른 것이다. 

동일성은 ==연산자로, 동등성은 equlas()메소드를 이용해 비교한다. 

두개의 오브젝트가 동일하다면 사실은 하나의 오브젝트만 존재하는것이고 두개의 오브젝트 레퍼런스 변수를 갖고 있는 것. 

동등하다면 두개의 서로다른 오브젝트가 메모리상에 존재한다. 

 

여기서 궁금한 것은 DaoFactory의 userDao()를 여러번 호출했을때 동일한 오브젝트가 돌아오는가? 이다 

새로운 Test클래스를 만들어서 직접 출력해 보았다. 

package springbook.user.dao;
import java.sql.SQLException;
public class Test {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
	DaoFactory factory = new DaoFactory();
	UserDao dao1=factory.userDao();
	UserDao dao2=factory.userDao();
	System.out.println(dao1);
	System.out.println(dao2);
	}
}

코드를 보면 매번 userDao메소드를 호출할 때마다 new 연산자에 의해 새로운 오브젝트가 만들어지게 되어있다. 

당연히 오브젝트를 직접 출력하면 오브젝트별로 할당되는 고유한 값이 출력 된다. (이 값이 같으면 동일한오브젝트) 

 

즉 두개의 오브젝트가 생겼다. 

 

 

이번엔 스프링의 애플리케이션 컨텍스트에 DaoFactory를 설정정보로 등록하고 

getBean() 메소드를 이용해 userDao라는 이름으로 등록된 오브젝트를 가져와보자. 

여기에서도 애플리케이션 컨텍스트가 DaoFactory의 userDao() 메소드를 호출해서 userDao 타입 오브젝트를 만드는 것은 마찬가지이다. 하지만 다른 결과가 나온다. 

package springbook.user.dao;
import java.sql.SQLException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		ApplicationContext context=new
				AnnotationConfigApplicationContext(DaoFactory.class);
		UserDao dao3=context.getBean("userDao",UserDao.class);
		UserDao dao4=context.getBean("userDao",UserDao.class);
		
		System.out.println(dao3);
		System.out.println(dao4);
	}
}

 

getBean() 을 두번 호출해서 가져온 오브젝트가 동일하다 .

단순하게 getBean()을 실행할 때마다 userDao() 메소드를 호출하고 매번 new에 의해 새로운 UserDao가 만들어지지않는다는 뜻이다.

 

오브젝트 팩토리와 스프링 애플리케이션 컨텍스트의 동작방식에는 무언가 차이점이 있다. 

왜그럴까? 

 

 

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

애플리케이션 컨텍스트는 오브젝트 팩토리와 비슷한 방식으로 동작하는 IoC컨테이너이며 동시에 싱글톤을 저장하고 관리하는 싱글톤 레지스트리(singleton registry)이다. 

 

스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다. 

스프링이 주로 적용되는 대상이 자바 엔터프라이즈 기술을 사용하는 서버환경이기 때문에 싱글톤으로 빈을 만든다. 

 

스프링이 처음 설계 됐던 대규모의 엔터프라이즈 서버 환경은 서버 하나당 최대로 초당 수십에서 수백번씩 요청을 받아 처리할 수 있는 높은 성능이 요구되는 환경이었다. 또 하나의 요청을 처리하기 위해 데이터 액세스 로직, 서비스 로직, 비지니스 로직, 프레젠테이션 로직 등 다양한 기능을 담당하는 오브젝트들이 참여하는 계층형 구조로 이루어진 경우가 대부분이다. 그런데 매번 클라이언트에서 요청이 올때마다 각 로직을 담당하는 오브젝트를 새로 만들어서 사용한다고 하면 부하가 걸리고 서버가 감당하기 힘들다.  그래서 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 사용해 왔다. 서블릿은 자바 엔터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트라고 할 수 있다. 

서블릿은 대부분 멀티스레드 환경에서 싱글톤으로 동작한다. 서블릿 클래스당 하나의 오브젝트만 만들어두고 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다. 

 

따라서 서버 환경에서는 서비스 싱글톤의 사용이 권장된다. 

 

<자바에서 싱글톤을 구현하는 방법 >

클래스 밖에서는 오브젝트를 생성하지 못하도록 생성자를 private 으로 만든다. 

생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다. 

스태틱 팩토리 메소드인 getInstance()를 만들고 이 메소드가 최초로 호출되는 시점에서 한번만 오브젝트가 만들어지게 한다. 

생성된 오브젝트는 스태틱 필드에 저장된다. 또는 스태택 필드의 초기값으로 오브젝트를 미리 만들어둘 수도 있다. 

한번 오브젝트(싱글톤)이 만들어지고 난 후에는 getInstance() 메소드를 통해 이미 만들어져 스태틱 필드에 저장해둔 오브젝트를 넘겨준다. 

 

package springbook.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import springbook.user.domain.User;

public class UserDao {
	private static UserDao INSTANCE;
	private ConnectionMaker connectionMaker;

	public UserDao(ConnectionMaker connectionMaker) {
		this.connectionMaker = connectionMaker;
	}
	
	public static synchronized UserDao getInstance() {
		if(INSTANCE==null) INSTANCE=new UserDao(???);  // 이부분이 에러난다. 
		return INSTANCE;
	}

	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = connectionMaker.makeConnection();
		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 = 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"));
		rs.close();
		ps.close();
		c.close();
		return user;
	}
}

위의 코드는 문제가 있다. 

private 로 바뀐 생성자는 외부에서 호출할 수가 없기 때문에 DaoFactory 에서 UserDao를 생성하며 ConnectionMaker 오브젝트를 넣어주는게 이제는 불가능 하다. 

 

 

<싱글톤 패턴 구현 방식에서의 문제점>

private 생성자를 갖고 있기 때문에 상속할 수 없다. 또한 상속과 다형성 같은 객체지향의 특징이 적용되지 않는 스태틱 필드와 메소드를 사용하는 것도 같은 문제를 발생 시킨다. 

싱글톤은 테스트 하기 힘들다 

서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다. 서버에서 클래스 로더를 어떻게 구성하고 있느냐에 따라서 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질수 있다. 

싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직 하지 못하다. 

 

 

하지만 이런 문제점이 있음에도 불구하고 스프링은 서버환경에서 싱글톤이 만들어져서 서비스 오브젝트 방식으로 사용되는 것을 적극 지지한다. 그래서 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다. 그것이 바로 싱글톤 레지스트리이다. 

스프링 컨테이너는 싱글톤을 생성하고 관리하고 공급하는 싱글톤 관리 컨테이너 이기도 하다. 

싱글톤 레니스트리의 장점은 스태틱 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아니라 평범한 자바 클래스로 싱글톤을 활용 하게 해준다는 것이다. 

평범한 자바 클래스라도 IoC 방식의 컨테이너를 사용해서 생성과 관계 설정, 사용등에 대한 제어권을 컨테이너에게 넘기면 손쉽게 싱글톤 방식으로 만들어져 관리할 수 있다. 

오브젝트 생성에 관한 모든 권한은 IoC 기능을 제공하는 애플리케이션 컨텍스트에게 있기 때문이다 .

 

스프링의 싱글톤 레지스트리 덕분에 싱글톤 방식으로 사용될 애플리케이션 클래스라도 public 생성자를 가질 수 있다. 

싱글톤으로 사용돼야 하는 환경이 아니라면 간단히 오브젝트를 생성해서 사용할 수 있다. 

따라서 테스트 환경에서 자유롭게 오브젝트를 만들수 있고 테스트를 위한 목 오브젝트(Mock- mockup object 말그대로 테스트를 위한 기능을 하는 오브젝트)로 대체하는 것도 간단하다. 

 

스프링이 빈을 싱글톤으로 만드는 것은 결국 오브젝트의 생성방법을 제어하는 IoC 컨테이너로서의 역할이다. 

위의 코드에서 이미 UserDao는 스프링 IoC를 적용하면서 싱글톤으로 만들어진다. 그래서 getBean()을 여러번 호출해서 UserDao을 요청하더라도 매범 동일한 오브젝트를 받게 된다. 

 

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

싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용 할 수 있다. 

기본적으로 싱글톤이 멀티스레드 환경에서 서비스형태의 오브젝트로 사용되는 경우에는 상태정보를 내부에 갖고 있지 않은 무상태방식으로 만들어져야 한다. 다중 사용자의 요청을 한꺼번에 처리하는 스레드들이 동시에 싱글톤 오브젝트의 인스턴스 변수를 수정하는 것은 매우 위험하다. 저장할 공간이 하나뿐이니 서로 값을 덮어쓰고 자신이 저장하지 않은 값을 읽어올 수 있기 때문이다. 

따라서 싱글톤은 기본적으로 인스턴스 필드의 값을 변경하고 유지하는 상태 유지 방식으로 만들지 않는다. 

 

1.6.3 스프링 빈의 스코프 

빈의 스코프 : 스프링이 관리하는 오브젝트, 즉 빈이 생성되고 존재하고, 적용되는 범위 

스프링 빈의 기본 스코프는 싱글톤이다. 

싱글톤 스코프는 컨테이너 내에 한 개의 오브젝트만 만들어져서, 강제로 제거하지 않는 한 스프링 컨테이너가 존재하는 동안 계속 유지된다. 

스프링에서 만들어지는 대부분의 빈은 싱글톤 스코프를 갖는다.

하지만 경우에 따라서는 싱글톤 외의 스코프를 가질 수 있다. 

->빈을 요청할때마다 매번 새로운 오브젝트를 만들어주는 프로토 타입 스코프, 웹을 통해 새로운 HTTP 요청이 생길때마다 생성되는 요청(request)스코프, 웹의 세션과 스코프가 유사한 세션 스코프 등 

 

1.7 의존관계 주입(DI) 

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

객체를 생성하고 관계를 맺어주는 등의 작업을 담당하는 기능을 일반화 한 것이 스프링의 IoC 컨테이너.

스프링의 IoC 기능의 대표적인 동작 원리는 주로 의존관계 주입이라고 불린다. 이는 스프링이 여타 프레임워크와 차별화 돼서 제공해주는 기능을 분명히 드러낸다. 

그래서 초기에는 주로 IoC 컨테이너라 불리던 스프링이 지금은 의존관계 주입 컨테이너 또는 DI 컨테이너 라고 불린다. 

DI의 핵심은 오브젝트 레퍼런스를 외부로부터 제공(주입)받고 이를 통해 여타 오브젝트와 다이내믹하게 의존관계가 만들어지는 것이다. 

 

1.7.2 런타임 의존관계 설정

두 개의 클래스 또는 모듈이 의존관계에 있다고 말할 때는 항상 방향성을 부여해주어야 한다. 

다음의 그림은 A가 B에 의존하고 있음을 나타낸다. 

의존하고 있다는 것은 B가 변하면 A에 영향을 미친다는 것이다. 대표적인 예는 A에서 B에 정의된 메소드를 호출해서 사용하는 경우이다. 

만약 B에 새로운 메소드가 추가되거나 기존 메소드의 형식이 바뀌면 A도 그에 따라 수정되거나 추가돼야 한다. 

반면, B는 A에 의존하지 않는다. 

 

의존관계 주입이란 다음의 세가지 조건을 충족하는 작업을 말한다. 

1. 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다. 

2. 런타임 시점의 의존관계는 컨테이너나 팩토리같은 제 3의 존재가 결정한다. 

3. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입) 해줌으로써 만들어진다. 

 

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

DI에서 말하는 제3의 존재는 바로 관계 설정 책임을 가진 코드를 분리해서 만들어진 오브젝트라고 볼 수 있다. 

전략패턴에 등장하는 클라이언트나 스프링의 애플리케이션 컨텍스트, 빈 팩토리, IoC 컨테이너 등이 모두 외부에서 오브젝트 사이의 런타임관계를 맺어주는 책임을 지닌 제 3의 존재라고 볼 수 있다. 

 

 

1.7.3 의존관계 검색과 주입 

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

스프링의 IoC 컨테이너인 애플리케이션 컨텍스트는 getBean() 이라는 메소드를 제공한다. 이 메소드가 의존관계 검색에 사용되는 것이다. 

 

코드로보면 DI가 더 단순하고 깔끔하지만 DL을 사용해야 할때가 있다. 애플리케이션 기동 시점에서 적어도 한번은 DL 을 사용해 오브젝트를 가져와야 한다. 스태틱 메소드인 main() 에서는 DI를 이용해 오브젝트를 주입받을 방법이 없기 때문이다. 서버에서도 마찬가지이다. 

 

 

의존관계 주입(DI) 와 의존관계 검색방식(DL)의 차이점 

의존관계 검색 방식에서는 검색하는 오브젝트는 자신이 스프링의 빈일 필요가 없다. 

반면 의존관계 주입를 원하는 오브젝트는 먼저 자신이 컨테이너가 관리하는 빈이 되어야 한다. 

 

1.7.4 의존관계 주입의 응용 

 

 

 

 

 

1.8 XML 을 이용한 설정 

스프링은 자바클래스를 이용하는 것 외에도 다양한 방법을 통해 DI 의존관계 설정정보를 만들 수 있다.

가장 대표적인 것이 XML.

XML은 단순한 텍스트 파일 이기 때문에 다루기 쉽다. 또, 쉽게 이해할 수 있으며 컴파일과 같은 별도의 빌드 작업이 없다는 것도 장점.

환경이 달라져서 오브젝트의 관계가 바뀌는 경우에도 빠르게 변경사항을 반영할 수 있다. 

 

1.8.1 XML 설정 

스프링의 애플리케이션 컨텍스트는 XML 에 담긴 DI 정보를 활용 할 수 있다. 

하나의 @ Bean 메소드를 통해 얻을 수 있는 빈의 DI 정보는 세가지 이다. 

1. 빈의 이름 : @ Bean  메소드 이름이 빈의 이름이다. 이 이름은 getBean() 에서 사용된다. 

2. 빈의 클래스 : 빈 오브젝트를 어떤 클래스를 이용해서 만들지를 정의한다. 

3. 빈의 의존 오브젝트: 빈의 생성자나 수정자 메소드를 통해 의존 오브젝트를 넣어준다. 의존 오브젝트도 하나의 빈이므로 이름이 있을 것이고 그 이름에 해당하는 메소드를 호출해서 의존 오브젝트를 가져온다. 의존 오브젝트는 하나 이상 일수도 있다. 

XML에서 <bean>을 사용해도 이 세가지 정보를 정의 할 수 있다. 

XML은 핵심 요소를 잘 짚어서 그에 해당하는 태그와 애트리뷰트가 무엇인지 알아야 한다. 

 

 

 

 

 

<용어 정리> 

싱글톤 패턴 : 디자인 패턴중에서 가장 자주 활용되는 패턴이며 비난 받는 패턴이기도 하다. 

어떤 클래스를 애플리케이션 내에서 제한된 인스턴스 개수 , 이름처럼 주로 하나만 존재하도록 강제하는 패턴이다. 

이렇게 하나만 만들어지는 클래스의 오브젝트는 애플리케이션 내에서 전역적으로 접근이 가능하다. 단일 오브젝트만 존재해야 하고 이를 애플리케이션의 여러곳에서 경유하는 경우에 주로 사용 한다. 

 

 

반응형