[LG CNS AM CAMP 1기] 백엔드 II 1 | Spring

letthem·2025년 1월 20일
0

LG CNS AM CAMP 1기

목록 보기
17/31
post-thumbnail

MySQL Community & Workbench 설치

Spring Initializr

com.000.000 밑에 폴더나 클래스 생성

예제 ⬇️

Greeter.java : 콘솔에 메시지를 출력하는 자바 클래스
AppContext.java : 스프링 설정 파일
TestApplication.java(main) : 스프링과 Greeter를 실행하는 자바 클래스

Greeter.java

package com.test.test1;

public class Greeter {
    private String format;

    public String greet(String name) {
        return String.format(format, name);
    }

    public void setFormat(String format) {
        this.format = format;
    }
}

AppContext.java

package com.test.test1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppContext {
    
    @Bean
    public Greeter greeter() {
        Greeter g = new Greeter();
        g.setFormat("%s, 안녕하세요.");
        return g;
    }
}

@Configuration

해당 클래스는 spring 설정 클래스로 동작한다.

@Bean

해당 메서드가 반환하는 값은 Bean 객체로 등록된다.

TestApplication.java

package com.test.test1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

//@SpringBootApplication
public class Test1Application {

	public static void main(String[] args) {
//		SpringApplication.run(Test1Application.class, args);
		{
			Greeter greeter = new Greeter();
			greeter.setFormat("Hello, %s!!!");
			String message = greeter.greet("Spring");
			System.out.println(message);
		}
		{
			AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppContext.class);

			Greeter greeter = ctx.getBean("greeter", Greeter.class);
			greeter.setFormat("안녕, %s!!!");
			String message = greeter.greet("스프링");
			System.out.println(message);
            
			ctx.close();
		}
	}
}

의존성 주입(DI. Dependency Injection)

위 예제에서 다음을 비교해보자.

Greeter greeter = new Greeter()

VS

Greeter greeter = ctx.getBean("greeter", Greeter.class);
  • new 해서 사용하지 않고 getBean으로 가져와서 사용한다. ("greeter"라는 이름의 객체를 가져와서 사용한다.)
  • 직접 선언하지 않고 외부로부터 가져와서 사용할 수 있도록 해준다.
    => 의존성 주입 개념

제어의 역전(IoC)

  • 객체의 생성과 소멸 등 제어 권한을 클래스가 하는 것이 아니라 스프링에게 위임해서 스프링 컨테이너가 대신 수해준다.
  • 위 예제에서 다음을 보자.
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppContext.class);
  • 객체를 만들고 소멸시키는 등 일련의 작업들을 스프링 컨테이너가 대신 수행해준다.

  • BeanFactory : 객체를 필요로 하는 시점에 객체를 생성
  • ApplicationContext : 객체의 컨테이너가 시작될 때 모든 객체를 싱글톤 패턴의 빈으로 만들어서 초기화해서 제공. 대부분은 ApplicationContext로 구현한다.
    • 싱글톤 : 인스턴스가 하나만 존재하는 것
  • AnnotationConfigApplicationContext : java 기반(@ : annotation)의 설정 클래스 (java 기반이 아닌 것 : xml)

빈(bean)

빈(bean) 객체는 싱글톤(singleton) 범위를 가진다.

  • 싱글톤(singleton) : 인스턴스가 하나만 생성되는 것을 보장하는 객체

=> 스프링은 기본적으로 한 개의 @Bean 어노테이션에 대해 한 개의 빈 객체를 생성

싱글톤 원리 코드 ⬇️

class Singleton {
	private static Single instance;
    
	private Singleton() { // 외부에서 생성자를 호출할 수 없도록 private 지정
    }
    
    // 인스턴스를 만들지 않고 호출할 수 있도록 static 지정
    public static Singleton getInstance() {
    	if (instance == null)
        	instance = new Singleton(); // null이면 생성하고
        
    	return instance; // 반환
    }
}

// 외부에서 호출
Singleton.getInstance();
  • private 으로 해서 외부에서 Singleton을 호출하지 못하게 만든다.
  • getInstance() 에서는 Singleton 타입의 객체를 반환
  • instance 없이 호출할 수 있도록 static 으로 !

싱글톤 예제

package com.test.test1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

//@SpringBootApplication
public class Test1Application {

