본문 바로가기

Java

Spring이 service 인터페이스와 serviceImpl 를 연결하는 법


- spring에서 @Autowired 쓰기- 

나는 학원에서 조금 다르게 배웠다.
MVC 패턴을 구현할 때 구조가 조금 달랐다.
controller > dao > database 구조로 배웠다.
지금 새로 공부하면서 구조가 조금 다르다는 것을 알게 됐다.
이런 식으로 더 많이 쓰는 듯했다.
controller > service > dao > database
그전에는 비지니스 로직을 컨트롤러에서 주로 처리했다.
아마 프로그래밍을 처음 배우는 단계이다 보니 그렇게 복잡한 기능을 구현하지는 않았기 때문이라고 생각한다.
하지만 규모가 커지면 비지니스 로직을 주로 다루는 단계가 따로 필요해진다.
controller는 클라이언트와 서버가 데이터를 주고 받는 활동을 주로 다룬다.
dao는 데이터베이스와 왔다 갔다하는 역할을 담당한다.
그 중간에서 비지니스 로직은 주로 service에서 처리한다.

이렇게 중간에 한 단계가 추가되면서 이해가 가지 않는 점이 생겼다.
controller에서는 service를 직접 부르지 않았다.
controller에서는 NoticeService, CartService 등 어떤 service의 interface를 선언만 한다
그리고 그 밑에서는 해당 service의 메소드를 바로 호출해다가 쓴다.
어떻게 interface를 선언만 했는데 객체도 넣어주지 않고서 바로 메소드를 사용할 수 있을까?
예를 들면 아래와 같은 식이다.

 

public class FruitController {

@Autowired
FruitService fruitService;

    @RequestMapping("list")
    public String listPage() {

        List<FruitModel> = fruitService.getList();
        model.addAttribute("fruitList", fruitList);

        return "fruit/list";
    }
}


여기서 FruitService 는 interface다.
대신 FruitServiceImpl 라는 파일이 따로 있다.
호출하는 메소드는 이 파일에서 불러다 쓰고 있었다.

 

 

- @Autowied 와 DI 기능 - 

아마 객체를 생성하지 않고 넣어주는 기능은 Spring Framework가 대신 해주고 있을 것이다.
그 기능을 불어오는 신호가 @Autowired일 것이다.
Spring은 DI라는 기능을 제공한다.
DI란 Dependency Injection의 줄임말이다.
직역하면 의존성 주입 정도 된다.
객체를 코드 내부에서 직접 부르지 않아도 된다.
Spring에서 객체를 가지고 있다가,
필요할 때 알아서 넣어 준다.
이렇게 하면 좋은 점은 효율성이다.
A라는 객체가 필요한데,
여기서도 A를 객체로 만들어서 쓰고,
저기서도 A를 객체로 만들고,
이게 여러 번 반복될수록 불필요한 과정이 생기므로 자원 활용이 비효율적이게 된다.
이러한 문제를 해결해 주는 것이 Spring 의 DI 기능이다.


- interface의 DI -

내가 의문인 점은 autowired로 부르고 있는 대상이 interface라는 점이다.
interface는 객체로 만들 수 없다.
interface는 선언만 하고, 객체를 넣어줄 때는 클래스를 대신 넣어준다고 가정해도 의문점이 있었다.
어떤 클래스를 넣어줄지 지정해 준 적이 없기 때문이다.
Animal 이라는 interface에 @Autowired를 걸어 놓고,
Dog와 Cat 구현체 중 어떤 클래스를 객체로 넣어줄지 어떻게 판단한다는 말인가?

어딘가에 xml 파일에서 지정해주고 있을 거라고 짐작했다.
그래서 설정 태그가 가득한 xml 파일들을 여러 곳 찾아봤다.
pom.xml
web.xml
applicationContext.xml
root-context.xml
servlet-context.xml
어디에도 이렇다 할 기능이 보이지 않았다.

예전에 학원에서 배울 때는 xml에서 아떤 Bean에 어떤 클래스를 연결할지 하나하나 설정해 주었다.
이번에도 그럴 것 같았다.
그도 그럴 것이, service interface를 상속받은 구현체가 serviceA와 serviceB 등 여러 개가 있으면
어느 쪽을 넣을지 어떻게 판단할  수 있겠는가?
어딘가에서 설정을 할 것이다.
도대체 어떤 클래스를 객체로 넣어줄지 어떻게 결정한다는 말인가?
interface에 @Autowired만 걸어놓고 나몰라라 하는데
어떻게 그 밑에서는 바로 구현체의 메소드를 가져다 쓸 수 있을까?

 


