java/배치 & Scheduling

ScheduledExecutorService

내가 만드는게 길이 된다 2023. 10. 24. 13:27
package com.twokimss.config;

import javax.annotation.PostConstruct;

import org.apache.ibatis.javassist.bytecode.stackmap.TypeData.ClassName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

import com.twokimss.web.batch.service.ScheduleExec;
import com.twokimss.web.batch.service.SchedulerFuture2Service;
import com.twokimss.web.batch.service.SchedulerFutureService;
import com.twokimss.web.batch.service.SchedulerService;
import com.twokimss.web.util.ContextUtil;

//스케쥴 테스트하고 싶을때 아래 3개 주석 풀어서 사용하기
@Configuration
@EnableScheduling
@EnableAsync
public class ScheduleConfig {
	// 스프링 부트에서 스케줄러 기능을 사용하기 위해 @EnableScheduling 설정

	private Logger logger = LoggerFactory.getLogger(ClassName.class);
	
	//@ConditionalOnBean(ContextUtil.class)

	@Autowired
	private SchedulerService schedulerService;

	@Autowired
	private SchedulerFutureService schedulerFutureService;
	
	@Autowired
	private SchedulerFuture2Service schedulerFuture2Service;	
	

//	1. Component는 class level에 선언,
//	   Bean은 메소드 레벨에 선언
//	2. Bean을 사용시 Configuration 어노테이션을 꼭 사용
//	   또는 @SpringBootApplication 어노테이션이 들어간 스프링 실행부에 사용
//	3. Bean은 외부 라이브러리가 제공하는 객체를 사용할 때
//	   Component는 내부에서 직접 접근 가능한 클래스에 사용
//	4. Bean은 유연한 빈 등록이 필요할때 사용(환경파일읽어서 동적생성)
	
	
	//순서: 생성자, @PostConstruct, @Bean

	public ScheduleConfig() {
		logger.debug("ScheduledConfig=================>생성자");
	}

	@PostConstruct
	public void init() {
		logger.debug("ScheduledConfig=================>init");

		//테스트할때 아래 열고하기...
		//schedulerFutureService.start();
		//schedulerFuture2Service.scheduleJob("biz003");
		

//      Thread.sleep(10000);
//      schedulerFutureService.changeCron("*/3 * * * * *");
//      Thread.sleep(20000);	    

	}
	
	@Bean
	public ScheduleExec scheduler() {
		logger.debug("ScheduleExec=================>ScheduleExec Bean 등록");
		
		ScheduleExec scheduleExec = new ScheduleExec();
		
		scheduleExec.createScheduler();
		
		return scheduleExec;
	}	

//	@Bean
//	public TaskScheduler scheduler() {
//		logger.debug("ScheduledConfig=================>TaskScheduler Bean 등록");
//		
//		ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
//		scheduler.setPoolSize(4);
//		scheduler.setThreadNamePrefix("threadBiz-");//threadNamePrefix
//		scheduler.setBeanName("threadPoolTaskScheduler");
//		scheduler.initialize();
//
//		
//		return scheduler;
//	}

}

 

 

package com.twokimss.web.batch.service;

import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;

import org.apache.ibatis.javassist.bytecode.stackmap.TypeData.ClassName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;


public class ScheduleExec {
	
	private Logger logger = LoggerFactory.getLogger(ClassName.class);
	
	
	@Autowired
	private JobBeforeProc jobBeforeProc;
	
		
	private String beanType = "";
	private int SINGLE_POOL_SIZE = 4;
	private long delay = 1000 * 2; //2초 주기
	
	private ScheduledExecutorService scheduler = null;
	private HashMap<String, ScheduledFuture<?>> mapJobs = null;
	


	public ScheduleExec() {
		logger.debug("ScheduleExec=========================>");
		this.mapJobs = new HashMap<String, ScheduledFuture<?>>();
		this.beanType = JobConst.BEAN_TYPE_ONLY_CLASS;
	}
	
	
	public void createScheduler() {
		logger.debug("createScheduler=========================>");
		this.scheduler = Executors.newScheduledThreadPool(SINGLE_POOL_SIZE);
	}
	
