[TIL] 2024-08-14

성장일기·2024년 8월 18일

회고

목록 보기
29/37

중요 학습 내용 [SpringBoot]

Handler method

  • @RequestParam

    • defaultValue: 사용자의 입력값이 없거나("") request의 parameter 키 값과 일치 않는 매개변수일 때 사용하고 싶은 값을 default값으로 설정 가능

    • name: request parameter의 키 값과 다른 매개변수 명을 사용하고 싶을 때 request parameter의 키 값을 작성(@Requestparam 어노테이션 생략 가능)

    • use

          @PostMapping("modify")
          public String modify(
                  Model model,
                  @RequestParam(defaultValue = "디폴트", name = "name") String name,
                  @RequestParam(defaultValue = "0") int modifyPrice
          ) {
              String message = name + " has modified to " + modifyPrice;
              System.out.println(modifyPrice);
              model.addAttribute("message", message);
      
              return "first/messagePrinter";
          }
  • Command Object: 핸들러 메소드의 매개변수에 POJO 클래스를 스프링이 객체로 만들며 내부적으로 setter를 활용해 값도 주입해줌

  • @ModelAttribute 어노테이션에는 어트리뷰트 키 값을 지정 가능 (키 값이 없을 땐 타입을 활용 가능)

  • use

        @PostMapping("search")
        public String search(@ModelAttribute("menu") MenuDTO menu) {
            System.out.println("menu = " + menu);
            return "searchResult";
        }

Session 관리

  • HttpSession에 담아 관리
  • use
    @PostMapping("login")
    public String sessionLogin(HttpSession session, @RequestParam String id) {
        session.setAttribute("id", id);
        return "loginResult";
    }

    @GetMapping("logout")
    public String logout(HttpSession session) {
        session.invalidate();
        return "login";
    }
  • @SessionAttribute 방식으로 session에 담아 관리
  • use
@Controller
@SessionAttributes("id")
public class FirstController {

    ...
    @PostMapping("login")
    public String sessionLogin(Model model, @RequestParam String id) {
        model.addAttribute("id", id);
        return "loginResult";
    }

    // 설명. @SessionAttribute 방식으로 session에 담긴 값은 SessionStatus 에서 제공하는 setComplete으로 만료시킨다.
    @GetMapping("logout")
    public String logout(SessionStatus sessionStatus) {
        sessionStatus.setComplete();
        return "loginResult";
    }
}

View resolver

  • ViewResolver(뷰 리졸버) 인터페이스를 구현한 ThymeleafViewResolver가 처리
  • 접두사(prefix): resources/templates/
  • 접미사(suffix): .html
  • 핸들러 메소드가 반환하는 String 값 앞 뒤에 접두사 및 접미사를 붙여 view를 찾게 된다.
  • SSR의 경우, welcome page에서 동적 데이터를 요구하는 경우가 많기에 index.html로 시작하는 것이 아닌, main.html로 랜더링시킴

@Controller
public class MainController {
    @RequestMapping(value= {"/", "/main"})
    public String main() {
        return "main";
    }
}
Model
ModelAndView
RedirectAttributes
flashAttribute
  • RedirectAttributes에 값을 담으면 리다이렉트 시에도 값(전달할 상태)이 유지됨
    @GetMapping("string-redirect-attr")
    public String stringRedirectionFlashAttribute(RedirectAttributes rttr) {
        rttr.addFlashAttribute("flashMessage1", "리다이렉트 attr 사용하여 redirect...");
        return "redirect:/";
    }
  • 단순 forward 반환 시 ModelAndView에 값 전달
    @GetMapping("modelandview")
    public ModelAndView modelAndViewTest(ModelAndView mv) {
        mv.addObject("message2", "ModelAndView를 이용한 forward");
        mv.setViewName("result");

        return mv;
    }
  • redirect 시에는 ModelAndView에 쿼리스트링 형태로 리다이렉트 됨
    @GetMapping("modelandview-redirect")
    public ModelAndView modelAndViewRedirectTest(ModelAndView mv) {
        mv.addObject("message2", "ModelAndView를 이용한 redirect");
        mv.setViewName("redirect:/");

        return mv;
    }
  • flashAttribute
    @GetMapping("modelandview-redirect-attr")
    public ModelAndView modelAndViewRedirectFlashAttribute(ModelAndView mv, RedirectAttributes rttr) {
        rttr.addFlashAttribute("flashMessage2", "ModelAndView를 이용한 redirect attribute");
        mv.setViewName("redirect:/");

        return mv;
    }

