본문 바로가기
Spring

[스프링] 스프링빈이 없다면?

by chu_dw 2023. 12. 4.

스프링을 계속 공부하던 중 jsp 관련 과제 질문을 받아서

당연하게 스프링을 사용하는 줄 알고 userRepository, BoardRepository 둘다 Service로 가져와서 쓰면 된다는 답을 하였다.

답을 하던중 스프링을 사용하지 않으면 저 둘을 같이 사용하지 못하고 두 테이블을 join해야만 하나? 라는 생각이 들었다.

하지만 명확하게 답을 모르겠어서 정리해보려한다.

이런 기본적 개념조차 명확하게 설명 못하는 상황.. 공부가 시급하다.

 

 


궁금증1  스프링 빈 등록없이 사용 하면?

분명 안될 거 같진 않았는데, 역시 코드로 돌려보니 빈 없이도 수동으로 의존성 주입하여 작동하였다.

    
    private final BoardRepository boardRepository;
    private final UserRepository userRepository;

    public  TestService(BoardRepository boardRepository, UserRepository userRepository){
        this.userRepository = userRepository;
        this.boardRepository = boardRepository;
    }

위와 같이 Service에 의존성을 주입을해 줬고

 


public String getBoard(Long id){
        Board board = boardRepository.findId((id));
        User user = userRepository.findId((id));
        String b_id = board.getId().toString();
        String b_r = board.getRes();
        String u_n = user.getName();

        String reVal = b_id + "/" + b_r + "/" + u_n;
        return reVal;
    }

이런식으로 간단한 예시 메서드를 구현 했다.

 

        
        UserRepository userRepository = new UserRepository();
        BoardRepository boardRepository = new BoardRepository();
        TestService testService = new TestService(boardRepository, userRepository);

        //user 생성
        String uname = "test";
        User user1 = testService.createUser(uname);

        //board 생성
        String res = "testres";
        Board board1 = testService.createBoard(res);

        //board 조회
        Long findId = Long.valueOf(1);
        String val = testService.findBoard(findId);        
        System.out.println(val);

test결과는 잘나왔다

-> spring 없이도 의존성 주입 방식으로 코드 작성 가능하다

-> spring 안쓴다고 서로 다른 테이블 정보 가져올때 꼭 join을 할 필요는 없다. (복잡해지면 join으로 가져오는게 깔끔하다고 한다.)

 

 


궁금중2 그럼 스프링 쓰는이유?

물론 지금 까지 이론으로 스프링 쓰는 이유는 계속 들었지만.. 위 코드를 짜고 보니 그렇게 안힘든데? 그냥 이렇게 쓰면 안되나? 라는 생각이 들었다.  (무식하면 용감하다..)

강의로 공부할때 다 설명 들었던 내용일건데... 다시 정리 해보자

 

강의 예시는 먼저 인터페이스화를 하여 설명한다.

일단 위 코드에 상황을 추가해보겠다. board를 조회하는데 문자열 맨앞에 hello를 붙이는 기능을 추가한다.

그리고 다른 기능으로 변경하고 할 수도 있으니 인터페이스와 구현체를 통해 만들겠다.

 


public interface HeaderSetPolicy {
    String helloSet(String header1);
}

인터페이스를 만들어주고 (반환 값에 인사말 더해주는)

 


public class Header1 implements HeaderSetPolicy {
    @Override
    public String helloSet(String header1) {
        return "hello" + header1;
    }
}