	@PostConstruct
	public void startScheduleList() {
		logger.debug("startScheduleList=========================>");
		
		//사용할 배치 작업 여기에 등록
		//현재는 scheduleWithFixedDelay 만 사용했는데, next trigger 운영환경에서 변경할수 있도록 매소드 추가 필요. (Cron, Rate, Fix....)
		
		this.scheduleJob("job00401"); 
		this.scheduleJob("job00402");
		
	}


	public void scheduleJob(String jobKey) {
		logger.debug("scheduleJob start=========================>");
		logger.debug("futureKey=================================>"+jobKey);
				
		
		HashMap<String,Object> mapRtn = jobBeforeProc.getClassNm(jobKey, this.beanType);
		if("-1".equals(mapRtn.get("rtnCd").toString())){
			logger.debug("futureKey================["+jobKey+"]실행중지된 작업");
			return;
		}
		
		//this.scheduler.scheduleWithFixedDelay(null, delay, delay, null)

		logger.debug("scheduleJob start========classNm=================>"+mapRtn.get("classNm").toString());
		
		Runnable runnable = new JobRunnable(jobKey, mapRtn); //jobKey 에 맞는 Biz Class Get
		ScheduledFuture<?> job = this.scheduler.scheduleWithFixedDelay(runnable, 0, this.delay, TimeUnit.MILLISECONDS); //schedule 에 Biz Class 등록
		//ScheduledFuture<?> future004 = this.scheduler.scheduleWithFixedDelay(schedulerBiz004, 0, this.delay, TimeUnit.MILLISECONDS);
 
		this.mapJobs.put(jobKey, job); //pause, delete, reschedule 용으로 담아두기
	}

	
	public void pauseDeleteJob(String jobKey) {
		
		if(jobKey == null|| jobKey.isBlank()) return;
		if(this.mapJobs == null || this.mapJobs.isEmpty()) return;
		
		ScheduledFuture<?> targetJob = this.mapJobs.get(jobKey);
				
		if(targetJob == null) return;
		if(targetJob.isCancelled()) return;
		if(targetJob.isDone()) return;
		
		targetJob.cancel(true); 
	}
	
	
	public void rescheduleJob(String jobKey) {

		this.pauseDeleteJob(jobKey);

		this.mapJobs.remove(jobKey);

		this.setDelay(1000 * 10);

		this.scheduleJob(jobKey);

	}	
	
	
	private void setDelay(long delay) {
		this.delay = delay;
		
		logger.debug("setDelay=====>"+this.delay);
	}		
	
	
	public void shutdownScheduler(boolean isNow) {
		if(isNow) {
			logger.debug("shutdownScheduler=====>true");
			
			this.scheduler.shutdownNow();
		}else {
			logger.debug("shutdownScheduler=====>false");
			
			this.scheduler.shutdown();
		}
	}
	
	

}
package com.twokimss.web.batch.service;

import java.io.Serializable;

public class JobConst implements Cloneable, Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	
	public static final String BEAN_TYPE_WITH_PACKAGE = "1";
	public static final String BEAN_TYPE_ONLY_CLASS = "2";	

}
package com.twokimss.web.batch.service;

import java.util.HashMap;

import org.apache.ibatis.javassist.bytecode.stackmap.TypeData.ClassName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component("jobBeforeProc")
public class JobBeforeProc {
	
	private Logger logger = LoggerFactory.getLogger(ClassName.class);
	
	
	public HashMap<String, Object> getClassNm(String jobKey, String beanType) {
		HashMap<String, Object> mapRtn = new HashMap<String,Object>();
		
		if(JobConst.BEAN_TYPE_WITH_PACKAGE.equals(beanType)) {
			mapRtn = this.getBizClassNmWithPackage(jobKey);
		}else if(JobConst.BEAN_TYPE_ONLY_CLASS.equals(beanType)) {
			mapRtn = this.getBizClassNmOnlyClass(jobKey);
		}
		mapRtn.put("beanType", beanType);
		return mapRtn;
	}		


