□ spect Oriented Programming : 관점 지향 프로그래밍
□ 대표적인 기능 Interceptor (배우진 않음)
□ Interceptor 를 활용 하면 Controller 도착 전 특정 작업을 수행 하거나
□ Controller 를 지나 client 에 도착 전 특정 작업을 수행 할 수 있다.
□ kr.co.gudi.config
□ kr.co.gudi.util
□ 객체화 / 의존성주입(어노테이션)
@Component : service가 아닌 페키지들 밖에서 애매한것들에 사용 그럼 쓸 수 있음

□ 컨트롤러를 거치기전에 이곳을 들른다.
□ 반환값이 false 면 컨트롤러에 접근 할 수 없다. (하얀화면을 보여줌)
□ 그래서 false 로 컨트롤러에 가지 못하게 한 다음 response 를 이용해서 다른곳으로 보내는게 일반 적이다.
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler)
throws Exception {
boolean pass = true;
logger.info("=== preHandle ===");
HttpSession session = req.getSession();
if(session.getAttribute("loginId")==null) {
pass = false;
resp.sendRedirect("/"); //context 경로가 있다면 같이 넣어줘야 하낟. /.15_TotalBoard/
}
return pass;
}
□ 컨트롤러를 접근한 후 뷰에 보내지기 전 들른다.
□ 뷰에 보내고 싶은 내용이 있다면 ModelAndView 에 넣어주면 된다.
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler,
ModelAndView mav) throws Exception {
logger.info("=== postHandle ===");
HttpSession session = req.getSession();
String loginId = (String) session.getAttribute("loginId");
mav.addObject("loginId",loginId);
}

□ 모든 요청에 대해서 인터셉터를 걸면 안된다 왜냐하면 서버 죽어버림
□ css, js, image 요청에 대해서도 예외를 꼭 넣어줘야한다 당연히
□ 예외를 둘 요청이 엄청 많기 때문에 리스트로 입력해주는것이 권장된다.
@Configuration □ 이 어노테이션이 있어야 설정 클래스로 인식 된다.
public class InterCeptorConfig implements WebMvcConfigurer {
□ 인터셉터에 등록할 클래스를 가져온다
@Autowired LoginChecker checker;
@Override
public void addInterceptors(InterceptorRegistry registry) {
□ 0. 인터셉터가 예외 둘 URL 패턴을 만든다
List<String> excludeList = new ArrayList<String>();
excludeList.add("/"); □ 메인 페이지 요청은 로그인 체크하면 안된다
excludeList.add("/join*"); □ join 뒤에 뭐가 오든지
excludeList.add("/login.*"); □ login. 뒤에 뭐가 오든지
excludeList.add("/logout*"); □ logout 뒤에 뭐가 오든지
excludeList.add("/resources/**"); // img, css, js 예외처리, /resources/ 뒤에 뭐가 오든지
□ 1. 인터셉터에 등록할 로직 추가
registry.addInterceptor(checker)
.addPathPatterns("/**") □ 2. 인터셉터가 가로챌 URL 패턴을 등록
.excludePathPatterns(excludeList); □ 3. 인터셉터가 예외로 둘 URL 패턴 등록
□ 모든 요청에 대해서 인터셉터를 걸면 안된다 왜냐하면 서버 죽어버림
□ css, js, image 요청에 대해서도 예외를 꼭 넣어줘야한다 당연히
□ 예외를 둘 요청이 엄청 많기 때문에 리스트로 입력해주는것이 권장된다
}
}

