Spring - (23) : 오브젝트와 의존관계

­이승환·2021년 12월 17일
0

spring

목록 보기
20/26

Overview


앞선 포스팅의 경우 개인적으로 개념적인 부분을 따로 쪼개서 추가한 자료들이다. 이번 포스팅 부터는 토비의 스프링 책을 읽고 정리해서 TIL을 남기고자 한다.

혹시 문제가 된다면 lshn1007@hanyang.ac.kr 로 메일 주면 삭제하겠습니다.

제어의 역전

개발자가 작성한 코드를 통해 객체를 생성하고, 메소드를 호출하는 등 코드를 사용하는 쪽에서 프로그램의 실행 흐름을 제어하는 것이 아니라, 역으로 프레임워크나 컨테이너 등이 제어권을 가지고 프로그램의 실행 흐름을 제어하는 것을 제어의 역전이라 한다.

// A 클래스의 생성자에서 B 클래스의 객체를 생성한다. (사용하는 쪽에서 실행 흐름을 제어)
public class A {
    private B b;
	
    public A() {
        b = new B();
    }
}

// 스프링 프레임워크가 미리 생성해둔 B 객체를 A 클래스에 주입하여 사용할 수 있게 한다. (프레임워크에서 실행 흐름을 제어)
public class A {
    @Autowired
    private B b;
}

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

  • @Configuration : 애플리케이션 컨텍스트 또는 빈 팩토리가 사용할 설정정보라는 표시
  • @Bean : 메서드에서 반환하는 객체를 애플리케이션 컨텍스트 또는 빈 팩토리의 빈으로 등록하고자 할 때 사용하는 표시(@Configuration 어노테이션을 사용한 클래스 내에서만 사용 가능하다.)
  • xml 로도 추가 가능하다
/**
 * userDao, connectionMaker Bean 등록
 */
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DaoFactory {

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

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

위에서 생성한 Application Context(빈 팩토리) 는 아래와 같이 테스트해볼 수 있다.

/**
 * 애플리케이션 컨텍스트에 등록된 빈 목록에서 userDao 빈 조회
 */
public class TestApplication {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
        UserDao userDao = context.getBean("userDao", UserDao.class);
    }
}

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

  1. @Configuration이 붙은 클래스를 설정 정보로 등록한다.
  2. @Configuration 클래스 내의 @Bean이 붙은 메소드의 이름을 가져와 빈 목록을 만든다.
  3. 클라이언트가 애플리케이션 컨텍스트의 getBean() 메소드를 호출한다.
  4. 빈 목록에서 클라이언트가 요청한 이름의 빈이 있는지 찾고, 있다면 빈을 생성하여 반환한다.

애플리케이션 컨텍스트의 장점

  • 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
  • 종합 IoC 서비스를 제공한다.
  • 빈을 검색하는 다양한 방법을 제공한다.
  • 싱글톤으로 관리할 수 있다.

스프링 IoC의 용어정리

빈(bean)

  • 스프링 프레임워크가 IoC 방식으로 관리하는 객체(스프링이 직접 생성과 제어를 담당하는 객체)

빈 팩토리(bean factory)

  • 스프링의 IoC를 담당하는 핵심 컨테이너
  • 빈 관리 기능(빈 등록, 생성, 조회, 반환 등)을 담당한다.
  • 빈 팩토리는 바로 사용하지 않고 이를 확장한 애플리케이션 컨텍스트를 이용한다.

애플리케이션 컨텍스트(application context)

  • 빈 팩토리를 확장(상속)한 IoC 컨테이너
  • 빈 관리 기능에 더불어 Spring이 제공하는 각종 부가 기능을 추가로 제공

설정정보/설정 메타정보(configuration metadata)

  • 애플리케이션 컨텍스트 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보
  • 컨테이너에 어떤 기능을 설정하거나 조정하는 경우에도 사용하나, 그보다는 IoC 컨테이너에 의해 관리되는 빈 객체를 생성하고 구성할 때 사용된다.

컨테이너 또는 IoC 컨테이너

  • 애플리케이션 컨텍스트, 빈 팩토리와 동일하나, IoC 방식으로 빈을 관리한다는 의미에서 다르게 말할 때 사용

  • 애플리케이션 컨텍스트는 ApplicationContext 인터페이스를 구현한 오브젝트를 가리키기도 하는데, 하나의 애플리케이션에서 보통 여러 개가 만들어져 사용된다. 이를 통틀어서 스프링 컨테이너라고 부를 수 있다.

  • 스프링 프레임워크

