스프링 프레임워크를 사용하면서 웹 애플리케이션 개발 시 자주 마주치게 되는 세 가지 중요한 기능에 대해 알아보겠습니다. 인터셉터, 에러 페이지 처리, 그리고 파일 업로드는 꼭 필요한 기능들인데요, 이번 글에서는 이 기능들의 개념부터 구현 방법까지 자세히 알아보겠습니다.
스프링 인터셉터 (Interceptor)
인터셉터란?
인터셉터는 DispatcherServlet이 컨트롤러를 호출하기 전과 후에 요청과 응답을 가로채서 처리하는 스프링 MVC의 기능입니다. 서블릿 필터와 유사하지만, 스프링 MVC의 컨텍스트 내에서 동작하기 때문에 스프링의 모든 기능을 활용할 수 있다는 장점이 있습니다.
인터셉터의 주요 메서드
인터셉터를 구현하기 위해서는 HandlerInterceptor 인터페이스를 구현해야 합니다. 이 인터페이스는 다음 세 가지 핵심 메서드를 제공합니다.
public class MyInterceptor implements HandlerInterceptor {
// 컨트롤러 실행 전
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 요청 처리 전 로직
// true 반환 시 계속 진행, false 반환 시 요청 처리 중단
return true;
}
// 컨트롤러 실행 후, 뷰 렌더링 전
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// 컨트롤러 로직 실행 후, 뷰 렌더링 전 로직
}
// 요청 처리 완료 후 (뷰 렌더링 후)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
// 요청 처리 완료 후 로직 (예외 처리 포함)
}
}
인터셉터 등록하기
인터셉터를 구현한 후에는 스프링 MVC 설정에 등록해야 합니다. 이는 WebMvcConfigurer 인터페이스를 구현하여 수행할 수 있습니다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/secure/**") // 인터셉터를 적용할 경로 패턴
.excludePathPatterns("/public/**", "/resources/**"); // 제외할 경로 패턴
}
}
인터셉터 활용 사례
인터셉터는 다양한 상황에서 유용하게 활용될 수 있습니다.
- 로그인 체크: 로그인이 필요한 페이지에 대한 접근 제어
- 권한 검사: 특정 리소스에 접근할 권한이 있는지 확인
- 로깅: 모든 요청에 대한 로그 기록
- 공통 데이터 설정: 요청 처리에 필요한 공통 데이터 설정
- 성능 측정: 요청 처리 시간 측정
실제 인터셉터 구현 예제
다음은 로그인 체크를 위한 인터셉터 구현 예제입니다.
@Component
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.debug("AuthInterceptor preHandle 실행");
// 세션에서 로그인 정보 확인
HttpSession session = request.getSession();
UserDto loginUser = (UserDto) session.getAttribute("loginUser");
// 로그인되지 않은 경우
if (loginUser == null) {
log.debug("인증되지 않은 사용자 접근");
// 로그인 페이지로 리다이렉트
response.sendRedirect(request.getContextPath() + "/login");
return false; // 요청 처리 중단
}
return true; // 요청 처리 계속 진행
}
}
여러 인터셉터의 실행 순서
여러 인터셉터를 등록하는 경우 실행 순서가 중요합니다.
- preHandle: 등록한 순서대로 실행됩니다.
- postHandle 및 afterCompletion: 등록한 순서의 역순으로 실행됩니다.
예를 들어, 인터셉터 A, B, C 순으로 등록했다면 실행 순서는 다음과 같습니다.
- A.preHandle → B.preHandle → C.preHandle
- 컨트롤러 실행
- C.postHandle → B.postHandle → A.postHandle
- 뷰 렌더링
- C.afterCompletion → B.afterCompletion → A.afterCompletion
세 가지 기능의 비교
필터, AOP, 인터셉터의 차이점
웹 애플리케이션에서 공통 관심사를 처리하는 방법으로 필터, AOP, 인터셉터가 있습니다. 이들의 주요 차이점은 다음과 같습니다.
1. Servlet Filter
- Spring과 무관하게 작동
- DispatcherServlet 호출 이전에 동작
- ServletRequest와 ServletResponse에 대한 전처리와 후처리만 가능
- Spring 컨텍스트와의 통합이 어려움
2. Handler Interceptor
- Spring 컨텍스트 내에서 동작
- DispatcherServlet이 요청을 처리한 후, 컨트롤러 호출 전/후에 작동
- Spring의 모든 빈과 기능에 접근 가능
- HTTP 요청과 응답에 관련된 작업에 적합
3. AOP (Aspect-Oriented Programming)
- Spring의 핵심 기능으로, 메소드 호출 시점에 작동
- HTTP 요청 처리와 직접적인 관련이 적음
- 주로 서비스, DAO 등 비즈니스 계층에 적용
- 트랜잭션 관리, 로깅, 보안 등 애플리케이션 전반적인 관심사 처리에 적합
에러 페이지 처리 (Error Page)
스프링 부트는 예외 처리를 위한 다양한 방법을 제공합니다. 이를 통해 사용자에게 친화적인 에러 페이지를 제공하고 예외 상황을 효과적으로 관리할 수 있습니다.
기본 에러 처리 메커니즘
스프링 부트는 기본적으로 에러 처리를 위한 메커니즘을 제공합니다.
- 오류 발생 시 /error 경로로 요청이 내부적으로 전달됩니다.
- BasicErrorController가 이 요청을 처리합니다.
- 해당 오류 코드에 맞는 에러 페이지를 찾아 표시합니다.
- /error/{status}.html 형식의 페이지를 찾습니다. (예: 404.html, 500.html)
- 없으면 기본 에러 페이지(Whitelabel Error Page)를 표시합니다.
커스텀 에러 페이지 설정
별도의 에러 페이지를 정의하는 방법은 크게 두 가지가 있습니다.
1. 정적 HTML 파일 사용
src/main/resources/static/error/ 또는 src/main/resources/templates/error/ 디렉토리에 에러 상태 코드와 일치하는 이름의 파일을 생성합니다.
- 404.html: 404 에러(페이지를 찾을 수 없음)용 페이지
- 500.html: 500 에러(서버 내부 오류)용 페이지
- error.html: 그 외 모든 에러를 위한 기본 페이지
2. 컨트롤러에서 @ExceptionHandler 사용
컨트롤러 내에서 발생하는 특정 예외를 처리하기 위해 @ExceptionHandler를 사용할 수 있습니다.
@Controller
public class ProductController {
// 이 컨트롤러 내에서 발생하는 ProductNotFoundException 예외를 처리
@ExceptionHandler(ProductNotFoundException.class)
public String handleProductNotFound(ProductNotFoundException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/product-not-found";
}
// 다른 컨트롤러 메서드들...
}
전역 예외 처리
애플리케이션 전체에서 발생하는 예외를 처리하려면 @ControllerAdvice 또는 @RestControllerAdvice를 사용할 수 있습니다.
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public String handleException(Exception ex, Model model) {
logger.error("예외 발생: {}", ex.getMessage(), ex);
model.addAttribute("errorMessage", "서비스 처리 중 오류가 발생했습니다.");
return "error/general-error";
}
@ExceptionHandler(ResourceNotFoundException.class)
public String handleResourceNotFound(ResourceNotFoundException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/not-found";
}
}
이 방식은 모든 컨트롤러에 적용되므로, 애플리케이션 전체의 예외 처리 로직을 한 곳에서 관리할 수 있습니다.
에러 응답 커스터마이징
더 세밀한 제어를 위해 ErrorAttributes를 커스터마이징할 수 있습니다.
@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
// 추가 정보 포함
errorAttributes.put("company", "My Company");
errorAttributes.put("contact", "support@mycompany.com");
return errorAttributes;
}
}
3. 파일 업로드 (File Upload)
스프링은 파일 업로드를 간편하게 구현할 수 있는 기능을 제공합니다. 이를 통해 사용자로부터 파일을 받아 서버에 저장하고 처리할 수 있습니다.
기본 설정
스프링 부트에서 파일 업로드를 위한 기본 설정은 application.properties 또는 application.yml 파일에서 할 수 있습니다.
# 최대 파일 크기
spring.servlet.multipart.max-file-size=10MB
# 최대 요청 크기
spring.servlet.multipart.max-request-size=10MB
# multipart 요청 활성화 (기본값은 true)
spring.servlet.multipart.enabled=true
# 임시 파일 저장 위치
spring.servlet.multipart.location=/tmp
단일 파일 업로드 구현
다음은 단일 파일 업로드를 처리하는 컨트롤러 예제입니다.
@Controller
@RequestMapping("/files")
public class FileUploadController {
private final String uploadDir = "uploads";
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file, Model model) {
if (file.isEmpty()) {
model.addAttribute("message", "파일을 선택해주세요.");
return "upload-form";
}
try {
// 업로드 디렉토리 생성
Path uploadPath = Paths.get(uploadDir);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 원본 파일명 추출
String originalFilename = file.getOriginalFilename();
// 파일명 충돌 방지를 위한 고유 파일명 생성
String uniqueFilename = UUID.randomUUID().toString() + "_" + originalFilename;
// 파일 저장
Path filePath = uploadPath.resolve(uniqueFilename);
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
model.addAttribute("message", "파일 업로드 성공: " + originalFilename);
model.addAttribute("filePath", filePath.toString());
return "upload-success";
} catch (IOException e) {
model.addAttribute("message", "파일 업로드 실패: " + e.getMessage());
return "upload-form";
}
}
@GetMapping("/form")
public String showUploadForm() {
return "upload-form";
}
}
다중 파일 업로드 구현
여러 파일을 한 번에 업로드하는 기능도 쉽게 구현할 수 있습니다.
@PostMapping("/upload-multiple")
public String uploadMultipleFiles(@RequestParam("files") MultipartFile[] files, Model model) {
List<String> uploadedFiles = new ArrayList<>();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
try {
// 파일 저장 로직 (위와 유사)
String filename = saveFile(file);
uploadedFiles.add(filename);
} catch (IOException e) {
model.addAttribute("message", "파일 업로드 실패: " + e.getMessage());
return "upload-form";
}
}
}
model.addAttribute("message", "파일 업로드 성공: " + uploadedFiles.size() + "개 파일");
model.addAttribute("files", uploadedFiles);
return "upload-success";
}
private String saveFile(MultipartFile file) throws IOException {
// 실제 파일 저장 로직
// ...
}
파일 검증 및 보안
파일 업로드 기능을 구현할 때는 보안에 각별히 신경 써야 합니다.
- 파일 타입 검증: 허용된 파일 형식만 업로드할 수 있도록 제한
- 파일 크기 제한: 서버 리소스를 보호하기 위해 파일 크기 제한
- 악성 파일 검사: 바이러스 검사 등을 통해 악성 파일 필터링
- 안전한 저장 경로: 웹 접근이 불가능한 안전한 경로에 파일 저장
- 고유 파일명 사용: 파일명 충돌 및 경로 순회 공격 방지
다음은 파일 타입 검증 예제입니다.
private boolean isImageFile(MultipartFile file) {
String contentType = file.getContentType();
return contentType != null && contentType.startsWith("image/");
}
@PostMapping("/upload-image")
public String uploadImage(@RequestParam("file") MultipartFile file, Model model) {
if (!isImageFile(file)) {
model.addAttribute("message", "이미지 파일만 업로드 가능합니다.");
return "upload-form";
}
// 파일 저장 로직
// ...
}
마무리
이번 글에서는 스프링 웹 애플리케이션 개발에서 중요한 세 가지 기능인 인터셉터, 에러 페이지 처리, 파일 업로드에 대해 살펴보았습니다.
인터셉터는 특히 인증, 로깅, 성능 측정과 같은 공통 관심사를 처리하는 데 유용하며, 에러 페이지 처리는 사용자 경험을 향상시키는 데 중요한 역할을 합니다. 마지막으로, 파일 업로드 기능은 웹 애플리케이션에서 자주 필요한 기능으로, 스프링에서 제공하는 간편한 API를 통해 안전하게 구현할 수 있습니다.
'BackEnd > Spring Framework' 카테고리의 다른 글
| MyBatis의 동적 SQL (0) | 2025.05.23 |
|---|---|
| MyBatis (3) | 2025.05.03 |
| Spring MVC (0) | 2025.04.27 |
| Spring AOP (0) | 2025.04.26 |
| SpringBoot (3) | 2025.04.24 |