1.8 컨테이너 확장 포인트 by ks

전형적으로, 앱 개발자는 subclass 인 ApplicationContext 구현체가 필요없다. 대신에, Spring IoC 컨테이너는 특별한 인터페이스를 구현할 플러그인을 확장할 수 있다. 다음 섹션은 이러한 인터페이스를 구현하는 것을 설명한다.

(요약하면, 스프링이 빈을 생성하고 난 후에 빈 오브젝트를 가공할 수 있게 해주는 걸 말하는거야!)

1.8.1 BeanPostProcessor를 사용해서 빈을 커스터마이징

BeanPostProcessor 인터페이스는 컨테이너의 기본을 오버라이드 하거나 인스턴스 로직, 의존성이 있는 해결 로직, 기타 등등에 콜백 메소드를 정의한다. 만약 스프링 컨테이너가 인스턴스화, configuring, 빈 초기화 를 끝내고 난 후에 커스텀 로직을 확장하기를 원한다면, 하나 그 이상의 커스텀 BeanPostProcessor 구현체를 끼울 수 있다.

콜백 : 호출자가 피호출자를 부르는 일반적인 형식과는 반대로, 피호출자가 호출자를 부르는 것.

참조 : http://www.dreamy.pe.kr/zbxe/CodeClip/3768942

여러개의 BeanPostProcessor 인스턴스를 설정할 수 있고, order 프로퍼티를 세팅해서 BeanPostProcessor 인스턴스를 실행하고 명령을 조절할 수 있다. 이 프로퍼티는 오직 Ordered 인터페이스를 확장한 BeanPostProcessor를 사용해서 설정할 수 있다. 만약 BeanPostProcessor 를 작성한다면 Ordered 인터페이스를 확장하는 것을 고려해보자. 심화적으로, BeanPostProcessor, Ordered 인터페이스 자바 doc를 보자. 또한 programmatic registration of BeanPostProcessor instance 도 보자.

BeanPostProcessor 인터페이스는 빈 또는 오브젝트 인스턴스에서 동작한다. 즉, 스프링 IoC 컨테이너는 빈 인스턴스를 초기화하고 나서 BeanPostProcessor가 초기화한다.

BeanPostProcessor 인스턴스는 컨테이너별로 스코프화 되어있다. 컨테이너 상하관계를 사용한다면 관련되어있다. 만약 BeanPostProcessor를 한 컨테이너에서 정의한다면 그 컨테이너 안에 있는 빈들만 나중에 동작한다(post-processes). 다른 말로, 한 컨테이너에 정의된 빈들은 또다른 컨테이너에 정의된 BeanPostProcessor 에 의해 나중에 처리되지 않는다. 비록 두 컨테이너 모두 같은 상하관계라고 해도.

실질적인 빈의 정의(즉, 청사진은 빈을 정의한다.) 를 변경하기 위해서는 BeanFactoryPostProcessor를 사용해서 대신한다. 이는 Customizing Configuration Metadata with aBeanFactoryPostProcessor 에 설명되어있다.

org.springframework.beans.factory.config.BeanPostProcessor 인터페이스는 정확하게 두개의 콜백 메소드로 구성되어있다. 이러한 클래스가 컨테이너에 포스트 프로세서로 등록되면 컨테이너에 의해 생성 된 각 빈 인스턴스에 대해 컨테이너 초기화 메소드 ( InitializingBean.afterPropertiesSet()선언 된 init메소드 또는 메소드)가 모두 호출되기 전과 빈 초기화 후 둘다 포스트 프로세서가 컨테이너에서 콜백을 받는다.(예제1) post-processor 는 콜백을 완전히 무시하는 것을 포함하여 Bean 인스턴스에 대해 조치를 취할 수 있다. 빈 post-processor는 전형적으로 콜백 인터페이스를 체크하거나 프록시와 함께 빈을 감쌀수 있다. 몇몇 스프링 AOP 인프라 구조 클래스들은 프록시를 감싸는 로직을 제공하기 위해 post processor 빈으로서 확장 구현한다.

