코딩하는 털보

Spring Framework - 2 본문

IT Study/Spring Framework

Spring Framework - 2

이정인 2020. 12. 18. 18:56

IoC (Inversion of Control)

Spring의 대표적인 특징 중 하나.

어떤 객체가 사용하는 의존 객체를 직접 만들어서 사용하는게 아니라, 주입(DI)받아서 사용하는 것.

Spring IoC Container
: 애플리케이션 컴포넌트의 중앙 저장소.

ApplicationContext
: IoC 컨테이너의 핵심적인 인터페이스.
IoC 컨테이너의 최상위 인터페이스인 BeanFactory를 상속받아 여러 기능이 추가됨.

  • 메시지 소스 처리 (i18n)

  • 이벤트 발행

  • 리소스 로딩

    IoC 컨테이너에 객체를 생성하고 생성된 객체들(Bean)의 의존성을 관리해준다.
    이해만 해둘뿐, 딱히 이 인터페이스에 대해 직접 보거나 사용할 일은 없는 듯.
    ClassPathXmlApplicationContext : XML 설정 파일 사용
    AnnotationConfigApplicationContext : 자바 설정 파일 사용 (@Configuration)

Bean
: Spring IoC 컨테이너가 관리하는 객체.
Class를 Bean으로 등록하려면 Component Scan을 사용하거나
또는 직접 XML이나 자바 설정 파일(@Configuration)에 등록해야 한다.

IoC 컨테이너에 등록되는 Bean들의 특징

  • 의존성 관리, 특히 테스트에서 가짜 객체(@Mock)를 활용한 단위테스트에 용이하다.
  • 스코프 : 기본적으로 싱글톤 스코프로 등록이된다. (or 프로토타입)
    싱글톤 스코프로 등록되는 클래스는 애플리케이션이 구동되는 초기에 Bean으로 생성된다.
  • 라이프사이클 인터페이스

Component Scanning
: Annotation 처리 프로세서 역할을 하는 @ComponentScan에 의하여
모든 @Component Annotation이 붙은 Class들을 Bean으로 등록한다.

@Component
    @Repository
    @Service
    @Controller

참고로 @ComponentScan은 기본적으로 이 어노테이션이 붙은 클래스가 위치한 곳 부터 Component Scanning을 실시한다.
또는 특정 위치에서 부터 Scanning을 하도록 설정할수도 있고 Filter 옵션을 통해 어떤 클래스를 스캔할 지 또는 하지 않을 지 정할 수도 있다.

직접 등록하기

@Configuration
public class Config() {
    @Bean
    public String test() {
        return "test";
    }   

    @Bean
    public BookRepository bookRepository() {
        return new BookRepository();
    }

    @Bean
    public BookService bookService {
        return new BookService(bookRespsitory());
    }
}

function을 이용한 직접 등록

public static void main(String[] args) {
    new SpringApplicationBuilder()
        .sources(Demospring51Application.class)
        .initializers((ApplicationContextInitializer<GenericApplicationContext>)
            applicationContext -> {
                applicationContext.registerBean(MyBean.class);
            })
        .run(args);
}

이 방법은 리플랙션이나 프록시를 사용하지 않기 때문에 애플리케이션 구동 시 성능상의 이점이 있다지만..
ComponentScan을 대체하기에는 쓰기 불편해서 좋은 방법은 아닌 것 같다.

Bean을 꺼내어 쓰는 방법
: @Autowired 또는 @Inject 또는 ApplicationContext.getBean()으로 직접 꺼내기
Bean으로 등록된 Class만 Bean을 꺼내어 쓸 수 있다.

의존성 주입(Dependency Injection)
: @Autowired 또는 @Inject를 사용하는데,
예외로 생성자가 오직 하나이면서 Bean을 파라미터로 받는다면 @Autowired를 생략하고도 DI를 자동으로 해준다.
@Autowired/@Inject를 붙인다면 생성자/필드/Setter에 붙이게 된다.

@Autowired에 대하여…

어노테이션을 setter에 달려있고 의존성의 Bean이 없을 때,
setter임에도 불구하고 생각과 달리 다른 경우와 마찬가지로 객체생성이 불가능할 수 있는데
Spring이 해당 의존성을 위해 Bean을 찾기 때문이다.
이를 피하기 위해서는 @Autowired(required = false)로 설정하여 의존성 주입이 안된 상태로 생성할 수 있다.
필드에 어노테이션을 다는 경우도 비슷함.

그리고 의존성 타입의 Bean이 여러가지 일 경우에 객체생성이 안될 수 있는데,
이때는 주입받으려는 Bean을 @Primary로 마킹하거나,

@Repository @Primary
public class PrimaryBookRepository implements BookRepository {
}

해당 타입의 모든 Bean을 주입 받거나

@Autowired
List<BookRepository> bookrepositories;

@Qualifier("beanName")으로 특정 Bean을 주입받는다.

@Autowired @Qualifier("sampleBookRepository")
BookRepository bookrepository;

BeanPostProcessor
: 빈 라이프 사이클 인터페이스이다.
빈 인스턴스의 생성 전후에 부가적인 작업을 할 수 있다.
특히, AutowiredAnnotationBeanPostProcessor의 postProcessorBeforeInitialization 메서드로 인해
빈 인스턴스 생성 전에 @Autowired 어노테이션을 처리한다.

BeanFactoryPostProcessor
: 다른 모든 Bean들을 만들기 이전에 수행된다.
특히, BeanFactoryPostProcessor를 구현한 ConfigurationClassPostProcessor는 @ComponentScan 어노테이션을 처리한다.

빈의 스코프에 대하여…