□ application.properties에서 static패턴을 resources/** 로 패턴변경
<link rel="stylesheet" href="resources/css/detail.css" type="text/css">
.jsp 에서 link 태그에 hreg = 'resources/' 추가

@Autowired LoginChecker checker; // 인터셉터에 등록 할 클래스를 가져온다.
@Configuration // 이 어노테이션이 있어야 설정 클래스로 인식된다.
// autowired 는 해야겠는데 Service 는 아니므로...
@Component
public class LoginChecker implements HandlerInterceptor{
Logger logger =LoggerFactory.getLogger(getClass());
□ 컨트롤러를 거치기전에 이곳을 들른다.
□ 반환값이 false 면 컨트롤러에 접근 할 수 없다.(하얀화면을 보여줌)
□ 그래서 false 로 컨트롤러에 가지 못하게 한 다음 response 를 이용해서 다른곳으로 보내는게 일반적이다.
@Override
public boolean preHandle(HttpServletRequest req,
HttpServletResponse resp, Object handler) throws Exception {
boolean pass = true;
logger.info("=== PRE HANDLER ===");
HttpSession session = req.getSession();
if(session.getAttribute("loginId") == null) {
pass = false;
resp.sendRedirect("/"); □ context 경로가 있다면 같이 넣어줘야 한다. /15_TotalBoard/
}
return pass;
}
□ 컨트롤러에 접근 한 후 뷰에 보내지기전 들른다.
□ 뷰에 보내고 싶은 내용이 있다면 ModelAndView 에 넣어주면 된다.
@Override
public void postHandle(HttpServletRequest req,
HttpServletResponse resp, Object handler, ModelAndView mav) throws Exception {
logger.info("=== POST HANDLER ===");
HttpSession session = req.getSession();
String loginId = (String) session.getAttribute("loginId");
mav.addObject("loginId", loginId);
}
□ service 딴에서 특정 패키지, 클래스, 매소드 등에 한정하여 매소드가 실행 하기 전과 후 뭔가 실행하고 싶으면 사용
□ File Upload 를 위해서느 commons-fileupload 와 commons-io 라는 라이브러리가 필요 하다.
□ 하지만 Boot 에서는 기본으로 위 라이브러리를 가지고 있다.
□ Application.properties 에서 아래 설정만 추가 해 주자
17_FileService
□ 1개 파일의 용량 제한 ( 파일 단위를 MB를 사용 가능 )
spring.servlet.multipart.max-file-size=50MB
□ 여러 개 파일을 올릴 경우 총 용량 제한
spring.servlet.multipart.max-request-size=500MB
□ 저장될 파일 위치
spring.servlet.multipart.location=C:/upload
@PostMapping(value="/upload")
public String upload(MultipartFile uploadFile) {
logger.info(" upload File : "+uploadFile.getOriginalFilename());
service.upload(uploadFile);
return"redirect:/fileList";
}
@Value("${spring.servlet.multipart.location}")
□ Spring의 환경 설정 파일 (예: application.properties 또는 application.yml)에 정의된 값을 자바 클래스의 필드에 주입하기 위해 사용됩니다. 여기서 ${spring.servlet.multipart.location}는 파일 업로드 시 임시 파일을 저장할 위치를 지정하는 프로퍼티를 나타냅니다.
@Value("${spring.servlet.multipart.location}")
private String root;
public void upload(MultipartFile file) {
// 1. 파일명 추출
String fileName = file.getOriginalFilename();
// 2. 새파일명 생성
String ext = fileName.substring(fileName.lastIndexOf("."));
String newFileName = System.currentTimeMillis()+ext;
logger.info(fileName+" -> "+newFileName);
// 3. 파일 저장
try {
byte[] bytes = file.getBytes();
Path path = Paths.get(root+"/"+newFileName);
Files.write(path, bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
@PostMapping(value="/multiUpload")
public String MultiUpload(MultipartFile[] files) {
logger.info("files length : "+files.length);
service.multiUpload(files);
return"redirect:/fileList";
}
□ 파일 업로드는 단일 업로드 할 때 사용한 upload service 이용하면 됨
public void multiUpload(MultipartFile[] files) {
for (MultipartFile file : files) {
try {
upload(file);
Thread.sleep(1); // 1ms 단위로 업로드 하도록 지연시킴
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
□ 브라우저에 이미지 불러올 수 있도록
@RequestMapping(value="/photo/{fileName}")
public ResponseEntity<Resource> imgView(@PathVariable String fileName) {
logger.info("fileName : "+fileName);
return service.imgView(fileName);
}
□ service 에서 처리 할 수 있도록 생성한다.
public ResponseEntity<Resource> imgView(String fileName) {
// 1. 특정 경로에서 파일을 읽어와 Resource 로 만든다.
Resource resource = new FileSystemResource(root+"/"+fileName);
// 2. 보내질 파일의 형태를 지정해 준다.(헤더에)
HttpHeaders header = new HttpHeaders();
// 예 : image/gif, image/png, image/jpg. image/jpeg
try {
String type = Files.probeContentType(Paths.get(root+"/"+fileName)); // 경로를 주면 해당 파일의 mime=type을 알아낸다.
logger.info("mime-type : "+ type);
header.add("content-type", type);
} catch (IOException e) {
e.printStackTrace();
}
// 보낼 내용 헤더, 상태(200 또는 OK 는 정상이라는 뜻)
return new ResponseEntity<Resource>(resource, header, HttpStatus.OK);
}
□ colsole 에 mime=type타입을 알아 내서 찍히는 내용
// 예 : image/gif, image/png, image/jpg. image/jpeg
[2m2024-05-24 11:42:56.012[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[p-nio-80-exec-8][0;39m [36mkr.co.gudi.controller.FileController [0;39m [2m:[0;39m fileName : 1711528559390.jpg
[2m2024-05-24 11:42:56.012[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[p-nio-80-exec-8][0;39m [36mkr.co.gudi.service.FileService [0;39m [2m:[0;39m mime-type : image/jpeg
[2m2024-05-24 11:42:56.017[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[p-nio-80-exec-9][0;39m [36mkr.co.gudi.controller.FileController [0;39m [2m:[0;39m fileName : 1716515217545.png
[2m2024-05-24 11:42:56.017[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[p-nio-80-exec-9][0;39m [36mkr.co.gudi.service.FileService [0;39m [2m:[0;39m mime-type : image/png
[2m2024-05-24 11:42:56.017[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[-nio-80-exec-10][0;39m [36mkr.co.gudi.controller.FileController [0;39m [2m:[0;39m fileName : 1711528559385.jpg
[2m2024-05-24 11:42:56.018[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[-nio-80-exec-10][0;39m [36mkr.co.gudi.service.FileService [0;39m [2m:[0;39m mime-type : image/jpeg
[2m2024-05-24 11:42:56.020[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[p-nio-80-exec-5][0;39m [36mkr.co.gudi.controller.FileController [0;39m [2m:[0;39m fileName : 1716516883910.png
[2m2024-05-24 11:42:56.021[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[p-nio-80-exec-5][0;39m [36mkr.co.gudi.service.FileService [0;39m [2m:[0;39m mime-type : image/png
[2m2024-05-24 11:42:56.024[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[p-nio-80-exec-1][0;39m [36mkr.co.gudi.controller.FileController [0;39m [2m:[0;39m fileName : 1716516883941.png
[2m2024-05-24 11:42:56.024[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[p-nio-80-exec-1][0;39m [36mkr.co.gudi.service.FileService [0;39m [2m:[0;39m mime-type : image/png
[2m2024-05-24 11:46:34.101[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[on(2)-127.0.0.1][0;39m [36minMXBeanRegistrar$SpringApplicationAdmin[0;39m [2m:[0;39m Application shutdown requested.
[2m2024-05-24 11:46:34.223[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[on(2)-127.0.0.1][0;39m [36mo.apache.catalina.core.StandardService [0;39m [2m:[0;39m Stopping service [Tomcat]
[2m2024-05-24 11:46:34.226[0;39m [32m INFO[0;39m [35m15124[0;39m [2m---[0;39m [2m[on(2)-127.0.0.1][0;39m [36mo.a.c.c.C.[Tomcat].[localhost].[/] [0;39m [2m:[0;39m Destroying Spring FrameworkServlet 'dispatcherServlet'
□ controller 부분
@RequestMapping(value="/download/{fileName}")
public ResponseEntity<Resource> download(@PathVariable String fileName) {
logger.info("download Name : "+fileName);
return service.download(fileName);
}
□ service 부분
public ResponseEntity<Resource> download(String fileName) {
// 1. 특정 경로에서 파일을 읽어와 Resource 로 만든다.
Resource resource = new FileSystemResource(root+"/"+fileName);
// 2. 보내질 파일의 형태를 지정해 준다.(헤더에)
HttpHeaders header = new HttpHeaders();
try {
// image/... 는 이미지 text/... 는 문자열, application/octet-stream 는 바이러리를 의미함
header.add("content-type","application/octet-stream"); //content=type
// 보낼 파일 이름
// content=Disposition 는 내다 보내려는 컨텐트의 형태를 의미한다. inline 이면 문자열, attachment 는 다운로드 파일을 의미
// attachment;filename="fileName.jpg"
// 이 떄 파일명이 한글일 경우 깨져서 다운로드 된다. 그래서 안전하게 인코딩 해준다
String oriFile = URLEncoder.encode("이미지_"+fileName, "UTF-8");
header.add("content=Disposition", "attachment;filename=\""+fileName+"\"");
} catch (Exception e) {
e.printStackTrace();
}
// 보낼 내용 헤더, 상태(200 또는 OK 는 정상이라는 뜻)
return new ResponseEntity<Resource>(resource, header, HttpStatus.OK);
}
일정관리,
□ Spring Boot Scheduler 는 별도의 라이브러리나 설정없이 사용 가능 하다. (쉬움)
□ 사용할 클래스에 @EnabledScheduling 을 추가 하고
□ 사용할 메서드에 @Scheduled 메서드를 사용하여 동작 할 수 있도록 한다.

★★ cron : 리눅스에 특정한 쉘을 특정 시간마다 돌려주는거 ([자동으로] 리눅스에서 채용)
@EnableScheduling
@Component
public class ScheduleUtil {
// 유의점 : 스케쥴러는 프로그램과 생명주기를 함계 한다. -> 켜질때 같이 켜지고 꺼질떄 같이 꺼진다.
Logger logger = LoggerFactory.getLogger(getClass());
/*@Scheduled(fixedDelay = 1000)
public void fixedDelay() {
logger.info("이전 작업이 끝나고 1초 후 실행 "); // 이전작업이 끝나면 거기서 부터 1초
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
@Scheduled(fixedRate = 1000)
public void fixedRate() {
logger.info("무조건 1초 마다 실행 "); // 무조건 1초
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}