Overview
so 별 별도 로직이 존재하므로 반복과 중복 if 문을 써서 코드가 복잡해 지는 구조가 있다.
java reflect 를 이용하여 동적으로 class 및 method 호출 과정에 대해 기록한다.
전체 코드는 다음 링크에서 확인 (https://github.com/pari0130/reflection)
Service 버전
service 별 버전이 상이하여 별도 클래스로 분리 할 경우 호출하는 로직에서 if 문 중첩이 발생 될 수 있으므로 동적 호출 방안에 대해 기록 한다.
- annotation 구성
- class 상위에 선언 될 annotation 을 구성 한다
123456789101112131415/*** 설명** Retention RunTime : compile time 과 binary 에도 포함되고, reflection 을 통해 접근 가능** Target CLASS : class, interface, object, annotation class 에 사용 가능하도록 제한** values : 서비스 버전 정보 입력을 위해 사용** */@Retention(AnnotationRetention.RUNTIME)@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS)annotation class ServiceVersion(val values: Array<String> = [])
- class 상위에 선언 될 annotation 을 구성 한다
- interface 및 class 구성
- getBeanProvider 를 이용해 하위 class 를 호출 하려면 interface 를 동일 지정, Annotation value 로 버전을 기록하여 구분
interface TestService {fun getTestItems(item: String): Map<String, Any>fun insertCharge(param: Map<String, Any>)}// v1 로 사용 될 class@Service("TestServiceV1Impl")@ServiceVersion(values = ["v1"])class TestServiceV1Impl(val testDiService: TestDiService) : TestService { }// v1.1 로 사용 될 class@Service("TestServiceV11Impl")@ServiceVersion(values = ["v1.1"])class TestServiceV11Impl(val testDiService: TestDiService) : TestService { }// v1.2 로 사용 될 class@Service("TestServiceV12Impl")@ServiceVersion(values = ["v1.2"])class TestServiceV12Impl(val testDiService: TestDiService) : TestService { }
- getBeanProvider 를 이용해 하위 class 를 호출 하려면 interface 를 동일 지정, Annotation value 로 버전을 기록하여 구분
- interface 동적 호출을 위한 DI class 구성
- 해당 class 의 목적은 전달받은 version 정보를 이용하여 interface 검색 후 override method 를 return
override fun getTestService(version: String): TestService {return applicationContext.getBeanProvider(TestService::class.java).stream() // TestService interface 를 상속한 Bean 조회.filter { b -> !ObjectUtils.isEmpty(b.javaClass.getDeclaredAnnotation(ServiceVersion::class.java)) }.filter { b ->Arrays.stream(b.javaClass.getDeclaredAnnotation(ServiceVersion::class.java).values) // version annotation 을 사용하는 method 중 일치하는 값 조회.anyMatch { v -> v.equals(version) }}.findAny().orElse(applicationContext.getBean("TestServiceV1Impl", TestService::class.java))}
- 해당 class 의 목적은 전달받은 version 정보를 이용하여 interface 검색 후 override method 를 return
- Test
- DI Service 에 version 정보를 전달하여 하위 interface 의 결과값을 조회 함
@Testfun getTestService() {var version = "v1"val itemV1 = testDiService.getTestService(version).getTestItems("v1 item test")version = "v1.1"val itemV11 = testDiService.getTestService(version).getTestItems("v1.1 item test")version = "v1.2"val itemV12 = testDiService.getTestService(version).getTestItems("v1.2 item test")logger.info("[TEST] item -> { $itemV1, $itemV11, $itemV12 }") // [TEST] item -> { {item=v1 item test}, {item=v1.1 item test}, {item=v1.2 item test} }assertAll({ Assertions.assertEquals(itemV1["item"], "v1 item test") },{ Assertions.assertEquals(itemV11["item"], "v1.1 item test") },{ Assertions.assertEquals(itemV12["item"], "v1.2 item test") })}
- DI Service 에 version 정보를 전달하여 하위 interface 의 결과값을 조회 함
Service 에 대한 특정 Function 실행
so 별 특정 함수를 호출하는 로직에 대해 중첩된 if 문이 발생 될 수 있으므로 보완 방안으로 Reflection 을 이용한 동적 method 호출에 대해 기록한다.
- annotation 구성
- method 상위에 선언 될 annotation 을 구성 한다
/*** 설명** Retention RunTime : compile time 과 binary 에도 포함되고, reflection 을 통해 접근 가능합니다.** Target FUNCTION : 생성자를 제외한 함수들에 사용 가능하도록 제한** soIds : so 별 특정 함수 실행 시 so 입력을 위해 사용* actionItem : action item 이 다를 수 있으므로 item 입력을 위해 사용** */@Retention(AnnotationRetention.RUNTIME)@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) // method 에 선언 될 경우 FUNCTION 으로 지정annotation class SpecificActionItem(val soIds: Array<String> = [],val actionItem: Array<String> = [])
- method 상위에 선언 될 annotation 을 구성 한다
- interface 및 class 구성
- 동일 interface 및 param type 을 지정하여 호출에 대한 공통화, Annotation 에 so 및 action item 으로 구분 분리
interface TestSpecificFunService {fun insertChargeByCarplat(param: Map<String, Any>)fun insertChargeByPeopleCar(param: Map<String, Any>)fun insertChargeByWay(param: Map<String, Any>)}@SpecificActionItem(soIds = ["carplat"], actionItem = ["insert_charge"])override fun insertChargeByCarplat(param: Map<String, Any>) {logger.info("insertCharge -> { so : carplat, item : insert_charge, param : $param }")}@SpecificActionItem(soIds = ["peopleCar"], actionItem = ["insert_charge"])override fun insertChargeByPeopleCar(param: Map<String, Any>) {logger.info("insertCharge -> { so : peopleCar, item : insert_charge, param : $param }")}@SpecificActionItem(soIds = ["way"], actionItem = ["insert_charge"])override fun insertChargeByWay(param: Map<String, Any>) {logger.info("insertCharge -> { so : way, item : insert_charge, param : $param }")}
- 동일 interface 및 param type 을 지정하여 호출에 대한 공통화, Annotation 에 so 및 action item 으로 구분 분리
- interface 동적 호출을 위한 DI class 구성
- TestSpecificFunService interfaces 및 하위 method 를 조회하여 soid, actionItem 에 해당하는 함수를 실행
override fun invokeServiceFun(so: String, actionItem: String, param: Map<*, *>): Any? {if (so.isNullOrEmpty() || actionItem.isNullOrEmpty()) {logger.info("[DI LOG] invoke func so or actionItem is Empty -> { so : $so, actionItem : $actionItem }")return null}var invokeResult: Any? = nullapplicationContext.getBeanProvider(TestSpecificFunService::class.java).stream().forEach extraClass@{ clazz ->clazz.javaClass.declaredMethods.forEach extraMethod@{ method ->val extraMethod = method.getAnnotation(SpecificActionItem::class.java) // Annotation method 조회if (!ObjectUtils.isEmpty(extraMethod)) {if (extraMethod.soIds.contains(so) && extraMethod.actionItem.contains(actionItem)) { // soid, actionIteminvokeResult = clazz.javaClass.getMethod(method.name, Map::class.java).invoke(clazz, param)return@extraClass // 일치하는 값이 있을 경우 조회 중인 interface loop 를 종료}} else {return@extraClass}}}return invokeResult}
- TestSpecificFunService interfaces 및 하위 method 를 조회하여 soid, actionItem 에 해당하는 함수를 실행
- Test
- 함수 실행 결과 로그 확인
@Testfun invokeServiceFun() {var so = "carplat"val actionItem = "insert_charge"val param = mapOf("param1" to 1, "param2" to 2)testDiService.invokeServiceFun(so, actionItem, param) // insertCharge -> { so : carplat, item : insert_charge, param : {param1=1, param2=2} }so = "peopleCar"testDiService.invokeServiceFun(so, actionItem, param) // insertCharge -> { so : peopleCar, item : insert_charge, param : {param1=1, param2=2} }so = "way"testDiService.invokeServiceFun(so, actionItem, param) // insertCharge -> { so : way, item : insert_charge, param : {param1=1, param2=2} }}
- 함수 실행 결과 로그 확인
성능 이슈에 대한 검색 내용
초기 호출을 제외하고는 Reflection API를 사용하는 것이 성능에 대한 별 차이가 없는 것에 대한 테스트 블로그 내용 (https://lob-dev.tistory.com/entry/Java%EC%9D%98-Reflection-API)
'개발 > Kotlin' 카테고리의 다른 글
Kotlin 고차함수에 대한 Callback param 전달 (0) | 2022.05.03 |
---|