Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4주차] 김지웅 서예원 장은채 윤지우 #3

Open
d11210920 opened this issue Jul 21, 2024 · 14 comments
Open

[4주차] 김지웅 서예원 장은채 윤지우 #3

d11210920 opened this issue Jul 21, 2024 · 14 comments

Comments

@d11210920
Copy link
Collaborator

d11210920 commented Jul 21, 2024

  • 4주차
    • 3장
    • 4~5장
    • 6~7장(2명)
@d11210920 d11210920 changed the title [4주차] 김지웅 윤지우 서예원 [4주차] 김지웅 윤지우 서예원 장은채 Jul 22, 2024
@d11210920 d11210920 changed the title [4주차] 김지웅 윤지우 서예원 장은채 [4주차] 김지웅 서예원 장은채 Jul 22, 2024
@d11210920 d11210920 changed the title [4주차] 김지웅 서예원 장은채 [4주차] 김지웅 서예원 장은채 윤지우 Jul 24, 2024
@silverchaeJ
Copy link

Chapter 6: 빈 라이프사이클과 범위

🤔빈 객체의 라이프 사이클을 알아보기 전에 스프링 컨테이너 라이프 사이클을 먼저 이해해보자


💡스프링 컨테이너는 초기화와 종료라는 라이프 사이클을 갖는다.

ex.
//컨테이너 초기화→스프링 컨테이너는 설정 클래스에서 정보를 읽어와 알맞은 빈객체 생성, 각 빈을 연결(의존 주입)함

AnnotationConfigApplicationContext ctx= new AnnotaionConfigApplicationContext(AppContext.class);

//컨테이너에서 빈 객체를 구해서 사용→컨테이너에 보관된 빈 객체를 구함

Greeter g= ctx.getBean(”greater”,Greeter.class);

String msg=g.greet(”스프링”);

System.out.println(msg);

//컨테이너 종료→빈 객체의 소멸

ctx.close();

💡스프링 컨테이너의 라이프 사이클에 따라 빈 객체도 자연스럽게 생성과 소멸이라는 라이프 사이클을 갖는다


🤔그렇다면 스프링 빈 객체의 라이프 사이클 단계는 어떻게 될까


  1. 객체 생성
  2. 의존설정
  3. 초기화
  4. 소멸

💡프링 컨테이너를 초기화 할때 스프링 컨테이너는 가장 먼저 빈 객체를 생성하고 의존 자동 주입을 통한 의존 설정이 이 시점에서 수행 된다. 빈 객체의 초기화를 수행하기 위해 빈 객체의 지정된 메소드를 호출하고 스프링 컨테이너를 종료하면 이때도 지정된 메소드를 통해 빈 객체의 소멸을 처리한다.


🤔 빈 객체의 초기화 와 소멸에 필요한 스프링 인터페이스엔 무엇이 있을까


1.org.springframework.beans.factory.lnitializingBean

→빈 객체를 생성한 뒤에 초기화 과정이 필요하면 InitializingBean 인터페이스를 상속하고 afterPropertiesSet()메서드를 알맞게 구현하면 된다.

2.org.springframework.beans.factory.DisposableBean
→빈 객체의 소멸 과정이 필요하면 DisposableBean 인터페이스를 상속하고 destory()메서드를 알맞게 구현하면 된다.


🤔모든 클래스가 InitializaingBean 인터페이스와 DisposableBean 인터페이스를 상속받아 구현할 수 있는 것은 아니다. 그렇다면 그땐 어떻게 해야할까


상황)

1.직접 구현한 클래스가 아닌 외부에서 제공받은 클래스를 스프링 빈 객체로 설정했을 때 소스 코드를 받지 못했을때

2.두 인터페이스를 사용하고 싶지 않을 때

해결)

스프링 설정에서 직접 메소드 지정
@bean 태그에서 initMethod 속성과 destoryMethod 속성을 사용해서 초기화 메서드스코프 지정 방법

🚨주의:초기화 메서드가 두 번 실행 되지 않도록 주의


🤔스프링은 다양한 빈 스코프를 제공하여 빈의 생명주기를 관리하는데 기본적으로 제공되는 스코프가 어떻게 되는가


1.싱글톤 (singleton):

  • 기본 스코프, 스프링 IoC 컨테이너당 하나의 빈 인스턴스만 생성됩니다.
  • 애플리케이션 전역에서 동일한 인스턴트를 공유합니다.

2.프로토타입(prototype):

  • 요청 시마다 새로운 빈 인스턴스를 생성합니다.
  • 각 요청에 대해 새로운 객체가 필요할 때 사용합니다.

🤔스코프 지정 방법

//”prototype”을 갖는 @scope 에노테이션을 @bean 에노테이션과 함께 사용

@Bean

@Scope(”prototype”)

public MyBean myBean(){

return new MyBean();

}

//싱글톤의 범위를 명시적으로 지정하고 싶다면 @scope 에노테이션 값으로 “singleton”을 주면됨

@@Bean(initMethod = “connect”, destroyMethod=”close”)

@Scope(”singleton”)

@Yewon2ee
Copy link

스프링5 프로그래밍 입문

Chapter 4 의존 자동 주입

@Autowired를 이용한 의존 자동 주입에 대해 알아보자
자동 주입: 의존 대상을 설정 코드에서 직접 주입하지 않고 스프링이 자동으로 의존하는 빈 객체를 주입해주는 기능

1.예제 프로젝트 준비

2. @Autowired 애노테이션을 이용한 의존 자동 주입

자동 주입 방법: 의존을 주입할 대상에 @Autowired 애노테이션을 붙여서 스프링이 자동으로 빈을 주입하도록 한다.

필드, 메서드 등에 주입할 수 있다.

필드 주입

import org.springframework.beans.factory.annotation.Autowired;

public class ChangePasswordService {