	public static void main(String[] args) {
//		SpringApplication.run(Test1Application.class, args);
		Greeter g1 = new Greeter(); // new 하면 힙 메모리에 새로운 변수가 만들어지므로
		Greeter g2 = new Greeter();

		System.out.println("g1 : " + g1);
		System.out.println("g2 : " + g2);
		System.out.println("g1 == g2 : " + (g1 == g2)); // false

		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppContext.class);

		// 주소가 동일하다. => 싱글톤이다 !!
		Greeter g3 = ctx.getBean("greeter", Greeter.class);
		Greeter g4 = ctx.getBean("greeter", Greeter.class);

		System.out.println("g3 : " + g3);
		System.out.println("g4 : " + g4);
		System.out.println("g3 == g4 : " + (g3 == g4)); // true

		ctx.close();
	}
}

의존(dependency)

한 클래스가 다른 클래스의 메서드를 사용(실행)할 때 의존한다라고 표현 => 변경에 의해 영향을 받는 관계

public class MemberDAO() {
	// 이메일 정보가 일치하는 사용자 정보를 조회해서 반환
    public Member selectByEmail(String email) {
    	return null;
    }
    // 회원 정보를 저장
    public void insert(Member member) {
    	// TODO
    }
}

// 서비스에서 각 메서드 사용
public class MemberRegisterService {
	private MemberDAO dao = new MemberDAO(); 
    
    public void regist(Member member) throws Exception {
    	Member m = dao.selectByEmail(member.getEmail());
        if (m != null) {
        	throw new Exception("이메일이 중복되었습니다.");
        }
        dao.insert(member);
    }
}

MemberRegisterService는 MemberDAO의 selectByEmail 메서드와 insert 메서드를 사용하고 있다.
=> MemberDAO에 의존한다.
=> MemberDAO의 selectByEmail 메서드 또는 insert 메서드를 변경하면 MemberRegisterService도 변경이 필요하다.
의존 관계가 많으면 많을 수록 하나의 변경에 따른 추가 변경이 많아져 복잡해진다.

의존 객체를 구하는 방법

방법 1. 의존 대상 객체를 직접 생성 (new)

public class MemberRegisterService {
	private MemberDAO dao = new MemberDAO(); 
}

=> 구현은 용이하나, 유지보수에 문제가 있다. 🥵

방법 2. DI(Dependency Injection, 의존 주입)

의존하는 객체를 직접 생성하는 대신, 의존 객체를 전달받는 방식

public class MemberRegisterService {
	private MemberDAO dao;
    
    // 바깥쪽에서 전달받아서 쓸 수 있다.
    public MemberRegisterService(MemberDAO dao) {
    	this.dao = dao;
    }
}

=> 의존 객체를 주입해 주는 곳만 수정하면 변경을 반영할 수 있다.

class Main {
	public static void main(String[] args) {
    	MemberDAO dao1 = new MemberDAO();
        MemberRegisterService mrs1 = new MemberRegisterService(dao1);
        
        MemberDAO dao2 = new NewMemberDAO();
        MemberRegisterService mrs2 = new MemberRegisterService(dao2);
    }
}

class NewMemberDAO extends MemberDAO {
..
}

이렇게 의존성을 떨어트리니까(외부에서 생성해서 넣어주니까) 나는 수정하지 않아도 된다.
=> DI(의존성 주입)가 필요한 이유

DI 예제 구현

회원 데이터를 관리하는 클래스

  • Member(dto 객체)
  • WrongIdPasswordException
  • MemberDAO(회원관리 DB 연동 역할만!)

회원 가입을 처리하는 클래스

  • DuplicateMemberException(동일한 email 이 존재하는가)
  • RegisterRequest(사용자가 전달해주는 입력값. request 객체)
  • MemberRegisterService(등록 담당)

패스워드 변경을 처리하는 클래스

  • MemberNotFoundException(멤버인지?)
  • ChangePasswordService(패스워드 변경)

Member : 회원 정보(데이터)를 표현하는 객체 ⬇️

package com.test.test1;

import java.time.LocalDateTime;

public class Member {
    private Long id;
    private String email;
    private String password;
    private String name;
    private LocalDateTime registerDateTime;

