티스토리 뷰

Programming/Spring

@Enable + Registrar

Albothyl 2019. 4. 11. 21:00

Sample Code

 

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ PracticeConfigRegistrar.class })
public @interface EnablePracticeConfigAnnotation {
Class<?>[] basePackageClasses();
}

1. @Enable 어노테이션을 만들어서 Conifguration에 Registrar를 등록한다.

 

import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

public class PracticeConfigRegistrar implements ImportBeanDefinitionRegistrar {

   private final String basePackageClasses = "basePackageClasses";

   @Override
   public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
      final AnnotationAttributes annotationAttributes = new AnnotationAttributes(annotationMetadata.getAnnotationAttributes(EnablePracticeConfigAnnotation.class.getName()));

      final AbstractBeanDefinition practiceConfigAttribute = createPracticeConfigAttribute(annotationAttributes);
      final AbstractBeanDefinition practiceScanConfig = createPracticeScanConfig();

      registry.registerBeanDefinition("practiceConfigAttribute", practiceConfigAttribute);
      registry.registerBeanDefinition("practiceScanConfig", practiceScanConfig);
   }

   private AbstractBeanDefinition createPracticeConfigAttribute(AnnotationAttributes annotationAttributes) {
      return BeanDefinitionBuilder.genericBeanDefinition(PracticeConfigAttribute.class)
         .addConstructorArgValue(annotationAttributes.getString(basePackageClasses))
         .getBeanDefinition();
   }

   private AbstractBeanDefinition createPracticeScanConfig() {
      return BeanDefinitionBuilder.genericBeanDefinition(PracticeScanConfig.class).getBeanDefinition();
   }
}

2. Resistrar에서 BeanDefinition을 생성하여 Registry에 등록되어 Bean으로 생성된다.

- PracticeConfigAttribute를 등록해주는 이유는 PracticeScanConfig에서 DI받아 basePackageClasses를 사용하기 위함이다.

 

import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
@Builder
public class PracticeConfigAttribute {
   public final List<Class> basePackageClasses;
}

3. 단순 basePackageClasses 전달

 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

public class PracticeScanConfig {

   @Autowired
   private ApplicationContext applicationContext;

   @Autowired
   private PracticeConfigAttribute practiceConfigAttribute;

   @Bean
   public SpringBeanRegisterar springBeanRegisterar() {
      SpringBeanRegisterar springBeanRegisterar = new SpringBeanRegisterar(applicationContext);
      springBeanRegisterar.registers(practiceConfigAttribute.getBasePackageClasses());

      return springBeanRegisterar;
   }
}

4. Bean으로 생성되면서, SpringBeanRegisterar.registers를 실행한다.

 

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.ClassPath;
import com.project.kanban.practice.some.SomePractice;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

import java.io.IOException;
import java.util.List;

public class SpringBeanRegisterar {

    private final ConfigurableListableBeanFactory beanFactory;

    public SpringBeanRegisterar(ApplicationContext applicationContext) {
        this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
    }

    public void registers(List<Class> basePackages) {
        for (Class basePackage : basePackages) {
            register(basePackage);
        }
    }

    private void register(Class basePackageClass) {
        for (Class targetClass : getSpecificAnnotationPresentedClass(basePackageClass, SpecificAnnotation.class)) {
            SomePractice targetObject = null;
            try {
                targetObject = (SomePractice) Class.forName(targetClass.getName()).newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            beanFactory.registerSingleton("somePractice", targetObject);
            beanFactory.registerSingleton(targetClass.getSimpleName(), targetObject);
        }
    }


    private List<Class> getSpecificAnnotationPresentedClass(Class basePackageClass, Class targetClass) {
        List<Class> targetClassList = Lists.newArrayList();

        ImmutableSet<ClassPath.ClassInfo> basePackageClassInfos = getClassPath(basePackageClass).getTopLevelClassesRecursive(basePackageClass.getPackage().getName());
        for (ClassPath.ClassInfo packageClassInfo : basePackageClassInfos) {
            if (packageClassInfo.load().isAnnotationPresent(targetClass)) {
                targetClassList.add(packageClassInfo.load());
            }
        }

        return targetClassList;
    }

    private ClassPath getClassPath(Class basePackageClass) {
        try {
            return ClassPath.from(basePackageClass.getClassLoader());
        } catch (IOException e) {
            throw new IllegalStateException("invalid class path");
        }
    }
}

5.  특정 Class를 기준으로 Package와 Class를 Scan하여 Bean으로 등록한다.

- beanFactory.registerSingleton를 통해 Bean으로 등록되면 일반적인 DI (@Autowired)를 사용할 수 없다. 

- 순서가 일반 Bean이 모두 생성된 후 Registerar가 실행되기 때문이다.

...더보기

BeanFactory는 특정 bean을 생성하는 방법에 대한 정보가 들어있는 bean 정의 외에도 (사용자 정의 코드에 의해) factory 외부에서 생성 된 기존 bean 객체를 등록 할 수있다. DefaultListableBeanFactory는 org.springframework.beans.factory.config.ConfigurableBeanFactory 인터페이스에 정의 된대로 registerSingleton 메소드를 통해이를 지원합니다 . 일반적인 응용 프로그램은 메타 데이터 bean 정의를 통해 정의 된 bean과 함께 작동합니다.

Besides bean definitions which contain information on how to create a specific bean, a BeanFactory can also allow to register existing bean objects that have been created outside the factory (by custom code). DefaultListableBeanFactory supports this through the registerSingleton method, as defined by the org.springframework.beans.factory.config.ConfigurableBeanFactory interface. Typical applications solely work with beans defined through metadata bean definitions, though.

 

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SpecificAnnotation {
}

6. Bean등록 기준이 되는 Annotation


 

이 예제에서는 특정 범위를 Scan하여 Bean으로 등록하는 것이지만, Registerar를 사용하면 Spring과 다른 Application을 융합하는데 큰 도움이 된다. 예를들면 Spark나 Akka를 사용하면서 DI를 편하게 하기 위해 Spring을 같이 사용할 수 있다.

 

7. 이 예제를 개선한 예제

https://jjhwqqq.tistory.com/141

'Programming > Spring' 카테고리의 다른 글

JDBC Template  (0) 2019.05.05
Enhanced Registrar  (0) 2019.04.12
ControllerAdvice  (1) 2019.04.01
Request Param Validate (JSR-303)  (0) 2019.03.30
AOP  (0) 2019.03.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함