본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
Spring basic

스프링 입문 : 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 자바 코드로 직접 스프링 빈 등록하기

by 방구석 대학생 2021. 11. 8.

"인프런의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 듣고 작성한 글 입니다."

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세

www.inflearn.com

 

자바 코드로 직접 스프링 빈을 등록하여 의존관계를 설정해 줄 수 있다.

@Service, @Repository, @Autowired 와 같은 컴포넌트 스캔을 통한 자동 의존관계 설정 기능을 사용하지 않고

자바 코드로 직접 스프링 빈을 등록하여 의존관계를 설정해 줄 수 있다.

- 컴포넌트 스캔을 통해 자동으로 스프링에게 각종 클래스에 대한 인식을 시켜줄 수도 있지만, 직접 설정파일에 등록을 해줄 수도 있다.

 

일단 MemberService 로 가서 @Service, @Autowired 어노테이션을 지워서 컴포넌트 스캔과 관련된 기능을 제거해주고 MemoryMemberRepository 에서도 마찬가지로 @Repository 어노테이션을 지워주자.

(MemberController 는 내버려둔다.)

 

- MemberService.java

// @Service 어노테이션 삭제
public class MemberService {

    private final MemberRepository memberRepository;
	// @Autowired 어노테이션 삭제
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    
    // 이하 코드 동일
}

- MemoryMemberRepository.java

// @Repository 어노테이션 삭제
public class MemoryMemberRepository implements MemberRepository{
	// 이하 코드 동일
}

이 상태로 어플리케이션을 실행시켜 보면 이전에 작성했던 글과 같이 스프링 컨테이너에 컨트롤러와 연결해줄 MemberService 가 없다는 오류 로그가 출력된다.

 

이제부터 컴포넌트 스캔을 활용하지 않고 자바 코드를 통해 직접 스프링 컨테이너에 필요한 스프링 빈을 등록시켜 보자.

HelloSpringApplication 클래스가 있는 패키지에 SpringConfig 라는 클래스 파일을 하나 만들어주고 아래와 같이 코드를 작성한다.

 

- SpringConfig.java

@Configuration
public class SpringConfig {

    @Bean // 스프링 빈으로 등록하라는 의미의 어노테이션
    public MemberService memberService(){
        return new MemberService(memberRepository()); // 스프링 빈에 등록할 객체 생성
    }

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository(); // 스프링 빈에 등록할 객체 생성
        // 인터페이스의 경우 new 를 통한 객체 생성이 불가능 하기 때문에
        // 해당 인터페이스의 구현체 클래스를 기준으로 객체를 만들어준다.
    }
}

 위와 같이 코드를 작성해주면 스프링이 동작할 때 @Configuration 어노테이션을 읽고 난 이후 코드를 보면서 @Bean 어노테이션이 붙어있는 메소드가 확인되면 메소드에 작성되어 있는대로 객체를 생성해준 다음 스프링 컨테이너에 해당 객체를 스프링 빈으로서 등록해준다.

 

좀 더 자세히 설명하자면, 코드의 실행 순서에 따라 MemberService 객체를 생성할 때 파라미터 값으로 들어있는 MemoryMemberRepository 객체 생성 메소드를 우선적으로 실행하여 MemoryMemberRepository 객체를 생성한 다음, @Bean 어노테이션이 부착되어 있는것을 확인하고 해당 객체를 스프링 빈에 등록해준다.

그 이후 다시 memberService 메소드로 돌아와서 MemberService 클래스 파일에 작성되어 있는 객체 생성자 로직에 따라 스프링 빈에 등록되어 있는 MemoryMemberRepository 객체를 의존성 주입을 통해 연결해주면서 최종적으로 MemberService 객체를 생성하여 스프링 빈에 등록해준다.

(컨트롤러는 어쩔수 없다. 어쨌든 스프링이 어차피 관리를 해줘야 하기 때문에 @Controller, @Autowired 어노테이션을 사용하는 컴포넌트 스캔 및 자동 의존관계 설정 방식으로 내버려둔다. - 따로 뭔가 설정을 해줄 수 있는게 아님)

 

다시 어플리케이션을 동작시켜 보면 오류 없이 정상적으로 실행되는 것을 확인해볼 수 있다.

 

* 과거에는 자바 코드가 아닌 XML 문서를 통해서 설정을 해줬었다. 하지만 요즘은 XML 이 아닌 자바 코드로 설정을 많이 해주는 편이다.

* 그렇기에 XML 방식으로 스프링 빈을 등록하는 방법은 생략한다.

 

* 참고 : DI 에는 필드 주입, Setter 주입, 생성자 주입, 이렇게 3가지 방법이 존재한다.(현재까지 강의에서 한 것은 생성자 주입방식이다.)

* 필드 주입의 경우 생성자 코드를 제외하고 final 키워드를 지워준 다음, 주입해주고자 하는 필드의 선언부에 @Autowired 어노테이션을 붙여주는 방식이다.(pro.gg 프로젝트에서 활용했던 방식)

- 예시

public class TempController{
	
    // DI 필드 주입방식
    @Autowired
    private TempService tempService;
    
    // 생성자 없음
}

필드 주입은 처음 객체를 생성해 줄 때를 제외하고 중간에 객체를 바꿔줄 수 있는 방법이 존재하지 않아서 좋지 않다고 한다.

(오히려 변경 시킬수 없는 편이 좋은거 아닌가....? 그리고 final 키워드가 없으면 오히려 중간에 바꿔주기 좋을텐데...?)

 

* Setter 주입은 final 키워드를 지워준 후 생성자 대신 setter 메소드를 작성한 다음 해당 메소드에 @Autowired 어노테이션을 붙여주는 방식이다.

- 예시

public class TempController{

    private TempService tempService;
    
 	// setter 메소드를 통한 의존성 주입
    @Autowired
    public void setTempService(TempService tempservice){
    	this.tempService = tempService;
    }
}

이 방식의 단점은 누군가가 위의 TempController 를 호출했을 때 해당 setter 메소드가 public 키워드를 통해 자유롭게 접근할 수 있도록 열려 있어야 한다는 것이다.

한번 주입된 TempService 객체는 이후로는 굳이 바꿔줄 필요가 없는데도 불구하고 public 키워드로 인해 외부에서의 접근에 노출 되게된다. (필드 주입의 경우엔 그래도 private 키워드 덕분에 외부에서 접근할 수 없다.)

 

스프링 빈에 등록된 객체가 중간에 변경되는 것도 어플리케이션을 동작 시킬 때 스프링이 처음 작동하면서 환경이 세팅되는 동안 변경되는 것이지, 한번 세팅이 되고 나면 굳이 스프링 빈에 등록된 객체를 바꿔줄 이유가 없다.

(필드 주입에서 중간에 변경시킬 수 없어서 안 좋다고 한 건 이 경우를 말하는 듯 하다.)

 

그래서 요즘 권장하는 스타일은 강의에서 지금까지 했던 바와 같이 생성자를 통해 의존성을 주입하는 것이다.

생성자 주입 방식을 통해 클래스 간 의존관계를 설정해주면, 처음 어플리케이션이 동작하면서 환경이 조립되는 시점에 생성자를 한번 참조하고 실행하는 것으로 스프링 빈 등록이 완료된다.

setter 주입의 경우 처럼 다른 누군가가 setter 메소드를 통해 해당 객체에 접근하여 내용을 변경하게 되는 위험성이 사라진다는 것이다.

 

결론적으로는 DI 설정을 할 때에는 생성자 주입 방식을 이용해주는 것이 좋다.

- 의존관계가 동적으로 변하는 경우(어플리케이션 동작 도중에 객체가 변경되는 경우)는 거의 없으므로 생성자 주입을 권장한다.

 

실무에서는 주로 정형화된 컨트롤러, 서비스, 레포지토리 같은 코드의 경우 컴포넌트 스캔을 사용한다, 그리고 정형화 되지 않거나 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.

 

컴포넌트 스캔 및 자동 의존관계 설정 방식과 자바 코드를 통해 직접 스프링 빈을 등록해주는 방식

이 둘을 비교하면 코드 작성 자체에 대해서는 당연히 컴포넌트 스캔 방식이 편하기야 하지만 둘 다 각각 장단점이 있다.

 

* 앞으로 강의를 진행하면서 무엇을 할 것이냐?

상황에 따라 구현 클래스를 변경해야 한다는 대목에 주목해보자.

현재 강의에서는 레포지토리를 사용하는데 있어 아직 데이터베이스가 결정되지 않았다는 가상의 시나리오가 있기 때문에, 추후에 데이터베이스가 결정되면 그에 맞춰서 레포지토리 구조를 변경해줘야 한다.

 

그렇기 때문에 현재는 레포지토리를 사용하는데 있어서 인터페이스로 먼저 설계를 해준 다음 MemoryMemberRepository 와 같은 구현체를 만들어서 사용해주고 있는 것이다.

나중에 데이터베이스가 결정되고 나면 현재 구현체로 사용하고 있는 MemoryMemberRepository 를 실제 데이터베이스에 연결하는 다른 Repository 로 변경할 것이다.

그런데 레포지토리를 변경할 때 기존에 사용중이던 코드를 하나도 손대지 않고 바꿔줄 수 있는 방법이 있다.(그걸 위해 현재 강의가 중요함)

 

- SpringConfig 클래스 파일에서 memberRepository 메소드를 호출할 때 객체를 생성할 클래스의 이름만 바꿔주면 된다.

예시 : new MemoryMemberRepository(); -> DbMemberRepository() // 이것 외에 아무것도 건드릴 필요 없음

(이에 대해서는 계속 강의를 들으면서 해당 내용이 나오는대로 정리해보도록 하자.)

 

이것이 바로 컴포넌트 스캔이 아닌 설정 파일을 통해 자바 코드로 직접 스프링 빈을 등록해 줄 때의 장점이다.

컴포넌트 스캔을 활용할 경우 이번 강의와 같이 나중에 Repository 를 변경시켜 줘야 할 일이 있을 때 다른 코드들도 바꿔줘야 할 것이 많은데 반해, 직접 자바 코드를 통해 객체를 스프링 빈에 등록해 줄 경우 후에 Repository 와 같은 것을 변경해줘야 할 때 객체를 생성해주는 메소드에서 생성해줄 객체 클래스의 이름만 변경해주면 간단하게 해결할 수 있다.

(설정 파일만 손대면 된다는 것

 

* 주의할 점

@Autowired 어노테이션을 통한 DI 는 helloController, MemberService 등과 같이 스프링이 관리하는 객체에서만 동작한다.

스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.

- 컴포넌트 스캔을 했든, 자바 코드를 통해 직접 설정 파일을 만들었든, 스프링 컨테이너에 스프링 빈으로서 등록되어 있어야만 @Autowired 어노테이션이 제대로 동작하여 의존성 주입을 해줄 수 있게된다.

(강의를 열심히 들었다면 당연한 소리 아닌가....?)

- 마찬가지로 new 키워드를 통해 개발자가 직접 따로 객체를 생성해준 경우에도 @Autowired 어노테이션은 동작하지 않는다.

(MemberService memberService = new MemberService(); 와 같이 객체를 생성해준 경우 @Autowired 어노테이션 적용 불가 - 이렇게 생성된 객체는 스프링 컨테이너에 적재되는 것이 아니라 메모리 상에 객체가 만들어지기 때문)