	//db/xml ( sm 하기 편한 방법 찾아서 변경 필요 ) : 운영하다가 사용여부 변경하고, 정지시킬수 있도록 처리 필요.
	private HashMap<String, Object> getBizClassNmWithPackage(String jobKey) {
		logger.debug("getBizClassNmWithPackage===============>");
		
		HashMap<String,Object> mapRtn = new HashMap<String, Object>();
		mapRtn.put("classNm", "");
		mapRtn.put("rtnCd", "-1");
		mapRtn.put("rtnMsg", "");
		
		String sPackage = "com.twokimss.web.batch.service.";
		
		
		switch (jobKey) {
		case "job00401":
			mapRtn.put("classNm", sPackage+"JobBiz00401");
			mapRtn.put("rtnCd", "0");
			mapRtn.put("rtnMsg", "가능");
			break;
		case "job00402":
			mapRtn.put("classNm", sPackage+"JobBiz00402");
			mapRtn.put("rtnCd", "0");
			mapRtn.put("rtnMsg", "가능");			
			break;			
		default:
			break;
		}
		
		
		return mapRtn;
		
	}
	

	private HashMap<String, Object> getBizClassNmOnlyClass(String jobKey) {
		
		logger.debug("getBizClassNmOnlyClass===============>");
		
		HashMap<String,Object> mapRtn = new HashMap<String, Object>();
		mapRtn.put("classNm", "");
		mapRtn.put("rtnCd", "-1");
		mapRtn.put("rtnMsg", "");
		
		
		switch (jobKey) {
		case "job00401":
			mapRtn.put("classNm", "jobBiz00401");
			mapRtn.put("rtnCd", "0");
			mapRtn.put("rtnMsg", "가능");
			break;
		case "job00402":
			mapRtn.put("classNm", "jobBiz00402");
			mapRtn.put("rtnCd", "0");
			mapRtn.put("rtnMsg", "가능");			
			break;			
		default:
			break;
		}
		
		
		return mapRtn;
		
	}



		
}
package com.twokimss.web.batch.service;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;

import org.apache.ibatis.javassist.bytecode.stackmap.TypeData.ClassName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.twokimss.web.util.ContextUtil;

public class JobRunnable implements Runnable {
	private Logger logger = LoggerFactory.getLogger(ClassName.class);

	private String jobKey = "";
	private String classNm = "";
	private String beanType = null; 

	public JobRunnable() {
	}

	public JobRunnable(String jobKey, HashMap<String, Object> clssInfo) {
		this.jobKey = jobKey;
		this.classNm = clssInfo.get("classNm").toString();
		this.beanType = clssInfo.get("beanType").toString();
	}

	@Override
	public void run() {

		this.invokeClass(this.beanType);

	}


	private void invokeClass(String beanType) {
		
		try {
			if (beanType.equals(JobConst.BEAN_TYPE_WITH_PACKAGE)) {

				this.invokeWithPackage();

			} else if (beanType.equals(JobConst.BEAN_TYPE_ONLY_CLASS)) {

				this.invokeOnlyClass();
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	private void invokeOnlyClass() {
		
		try {
			String methodNm = "execute";
			// Object[] methodArgs = null;
			Method method = null;
			Object obj = null;			
            
			////ContextUtil.getBean 방식으로 했더니 톰켓 start 할때 nullpoint 에러 나서. ( 실행할때는 문제가 없는데 )	
			try {
				obj = ContextUtil.getBean(this.classNm); // 스프링에 등록된 서비스 클래스 가져오기
			} catch (NullPointerException ee) {
				logger.debug("ContextUtil.getBean 로드 에러");
				return;
			}

			String taskClassName = obj.getClass().getName().split("\\$")[0];

			Class<?> taskClass = getClass().getClassLoader().loadClass(taskClassName);

			method = taskClass.getDeclaredMethod(methodNm);
			// methodArgs = new Object[] {}; //메서드 param
			// method.invoke(obj, methodArgs);
			method.invoke(obj);			
			
		} catch (Exception e) {

		}

	}

	private void invokeWithPackage() {
		
		try {
			
			String methodNm = "execute";
			// Object[] methodArgs = null;

			Class<?> clss = Class.forName(this.classNm);

			Constructor<?> constructor = clss.getConstructor();
			Object obj = constructor.newInstance();

			Method method = clss.getDeclaredMethod(methodNm);
			// methodArgs = new Object[] {}; //메서드 param
			// method.invoke(obj, methodArgs); //파라미터 있을때
			method.invoke(obj);			
			
		} catch (Exception e) {
			
		}

	}

}

 

package com.twokimss.web.batch.service;

import java.util.concurrent.CompletableFuture;

import org.apache.ibatis.javassist.bytecode.stackmap.TypeData.ClassName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service("jobBiz00401")
public class JobBiz00401 {

	private Logger logger = LoggerFactory.getLogger(ClassName.class);
	

	private JobSyncBiz jobSyncBiz;
	private JobAsyncBiz jobAsyncBiz;
	

	public JobBiz00401() {
		this.jobSyncBiz = new JobSyncBiz();
		this.jobAsyncBiz = new JobAsyncBiz();
	}
	
	public void execute() {
		//Biz 로직 구현
		logger.info("[00401===>실행 시작] : {}", JobDateUtil.getDateTime());
		
		this.jobSyncBiz.saveFileInfo();
		//this.jobAsyncBiz.createFile();		

		//비동기 실행때문에 CompletableFuture 에서 호출		
		CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
				this.jobAsyncBiz.createFile();
			}
		);
		
		logger.info("[00401===>실행 종료] : {}", JobDateUtil.getDateTime());
	}
	
	

//	public void execute() {
//		
//		logger.info("[00401===>실행 시작] : {}", JobDateUtil.getDateTime());
//
//		try {
//			Thread.sleep(1000 * 2); //2초 대기
//			
//			
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}	
//		
//		logger.info("[00401===>실행 종료] : {}", JobDateUtil.getDateTime());
//		
//	}


}

 

