[Spring] 스프링은 객체 컨테이너다.(Feat. 의존성 주입)

Date:     Last Updated:

스프링 컨테이너는 Bean으로 등록해둔 객체의 생성, 초기화, 의존 주입의 관리를 한다.

  • 스프링은 객체를 생성, 초기화, 의존주입 등 관리해주는 것이 핵심이다.
  • 이를 위해 다양한 기능을 제공하는데 BeanFactory, ApplicationContext 등이 있다..

Bean으로 등록한 객체는 별도 설정을 하지 않으면 Singleton 범위를 갖는다.

1
2
3
4
5
  GenericXmlApplicationContext context = new GenericXmlApplicationContext("app-context.xml");
  Greeter gt1 = context.getBean("greeter", Greeter.class);
  Greeter gt2 = context.getBean("greeter", Greeter.class);

  System.out.println(gt1.getClass() == gt2.getClass() ? true : false); //true 출력
  • 당연히 false가 나올 줄 알았는데 스프링 컨테이너가 Bean 객체를 singleton으로 관리하기 때문에 true가 출력된다.

의존(Dependency)이란 무엇인가?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MemberRegisterService {
	
	private MemberDao memberDao = new MemberDao();
	
	public void regist(RegisterRequest request) throws DuplicateMemberException {
		
		Member member = memberDao.findById(request.getId());
		if (member != null) {
			throw new DuplicateMemberException("dup ID " + request.getId());
		}
		
		member = new Member(request.getId(), request.getPassword(), request.getEmail(), request.getName(), LocalDateTime.now());
		memberDao.insert(member);
			
	}
}
  • 위의 코드를 “MemberRegisterService가 MemberDao에 의존한다” 고 한다.
  • 의존이란 변경에 의해 영향을 받는 관계를 의미한다.
  • 예를 들어 MemberDao의 insert메서드가 insertMember로 변경되면 MemberRegisterService의 코드또한 변경된다.
  • 이렇게 변경에 따른 영향이 전파되는 관계를 의존한다고 표현한다.
  • 추가적으로 반대의 경우는 의존 관계가 아니다. MemberRegisterService의 regist메서드 이름이 registMemeber로 변경되어도 MemberDao의 변경은 없기 때문이다.

의존성 주입(Dependency Injection)을 통한 의존 처리

  • 위 코드를 의존성 주입으로 바꾸면 이렇게 된다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    public class MemberRegisterService {
    	
      private MemberDao memberDao = null;
    	
    	public MemberRegisterService(MemberDao memberDao){
      	this.memberDao = memberDao;
    	}
    
      public void regist(RegisterRequest request) throws DuplicateMemberException {
    		
          Member member = memberDao.findById(request.getId());
          if (member != null) {
              throw new DuplicateMemberException("dup ID " + request.getId());
          }
    		
          member = new Member(request.getId(), request.getPassword(), request.getEmail(), request.getName(), LocalDateTime.now());
          memberDao.insert(member);
      }
    }
    
  • 코드가 조금 더 많아지고 복잡해진 것 같지만 이제 약간의 변경의 유연함이 생겼다고 볼 수 있다.

왜 의존성 주입을 해야하는 걸까?

  • 아직도 new로 생성하면 안되나? 라는 의구심이 내 머리속에 있다. 난 객체지향을 조금 더 이해해야 할것 같다.
  • 예시를 들어서 내 머리를 조금 더 설득해보자.

new로 객체를 직접 생성해보자.

1
2
3
4
public class MemberRegisterService {
	private MemberDao memberDao = new MemberDao();
	...
}
  • 위와 같이 new 연산자로 생성을 해보자.
  • 그리고 회원 암호 변경 기능을 제공하는 ChangePasswordService 클래스가 생겼다고 가정해보자.
1
2
3
4
public class ChangePasswordService {
	private MemberDao memberDao = new MemberDao();
	...
}
  • 동일하게 new 연산자로 memberDao를 생성한다.
  • 그리고 MemberDao는 회원 데이터를 데이터베이스에 저장한다고 가정해보자.
  • 이 상태에서 회원 데이터의 빠른 조회를 위해 캐시(Cache)를 적용해야 하는 상황이 발생했다.
  • 그래서 MemberDao를 상속받는 CachedMemberDao 클래스를 만들었다.
1
2
3
public class CachedMemberDao extends MemberDao{
	...
}


  • 이제 캐시 기능을 적용한 CachedMemberDao 클래스를 각 서비스에 적용해보자.
1
2
3
4
5
6
7
8
9
public class MemberRegisterService {
	private MemberDao memberDao = new MemberDao();
	...
}

public class ChangePasswordService {
	private MemberDao memberDao = new MemberDao();
	...
}
  • 이 코드를 아래처럼 변경했다.
1
2
3
4
5
6
7
8
9
public class MemberRegisterService {
	private MemberDao memberDao = new CachedMemberDao();
	...
}

public class ChangePasswordService {
	private MemberDao memberDao = new CachedMemberDao();
	...
}
  • 약간 이해가 된다. 만약 ChangePasswordService에 추가로 ChangeIdService, SearchNameService … 계속해서 필요한 클래스가 생긴다면
  • 생긴 수 만큼 반복해서 코드를 수정해주어야 한다. 지옥이다!

의존성 주입으로 객체를 생성해보자.

  • new로 직접 객체를 생성하는 것보다 의존성 주입이 어떤면에서 좋은 지 알아보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MemberRegisterService {
	private MemberDao memberDao = null;
	
	public MemberDao(MemberDao memberDao){
		this.memberDao = memberDao;
	}
	...
}

public class ChangePasswordService {
	private MemberDao memberDao = null;

	public MemberDao(MemberDao memberDao){
		this.memberDao = memberDao;
	}
	...
}
  • 이렇게 의존성 주입을 통해서 생성을 하게 되면 당연히 소스코드 어딘가에서는 따로 주입을 해주어야한다.
  • 아직은 배우는 단계라 막연히 스프링 컨테이너가 생성해주겠지 라고 예상만 하고 있다.
  • 아무튼 외부에서 MemberDao를 주입해주는 코드를 보자.
1
2
3
private MemberDao memberDao = new MemberDao();
private MemberRegisterService memberRegisterService = new MemberRegisterService(memberDao);
private ChangePasswordService changePasswordService = new ChangePasswordService(memberDao);
  • 이제 new 로 생성했을 때처럼 MemberDao를 CachedMemberDao로 변경해보자.
    1
    2
    3
    
    private MemberDao memberDao = new CachedMemberDao();
    private MemberRegisterService memberRegisterService = new MemberRegisterService(memberDao);
    private ChangePasswordService changePasswordService = new ChangePasswordService(memberDao);
    
  • new로 생성하는 것과 동일하게 만약 ChangePasswordService에 추가로 ChangeIdService, SearchNameService … 계속해서 필요한 클래스가 생긴다고해도
  • 우리가 변경할 부분은 딱 한곳, = new CachedMemberDao(); 이 부분만 변경해주면 10개든 100개든 클래스가 더 생겨도 상관이 없다.
  • 이게 바로 new로 직접생성보다는 의존성 주입을 통해서 생성하는 좋은 부분 중에 하나다. 앞으로 더 나오겠지만 아직 수정해야할 부분은 더 있다.
  • 위에서 말했던 memberDao의 insert메서드가 수정됐을때, 현재의 방법으로는 해결 할 수 가 없었다.
  • 이 방법은 다음 포스팅으로 해보자.

Leave a comment