IoC 컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말할 때 주로 사용하는 용어

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

  • 스프링의 애플리케이션 컨텍스트는 빈 객체를 싱글톤으로 저장하고 관리한다.
  • 이유는 대규모 서버 환경에서 매 클라이언트의 요청마다 로직 처리를 위해 객체를 생성해야 할 경우, 생성한 객체의 메모리 반환을 위해 GC 수행 횟수가 빈번해지면서 서버에 가해지는 부하가 상당해지기 때문이다.
  • 따라서 애플리케이션 컨텍스트 내에 싱글톤으로 하나의 객체를 생성해놓고 클라이언트의 요청이 들어오면 생성해놓은 객체를 반환해 이를 여러 스레드에서 공유하여 사용할 수 있도록 하는 싱글톤 레지스트리 로서의 기능을 지원한다.

싱글톤 패턴이란?

  • 디자인 패턴 중의 하나로 애플리케이션 내에 인스턴스가 단 하나만 존재하도록 강제하는 패턴이다.
  • 싱글톤 패턴으로 생성된 객체는 여러 클래스에서 전역적으로 접근이 가능해지기 때문에 여러 문제점을 내포하고 있으며 사용 시 주의가 필요하다.

싱글톤 패턴의 한계

  • 전형적인 싱글톤 패턴을 사용한 클래스는 한계가 뚜렷하다.
  • private 생성자를 갖고 있어 상속 불가(객체지향적 설계 적용 불가)
  • 서버환경에서 싱글톤 오브젝트가 단 하나만 만들어지는 것을 보장하지 못함(여러 개가 만들어질 수 있음)

싱글톤 레지스트리

  • 자바의 기본적인 싱글톤 패턴의 구현 방식을 이용할 경우에는 위에서 말한 한계, 단점이 그대로 적용된다.
  • 하지만, 스프링에서는 Bean 객체를 싱글톤으로 생성, 관리, 공급하는 싱글톤 레지스트리 기능을 지원(싱글톤 패턴을 직접 구현하지 않아도, ApplicationContext에 객체를 Bean으로 등록하면 해당 객체는 싱글톤으로 등록되어 관리됨) 하기 때문에 싱글톤 객체임에도 public 생성자를 가질 수 있게 되는 등. 싱글톤 패턴의 장점만 취할 수 있게 되었다.

싱글톤과 오브젝트의 상태

  • 싱글톤 객체는 반드시 내부에 상태 정보를 갖지 않는 무상태(stateless) 방식으로 만들어져야 한다.
  • 여러 스레드에서 전역적으로 사용하는 싱글톤 객체 내부에 상태 정보가 보관될 경우, 싱글톤 객체의 값을 서로 덮어 쓰거나 아직 저장되지 않은 값을 읽어올 수 있기 때문이다.
  • 스프링에서는 Bean으로 관리되는 싱글톤 객체는 한번 초기화되고 나면 이후에는 수정할 수 없기 때문에 멀티 스레드 환경에서 사용해도 문제가 없다.

따라서 팩토리 패턴에서 유례된 IoC (스프링 컨텍스트) 를 싱글톤으로 관리하여 활용한다.

스프링 빈의 스코프란?

  • 빈의 스코프 란, 스프링이 관리하는 오브젝트인 빈이 생성되고, 존재하고, 적용되는 범위를 말한다.
  • 스프링 빈의 default 스코프는 싱글톤이다.
  • 경우에 따라서는 싱글톤 외의 스코프를 가질 수 있다. (prototype, request, session)

이와 관련해서는 앞선 포스팅에서 메인 주제로 다룬 적이 있다.

의존관계 주입(DI, Dependency Injection)이란?

IoC 라는 용어는 매우 느슨하게 정의되어 폭넓게 사용되므로 스프링에서는 이를 좀 더 의도가 명확하게 드러나는 용어로 사용하기 위해 의존관계 주입이라는 용어를 사용하기 시작했다.

의존관계란?

  • 두 개의 서로 다른 클래스 또는 모듈이 있을 때 하나의 모듈이 다른 모듈을 사용할 때 가지게 되는 관계를 말한다

  • 특정 모듈의 변화가 다른 모듈에 영향(ex. B를 수정하면, A도 수정되어야 함) 을 미칠 수 있기 때문에 중간에 인터페이스를 두는 등의 방식으로 의존관계를 제한해주는 것이 좋다. (결합도를 낮추어야 한다.)

의존관계 주입

  • 의존관계 주입은 구체적인 의존 오브젝트와 그것을 사용하는 주체(클라이언트 오브젝트)를 런타임 시에 연결해주는 작업을 말한다. 일반적으로 아래 3가지 조건을 충족하는 작업을 말한다.
    (1) 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다.
    (2) 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
    (3) 의존관계는 사용할 오브젝트에 대한 레퍼런스(인터페이스)를 외부에서 제공해줌으로써 만들어진다.
  • 위 3가지 조건을 충족시켜 의존관계를 주입할 경우, 런타임 시에 의존관계가 나타나므로 모듈간 결합도를 낮출 수 있다. (DI의 장점)
  • 스프링에서 의존관계 주입은 DI 컨테이너(=넓게 보면, 애플리케이션 컨텍스트)가 의존관계를 맺어줄 클래스의 객체를 만들고, 생성자의 파라미터로 객체의 레퍼런스를 전달하는 방식으로 수행된다.

