# 의존 주입(DI)
# 조립기
# 스프링 빈(Bean)
# 싱글톤
# Autowired 애노테이션
프로젝트 생성 및 실행 > 생략
public class MemberRegisterService {
...
public Long regist(RegisterRequest req) {
Member member = memberDao.selectByEmail(req.getEmail()); // memberDao 객체의 메서드 호출
...
Member newMember = new Member(
req.getEmail(), req.getPassword(), req.getName(),
LocalDateTime.now());
memberDao.insert(newMember); // memberDao 객체의 메서드 호출
return member.getId();
}
}
// 객체를 직접 생성하는 방식
public class MemberRegisterService() {
private MemberDao memberDao = new MemberDao();
}
// 의존 객체를 전달받는 방식
private MemberDao memberDao;
public MemberRegisterService(MemberDao memberDao) {
this.memberDao = memberDao;
}
가정: MemberDao 클래스가 회원 데이터를 DB에 저장
# 캐시: 데이터 값을 복사해놓은 임시 장소, 조회 속도 향상을 위해 캐시를 사용한다.
// 객체 의존 주입을 사용했을 때
//MemberDao memberDao = new MemberDao();
MemberDao memberDao = new CachedMemberDao(); // 이 부분만 바꿔주면 된다.
MemberRegisterService memberRegisterService = new MemberRegisterService(memberDao);
ChangePasswordService service = new ChangePasswordService(memberDao);
// new를 사용하여 객체를 생성했을 때
// new를 사용하여 객체를 생성한 코드를 모두 수정해야 한다.
// MemberRegisterService.java
public class MemberRegisterService{
MemberDao memberDao = new CachedMemberDao();
}
// ChangePasswordService.java
public class ChangePasswordService{
MemberDao memberDao = new CachedMemberDao();
}
public class MemberRegisterService {
private MemberDao memberDao;
// 생성자를 통해 의존 객체 주입
public MemberRegisterService(MemberDao memberDao) {
this.memberDao = memberDao; // 객체 필드에 할당
}
public Long regist(RegisterRequest req) {
...
return newMember.getId(); // 주입받은 객체의 메서드 사용
}
세터 메서드의 규칙
public class MemberInfoPrinter {
private MemberDao memberDao;
private MemberPrinter printer;
...
// setter
public void setMemberDao(MemberDao memberDao) {
this.memDao = memberDao;
}
// setter
public void setPrinter(MemberPrinter printer) {
this.printer = printer;
}
}
의존 객체를 주입하다 = 서로 다른 객체를 조립하다
public class Assembler {
private MemberDao memberDao;
private MemberRegisterService regSvc;
private ChangePasswordService pwdSvc;
public Assembler() {
memberDao = new MemberDao();
regSvc = new MemberRegisterService(memberDao); // 의존 객체 주입
pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao); // 의존 객체 주입
}
// MemberDao 객체가 필요한 곳에 객체를 제공하는 메서드
public MemberDao getMemberDao() {
return memberDao;
}
// MemberRegisterService 객체가 필요한 곳에 객체를 제공하는 메서드
public MemberRegisterService getMemberRegisterService() {
return regSvc;
}
// ChangePasswordService 객체가 필요한 곳에 객체를 제공하는 메서드
public ChangePasswordService getChangePasswordService() {
return pwdSvc;
}
}
main에서 Assembler 클래스를 호출하여 객체를 생성한다.
public class MainForAssembler {
private static Assembler assembler = new Assembler();
private static void processNewCommand(String[] arg) {
if (arg.length != 5) {
printHelp();
return;
}
// Assembler 클래스에 있는 getMemberRegisterService 메서드 호출
MemberRegisterService regSvc = assembler.getMemberRegisterService();
try {
regSvc.regist(req); // 객체가 갖는 regist 메서드 호출
System.out.println("등록했습니다.\n");
} catch (DuplicateMemberException e) {
System.out.println("이미 존재하는 이메일입니다.\n");
}
}
스프링은 "DI를 지원하는 조립기"
@Configuration // 스프링 설정 클래스로 설정
public class AppCtx {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao()); // memberDao 객체 주입
}
private static ApplicationContext ctx = null;
public static void main(String[] args) throws IOException {
// 스프링 컨테이너 생성
ctx = new AnnotationConfigApplicationContext(AppCtx.class);
// 빈 객체 구하기
MemberRegisterService regSvc =
ctx.getBean("memberRegSvc", MemberRegisterService.class);
// getBean("메서드 name", "메서드 반환 type")
}
ctx.getBean("Printer", Printer.class);
// situation 1
@Bean
public VersionPrinter Printer(){ }
// situation 2
ctx.getBean(Printer.class);
@Bean
public Printer Printer1(){ }
@Bean
public Printer Printer2(){ }
[ sit1 ] 실제 타입과 getBean() 메서드에 지정한 타입이 다른 경우
[ sit2 ] 빈 이름을 지정하지 않았을 때, 같은 타입의 빈 객체가 2개 이상 존재할 때
✏️ 빈 이름을 지정하지 않고 타입만으로 빈을 구할 수 있다.
✏️ 해당 타입의 빈이 1개만 존재할 때 해당 빈을 구해서 리턴한다. 오류 발생X
@Configuration
public class AppCtx {
@Bean
public MemberDao memberDao() {
return new MemberDao(); // MemberDao 객체 생성
}
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao()); // MemberDao 메서드 실행
}
@Bean
public ChangePasswordService changePwdSvc() {
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemberDao(memberDao()); // MemberDao 메서드 실행
return pwdSvc;
}
}
스프링 컨테이너가 생성한 빈(Bean)은 싱글톤 객체이다.
= 다른 설정 메서드에서 memberDao()를 n번 호출하여도 항상 같은 객체를 리턴
한 번 생성한 객체를 보관했다가 이후에 동일한 객체 리턴(교재 92p 참고)
생성자 방식
세터 메서드 방식
주입할 객체가 꼭 스프링 빈일 필요는 없다.
@Configuration
public class AppCtxNoMemberPrinterBean {
private MemberPrinter printer = new MemberPrinter(); // 스프링 빈이 아니다.
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberListPrinter listPrinter() {
return new MemberListPrinter(memberDao(), printer); // 일반 객체 주입
}
@Bean
public MemberInfoPrinter infoPrinter() {
MemberInfoPrinter infoPrinter = new MemberInfoPrinter(); // 스프링 빈이 아니다.
infoPrinter.setMemberDao(memberDao());
infoPrinter.setPrinter(printer); // 일반 객체 주입
return infoPrinter;
}
}
// 메인.js
MemberPrinter printer = ctx.getBean(MemberPrinter.class);
// 스프링 빈으로 등록되어 있지 않아 에러 발생
영역별로 설정 파일을 나누어 관리하면 스프링 빈을 관리하기 용이하다.
// MainForSpring2.java
ctx = new AnnotationConfigApplicationContext(AppConf1.class, AppConf2.class);
// 인자에 설정 클래스를 넣어 전달해준다.
// AppConf1.java
@Configuration
public class AppConf1 {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
}
// AppConf2.java
@Configuration
public class AppConf2 {
@Autowired // 스프링의 자동 주입 기능
private MemberDao memberDao;
// 스프링 컨테이너가 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;
}
}
// MainForImport.java
ctx = new AnnotationConfigApplicationContext(AppConfImport.class);
// AppConfImport.java
@Configuration
@Import(AppConf2.class) // AppConf2 설정도 함께 사용
@Import({AppConf1.class, AppConf2.class}) // 2개 이상의 설정 클래스 지정
public class AppConfImport. {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
}
// AppCtxNoMemberPrinterBean 클래스
@Configuration
public class AppCtxNoMemberPrinterBean {
private MemberPrinter printer = new MemberPrinter();
@Bean
public MemberDao memberDao1() {
return new MemberDao();
}
@Bean
public MemberDao memberDao2() {
return new MemberDao();
}
@Bean
public MemberListPrinter listPrinter() {
return new MemberListPrinter(memberDao(), printer);
}
@Bean
public MemberInfoPrinter infoPrinter() {
MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
infoPrinter.setMemberDao(memberDao());
infoPrinter.setPrinter(printer); // 일반 객체 주입
return infoPrinter;
}
}
// 메인.js
MemberPrinter printer = ctx.getBean(MemberInfoPrinter.class); // 1
MemberPrinter printer = ctx.getBean("memberDao1", MemberInfoPrinter.class); // 2
MemberPrinter printer = ctx.getBean("listPrinter", MemberListPrinter.class); // 3
MemberPrinter printer = ctx.getBean(MemberDao.class); // 4
정답: 2번, 4번
1. 스프링 컨테이너를 생성하고 빈 객체를 불러오는 것 까지의 코드를 작성해보자.
// AppCtxNoMemberPrinterBean.class
@Configuration
public class AppCtxNoMemberPrinterBean {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
}
// 메인.java
// 스프링 컨테이너 생성, 2장에서 사용한 AnnotationConfigApplicationContext 클래스를 이용해서
// 스프링 컨테이너를 생성할 수 있다.
ApplicatonContext ctx =
// 스프링 빈 객체 얻기, getBean()을 사용하여 MemberDao 타입의 스프링 빈 객체를 얻는다.
// 정답
new AnnotationConfigApplicationContext(AppCtxNoMemberPrinterBean.class);
MemberDao 객체 이름 = ctx.getBean("memberDao", MemberDao.class);
2. 2개 이상의 설정 파일을 사용한 스프링 컨테이너를 생성하는 코드를 작성해봅시다. @Autowired 애노테이션을 사용합니다.
// AppConf1.java
@Configuration
public class AppConf1 {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
}
// AppConf2.java
@Configuration
public class AppConf2 {
private MemberDao memberDao;
private MemberPrinter memberPrinter;
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao);
}
@Bean
public ChangePasswordService changePwdSvc() {
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemberPrinter(memberPrinter);
return pwdSvc;
}
}
// MainForSpring2.java
ctx = new AnnotationConfigApplicationContext(/*빈칸을 채워주세요*/);
Corner Spring #2
Editor : Otcr
[스프링2] 6장. 빈 라이프사이클과 범위 (0) | 2022.10.13 |
---|---|
[스프링2] 5장. 컴포넌트 스캔 (0) | 2022.10.13 |
[스프링2] 4장. 의존 자동 주입 (0) | 2022.10.13 |
[스프링2] 2장. 스프링 시작하기 (1) | 2022.09.29 |
[스프링2] 1장. 들어가며 (1) | 2022.09.29 |