빈의 스코프는 기본적으로 싱글톤이고, 그 외에 프로토타입이 있다.
프로토타입

  • Request
  • Session
  • WebSocket

@Component @Scope("prototype")
위의 어노테이션처리된 Bean은 받아 올 때마다 새로운 인스턴스가 생성된다.

참고로 프로토타입 빈이 싱글톤 빈을 의존하는 것은 문제없지만,
싱글톤 빈이 의존하는 프로토타입 빈은 의도한 것처럼 새로운 인스턴스가 생성되지 않는다는 문제가 있다.
이 문제를 해결하는 방법으로

  • Proxy mode
    프로토타입 빈을 Proxy 빈으로 감싸서 다른 빈이 이 프로토타입 빈을 참조할 때 Proxy를 거쳐서 참조하도록
    이때, Proxy 빈은 해당 프로토타입 빈을 상속하며, 참조하는 빈은 Proxy 빈을 참조한다.
##for class
@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
##for interface
@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
  • Object-Provider
@Autowired
ObjectProvider<Proto> proto;
  • Provider

ApplicationContext의 기능 Environment…

EnvironmentCapable
: 프로파일과 프로퍼티를 다루는 인터페이스이다.

프로파일
: 빈들의 그룹, Environment가 활성화할 프로파일을 확인하고 설정한다.
환경에따라 사용할 빈을 구분할 때 사용된다. (ex 프로덕션에서는 A 빈, 테스트환경에서는 B 빈을 사용)

프로파일 정의하기

  • 클래스에 정의
    test 프로파일로 애플리케이션을 실행할 때 사용될 Java Configuration
@Configuration @Profile("test")

proc 프로파일이 아닌 애플리케이션을 실행할 때 사용될 Component

@Component @Profile("!proc")
  • Bean 생성 메서드에 정의
@Bean @Profile("test")

@Profile에는 표현식으로 !(not), &(and), |(or) 가능.

프로파일 지정하기

  • JVM 시스템 프로퍼티 [-Dspring.profiles.avtive=”test,A,B,…”]
    == intelliJ의 Run/Debug Configuration의 Active profiles

  • @ActiveProfiles (테스트용)

프로퍼티
: 다양한 방법으로 정의할 수 있는 설정 값, Environment가 프로퍼티 소스를 설정하고 프로퍼티 값을 가져온다.
프로퍼티는 우선순위가 있다.(계층형이다.)
1.ServletConfig
1.ServletContext
1.JNDI (java:comp/env/)
1.JVM 시스템 프로퍼티 (-Dkey="value")
1.JVM 시스템 환경 변수 (OS 환경 변수)

Environment를 통해 프로퍼티 추가하기

@PropertySource("classpath:/app.properties")

app.properties 파일을 참조한다.
참고로 스프링부트에서는 application.properties라는 기본 프로퍼티 소스를 지원한다.

ApplicationContext의 기능 MessageSource…

MessageSource
: i18n(국제화) 기능을 제공하는 인터페이스이다.

MessageSource의 getMessage로 참조할 수 있다.
Locale에 따라서 다른 메세지를 보여준다.

    @Autowired
    MessageSource messageSource;

    public void run() {
        System.out.println(messageSource.getMessage("code1"), Locale.KOREA);
    }

스프링부트에서는 messages로 시작하는 소스들을 자동으로 참조한다.
messages.properties
messages_ko_KR.properties

리로딩 기능이 있는 메시지 소스를 @Bean으로 직접 만들어서 참조

    @Bean
    public MessageSource messageSource() {
        var messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:/messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

ApplicationContext의 기능 ApplicationEventPublisher…

ApplicationEventPublisher
: 이벤트 프로그래밍에 필요한 인터페이스. 옵저버 패턴의 구현체이다.

spring 4.2 전에는
이벤트 만들기
ApplicationEvent Class를 상속받아야 한다.

public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
        super(source);
    }
}

이벤트 핸들러 만들기
ApplicationListener<이벤트> 인터페이스를 구현하고 빈으로 등록되어야 한다.

@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("이벤트 확인");
    }
}

spring 4.2 부터는 이벤트나 이벤트 핸들러 생성에서 상속/구현이 빠지고 어노테이션으로 처리한다.
이벤트 만들기

public class MyEvent {
    private Object source;

    public MyEvent(Object source) {
        this.source = source;
    }

    public Object getSource() {
        return source;
    }
}

이벤트 핸들러 만들기

@Component
public class MyEventHandler {

    @EventListener
    public void handle(MyEvent event) {
        System.out.println("이벤트 확인");
    }
}

이벤트 발생시키기

    ApplicationEventPublisher.publishEvent(new MyEvent(this));

만약 이벤트 핸들러가 여러개라면, Spring은 기본적으로 이벤트를 여러 핸들러로 순차적으로 처리하게 된다.
이때, 순서를 정하고 싶다면 핸들러에 @Order 어노테이션을 사용한다.
또는 비동기적(여러 쓰레드)으로 처리하고 싶다면 @Async 사용한다. (Java Configuration File에서는 @EnableAsync 필요)

Spring에서 제공하는 기본 이벤트

  • ContextRefreshedEvent
  • ContextStartedEvent
  • ContextStoppedEvent
  • ContextClosedEvent
  • RequestHandledEvent

ResouceLoader

: 리소스를 읽어오는 기능을 제공하는 인터페이스

Resource getResource(String location);

파일시스템에서 읽어오기
클래스패스에서 읽어오기(target/classes) getResource("classpath:test.txt");
resources에 있는 파일들이 빌드될 때 클래스패스에 들어가게 된다.
URL로 읽어오기
상대/절대 경로로 읽어오기

Comments