[F-Lab 모각코 챌린지] 49일차 - DI 구현하기
F-Lab 모각코 챌린지 - 49일차. DI 구현하기 입니다. 대부분 코드로 작성되어 있습니다.
![[F-Lab 모각코 챌린지] 49일차 - DI 구현하기](/content/images/size/w1200/2023/07/f_lab_mogacko-9.png)
코드
BeanFactory
- Spring 에서 ApplicationContext 가 extands 한 BeanFactory 와 같다. 일부 필요 한 메서드는 추가하여 구현하였다
package wsffs.springframework.beans.factory;
import wsffs.springframework.beans.BeansException;
public interface BeanFactory {
void refresh();
Object getBean(String name);
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredTpe, Object... args) throws BeansException;
boolean containsBean(String name);
}
ApplicationContext
- 실제로 Bean 을 저장하고 관리하는 구간으로 생각하였다
- BeanFactory 를 바로 구현하고 싶었지만, BeanFactory 의 역할 만을 수행하기 위한 클래스는 아니기 때문에 ApplicationContext 라는 인터페이스를 두었다.
package wsffs.springframework.context;
import wsffs.springframework.beans.factory.BeanFactory;
public interface ApplicationContext extends BeanFactory {
}
ApplicationContextImpl
- BeanDefinition : Bean 의 정보
- beanRegister : Bean 의 저장소
ApplicationContext 의 구현체이다. DI 에서 주된 역할을 수행하는 클래스로, 여러 객체들에게 명령을 내려 실제 DI 를 동작하도록 만든다.
다만, 최적화는 수행하지 않았다
Bean 의 생성과 주입을 수행하는데, 생성은 객체가 필요 한 시점에 할 수 있도록 getBean 메서드를 호출하는 순간으로 결정하였다
따라서, 객체가 정말 필요 할 때에만 객체를 생성한다
객체가 소멸되어야 하는 타이밍은 따로 지정하지 않았다.
package wsffs.springframework.boot.web.servlet.context;
import wsffs.Application;
import wsffs.springframework.beans.*;
import wsffs.springframework.beans.annotation.Component;
import wsffs.springframework.beans.exception.BeanNotOfRequiredTypeException;
import wsffs.springframework.context.ApplicationContext;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class AnnotationConfigServletWebServerApplicationContext implements ApplicationContext {
private final Map<String, BeanDefinition> beanDefinitions;
private final Map<String, Object> beanRegister;
private BeanScanner beanScanner;
public AnnotationConfigServletWebServerApplicationContext(Class scanStartPoint) {
beanScanner = new BeanScanner(scanStartPoint);
this.beanDefinitions = new HashMap<>();
this.beanRegister = new HashMap<>();
}
public void refresh() {
beanDefinitions.clear();
beanRegister.clear();
Class<? extends Annotation>[] annotations = new Class[]{Component.class};
Set<Class<?>> scan = beanScanner.scan(annotations);
System.out.println("scan = " + scan);
for (Class<?> candidateBean : scan) {
BeanDefinition beanDefinition = new DefaultBeanDefinition(candidateBean);
beanDefinitions.put(beanDefinition.getBeanName(), beanDefinition);
}
}
public Object getBean(String name) {
String beanName = BeanNameUtils.getName(name);
if(beanRegister.containsKey(beanName)) {
return beanRegister.get(beanName);
}
BeanDefinition beanDefinition = beanDefinitions.get(beanName);
if(Objects.isNull(beanDefinition)) {
throw new BeansException("bean definition is not found");
}
Class<?>[] requiredDependencies = beanDefinition.getDependencies();
Object[] constructorArguments = new Object[requiredDependencies.length];
for (int i = 0; i < requiredDependencies.length; i++) {
String argumentBeanName = BeanNameUtils.getName(requiredDependencies[i]);
constructorArguments[i] = getBean(argumentBeanName);
}
return getBean(beanDefinition.getOriginalClass(), constructorArguments);
}
@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
String beanName = BeanNameUtils.getName(name);
BeanDefinition beanDefinition = beanDefinitions.get(beanName);
if(Objects.isNull(beanDefinition)) {
throw new BeansException("bean definition is not found");
}
if(beanDefinition.getOriginalClass() != requiredType) {
throw new BeanNotOfRequiredTypeException("bean type not matched");
}
Class<?>[] dependencies = beanDefinition.getDependencies();
Object[] instanceOfDependency = new Object[dependencies.length];
for (int i = 0; i < dependencies.length; i++) {
String simpleName = dependencies[i].getSimpleName();
String dependencyBeanName = BeanNameUtils.getName(simpleName);
instanceOfDependency[i] = getBean(dependencyBeanName);
}
return getBean(requiredType, instanceOfDependency);
}
public <T> T getBean(Class<T> requiredTpe, Object... args) throws BeansException {
String beanName = BeanNameUtils.getName(requiredTpe.getSimpleName());
BeanDefinition beanDefinition = beanDefinitions.get(beanName);
if(Objects.isNull(beanDefinition)) {
throw new BeansException("bean definition is not found");
}
Constructor<?> beanConstructor = beanDefinition.getBeanConstructor();
try {
return (T) beanConstructor.newInstance(args);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public boolean containsBean(String name) {
String beanName = BeanNameUtils.getName(name);
Object instance = beanRegister.get(beanName);
return instance != null;
}
}
BeanDefinition
객체의 정보를 가지고 있다.
Spring 의 구현 방식을 비슷하게 따라했지만 모두 비슷한것은 아니며 개인적인 판단으로 들어간 메서드도 있다.
package wsffs.springframework.beans;
import java.lang.reflect.Constructor;
public interface BeanDefinition {
String getBeanName();
Class<?> getOriginalClass();
Constructor<?> getBeanConstructor();
Class<?>[] getDependencies();
}
DefaultBeanDefinition
BeanDefinition 인터페이스를 구현한 구현체
BeanDefinition 의 함수를 수행 할 수 있도록 생성자에서 정보를 셋팅하고, 각 함수를 구현하고 있다.
package wsffs.springframework.beans;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Comparator;
public class DefaultBeanDefinition implements BeanDefinition {
private final Class<?> originalClass;
private final Constructor<?> constructor;
private final Class<?>[] dependencies;
public DefaultBeanDefinition(Class<?> clazz) {
this.originalClass = clazz;
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
this.constructor = Arrays.stream(declaredConstructors)
.max(Comparator.comparingInt(Constructor::getParameterCount))
.orElseThrow();
this.dependencies = this.constructor.getParameterTypes();
}
@Override
public String getBeanName() {
String simpleName = originalClass.getSimpleName();
return simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
}
@Override
public Class<?> getOriginalClass() {
return this.originalClass;
}
@Override
public Constructor<?> getBeanConstructor() {
return this.constructor;
}
@Override
public Class<?>[] getDependencies() {
return this.dependencies;
}
@Override
public String toString() {
return "DefaultBeanDefinition{" +
"originalClass=" + originalClass +
", constructor=" + constructor +
", dependencies=" + Arrays.toString(dependencies) +
'}';
}
}
BeanScanner
BeanScan 을 담당하는 클래스이다
생성자로 Class 를 받고있는데, 이는 main 함수에서 추가되는 클래스이다
해당 클래스를 기준으로 하위 패키지에서 존재하는 특정 애노테이션들을 검색한다
그리고 그런 클래스들을 리턴하면, ApplicationContext 의 구현체에서 BeanDefinition 정보로 저장하도록 유도하였다.
이 위치에서 BeanDefinition 을 만들어서 넘길 수도 있지만, 여기서 BeanDefinition 을 알고있으면 안된다고 판단하였다.
클래스들의 의존성은 최대한 낮추는 방향으로.
package wsffs.springframework.beans;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
public class BeanScanner {
private final Class beanScanStartPoint;
public BeanScanner(Class beanScanStartPoint) {
this.beanScanStartPoint = beanScanStartPoint;
}
public Set<Class<?>> scan(Class<? extends Annotation>[] annotations) {
final Set<Class<?>> result = new HashSet<>();
for (Class<? extends Annotation> annotation : annotations) {
Set<Class<?>> scan = scan(annotation);
result.addAll(scan);
}
return result;
}
public Set<Class<?>> scan(Class<? extends Annotation> annotation) {
Reflections reflections = new Reflections(this.beanScanStartPoint.getPackageName(), Scanners.TypesAnnotated);
return reflections.getTypesAnnotatedWith(annotation);
}
}
BeanNameUtils
BeanName 을 담당하는 Util 이다
Bean 의 이름은 공통된 형태로 만들어져야 한다고 생각했고, 그것을 위한 클래스이다.
package wsffs.springframework.beans;
public class BeanNameUtils {
public static String getName(Class<?> clazz) {
String simpleName = clazz.getSimpleName();
return getName(simpleName);
}
public static String getName(String name) {
return name.substring(0, 1).toLowerCase() + name.substring(1);
}
}
Main code
Main 코드에서는 위에서 구현한 구현체를 통해 실제로 DI 를 수행해본다
아래의 코드를 보면 DummyService 를 getBean 을 통해 가져오는데,
package wsffs;
import wsffs.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import wsffs.springframework.context.ApplicationContext;
import wsffs.web.DummyObject;
import wsffs.web.DummyRepository;
import wsffs.web.DummyService;
public class Application {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigServletWebServerApplicationContext(Application.class);
applicationContext.refresh();
DummyService dummyService = applicationContext.getBean("dummyService", DummyService.class);
DummyObject dummy = dummyService.get();
System.out.println("dummy = " + dummy);
}
}
사용 코드
DummyObject
- 객체 생성을 위한 더미 객체이다
package wsffs.web;
public class DummyObject {
public Long id;
public String name;
public String email;
public DummyObject(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
@Override
public String toString() {
return "DummyObject{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
DummyRepository
- 의존성 테스트를 위한 객체이다.
- 이 객체가 필요로 하는 의존성은 없으며 주입되기 위한 객체이다.
package wsffs.web;
import wsffs.springframework.beans.annotation.Component;
@Component
public class DummyRepository {
public DummyObject getDummy() {
return new DummyObject(1L, "pollra", "pollra@gmail.com");
}
}
DummyService
- 의존성을 주입받을 객체이다
package wsffs.web;
import wsffs.springframework.beans.annotation.Component;
@Component
public class DummyService {
private final DummyRepository dummyRepository;
public DummyService(DummyRepository dummyRepository) {
this.dummyRepository = dummyRepository;
}
public DummyObject get() {
return dummyRepository.getDummy();
}
}
동작
사실 위에 소개 한 객체들은 주된 객체들이며 나머지는 아래와 같이 구성되어있다
![](https://blog.pollra.com/content/images/2023/07/-----2023-07-16----2.01.27.png)
메인 함수를 동작시킨 결과
![](https://blog.pollra.com/content/images/2023/07/-----2023-07-16----2.06.48.png)