스프링 입문5
3장
1. 의존이란?
- 객체 간의 의존을 의미한다.
- 한 클래스가 다른 클래스의 메서드를 실행할 때 이를 '의존'한다고 표현
- 의존은 변경에 의해 영향을 받는관계를 의미한다.
2. DI를 통한 의존 처리
- DI(Dependecy Injection, 의존 주입)는 의존하는 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식을 사용한다.
- 예를들어, 아래 코드는 생성자를 통해 MemberRegisterService가 의존(Dependency)하고 있는 MemberDao 객체를 주입(Injection) 받은 것이다.
package chap03.spring;
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("dup email" + req.getEmail());
}
Member newMember = new Member(req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now());
memberDao.insert(newMember);
return newMember.getId();
}
}
3. DI와 의존 객체 변경의 유연함
- 의존 객체를 직접 생성하는 방식이 아닌 의존하는 객체를 주입하는 방식을 사용하는 이유는?
- 직접 객체를 생성한 코드가 여러 클래스에 분포되어 있는 경우 생성하려고 하는 객체가 변경 되었을 때 분포되어 있는 생성 코드를 다 변경해야 하는 번거로움이 있다.
- 의존 주입을 사용해 객체를 생성하는 경우 클라이언트에서 주입하는 대상만 바꿔주면 된다.
4. 객체 조립기
- 객체 생성에 사용할 클래스를 변경하기 위해 객체를 주입하는 코드 한 곳만 변경하면 된다고 했었다.
- 그렇다면 실제 객체를 생성하는 코드는 어디에 존재할까?
- 쉽게 생각하면 메인 메서드에서 객체를 생성하면 될 것 같다.
- 객체를 생성하고 의존 객체를 주입해주는 클래스를 따로 작성하는 것 (= 조립기 클래스)
- 조립기는 객체를 생성하고 의존 객체를 주입하는 기능을 제공한다.
5. 스프링의 DI 설정
- 스프링은 앞서 구현한 조립기와 유사하게 필요한 객체를 생성하고 생성한 객체에 의존을 주입한다.
- 객체를 제공하는 기능(메서드)을 정의하고 있다.
5-1. 스프링을 이용한 객체 조립과 사용
- 스프링이 어떤 객체를 생성하고, 의존을 어떻게 주입할지를 정의한 설정 정보를 작성해야 한다.
- @Configuration 어노테이션을 단 클래스 내부에서 @Bean 어노테이션을 단 메서드를 통해 빈 객체를 생성한다.
- AnnotationConfigApplicationContext를 사용해서 스프링 컨테이너를 생성한다.
- getBean() 메서드를 통해 객체를 불러온다.
6. 생성자 DI vs setter 메서드 DI장점
- 생성자 방식 : 빈 객체를 생성하는 시점에 모든 의존 객체가 주입된다.
- setter 메서드 방식 : 세터 메서드 이름을 통해 어떤 의존 객체가 주입되는지 알 수 있다.
단점
- 생성자 방식 : 인수가 많아질 경우 생성자 코드를 확인해야하는 번거로움
- setter 메서드 방식 : setter 메서드를 사용해서 필요한 의존 객체를 전달하지 않아도 빈 객체가 생성되기 때문에 객체를 사용하는 시점에 NullPointerException이 발생할 수 있다.
7. @Configuration 설정 클래스의 @Bean 설정과 싱글톤
- @Bean이 설정된 메서드가 호출될 때마다 새로운 객체를 생성해서 리턴하지 않을까?
- @Bean이 붙은 메서드에 대한 한 개의 객체만 생성한다. 메서드를 몇번을 호출하더라도 항상 같은 객체를 리턴 한다.
- 스프링은 설정 클래스를 그대로 사용하지 않고 설정 클래스를 상속한 새로운 설정 클래스를 만들어 사용한다.
- 매번 새로운 객체를 생성하지 않고 한 번 생성한 객체를 보관했다가 이후에는 동일한 객체를 리턴하도록 한다.
8. 두 개 이상의 설정 파일 사용하기
- 스프링은 한 개 이상의 설정 파일을 이용해서 컨테이너를 생성할 수 있다.
- @Autowired 애노테이션을 활용하여 설정 파일간 등록된 빈도 사용할 수 있다.
@Configuration
public class AppConf1 {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
}
@Configuration
public class AppConf2 {
@Autowired
private MemberDao memberDao;
@Autowired
private MemberPrinter memberPrinter;
... // 생략
}
8-1. @AutoWired 애노테이션
- @Autowired 애노테이션은 스프링 빈에 의존하는 다른 빈을 자동으로 주입하고 싶을 때 사용한다.
- 아래의 예제와 같이 @Autowired를 사용하여 의존 객체를 주입한 경우. setter 메서드를 사용해서 의존 주입을 하지 않아도 @Autowired를 붙인 필드에 스프링 컨테이너가 자동으로 해당 타입의 빈 객체를 주입 해준다.
// @Autowired를 사용하지 않은 경우
public class MemberInfoPrinter {
private MemberDao memberDao;
private MemberPrinter printer;
public void printMemberInfo(String email) {
Member member = memberDao.selectByEmail(email);
if(member == null) {
System.out.println("데이터 없음");
return;
}
printer.print(member);
System.out.println();
}
public void setMemberDao(MemberDao memberDao) {
this.memberDao = memberDao;
}
public void setPrinter(MemberPrinter printer) {
this.printer = printer;
}
}
// @Autowired를 사용한 경우
public class MemberInfoPrinter {
@Autowired
private MemberDao memberDao;
@Autowired
private MemberPrinter printer;
public void printMemberInfo(String email) {
Member member = memberDao.selectByEmail(email);
if(member == null) {
System.out.println("데이터 없음");
return;
}
printer.print(member);
System.out.println();
}
}
8-2. @Import 애노테이션 사용
- 두 개 이상의 설정 파일을 사용하는 또 다른 방법으로 @Import 애노테이션을 사용하는 것이 있다.
- @Import 애노테이션은 함께 사용할 설정 클래스를 지정한다.
// 위의 예 AppConf1에 AppConf2를 포함하여 1개의 설정 파일처럼 사용하게 만든 예제
@Configuration
@Import(AppConf2.class)
public class AppConfImport {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
}
// 두 개 이상의 설정 클래스도 지정 가능하다.
@Configuration
@Import({AppConf1.class, AppConf2.class})
public class AppConfImport {
}
9. getBean() 메서드 사용
- getBean() 메서드의 첫 번째 인자는 빈의 이름, 두 번째 인자는 빈의 타입이다.
- 빈 이름을 지정하지 않고 타입만으로도 빈을 구할 수 있다.
- 해당 타입의 빈 객체가 한 개만 존재하면 해당 빈을 구해서 리턴한다.
- 해당 타입으로 여러개의 빈이 등록된 경우 익셉션(NoUniqueBeanDefinitionException) 이 발생한다.
- getBean() 메서드 호출 시 존재하지 않는 빈 이름을 사용하면 익셉션(NoSuchBeanDefinitionException)이 발생한다.
- 반대로 지정된 타입이 다를 경우 익센셥(BeanNotOfRequiredTypeException)이 발생한다.
VersionPrinter versionPrinter = ctx.getBean("versionPrinter", VersionPrinter.class);
VersionPrinter versionPrinter = ctx.getBean(VersionPrinter.class);
// 존재하지 않는 빈 이름을 사용하여 메서드 호출한 경우
VersionPrinter versionPrinter = ctx.getBean("versionPrinter2", VersionPrinter.class);
// 빈의 실제 타입과 메서드에서 지정한 타입을 다르게 하여 메서드 호출한 경우
// 이름이 listPrinter인 빈의 타입이 MemberListPrinter 일 때
VersionPrinter versionPrinter = ctx.getBean("listPrinter", VersionPrinter.class);
10. 주입 대상 객체를 모두 빈 객체로 설정해야 하나?
- 주입할 객체가 꼭 스프링 빈이어야 할 필요는 없다.
- 아래 예제와 같이 MemberPrinter 객체를 생성해서 사용해도 정상적으로 동작한다.
- 객체를 스프링 빈으로 등록할 때와 등록하지 않을 때의 차이는 스프링 컨테이너가 객체를 관리하는지 여부이다.
- 스프링 컨테이너가 제공하는 관리 기능이 필요없고 getBean() 메서드로 구할 필요가 없다면 빈 객체로 꼭 등록해야 하는 것은 아니다.
@Configuration
public class AppCtxNoMemberPrinterBean {
private MemberPrinter printer = new MemberPrinter(); //빈이 아님
... // 생략
@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;
}
... // 생략
}
// MemberPrinter를 빈으로 등록하지 않았으므로
// 아래 코드는 익셉션을 발생한다.
MemberPrinter printer = ctx.getBean(MemberPrinter.class);
'Spring' 카테고리의 다른 글
Spring과 Spring Boot의 차이 (0) | 2021.07.06 |
---|---|
Chapter 2. 스프링 컨테이너, 싱글톤 객체 (0) | 2021.06.16 |
Chapter 1. 들어가며 (0) | 2021.06.16 |