DI 방식1 : 생성자 방식
DI 방식2 : 세터 메서드 방식
@Configuration
@Bean
🔼 이전글에 있습니다! 😋
1) 생성자 매개변수
✨AnnotationConfigApplicationContext(Class<?>... componentClasses)
관리 객체가 많을때는 설정 클래스를 분리하는 것이 좋다.
설정클래스는 현재 통합된 상태

✨AnnotationConfigApplicationContext(String... basePackages)
매개변수에 설정클래스 패키지로 지정도 가능하다.

2) @Import

🔼 설정 클래스가 없다면 사용 불가
Improt애노테이션 사용

#AppCtx설정 클래스
//설정 클래스 - 스프링 컨테이너가 관리할 객체를 정의하고 설정하는 역할
@Import(value = {AppCtx2.class})
@Configuration
public class AppCtx {
@Bean //스프링 컨테이너가 관리 해야할 객체임을 알려주는 역할(Bean)
public Greeter greeter(){
return new Greeter();
}
}
value값을 명시하지 않아도 되고 설정이 1개일때는 중괄호도 생략 가능하다.
@Import(AppCtx2.class)
이후..
동작 성공 ⭕⭕

스프링의 역할은 객체 관리💨📖

👩💻@Autowired를 설정해준 객체가 객체 컨테이너에 있다면 해당 객체를 꺼내서 의존성을 자동 주입해준다.
...
@Autowired
private JoinValidator validator;
@Autowired
private MemberDao memberDao;
...
설정 클래스 내(AppCtx2)

자동 주입 전, 위 처럼 memberDao 객체를 주입해주지 않으면 nullPointException이 발생한다.

setter메서드 위에 정의했을 경우 ! memberDao 의존성 자동 주입
public class JoinValidator implements Validator<RequestJoin> {
private MemberDao memberDao;
@Autowired
public void setMemberDao(MemberDao memberDao) {
this.memberDao = memberDao;
}
@Override
public void check(RequestJoin form) {
}
}
isPresent(): 값이 존재하면 true를 반환하고, 존재하지 않으면 false를 반환합니다.
ifPresent(Consumer): 값이 존재하면 주어진 동작을 수행합니다.
get(): 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 NoSuchElementException을 던집니다.
orElse(T other): 값이 존재하면 그 값을 반환하고, 그렇지 않으면 other를 반환합니다.
orElseGet(Supplier): 값이 존재하면 그 값을 반환하고, 그렇지 않으면 Supplier에서 제공하는 값을 반환합니다.
orElseThrow(Supplier): 값이 존재하면 그 값을 반환하고, 그렇지 않으면 Supplier가 제공하는 예외를 던집니다.
설정 클래스 내(AppCtx2)

public class InfoService {
private MemberDao memberDao;
@Autowired
public void setMemberDao(Optional<MemberDao> opt) {
this.memberDao = opt.get();
}
public void printList(){
List<Member> members = memberDao.getList();
members.forEach(System.out::println);
}
}
참고)👩🏫 옛날엔 존재했던...
@Resource
@Inject

의존성이 현재는 2개짜리인 예시지만 10개일경우 이렇게 10번을 정의해야한다??
ㄴㄴ
롬복으로 편리하게 생성자 매개변수에 원하는것만 넣을 수 있는 애노테이션이 존재한다..
@RequiredArgConstructor로 생성자 매개변수에 추가 될 수 있게 넣어주면 편리하게 쓸 수 있다.
👩🏫
@RequiredArgConstructor
- Lombok 라이브러리에서 제공하는 기능으로, 클래스 내의 final 키워드가 붙은 필드나 @NonNull 애노테이션이 붙은 필드들을 생성자의 매개변수로 자동으로 추가해주는 역할을 한다.
-> final 키워드가 붙어 있는 필드는 반드시 초기화가 필요함 즉, 객체가 생성될때 초기값을 설정해야하고 이후에는 값을 변경할 수 없다.
애노테이션을 사용하면 final 키워드가 붙은 필드를 자동으로 생성자의 매개변수로 추가 해주게 된다. (생성자를 직접 만들 필요 x)
@NonNull 애노테이션
- 해당 필드가 null이 아니어야 함을 명시한다.
- @RequiredArgsConstructor 애노테이션을 사용하면 생성자의 매개변수로 추가된다.
- @NonNull 애노테이션이 붙은 필드는 final 필드와 다르게 이후에 값을 변경할 수 있다. 즉, 생성 시점에 null이 아닌 값으로 초기화되지만 이후에 값을 변경할 필요가 있는 경우 사용한다.

