나만의 DI프로그램 만들기
- 프로그래밍 언어/자바
- 2021. 1. 3. 10:20
리플렉션을 학습하였다.
리플렉션에 대해 살짝 언급한다면,
클래스에 대한 정보를 아무런 제약없이 가져올 수 있다는 것인데...
이것을 잘못 사용할시..
프로그램의 성능 이슈가 있을 수 있다.
또, 이것은 접근지시자 private도 접근할 수 있기 때문에 신중에 신중을 기여하면서 사용해야한다.
만약, 이렇게 사용할시, 엄청난 문제가 발생할지도 모른다.
리플렉션으로 무언가를 만들 수 는 없을까?
우리는 간단하게 DI 의존성 주입을 만들 수 있다.
DI는 프로그램이 만들어지는 순서를 역으로 만든다는 것인데,
원래 객체가 들어가야 되지만 이것을 한번 꺽어서 만든다는 것이 특징이다.
이제 직접 만들어 보자.
일단 어노테이션부터 만들어야 된다.
왜냐하면 어노테이션으로 값을 찾기 때문이다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Injection {
}
나는 이것을 토대로 만들 예정이다.
public class CovidService {
@Injection
CovidRepository covidRepository;
}
public class CovidRepository {
}
이제 본격적으로 만들어 보자.
제일 먼저 객체를 가져오는 코드를 작성해보자.
public static <T>T getObject(Class<T> getType)
를 만들어야 된다. 아직 제네릭을 학습하지 않아 많이 헷갈릴지도 모르지만
대충 설명하면 T라는 값으로 리턴된다는 것이고 T는 외부 환경에 맞게 동적으로 변한다는 특징을 가지고 있다.
가만 생각해보니 자바는 객체를 가져올때 인스턴스를 이용해 가져온다.
그렇기 때문에 인스턴스를 만들어주는 코드도 만들면 좋겠다.
private static <T> T getInstance(Class<T> getType) {
return getType.getConstructor(null).newInstance();
}
이렇게 만들면 인스턴스를 만들 수 있다.
여기서 기본 생성자가 아닌 사용자 정의 생성자로 만들고 싶다면,
getType.getConstructor(null,int.class).newInstance(2);
이런식으로 수정해야된다. 하지만 지금은 중요한게 아니기 때문에 넘어가자.
그리고 이 상태에서 실행해보자.
@Test
void repository() {
CovidRepository repository = ContainerService.getObject(CovidRepository.class);
assertNotNull(repository);
}
그렇다면 이건 어떨까?
위에서 Service안에 Repository를 @Injection을 통해 만들엇다,
그 이야기는 이것또한 null이 아니라는 뜻으로 해석된다.
@Test
void service() {
CovidService service = ContainerService.getObject(CovidService.class);
assertNotNull(service);
assertNotNull(service.covidRepository);
}
하지만 이것은 잘못되었다.
우리는 생각해야한다.
@Injection의 역할은 객체를 만들어주는 역할이다.
이 어노테이션이 붙은 곳은 현재는 FILED값이다. (다른곳도 존재할 수 있지만.. 이 코드에서는 여기에만 존재한다.)
그렇기 때문에 정의한 FILED값을 가져와보자.
private static <T> void extracted(Class<T> getType) {
Arrays.stream(getType.getDeclaredFields()).forEach(f -> {
});
}
이런식으로 가져올 수 있다. 이 것의 특징은 선언된 필드값을 모두 가져오는 코드다.
우리는 여기서 @Injection을 찾아된다.
그러면 코드를 이렇게 수정할 수 있다.
private static void injection(Field f) {
Injection annotation = f.getAnnotation(Injection.class);
if (annotation != null) {
}
}
이것은 필드값에서 어노테이션을 찾는데, 그것이 Injection이냐라고 물어보는 것이다.
그리고 그게 null이 아니라면 (이것을 사용했다면) 다음 코드를 동작하라는 뜻이다.
어노테이션이 존재하면
인스턴스를 만들어서 추가시켜줘야 한다.
하지만 그전에 private타입일 수도 있다, 그렇기 때문에
private static void find(Field f) {
f.setAccessible(true);
}
이것을 추가시켜줘야한다.
그리고 나서 위에서 생성한 인스턴스로 객체를 만들어 주기만 하면 된다.
f.set(instance,getInstance(f.getType()));
이제 이것을 실행 시켜보자.
놀랍게도 성공이다.
전체 코드:🥰
public class ContainerService {
public static <T>T getObject(Class<T> getType) {
T instance = getInstance(getType);
extracted(getType,instance);
return instance;
}
private static <T> void extracted(Class<T> getType,T instance) {
Arrays.stream(getType.getDeclaredFields()).forEach(f -> {
injection(f,instance);
});
}
private static <T> void injection(Field f, T instance) {
Injection annotation = f.getAnnotation(Injection.class);
if (annotation != null) {
try {
find(f, instance);
} catch (IllegalAccessException e) {
throw new RuntimeException();
}
}
}
private static <T> void find(Field f, T instance) throws IllegalAccessException {
f.setAccessible(true);
f.set(instance,getInstance(f.getType()));
}
private static <T> T getInstance(Class<T> getType){
try {
return getType.getConstructor(null).newInstance();
} catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException();
}
}
}
마지막으로 다른 프로젝트에서 사용해보자.
이제 이것은 로컬저장소에 저장되어있다.
이것을 사용해보자.
메이븐에 추가
<dependency>
<groupId>org.example</groupId>
<artifactId>reflection</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
public class CovidRepository {
}
public class CovidService {
@Injection
CovidRepository covidRepository;
}
public class App
{
public static void main( String[] args )
{
CovidService service = getObject(CovidService.class);
System.out.println(service);
System.out.println(service.covidRepository);
}
}
정상적으로 나오는것을 확인 할 수 있다.