    public Member(String email, String password, String name, LocalDateTime registerDateTime) {
        this.email = email;
        this.password = password;
        this.name = name;
        this.registerDateTime = registerDateTime;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public String getPassword() {
        return password;
    }

    public String getName() {
        return name;
    }

    public LocalDateTime getRegisterDateTime() {
        return registerDateTime;
    }

    public void changePassword(String oldPassword, String newPassword) {
        if (!this.password.equals(oldPassword)) {
            throw new WrongIdPasswordException();
        }
        this.password = newPassword;
    }
}

WrongIdPasswordException ⬇️

package com.test.test1;

public class WrongIdPasswordException extends RuntimeException{
}

MemberDAO(Data Access Object) : 회원관리 DB 연동 역할

데이터를 읽고, 쓰고, 수정하고, 삭제하는 기능 포함

package com.test.test1;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class MemberDAO {
    private static long nextId = 0;

    // 키: 이메일, 값: Member
    private Map<String, Member> map = new HashMap<>();
    
    // 조회
    public Member selectByEmail(String email) {
        return map.get(email);
    }
    
    // 등록
    public void insert(Member member) {
        member.setId(++nextId);
        map.put(member.getEmail(), member);
    }
    
    // 수정
    public void update(Member member) {
        map.put(member.getEmail(), member);
    }
    
    // 전체 조회
    public Collection<Member> selectAll() {
        return map.values();
    }
}

RegisterRequest.java : 사용자가 입력한 회원 가입에 필요한 정보를 담고 있는 객체

package com.test.test1;

public class RegisterRequest {
    private String email;
    private String password;
    private String confirmPassword;
    private String name;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getConfirmPassword() {
        return confirmPassword;
    }

    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isPasswordEqualToConfirmPassword() {
        return this.password.equals(this.confirmPassword);
    }
}

isPasswordEqualToConfirmPassword() : 비밀번호 입력값과 비밀번호 확인 입력값이 같은지 !!

MemberRegisterService : MemberDAO를 이용해서 구현하려고 하는 서비스(회원가입)를 구현

package com.test.test1;

import java.time.LocalDateTime;

public class MemberRegisterService {
    private MemberDAO memberDAO; // 의존 객체

    // 생성자를 이용해서 의존성 주입
    public MemberRegisterService(MemberDAO memberDAO) {
        this.memberDAO = memberDAO;
    }

    public long regist(RegisterRequest req) {
        Member member = memberDAO.selectByEmail(req.getEmail());
        if (member != null) {
            throw new DuplicateMemberException("이메일 중복 " + req.getEmail());
        }

        Member newMember = new Member(req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now());
        memberDAO.insert(newMember);
        return newMember.getId();
    }
}

DuplicateMemberException.java

package com.test.test1;

public class DuplicateMemberException extends RuntimeException {
    public DuplicateMemberException(String message) {
        super(message); // 상위 클래스에 메시지만 전달
    }
}

ChangePasswordService.java

MemberDAO로 의존성 주입 받기

package com.test.test1;

public class ChangePasswordService {
    private MemberDAO memberDAO;

    public ChangePasswordService(MemberDAO memberDAO) {
        this.memberDAO = memberDAO;
    }

    public void changePassword(String email, String currentPw, String newPw) {
        Member member = memberDAO.selectByEmail(email);
        if (member == null) {
            throw new RuntimeException("등록된 회원이 없습니다.");
        }
        
        member.changePassword(currentPw, newPw);
        memberDAO.update(member);
    }
}

객체를 생성하고 의존 객체를 주입하는 기능을 제공하는 클래스를 정의 => assembler.Assebler.java

package com.test.test1.assembler;

import com.test.test1.ChangePasswordService;
import com.test.test1.MemberDAO;
import com.test.test1.MemberRegisterService;

public class Assembler {
    private MemberDAO memberDAO;
    private MemberRegisterService regSvc;
    private ChangePasswordService pwdSvc;
    
    // 필요한 객체들의 인스턴스를 만들고 넣어준다.
    public Assembler() {
        this.memberDAO = new MemberDAO();
        this.regSvc = new MemberRegisterService(this.memberDAO);
        this.pwdSvc = new ChangePasswordService(this.memberDAO);
    }
    
    public MemberDAO getMemberDAO() {
        return this.memberDAO;
    }
    
    public MemberRegisterService getRegSvc() {
        return this.regSvc;
    }
    