(BeanPostProcessor가 스프링이 빈을 생성한 후에 가공할 수 있게 해주는 거니까, 이 자체를 빈으로 등록하면 빈 오브젝트가 생성될 때 마다 특정 작업을 할 수 있게 해줄 수 있는(AOP의 기능!)거래. 강제로 프로퍼티를 바꾸거나 빈을 다른걸로 설정하거나.. 프록시를 빈으로 대신 설정할수도있대. 참조 : https://jongmin92.github.io/2018/04/15/Spring/toby-6/)

프록시를 이용한 스프링 AOP

아래 그림과 같이 직접 접근하지않고 proxy 객체를 통해서 간접 접근하게끔 한대

ApplicationContext는 자동적으로 BeanPostProcessor인터페이스를 확장하는 설정(configuration) 메타데이터에 정의된 빈들을 탐지한다. ApplicationContext 레지스터는 빈을 post-processor로 등록해서, 빈을 생성시에 부른다. 빈 post-processor는 다른 빈들과 같은 형태로 컨테이너에 디플로이될 수 있다.

설정(configuration) 클래스에 @Bean 팩토리 매소드를 사용해서 BeanPostProcessor를 선언할 때, 팩토리 메소드의 반환타입은 스스로의 확장 클래스나 적어도 org.springframework.beans.factory.config.BeanPostProcessor 인터페이스의 bean 이어야하며 해당 bean의 프로세서 후 특성을 명확하게 표시해야한다. 그렇지 않으면 ApplicationContext완전히 자동 생성하기 전에 유형별로 자동 검색 할 수 없다. BeanPostProcessor가 컨텍스트의 다른 빈들의 초기화에 적용하기 위해 일찍 초기화 될 필요성이 있기 때문에 빠르게 타입을 탐지하는것은 치명적이다.

프로그래밍적으로 BeanPostProcessor인스턴스를 등록

BeanPostProcessor 등록을 위해 추천하는 접근법이 ApplicationContext를 통해 자동 탐지되(앞에서 설명했듯이)는 동안 프로그래밍적으로 ConfigurableBeanFactory에 대항하여 addBeanPostProcessor 메소드를 사용하여 등록할 수 있다. 이는 등록 또는 상하관계에 있는 컨텍스트를 아울러 post processor가 빈을 복사하기 전에 상태 로직을 발전시키는게 필요할 때 유용하다. 하지만 프로그래밍적으로 더해진 BeanPostProcessor 인스턴스가 Ordered 인터페이스를 따르진 않는다. 여기에 실행 명령 등록에 순서가 있다. 프로그래밍적으로 등록된 BeanPostProcessor 인스턴스는 항상 자동 탐지를 통해 등록되기 전에 실행된다. ordering에 충돌이 일어나더라도 말이다.

BeanPostProcessor 인스턴스와 AOP auto-proxying

BeanPostProcessor를 확장하는 클래스들은 특별하고, 컨테이너가 다르게 다룬다. 모든 BeanPostProcessor 인스턴스와 시작할때 직접적으로 초기화되는 레퍼런스인 빈들은 ApplicatonContext의 특별한 시작 단원의 일부분이다. 다음 모든 BeanPostProcessor 인스턴스들은 정렬되어 등록되고 컨테이너의 모든 빈들에 적용된다. 왜냐하면 AOP auto-proxying은 BeanPostProccessor 로서 확장되기 때문에 BeanPostProcessor인스턴스 뿐만아니라 직접적으로 참조하는 빈도 auto-proxying할 권리가 있진 않으므로 그것들 안으로 관점을 가지지 않는다.

그런 빈들은 info레벨 로그를 볼 수 있다 : Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).

만약 @Resource(자동 연결을 위해 fall back 될지도 모르는) 나 자동 연결을 사용해서 BeanPostProcessor에 연결된 빈들이 있다면 스프링은 타입 매칭하는 의존성 후보들을 찾을 때 기대하지 않는 빈들에 접근할지도 모르므로 post-processing한 빈들이나 auto-proxying에 권한이 없도록 해야한다. 예를 들어서 만약 필드나 세터 이름이 직접적으로 선언된 이름과 맞지 않고 사용되는 name attribute가 없는 @Resource에 annotate된 의존성이 있다면, 스프링은 타입을 매칭하기 위해 다른 빈들을 접근한다.

아래 예제들은 어떻게 쓰고 등록하고 BeanPostProcessor인스턴스를 ApplicationContext에 사용하는지 보여준다.