인터페이스를 상속 받아 구현체를 만들었다. (hello + 파라미터 값으로 인사말 추가하는)

 

  
  public class TestService {  	
        private final BoardRepository boardRepository = new BoardRepository();
    	private final UserRepository userRepository = new UserRepository();
    	private final HeaderSetPolicy headerSetPolicy = new Header1();
    
        public String findBoard(Long id){
        Board board = boardRepository.findId((id));
        User user = userRepository.findId((id));
        String b_id = board.getId().toString();
        String b_r = board.getRes();
        String u_n = user.getName();

        String header = headerSetPolicy.helloSet("인사말 입니다.");

        String reVal = header + b_id + "/" + b_r + "/" + u_n;
        return reVal;
 }

그리고 서비스에서 이렇게 사용해 줬다. (board 문자열 앞에 인사말(header) 추가 해주는 기능)

 

그럼 객체지향 관점에서 이 코드의 문제점을 알아보자

-> service가 인터페이스 뿐만아닌 구현체인 Header1에 동시에 의존하고있다. (DIP위반)
-> 새로운 구현체 Header2 로 변경하려면 service의 코드를 수정해야한다. (OCP위반)

 

이 문제를 해결하기 위해

private final HeaderSetPolicy headerSetPolicy = new HeaderSetPolicy();

 위와 같이 인터페이스에만 의존하도록 의존관계를 바꿔주면 당연히 구현체를 못 찾아 에러가난다.

이를 해결하기 위해 스프링의 중요한 개념인 관심사 분리가 나온다.

 

공연을 통해 예시를 들어보면 (강의에서 알려주신)

  - 공연에는 각각 정해진 배역(인터페이스가)이 있고, 그 배역을 수행하는 배우(구현체)가 있다.

  - 각 배우는 상대 배역의 배우가 누구든 똑같이 정해진 공연을 수행한다.

  - 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는것은 공연 기획자가 한다.

 

현재 문제가 있는 위 코드는 

배역인 인터페이스(HeaderSetPolict) 를 연기하는 배우인 구현체가 바뀌게 되면서 전체 공연인 service가 변경되는 상황인 것이다.

이는 배역을 지정할 공연 기획자가 없어서이다.

그럼 공연 기획자 역할인 AppConfig를 만들어 보겠다.

 

***

이제 보니 위에 맨처음 코드는 appConfig의 역할을 테스트 코드에서 해주고 있다 (기능 변경시 클라이언트 변경 최악 방법인 듯)

지금 코드에선 서비스 내에서 하나씩 의존성을 주입하고

맨 처음 코드는 service에서 아래 설명할 appConfig 개념을 살짝 가져와 생성자로 의존성을 주입해준 것이다.

***

 


public class AppConfig {

    public TestService testService(){
        return new TestService(new BoardRepository(),new UserRepository(), new Header1());
    }
}

AppCofig를 이렇게 만들어주고

 


public TestService(BoardRepository boardRepository, UserRepository userRepository, HeaderSetPolicy headerSetPolicy){
        this.userRepository = userRepository;
        this.boardRepository = boardRepository;
        this.headerSetPolicy = headerSetPolicy;
    }

서비스에는 생성자로 의존성을 주입 해준다.

 

        
        AppConfig appConfig = new AppConfig();
        TestService testService = appConfig.testService();
        
        Long findId = Long.valueOf(1);
        String val = testService.findBoard(findId);
        System.out.println(val);

테스트는 다음과 같이 해준다.

 

이렇게 해놓고 다시 객체지향 관점에서 보자.

-> Servicee는 인터페이스인 HeaderSetPolicy에만 의존하고 있다.

-> HeaderSetPolicy 구현체를 바꿔줄 때 공연 기획자 즉 AppConfig만 바꿔주면 된다.

Service는 어떤 구현체가 들어올지 신경 안쓰고 실행 역할에만 집중하게 된다.

 

 


결론

appConfig를 사용했지만 di를 활용하여 객체지향적으로 만들어 졌을 뿐 여전히 순수 자바 코드이다.

이를 스프링으로 변환하여 사용하는데 글이 길어져 다음 글에 계속 하겠다.

이 글은 스프링은 전혀 나오지 않은 di개념 정리 정도가 되어버린것 같다..ㅎ

'Spring' 카테고리의 다른 글

[spring] 스프링 login  (0) 2024.02.21
[스프링] 스프링빈 사용이유  (0) 2024.01.13
[프로젝트] dto, mapper 개념  (0) 2023.10.27
[스프링] Security  (0) 2023.08.05
[스프링] 트랜잭션  (0) 2023.08.04