Hyunseok
프로그래밍/개인홈페이지 [Spring] @Autowired vs @RequiredArgsConstructor 뭘 쓸까 feat .IOC, DI, Autowiring
2022. 11. 26. 15:22

이번에 개인 블로그를 만들면서도 그렇고 .. 다른 팀프로젝트 할 때도 그렇고 .. 

 

둘 중에 뭘 써야 더 좋을까 라는 생각이다..

 

또한 이번 프로젝트에서도 자꾸 습관적으로 @Autowired를 쓰는 나 자신을 보고 있었다..

 

여하튼 결론부터 말하자면 4.3 (지금은 6버전이다)버전부터 

 

@RequiredArgsConstructor 인 생성자를 주입하여 쓰는 것을 권장한다 

(권장이면.. 아마 그래도 써도 괜찮지 않을까라는 생각을 하고.. 난 실제로도 무의식적으로 가끔 쓴다..)

 

 

시작하기 전에 돌아가는 원리는 스프링의 IOC컨테이너를 생각해야 한다

 

IOC 컨테이너, 흔히 말하는 제어가 역전된 컨테이너 조금 더 풀어서 쓴 글들을 보면

컨테이너가 알아서 가비지콜렉팅, 호출 등을 대신 관리해주는 것이라고 설명해놓았다

 

더 간단하게 하면..

 

그냥 내가 적절한 붕어빵 금형을 만들어두고 로직을 짜두면

 

컨테이너가 알아서 붕어빵을 만들어내고 팔고 팥이던 슈크림이던 알아서 해결해서 장사한다..라고 생각하면 될 것 같다

 

 

고로 IOC컨테이너는 우리가 개발에 최대한 몰두할 수 있게끔 만들어주니.. 편하다고 생각할 수가 있다..

 

그렇다면 같이 단골로 나오는 DI도 같이 짚고 넘어가자

 

간단하게 의존성 주입이라 말하고 조금 풀어쓰면 A, B라는 클래스가 있고 A안에 B를 불러온 상태라 생각하자 

 

public class A{

    public B b  = new B();

}

 

자연스레 A를 불러오면 B라는 b를 새로 만들어 가져오게 된다

 

그럼 무슨 상황이 발생하는가??

 

B를 바꾼다 > A도 무조건 영향을 받는다 라는 결과를 당연하게 알 수 있다 

 

여기서 코드를 조금만 더 꼬아보자 

 

public class A{

    public B b = b;

 

    public A(B b){

        this.b = b

    }

}

 

A라는 클래스에 B라는 금형의 b를 선언만 해두고

실제 b의 알맹이는 A를 가져오는 외부에서 b를 주입받아 b를 가지게 된다 

 

그럼 여기서 최종적 가장 큰 주체는 누구인가?

 

A? B 아니다 b를 주입받는 A 위에 있는 부모 클래스가 될 것이다 

 

결국 A가 들어간 부모의 클래스에서 정의가 된

b가 A에 영향을 끼치는 것이지

b자체가  바뀐다 해서 A의 내용이 확 바뀌는 것은 아니다

 

결국 둘 다 A가 영향을 받는 건 같은 내용이다

 

하지만 시점의 차이이다

A상위에서 미리 만들어진 b 에 영향을 받느냐

b를 직접 가져온 A의 b 가 b의 바뀜에 따라 바로 바뀌느냐

 

별거 아닐 수도 있으나 이게 가끔 문제가 되기도 하니.. 잘 생각하면서 짜야한다 

 

 

결론을 말하자면

IOC = 컨트롤의 주체의 내용

DI = 클래스 간의 영향관계에 앞서 어떤 식으로 정의될지의 내용

 

나는 이 정도로 생각한다

 

잡담은 집어치우고 본론으로 돌아와서 그래서 왜 @RequiredArgsConstructor가 추천되는가?

 

IOC관점에서 보는가 DI관점에서 보는가가 여기서 갈린다 

 

 

두 코드의 예를 한번 들어보자 

 

1. 생성자 주입

@RestController
//@RequiredArgsConstructor
public class BlogController {
    private final BlogService bser;

    @RequestMapping(value = "/menulist", method = RequestMethod.GET)
    public ResponseEntity<List<MenuDTO>> menuList(){
        return new ResponseEntity<>(bser.getMenuList(), HttpStatus.OK);
    }
    // @Autowired
    public BlogController (BlogService bser){
    this.bser = bser;
}

2. 필드 주입

@RestController
public class BlogController {
    @Autowired
    private BlogService bser;