예제 : Hello,World, BeanPostProcessort-style

이 첫 예제는 기본 사용을 보여준다. 예제는 컨테이너에 의해 생성된 각 빈들의 toString()메소드를 주입하고 system console에 문자열을 출력하는 메소드를 주입한 커스텀 BeanPostProcessor 확장을 보여준다.

아래 나오는 리스트는 커스텀 BeanPostProcessort 확장 클래스 정의다.:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    // 초기화 전에 부름
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }
    //초기화 후에 부름
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

beans 요소는 InstantiationTracingBeanPostProcessor를 사용한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

어떻게 InstantiationTracingBeanPostProcessor는 정의될까. 이름을 가지고 있지 않더라도 빈이기 때문에 어떠한 다른 빈들로 의존성 주입될 수 있다.(configuration 을 앞서는 것은 또한 그루비 스크립트에 의해 만들어진 빈을 정의한다. 스프링 동적 언어 지원은 Dynamic Language Support 에서 설명한다.)

아래 자바 앱은 코드와 configuration을 동작시킨다.

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

아래는 결과다.

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

예제 : The RequeiredAnnotationBeanPostProcessor

콜백 인터페이스나 커스텀 BeanPostProcessor확장자로 연결하는 어노테이션을 사용하는 것은 일반적인 스프링 IoC 컨테이너의 확장의 이유이다. 예제는 스프링의 RequiredAnnotationBeanPostProcessor이다. - 스프링 배포와 함께 제공되며 (임의의) 주석으로 표시된 빈의 자바빈 속성이 값으로 종속성 주입되도록 실제로 구성되도록 하는 BeanPostProcessor구현이다.

1.8.2 BeanFactoryPostProcessor와 configuration 메타데이터 커스터마이징

다음 확장 포인트는 org.springframework.beans.factory.config.BeanFactoryPostProcessor이다. 이 인터페이스의 순서(시맨틱)은 BeanPostProcessor와 유사하다. 한가지 주된 다른점은 : BeanFactoryPostProcessor는 설정(configuration) 메타데이터 빈에서 실행한다. 즉, 스프링 IoC 컨테이너 BeanFactoryPostProcessor가 configuration metadata를 읽게 하고 잠재적으로 컨테이너가 BeanFactoryPostProcessor 인스턴스가 빈을 초기화하기 전에 변경하게 한다. (정말 빈의 메타데이터만 들고있고, 인스턴스화되기 전에 호출된다고해. BeanPostProcessor와 마찬가지로 초기화전에 불려서 등록되고, 인스턴스화가 완료되면 후처리로 불러지는거같아)

여러 BeanFactoryPostProcessor인스턴스를 설정(configuration)할 수 있고 이 BeanFactoryPostProcessor 인스턴스가 order 프로퍼티를 세팅해서 동작하는 순서를 제어할 수 있다. 하지만 이 프로퍼티는 BeanFactoryPostProcessor가 Ordered 인터페이스를 확장한다면 세팅 가능하다. 만약 스스로의 BeanFactoryPostProcessort를 작성한다면, Ordered 인터페이스를 확장하는 것을 고려해봐라. BeanFactoryPostProcessor and Ordered 에서 상세사항을 확인보자.

실제 Bean 인스턴스 (즉, configuration 메타 데이터에서 작성된 오브젝트)를 변경하려면 a BeanPostProcessor (이전에 BeanPostProcessor를 사용해서 빈을 커스터마이징한 것을 설명했듯이) 를 사용해야한다 . 기술적으로 BeanFactoryPostProcessor(예를 들어 using을 사용하여 BeanFactory.getBean()) bean 인스턴스로 작업하는 것이 가능하지만 , 그렇게하면 표준 bean 라이프 사이클을 위반하여 조기 bean b인스턴스화가 발생한다. 이로 인해 bean post processing를 우회하는 것과 같은 부작용이 발생할 수 있다.

또한 BeanFactoryPostProcessor인스턴스는 컨테이너별로 범위가 지정된다. 이것은 컨테이너 계층을 사용하는 경우에만 관련이 있다. BeanFactoryPostProcessor하나의 컨테이너에서 정의하면 해당 컨테이너의 bean 정의에만 적용된다. 한 컨 테이너의 Bean 정의는 BeanFactoryPostProcessor두 컨 테이너가 동일한 계층 구조의 일부인 경우에도 다른 A 테이너의 인스턴스에 의해 사후 처리되지 않는다 .