의존관계 검색(DL, Dependency Lookup)

  • 스프링이 제공하는 IoC 기법으로 의존관계 주입과 비슷한 의존관계 검색이라는 방법이 있다.
  • 이 방식은 의존관계를 맺을 오브젝트를 결정하는 것과 생성하는 작업은 외부 컨테이너가 맡지만, 생성된 객체를 가져올 때는 메서드나 생성자를 이용하는 것이 아니라, 컨테이너에게 직접 요청하여 가져오는 방식이다.
  • ApplicationContext(AbstractApplicationContext)의 getBean() 메소드를 통해 수행할 수 있다.
/**
 * 의존관계 검색을 이용하는 UserDao 생성자
 */
public UserDao() {
	AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
	this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
}

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

의존관계 검색은 코드에 오브젝트 팩토리 클래스나 스프링 API를 사용해야 되는데, 애플리케이션 컴포넌트가 성격이 다른 오브젝트에 의존하게 되는 것이므로 바람직하지 않다. 반면에 의존관계 주입은 개발자가 비즈니스 로직에 집중할 수 있도록 이러한 코드 없이 훨씬 단순하고 깔끔하게 처리가 가능하므로 특별한 경우를 제외하고는 의존관계 주입을 사용하는 것이 좋다.

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

public class UserDao {
	private ConnectionMaker connectionMaker;
	
	public void setConnectionMaker(ConnectionMaker connectionMaker) {
		this.connectionMaker = connectionMaker;
	}
}

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

	@Bean
	public ConnectionMaker connectionMaker() {
		return new LocalDBConnectionMaker();
	}
}
  • 일반 메소드를 이용해서 주입도 가능하다. 이 경우에는 여러개의 파라미터를 의존관계 주입 동시에 진행 가능하다(생성자와 동일)

DI를 위한 설정 정보 생성 방법

  • XML을 이용하거나, Java 코드로 DI를 위한 설정 정보를 생성할 수 있다.
  • 위에서 언급한바와 같이 @Configuration , @Bean 어노테이션을 활용한다

XML을 이용한 설정

  • 스프링의 애플리케이션 컨텍스트는 XML에 담긴 DI 정보를 활용할 수 있다.
  • DI 정보가 담긴 XML 파일은 를 루트 엘리먼트로 사용한다.
  • 안에는 여러 개의 태그를 정의할 수 있다.
  • 태그의 여러 attribute를 이용해 빈에 대한 세부 설정이 가능하다.

굳이 xml을 사용할 필요는 없으나 필요하다면 구글링을 추천한다..

/**
 * 클래스 설정 방식
 */
@Configuration
public class DataSourceConfiguration {
	@Bean
	public UserDao userDao() {
		UserDao userDao = new UserDao();
		userDao.setDataSource(dataSource());
		return userDao;A
	}

	@Bean
	public DataSource dataSource() {
		return DataSourceBuilder.create()
				.type(BasicDataSource.class)
				.driverClassName("com.mysql.cj.jdcb.Driver")
				.url("jdbc:mysql://localhost:3306/test")
				.username("root")
				.password("root")
				.build();
	}
}

/**
 * XML 설정 방식
 */
<beans xmlns=...>
	<bean id="userDao" class="com.corgi.dao.UserDao">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
		<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/test" />
		<property name="username" value="root" />
		<property name="password" value="root" />
	</bean>
</beans>

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

애플리케이션 컨텍스트가 XML 설정정보를 활용하기 위해서는 GenericXmlApplicationContext 를 사용하면 된다. GenericXmlApplicationContext 의 생성자 파라미터로 XML 파일의 클래스패스를 지정해주면 된다.

  • 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
			https://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

	<bean id="connectionMaker" class="springbook.user.dao.DConnectionMaker" />
	
	<bean id="userDao" class="springbook.user.dao.UserDao">
		<property name="connectionMaker" ref="connectionMaker" />
	</bean>
</beans>
  • 테스트코드
public class Main {
	public static void main(String[] args) {
		// 혹은 ClassPathXmlApplicationContext를 사용해 XML 설정정보를 사용할 수 있다.
		ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
	}
}

자바 코드를 이용한 설정

jdbc 를 이용한 드라이버 설정이라던지 세부내용은 configure 파일을 따로 추가하거나 빈 팩토리 추가 과정에 세팅을 하면 된다.

@Configuration
public class DataSourceConfiguration {
	@Bean
	public DataSource dataSource() {
		SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
		dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
		dataSource.setUrl("jdbc:mysql://localhost/springbook");
		dataSource.setUsername("root");
		dataSource.setPassword("root");

		return dataSource;
	}
}
profile
Mechanical & Computer Science

0개의 댓글