    public ChangePasswordService getPwdSvc() {
        return this.pwdSvc;
    }
}

main.MainForAssembler.java

직접 new 해서 만드는 방법 ⬇️

package com.test.test1.main;

import com.test.test1.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class MainForAssembler {

    // new : 새로운 회원 데이터를 추가
    // change : 회원의 패스워드를 변경
    // exit : 프로그램을 종료
    // new      : 새로운 회원 데이터를 추가
    // change   : 회원의 패스워드를 변경
    // exit     : 프로그램을 종료
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            System.out.println("명령어를 입력하세요. ");
            String command = reader.readLine();
            if (command.startsWith("exit")) {
                System.out.println("종료합니다.");
                break;
            }
            if (command.startsWith("new")) {
                processNewCommand(command.split(" "));
                continue;
            }
            if (command.startsWith("change")) {
                processChangeCommand(command.split(" "));
                continue;
            }
            printHelp();
        }
    }
    private static MemberDAO memberDAO = new MemberDAO();
    private static MemberRegisterService regSvc = new MemberRegisterService(memberDAO);
    private static ChangePasswordService pwdSvc = new ChangePasswordService(memberDAO);

    private static void printHelp() {
        System.out.println();
        System.out.println("잘못된 명령입니다. 아래 명령어 사용법을 확인하세요.");
        System.out.println("new email name password confirmPassword");
        System.out.println("change email currentPassword newPassword");
        System.out.println();
    }

    private static void processNewCommand(String[] args) {
        if (args.length != 5) {
            printHelp();
            return;
        }

        // MemberRegisterService regSvc = new MemberRegisterService(new MemberDAO());
        RegisterRequest reg = new RegisterRequest();
        reg.setEmail(args[1]);
        reg.setName(args[2]);
        reg.setPassword(args[3]);
        reg.setConfirmPassword(args[4]);

        if (!reg.isPasswordEqualToConfirmPassword()) {
            System.out.println("패스워드와 패스워드 확인이 일치하지 않습니다.");
            return;
        }

        try {
            regSvc.regist(reg);
            System.out.println("등록되었습니다.");
        } catch(DuplicateMemberException e) {
            System.out.println("이미 존재하는 이메일입니다.");
        }
    }

    private static void processChangeCommand(String[] args) {
        if (args.length != 4) {
            printHelp();
            return;
        }

        // ChangePasswordService pwdSvc = new ChangePasswordService(new MemberDAO());

        try {
            pwdSvc.changePassword(args[1], args[2], args[3]);
            System.out.println("패스워드를 변경하였습니다.");
        } catch(RuntimeException e) {
            System.out.println(e.getMessage());
        }
    }
}

assembler의 get 메서드를 사용해서 필요하는 곳에서 인스턴스를 받아와 사용 ⬇️
assembler로 생성

package com.test.test1.main;

import com.test.test1.*;
import com.test.test1.assembler.Assembler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class MainForAssembler {

    // new : 새로운 회원 데이터를 추가
    // change : 회원의 패스워드를 변경
    // exit : 프로그램을 종료
    // new      : 새로운 회원 데이터를 추가
    // change   : 회원의 패스워드를 변경
    // exit     : 프로그램을 종료
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            System.out.println("명령어를 입력하세요. ");
            String command = reader.readLine();
            if (command.startsWith("exit")) {
                System.out.println("종료합니다.");
                break;
            }
            if (command.startsWith("new")) {
                processNewCommand(command.split(" "));
                continue;
            }
            if (command.startsWith("change")) {
                processChangeCommand(command.split(" "));
                continue;
            }
            printHelp();
        }
    }

    /*
    private static MemberDAO memberDAO = new MemberDAO();
    private static MemberRegisterService regSvc = new MemberRegisterService(memberDAO);
    private static ChangePasswordService pwdSvc = new ChangePasswordService(memberDAO);
    */
    private static Assembler assembler = new Assembler();


    private static void printHelp() {
        System.out.println();
        System.out.println("잘못된 명령입니다. 아래 명령어 사용법을 확인하세요.");
        System.out.println("new email name password confirmPassword");
        System.out.println("change email currentPassword newPassword");
        System.out.println();
    }

    private static void processNewCommand(String[] args) {
        if (args.length != 5) {
            printHelp();
            return;
        }

        MemberRegisterService regSvc = assembler.getRegSvc();
        RegisterRequest reg = new RegisterRequest();
        reg.setEmail(args[1]);
        reg.setName(args[2]);
        reg.setPassword(args[3]);
        reg.setConfirmPassword(args[4]);

        if (!reg.isPasswordEqualToConfirmPassword()) {
            System.out.println("패스워드와 패스워드 확인이 일치하지 않습니다.");
            return;
        }

        try {
            regSvc.regist(reg);
            System.out.println("등록되었습니다.");
        } catch(DuplicateMemberException e) {
            System.out.println("이미 존재하는 이메일입니다.");
        }
    }

    private static void processChangeCommand(String[] args) {
        if (args.length != 4) {
            printHelp();
            return;
        }

        ChangePasswordService pwdSvc = assembler.getPwdSvc();
        try {
            pwdSvc.changePassword(args[1], args[2], args[3]);
            System.out.println("패스워드를 변경하였습니다.");
        } catch(RuntimeException e) {
            System.out.println(e.getMessage());
        }
    }
}