Exception handler

with Bean(Global)

  • SimpleMappingExceptionResolver
@Configuration
public class RootConfiguration {

    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

        Properties props = new Properties();
        props.setProperty("java.lang.NullPointerException", "error/nullPointer");

        exceptionResolver.setExceptionMappings(props);

        exceptionResolver.setDefaultErrorView("error/default");
        exceptionResolver.setExceptionAttribute("exceptionMessage");

        return exceptionResolver;
    }

}

in Controller

  • 특정 Controller Class에서 특정 에러 local로 설정 가능

    @ExceptionHandler(NullPointerException.class)
    public String nullPointerExceptionHandler() {
        System.out.println("이 Controller에서 NullPointerException 발생 시 logging");
        return "error/nullPointer";
    }

Interceptor

목적

  • 로그인 체크, 권한 체크, 프로그램 실행 시간 계산 작업 로그 처리, 업로드 파일 처리, 로케일(지역) 설정 등
  • static resource에 대한 경로는 handler method로 인식하지 않게 처리

vs AOP

  • handler method로 가기 전에 bean들에 대한 접근 가능하게 하는 도구
    -> 따라서 특정 작업의 (handler method의) 전/후처리 가능

    • ex. 관리자는 controller를 진행하지 않고, service에 접근하는 등 여러 작업 가능 - db에서 권한 데이터를 먼저 가져오는 등

    • 흐름이 어지럽혀지지 않는 범위 내에서 다양하게 활용 가능

    • use. 요청 시간 측정

      • // 설명. Interceptor 설정
        @Configuration
        public class StopwatchInterceptor implements HandlerInterceptor {
        
            private final MenuService menuService;
        
            @Autowired
            public StopwatchInterceptor(MenuService menuService) {
                this.menuService = menuService;
            }
        
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
                menuService.method();
        
                System.out.println("preHandle() 호출함...(핸들러 메소드 이전)");
        
                long startTime = System.currentTimeMillis();
                request.setAttribute("startTime", startTime);
        
                // 설명. 반환형을 false로 하면 특정 조건에 의해 이후 핸들러 메소드가 실행되지 않게 할 수도 있다.
        //        return false;
                return true;
            }
        
            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                System.out.println("PostHandler 호출함...(핸들러 메소드 이후)");
                long startTime = (long) request.getAttribute("startTime");
                long endTime = System.currentTimeMillis();
        
                modelAndView.addObject("interval", (endTime - startTime));
        
            }
        
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
            }
        }
        
      • // 설명. Interceptor 의존성 주입
        // 설명. Interceptor 추가 및 static 리소스 호출 경로 등록 설정
        @Configuration
        public class WebConfiguration implements WebMvcConfigurer {
            private StopwatchInterceptor stopwatchInterceptor;
        
            @Autowired
            public WebConfiguration(StopwatchInterceptor stopwatchInterceptor) {
                this.stopwatchInterceptor = stopwatchInterceptor;
            }
        
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(stopwatchInterceptor);
            }
        
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/css/**")
                        .addResourceLocations("/classpath:/static/css/")
                        .setCachePeriod(50);
            }
        }
      • // 설명. 컨트롤러
        @Controller
        public class InterceptorTestController {
        
            @GetMapping("stopwatch")
            public String handlerMethod() throws InterruptedException {
                System.out.println("핸들러 메소드 호출됨..");
                Thread.sleep(1000);
                return "result";
            }
        }
        

File upload

통신 데이터 형식

  1. 바이트배열
  2. MultipartFile
    • form 태그의 enctype="multipart/form-data"로 들어와야 가능
  3. base64 문자열(브라우저가 랜더링 가능)

파일 저장

  • 배포 서버의 static 공간(서버 로직이 아닌 직접적인 접근 가능)
    • ResourceLoader
  • NAS
  • S3

SingleFile

  • RedirectAttributes 사용(redirect이며, static 영역의 값을 가져오기에, parameter 불가능)

@Controller
public class FileUploadController {

    private ResourceLoader resourceLoader;

    @Autowired
    public FileUploadController(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @PostMapping("single-file")
    public String singleFileUpload(
            @RequestParam MultipartFile singleFile,
            @RequestParam String singleFileDescription,
            RedirectAttributes rttr
    ) throws IOException {
        Resource resource = resourceLoader.getResource("classpath:static/uploadFiles/img/single");
        String filePath = resource.getFile().getAbsolutePath();
        String originalFileName = singleFile.getOriginalFilename();
        String ext = originalFileName.substring(originalFileName.lastIndexOf("."));
        String saveName = UUID.randomUUID().toString().replace("-", "") + ext;

        try {
            singleFile.transferTo(new File(filePath + "/" + saveName));

            // BusinessLogic이 성공하면 redirect 된 페이지를 값을 넘기기 위해 RedirectAttributes로 담는다. (flashAttribute)
            rttr.addFlashAttribute("message", "파일 업로드 성공!");
            rttr.addFlashAttribute("img", "uploadFiles/img/single/" + saveName);
            rttr.addFlashAttribute("singleFileDescription", singleFileDescription);

        } catch (Exception e) {
            e.printStackTrace();
            new File(filePath + "/" + saveName).delete();
        }
        return "redirect:/result";
    }
}

MultiFile

  • 파일 정보 저장 방식
    • Map(개발 시, 변동이 많을 수 있기에 유용)
    • FileUploadDTO
@Controller
public class FileUploadController {
    private ResourceLoader resourceLoader;

    @Autowired
    public FileUploadController(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
    @PostMapping("/multi-file")
    public String multiFileUpload(@RequestParam List<MultipartFile> multiFiles
                                , @RequestParam String multiFileDescription
                                , RedirectAttributes rttr) throws IOException {
        String filePath = resourceLoader.getResource("classpath:static/uploadFiles/img/multi")
                                        .getFile()
                                        .getAbsolutePath();

        List<Map<String, String>> files = new ArrayList<>();
        List<String> saveFiles = new ArrayList<>();
        try {

            for (int i = 0; i < multiFiles.size(); i++) {
                String originFileName = multiFiles.get(i).getOriginalFilename();
                String ext = originFileName.substring(originFileName.lastIndexOf("."));
                String saveName = UUID.randomUUID().toString().replace("-", "") + ext;

                Map<String, String> file = new HashMap<>();
                file.put("originFileName", originFileName);
                file.put("saveName", saveName);
                file.put("filePath", filePath);
                file.put("multiFileDescription", multiFileDescription);

                files.add(file);

                multiFiles.get(i).transferTo(new File(filePath + "/" + saveName));
                saveFiles.add("uploadFiles/img/multi/" + saveName);
            }

            rttr.addFlashAttribute("message", "다중 파일 업로드 성공!");
            rttr.addFlashAttribute("imgs", saveFiles);
            rttr.addFlashAttribute("multiFileDescription", multiFileDescription);
        } catch (Exception e) {

            // 설명. 전체 파일 업로드가 아닌 일부 파일 업로드 시 올라간 파일 삭제
            for(int i = 0; i < saveFiles.size(); i++) {
                Map<String, String> file = files.get(i);
                new File(filePath + "/" + file.get("saveName")).delete();
            }

            rttr.addFlashAttribute("message", "다중 파일 업로드 실패");
        }

        return "redirect:/result";
    }
}
profile
엔지니어로의 성장일지

0개의 댓글