🌿 Spring

Spring DI(의존주입), Container(객체 조립)

연_우리 2022. 3. 28. 16:49
반응형

목차

     

     

     

    의존이란?

    public class MemberRegisterService {
    
        //객체 직접 생성
        private MemberDao memberDao = new MemberDao();
    
        public void register(String email){
            //이메일 중복 체크
            Member member = memberDao.selectByEmail(email);
            ...
            
            //회원가입 처리
            ...
        }
    }

    MemberRegisterService 클래스는 register() 메서드 내부에서 MemberDao의 selectByEmail() 메서드를 사용한다.

     

    MemberDao의 selectByEmail() 메서드 이름을 findByEmail()로 변경한다면? -> MemberRegisterService의 소스코드도 변경해야한다.

     

    변경에 따른 영향이 전파되는 것을 의존한다고 한다. 

    = MemberRegisterService는 MemberDao에 의존한다.

     

     

     

     

    의존 대상 구하기

    1. 의존 대상 객체를 직접 생성하기

    (위 예시와 동일하다) 

    public class MemberRegisterService {
    
        //객체 직접 생성 ⭐⭐
        private MemberDao memberDao = new MemberDao();
    
        public void register(String email){
            //이메일 중복 체크
            Member member = memberDao.selectByEmail(email);
            ...
            
            //회원가입 처리
            ...
        }
    }

     

    >> 문제되는 상황

    빠른 조회를 위해 MemberDao를 상속받은 CachedMemberDao를 만들었다고하자.

    CachedMemberDao를 사용하려면 MemberRegisterService, ChangePasswordService 둘다 코드를 수정해야한다.

     

    만약 MemberDao가 사용되는 곳이 100개라면..? 100개의 코드를 열어서 모두 수정해주어야한다..

    public class MemberRegisterService {
        private MemberDao memberDao = new MemberDao();  //-> new CachedMemberDao();
    }
    
    public class ChangePasswordService {
        private MemberDao memberDao = new MemberDao();  //-> new CachedMemberDao();
    }

     

     

     

    2. Dependency Injection : 의존 주입을 통해 객체를 전달받기

    public class MemberRegisterService {
    
        //객체 전달받기 ⭐⭐
        private MemberDao memberDaoDI;
        public MemberRegisterService(MemberDao memberDaoDI){
            this.memberDaoDI = memberDaoDI;
        }
    
        public void register(String email){
            //이메일 중복 체크
            Member member = memberDao.selectByEmail(email);
            ...
            
            //회원가입 처리
            ...
        }
    }

     

    생성자를 통해 MemberRegisterService가 의존(Dependency)하는 MemberDao객체를 주입(Injection) 받는 형태이다.

    위 형태를 사용하려면 MemberRegisterService와 MemberDao를 생성해서 조립해주는 곳이 있어야한다.

     

    일단 어딘가에서 두 객체를 생성해서 조립한다고 생각하고, 생성될때의 코드를 생각해보자.

    MemberDao memberDao = new MemberDao();  // -> new CachedMemberDao();
    MemberRegisterService reg = new MemberRegisterService(memberDao);
    ChangePasswordService pwd = new ChangePasswordService(memberDao);

    new MemberDao()를 new CachedMemberDao()로만 바꾸어주면 해결된다.

    MemberDao가 사용되는 곳이 100개라면..? 1000개여도 상관없다. MemberDao를 생성하는 딱 1곳만 바꾸어주면 된다.

     

    1곳만 코드를 수정하면 다른 모든 곳에 변경된 객체를 사용할 수 있게된다.

    변경이 유연해졌다!

     

     

     

     

    DI 방식

    생성자 방식

    생성자의 매개변수로 객체를 주입받는 방식이다.

    public class MemberRegisterService {
    
        private MemberDao memberDaoDI;
        public MemberRegisterService(MemberDao memberDaoDI){
            this.memberDaoDI = memberDaoDI;
        }
    
    }

     

    Setter 방식

    setter 메서드의 매개변수로 객체를 주입받는 방식이다.

    public class ChangePasswordService {
        private MemberDao memberDao;
    
        public void setMemberDao(MemberDao memberDao) {
            this.memberDao = memberDao;
        }
    
        public void changePassword(String email, String oldpwd, String newpwd){
            //비밀번호 변경 로직
        }
    }

     

     

     

     

    객체 조립하기

    MemberRegisterService와 MemberDao를 생성하고, 조립하는 객체 조립기를 만들어보자.

     

    자바 객체 조립기 Assembler

    @Getter
    public class Assembler {
        private MemberDao memberDao;
        private MemberRegisterService memberRegisterService;
        private ChangePasswordService changePasswordService;
    
        public Assembler() {
            memberDao = new MemberDao();    // new CachedMemberDao();
            memberRegisterService = new MemberRegisterService(memberDao);   //생성자 주입
            changePasswordService = new ChangePasswordService();            //setter 주입
            changePasswordService.setMemberDao(memberDao);
        }
    }
    public class MainForAssembler {
    
        private static Assembler assembler = new Assembler();  //⭐
    
        public static void main(String[] args) throws IOException {
            processNewCommand();
            processChangeCommand();
        }
    
        private static void processNewCommand() {
            MemberRegisterService regSvc = assembler.getMemberRegisterService();        
            //⭐ assembler가 생성되면서 조립된 MemberRegisterService를 가져온다
            
            RegisterDTO req = new RegisterDTO();
            req.setEmail("test@test.com");
            req.setName("testname");
            req.setPassword("testpwd");
            req.setConfirmPassword("testpwd");
           
            regSvc.regist(req);
            System.out.println("등록했습니다.");
        }
    
        private static void processChangeCommand() {
            ChangePasswordService changePwdSvc = assembler.getChangePasswordService();  
            //⭐ assembler가 생성되면서 조립된 ChangePasswordService를 가져온다
            
            changePwdSvc.changePassword("test@test.com", "testpwd", "updatepwd");
            System.out.println("암호를 변경했습니다.");
        }
    
    }

    Assembler클래스가 생성되며

    MemberRegisterService의 생성자에 memberDao를 주입하고, 

    ChangePasswordService의 Setter에 memberDao를 주입해준다.

     

    main메서드에서는 Assembler에서 조립완료된 MemberRegisterService, ChangePasswordService를 가져와서 사용하기만 하면 된다.

     

     

     

     

    스프링 객체 조립기 Spring Container

    이전 게시글(https://lotuus.tistory.com/115)에서 스프링 컨테이너는 Bean 객체들의 생성, 관리 등을 맡고있다는 것을 알게되었다.

    그럼 굳이 자바로 Assembler를 만들지 않고, 컨테이너에 빈으로 등록해서 조립해도 되지않을까? 

    맞다. 실제로 스프링 컨테이너는 Assembler 코드처럼 필요한 객체를 생성하고, 생성한 객체에 의존을 주입하는 DI 기능을 지원한다.

     

    이전 게시글에서 @Configuration, @Bean을 사용하여 AppContext 설정 클래스를 생성했었고,

    ApplicationContext를 이용하여 스프링 컨테이너를 생성했다.

     

    이번에도 동일하게 설정클래스를 생성해보자

     

    @Configuration
    public class AppCtx {
        @Bean
        public MemberDao memberDao(){
            return new MemberDao();
        }
    
        @Bean
        public MemberRegisterService memberRegisterService(){
            return new MemberRegisterService(memberDao());
        }
    
        @Bean
        public ChangePasswordService changePasswordService(){
            ChangePasswordService changePasswordService = new ChangePasswordService();
            changePasswordService.setMemberDao(memberDao());
            return changePasswordService;
        }
    }
    public class MainForSpring {
    
        private static ApplicationContext ctx = null; //⭐
    
        public static void main(String[] args) throws IOException {
            ctx = new AnnotationConfigApplicationContext(AppCtx.class);  //⭐
            
            processNewCommand();
            processChangeCommand();
        }
    
        private static void processNewCommand() {
            MemberRegisterService regSvc = ctx.getBean("memberRegisterService", MemberRegisterService.class);        
            //⭐ 스프링 컨테이너가 생성되면서 조립한 MemberRegisterService를 가져온다
            
            RegisterDTO req = new RegisterDTO();
            req.setEmail("test@test.com");
            req.setName("testname");
            req.setPassword("testpwd");
            req.setConfirmPassword("testpwd");
    
            regSvc.regist(req);
            System.out.println("등록했습니다.");
        }
    
        private static void processChangeCommand() {
            ChangePasswordService changePwdSvc = ctx.getBean("changePasswordService", ChangePasswordService.class);  
            //⭐ 스프링 컨테이너가 생성되면서 조립한 ChangePasswordService를 가져온다
            
            changePwdSvc.changePassword("test@test.com", "testpwd", "updatepwd");
            System.out.println("암호를 변경했습니다.");
        }
    }

    AppCtx 설정 클래스를 바탕으로 한 스프링 컨테이너를 생성하고

    getBean메서드를 통해 컨테이너에서 조립되고 등록된 객체들을 가져와서 사용하기만 하면 된다.

     

     

     

    스프링 컨테이너의 Bean은 싱글톤

    엇? 위의 코드에서 둘다 memberDao()를 호출하면 return new MemberDao() 되니까 매번 새로운 객체가 생성되는건가?

    결론은 매번 같은 객체가 반환되게된다. 

     

    스프링 컨테이너는 @Bean이 붙은 메서드에 대해 한개의 객체만 생성한다.

    memberDao()를 여러번 호출해도 항상 같은 객체를 리턴한다는 것이다.

     

    스프링은 설정 클래스(AppCtx)를 그대로 사용하지 않는다.

    설정클래스를 상속한 새로운 설정클래스를 만들어서 사용한다.

    새로 만든 설정 클래스에서 한번 생성한 객체를 보관했다가 이후에는 동일한 객체를 리턴하는 것이다.

     

     

     

     

    두 개 이상의 설정 파일 사용하기

    @Configuration
    public class AppCtx1 {
        @Bean
        public MemberDao memberDao(){
            return new MemberDao();
        }
    }
    @Configuration
    public class AppCtx2 {
        @Autowired  //스프링 설정클래스에 작성하면 해당 타입의 빈을 찾아서 필드에 DI한다. AppCtx1에서 @Bean으로 정의된 MemberDao를 가져오게된다.
        private MemberDao memberDao;
    
        @Bean
        public MemberRegisterService memberRegisterService(){
            return new MemberRegisterService(memberDao);
        }
    
        @Bean
        public ChangePasswordService changePasswordService(){
            ChangePasswordService changePasswordService = new ChangePasswordService();
            changePasswordService.setMemberDao(memberDao);
            return changePasswordService;
        }
        
    }

    @Autowired로 AppCtx1에서 Bean으로 등록된 MemberDao를 가져온다.

     

    public class MainForSpringMultiAppCtx {
    
        private static ApplicationContext ctx = null;  //⭐
    
        public static void main(String[] args) throws IOException {
            ctx = new AnnotationConfigApplicationContext(AppCtx1.class, AppCtx2.class); //⭐ 설정파일 2개!!!
    
            processNewCommand();
            processChangeCommand();
        }
    
        private static void processNewCommand() {
            MemberRegisterService regSvc = ctx.getBean("memberRegisterService", MemberRegisterService.class);        
            //⭐ 스프링 컨테이너가 생성되면서 조립한 MemberRegisterService를 가져온다
            
            RegisterDTO req = new RegisterDTO();
            req.setEmail("test@test.com");
            req.setName("testname");
            req.setPassword("testpwd");
            req.setConfirmPassword("testpwd");
    
            regSvc.regist(req);
            System.out.println("등록했습니다.");
        }
    
        private static void processChangeCommand() {
            ChangePasswordService changePwdSvc = ctx.getBean("changePasswordService", ChangePasswordService.class);  
            //⭐ 스프링 컨테이너가 생성되면서 조립한 ChangePasswordService를 가져온다
            
            changePwdSvc.changePassword("test@test.com", "testpwd", "updatepwd");
            System.out.println("암호를 변경했습니다.");
        }
    }

     

     

     

     

     

     

    반응형
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기