Chap04. 의존 자동 주입
@Autowired 애노테이션 필수 여부
- 자동 주입할 대상이 필수가 아닌경우 3가지 방법을 이용해서 처리할 수 있다.
- 메서드와 필드에 둘 다 적용 가능하다.
- @Autowired 애노테이션의 required 속성을 false로 설정
- 매칭되는 빈이 없어도 익셉션이 발생하지 않으며 자동 주입을 수행하지 않는다.
@Autowired(required = false)
public void setDateTimeFormatter(DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
}
- 자바8의 Optional을 사용
- 자동 주입 대상 타입이 Optional인 경우,
- 일치하는 빈이 존재하지 않으면 값이 없는 Optional을 인자로 전달(예외발생x)
- 일치하는 빈이 존재하면 해당 빈을 값으로 갖는 Optional을 인자로 전달
public void setDateTimeFormatter(Optional<DateTimeFormatter> formatterOpt) {
if(formatterOpt.isPresent()) {
this.dateTimeFormatter = formatterOpt.get();
}else {
this.dateTimeFormatter = null;
}
}
- @Nullable 애노테이션 사용
- 의존 주입 대상 파라미터에 @Nullable 을 붙이면 세터 메서드를 호출할 때 자동 주입할 빈이 존재하면 해당 빈을 인자로 전달하고, 존재하지 않으면 인자로 null을 전달
- @Autowired(require = false)와 다른점은 자동 주입할 빈이 존재하지 않아도 메서드가 호출된다는점이다.
public void setDateTimeFormatter(@Nullable DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
}
생성자 초기화와 필수 여부 지정 방식 동작 이해
- @Autowired(required = false)는 일치하는 빈이 없으면 값 할당 자체를 하지 않음
- @Nullable 일치하는 빈이 없을 때 null 값을 할당
package spring;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
public class MemberPrinter {
private DateTimeFormatter dateTimeFormatter;
public MemberPrinter() {
dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
}
public void print(Member member) {
if(dateTimeFormatter == null) {
System.out.printf(
"회원 정보: 아이디=%d, 이메일=%s, 이름=%s, 등록일=%tF\n",
member.getId(),
member.getEmail(),
member.getName(),
member.getRegisterDateTime()
);
}else {
System.out.printf(
"회원 정보: 아이디=%d, 이메일=%s, 이름=%s, 등록일=%s\n",
member.getId(),
member.getEmail(),
member.getName(),
dateTimeFormatter.format(member.getRegisterDateTime())
);
}
}
@Autowired(required = false)
public void setDateTimeFormatter(DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
}
}
자동 주입과 명시적 의존 주입 관의 관계
- 설정 클래스에서 의존을 주입했는데 자동 주입 대상이면 어떻게 될까?
- 설정 클래스에서 세터 메서드를 통해 의존을 주입해도 해당 세터 메서드에 @Autowired 가 붙어있으면 자동 주입을 통해 일치하는 빈을 주입
- @Autowired 애노테이션을 사용했다면 설정 클래스에서 객체를 주입하기 보다는 스프링이 제공하는 자동 주입 기능을 사용하는 편이 낫다.
@Bean
@Qualifier("printer")
public MemberPrinter memberPrinter1() {
return new MemberPrinter();
}
@Bean
@Qualifier("summaryPrinter")
public MemberSummarayPrinter memberPrinter2() {
return new MemberSummarayPrinter();
}
@Bean
public MemberInfoPrinter infoPrinter() {
MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
infoPrinter.setMemberPrinter(memberPrinter2());
return infoPrinter;
}
@Autowired
@Qualifier("printer")
public void setMemberPrinter(MemberPrinter memberPrinter) {
this.memberPrinter = memberPrinter;
}
chap05.컴포넌트 스캔
- 컴포넌트 스캔은 스프링이 직접 클래스를 검색해서 빈으로 등록해주는 기능
- 설정 클래스에 빈으로 등록하지 않아도 원하는 클래스를 빈으로 등록할 수 있으므로 컴포넌트 스캔 기능을 사용하면 설정 코드가 크게 줄어든다.
@Component 애노테이션으로 스캔 대상 지정
- 스프링이 검색해서 빈으로 등록할 수 있으려면 클래스에 @Component 애노테이션을 붙여야함
- @Component 애노테이션은 해당 클래스를 스캔 대상으로 표시
- @Component에 값을 주지않으면 클래스 이름의 첫 글자를 소문자로 바꾼 이름을 빈 이름으로 사용
- @Component에 값을 주면 그 값을 빈 이름으로 사용
@Component
public class MemberDao {
private static long nextId = 0;
}
@Component("infoPrinter")
public class MemberInfoPrinter {
}
@ComponentScan 애노테이션으로 스캔 설정
- @Component를 스프링 빈으로 적용하려면 설정 클래스에 @ComponentScan 애노테이션을 적용해야 한다.
- @ComponentScan을 통해서 스프링 컨테이너가 자동으로 빈으로 등록시켜주기 때문에 설정 클래스 코드가 많이 줄어든다
- @ComponentScan의 basePackages 속성값은 스캔 대상 패키지 목록을 지정
- 지정한 패키지 안에 @Component 애노테이션이 붙은 클래스의 객체를 생성해서 빈으로 등록
package config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
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.MemberSummarayPrinter;
import spring.VersionPrinter;
@Configuration
@ComponentScan(basePackages = {"spring"})
public class AppCtx {
@Bean
@Qualifier("printer")
public MemberPrinter memberPrinter1() {
return new MemberPrinter();
}
@Bean
@Qualifier("summaryPrinter")
public MemberSummarayPrinter memberPrinter2() {
return new MemberSummarayPrinter();
}
@Bean
public VersionPrinter versionPrinter() {
VersionPrinter versionPrinter = new VersionPrinter();
versionPrinter.setMajorVersion(5);
versionPrinter.setMinorVersion(0);
return versionPrinter;
}
}
스캔 대상에서 제외하거나 포함하기
- excludeFilters 속성을 사용하면 스캔할 때 특정 대상을 자동 등록 대상에서 제외할 수 있다
- FilterType.REGEX
- pattern
- FilterType에 적용할 값 설정
- String[ ] 타입이므로 배열을 이용해서 한 개 이상 지정할 수 있다.
@Configuration
@ComponentScan(
basePackages = {"spring"}
, excludeFilters = @Filter(type = FilterType.REGEX , pattern = "spring\\..*Dao")
)
public class AppCtx {
- FilterType.AspectJ
- AspectJ 패턴을 통해서 제외 대상을 지정
- AspectJ 패턴이 동작하려면 aspectjweaver 모듈을 pom.xml에 추가해야함
@Configuration
@ComponentScan(
basePackages = {"spring"}
, excludeFilters = @Filter(type = FilterType.AspectJ , pattern = "spring.*Dao")
)
public class AppCtx {
- 특정 애노테이션을 붙인 타입을 대상에서 제외 할수 있다.
@Configuration
@ComponentScan(
basePackages = {"spring"}
, excludeFilters = @Filter(type = FilterType.ANNOTATION, classes ={no.class , product.class} )
)
public class AppCtx {
- 특정 타입이나 그 하위 타입을 컴포넌트 스캔 대상에서 제외
@Configuration
@ComponentScan(
basePackages = {"spring"}
, excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes ={MemberDao.class , product.class} )
)
public class AppCtx {
@Configuration
@ComponentScan(
basePackages = {"spring"}
, excludeFilters = {
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes ={MemberDao.class , product.class} ) ,
@Filter(type = FilterType.AspectJ , pattern = "spring.*Dao")
}
)
public class AppCtx {
기본 스캔 대상
- 다음 어노테이션을 붙인 클래스가 컴포넌트 스캔 대상에 포함된다
- @Component
- @Controller
- @Service
- @Repository
- @AspectJ
- @Configuration
- @AspectJ를 제외한 나머지 어노테이션은 실제로는 @Component 어노테이션에 대한 특수 어노테이션.....
컴포넌트 스캔에 따른 충돌 처리
빈 이름 충돌
- 각각 다른 패키지에서 @Componet로 같은 이름의 빈을 설정하면 오류가 발생
- 컴포넌트 스캔과정에서 서로 다른 타입인데 같은 빈 이름을 사용하는 경우가 있다면 둘 중 하나에 명시적으로 빈 이름을 지정해서 충돌을 피해야한다.
수동 등록한 빈과 충돌
- 스캔할 때 사용하는 빈 이름(@Component)과 수동 등록한 빈 이름이 같은 경우 수동 등록한 빈이 우선 적용한다.따라서 빈은 하나만 존재
- 스캔할 때 사용하는 빈 이름(@Component)과 수동 등록한 빈 이름이 다른 경우에는 @Qualifer를 통해서 알맞은 빈을 선택해야 한다. 이때 빈은 두개 다 존재한다.