스프링을 이용한 객체 조립과 사용 (스프링을 사용해보자 !! 🌱)

스프링 설정 클래스

config.AppCtx

  • 스프링 빈이 어떻게 만들어져야 하는지 정의되어 있는 곳
    @configuration
    @Bean
package com.test.test1.config;

import com.test.test1.ChangePasswordService;
import com.test.test1.MemberDAO;
import com.test.test1.MemberRegisterService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppCtx {
    @Bean
    public MemberDAO memberDAO() {
        return new MemberDAO();
    }
    
    @Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService(memberDAO());
    }
    
    @Bean
    public ChangePasswordService changePwdSvc() {
        return new ChangePasswordService(memberDAO());
    }
}

스프링 컨테이너

main.MainForSpring.java

  • 빈을 만들고 관리하는 곳
package com.test.test1.main;

import com.test.test1.*;
import com.test.test1.config.AppCtx;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class MainForSpring {

    private static ApplicationContext ctx = null;

    public static void main(String[] args) throws IOException {

		// 빈 설정 정보를 읽어와서 ApplicationContext에 저장하자
        ctx = new AnnotationConfigApplicationContext(AppCtx.class);

        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            System.out.println("명령어를 입력하세요. ");
            String command = reader.readLine();
            if (command.startsWith("exit")) {
                System.out.println("종료합니다.");
                break;
            }
            if (command.startsWith("new")) {
                processNewCommand(command.split(" "));
                continue;
            }
            if (command.startsWith("change")) {
                processChangeCommand(command.split(" "));
                continue;
            }
            printHelp();
        }
    }

    private static void printHelp() {
        System.out.println();
        System.out.println("잘못된 명령입니다. 아래 명령어 사용법을 확인하세요.");
        System.out.println("new email name password confirmPassword");
        System.out.println("change email currentPassword newPassword");
        System.out.println();
    }

    private static void processNewCommand(String[] args) {
        if (args.length != 5) {
            printHelp();
            return;
        }

        // MemberRegisterService regSvc = assembler.getRegSvc();
        
        // 필요한 시점에 getBean으로 타입캐스팅을 해서 범용적으로 사용할 수 있다.
        MemberRegisterService regSvc = ctx.getBean("memberRegSvc", MemberRegisterService.class);
        RegisterRequest reg = new RegisterRequest();
        reg.setEmail(args[1]);
        reg.setName(args[2]);
        reg.setPassword(args[3]);
        reg.setConfirmPassword(args[4]);

        if (!reg.isPasswordEqualToConfirmPassword()) {
            System.out.println("패스워드와 패스워드 확인이 일치하지 않습니다.");
            return;
        }

        try {
            regSvc.regist(reg);
            System.out.println("등록되었습니다.");
        } catch(DuplicateMemberException e) {
            System.out.println("이미 존재하는 이메일입니다.");
        }
    }

    private static void processChangeCommand(String[] args) {
        if (args.length != 4) {
            printHelp();
            return;
        }

        // ChangePasswordService pwdSvc = assembler.getPwdSvc();
        
        // 필요한 시점에 getBean으로 타입캐스팅을 해서 범용적으로 사용할 수 있다.
        ChangePasswordService pwdSvc = ctx.getBean("changePwdSvc", ChangePasswordService.class);
        try {
            pwdSvc.changePassword(args[1], args[2], args[3]);
            System.out.println("패스워드를 변경하였습니다.");
        } catch(RuntimeException e) {
            System.out.println(e.getMessage());
        }
    }
}