    @Autowired
    private MemberDao memberDao; 

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

설명: 이렇게 하면 ChangePasswordService가 생성될 때, 스프링이 자동으로 MemberDao 객체를 memberDao 필드에 주입한다.

if @Autowired 애노테이션을 설정한 필드에 알맞은 빈 객체가 주입되지 않는다면?

= memberDao가 주입되지 않으면?

ChangePasswordServicememberDao 필드가 null-> 암호 변경 기능을 실행 시 NullPointerException 가 나옴

if 일치하는 빈이 없다면?

@Autowired 애노테이션을 사용했지만 일치하는 빈이 없는 경우,스프링은 NoSuchBeanDefinitionException을 발생시킴. MemberDao 타입의 빈이 스프링 컨테이너에 등록되지 않았기 때문이다.

따라서 암호 변경 기능이 정상 작동함 = @Autowired 애노테이션을 붙인 필드에 실제 MemberDao 타입의 빈 객체가 잘 들어감

3. @qualifier 애노테이션을 이용한 의존 객체 선택

자동 주입 가능한 빈이 두 개 이상이면 자동 주입할 빈을 지정할 수 있는 방법이 필요 -> @qualifier 애노테이션을 사용하여 여러 빈 중에서 특정 빈을 선택할 수 있다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class ChangePasswordService {

    private final MemberDao memberDao;

    @Autowired
    public ChangePasswordService(@Qualifier("myMemberDao") MemberDao memberDao) {
        this.memberDao = memberDao; 
    }

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

@qualifier의 두 가지 사용 위치

1. 빈 설정 메서드에서 @qualifier 사용
@bean 애노테이션을 사용하여 빈을 등록할 때, @qualifier를 통해 빈의 이름을 명시할 수 있다.
예제:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Qualifier;

@Configuration
public class AppCtx {

    @Bean
    @Qualifier("memberDao1")
    public MemberDao memberDao1() {
        return new MemberDao1();
    }

    @Bean
    @Qualifier("memberDao2")
    public MemberDao memberDao2() {
        return new MemberDao2();
    }
}
 설정에서는 @Qualifier를 사용하여 빈의 이름을 지정하고,  이름으로 빈을 구분합니다.

2. 의존성 주입에서 @qualifier 사용
@Autowired와 함께 사용되는 @qualifier는 의존성 주입 시 특정 빈을 선택하는 데 사용된다. 필드 주입, 메서드 주입, 생성자 주입에서 모두 가능하다.

public class ChangePasswordService {

    @Autowired
    @Qualifier("memberDao1")
    private MemberDao memberDao;

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

-메서드 주입: 생성자 또는 세터 메서드의 파라미터에 @qualifier를 사용하여 빈을 주입가능
*맨 위 예시 참고

빈 설정 메서드에서의 사용은 빈을 등록할 때 이름을 지정하는 것이고, 의존성 주입에서의 사용은 이미 등록된 여러 빈 중에서 어떤 것을 주입할지 명시적으로 선택하는 것

3.1 빈 이름과 기본 한정자

별도의 @qualifier 애노테이션을 사용하지 않으면 기본 한정자로는 빈 이름이 사용
따라서 같은 타입의 빈이 여러 개면 @qualifier 애노테이션을 써서 한정자를 따로 명시해주자

4. 상위/하위 타입 관계와 자동 주입

상속 관계에서 발생할 수 있는 문제점:

MemberPrinter를 상속한 MemberSummaryPrinter클래스가 있다고 하자.

@Configuration
public class AppConfig {

    @Bean
    public MemberPrinter memberPrinter1() {
        return new MemberPrinter();
    }

    @Bean
    public MemberPrinter memberPrinter2() {
        return new MemberSummaryPrinter();
    }
}


public class MemberService {

    @Autowired
    private MemberPrinter memberPrinter;

   
}

겉으로는 다른 타입이여서 문제 없을 거 같지만 @Autowired를 사용하면, 실제로는 MemberSummaryPrinter클래스는 MemberPrinter에 할당될 수 있어서 스프링이 두 개의 빈 중 어느 것을 주입해야할 지 모호해지는 문제가 발생

두 가지 해결 방법:

  1. @qualifier 사용하기
public class MemberService {

    @Autowired
    @Qualifier("memberPrinter1")
    private MemberPrinter memberPrinter;

}```


2. 상속 구조 활용하기 

@Configuration
public class AppConfig {

    @Bean
    public MemberPrinter memberPrinter() {
        return new MemberSummaryPrinter();
    }
}

MemberSummaryPrinter 타입은 하나만 존재하므로 MemberSummaryPrinter 빈을 자동 주입 받도록 코드를 수정하면 자동 주입 대상이 두 개여서 발생하는 문제를 해결할 수있음.

5. @Autowired 애노테이션의 필수 여부

5.1 생성자 초기화와 필수 여부 지정 방식 동작 이해

기본값 (required=true): 빈이 필수로 주입되어야 하며, 빈이 없으면 예외 발생.

빈이 없어도 되는 경우(빈 주입이 선택적인 경우)
1.required=false 쓰기: 빈이 없어도 애플리케이션이 정상 작동하며, 빈이 없으면 필드가 null로 설정됨.

@Autowired(required=false)
private MyDependency myDependency; 

2.@nullable: 빈이 없을 경우 필드에 null이 할당됨.

import javax.annotation.Nullable;

@Autowired
@Nullable
private MyDependency myDependency;

3.@optional: 빈이 없으면 필드가 null로 설정됨.

import org.springframework.beans.factory.annotation.Optional;

@Autowired
@Optional
private MyDependency myDependency; // 빈이 없어도 null로 설정됨

6. 자동 주입과 명시적 의존 주입 간의 관계

자동 주입: @Autowired를 사용하여 스프링이 빈을 자동으로 주입한다.
명시적 주입: 수동으로 빈을 설정하거나 XML 파일을 사용하여 빈을 정의한다.

만약 명시적 주입과 자동 주입 둘다 써져있으면 자동주입으로 처리된다고 한다.
둘다 쓰면 나중에 오류 찾기 힘들다고 한다. 일부 자동주입 적용이 어려운 코드를 제외하고는 그냥 자동주입을 쓰라고 한다.

Chapter 5 컴포넌트 스캔

컴포넌트 스캔은 @Component 애노테이션을 사용하여 스프링이 자동으로 빈을 검색하여 등록하는 기능이다.

1. @component 애노테이션으로 스캔 대상 지정

import org.springframework.stereotype.Component;

@Component("muComponent")
public class MyComponent {
    
}

@component를 붙인 클래스는 스캔 대상이 된다. @component 애노테이션에 값을 주면 그 값을 빈 이름으로 사용한다.

2. @componentscan 애노테이션으로 스캔 설정

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "spring") 
public class AppConfig {
  
}

@component 애노테이션이 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정클래스에 @componentscan 애노테이션을 적용해야한다.
basePackages 속성으로 지정한 패키지 내의 빈들만 스캔하여 등록한다.

3. 예제 실행

4. 스캔 대상에서 제외하거나 포함하기

excludeFilters - 특정 클래스를 스캔에서 제외함.
FilterType -
ANNOTATION: 특정 애노테이션이 붙은 클래스를 스캔에서 제외함
ASSIGNABLE_TYPE: 특정 타입을 스캔에서 제외함

예시로 보자
1.애노테이션 필터

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class)
)
public class AppConfig {
}

2.타입 필터

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class)
)
public class AppConfig {
}

3.두 개 이상의 필터 설정

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class)
    }
)
public class AppConfig {
}

4.1 기본 스캔 대상
@component, @service, @repository, @controller 등 스프링의 스테레오타입 애노테이션이 붙은 클래스들

5. 컴포넌트 스캔에 따른 충돌 정리

5.1 빈 이름 충돌
같은 이름의 빈이 여러 개 등록되면 충돌이 발생할 수 있다. 이 경우 빈 이름을 명확히 하여 문제를 해결한다.
예시: 두 개의 @component 빈이 같은 이름을 가진 경우, 스프링은 어떤 빈을 사용할지 결정하지 못할 수 있다. 빈 이름을 명시적으로 설정하여 문제를 해결한다.

5.2 수동 등록한 빈과 충돌
XML 설정 파일에서 수동 등록한 빈과 @component로 등록된 빈이 충돌할 수 있다. 이 경우 빈의 정의를 명확히 하여 충돌을 방지한다.

예시: XML 설정 파일에서 등록한 빈과 @component로 등록된 빈 이름이 같은 경우 수동 등록한 빈이 우선되어 하나만 존재한다.

@wldnd7145

This comment was marked as resolved.

@wldnd7145

This comment was marked as resolved.

@Yewon2ee
Copy link

Yewon2ee commented Jul 26, 2024

Chapter 6: 빈 라이프사이클과 범위

🤔빈 객체의 라이프 사이클을 알아보기 전에 스프링 컨테이너 라이프 사이클을 먼저 이해해보자

💡스프링 컨테이너는 초기화와 종료라는 라이프 사이클을 갖는다.

ex.
//컨테이너 초기화→스프링 컨테이너는 설정 클래스에서 정보를 읽어와 알맞은 빈객체 생성, 각 빈을 연결(의존 주입)함

AnnotationConfigApplicationContext ctx= new AnnotaionConfigApplicationContext(AppContext.class);

//컨테이너에서 빈 객체를 구해서 사용→컨테이너에 보관된 빈 객체를 구함

Greeter g= ctx.getBean(”greater”,Greeter.class);

String msg=g.greet(”스프링”);

System.out.println(msg);

//컨테이너 종료→빈 객체의 소멸

ctx.close();

💡스프링 컨테이너의 라이프 사이클에 따라 빈 객체도 자연스럽게 생성과 소멸이라는 라이프 사이클을 갖는다

🤔그렇다면 스프링 빈 객체의 라이프 사이클 단계는 어떻게 될까

  1. 객체 생성
  2. 의존설정
  3. 초기화
  4. 소멸

💡프링 컨테이너를 초기화 할때 스프링 컨테이너는 가장 먼저 빈 객체를 생성하고 의존 자동 주입을 통한 의존 설정이 이 시점에서 수행 된다. 빈 객체의 초기화를 수행하기 위해 빈 객체의 지정된 메소드를 호출하고 스프링 컨테이너를 종료하면 이때도 지정된 메소드를 통해 빈 객체의 소멸을 처리한다.

🤔 빈 객체의 초기화 와 소멸에 필요한 스프링 인터페이스엔 무엇이 있을까

1.org.springframework.beans.factory.lnitializingBean

→빈 객체를 생성한 뒤에 초기화 과정이 필요하면 InitializingBean 인터페이스를 상속하고 afterPropertiesSet()메서드를 알맞게 구현하면 된다.

2.org.springframework.beans.factory.DisposableBean →빈 객체의 소멸 과정이 필요하면 DisposableBean 인터페이스를 상속하고 destory()메서드를 알맞게 구현하면 된다.

🤔모든 클래스가 InitializaingBean 인터페이스와 DisposableBean 인터페이스를 상속받아 구현할 수 있는 것은 아니다. 그렇다면 그땐 어떻게 해야할까

상황)

1.직접 구현한 클래스가 아닌 외부에서 제공받은 클래스를 스프링 빈 객체로 설정했을 때 소스 코드를 받지 못했을때

2.두 인터페이스를 사용하고 싶지 않을 때

해결)

스프링 설정에서 직접 메소드 지정 →@bean 태그에서 initMethod 속성과 destoryMethod 속성을 사용해서 초기화 메서드스코프 지정 방법

🚨주의:초기화 메서드가 두 번 실행 되지 않도록 주의

🤔스프링은 다양한 빈 스코프를 제공하여 빈의 생명주기를 관리하는데 기본적으로 제공되는 스코프가 어떻게 되는가

1.싱글톤 (singleton):

  • 기본 스코프, 스프링 IoC 컨테이너당 하나의 빈 인스턴스만 생성됩니다.
  • 애플리케이션 전역에서 동일한 인스턴트를 공유합니다.

2.프로토타입(prototype):

  • 요청 시마다 새로운 빈 인스턴스를 생성합니다.
  • 각 요청에 대해 새로운 객체가 필요할 때 사용합니다.

🤔스코프 지정 방법

//”prototype”을 갖는 @scope 에노테이션을 @bean 에노테이션과 함께 사용

@Bean

@Scope(”prototype”)

public MyBean myBean(){

return new MyBean();

}

//싱글톤의 범위를 명시적으로 지정하고 싶다면 @scope 에노테이션 값으로 “singleton”을 주면됨

@@Bean(initMethod = “connect”, destroyMethod=”close”)

@Scope(”singleton”)

1.```java사용해서 코드처럼 적어주세욯 ㅎ
2.빈 객체의 초기화와 소멸에 필요한 인터페이스에 대한 설명 부분이랑 그아래부분들도 예시같은거 적어주시거나 좀 더 자세히 설명해주시면 좋을 것 같습니당 잘 안와닿아요ㅠㅠㅠ

@silverchaeJ
Copy link

스프링5 프로그래밍 입문

Chapter 4 의존 자동 주입

@Autowired를 이용한 의존 자동 주입에 대해 알아보자
자동 주입: 의존 대상을 설정 코드에서 직접 주입하지 않고 스프링이 자동으로 의존하는 빈 객체를 주입해주는 기능

1.예제 프로젝트 준비

2. @Autowired 애노테이션을 이용한 의존 자동 주입

자동 주입 방법: 의존을 주입할 대상에 @Autowired 애노테이션을 붙여서 스프링이 자동으로 빈을 주입하도록 한다.

필드, 메서드 등에 주입할 수 있다.

필드 주입

import org.springframework.beans.factory.annotation.Autowired;

public class ChangePasswordService {

    @Autowired
    private MemberDao memberDao; 

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

설명: 이렇게 하면 ChangePasswordService가 생성될 때, 스프링이 자동으로 MemberDao 객체를 memberDao 필드에 주입한다.

if @Autowired 애노테이션을 설정한 필드에 알맞은 빈 객체가 주입되지 않는다면?

= memberDao가 주입되지 않으면?

ChangePasswordServicememberDao 필드가 null-> 암호 변경 기능을 실행 시 NullPointerException 가 나옴

if 일치하는 빈이 없다면?

@Autowired 애노테이션을 사용했지만 일치하는 빈이 없는 경우,스프링은 NoSuchBeanDefinitionException을 발생시킴. MemberDao 타입의 빈이 스프링 컨테이너에 등록되지 않았기 때문이다.

따라서 암호 변경 기능이 정상 작동함 = @Autowired 애노테이션을 붙인 필드에 실제 MemberDao 타입의 빈 객체가 잘 들어감

3. @qualifier 애노테이션을 이용한 의존 객체 선택

자동 주입 가능한 빈이 두 개 이상이면 자동 주입할 빈을 지정할 수 있는 방법이 필요 -> @qualifier 애노테이션을 사용하여 여러 빈 중에서 특정 빈을 선택할 수 있다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class ChangePasswordService {

    private final MemberDao memberDao;

    @Autowired
    public ChangePasswordService(@Qualifier("myMemberDao") MemberDao memberDao) {
        this.memberDao = memberDao; 
    }

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

@qualifier의 두 가지 사용 위치

1. 빈 설정 메서드에서 @qualifier 사용
@bean 애노테이션을 사용하여 빈을 등록할 때, @qualifier를 통해 빈의 이름을 명시할 수 있다.
예제:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Qualifier;

@Configuration
public class AppCtx {

    @Bean
    @Qualifier("memberDao1")
    public MemberDao memberDao1() {
        return new MemberDao1();
    }

    @Bean
    @Qualifier("memberDao2")
    public MemberDao memberDao2() {
        return new MemberDao2();
    }
}
 설정에서는 @Qualifier를 사용하여 빈의 이름을 지정하고,  이름으로 빈을 구분합니다.

2. 의존성 주입에서 @qualifier 사용
@Autowired와 함께 사용되는 @qualifier는 의존성 주입 시 특정 빈을 선택하는 데 사용된다. 필드 주입, 메서드 주입, 생성자 주입에서 모두 가능하다.

public class ChangePasswordService {

    @Autowired
    @Qualifier("memberDao1")
    private MemberDao memberDao;

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

-메서드 주입: 생성자 또는 세터 메서드의 파라미터에 @qualifier를 사용하여 빈을 주입가능 *맨 위 예시 참고

빈 설정 메서드에서의 사용은 빈을 등록할 때 이름을 지정하는 것이고, 의존성 주입에서의 사용은 이미 등록된 여러 빈 중에서 어떤 것을 주입할지 명시적으로 선택하는 것

3.1 빈 이름과 기본 한정자

별도의 @qualifier 애노테이션을 사용하지 않으면 기본 한정자로는 빈 이름이 사용
따라서 같은 타입의 빈이 여러 개면 @qualifier 애노테이션을 써서 한정자를 따로 명시해주자

4. 상위/하위 타입 관계와 자동 주입

상속 관계에서 발생할 수 있는 문제점:

MemberPrinter를 상속한 MemberSummaryPrinter클래스가 있다고 하자.

@Configuration
public class AppConfig {

    @Bean
    public MemberPrinter memberPrinter1() {
        return new MemberPrinter();
    }

    @Bean
    public MemberPrinter memberPrinter2() {
        return new MemberSummaryPrinter();
    }
}


public class MemberService {

    @Autowired
    private MemberPrinter memberPrinter;

   
}

겉으로는 다른 타입이여서 문제 없을 거 같지만 @Autowired를 사용하면, 실제로는 MemberSummaryPrinter클래스는 MemberPrinter에 할당될 수 있어서 스프링이 두 개의 빈 중 어느 것을 주입해야할 지 모호해지는 문제가 발생

두 가지 해결 방법:

  1. @qualifier 사용하기
public class MemberService {

    @Autowired
    @Qualifier("memberPrinter1")
    private MemberPrinter memberPrinter;

}```


2. 상속 구조 활용하기 

@Configuration
public class AppConfig {

    @Bean
    public MemberPrinter memberPrinter() {
        return new MemberSummaryPrinter();
    }
}

MemberSummaryPrinter 타입은 하나만 존재하므로 MemberSummaryPrinter 빈을 자동 주입 받도록 코드를 수정하면 자동 주입 대상이 두 개여서 발생하는 문제를 해결할 수있음.

5. @Autowired 애노테이션의 필수 여부

5.1 생성자 초기화와 필수 여부 지정 방식 동작 이해

기본값 (required=true): 빈이 필수로 주입되어야 하며, 빈이 없으면 예외 발생.

빈이 없어도 되는 경우(빈 주입이 선택적인 경우) 1.required=false 쓰기: 빈이 없어도 애플리케이션이 정상 작동하며, 빈이 없으면 필드가 null로 설정됨.

@Autowired(required=false)
private MyDependency myDependency; 

2.@nullable: 빈이 없을 경우 필드에 null이 할당됨.

import javax.annotation.Nullable;

@Autowired
@Nullable
private MyDependency myDependency;

3.@optional: 빈이 없으면 필드가 null로 설정됨.

import org.springframework.beans.factory.annotation.Optional;

@Autowired
@Optional
private MyDependency myDependency; // 빈이 없어도 null로 설정됨

6. 자동 주입과 명시적 의존 주입 간의 관계

자동 주입: @Autowired를 사용하여 스프링이 빈을 자동으로 주입한다.
명시적 주입: 수동으로 빈을 설정하거나 XML 파일을 사용하여 빈을 정의한다.

만약 명시적 주입과 자동 주입 둘다 써져있으면 자동주입으로 처리된다고 한다. 둘다 쓰면 나중에 오류 찾기 힘들다고 한다. 일부 자동주입 적용이 어려운 코드를 제외하고는 그냥 자동주입을 쓰라고 한다.

Chapter 5 컴포넌트 스캔

컴포넌트 스캔은 @Component 애노테이션을 사용하여 스프링이 자동으로 빈을 검색하여 등록하는 기능이다.

1. @component 애노테이션으로 스캔 대상 지정

import org.springframework.stereotype.Component;

@Component("muComponent")
public class MyComponent {
    
}

@component를 붙인 클래스는 스캔 대상이 된다. @component 애노테이션에 값을 주면 그 값을 빈 이름으로 사용한다.

2. @componentscan 애노테이션으로 스캔 설정

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "spring") 
public class AppConfig {
  
}

@component 애노테이션이 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정클래스에 @componentscan 애노테이션을 적용해야한다.
basePackages 속성으로 지정한 패키지 내의 빈들만 스캔하여 등록한다.

3. 예제 실행

4. 스캔 대상에서 제외하거나 포함하기

excludeFilters - 특정 클래스를 스캔에서 제외함.
FilterType -
ANNOTATION: 특정 애노테이션이 붙은 클래스를 스캔에서 제외함
ASSIGNABLE_TYPE: 특정 타입을 스캔에서 제외함

예시로 보자 1.애노테이션 필터

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class)
)
public class AppConfig {
}

2.타입 필터

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class)
)
public class AppConfig {
}

3.두 개 이상의 필터 설정

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class)
    }
)
public class AppConfig {
}

4.1 기본 스캔 대상
@component, @service, @repository, @controller 등 스프링의 스테레오타입 애노테이션이 붙은 클래스들

5. 컴포넌트 스캔에 따른 충돌 정리

5.1 빈 이름 충돌
같은 이름의 빈이 여러 개 등록되면 충돌이 발생할 수 있다. 이 경우 빈 이름을 명확히 하여 문제를 해결한다.
예시: 두 개의 @component 빈이 같은 이름을 가진 경우, 스프링은 어떤 빈을 사용할지 결정하지 못할 수 있다. 빈 이름을 명시적으로 설정하여 문제를 해결한다.

5.2 수동 등록한 빈과 충돌
XML 설정 파일에서 수동 등록한 빈과 @component로 등록된 빈이 충돌할 수 있다. 이 경우 빈의 정의를 명확히 하여 충돌을 방지한다.

예시: XML 설정 파일에서 등록한 빈과 @component로 등록된 빈 이름이 같은 경우 수동 등록한 빈이 우선되어 하나만 존재한다.

스프링5 프로그래밍 입문

Chapter 4 의존 자동 주입

@Autowired를 이용한 의존 자동 주입에 대해 알아보자
자동 주입: 의존 대상을 설정 코드에서 직접 주입하지 않고 스프링이 자동으로 의존하는 빈 객체를 주입해주는 기능

1.예제 프로젝트 준비

2. @Autowired 애노테이션을 이용한 의존 자동 주입

자동 주입 방법: 의존을 주입할 대상에 @Autowired 애노테이션을 붙여서 스프링이 자동으로 빈을 주입하도록 한다.

필드, 메서드 등에 주입할 수 있다.

필드 주입

import org.springframework.beans.factory.annotation.Autowired;

public class ChangePasswordService {

    @Autowired
    private MemberDao memberDao; 

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

설명: 이렇게 하면 ChangePasswordService가 생성될 때, 스프링이 자동으로 MemberDao 객체를 memberDao 필드에 주입한다.

if @Autowired 애노테이션을 설정한 필드에 알맞은 빈 객체가 주입되지 않는다면?

= memberDao가 주입되지 않으면?

ChangePasswordServicememberDao 필드가 null-> 암호 변경 기능을 실행 시 NullPointerException 가 나옴

if 일치하는 빈이 없다면?

@Autowired 애노테이션을 사용했지만 일치하는 빈이 없는 경우,스프링은 NoSuchBeanDefinitionException을 발생시킴. MemberDao 타입의 빈이 스프링 컨테이너에 등록되지 않았기 때문이다.

따라서 암호 변경 기능이 정상 작동함 = @Autowired 애노테이션을 붙인 필드에 실제 MemberDao 타입의 빈 객체가 잘 들어감

3. @qualifier 애노테이션을 이용한 의존 객체 선택

자동 주입 가능한 빈이 두 개 이상이면 자동 주입할 빈을 지정할 수 있는 방법이 필요 -> @qualifier 애노테이션을 사용하여 여러 빈 중에서 특정 빈을 선택할 수 있다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class ChangePasswordService {

    private final MemberDao memberDao;

    @Autowired
    public ChangePasswordService(@Qualifier("myMemberDao") MemberDao memberDao) {
        this.memberDao = memberDao; 
    }

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

@qualifier의 두 가지 사용 위치

1. 빈 설정 메서드에서 @qualifier 사용
@bean 애노테이션을 사용하여 빈을 등록할 때, @qualifier를 통해 빈의 이름을 명시할 수 있다.
예제:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Qualifier;

@Configuration
public class AppCtx {

    @Bean
    @Qualifier("memberDao1")
    public MemberDao memberDao1() {
        return new MemberDao1();
    }

    @Bean
    @Qualifier("memberDao2")
    public MemberDao memberDao2() {
        return new MemberDao2();
    }
}
 설정에서는 @Qualifier를 사용하여 빈의 이름을 지정하고,  이름으로 빈을 구분합니다.

2. 의존성 주입에서 @qualifier 사용
@Autowired와 함께 사용되는 @qualifier는 의존성 주입 시 특정 빈을 선택하는 데 사용된다. 필드 주입, 메서드 주입, 생성자 주입에서 모두 가능하다.

public class ChangePasswordService {

    @Autowired
    @Qualifier("memberDao1")
    private MemberDao memberDao;

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

-메서드 주입: 생성자 또는 세터 메서드의 파라미터에 @qualifier를 사용하여 빈을 주입가능 *맨 위 예시 참고

빈 설정 메서드에서의 사용은 빈을 등록할 때 이름을 지정하는 것이고, 의존성 주입에서의 사용은 이미 등록된 여러 빈 중에서 어떤 것을 주입할지 명시적으로 선택하는 것

3.1 빈 이름과 기본 한정자

별도의 @qualifier 애노테이션을 사용하지 않으면 기본 한정자로는 빈 이름이 사용
따라서 같은 타입의 빈이 여러 개면 @qualifier 애노테이션을 써서 한정자를 따로 명시해주자

4. 상위/하위 타입 관계와 자동 주입

상속 관계에서 발생할 수 있는 문제점:

MemberPrinter를 상속한 MemberSummaryPrinter클래스가 있다고 하자.

@Configuration
public class AppConfig {

    @Bean
    public MemberPrinter memberPrinter1() {
        return new MemberPrinter();
    }

    @Bean
    public MemberPrinter memberPrinter2() {
        return new MemberSummaryPrinter();
    }
}


public class MemberService {

    @Autowired
    private MemberPrinter memberPrinter;

   
}

겉으로는 다른 타입이여서 문제 없을 거 같지만 @Autowired를 사용하면, 실제로는 MemberSummaryPrinter클래스는 MemberPrinter에 할당될 수 있어서 스프링이 두 개의 빈 중 어느 것을 주입해야할 지 모호해지는 문제가 발생

두 가지 해결 방법:

  1. @qualifier 사용하기
public class MemberService {

    @Autowired
    @Qualifier("memberPrinter1")
    private MemberPrinter memberPrinter;

}```


2. 상속 구조 활용하기 

@Configuration
public class AppConfig {

    @Bean
    public MemberPrinter memberPrinter() {
        return new MemberSummaryPrinter();
    }
}

MemberSummaryPrinter 타입은 하나만 존재하므로 MemberSummaryPrinter 빈을 자동 주입 받도록 코드를 수정하면 자동 주입 대상이 두 개여서 발생하는 문제를 해결할 수있음.

5. @Autowired 애노테이션의 필수 여부

5.1 생성자 초기화와 필수 여부 지정 방식 동작 이해

기본값 (required=true): 빈이 필수로 주입되어야 하며, 빈이 없으면 예외 발생.

빈이 없어도 되는 경우(빈 주입이 선택적인 경우) 1.required=false 쓰기: 빈이 없어도 애플리케이션이 정상 작동하며, 빈이 없으면 필드가 null로 설정됨.

@Autowired(required=false)
private MyDependency myDependency; 

2.@nullable: 빈이 없을 경우 필드에 null이 할당됨.

import javax.annotation.Nullable;

@Autowired
@Nullable
private MyDependency myDependency;

3.@optional: 빈이 없으면 필드가 null로 설정됨.

import org.springframework.beans.factory.annotation.Optional;

@Autowired
@Optional
private MyDependency myDependency; // 빈이 없어도 null로 설정됨

6. 자동 주입과 명시적 의존 주입 간의 관계

자동 주입: @Autowired를 사용하여 스프링이 빈을 자동으로 주입한다.
명시적 주입: 수동으로 빈을 설정하거나 XML 파일을 사용하여 빈을 정의한다.

만약 명시적 주입과 자동 주입 둘다 써져있으면 자동주입으로 처리된다고 한다. 둘다 쓰면 나중에 오류 찾기 힘들다고 한다. 일부 자동주입 적용이 어려운 코드를 제외하고는 그냥 자동주입을 쓰라고 한다.

Chapter 5 컴포넌트 스캔

컴포넌트 스캔은 @Component 애노테이션을 사용하여 스프링이 자동으로 빈을 검색하여 등록하는 기능이다.

1. @component 애노테이션으로 스캔 대상 지정

import org.springframework.stereotype.Component;

@Component("muComponent")
public class MyComponent {
    
}

@component를 붙인 클래스는 스캔 대상이 된다. @component 애노테이션에 값을 주면 그 값을 빈 이름으로 사용한다.

2. @componentscan 애노테이션으로 스캔 설정

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "spring") 
public class AppConfig {
  
}

@component 애노테이션이 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정클래스에 @componentscan 애노테이션을 적용해야한다.
basePackages 속성으로 지정한 패키지 내의 빈들만 스캔하여 등록한다.

3. 예제 실행

4. 스캔 대상에서 제외하거나 포함하기

excludeFilters - 특정 클래스를 스캔에서 제외함.
FilterType -
ANNOTATION: 특정 애노테이션이 붙은 클래스를 스캔에서 제외함
ASSIGNABLE_TYPE: 특정 타입을 스캔에서 제외함

예시로 보자 1.애노테이션 필터

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class)
)
public class AppConfig {
}

2.타입 필터

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class)
)
public class AppConfig {
}

3.두 개 이상의 필터 설정

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class)
    }
)
public class AppConfig {
}