    @RequestMapping(value = "/menulist", method = RequestMethod.GET)
    public ResponseEntity<List<MenuDTO>> menuList(){
        return new ResponseEntity<>(bser.getMenuList(), HttpStatus.OK);
    }
}

 

 

설명하기 전에 @Autoweired의 역할을 짚고 넘어가야 한다

이 어노테이션을 이해하기 전에 스택오버플로우에서는 제안하는 오토와이어링에대해서 알아야 한다고 한다

https://stackoverflow.com/questions/3153546/how-does-autowiring-work-in-spring

 

How does autowiring work in Spring?

I'm a little confused as to how the inversion of control (IoC) works in Spring. Say I have a service class called UserServiceImpl that implements UserService interface. How would this be @Autowir...

stackoverflow.com

답변은 아래와 두 가지의 개념을 강조합니다

1. 모든 Bean은 스프링에서 관리되며 "ApplicationContext"라고 불리는 컨테이너에서 "존재합니다"

2. 각각의 어플리케이션은 그 애플리케이션마다의 컨텍스트에 엔트리포인트가 있습니다

웹어플리케이션은 servlet과 el-resolver를 사용하는 JSF(Jakarta server face, (구 자바서버페이스)) 등을 가지며

또한 어플리케이션이 자동으로 Bean을 "오토와이어" 하는 공간이 있습니다. 이는 웹어플리케이션의 시작 리스너가 될 수 있습니다

 

Autowiring은 앞서 선언된 Bean을 다른 Bean의 인스턴스에 배치할 때 일어납니다 

두 인스턴스는 다 Bean이어야 합니다 컨텍스트 내에 정의가 되어있어야 한다는 것이고

(이 말은 즉슨 우리가 제어하는 것이 아닌 어플리케이션 컨텍스트가 관리한다는 뜻이 되기도 합니다)

 

고로 종합해보면 앞서 어플리케이션 컨텍스트안에서 "존재한다"라는 의미는 코더가 생성하는 것이 아닌

어플리케이션 컨텍스트에서의 인스턴스화를 한다는 의미가 되는 것이며

어플리케이션컨텍스트가 각 주입 지점을 찾아 인스턴스를 주입합니다

 

위의 코드에서 설명을 하자면

 

BlogService를 만드는 것이 아닌 

컨테이너(application context)에서의 주입점을 찾아 Bean으로 인스턴스화 한다는 것을 알 수 있습니다 

(BlogService는  @Service로 Bean등록이 되어있습니다)

 

이것이 Autowiring의 정의인 듯하고 

 

이 글을 추천해준 스택오버플로우를 읽어보자

 

https://stackoverflow.com/questions/21276136/spring-mvc-how-does-autowired-work

 

Spring MVC - How does @Autowired work?

I am working on a web application using Java and Spring. I'm new at Spring, so as an opportunity to learn, I was basically given an existing application and was told to extend it. I am having trouble

stackoverflow.com

(스프링 부트가 아니라 스프링이지만  우리가@Bean으로 Bean을 생성하듯 XML로 (<bean id="postDaoPublicImpl>) Bean을 지정한다는 것을 알 수 있다)

 

그리고 앞서 설명된 Autowiring개념과 같이 만들어진 Bean이 필요 지점에서 만들어지는 것이 아닌 만들어진 Bean을

인스턴스화 된다는 것을 알 수 있다

 

이것이 Autowired의 역할이라는 것이다 

 

결국 정리하면 어플리케이션 컨텍스트에 등록된 Bean을

자동으로 주입 지점을 찾아 인스턴트화를 해준다라고 생각하면 될 것 같다 

 

그렇다면 @Autowired를 왜 쓰지 말라는 것일까

 

생성자주입을 쓰는 이유는..

 

먼저 생성자 코드를 보면 알 수 있듯 private final로 선언되어 생성자를 주입받아 클래스 안에서 사용하게 된다

이 뜻은 초기화는 딱 한 번만 가능, 더 쉽게 풀면 딱 한번 선언되면 변하지 않는 메서드가 된다는 것이다

이로써 호오오옥시나 중간에 변경될 위험을 방지할 수 있다

추가로 final로 되었다..? 초기화가 되지 않으면 무조건 NPE를 뿜어내니 이 또한 방지할 수 있는 좋은 결과를 낼 수 있다

 

두 번째로는 POJO라는 관점에서 다들 설명을 한다 

간단하게 확장/상속 등을 사용하지 않은 독립적으로 인스턴스화가 가능한 그런 개념이다

간단히 테스트에 용이하다고만 보았고 나중에 따로 공부하면 좋을 듯하다 

 

세 번째로는 순환 참조를 효과적으로 막을 수 있다는 점이다

물론 생성자 주입을 통해서도 순환 참조는 일어난다.. 매우 잘 일어난다..

그래도 어디서 어떻게 일어나는지 생성자 주입을 이용하면 잘 알려주니 매우 좋다

 

또 여러가지 기타 내용들도 있는데.. 이제는 코드가 치고 싶기도 하고..

예전부터 궁금한 내용들이 좀 풀린 기분이라 글은 여기서 마치려 한다 

 

틀린 점이 있다면 부디 댓글로 알려주시길 바랍니다 흑흑 

 

 

 

 

 


프로그래밍/개인홈페이지의 다른 글