설정 클래스 내(AppCtx2)

🔼 컨테이너에서 해당 객체를 찾을 수 없기 때문에 오류 발생💥💥


어떤 Bean인지 직접 알려줘야한다.⬇




해당 애노테이션으로 이름을 바꿔줄수도 있다~
@Bean: 메서드명
@Qualifier: 변경한 이름
클래스명(자동스캔의 경우) -> 앞 첫 문자는 소문자
ex) class Joinservice -> joinService
날짜 형식 바꾸기..📆

public class InfoService {//회원목록 조회
private MemberDao memberDao;
private DateTimeFormatter formatter;
@Autowired
@Qualifier("memberDao")
public void setMemberDao(Optional<MemberDao> opt) {
this.memberDao = opt.get();
}
@Autowired
public void setFormatter(DateTimeFormatter formatter) {
this.formatter = formatter;
}
public void printList(){
List<Member> members = memberDao.getList();
members.forEach(m -> {
System.out.println(m);
LocalDateTime regDt = m.getRegDt();
System.out.println(formatter.format(regDt));
});
}
}

@Autowired가 붙어있는 경우 해당하는 객체를 컨테이너에서 찾을 수 없으면 오류 발생

required: default값은 true임
-> 주입 받는 객체는 반드시 스프링 컨테이너에 생성되어 있어야 한다. 없을시 예외발생!!!
-> 객체가 없는 경우에 대처 하기 위해 required를 false로 바꿔주어야 한다.

🔽


public class InfoService {//회원목록 조회
private MemberDao memberDao;
private DateTimeFormatter formatter;
@Autowired
@Qualifier("memberDao")
public void setMemberDao(Optional<MemberDao> opt) {
this.memberDao = opt.get();
}
@Autowired(required = false) //DateTimeFormatter bean이 없으면 호출 X
public void setFormatter(DateTimeFormatter formatter) {
System.out.println("호출!"); //호출 안됨
this.formatter = formatter;
}
public void printList(){
List<Member> members = memberDao.getList();
members.forEach(m -> {
System.out.println(m);
LocalDateTime regDt = m.getRegDt();
if(formatter != null){
System.out.println(formatter.format(regDt));
}
});
}
}
@Nullable 애노테이션을 적용해도 필수 여부 해제
-> formatter에 Null로 대입되면서 출력문이 실행 되었다.
@Autowired(required = false) 의 경우 '호출!'이 출력되지 않았음
-> 아예 메서드 자체를 무시함



설정한 스캔 범위를 보고 컨테이너가 클래스의 애노테이션들을 확인한다. 있다면 관리 할 객체임을 알고 객체를 생성해준다.
ComponentScan이 지정한 패키지 안에 기본 스캔대상 애노테이션이 붙어있는 클래스를 찾는다.
기본 스캔 대상 애노테이션을 자동적으로 찾아서 관리 객체로 지정! 이후 스프링 컨테이너에서 객체가 생성된다.
암기!!👩🏫
@Component
@Service
@Configuration
@Controller
@RestController
@ControllerAdvice
@RestControllerAdvice
@Aspect
@Repository: DAO에 주로 정의
기본 스캔 대상이 지정된 클래스 안에서 의존성을 필요로 한다면 의존성 자동 주입으로 @Autowired 애노테이션을 붙여주면 컨테이너에서 객체를 찾아서 의존성 주입을 해주게된다.




🔼 패키지가 다르면 다른 클래스니까 같은 클래스명을 가진다해도(bean의 이름이 같다고 해도) 충돌이 안나지 않을까?!
..
..
but! 충돌 발생함
@Component("memberDao2")
public class MemberDao {
}
..
이렇게 bean의 이름을 바꿔주어야 한다.
MemberDao 클래스의 bean 이름은 클래스명 그대로 memberDao가 된다.


bean의 이름이 같은데 충돌이 나지 않는다❓❔⁉️
💫우선순위가 있기 때문!
보통은 자동 스캔범위 보다 수동으로 등록된 bean이 우선적으로 선택이 된다.
-> 수동으로 등록된 bean을 실행하고 자동스캔 한 bean은 실행하지 않는다
단, 수동 등록 bean이 2개라면 오류 발생함

해결법 ? bean의 이름 직접 지정해주기 @Qualifier

filter에 배제 할 애노테이션을 설정 할 수 있다.

🔼Filter 내부 애노테이션

Filter 애노테이션의 type속성의 형식을 주어서 제외 대상을 지정할 수 있다.
🔖type = FilterType.ANNOTATION
@Configuration
@ComponentScan(basePackages = "member",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {ManualBean.class})})
//ManualBean이 포함 되어있는 대상은 자동 스캔 범위에서 제외된다.
//한개 일 경우 중괄호 생략 가능
public class AppCtx {
}
@Repository
@ManualBean
public class MemberDao {
...
...
}
}
이 상태로 돌리면 MemberDao의존성을 필요로 하는곳에서 의존성 주입이 되지 않기 때문에 테스트 에러 발생!
제외 대상에 ManualBean이 포함되었기 때문에 자동 스캔을 하지 않았다.
🔖type = FilterType.ASSIGNABLE_TYPE
@Configuration
@ComponentScan(basePackages = "member",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = {MemberDao.class, JoinValidator.class})
)
public class AppCtx {
}
🔖type = FilterType.REGEX
@Configuration
@ComponentScan(basePackages = "member",
excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX,
pattern = "member\\\\..*Dao")})
public class AppCtx {
}

implementation'org.aspectj:aspectjweaver:1.9.22.1'
🔖type = FilterType.ASPECTJ
@Configuration
@ComponentScan(basePackages = "member",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "member..*Dao")
)
public class AppCtx {
}
@Configuration
@ComponentScan(basePackages = {"spring"},
excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = ManualBean.class),
@Filter(type = FilterType.REGEX, pattern = "spring2\\.*")
})
📕정리📘
스프링 컨테이너(AnnotationConfigApplicationContext)
- 💡객체 생성
- 스프링 설정을 참고해서 객체를 생성함
- 설정 클래스: @Configuration 애노테이션이 붙어있는 클래스 ex) AppCtx
◻@Bean애노테이션: 메서드 명 위에 -> 수동 등록 빈
◻@ComponentScan("스캔범위"): 스프링 컨테이너가 생성할 객체의 클래스를 스캔할 범위- 스캔 범위가 설정되어 있으면 그 범위를 스캔한다.
- 기본 스캔 대상에 해당하는 클래스이면 객체 생성
@Component
@Service
<특정 기능과 관련된 애노테이션>
@Configuration
@Controller
@RestController
@ControllerAdvice
@RestControllrAdvice
@Aspect
@Repository- 💡의존성 주입
- @Autowired: 의존성 자동 주입
1) 멤버 변수 위
2) setter 메서드 위
3) Optional 형태로 감싼 구조(값이 없을 경우를 대비)/ 멤버 변수, setter 메서드- @Autowired 애노테이션을 사용하지 않고 의존성 주입
- 생성자 매개변수에 의존성을 정의한 경우(주의! -> 기본 생성자는 정의 X)
: 객체 생성할때 의존성 주입을 강제한다.
- 롬복@RequiredArgsConstructor와 함께 많이 사용된다.
◻ 초기화가 반드시 필요한 멤버변수를 생성자 매개변수에 추가
1) 컨테이너 초기화
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppContext.class);
AnnotationConfigApplicationContext의 생성자를 이용해서 컨텍스트 객체를 생성하는데 이 시점에 스프링 컨테이너를 초기화한다.
스프링 컨테이너는 설정 클래스에서 정보를 읽어와 알맞은 빈 객체를 생성하고 각 빈을 연결(의존 주입)하는 작업을 수행한다.
2) 컨테이너에서 빈 객체를 구해서 사용
Greeter g = ctx.getBean("greeter", Greeter.class);
String msg = g.greet("스프링");
System.out.println(msg);
컨테이너 초기화가 완료되면 컨테이너를 사용할 수 있다.
컨테이너를 사용한다는 것은 getBean()과 같은 메서드를 이용해서 컨테이너에 보관된 빈 객체를 구한다는 것을 뜻한다.
ctx.close();
컨테이너 사용이 끝나면 컨테이너를 종료한다. 컨테이너를 종료할 때 사용하는 메서드가 close() 메서드이다.
close() 메서드는 AbstractApplicationContext 클래스에 정의되어 있다.
스프링 컨테이너의 라이프사이클에 따라 빈 객체도 자연스럽게 생성과 소멸이라는 라이프사이클을 갖는다.
- 스프링 컨테이너를 초기화할 때 스프링 컨테이너는 가장 먼저 빈 객체를 생성하고 의존을 설정한다.
- 의존 자동 주입을 통한 의존 설정이 이 시점에 수행된다.
- 모든 의존 설정이 완료되면 빈 객체의 초기화를 수행한다. 빈 객체를 초기화하기 위해 스프링은 빈 객체의 지정된 메서드를 호출한다.
- 스프링 컨테이너를 종료하면 스프링 컨테이너는 빈 객체의 소멸을 처리한다. 이때에도 지정된 메서드를 호출한다.
초기화: 객체가 완전히 생성되고 조립된 다음에 처리 할 작업을 정의하면 실행되는 단계
소멸 -> (ctx.close()- 객체 소멸): 소멸 전에 처리할 작업을 정의하면 실행되는 단계
📢스프링 컨테이너 생성시 진행되는 부분(객체 생성 -> 의존 설정 -> 초기화)
2) InitializingBean 인터페이스
빈 객체가 InitializingBean 인터페이스를 구현하면 스프링 컨테이너는 초기화 과정에서 빈 객체의 afterPropertiesSet() 메서드를 실행한다.
afterPropertiesSet 메서드: 초기화 단계시에 실행된다.

afterPropertiesSet 은 컨테이너 생성시 발생된다.
public class Ex02 {
@Test
void test1(){
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
// BoardService service = ctx.getBean(BoardService.class);
// service.write();
ctx.close();
}
}
없어도 똑같이 실행됨
3) DisposableBean 인터페이스
스프링 컨테이너는 빈 객체가 DisposableBean 인터페이스를 구현한 경우 소멸 과정에서 빈 객체의 destroy() 메서드를 실행한다.
destroy 메서드: 컨테이너에 있는 객체가 소멸되기 전에 실행
@Service
public class BoardService implements InitializingBean, DisposableBean {
public void write(){
System.out.println("글쓰기!");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet()!");
}
@Override
public void destroy() throws Exception {
//객체 소멸 직후 실행
System.out.println("destroy()!");
}
}
...
#Test
public class Ex02 {
@Test
void test1(){
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
ctx.close();
}
}
destroy는 ctx.close() 가 호출되면 close하기 전에 호출된다.
만약에 ctx.close()가 없다면 호출되지 않음
👩🏫제한 조건 -> 인터페이스 추가하는 것은 직접 만든 클래스만 가능하다.
외부에서 이용하는 클래스들을 사용해야 할 경우가 더 많을텐데 그건 어떻게 해야할까?
🔽 🔽 🔽
ex) 외부 라이브러리, 자바 JDK 기본 클래스, 스프링 프레임 워크 기본 클래스 등등
이렇게 InitializingBean 인터페이스와 DisposableBean 인터페이스를 구현할 수 없거나 이 두 인터페이스를 사용하고 싶지 않은 경우에는 스프링 설정에서 직접 메서드를 지정할 수 있다.
방법은 @Bean 태그에서 initMethod 속성과 destroyMethod 속성를 사용해서 초기화 메서드와 소멸 메서드의 이름을 지정하면 된다.


🔸BoardService2는 외부에서 제공 받은 클래스이고 변경 불가를 가정하고 있다.


@Scope("singleton"): 정의하지 않아도 기본은 싱글톤 패턴으로 관리@Scope("prototype"): 매번 조회시마다 새로운 객체를 생성
🔼갱신할때마다 새로운 객체 만들어짐

⬇