4.1 기본 스캔 대상
@component, @service, @repository, @controller 등 스프링의 스테레오타입 애노테이션이 붙은 클래스들

5. 컴포넌트 스캔에 따른 충돌 정리

5.1 빈 이름 충돌
같은 이름의 빈이 여러 개 등록되면 충돌이 발생할 수 있다. 이 경우 빈 이름을 명확히 하여 문제를 해결한다.
예시: 두 개의 @component 빈이 같은 이름을 가진 경우, 스프링은 어떤 빈을 사용할지 결정하지 못할 수 있다. 빈 이름을 명시적으로 설정하여 문제를 해결한다.

5.2 수동 등록한 빈과 충돌
XML 설정 파일에서 수동 등록한 빈과 @component로 등록된 빈이 충돌할 수 있다. 이 경우 빈의 정의를 명확히 하여 충돌을 방지한다.

예시: XML 설정 파일에서 등록한 빈과 @component로 등록된 빈 이름이 같은 경우 수동 등록한 빈이 우선되어 하나만 존재한다.

1.@qualifier의 두 가지 사용 위치 설명 부분에서 마지막 설명 내용 코드 밖으로 빼주면 너무 좋을거같아용
2.상속구조 설명 보충해주세용
3. 4-스캔대상에서 제외하거나 포함하기 부분에서 어떤 예시인지 더 명확히 표시해주는게 좋을거같아요 어떤 부분을 설명하는지 구분이 잘 안갑니당