회원 목록 출력 기능을 추가

MemberPrinter.java

package com.test.test1;

public class MemberPrinter {
    public void print(Member member) {
        System.out.printf("회원정보: ID=%s, 이메일=%s, 이름=%s, 등록일=%tF\n",
                member.getId(), member.getEmail(), member.getName(), member.getRegisterDateTime());
    }
}

MemberListPrinter.java

package com.test.test1;

import java.util.Collection;

public class MemberListPrinter {
    private MemberDAO memberDAO;
    private MemberPrinter printer;

    public MemberListPrinter(MemberDAO memberDAO, MemberPrinter printer) {
        this.memberDAO = memberDAO;
        this.printer = printer;
    }

    public void printAll() {
        Collection<Member> members = memberDAO.selectAll();
        members.forEach(member -> printer.print(member));
    }
}

스프링 설정 클래스(AppCtx.java)를 수정 - 생성자로 DI(의존성 주입) 제공

// 생성자로 주입
@Bean
public MemberPrinter memberPrinter() {
    return new MemberPrinter();
}

@Bean
public MemberListPrinter memberListPrinter() {
    return new MemberListPrinter(memberDAO(), memberPrinter()); // 생성자로 주입
}

스프링 컨테이너(MainForSpring.java)를 수정

if (command.startsWith("list")) {
    processListCommand(command.split(" "));
    continue;
}
private static void processListCommand(String[] args) {
    MemberListPrinter memberListPrinter = ctx.getBean("memberListPrinter", MemberListPrinter.class);
    memberListPrinter.printAll();
}

실행 결과


회원 정보를 출력하는 기능

MemberInfoPrinter.java

이메일 정보와 일치하는 회원의 정보를 출력하는 기능

package com.test.test1;

public class MemberInfoPrinter {
    private MemberDAO memberDAO;
    private MemberPrinter printer;

    public void setMemberDAO(MemberDAO memberDAO) {
        this.memberDAO = memberDAO;
    }

    public void setMemberPrinter(MemberPrinter printer) {
        this.printer = printer;
    }

    public void printMemberInfo(String email) {
        Member member = memberDAO.selectByEmail(email);
        if (member == null) {
            System.out.println("일치하는 데이터가 없습니다.");
            return;
        }
        printer.print(member);
        System.out.println();
    }
}

스프링 설정 클래스(AppCtx.java)를 수정 - setter 메서드로 DI(의존성 주입) 제공

빈을 등록해줘야한다 !

@Bean
public MemberInfoPrinter memberInfoPrinter() {
    MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
    infoPrinter.setMemberDAO(memberDAO()); // setter 메서드로 주입
    infoPrinter.setMemberPrinter(memberPrinter());
    return infoPrinter;
}

스프링 컨테이너(MainForSpring.java)를 수정

if (command.startsWith("info")) {
    processInfoCommand(command.split(" "));
    continue;
}
private static void processInfoCommand(String[] args) {
    if (args.length != 2) {
        printHelp();
        return;
    }

    MemberInfoPrinter memberInfoPrinter = ctx.getBean("memberInfoPrinter", MemberInfoPrinter.class);
    memberInfoPrinter.printMemberInfo(args[1]);
}

실행 결과


버전 정보를 출력하는 기능

VersionPrinter.java

package com.test.test1;

public class VersionPrinter {
    private int majorVersion;
    private int minorVersion;

    public void print() {
        System.out.printf("이 프로그램의 버전은 %d.%d 입니다.\n", this.majorVersion, this.minorVersion);
    }

    public void setMajorVersion(int majorVersion) {
        this.majorVersion = majorVersion;
    }

    public void setMinorVersion(int minorVersion) {
        this.minorVersion = minorVersion;
    }
}

스프링 설정 클래스(AppCtx.java)를 수정

@Bean
public VersionPrinter versionPrinter() {
    VersionPrinter versionPrinter = new VersionPrinter();
    versionPrinter.setMajorVersion(5);
    versionPrinter.setMinorVersion(3);
    return versionPrinter;
}

스프링 컨테이너(MainForSpring.java)를 수정