package com.twokimss.web.batch.service;

import org.apache.ibatis.javassist.bytecode.stackmap.TypeData.ClassName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service("jobBiz00402")
public class JobBiz00402 {

	private Logger logger = LoggerFactory.getLogger(ClassName.class);


	public void execute() {
		//Biz 로직 구현
		
		logger.info("[00402===>실행 시작] : {}", JobDateUtil.getDateTime());

		try {
			Thread.sleep(1000 * 2); //2초 대기
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		
		
		logger.info("[00402===>실행 종료] : {}", JobDateUtil.getDateTime());
		
	}


}

 

 

package com.twokimss.web.login.controller;

import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.javassist.bytecode.stackmap.TypeData.ClassName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import com.twokimss.web.batch.service.SchedulerFuture2Service;
import com.twokimss.web.batch.service.ScheduleExec;
import com.twokimss.web.batch.service.SchedulerFutureService;

@Controller
public class MyAjaxController {
	
	private Logger logger = LoggerFactory.getLogger(ClassName.class);
	
       
    
    @Autowired
    private ScheduleExec scheduleExec;   
    
	@RequestMapping(value="/pauseDeleteJob.do")
	@ResponseBody
	public void pauseDeleteJob(HttpServletRequest req) {
		trace("[pauseDeleteJob]=====================start");	
		//http://localhost:9080/pauseDeleteJob.do?jobKey=job00401
		//http://localhost:9080/pauseDeleteJob.do?jobKey=job00402
		
		Object obj = req.getParameter("jobKey");
		
		if(!ObjectUtils.isEmpty(obj)) {
			String jobKey = obj.toString();
			
			logger.debug("jobKey===============>{}", jobKey);

			scheduleExec.pauseDeleteJob(jobKey);			
		}
	}	
	
	@RequestMapping(value="/rescheduleJob.do")
	@ResponseBody
	public void rescheduleJob(HttpServletRequest req) {
		trace("[rescheduleJob]=====================start");	
		//http://localhost:9080/rescheduleJob.do?jobKey=job00401
		//http://localhost:9080/rescheduleJob.do?jobKey=job00402
		
		Object obj = req.getParameter("jobKey");
		
		if(!ObjectUtils.isEmpty(obj)) {
			String jobKey = obj.toString();
			
			logger.debug("jobKey===============>{}", jobKey);

			scheduleExec.rescheduleJob(jobKey);			
		}
	}	
	
	@RequestMapping(value="/shutdownScheduler.do")
	@ResponseBody
	public void shutdownScheduler(HttpServletRequest req) {
		trace("[shutdownScheduler]=====================start");	
		//http://localhost:9080/shutdownScheduler.do?isNow=true
		//http://localhost:9080/shutdownScheduler.do?isNow=false
		
		Object obj = req.getParameter("isNow");
		
		if(!ObjectUtils.isEmpty(obj)) {
			boolean isNow = Boolean.parseBoolean(obj.toString());
			
			logger.debug("isNow===============>{}", isNow);

			scheduleExec.shutdownScheduler(isNow);			
		}else {
			scheduleExec.shutdownScheduler(true);
		}
	}		
	