@wldnd7145

This comment was marked as resolved.

@silverchaeJ
Copy link

silverchaeJ commented Jul 26, 2024

자바 스프링.pptx

1.빈 객체의 용도에 대해서 예시를 들어서 설명한 부분 너무 좋았는데 모두가 이해하기 쉬운 c++내용이 아니면 이해하기 어려울거같아용
2.커스텀메서드 내용 부분이 책 내용과 다른 부분이 있어서 보충설명과 책에 없는 내용은 빼도 좋을 거 같아용
3.빈객체의 생성과 관리 범위쪽 저도 중요하지 않다고 느끼긴 했는데 여기서 싱글톤 부분 더 보충 설명해주면 좋을거 같아용 + 방법같은 부분도 예시를 들어서 설명해주면 좋을 거 같습니다
4.개인적인 생각입니다! 프록시 설명전에 AOP에 관해 먼저설명해주면 어떨까요..? 바로 프록시가 나와버리니깐 AOP와 관계성을 잘 못잇겠어용..
화이팅입니다:)

@Yewon2ee
Copy link

Yewon2ee commented Jul 26, 2024

자바 스프링.pptx

  1. md로 바꿔주세요ㅠㅠ
  2. 개념만 적혀있는게 많은데 이해가 잘 안돼요 예시를 들면 좋을 것 같습니다ㅠㅠ
  3. 빈 객체에 대한 정의 수정보완해시면 좋을 것 같아요
  4. 프록시랑 AOP 부분도 추가보완해주시면 좋을 것 같아요.
  5. 책에 있는 내용들 중에서 빠져있는 부분들이랑 7장 마저 다뤄주시면 좋을 것 같습니다 주의할 점 이런거도 빠져있어요 ㅜ ㅅ ㅜ

@wldnd7145
Copy link

Chapter 7 : AOP 프로그래밍 요약

이 장에서 다룰 내용

프록시와 AOP
스프링 AOP 구현

AOP(Aspect-Oriented Programming)란
프로그램의 핵심 기능과 공통 기능을 분리하여 개발하는 프로그래밍 기법.
AOP는 주로 중복 코드를 제거하고, 공통 기능을 중앙집중식으로 관리할 수 있도록 한다.
이는 코드의 가독성, 유지보수성, 재사용성을 높이는 데 큰 도움을 줌.

AOP를 이용하면 공통 기능을 핵심 로직에서 분리하여 별도의 클래스(Aspect)로 작성하고,
핵심 로직을 변경하지 않고, 핵심 로직에 공통 기능을 적용할 수 있다.
AOP는 핵심 기능을 깔끔하게 유지하면서 부가 기능을 추가할 수 있다.

1. 프로젝트 준비

메이븐 프로젝트의 pom.xml 파일에 다음 의존성을 추가

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
</dependencies>

spring-context 모듈을 의존 대상으로 추가하면 spring-aop 모듈도 함께 포함됨.
aspectjweaver 모듈은 AOP 설정에 필요한 애노테이션을 제공.

Calculator 인터페이스 정의:

package chap07;

public interface Calculator {
    long factorial(long num){
        long result = 1;
        for(long i = 1)
    }
}

양의 정수 n의 계승은 n!으로 표현하며, 이는 1부터 n까지 숫자의 곱. 예를 들어, 4!은 4 * 3 * 2 * 1 = 24

Calculator 인터페이스 구현 (ImpeCalculator):

package chap07;

public class ImpeCalculator implements Calculator {
    @Override
    public long factorial(long num) {
        long result = 1;
        for (long i = 1; i <= num; i++) {
            result *= i;
        }
        return result;
    }
}

Calculator 인터페이스 구현 (RecCalculator):

package chap07;

public class RecCalculator implements Calculator {
    @Override
    public long factorial(long num) {
        if (num == 0) return 1;
        else return num * factorial(num - 1);
    }
}

2. 프록시와 AOP

실행 시간 측정 코드 삽입 방법

1. ImpeCalculator:

public class ImpeCalculator implements Calculator {
    @Override
    public long factorial(long num) {
        long start = System.currentTimeMillis();
        long result = 1;
        for (int i = 1; i <= num; i++) {
            result *= i;
        }
        long end = System.currentTimeMillis();
        System.out.printf("ImpeCalculator.factorial(%d) 실행 시간 = %d\n", num, (end - start));
        return result;
    }
}

문제점 :