private static void processVersionCommand(String[] args) {
    VersionPrinter versionPrinter = ctx.getBean("versionPrinter", VersionPrinter.class);
    versionPrinter.print();
}

getBean으로 제공하는 기능을 쓸 수 있다 !

실행 결과


스프링 특징

스프링은 런타임에 설정 클래스를 상속한 새로운 설정 클래스를 만들어서 사용한다. => 싱글톤 범위를 제공

[의사코드]

@Configuration
public class AppCtx {
	@Bean
    public MemberDAO memberDAO() {
    	return new MemberDAO();
    }
    ... (생략) ...
}

public class AppCtxExt extends AppCtx {
	private Map<String, Object> beans = ...;
    
    @Override
    public MemberDAO memberDAO() {
    	if (!beans.containsKey("memberDAO")) {
        	beans.put("memberDAO", super.memberDAO());
        }
        return (MemberDAO) beans.get("memberDAO");
    }
}

최초 호출 됐을 때는 넣어주고(put), 그다음부턴 return 해준다.
=> 싱글톤으로 관리하기


빈의 개수가 증가하면 영역별로 설정 파일을 나누어서 관리하는 것이 편리 => AppCtx를 AppConf1, AppConf2로 분리

AppConf1

package com.test.test1.config;

import com.test.test1.MemberDAO;
import com.test.test1.MemberPrinter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 의존 관계가 없는것들
@Configuration
public class AppConf1 {
    @Bean
    public MemberDAO memberDAO() {
        return new MemberDAO();
    }

    @Bean
    public MemberPrinter memberPrinter() {
        return new MemberPrinter();
    }
}

AppConf2

package com.test.test1.config;

import com.test.test1.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 의존 관계가 있는 것들
@Configuration
public class AppConf2 {
    // 필드 정의
    // @Autowired: 스프링 빈에 의존하는 다른 빈에 자동으로 주입!!
    @Autowired
    private MemberDAO memberDAO;
    private MemberPrinter memberPrinter;

    @Bean
    public MemberRegisterService memberRegSvc() {
        return new MemberRegisterService(memberDAO);
    }

    @Bean
    public ChangePasswordService changePwdSvc() {
        return new ChangePasswordService(memberDAO);
    }


    @Bean
    public MemberListPrinter memberListPrinter() {
        return new MemberListPrinter(memberDAO, memberPrinter); // 생성자로 주입
    }

    @Bean
    public MemberInfoPrinter memberInfoPrinter() {
        MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
        infoPrinter.setMemberDAO(memberDAO); // setter 메서드로 주입
        infoPrinter.setMemberPrinter(memberPrinter);
        return infoPrinter;
    }

    @Bean
    public VersionPrinter versionPrinter() {
        VersionPrinter versionPrinter = new VersionPrinter();
        versionPrinter.setMajorVersion(5);
        versionPrinter.setMinorVersion(3);
        return versionPrinter;
    }
}

스프링 컨테이너를 수정 => 새로운 설정 클래스를 사용하도록

// ctx = new AnnotationConfigApplicationContext(AppCtx.class);
⬇️
ctx = new AnnotationConfigApplicationContext(AppConf1.class, AppConf2.class);

@Autowired 🩷🩷🩷🩷🩷🩷🩷🩷🩷🩷

설정 파일을 분리하니까 의존 객체들이 생겼다..!!!
필드를 만들어서 memberDAO, memberPrinter를 사용할 수 있도록 @Autowired 사용하자!
그럼 자동으로 인스턴스를 넣어준다.
단, memberDAO와 memberPrinter가 bean으로 등록되어 있어야만 한다.

  • 의존 주입 대상에 Autowired를 쓰면 의존 주입을 위한 코드(생성자 이용, setter 메서드 이용)를 사용하지 않아도 된다!!

예시 ⬇️

MemberInfoPrinter.java

public class MemberInfoPrinter {
    @Autowired
    private MemberDAO memberDAO;
    @Autowired
    private MemberPrinter printer;
    
    .. (생략) ..

AppConf2.java

@Bean
public MemberInfoPrinter memberInfoPrinter() {
    MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
    //infoPrinter.setMemberDAO(memberDAO); // setter 메서드로 주입
    //infoPrinter.setMemberPrinter(memberPrinter);
    return infoPrinter;
}

0개의 댓글

관련 채용 정보