@component
에 대한 설명을 하였지만 스프링이 어떻게 컴포넌트를 스캔을 하는지 대답을 하지 못하였다.DI
를 직접 만들었습니다.Reflection
과 IOC
DI
에 대하여 다시 한번 학습하고 Di Container
를 직접 만들고 정리해본다.리플렉션을 언제 사용할까?
1. 동적으로 클래스를 사용해야할 때
2. 작성을 할때에는 어떤 클래스를 사용할지 모르지만 런타임 시점에서 가져와 사용할 때
class
를 통하여 프로퍼티를 흭득한다.getClass()
를 통하여 흭득한다.forName()
을 통하여 FQCN(Fully Qualified Class Name)를 전달하여 해당 경로와 대응하는 클래스에 대한 Class 클래스의 인스턴스를 얻는 방법이다. Class<UserDomain> userDomainClass = UserDomain.class;
System.out.println(" 방법 1 = " + userDomainClass);
UserDomain userDomain = new UserDomain();
//Class<? extends UserDomain> user = UserDomain.class;
Class<? extends UserDomain> user = userDomain.getClass();
System.out.println(" 방법 2 = " + user);
Class<?> aClass = null;
try {
aClass = Class.forName("com.example.dipractice.test.UserDomain");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
System.out.println(" 방법 = 3 " + aClass);
방법 1 = class com.example.dipractice.test.UserDomain
방법 2 = class com.example.dipractice.test.UserDomain
방법 3 = class com.example.dipractice.test.UserDomain
@Bean
을 사용해 등록하거나 xml을 통하여 등록하고 등록된 객체는 쉽게 주입하여 사용이 가능하다.Bean 생명주기
스프링 컨테이너 생성
-> 스프링 빈 생성
-> 의존 관계 주입
-> 초기화 콜백
-> 사용
-> 소멸전 콜백
-> 스프링 종료
Spring IOC Container는 스프링 핵심 중 하나이다. 공식문서를 살펴보면 Bean의 인스턴스화, 구성, 조립을 담당합니다. 응용 프로그램을 구성하는 개체와 해당 개체간의 풍부한 상호 종속성을 표현할 수 있다고 작성이 되어져있다.
즉 스프링 컨테이너는 객체의 라이프 사이클을 관리하며 DI를 통해서 의존성을 관리할 수 있다.
Spring IOC 컨테이너로 BeanFactory
, ApplicationContext
가 사용이 되며 BeanFactory
는 프레임워크와 기본 기능을 제공하고ApplicationContext
는 더 많은 기능을 제공을 한다.
Controller, Service, Repository
어노테이션을 살펴보면 @componet
가 등록되어 있다. @compoent
는 Bean으로 등록하고 싶은 클래스에 명시를 해준다. 위 어노테이션이 분으면 스프링 프레임워크가 Scan을 하여 Bean을 찾고 등록한다.
이때 Scan하는 방식으로는 Reflection
을 이용해서 빈을 Find, 추가를 한다.
@SpringBootApplication
은 3개의 주석의 조합입니다.@Configuration
@EnableAutoConfiguration
@ComponentScan
@ComponentScan
은 지정된 패키지 및 하위 패키지에서 Spring의 컴포넌트들을 스캔하도록 지시합니다. 컴포넌트 스캔은 @Component 어노테이션 뿐만 아니라, @Controller, @Service, @Repository 등 Spring에서 제공하는 다양한 어노테이션들도 스캔 대상에 포함합니다. 이 어노테이션을 사용하면 스캔된 컴포넌트들이 Spring 컨테이너에 자동으로 등록됩니다.
이때 하위 패키지에 등록하는 방식이 Reflection
을 사용을 했다고 이해하면 된다.
Github 소스 - https://github.com/KMGeon/SpringPlayGround/tree/main/di-practice
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
}
Target
: 애너테이션이 적용가능한 대상을 지정하는데 사용한다.ElementType.TYPE
: 아래 사진을 살펴보면 TYPE을 public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
...
}
Retention
: 애너테이션이 유지되는 범위를 지정하는데 사용한다.RetentionPolicy.RUNTIME
은 런타임 유지기간을 설정을 합니다.
public class BeanFactory {
//class 타입 객체
private Set<Class<?>> preInstantiatedBeans;
//class 타입을 키 인스턴스를 value
private Map<Class<?>, Object> beans = new HashMap<>();
public BeanFactory(Set<Class<?>> preInstantiatedBeans) {
this.preInstantiatedBeans = preInstantiatedBeans;
initialize();
}
// class 타입 객체를 가지고 인스턴스를 가지고 초기화
public void initialize() {
for (Class<?> clazz : preInstantiatedBeans) {
Object instance = createInstance(clazz);
beans.put(clazz, instance);
}
}
private Object createInstance(Class<?> concreteClass) {
//생성자
Constructor<?> constructor = findConstructor(concreteClass);
//파라미터
List<Object> parameters = new ArrayList<>();
for (Class<?> typeClass : Objects.requireNonNull(constructor).getParameterTypes()) {
parameters.add(getBean(typeClass));
}
//인스턴스 생성
try {
return constructor.newInstance(parameters.toArray());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
//inject가 붙은 어노테이션을 가져온다.
private Constructor<?> findConstructor(Class<?> concreteClass) {
Constructor<?> constructor = getInjectedConstructor(concreteClass);
if (Objects.nonNull(constructor)) {
return constructor;
}
return concreteClass.getConstructors()[0];
}
public <T> T getBean(Class<T> requiredType) {
return (T) beans.get(requiredType);
}
public Constructor<?> getInjectedConstructor(Class<?> clazz) {
Set<Constructor> injectedConstructors = getAllConstructors(clazz, withAnnotation(Inject.class));
if (injectedConstructors.isEmpty()) {
return null;
}
return injectedConstructors.iterator().next();
}
}
class BeanFactoryTest {
private Reflections reflections;
private BeanFactory beanFactory;
@BeforeEach
void setUp() {
//reflection 대상
reflections = new Reflections("com.example.dipractice");
// controller, service 조회
Set<Class<?>> typesAnnotatedWith = getTypesAnnotatedWith(Controller.class, Service.class);
beanFactory = new BeanFactory(typesAnnotatedWith);
}
private Set<Class<?>> getTypesAnnotatedWith(Class<? extends Annotation>... annotations) {
Set<Class<?>> beans = new HashSet<>();
for (Class<? extends Annotation> annotation : annotations) {
beans.addAll(reflections.getTypesAnnotatedWith(annotation));
}
return beans;
}
@Test
@DisplayName("diTest")
public void diTest() throws Exception {
//given
UserController userController = beanFactory.getBean(UserController.class);
//when
//Then
Assertions.assertThat(userController).isNotNull();
}
}
Controller
service
를 조회를 합니다.https://hudi.blog/java-reflection/
https://www.youtube.com/watch?v=Q-8FC09OSYg
https://www.youtube.com/watch?v=67YdHbPZJn4
https://docs.spring.io/spring-framework/reference/core/beans/introduction.html
많은 도움이 되었습니다, 감사합니다.