실행 시간 측정 코드가 비즈니스 로직과 섞여 있어 코드가 복잡해짐
중복 코드가 발생함.
예를 들어, 다른 메서드에도 동일한 기능을 적용하려면 동일한 코드를 반복해야 한다.
=> 실행 시간을 나노 초 단위로 구해야 한다면 모든 측정 코드르 수정해야 함

2. RecCalculator:

public class RecCalculator implements Calculator {
    @Override
    public long factorial(long num) {
        long start = System.currentTimeMillis();
        try {
            if (num == 0) return 1;
            return num * factorial(num - 1);
        } finally {
            long end = System.currentTimeMillis();
            System.out.printf("RecCalculator.factorial(%d) 실행 시간 = %d\n", num, (end - start));
        }
    }
}

메서드 실행 전후에 시간을 측정하여 실행 시간을 출력한다.

문제점 :

재귀 호출로 인해 실행 시간이 여러 번 출력된다.
예를 들어 factorial(2)를 호출하면 내부적으로 factorial(1)과 factorial(0)도 호출되어 메시지가 여러 번 출력됨.
코드 중복이 발생하며, 비즈니스 로직과 실행 시간 측정 코드가 섞여 있다.

3. 실행 시간 측정을 위한 별도 코드 작성:

ImpeCalculator impeCal = new ImpeCalculator();
long start1 = System.currentTimeMillis();
long fourFactorial1 = impeCal.factorial(4);
long end1 = System.currentTimeMillis();
System.out.printf("ImpeCalculator.factorial(4) 실행 시간 = %d\n", (end1 - start1));

RecCalculator recCal = new RecCalculator();
long start2 = System.currentTimeMillis();
long fourFactorial2 = recCal.factorial(4);
long end2 = System.currentTimeMillis();
System.out.printf("RecCalculator.factorial(4) 실행 시간 = %d\n", (end2 - start2));

이것도 문제가 있음

문제점:

실행 시간을 나노초 단위로 측정하려면 모든 측정 코드를 수정해야 한다.

해결법 =======>

프록시 객체를 이용한 해결

ExeTimeCalculator:

package chap07;

public class ExeTimeCalculator implements Calculator {
    private Calculator delegate;

    public ExeTimeCalculator(Calculator delegate) {
        this.delegate = delegate;
    }

    @Override
    public long factorial(long num) {
        long start = System.nanoTime();
        long result = delegate.factorial(num);
        long end = System.nanoTime();
        System.out.printf("%s.factorial(%d) 실행 시간 = %d\n", delegate.getClass().getSimpleName(), num, (end - start));
        return result;
    }
}

장점 :

실행 시간 측정 코드가 비즈니스 로직과 분리되어 코드의 가독성이 향상된다.
실행 시간 측정 기능을 여러 객체에 쉽게 적용할 수 있음 => 한 번에 변경가능
코드 중복을 제거하여 유지보수성이 향상된다
실행 시간을 나노초 단위로 측정할 수 있다

ExeTimeCalculator 사용 예시 ===>

ImpeCalculator impeCal = new ImpeCalculator();
ExeTimeCalculator calculator = new ExeTimeCalculator(impeCal);
long result = calculator.factorial(4);

ExeTimeCalculator 클래스는 Calculator 인터페이스를 구현하고 있다.
또한 다른 Calculator 객체를 전달받아 실행 시간을 측정한다.

=> 코드 중복을 제거
=> 기존 코드를 변경하지 않고 실행 시간을 측정 가능

프록시의 역할 :

핵심 기능의 실행을 다른 객체에 위임하고 공통 기능을 제공하는 객체를 프록시라고 함.

  • 프록시의 특징 :
  • 핵심 기능을 구현하지 않음.
  • 여러 객체에 공통 기능을 적용할 수 있음.

프록시와 데코레이터의 차이

프록시: 접근 제어에 초점을 맞추며, 핵심 기능을 구현하지 않고 다른 객체에 위임

데코레이터: 기능 추가와 확장에 초점을 맞추며, 기존 기능에 추가적인 기능을 제공

예시

public class MainProxy {
    public static void main(String[] args) {
        ExeTimeCalculator ttCal1 = new ExeTimeCalculator(new ImpeCalculator());
        System.out.println(ttCal1.factorial(20));
        
        ExeTimeCalculator ttCal2 = new ExeTimeCalculator(new RecCalculator());
        System.out.println(ttCal2.factorial(20));
    }
}

실행 결과 :

ImpeCalculator.factorial(20) 실행 시간 = 3123
RecCalculator.factorial(20) 실행 시간 = 3570

프록시 적용의 이점:

  • 기존 코드를 변경하지 않고 실행 시간을 출력할 수 있다.
  • 실행 시간을 구하는 코드의 중복을 제거할 수 있디.
  • 나노초 대신 밀리초를 사용하려면 ExeTimeCalculator 클래스만 변경하면 된다.

결론 ===>

  • ImpeCalculator와 RecCalculator는 팩토리얼 계산이라는 핵심 기능을 구현
  • ExeTimeCalculator는 실행 시간 측정이라는 공통 기능을 구현해 공통 기능과 핵심 기능을 분리
  • 이렇게 공통 기능 구현과 핵심 기능 구현을 분리하는 것이 AOP의 핵심!

2.1 AOP

AOP란?

Aspect Oriented Programming 의 약자로

여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법.
핵심 기능과 공통 기능의 구현을 분리함으로써, 핵심 기능을 구현한 코드의 수정 없이 공통 기능을 적용할 수 있다.
'Aspect Oriented Programming'을 우리말로 변역하면 '관점 지향 프로그래밍'

=> 쉽게 말해, 객체 지향 프로그래밍 (OOP)의 상위 호환

AOP의 기본 개념:

핵심 기능에 공통 기능을 삽입하는 것
즉, 핵심 기능의 코드를 수정하지 않으면서 공통 기능의 구현을 추가하는 것이 AOP.

공통 기능 삽입 방법

  • 컴파일 시점: 소스 코드를 컴파일하기 전에 공통 기능 코드를 소스에 삽입.
  • 클래스 로딩 시점: 클래스 로딩 시 바이트 코드에 공통 기능을 삽입.
  • 런타임 시점: 프록시 객체를 생성하여 공통 기능을 삽입.

스프링 AOP는 프록시를 이용한 세 번째 방법을 주로 사용

스프링 AOP는 프록시 객체를 자동으로 생성
따라서 ExeTimeCalculator 클래스처럼 상위 타입의 인터페이스를 상속받은 프록시 클래스를 직접 구현할 필요가 없다

=> 공통 기능을 구현한 클래스만 알맞게 구현하면 됨

AOP 주요 용어

  • Advice
    언제 공통 관심 기능을 핵심 로직에 적용할지를 정의
    예 : 메서드 호출 전 트랜잭션 시작

  • Joinpoint
    Advice를 적용 가능한 지점.
    예: 메서드 호출, 필드 값 변경

  • Pointcut
    Joinpoint의 부분 집합으로, 실제 Advice가 적용되는 지점.
    정규 표현식이나 AspectJ 문법을 이용하여 정의

  • Weaving
    Advice를 핵심 로직 코드에 적용하는 것

  • Aspect
    여러 객체에 공통으로 적용되는 기능
    예: 트랜잭션, 보안 등

2.2 Advice의 종류

  • Before Advice
    대상 객체의 메서드 호출 전에 공통 기능을 실행

  • After Returning Advice
    대상 객체의 메서드가 예외 없이 실행된 후에 공통 기능을 실행

  • After Throwing Advice
    대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능을 실행

  • After Advice
    예외 발생 여부와 상관없이 대상 객체의 메서드 실행 후 공통 기능을 실행 (try-catch-finally의 finally 블록과 비슷)

  • Around Advice
    대상 객체의 메서드 실행 전, 후 또는 예외 발생 시점에 공통 기능을 실행

  • Around Advice를 가장 보편적으로 사용 함

이유 ===>

대상 객체의 메서드를 실행하기 전/후, 예외 발생 시점 등 다양한 시점에 원하는 기능을 삽입할 수 있음
캐시 기능, 성능 모니터링 기능과 같은 Aspect를 구현할 때 주로 이용

3. 스프링 AOP 구현

스프링 AOP를 이용해 공통 기능을 구현하고 적용하는 방법

  1. Aspect로 사용할 클래스에 @aspect 애노테이션을 붙인다
  2. @pointcut 애노테이션으로 공통 기능을 적용할 Pointcut을 정의
  3. 공통 기능을 구현한 메서드에 @around 애노테이션을 적용

3.1 @aspect, @pointcut, @around를 이용한 AOP 구현

개발자는 공통 기능을 제공하는 Aspect 구현 클래스를 만듦
자바 설정을 이용해 Aspect를 어디에 적용할지 설정
Aspect는 @aspect 애노테이션을 이용해 구현하며, 프록시는 스프링 프레임워크가 알아서 만들어준다.

Aspect 구현 예제:

@Aspect
public class ExeTimeAspect {

    @Pointcut("execution(public * chap07..*(..))")
    private void publicTarget() {}

    @Around("publicTarget()")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();
        try {
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long finish = System.nanoTime();
            Signature sig = joinPoint.getSignature();
            System.out.printf("%s.%s(%s) 실행 시간: %d ns\n",
                joinPoint.getTarget().getClass().getSimpleName(),
                sig.getName(), Arrays.toString(joinPoint.getArgs()), (finish - start));
        }
    }
}
  • @aspect 애노테이션을 적용한 클래스는 Advice와 Pointcut을 함께 제공한다
  • @pointcut 애노테이션은 공통 기능을 적용할 대상을 설정
  • @around 애노테이션은 Around Advice를 설정
  • 여기서 publicTarget() 메서드에 정의한 Pointcut에 공통 기능을 적용

3.2 ProceedingJoinPoint의 메서드

Around Advice에서 사용할 공통 기능 메서드는
대부분 파라미터로 전달받은 ProceedingJoinPoint의 proceed() 메서드만 호출하면 된다

ProceedingJoinPoint 인터페이스의 주요 메서드:

Signature getSignature(): 호출되는 메서드에 대한 정보를 구함
Object getTarget(): 대상 객체를 구함
Object[] getArgs(): 파라미터 목록을 구함

Signature 인터페이스의 주요 메서드:

String getName(): 호출되는 메서드의 이름을 구함
String toLongString(): 호출되는 메서드를 완전하게 표현한 문장을 구함
String toShortString(): 호출되는 메서드를 축약해서 표현한 문장을 구함

@wldnd7145
Copy link

wldnd7145 commented Jul 26, 2024

4. 프록시 생성 방식

예제: 프록시 생성 방식