	@RequestMapping(value="/changeCron.do")
	@ResponseBody
	public void changeCron() {
		trace("[changeCron]=====================start");	
		
	
		
//		try {
//			Thread.sleep(10000);
//			//schedulerFutureService.changeCron("*/5 * * * * *");//5초 주기로 변경
//			//schedulerFuture2Service.reScheduleJob(1000*5);//5초 주기로 변경
//			Thread.sleep(20000);
//			
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
	
	}
	

	

	// 요청 매핑 어트리뷰트
	@RequestMapping(value = "/hello.do")
	@ResponseBody
	public String helloworld(Model model) {
	
		ExecutorService execTP = Executors.newCachedThreadPool();

		// 오래걸리는업무 (스레드)
		execTP.submit(() -> {
			trace("[오래걸리는업무]=====================start");
			try {
				Thread.sleep(5000); //5초
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			trace("[오래걸리는업무]=======================end");
		});

		// 작업2
		trace("[간단업무]=====================start"); 
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		trace("[간단업무]=====================end");

		execTP.shutdown();
		
//		HashMap<String,Object> mapResult = new HashMap<String, Object>();
//		mapResult.put("RTN_CODE", "0");
//		mapResult.put("RTN_MSG", "성공적으로 처리되었습니다.");

		// 결과는 hello world
		return "hello world react call"; //Json return
		
	}
	
	// 요청 매핑 어트리뷰트
	@RequestMapping(value = "/helloJson.do")
	@ResponseBody
	public ModelAndView helloJson(Model model) {
		trace("[helloJson.do]=====================start");
		
		ModelAndView mv = new ModelAndView("/helloJson");
	
		ExecutorService execTP = Executors.newCachedThreadPool();

		// 오래걸리는업무 (스레드)
		execTP.submit(() -> {
			trace("[오래걸리는업무]=====================start");
			try {
				Thread.sleep(5000); //5초
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			trace("[오래걸리는업무]=======================end");
		});

		// 작업2
		trace("[간단업무]=====================start");
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		trace("[간단업무]=====================end");

		execTP.shutdown();
		
		HashMap<String,Object> mapResult = new HashMap<String, Object>();
		mapResult.put("RTN_CODE", "0");
		mapResult.put("RTN_MSG", "성공적으로 처리되었습니다.");
		
		logger.debug("mapResult========={}", mapResult);
		
		mv.addObject("mapResult", mapResult);
		mv.addObject("msg", "테스트입니다");

		// 결과는 hello world
		//return "hello world react call"; //Json return
		
		return mv;
		
	}	

	// 출력을 어떤 스레드에서 하고 있는지 확인
	private static void trace(String strLog) {
		System.out.println(Thread.currentThread().getName() + ">>>>" + strLog);
	}
}
package com.twokimss.web.batch.service;

import org.apache.ibatis.javassist.bytecode.stackmap.TypeData.ClassName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service("jobAsyncBiz")
public class JobAsyncBiz {
	
	//오래걸리는 업무 : 비동기 
	
	private Logger logger = LoggerFactory.getLogger(ClassName.class);
	
	@Async
	public void createFile() {
		
		logger.info("[createFile==============>실행 시작] : {}", JobDateUtil.getDateTime());
		
		try {
			Thread.sleep(1000 * 20); //20초 대기
			
			//물리파일 생성 작업 여기에 코딩...
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}			
		
		logger.info("[createFile=============>실행 종료] : {}", JobDateUtil.getDateTime());
	}		

}

 

 

package com.twokimss.web.batch.service;

import org.apache.ibatis.javassist.bytecode.stackmap.TypeData.ClassName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service("jobSyncBiz")
public class JobSyncBiz {
	
	//빨리 끝나는 업무 : 동기
	
	private Logger logger = LoggerFactory.getLogger(ClassName.class);
	
	
	public void saveFileInfo() {
		
		logger.info("[saveFileInfo===>실행 시작] : {}", JobDateUtil.getDateTime());
		
		
		try {
			Thread.sleep(1000 * 2); //2초 대기
			
			//파일정보 db 저장 관련 로직 여기에..
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		
		
		logger.info("[saveFileInfo===>실행 종료] : {}", JobDateUtil.getDateTime());
	}

}