ApplicationContext컨테이너를 정의하는 구성 메타 데이터에 변경 사항을 적용하기 위해 bean factory post-processor가 내부에 선언되면 자동으로 실행된다. 스프링은 가령 PropertyOverrideConfigurerPropertyPlaceholderConfigurer 같은 미리 정의된 빈 팩토리 post processor를 포함한다.BeanFactoryPostProcessor 사용자 정의 속성 편집기를 등록 하는 등의 사용자 정의 기능을 사용할 수도 있다.

ApplicationContextBeanFactoryPostProcessor인터페이스 를 구현하는 모든 bean을 자동으로 감지한다 . 적절한 시점에이 빈을 빈 팩토리 post processor로 사용한다. 이 포스트 프로세서 빈은 다른 빈처럼 배포 할 수 있다.

BeanPostProcessor 와 마찬가지로 일반적으로BeanFactoryPostProcessor지연 초기화(lazy initialization)를 위해 구성하지 않으려한다 . 만약 빈 팩토리 postprocessor 를 참조하는 다른 bean이 없으면 해당 post-processor는 전혀 인스턴스화되지 않는다. 따라서 lazy 초기화를 위해 그것을 표시하는 것은 무시 될 것이고, 빈 팩토리 postprocessor는 default-lazy-init 속성을 true로 선언하여 <beans/> 태그를 선언하더라도 초기화될 것이다.

예제 : 클래스 이름 대체 PropertyPlaceholderConfigurer

PropertyPlaceholderConfigurer를 사용하여 표준 Java Properties형식 을 사용하여 별도의 파일에서 bean 정의의 특성 값을 외부화 할 수 다 . 이렇게하면 응용 프로그램을 배포하는 사람이 컨테이너의 기본 XML 정의 파일을 수정하는 복잡성이나 위험없이 데이터베이스 URL 및 암호와 같은 환경 별 속성을 사용자 지정할 수 있습니다.

placeholder로 DataSource를 정의한 XML configuration 메타데이터를 고려해보자.

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

이 예제는 외부 Properties파일 에서 구성된 등록 정보를 보여준다 . 런타임에 PropertyPlaceholderConfigurersms DataSource의 프로퍼티를 대체하는 메타데이터로 적용된다. 대체하는 값은 ${property-name}의 형태의 placeholder로서 구체화되고 Ant와 log4j와 JSP EL 스타일을 따른다. 실제 값은 자바 표준 Properties 형식의 다른 파일로부터 온다.

jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa

jdbc.password=root

그러므로 ${jdbc.username}같 문자열은 'sa' 값으로 런타임에 대체되고 같은 것이 프로퍼티스 파일에 키에 매치하는 placeholder 값에 적용된다. PropertyPlaceholderConfigurer 은 빈의 정의 attribute와 대부분 프로퍼티스에 placeholder를 체크한다. 게다가 placeholder prefix, suffix를 커스터마이즈할 수 있다.

context 네임스페이스는 스프링 2.5와 소개되었고, configuration 요소가 가리키는 property placeholder를 설정(configuration)할 수 있다. 하나 그 이상의 위치를 location attribute에서 ,로 분리해 리스트로 제공한다.

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertyPlaceholderConfigurer 은 구체화한 Properties파일의 properties를 찾지만은 않는다. 기본적으로 properties파일에서 property를 못찾는다면 자바 System 프로퍼티를 체크한다. 이 행위를 지원하는 세 개의 integer 값중에 하나와 configurer의 systemPropertiesMode 프로퍼티를 세팅해서 커스터마이즈 할 수 있다.

1. never(0) : system properties를 체크하지 않음

2. fallback(1) system property를 체크함. properties파일이 없다면. 이게 기본값

3. override(2) : system property를 먼저 찾고, 그다음 프로퍼티스 파일을 찾는다. system property를 다른 프로퍼티 소스에 오버라이드한다.

PropertyPlaceholderConfigurer java doc을 더 살펴보자.

클래스명을 대신하기 위해 PropertyPlaceholderConfigurer 를 사용할 수 있고, 때때로 런타임에 부분적인 확장 클래스를 골라야만 할 때 유용하다. 아래는 예제다.

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