1. 기본 코드 수정

  • 기존 코드에서는 Calculator 인터페이스 타입으로 빈을 가져옴.
// 기존 코드
Calculator cal = ctx.getBean("calculator", Calculator.class);

=> 이를 RecCalculator 타입으로 변경

// 수정된 코드
RecCalculator cal = ctx.getBean("calculator", RecCalculator.class);

2. 빈 설정 코드

  • AppCtx 설정 클래스에서 빈을 생성할 때 RecCalculator 클래스를 사용하고 있다.
@Bean
public Calculator calculator() {
    return new RecCalculator();
}

3. 문제(예외) 발생

  • 코드를 수정한 후, MainAspect 클래스를 실행하면 다음과 같은 예외가 발생
Exception in thread "main" org.springframework.beans.factory.BeanNotOfRequiredTypeException: 
Bean named 'calculator' is expected to be of type 
'chap07.RecCalculator' but was actually of type 'com.sun.proxy.$Proxy17'
  • 예외 메시지를 보면 getBean() 메서드에 사용한 타입이 RecCalculator인데,
    실제로 반환된 객체는 $Proxy17 타입이라는 것을 알 수 있다.

4. 프록시 객체 생성 원리

스프링은 AOP를 적용할 때,
실제 객체가 인터페이스를 상속받고 있으면 해당 인터페이스를 기반으로 프록시 객체를 생성

RecCalculator 클래스는 Calculator 인터페이스를 상속받기 때문에,
프록시 객체는 Calculator 인터페이스를 상속받은 형태로 생성 됨

프록시 객체의 계층 구조

            <<Interface>>
              Calculator
                  Δ
                  |
        ---------------------
        |                   |
    $Proxy17          RecCalculator

5. 해결 방법

  • 빈 객체가 인터페이스를 상속하는 대신,
    클래스 자체를 이용하여 프록시를 생성하도록 설정하면 됨
    => 이를 위해 @EnableAspectJAutoProxy 애노테이션의 proxyTargetClass 속성을 true로 지정
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppCtx {
    @Bean
    public Calculator calculator() {
        return new RecCalculator();
    }
}

6. 변경된 코드 실행

코드 복사
RecCalculator cal = ctx.getBean("calculator", RecCalculator.class);

위 설정을 적용한 후, 다음과 같이 getBean() 메서드에 RecCalculator 클래스를 사용하면 빈 객체를 가져올 수 있음

4.1 Execution 명시자 표현식

execution(수식어패턴? 리턴타입패턴 클래스이름패턴? 메서드이름패턴(파라미터패턴))
  • 이런식으로 사용
  • 수식어패턴: 생략 가능,
    public, protected 등이 올 수 있음.
  • 리턴타입패턴: 메서드의 리턴 타입을 지정
  • 클래스이름패턴: 클래스 이름을 패턴으로 지정
  • 메서드이름패턴: 메서드 이름을 패턴으로 지정
  • 파라미터패턴: 매칭될 파라미터를 지정

=> 각 패턴은 *를 이용하여 모든 값을 표현할 수 있으며, ..(점 두 개)를 사용하여 0개 이상을 의미

  • 리턴 타입이 void이고, 메서드 이름이 set으로 시작하며, 파라미터가 0개 이상인 메서드 호출
execution(public void set*(..))
  • chap07 패키지의 타입에 속한, 파라미터가 없는 모든 메서드 호출
execution(* chap07.*.*())
  • chap07 패키지와 그 하위 패키지의 모든 클래스에 있는 파라미터가 0개 이상인 모든 메서드에 호출 & 적용
execution(* chap07..*.*(..))
  • 리턴 타입이 Long이고 Calculator 클래스의 factorial 메서드에 호출 & 적용
execution(Long chap07.Calculator.factorial(..))
  • 메서드 이름이 get으로 시작하고, 파라미터가 하나인 메서드에 호출 & 적용
execution(* get*(*))
  • 메서드 이름이 get으로 시작하고, 파라미터가 두 개인 메서드에 호출 & 적용
execution(* get*(*, *))
  • 메서드 이름이 read로 시작하고, 첫 번째 파라미터가 Integer 타입이며, 한 개 이상의 파라미터를 갖는 메서드에 호출 & 적용
execution(* read*(Integer, ..))

4.2 Advice 적용 순서

예제

@Aspect
public class CacheAspect {
    private Map<Long, Object> cache = new HashMap<>();

    @Pointcut("execution(public * chap07..*(long))")
    public void cacheTarget() {}

    @Around("cacheTarget()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        Long num = (Long) joinPoint.getArgs()[0];
        if (cache.containsKey(num)) {
            System.out.printf("CacheAspect: Cache에서 구함 [%d]\n", num);
            return cache.get(num);
        }
        Object result = joinPoint.proceed();
        cache.put(num, result);
        System.out.printf("CacheAspect: Cache에 추가 [%d]\n", num);
        return result;
    }
}
  • 위 CacheAspect 클래스는 간단한 캐시를 구현한 공통 기능을 제공하는 Aspect

    • execute() 메서드는 메서드 호출 전 캐시를 확인
    • 존재하면 캐시에서 값을 반환하며
    • 존재하지 않으면 실제 메서드를 호출하여 결과를 캐시에 저장
  • 설정 클래스에 여러 Aspect 추가
    새로운 Aspect를 구현
    => 설정 클래스에 두 개의 Aspect 추가 가능

@Configuration
@EnableAspectJAutoProxy
public class AppCtxWithCache {
    @Bean
    public CacheAspect cacheAspect() {
        return new CacheAspect();
    }

    @Bean
    public ExeTimeAspect exeTimeAspect() {
        return new ExeTimeAspect();
    }

    @Bean
    public Calculator calculator() {
        return new RecCalculator();
    }
}
public class MainAspectWithCache {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtxWithCache.class);
        Calculator cal = ctx.getBean("calculator", Calculator.class);

        System.out.println(cal.factorial(7));
        System.out.println(cal.factorial(7));
        System.out.println(cal.factorial(5));
        System.out.println(cal.factorial(5));

        ctx.close();
    }
}

실행 결과 분석

첫 번째 factorial(7) 실행: ExeTimeAspect와 CacheAspect가 모두 적용
두 번째 factorial(7) 실행: CacheAspect만 적용
첫 번째 factorial(5) 실행: ExeTimeAspect와 CacheAspect가 모두 적용
두 번째 factorial(5) 실행: CacheAspect만 적용

RecCalculator.factorial(7) 실행 시간: 26775 ns
CacheAspect: Cache에 추가 [7]
CacheAspect: Cache에서 구함 [7]
RecCalculator.factorial(5) 실행 시간: 6247 ns
CacheAspect: Cache에 추가 [5]
CacheAspect: Cache에서 구함 [5]

Advice 적용 순서

CacheAspect
    |
    V
ExeTimeAspect
    |
    V
RecCalculator

@order 애노테이션을 이용한 적용 순서 지정

  • Advice 적용 순서를 직접 지정하려면 @order 애노테이션을 사용
    @order 애노테이션의 값이 작을수록 먼저 적용 됨
@Aspect
@Order(1)
public class ExeTimeAspect {}

@Aspect
@Order(2)
public class CacheAspect {}

예를 들어 위 코드처럼 두 Aspect 클래스에 @order 애노테이션을 적용했다고 가정

  • 순서가 아래와 같이 바뀜
ExeTimeAspect
    |
    V
CacheAspect
    |
    V
RecCalculator

변경 후 실행 결과

  • 실행 결과 또한 아래와 같이 바뀜
CacheAspect: Cache에 추가 [7]
RecCalculator.factorial(7) 실행 시간: 326207 ns
CacheAspect: Cache에서 구함 [7]
RecCalculator.factorial(7) 실행 시간: 96836 ns

4.3 @around의 Pointcut 설정과 @pointcut 재사용

@around 애노테이션에 직접 execution 명시자 지정
@pointcut 애노테이션을 사용하지 않고,
@around 애노테이션에 직접 execution 명시자를 지정할 수 있음.

예시

@Aspect
public class CacheAspect {
    @Around("execution(public * chap07..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        // Advice logic
        Long num = (Long) joinPoint.getArgs()[0];
        if (cache.containsKey(num)) {
            System.out.printf("CacheAspect: Cache에서 구함 [%d]\n", num);
            return cache.get(num);
        }
        Object result = joinPoint.proceed();
        cache.put(num, result);
        System.out.printf("CacheAspect: Cache에 추가 [%d]\n", num);
        return result;
    }

    private Map<Long, Object> cache = new HashMap<>();
}

공통 Pointcut 재사용

  • 같은 Pointcut을 여러 Advice에서 사용하려면 @pointcut 애노테이션을 사용하여 공통 Pointcut을 정의하고 재사용할 수 있다
@Aspect
public class ExeTimeAspect {
    @Pointcut("execution(public * chap07..*(..))")
    private void publicTarget() {}

    @Around("publicTarget()")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();
        Object result = joinPoint.proceed();
        long finish = System.nanoTime();
        System.out.printf("%s.%s(%s) 실행 시간: %d ns\n",
            joinPoint.getTarget().getClass().getSimpleName(),
            joinPoint.getSignature().getName(),
            Arrays.toString(joinPoint.getArgs()),
            (finish - start));
        return result;
    }
}

다른 클래스에서 공통 Pointcut 사용하기

  • 다른 클래스에서 @pointcut을 정의한 메서드를 사용하려면,
    @pointcut 메서드를 public으로 변경 후,
    완전한 클래스 이름을 포함하여 @around 애노테이션에서 참조 가능
@Aspect
public class ExeTimeAspect {
    @Pointcut("execution(public * chap07..*(..))")
    public void publicTarget() {}
}
@Aspect
public class CacheAspect {
    @Around("aspect.ExeTimeAspect.publicTarget()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        // Advice logic
        Long num = (Long) joinPoint.getArgs()[0];
        if (cache.containsKey(num)) {
            System.out.printf("CacheAspect: Cache에서 구함 [%d]\n", num);
            return cache.get(num);
        }
        Object result = joinPoint.proceed();
        cache.put(num, result);
        System.out.printf("CacheAspect: Cache에 추가 [%d]\n", num);
        return result;
    }

    private Map<Long, Object> cache = new HashMap<>();
}

별도 클래스에서 공통 Pointcut 정의 및 재사용

  • 여러 Aspect에서 공통으로 사용할 Pointcut이 있다면,
    별도의 클래스로 분리하여 Pointcut을 정의 후, 각 Aspect 클래스에서 해당 Pointcut 사용 가능
