인프런/스프링 입문

6)스프링 빈과 의존관계, 스프링 빈 등록방법 1. 컴포넌트 스캔 2. 자바로 직접 등록

backend dev 2022. 11. 11.

스프링 빈을 등록하고, 의존관계 설정하기

회원 컨트롤러가 회원서비스와 회원 리포지토리를 사용할 수 있게 의존관계를 준비하자.

멤버(회원)컨트롤러가 멤버 서비스를 통해서 회원가입을 하고 , 데이터를 조회할 수 있어야한다.(이런것을 서로 의존관계가 있다고 한다.멤버 컨트롤러가 멤버서비스를 의존한다고 표현한다.)

 

 

MemberController.java 생성

@Controller //이렇게 컨트롤러 어노테이션을 적어놓으면 스프링컨테이너가 생성되면서 멤버컨트롤러로서 해당 어노테이션을 가진 컨트롤러를 넣어놓는다.
//그리고 스프링이 관리를 한다.
public class MemberController {


}

@Controller

이렇게 컨트롤러 어노테이션을 적어놓으면 스프링컨테이너가 생성되면서 멤버컨트롤러로서 해당 어노테이션을 가진 컨트롤러를 넣어놓는다.
그리고 스프링이 관리를 한다. 

[@controller 어노테이션이 있는 컨트롤러는 스프링이 실행될때 스프링이 들고있는다. -> 이런걸 스프링컨테이너에서 스프링Bean이 관리된다고 말한다.]

그래서 예전에 helloController를 만들고 동작방식을 설명할때 스프링컨테이너안에 helloController가 있는걸 확인할 수 있다.

 

이제 MemberController에서 MemberService를 생성해서 그안에 있는 메소드들을 사용해야하는데 

private final MemberService memberService = new MemberService();

이런식으로 생성할 수 도 있지만 

스프링이 관리를 하게되면 다 스프링컨테이너에 등록을 하고 스프링컨테이너에서 받아서 쓰도록 바꿔야한다.

 

그래서 스프링컨테이너에 등록하고 쓰게끔 수정해보자

@Controller //이렇게 컨트롤러 어노테이션을 적어놓으면 스프링컨테이너가 생성되면서 멤버컨트롤러로서 해당 어노테이션을 가진 컨트롤러를 넣어놓는다.
//그리고 스프링이 관리를 한다.
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }



}

이런식으로 수정했다.

MemberController는 스프링컨테이너가 생성될때 같이 생성되서 컨테이너에 저장된다. 

그때 생성되면서 방금 작성한 생성자가 호출이 될텐데

@Autowired
public MemberController(MemberService memberService) {
    this.memberService = memberService;
}

@Autowired가 붙어있다면 MemberController가 생성될때 전달인자인 memberSerivce는 스프링컨테이너에 있는 memberService로 전달해서 생성해준다.

여기서 이전 테스트는  MemberService를 테스트할때 

멤버서비스에 넣어줘야하는 멤버리포지토리를 생성해서 넣어준 이부분을 이야기하는거 같다.

[스프링컨테이너가 생성되면서 생성된 객체를 넣어준게 아니라 직접 생성해서 넣어줬으므로]

 

다시 돌아가서

현재 이상태로 Run을 해보면 다음과 같은 오류가 발생한다.

 

오류가 발생하는 이유는 다음과 같다!

멤버컨트롤러는 스프링이 뜰때 스프링컨테이너 생성되면서 같이 생성되서 컨테이너 안에 존재하지만

멤버서비스는 그렇지않다. 그래서 멤버컨트롤러를 생성하려고할때   생성자에 Autowired가 붙어있으므로 생성에 필요한 매개변수(멤버서비스)를 스프링컨테이너에서 찾으려고 했지만 멤버서비스가 컨테이너에 등록이 되어있지않아서 해당 오류가 발생하는것이다. [요약 : 멤버서비스가 스프링빈으로 등록되지않아서 스프링컨테이너에 존재하지않았고 @Autowired 어노테이션때문에 멤버서비스를 컨테이너에서 찾아보려했지만 없으니까 오류발생 ]

스프링컨테이너에 등록된것을 스프링빈이라고 한다.  이 그림에서는 멤버컨트롤러가 스프링빈 

 

 

멤버서비스가 스프링빈으로 등록되지않았던것은