- interface 자동 -

다른 팀원에게 물었다.
'그냥 스프링에서 자동으로 해주는 거 아니에요?'
그 팀원은 학원에서 처음 때 이렇게 하도록 배웠고,,
그래서 그런 걸 생각해 본 적이 없다고 했다.
'인터페이스에 @Autowired를 걸면 
스프링에서 그 인터페이스를 상속 받은 클래스를 알아서 찾아서
자동으로 그 클래스를 객체로 만들어 넣어 주는 거 아니에요?'

controller에서는 
FruitService fs에 @Autowired를 걸면
FruitService를 상속받은 FruitServiceImpl를 
Spring이 알아서 찾아다가 fs에 넣어준다는 것이다.

정말로 그런 걸까?
Spring Legacy MVC project를 새로 만들어서 실험해 봤다.
대충 패키지를 만들고 service에
인터페이스로 service 파일과 그 인터페이스를 상속받아 내부를 구현한 serviceImpl 파일을 만들었다.
controller에서는 인터페이스인 service 를 선언하고, @Autowired를 달아 주었다.
밑에서는 구현체의 기능을 바로 호출해 보았다.


@Controller
public class DogController {

    @Autowired
    Dog dog;

    @RequestMapping(value = "/dog", method = RequestMethod.GET)
    public String dog() {
        dog.bark("bark");

        return "dog";
    }

}

 

public interface Dog {

	abstract void bark(String s);

}

 

@Service
public class DogImplA implements Dog {

    public void bark(String con) {
    	System.out.println(con + " : bow-wow!!");
    }
}

 

@Service
public class DogImplB implements Dog{

    public void bark(String con) {
    	System.out.println(con + " : woof!!");
    }
}



 - 결론 -

팀원 말이 맞았다.
다른 xml을 만지지 않았음에도 정상적으로 작동했다.
Spring에서 해당 interface를 상속 받은 구현체를 찾아다가 객체로 만들어서 넣어주는 것이었다.
그렇다면 한 인터페이스를 상속받은 구현체가 serviceA와 serviceB 등 여러 개가 있다면 어느 쪽을 넣어줄지 어떻게 판단할 수 있는가?
같은 인터페이스를 상속 받은 구현체가 여러 개 있더라도
@Service 를 달아 놓은 클래스는 하나뿐이라면 그 녀석만 불러온다.
같은 인터페이스를 여러 구현체가 상속 받은 상태에서
@Service 가 여러 클래스에 달려 있으면 애초에 서버가 올라가지 않고 에러가 난다.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'homeController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: kr.test.interf3.Dog kr.test.interf3.HomeController.dog; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [kr.test.interf3.Dog] is defined: expected single matching bean but found 2: [dogImplA, dogImplB]

 

- 배운점 -

아마 평소 같았으면 구글링을 했을 거다.
실제로 이 문제로 여러 번 구글링을 했다.
검색어를 바꿔 가면서 찾아 봐도 잘 나오지 않았다.
내가 물어 봤던 팀원은 구글링을 웬만하면 하지 않는다.
그는 가설을 먼저 세우고 직접 확인할 방법을 찾는다.
과학적인 접근이다.
과학 분야들에서도 먼저 가설을 세우고 그 가설을 증명할 수 있도록 실험을 설계한다.
그는 이런 작업과 센스가 잘 발달해 있었다.
웬만한 것들은 스스로 생각해 보고 실험을 통해 자신의 생각이 맞는지 확인했다.
그 속도도 빠르고 능숙했다.

반면에 나는 그동안 구글링을 많이 해와서인지, 스스로 찾는 과정이 미숙했다.
아니, 애초에 스스로 생각해 보고 실험해 봐야겠다는 생각조차 들지 않았다.
강하게 말하자면, 내 선택지에 없었다.
이번에 Sprinf Framework에서 interface에 @Autowired를 걸면 어떻게 작동하는지 스스로 실험해 보니까 느낀 점이 있다.
구글링보다 스스로 알아보는 것도 아주 빠르고 나름대로 정확하다는 것이다.
나아가서 혼자 이렇게 저렇게 직접 시행착오를 겪다 보면 기억에도 더 잘 남을 것이다.
앞으로 이런 태도를 배우고, 습과으로서 더 크게 키워가야겠다는 생각이 들었다.

반응형