public class CommonPointcut {
    @Pointcut("execution(public * chap07..*(..))")
    public void commonTarget() {}
}
@Aspect
public class CacheAspect {
    private Map<Long, Object> cache = new HashMap<>();

    @Around("CommonPointcut.commonTarget()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        Long num = (Long) joinPoint.getArgs()[0];
        if (cache.containsKey(num)) {
            System.out.printf("CacheAspect: Cache에서 구함 [%d]\n", num);
            return cache.get(num);
        }
        Object result = joinPoint.proceed();
        cache.put(num, result);
        System.out.printf("CacheAspect: Cache에 추가 [%d]\n", num);
        return result;
    }
}
@Aspect
public class ExeTimeAspect {
    @Around("CommonPointcut.commonTarget()")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();
        Object result = joinPoint.proceed();
        long finish = System.nanoTime();
        System.out.printf("%s.%s(%s) 실행 시간: %d ns\n",
            joinPoint.getTarget().getClass().getSimpleName(),
            joinPoint.getSignature().getName(),
            Arrays.toString(joinPoint.getArgs()),
            (finish - start));
        return result;
    }
}

CommonPointcut 클래스는 공통으로 사용할 Pointcut을 정의
CacheAspect와 ExeTimeAspect 클래스는 CommonPointcut 클래스의 Pointcut을 참조하여 공통 Pointcut을 재사용

=> Pointcut 관리 용이
=> 코드의 중복 감소

@silverchaeJ
Copy link

silverchaeJ commented Jul 28, 2024

Chapter 6: 빈 라이프사이클과 범위

🤔빈 객체의 라이프 사이클을 알아보기 전에 스프링 컨테이너 라이프 사이클을 먼저 이해해보자


💡스프링 컨테이너는 초기화와 종료라는 라이프 사이클을 갖는다.

ex.
    //컨테이너 초기화: 스프링 컨테이너는 설정 클래스를 읽어 빈 객체를 생성하고, 각 빈을 연결(의존 주입)함

    AnnotationConfigApplicationContext ctx= new AnnotaionConfigApplicationContext(AppContext.class);

    //컨테이너에서 빈 객체를 구해서 사용 : 컨테이너에 보관된 빈 객체를 구함

    Greeter g= ctx.getBean(”greater”,Greeter.class);

    String msg=g.greet(”스프링”);

    System.out.println(msg);

    //컨테이너 종료: 빈 객체의 소멸

    ctx.close();

💡스프링 컨테이너는 설정 클래스(AppContext)에서 정보를 읽어와 빈 객체를 생성하고 의존성을 주입한 뒤, 빈 객체를 사용하게 한다. 컨테이너가 종료되면 빈 객체도 소멸된다.


🤔그렇다면 스프링 빈 객체의 라이프 사이클 단계는 어떻게 될까


  1. 객체 생성
  2. 의존설정
  3. 초기화
  4. 소멸

💡스프링 컨테이너는 먼저 빈 객체를 생성하고, 의존 자동 주입을 통해 의존성을 설정한다. 그런 다음 빈 객체의 초기화를 수행하기 위해 지정된 초기화 메서드를 호출한다. 컨테이너 종료 시에도 지정된 소멸 메서드를 호출해 빈 객체를 소멸시킨다


🤔 빈 객체의 초기화 와 소멸에 필요한 스프링 인터페이스엔 무엇이 있을까


1.org.springframework.beans.factory.lnitializingBean

:초기화 작업이 필요한 빈 객체는 InitializingBean 인터페이스를 구현하고 afterPropertiesSet() 메서드를 오버라이드하여 초기화 작업을 수행한다.

public class MyBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        // 초기화 작업
    }
}

2.org.springframework.beans.factory.DisposableBean
: 소멸 작업이 필요한 빈 객체는 DisposableBean 인터페이스를 구현하고 destroy() 메서드를 오버라이드하여 소멸 작업을 수행한다.

public class MyBean implements DisposableBean {
   @Override
   public void destroy() throws Exception {
       // 소멸 작업
   }
}

🤔 인터페이스를 사용하지 않을경우 (커스텀 메서드)


모든 클래스가 InitializingBean과 DisposableBean 인터페이스를 구현할 수 있는 것은 아니다. 이러한 상황에는 다른 방법을 사용해야 한다:

1.직접 구현한 클래스가 아닌 외부에서 제공받은 클래스를 스프링 빈 객체로 설정했을 때 소스 코드를 수정할 수 없는 경우
두 인터페이스를 사용하고 싶지 않을 때

2.스프링 설정에서 직접 메소드 지정
@bean 태그에서 initMethod 속성과 destoryMethod 속성을 사용해서 초기화 메서드스코프 지정 방법

해결 방법: 스프링 설정에서 메서드 지정
스프링 설정에서 직접 초기화 메서드와 소멸 메서드를 지정할 수 있다. @bean 애너테이션의 initMethod와 destroyMethod 속성을 사용하여 초기화 및 소멸 메서드를 설정할 수 있다.

@Configuration
public class AppContext {
    
    @Bean(initMethod = "init", destroyMethod = "cleanup")
    public MyBean myBean() {
        return new MyBean();
    }
}

'initMethod' 속성으로 초기화 메서드를, destroyMethod 속성으로 소멸 메서드를 지정할 수 있다.

🚨 주의: 초기화 메서드가 두 번 실행되지 않도록 주의해야 한다. 이는 InitializingBean 인터페이스와 initMethod 속성을 함께 사용할 때 발생할 수 있다.


🤔다양한 빈 스코


스프링은 다양한 빈 스코프를 제공하여 빈의 생명주기를 관리한다. 기본적으로 제공되는 스코프는 다음과 같다:

1.싱글톤 (singleton):

-기본 스코프.
-스프링 IoC 컨테이너당 하나의 빈 인스턴스만 생성된다.
-애플리케이션 전역에서 동일한 인스턴스를 공유한다.

Client client1 =ctx. getBean("client", Client.class);
CLient client2 = ctx.getBean("client",Client.class);

2.프로토타입(prototype):

-요청 시마다 새로운 빈 인스턴스를 생성한다.
-각 요청에 대해 새로운 객체가 필요할 때 사용한다.


🤔스코프 지정 방법

프로토타입 스코프

"prototype" 스코프를 갖는 빈을 정의하려면 @scope 애너테이션을 @bean 애너테이션과 함께 사용한다.

    @Bean

    @Scope(”prototype”)

    public MyBean myBean(){

    return new MyBean();

    }

싱글톤 스코프

싱글톤 스코프를 명시적으로 지정하려면 @scope 애너테이션 값으로 "singleton"을 지정한다.

    @@Bean(initMethod = “connect”, destroyMethod=”close”)

    @Scope(”singleton”)

@silverchaeJ
Copy link

Chapter3 스프링DI

의존이란?


:의존은 변경에 의해 영향을 받는 관계를 의미한다.

(아래의 예시를 들어 설명)

import java.time.LocalDateTime;

public class MemberRegisterService{
      private MemberDao memberDao = new MemberDao();

      public void regist(RegisterRequest req){
             //이메일로 화원 데이터(Member)조회
             Member member=memberDao.selectByEmail(req.getEmail());
             if(member !=nul){
                //같은 이메일을 가진 회원이 이미 존재하면 익셉션 발생
                throw new DuplicateMemberException("dup email "+ req.getEmail());
              }
             //같은 이메일을 가진 회원이 존재하지 않으면  DB에 삽입
             Member newMember= new Member( req.getEmail(), req.getPassWord(),req.getName(),LocalDateTime.now());
             memberDao.insert(newMember);
         }
}

->MemberDao의 insert()메서드의 이름을 insertMember()로 변경하면 이 메서드를 사용하는 MemberRegisterService 클래스의 소스코드도 함께 변경된다. 이렇게 변경에 따른 영향이 전파되는 관계를 '의존' 이라고 표현한다.


DI를 통한 의존 처리


:DI의 주요 형태는 의존하는 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식

...
public class MemberRegisterService{
      private MemberDao memberDao;

      public MemberRegisterService(MemberDao memberdao){
             this.memberDao=memberDao;
     }
...
}

->생성자를 통해 MemberResiterService 가 의존하고 있는 MemberDao객체를 주입받은 것이다.

🚨DI를 적용한 결과 MemberRegisterService 클래스를 사용하는 코드는 다음과 같이 MemberRegisterService객체를 생성할때 생성자에 MemberDao객체를 전달해야한다.

MemberDao dao=new MemberDao(); //의존 객체를 생성자를 통해 주입
MemberRegisterService svc=new MemberRegisterService(dao);

🤔 객체를 생성하는 부분의 코드가 길어지고 복잡해졌다.

근데 직접 의존 객체를 생성하면 되는데 굳이 생성자를 통해서 의존하는 객체를 주입하는걸까?
->변경의 유연함이다 (아래설명참고)


DI와 의존 객체 변경의 유연함


DI를 사용하면 의존 객체를 변경하는 작업이 매우 유연해진다.

.코드 수정없아 의존객체 교체

  • DI를 사용하면 코드 수정 없이 설정만 변경하여 다른 구현체로 교체할 수 있다.

-예를 들어, 데이터 저장소를 메모리 기반에서 데이터베이스로 변경하거나, 로깅 구현체를 콘솔 로깅에서 파일 로깅으로 변경할 수 있다.


객체 조립기


:main메서드에서 의존 대상 객체를 생성하고 주입하는 방법 대신 객체를 생성하고 의존 객체를 주입한다는것
==서로 다른 두객체를 조립한다

다시 말해 개별적으로 존재하는 객체들이 서로 협력할 수 있도록 관계를 설정

객체 조립기의 역할

:객체 조립기는 애플리케이션의 여러 구성 요소를 생성하고, 이들간의 의존성을 설정합니다.

이를 통해 각각의 독립적인 객체들이 유기적으로 연결되어 동작 할 수 있도록 하며 객체 간의 결합도를 낮추고 재사용성을 높이는것이 중요

객체 조립기 사용 예제

//MemberRegisterService객체와 ChangePasswordService 객체에 대한 의존 주입
//다음의 코드는 Assemble 클래스를 사용하는 코드