@Serivce 어노테이션이 없었기 때문 , 이 어노테이션이 없다면 멤버서비스는 그냥 자바파일일 뿐이다.

이런식으로 붙여주면 된다.

이렇게 붙여줬더니 아래 생성자 매개변수에서 불이 들어왔다.

그 이유는 @Service가 붙었기때문이고

이제 스프링이 실행되면서,스프링컨테이너가 생성되면서 멤버서비스를 컨테이너에 담기위해 해당 생성자를 실행할텐데 그때 전달인자로 멤버리포지토리의 구현체가 전달될게 없기때문이다. (생성된게 없으니) 

이 오류를 해결하려면 멤버리포지토리 구현체로 가서 , 어노테이션을 붙여서 스프링빈으로 등록시켜야  스프링컨테이너에 존재할것이고 , 그리고 해당 생성자에 @Authwired를 붙여 멤버리포지토리 구현체를 스프링컨테이너 안에서 찾게끔하면 될것이다.  (@Autowired를 붙이지않아도 빨간불이 사라지는것을 확인할수 있는데 , 생성자가 1개일경우는 @Autowired를 생략할 수 있기때문이다!-> 하지만 붙이는걸 습관화 하자)

@Autowired

이렇게 생성자에 사용하며, 스프링 컨테이너에 있는 스프링빈을 이용하여 객체를 생성해준다.

[Autowired를 이용하여 멤버컨트롤러 -> 멤버서비스 -> 멤버리포지토리 를 연결해준다. 연결해주는 역할을 @Autowired가 하는것이다. 의존성주입을 함으로서 연결]

여기서는 멤버컨트롤러를 만들때 스프링 컨테이너에 있는 멤버서비스를 전달(주입)해서 멤버컨트롤러를 생성해준다.

이런게 Dependency injection,  의존성 주입이다.

인터페이스가 아닌 해당 인터페이스를 구현한 구현체에다가 해당 어노테이션을 붙여준다.

이렇게 @Controller,@Service,@Repository라는 어노테이션을 가지고 스프링빈으로 등록가능

되게 정형화된 패턴인데 , 컨트롤러에서 외부요청을받고 , 서비스에서 비즈니스로직을 만들고, 리포지토리에서 데이터를 저장하고와 같은 패턴 

 

 

스프링빈을 등록하는 2가지 방법
1. 컴포넌트 스캔

컴포넌트 스캔 -> 스프링이 실행되면서 컴포넌트와 관련된 어노테이션이 있으면 전부 객체 생성해서 스프링컨테이너에 등록시킨다. 

원래는 @Component와 같이 어노테이션을 작성해서 스프링빈을 등록한다.

그래서 @Service말고 이렇게 등록해도 잘 빌드가 되는것같다.

하지만 @Service를 대신넣어도 되는이유는 Service의 내부를 살펴보면 알 수있다.

@Service안에 @Component 어노테이션이 등록이 되어있기때문이다.

@controller나 @repository와 같은것도 마찬가지이다.

 

스프링빈 등록 이미지

현재 상황

현재 스프링컨테이너에 멤버서비스와 멤버리포지토리가 스프링빈으로 등록된 상태이다.

스프링빈은 하나만 등록가능하므로 같은 스프링빈이면 모두 같은 인스턴스라고 한다.(싱글톤패턴)

예시로 만약에 OrderSevice를 스프링빈으로 등록했고 Autowired를 통해 멤버리포지토리를 생성자의 매개변수로 사용한다면 다음과 같은 상황이 될것이다.

그 이유는 멤버리포지토리는 스프링컨테이너에 딱하나뿐이기때문이다(싱글톤)

 

 

전체상황을 보면

멤버컨트롤러를 생성하려면 멤버서비스가 필요하고, 멤버서비스를 생성하려면 멤버리포지토리가 필요하다.

저 화살표는 @Autowired 때문에 생기는것이고 ( 그 뜻은 곧 의존성 주입때문에 생기는것이다라고 볼 수 있다.)

화살표를 해석해보자면

스프링컨테이너에 있는 스프링빈인

멤버 컨트롤러는 생성되면서 생성자의 @Autowired때문에 스프링컨테이너에 있는 멤버서비스가 필요하다. 그래서 화살표로 저렇게 가리키고 있는것이고

멤버서비스는 생성되면서 생성자의 @Autowired때문에 스프링컨테이너에있는 멤버리포지토리가 필요한것이다 그래서 화살표로 저렇게 가리키고 있는것이다.

 

컴포넌트 스캔을 이용하여 스프링빈으로 등록하기 위해 각 파일을 수정한 결과

그렇다면 아무 파일을 만들어서
@Service와 같이 어노테이션을 붙여 스프링빈으로 등록해도되나요?

이렇게 demo 패키지를 java밑에 생성해서 demo 자바파일을 만들고 @Service 어노테이션을 사용해보았다.

결과 -> 이렇게 해도 스프링빈으로 등록이 되지않는다.

이유 ->

컴포넌트 스캔(컴포넌트관련 어노테이션을 찾아 스프링빈으로 등록해주는 일련의 과정)

을 할때는 SpringApplication이 존재하는 패키지내부에서 진행하기 때문이다

HelloSpringApplication은 현재 hello.hellospring 패키지내부에 있으므로

hello.hellospring 내부에서 컴포넌트 스캔을 진행한다. ( 해당 패키지를 포함한 하위 패키지들을 체크)

그러므로 그 밖에있는 패키지인 demo안에 파일들은 컴포넌트 스캔을 진행하지않음 -> 빈으로 등록되지않는다.(어떤 설정을 해주면 되긴하는데 , 기본적으로는 등록되지 않는다)

 

 

스프링빈을 등록하는 2가지 방법
2. 자바코드로 직접 스프링 빈 등록하기

일단 위에서 

어노테이션을 이용해서 스프링빈을 등록했으니까

어노테이션들을 지워준다. ( @Service, @Repository, @Autowired ....)

@Controller는 일단 냅둠.

Controller 빼고 어노테이션 지웠으니 오류가 발생한다.

 

이 상황에서

직접 등록해보자

 

 

SpringConfig

SpringConfig라는 파일을 생성해준다.

 

@Configuration
public class SpringConfig {
    
    @Bean //@Bean 어노테이션 -> 스프링빈을 등록할거다 라는 의미
    public MemberService memberService() {
        return new MemberService();
    }


}

@Configuration -> 설정파일을 만들거나 Bean을 등록하기 위한 어노테이션이다

@Bean -> 스프링빈을 등록하기 위한 어노테이션이다.

 

@Bean 어노테이션을 달고 다음과 같이 생성한 멤버서비스를 리턴해주는 메서드로 구성해준다.

이때 멤버서비스는 매개변수로 리포지토리를 받으니 리포지토리 또한 빈으로 등록해준다. 

 

이렇게 수정해준다.

 

@Bean //@Bean 어노테이션 -> 스프링빈을 등록할거다 라는 의미
public MemberService memberService() {
    return new MemberService(memberRepository());
}

@Bean
public MemberRepository memberRepository() {
    return new MemoryMemberRepository();
}

동작방식

1. 멤버서비스와 멤버리포지토리를 스프링빈에 등록시켜준다.

2. 멤버서비스를 생성해서 빈으로 등록시켜줄때 스프링빈에 있는 리포지토리를 이용한다.

 

우리가 위에서 계속 하던거는 생성자 주입이다, 배운대로 하면된다!

 

정형화 되지않거나 , 상황에 따라 구현 클래스를 변경해야한다면 -> 설정파일 만들어서 스프링빈으로 등록하는게 나중에 수정이 더쉽다.

나중에 멤버리포지토리의 구현체를 다른거로 바꾸고 싶을때  다른 코드를 전혀 손댈필요없이

@Bean
public MemberRepository memberRepository() {
    return new dbMemberRepository();
}

이런식으로 new 뒷부분만 바꿔주면 된다.  ( 컴포넌트 스캔을 사용하면 여러코드를 바꿔야한다.) 

리포지토리를 바꿔줄 예정이라서 자바코드로 스프링빈을 설정한다.

 

 

멤버서비스가 @Service를 사용하거나 설정파일에서 빈으로 등록되어있지않은 상태에서 

@Autowired를 이용해서 의존성주입을 하려면 동작하지않는다는 말이다.

 

멤버서비스가 스프링에 등록되고 스프링이 관리를 해야 @Autowired로 적용할 수 있다는 것이다.

그리고 내가 직접 멤버서비스를 생성해준다고 해도 @Autowired가 동작하지않는다. 

@Autowired는 스프링이 올라갈때 알아서 실행됨 

 

 

 

 

 

 

 

댓글