런타임에 클래스를 유효한 클래스로 해석 할 수없는 경우 Bean이 생성 될 때 실패한다. 이는 lazy-init Bean의 ApplicationContext의 preInstantiateSingletons () 단계 중에 발생한다.

예제 : PropertyOverrideConfigurer

또 다른 빈 팩토리 포스트 프로세서 인 PropertyOverrideConfigurer는 PropertyPlaceholderConfigurer와 유사하지만 후자와 달리 원래 정의는 빈 값을 기본값으로 갖거나 전혀 갖지 않는다. 만약 Properties 파일을 오버라이드 한 것이 특정한 빈 프로퍼티를 갖지 않는다면 기본 컨텍스트 정의가 사용된다. 빈 정의는 override 된 것을 인식하지 않아서, 오버라이드한 configurer가 사용된 XML 정의 파일로부터 즉시 명백하진 않다. 같은 빈 프로퍼티에 다른 값을 정의한 여러 PropertyOverrideConfigurer 인스턴스의 경우에는 오버라이드 매커니즘때문에 마지막것으로 사용한다. Properties file configuration 예제 포맷 :

beanName.property=value

아래는 리스팅 예제다.

dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb

이 예제 파일은 driver와 url properties를 가진 dataSource 라 불리는 빈을 포함하는 컨테이너 정의를 사용한다. 오버라이드된 마지막 프로퍼티를 제외하고 경로의 모든 컴포넌트들은 이미 null이 아닌 한 혼합된 프로퍼티 이름들도 지원된다.(아마도 생성자로 초기화된). 아래 예제는 tom 빈의 fred 프로퍼티의 bob 프로퍼티의 sammy 프로퍼티가 123 스칼라값으로 설정된 것이다.

tom.fred.bob.sammy=123

구체화된 오버라이드 값은 항상 문자값이다. 빈 레퍼런스로 변환되지 않는다. 이 컨벤션은 XML 빈 정의에서 원래 값이 빈 레퍼런스를 구체화할때 또한 적용된다.

context 네임스페이스는 스프링 2.5에 소개되어있고 아래 예제처럼 cinfiguration 요소를 가리키는 오버라이드하는 프로퍼티를 설정할 수 있다.

<context:property-override location="classpath:override.properties"/>

1.8.3 FactoryBean으로 로직 초기화 커스터마이징

org.springframework.beans.factory.FactoryBean 인터페이스를 스스로의 factories인 오브젝트로 확장할 수 있다.

FactoryBean 인터페이스는 스프링 IoC 컨테이너의 초기화 로직으로 끼울수 있는 포인트다. 만약 XML가 많은 것에 대항하여 Java로 표현하는게 더 좋은 복잡한 초기화 로직이 있다면, FactoryBean을 생성할 수 있고, 클래스안에 복잡한 초기화 코드를 작성할 수 있고 그리고 나서 컨테이너에 커스텀 FactoryBean 을 끼울 수 있다. FactoryBean인터페이스는 다음 3가지의 메소드를 지원한다.

1. Object getObject() : 이 팩토리가 생성한 오브젝트의 인스턴스를 반환한다. 인스턴스는 공유될 수 있고, 팩토리가 싱글톤을 반환하던지 프로토타입을 반환하던지에 의존한다.

2. boolean isSimgleton() : FactoryBean이 싱글톤을 반환하면 true를 반환하고 아니면 false를 반환한다.

3. Class getObjectType() : getObject() 메소드에 의해 반환된 오브젝트 타입을 반환하거나 타입이 사전에 알려지지 않았다면 null을 반환한다.

FactoryBean 개념과 인터페이스는 스프링프레임워크 안에서 많은 것에 사용된다. 50개 이상의 FactoryBean의 확장체는 스프링과 함께 제공된다.

컨테이너가 빈 팩토리가 반환한 빈 대신에 실제 빈이 필요할 때, myBean의 ID를 가진 주어진 FactoryBean의 경우 컨테이너에서 getBean ( "myBean")을 호출하면 FactoryBean의 product가 반환되고 getBean ( "& myBean")을 호출하면 FactoryBean 인스턴스 자체가 반환다.

Last updated