private staic Assemble assemble =new Assemble();
//Assemble객체를 생성 
...
MemberRegisterService regSvc = assemble.getMemberRegisterService();
//get 메서드를 이용해서 필요한 객체를 구하고 그 객체 사용
RegisterRequest req=new RegisterRequest();
req.setEmail(arg[1]);
...
private static void processChangeCommand(String [] arg){
        if(arg.length !=4){
           printHelp();
           return;
        }
       ChangePasswordService changePwSvc= assemble.getChangePassWordService();
...

"제목 없음"

<위 사진은 조립기가 생성한 객체의 연결 관계>

-Assemble 객체를 생성하는 시점에서 사용할 객체가 모두 생성되고 Assemble은 자신이 생성하고 조립한 객체를 리턴하는 메서드를 제공한다.

Assemble가 제공하는 메서드를 이용해서 필요한 객체를 구하고 그 객체를 사요하는것은 전형적인 Assemble 사용법이다

@Yewon2ee
Copy link

Yewon2ee commented Jul 28, 2024

Chapter 4 의존 자동 주입

@Autowired를 이용한 의존 자동 주입에 대해 알아보자
자동 주입: 의존 대상을 설정 코드에서 직접 주입하지 않고 스프링이 자동으로 의존하는 빈 객체를 주입해주는 기능

1.예제 프로젝트 준비

2. @Autowired 애노테이션을 이용한 의존 자동 주입

자동 주입 방법: 의존을 주입할 대상에 @Autowired 애노테이션을 붙여서 스프링이 자동으로 빈을 주입하도록 한다.

필드, 메서드,생성자,세터 등에 주입할 수 있다.

필드 주입

import org.springframework.beans.factory.annotation.Autowired;

public class ChangePasswordService {

    @Autowired
    private MemberDao memberDao; 

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

설명: 이렇게 하면 ChangePasswordService가 생성될 때, 스프링이 자동으로 MemberDao 객체를 memberDao 필드에 주입한다.

if @Autowired 애노테이션을 설정한 필드에 알맞은 빈 객체가 주입되지 않는다면?

= memberDao가 주입되지 않으면?

ChangePasswordServicememberDao 필드가 null-> 암호 변경 기능을 실행 시 NullPointerException 가 나옴

if 일치하는 빈이 없다면?

@Autowired 애노테이션을 사용했지만 일치하는 빈이 없는 경우,스프링은 예외를 발생시킴. MemberDao 타입의 빈이 스프링 컨테이너에 등록되지 않았기 때문이다.

따라서 암호 변경 기능이 정상 작동함 = @Autowired 애노테이션을 붙인 필드에 실제 MemberDao 타입의 빈 객체가 잘 들어감

3. @qualifier 애노테이션을 이용한 의존 객체 선택

자동 주입 가능한 빈이 두 개 이상이면 자동 주입할 빈을 지정할 수 있는 방법이 필요 -> @qualifier 애노테이션을 사용하여 여러 빈 중에서 특정 빈을 선택할 수 있다.

@qualifier의 두 가지 사용 위치

1. 빈 설정 메서드에서 @qualifier 사용
@bean 애노테이션을 사용하여 빈을 등록할 때, @qualifier를 통해 빈의 이름을 명시할 수 있다.
예제:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Qualifier;

@Configuration
public class AppCtx {

    @Bean
    @Qualifier("memberDao1")
    public MemberDao memberDao1() {
        return new MemberDao1();
    }

    @Bean
    @Qualifier("memberDao2")
    public MemberDao memberDao2() {
        return new MemberDao2();
    }
}
 설정에서는 @Qualifier를 사용하여 빈의 이름을 지정하고,  이름으로 빈을 구분합니다.

2. 의존성 주입에서 @qualifier 사용
@Autowired와 함께 사용되는 @qualifier는 의존성 주입 시 특정 빈을 선택하는 데 사용된다. 필드 주입, 메서드 주입, 생성자 주입에서 모두 가능하다.

public class ChangePasswordService {

    @Autowired
    @Qualifier("memberDao1")
    private MemberDao memberDao;

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

-메서드 주입: 생성자 또는 세터 메서드의 파라미터에 @qualifier를 사용하여 빈을 주입가능

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class ChangePasswordService {

   private final MemberDao memberDao;

    @Autowired
    public ChangePasswordService(@Qualifier("myMemberDao") MemberDao memberDao) {
       this.memberDao = memberDao; 
    }

    public void changePassword(String userId, String newPassword) {
        memberDao.updatePassword(userId, newPassword);
    }
}

빈 설정 메서드에서의 사용은 빈을 등록할 때 이름을 지정하는 것이고, 의존성 주입에서의 사용은 이미 등록된 여러 빈 중에서 어떤 것을 주입할지 명시적으로 선택하는 것

3.1 빈 이름과 기본 한정자

별도의 @qualifier 애노테이션을 사용하지 않으면 기본 한정자로는 빈 이름이 사용
따라서 같은 타입의 빈이 여러 개면 @qualifier 애노테이션을 써서 한정자를 따로 명시해주자

4. 상위/하위 타입 관계와 자동 주입

상속 관계에서 발생할 수 있는 문제점:

MemberPrinter를 상속한 MemberSummaryPrinter클래스가 있다고 하자.

@Configuration
public class AppConfig {

    @Bean
    public MemberPrinter memberPrinter1() {
        return new MemberPrinter();
    }

    @Bean
    public MemberSummaryPrinter memberPrinter2() {
        return new MemberSummaryPrinter();
    }
}


public class MemberService {

    @Autowired
    private MemberPrinter memberPrinter;

   
}

겉으로는 다른 타입이여서 문제 없을 거 같지만 @Autowired를 사용하면, 실제로는 MemberSummaryPrinter클래스는 MemberPrinter의 자식클래스여서 MemberPrinter에 할당될 수 있어서 스프링이 두 개의 빈 중 어느 것을 주입해야할 지 모호해지는 문제가 발생

두 가지 해결 방법:

  1. @qualifier 사용하기
public class MemberService {

    @Autowired
    @Qualifier("memberPrinter1")
    private MemberPrinter memberPrinter;

}
  1. 상속 구조 활용하기
@Configuration
public class AppConfig {

    @Bean
    public MemberSummaryPrinter memberSummaryPrinter() {
        return new MemberSummaryPrinter();
    }
}

MemberSummaryPrinter 타입은 하나만 존재하므로 MemberSummaryPrinter 빈을 자동 주입 받도록 코드를 수정하면 자동 주입 대상이 두 개여서 발생하는 문제를 해결할 수있음.

5. @Autowired 애노테이션의 필수 여부

5.1 생성자 초기화와 필수 여부 지정 방식 동작 이해

기본값 (required=true): 빈이 필수로 주입되어야 하며, 빈이 없으면 예외 발생.

빈이 없어도 되는 경우(빈 주입이 선택적인 경우)
1.required=false : 이렇게 쓰면 빈이 없어도 애플리케이션이 정상 작동한다. 대신 빈이 없으면 해당 메서드는 호출되지 않음

@Autowired(required=false)
private MyDependency myDependency; 

2.@nullable:빈이 있으면 빈 전달하고, 빈이 없을 경우 필드에 null이 할당됨. 상 메서드는 호출됨

import javax.annotation.Nullable;

@Autowired
@Nullable
private MyDependency myDependency;

3.자동 주입 대상 타입이 Optional인 경우: 일치하는 빈 있으면 해당 빈 값 갖는 Optional을 인자로 전달 없으면 값 없는 Optional을 인자로 전달

@Autowired
public void setDateFormatter(Optional</DateTimeFormatter>formatterOpt){
	if(formatterOpt.isPresent()){
  		this.dateTimeFormatter = formatterOpt.get();
  } else{
  		this.dateTimeFormatter = null;
  }
}

6. 자동 주입과 명시적 의존 주입 간의 관계

자동 주입: @Autowired를 사용하여 스프링이 빈을 자동으로 주입한다.
명시적 주입: 수동으로 빈을 설정하거나 XML 파일을 사용하여 빈을 정의한다.

만약 명시적 주입과 자동 주입 둘다 써져있으면 자동주입으로 처리된다고 한다.
둘다 쓰면 나중에 오류 찾기 힘들다고 한다. 일부 자동주입 적용이 어려운 코드를 제외하고는 그냥 자동주입을 쓰라고 한다.

Chapter 5 컴포넌트 스캔

컴포넌트 스캔은 @Component 애노테이션을 사용하여 스프링이 자동으로 빈을 검색하여 등록하는 기능이다.

1. @component 애노테이션으로 스캔 대상 지정

import org.springframework.stereotype.Component;

@Component("muComponent")
public class MyComponent {
    
}

@component를 붙인 클래스는 스캔 대상이 된다. @component 애노테이션에 값을 주면 그 값을 빈 이름으로 사용한다.

2. @componentscan 애노테이션으로 스캔 설정

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "spring") 
public class AppConfig {
  
}

@component 애노테이션이 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정클래스에 @componentscan 애노테이션을 적용해야한다.
basePackages 속성으로 지정한 패키지 내의 빈들만 스캔하여 등록한다.

3. 예제 실행

4. 스캔 대상에서 제외하거나 포함하기

excludeFilters 속성을 사용하면 특정 대상을 자동등록 대상에서 제외할 수있다.

예시로 보자
1.애노테이션 필터
특정 애노테이션이 붙은 타입을 제외함

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class)
)
public class AppConfig {
}

2.타입 필터
특정 타입 제외

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class)
)
public class AppConfig {
}

3.두 개 이상의 필터 설정

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.example",
    excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomAnnotation.class),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class)
    }
)
public class AppConfig {
}

4.정규표현식 필터:
spring으로 시작하고 Dao로 끝나는 특 패턴을 사용하여 클래스를 스캔에서 제외함

@ComponentScan(basePackages = {"spring"},
 excludeFilters = @Fiter(type = FilterType.REGEX,pattern = "spring\\..*Dao"))

4.1 기본 스캔 대상
@component, @service, @repository, @controller 등 스프링의 스테레오타입 애노테이션이 붙은 클래스들

5. 컴포넌트 스캔에 따른 충돌 정리

5.1 빈 이름 충돌
같은 이름의 빈이 여러 개 등록되면 충돌이 발생할 수 있다. 이 경우 빈 이름을 명확히 하여 문제를 해결한다.
예시: 두 개의 @component 빈이 같은 이름을 가진 경우, 스프링은 어떤 빈을 사용할지 결정하지 못할 수 있다. 빈 이름을 명시적으로 설정하여 문제를 해결한다.

5.2 수동 등록한 빈과 충돌
XML 설정 파일에서 수동 등록한 빈과 @component로 등록된 빈이 충돌할 수 있다. 이름이 같은 경우는 수동으로 이름을 등록한 쪽이 살아남는다.. 이름이 다른 경우는 둘 다 생성되니까 잘 표시해서 잘 골라쓰자.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants