참고 자료 : 초보 개발자를 위한 스프링5 프로그래밍 입문
해당 게시글은 초보 개발자를 위한 스프링5 프로그래밍 입문
1. 스프링을 이용한 객체 조립과 사용
(1) 스프링 사용 전, 설정 정보 클래스를 작성해야 함
-> 스프링이 어떤 객체를 생성하고
의존을 어떻게 주입할지에 관련하여 정의한 설정 정보들
@Configuration
: 스프링 설정 클래스를 의미,
해당 어노테이션을 붙여야 스프링 설정 클래스로 인식함
@Bean
: 해당 메서드가 생성한 객체를 스프링 빈이라고 설정함
예를 들어 memberDao() 메서드를 이용해서 생성한 빈 객체는
memberDao라는 이름으로 스프링에 등록된다.
@Configuration
public class AppCtx{
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao());
}
@Bean
public ChangePasswordService changePwdSvc(){
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao());
return pwdSvc;
}
}
스프링 빈 객체를 생성해야 하는 이유?
-> 스프링 컨테이너가 관리하는 대상으로 등록하기 위함. (아래 추가 설명)
(2) 스프링 컨테이너를 생성
-> 객체 생성 및 의존 객체를 주입하는 건 스프링 컨테이너.
따라서 설정 클래스를 이용하여 컨테이너를 생성해야 함
AnnotaionConfigApplicationContext 클래스를 이용한다
해당 클래스는 자바 어노테이션을 이용한 클래스로부터 객체 설정 정보를 가져온다.
// 스프링 컨테이너 생성
ApplicaionContext ctx = new AnnotationConfigApplicaiontContext(AppCtx.class);
// getBean() 메서드를 통해 객체생성
// 컨테이너에서 이름이 memberRegSvc인 빈 객체를 구한다.
MemberRegisterService regSvc= ctx.getBean("memberRegSvc", MemberRegisterService.class);
-> 스프링 컨테이너(ctx)로부터 이름이 memberRegSvc인 빈 객체를 구하는 과정임
MainForAssembler.java
-> 해당 클래스를 스프링 컨테이너를 사용하도록 변경할 것.
package main;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import assembler.Assembler;
import spring.ChangePasswordService;
import spring.DuplicateMemberException;
import spring.MemberNotFoundException;
import spring.MemberRegisterService;
import spring.RegisterRequest;
import spring.WrongIdPasswordException;
public class MainForAssembler {
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.equalsIgnoreCase("exit")) {
System.out.println("종료합니다.");
break;
}
if (command.startsWith("new ")) {
processNewCommand(command.split(" "));
continue;
} else if (command.startsWith("change ")) {
processChangeCommand(command.split(" "));
continue;
}
printHelp();
}
}
private static Assembler assembler = new Assembler();
private static void processNewCommand(String[] arg) {
if (arg.length != 5) {
printHelp();
return;
}
MemberRegisterService regSvc = assembler.getMemberRegisterService();
RegisterRequest req = new RegisterRequest();
req.setEmail(arg[1]);
req.setName(arg[2]);
req.setPassword(arg[3]);
req.setConfirmPassword(arg[4]);
if (!req.isPasswordEqualToConfirmPassword()) {
System.out.println("암호와 확인이 일치하지 않습니다.\n");
return;
}
try {
regSvc.regist(req);
System.out.println("등록했습니다.\n");
} catch (DuplicateMemberException e) {
System.out.println("이미 존재하는 이메일입니다.\n");
}
}
private static void processChangeCommand(String[] arg) {
if (arg.length != 4) {
printHelp();
return;
}
ChangePasswordService changePwdSvc =
assembler.getChangePasswordService();
try {
changePwdSvc.changePassword(arg[1], arg[2], arg[3]);
System.out.println("암호를 변경했습니다.\n");
} catch (MemberNotFoundException e) {
System.out.println("존재하지 않는 이메일입니다.\n");
} catch (WrongIdPasswordException e) {
System.out.println("이메일과 암호가 일치하지 않습니다.\n");
}
}
private static void printHelp() {
System.out.println();
System.out.println("잘못된 명령입니다. 아래 명령어 사용법을 확인하세요.");
System.out.println("명령어 사용법:");
System.out.println("new 이메일 이름 암호 암호확인");
System.out.println("change 이메일 현재비번 변경비번");
System.out.println();
}
}
MainForSpring.java
-> MainForAssembler.java 클래스와 다른점은
Assembler클래스 대신 ApplicationContext(스프링 컨테이너)를 사용했다는 것
Assembler는 직접 객체를 생성하지만
AnnotaionConfigApplicationContext는 설정파일(AppCtx클래스)로부터
생성할 객체와 의존 주입 대상을 정하는 방식
package main;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import config.AppCtx;
import spring.ChangePasswordService;
import spring.DuplicateMemberException;
import spring.MemberInfoPrinter;
import spring.MemberListPrinter;
import spring.MemberNotFoundException;
import spring.MemberRegisterService;
import spring.RegisterRequest;
import spring.VersionPrinter;
import spring.WrongIdPasswordException;
public class MainForSpring {
private static ApplicationContext ctx = null;
public static void main(String[] args) throws IOException {
ctx = new AnnotationConfigApplicationContext(AppCtx.class);
BufferedReader reader =
new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.println("명령어를 입력하세요:");
String command = reader.readLine();
if (command.equalsIgnoreCase("exit")) {
System.out.println("종료합니다.");
break;
}
if (command.startsWith("new ")) {
processNewCommand(command.split(" "));
continue;
} else if (command.startsWith("change ")) {
processChangeCommand(command.split(" "));
continue;
} else if (command.equals("list")) {
processListCommand();
continue;
} else if (command.startsWith("info ")) {
processInfoCommand(command.split(" "));
continue;
} else if (command.equals("version")) {
processVersionCommand();
continue;
}
printHelp();
}
}
private static void processNewCommand(String[] arg) {
if (arg.length != 5) {
printHelp();
return;
}
MemberRegisterService regSvc =
ctx.getBean("memberRegSvc", MemberRegisterService.class);
RegisterRequest req = new RegisterRequest();
req.setEmail(arg[1]);
req.setName(arg[2]);
req.setPassword(arg[3]);
req.setConfirmPassword(arg[4]);
if (!req.isPasswordEqualToConfirmPassword()) {
System.out.println("암호와 확인이 일치하지 않습니다.\n");
return;
}
try {
regSvc.regist(req);
System.out.println("등록했습니다.\n");
} catch (DuplicateMemberException e) {
System.out.println("이미 존재하는 이메일입니다.\n");
}
}
private static void processChangeCommand(String[] arg) {
if (arg.length != 4) {
printHelp();
return;
}
ChangePasswordService changePwdSvc =
ctx.getBean("changePwdSvc", ChangePasswordService.class);
try {
changePwdSvc.changePassword(arg[1], arg[2], arg[3]);
System.out.println("암호를 변경했습니다.\n");
} catch (MemberNotFoundException e) {
System.out.println("존재하지 않는 이메일입니다.\n");
} catch (WrongIdPasswordException e) {
System.out.println("이메일과 암호가 일치하지 않습니다.\n");
}
}
private static void printHelp() {
System.out.println();
System.out.println("잘못된 명령입니다. 아래 명령어 사용법을 확인하세요.");
System.out.println("명령어 사용법:");
System.out.println("new 이메일 이름 암호 암호확인");
System.out.println("change 이메일 현재비번 변경비번");
System.out.println();
}
private static void processListCommand() {
MemberListPrinter listPrinter =
ctx.getBean("listPrinter", MemberListPrinter.class);
listPrinter.printAll();
}
private static void processInfoCommand(String[] arg) {
if (arg.length != 2) {
printHelp();
return;
}
MemberInfoPrinter infoPrinter =
ctx.getBean("infoPrinter", MemberInfoPrinter.class);
infoPrinter.printMemberInfo(arg[1]);
}
private static void processVersionCommand() {
VersionPrinter versionPrinter =
ctx.getBean("versionPrinter", VersionPrinter.class);
versionPrinter.print();
}
}
2. DI 방식 (1) : 생성자 방식
: 두 개 이상의 인자를 받는 생성자를 사용하는 설정을 추가하는 방식
@Bean
public MemberListPrinter listPrinter() {
return new MemberListPrinter(memberDao(), memberPrinter());
}
AppCtx 파일 내에 위와 같이 코드를 추가한다.
package config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberInfoPrinter;
import spring.MemberListPrinter;
import spring.MemberPrinter;
import spring.MemberRegisterService;
import spring.VersionPrinter;
@Configuration
public class AppCtx {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao());
}
@Bean
public ChangePasswordService changePwdSvc() {
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao());
return pwdSvc;
}
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
@Bean
public MemberListPrinter listPrinter() {
return new MemberListPrinter(memberDao(), memberPrinter());
}
@Bean
public MemberInfoPrinter infoPrinter() {
MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
infoPrinter.setMemberDao(memberDao());
infoPrinter.setPrinter(memberPrinter());
return infoPrinter;
}
@Bean
public VersionPrinter versionPrinter() {
VersionPrinter versionPrinter = new VersionPrinter();
versionPrinter.setMajorVersion(5);
versionPrinter.setMinorVersion(0);
return versionPrinter;
}
}
3. DI 방식(2) :세터 메서드 방식
-> 생성자를 이용하는 방식 외에
세터 메서드를 이용해서도 객체를 주입받을 수 있다.
public class MemberInfoPrinter {
private MemberDao memDao;
private MemberPrinter printer;
public void printMemberInfo(String email){
Member member = membDao.selectByEmail(email);
if (member==null) {
System.out.println("데이터없음");
return;
}
printer.print(member);
System.out.println();
}
public void setMemberDao(MemberDao memberDao){
this.memDao=memDao;
}
public void setPrinter(MemberPrinter printer) {
this.printer=printer;
}
}
setMemberDao와 setPrinter는
MemberDao 타입의 객체 및 MemberPrinter 타입의 객체에 대한 의존을 주입할 때 사용된다.
즉 세터 메서드를 이용하여 의존성 주입을 하는 것이다.
생성자 방식은 빈 객체를 생성하는 시점에 모든 의존 객체가 주입된다.
설정 메서드 방식은 세터 메서드 이름을 통해
어떤 의존 객체가 주입되는지 알 수 있다.
(장점)
생성자 방식 : 객체 사용시 완전한 상태로 사용 가능
설정 메서드 방식 : 메서드 이름만으로 어떤 의존 객체를 설정하는지 유추 가능
(단점)
생성자 방식 : 파라미터 개수가 많을 경우 , 각 인자가 어떤 객체를 의존하는지 확인하려면
코드를 확인해야 함
설정 메서드 방식 : 필요한 의존 객체를 전달하지 않아도 빈 객체가 생성되기에
객체 사용 시점에 NullPointerExceptino이 발생할 수 있음
4. @Configuration 설정 클래스의 @Bean 설정과 싱글톤
- 스프링 컨테이너가 생성한 빈은 싱글톤 객체,
@Bean이 붙은 메서드에 대해 한 개의 객체만 생성함
다른 설정 메서드에서 memberDao()를 몇 번을 호출하더라도
항상 같은 객체만을 리턴한다는 의미.
이게 가능한 이유는
스프링 컨테이너가 설정 클래스를 상속한
새로운 설정 클래스를 만들어서 사용하기 때문이다.
5. 두 개 이상의 설정 파일 사용하기
빈의 개수가 많아질수록
하나의 설정 파일에 기재하기 보다는
영역별로 설정 파일을 구분하여 사용하는 것이 좋다.
package config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.MemberDao;
import spring.MemberPrinter;
@Configuration
public class AppConf1 {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
}
package config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberInfoPrinter;
import spring.MemberListPrinter;
import spring.MemberPrinter;
import spring.MemberRegisterService;
import spring.VersionPrinter;
@Configuration
public class AppConf2 {
@Autowired
private MemberDao memberDao;
@Autowired
private MemberPrinter memberPrinter;
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao);
}
@Bean
public ChangePasswordService changePwdSvc() {
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao);
return pwdSvc;
}
@Bean
public MemberListPrinter listPrinter() {
return new MemberListPrinter(memberDao, memberPrinter);
}
@Bean
public MemberInfoPrinter infoPrinter() {
MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
infoPrinter.setMemberDao(memberDao);
infoPrinter.setPrinter(memberPrinter);
return infoPrinter;
}
@Bean
public VersionPrinter versionPrinter() {
VersionPrinter versionPrinter = new VersionPrinter();
versionPrinter.setMajorVersion(5);
versionPrinter.setMinorVersion(0);
return versionPrinter;
}
}
@Autowired 어노테이션은
스프링의 자동 주입 기능을 위함이다.
스프링 설정 클래스의 @Bean 메서드에서
의존 주입을 위한 코드를 작성하지 않아도 된다.
세터메서드를 사용해서 의존 주입을 하지 않아도
@Autowired를 붙인 필드에
자동으로 해당 타입의 빈 객체가 주입이 된다.
해당 타입의 빈을 찾아서
필드에 할당한다.
위 코드에서 예를 들면
MemberDao 타입의 빈을 memberDao 필드에 할당한다
AppConf2 클래스의 meberDao 필드에는
AppConf1 클래스에서 설정한 빈이 할당된다.
(싱글톤이니까 하나 이상의 객체를 생성할 수 없다)
설정 클래스가 두개 이상일 경우
방식은 두 가지
1. 파라미터로 전달
파라미터로 설정 클래스를 추가로 전달하면 된다.
ctx = new AnnotationConfigApplicationContext(AppConf1.class, AppConf2.class);
2. @import 어노테이션 사용
@import(AppConf2.class)
package config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import spring.MemberDao;
import spring.MemberPrinter;
@Configuration
@Import({AppConf2.class})
public class AppConfImport {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
}
이렇게 하면
스프링 컨테이너 생성시 AppConf2 클래스를 따로 지정할 필요가 없어진다.
즉 파라미터로 넘겨주지 않아도 된다는 말이다.
배열을 이용해서
2개 이상의 설정 클래스도 지정이 가능하다.
@import ({AppConf1.class, AppConfig2.class})
6. 객체를 스프링 빈으로 등록할 때와 등록하지 않을 때의 차이점
: 스프링 컨테이너가 객체를 관리하는지의 여부
스프링 컨테이너는
자동 주입, 라이프 사이클 관리 등
단순 객체 생성 외에도 객체 관리를 위한 다양한 기능을 제공하는데
빈으로 등록한 객체에만 해당 기능을 적용한다.
그러나, 의존 주입 대상을 스프링 빈으로 등록하는 것이 보통이다.
'여니의 프로그래밍 study > Spring & Spring Boot' 카테고리의 다른 글
[Spring] 컴포넌트 스캔 + 빈 라이브사이클과 범위 (Chapter 5~6) (0) | 2023.08.27 |
---|---|
[Spring] 의존 자동 주입 (Chapter 4) (0) | 2023.08.12 |
[Spring] 스프링 DI (의존, 의존주입, 조립기) - 1 (0) | 2023.07.23 |
[Spring] 스프링 시작하기 (빌드 관리 도구, 메이븐, 그래들, 빈 객체, 컨테이너) (0) | 2023.07.16 |
Spring Boot 소셜 로그인 구현 (3) | feat. kakaoTalk (0) | 2023.07.14 |