public class HandlerFactory implements InitializingBean {
private final ApplicationContext applicationContext;
private static Map<String, RouteHandler> handlerMap = null;
private static Map<String, Boolean> handlerBodyRefBoolMap = null;
private static LocalDateTime lastModifiedDate = null;
@Autowired
private AdmHndlrDplyService admHndlrDplyService;
public static void process(String handlerType, ServerWebExchange exchange, TransactionDto transaction) throws InternalRuntimeException {
String errorHandler = null;
try {
// API ID extraction
AdmApiDply apiVo = transaction.getComn().getApi();
List<String> apiHandlerList = null;
// 처리 대상 핸들러 그룹 추출
if (handlerType.equals(RequestHandlerFilter.REQUEST_HANDLER)) {
if (apiVo.getReqHndlr() != null) {
apiHandlerList = apiVo.getReqHndlr();
}
} else if (handlerType.equals(ResponseHandlerFilter.RESPONSE_HANDLER)) {
if (apiVo.getResHndlr() != null) {
apiHandlerList = apiVo.getResHndlr();
}
}else if (handlerType.equals(ErrorHandler.ERROR_HANDLER)) {
if (apiVo.getErrHndlr() != null) {
errorHandler = apiVo.getErrHndlr();
}
}
// Loop를 수행하면서 핸들러 기능 처리
if (apiHandlerList != null) {
for (String routeHandlerId : apiHandlerList) {
RouteHandler routeHandler = handlerMap.get(routeHandlerId);
if (!routeHandler.process(transaction)) {
break;
}
}
} else if(StringUtils.hasText(errorHandler)){
RouteHandler routeHandler = handlerMap.get(errorHandler);
if (!routeHandler.process(transaction)) {
transaction.setError(AgwErrorDto.INTERNAL_SERVER_ERROR);
}
}
} catch (InternalRuntimeException e) {
log.error("Request handling processing failure", e);
transaction.setError(AgwErrorDto.INTERNAL_SERVER_ERROR);
} catch (Exception e) {
log.error("Request handling processing failure", e);
transaction.setError(AgwErrorDto.INTERNAL_SERVER_ERROR);
}
if (transaction.getErr() != null && !StringUtils.hasText(errorHandler)) {
throw new InternalRuntimeException("API handling failure");
}
}
@Override
public void afterPropertiesSet() throws InternalException {
log.info("HandlerFactory.afterPropertiesSet - Start");
handlerMap = new ConcurrentHashMap<String, RouteHandler>();
handlerBodyRefBoolMap = new ConcurrentHashMap<String, Boolean>();
synchronizeDB();
log.info("HandlerFactory.afterPropertiesSet - End");
}
/**
* Handler synchronization
* @throws InternalException
*/
@Scheduled(fixedDelayString = "${common.handler.schedule.dbSync}", timeUnit = TimeUnit.SECONDS)
public void synchronizeDB() throws InternalException {
List<AdmHndlrDply> dbHndlrList = null;
// 마지막 동기화 시점 시간을 비교하여 데이터 동기화 처리
if (lastModifiedDate == null) {
// 최초 데이터 조회
dbHndlrList = admHndlrDplyService.getAdmHndlrList("DPLY");
} else {
// 변경분 데이터 조회
dbHndlrList = admHndlrDplyService.getAdmHndlrList(lastModifiedDate);
}
try {
// 핸들러 동기화
if(dbHndlrList != null && dbHndlrList.size() > 0) {
setHandler(dbHndlrList);
AdmHndlrDply lastHndlr = dbHndlrList.stream().max((o1, o2) -> o1.getDplyDt().compareTo(o2.getDplyDt())).get();
lastModifiedDate = lastHndlr.getDplyDt();
}
} catch (Exception e) {
log.info("hndlr or hndlrGroup load fail", e);
}
}
public void setHandler(List<AdmHndlrDply> hndlrList) throws InternalException, ClassNotFoundException {
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
for(AdmHndlrDply hndlr : hndlrList) {
if("DPLY".equals(hndlr.getDplyType())){
if(!hndlr.getType().equals("SYS")) { // 커스텀 핸들러 로딩
try {
//CompilerUtils (즉, 자바 컴파일러로 컴파일 .java를 컴파일해서 .class로 변경시킬때 - 컴파일러를 호출)
Class<?> cls = CompilerUtils.compileSource(hndlr.getCstmClassAdr(), hndlr.getCstmClassSrc());
if(defaultListableBeanFactory.containsBeanDefinition(hndlr.getHndlrId())){
defaultListableBeanFactory.removeBeanDefinition(hndlr.getHndlrId());
}
//스프링 컨테이너에 빈을 등록할때 일반적으로 블로킹 작업이 걸리지는 않지만, 의존성이 많거나 초기작업등이 길어지면 성능이 많이 저하되어 마치 블로킹과 같은 현상이 일어날수도 있다.
defaultListableBeanFactory.registerBeanDefinition(hndlr.getHndlrId(), BeanDefinitionBuilder.genericBeanDefinition(cls).getRawBeanDefinition());
} catch (Exception e) {
throw new InternalException("Custom handler load failed", e);
}
}
// 핸들러로 정의된 Bean 로딩
RouteHandler routeHandler = (RouteHandler) applicationContext.getBean(hndlr.getHndlrId());
if (!routeHandler.init()) {
throw new InternalException("Handler initialization failed : " + hndlr.getHndlrId());
}
log.info("HandlerFactory.setHandler - Load : " + hndlr.toString());
handlerMap.put(hndlr.getHndlrId(), routeHandler);
handlerBodyRefBoolMap.put(hndlr.getHndlrId(),hndlr.isBodyRfrn());
}else{
if(!hndlr.getType().equals("SYS")) { // 커스텀 핸들러 로딩
if(defaultListableBeanFactory.containsBeanDefinition(hndlr.getHndlrId())){
defaultListableBeanFactory.removeBeanDefinition(hndlr.getHndlrId());
}
}
handlerMap.remove(hndlr.getHndlrId());
handlerBodyRefBoolMap.remove(hndlr.getHndlrId());
log.info("HandlerFactory.setHandler - Remove : " + hndlr.toString());
}
}
}
public static boolean isHandlerGroupBodyRef(List<String> hndlrList) {
if(CollectionUtils.isEmpty(hndlrList)){
return false;
}
for(String hndlrId:hndlrList){
if(handlerBodyRefBoolMap.get(hndlrId)){
return true;
}
}
return false;
}
@ReadOperation
public Map<String, Object> handlerFeatures() {
Map<String, Object> hndlrInfoMap = new HashMap<>();
hndlrInfoMap.put("handler", handlerMap.keySet());
return hndlrInfoMap;
}
}
**//CompilerUtils (즉, 자바 컴파일러로 컴파일 .java를 컴파일해서 .class로 변경시킬때 - 컴파일러를 호출)**
Class<?> cls = CompilerUtils.compileSource(hndlr.getCstmClassAdr(), hndlr.getCstmClassSrc());
<aside> 💬 아무래도 컴파일러는 독립된 프로그램이다보니, 소스 중간에 호출하게되면 I/O작업이 되지 않을까 한다.
</aside>
**//스프링 컨테이너에 빈을 등록할때 일반적으로 블로킹 작업이 걸리지는 않지만, 의존성이 많거나 초기작업등이 길어지면 블로킹이 될 가능성이 있다.**
defaultListableBeanFactory.registerBeanDefinition(hndlr.getHndlrId(), BeanDefinitionBuilder.genericBeanDefinition(cls).getRawBeanDefinition());
<aside> 💬 빈 등록이 길어질경우 다른 스레드들에 과부하가 걸릴 경우도 생길수 있다. 최악의 경우 블로킹과 같은 현상이 일어날수도 있다고 본다. (but not blocking)
</aside>
참고 : 핸들러 내용은 상관하지 않는다.