학습 목표 

1. 프로젝트 생성 (mybankapp)
2. 의존성 설정 
3. yml 파일로 변경 처리 하기 
4. h2 메모리 디비에 접근 하기 ( http://localhost:8080/h2-console )
5. index.html 파일 연결 확인 
6. git 설정 - 프로젝트 단위

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

추가해야 할 의존성

 

implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.0'


의존성 간단 설명

implementation 'org.apache.tomcat.embed:tomcat-embed-jasper’

Tomcat에서 JSP를 처리하기 위해 필요한 라이브러리 중 하나 입니다.

Embedded Tomcat은 스프링 부트(Spring Boot)에서 내장된 Tomcat 서버를 사용하는 것으로, 
외부 Tomcat 서버를 설치하고 구성할 필요 없이 애플리케이션을 실행할 수 있습니다. 
**`tomcat-embed-jasper`** 라이브러리는 Embedded Tomcat에서 JSP를 처리하기 위해 Jasper라는 
JSP 컴파일러와 관련된 기능들을 제공합니다.

**implementation 'javax.servlet:jstl'
`javax.servlet:jstl`**은 JSP(Java Server Pages)를 개발할 때 사용되는 태그 라이브러리인 
JSTL(JavaServer Pages Standard Tag Library)을 제공하는 라이브러리입니다.

JSP는 Java 언어 기반으로 웹 페이지를 동적으로 생성하는 기술이며, 
JSTL은 JSP에서 반복문, 조건문, 데이터베이스 연동 등과 같은 일반적인 로직 처리를 수행할 수 있도록
여러 가지 태그를 제공합니다.

**`javax.servlet:jstl`** 라이브러리는 JSTL의 구현체를 포함하고 있으며, 
JSP 페이지에서 JSTL 태그를 사용하기 위해 프로젝트에 의존성으로 추가됩니다

**org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.0**
스프링 부트(Spring Boot)에서 MyBatis를 사용하기 위해 필요한 라이브러리 중 하나 입니다.

MyBatis는 SQL 매퍼 프레임워크로, **객체와 SQL 쿼리를 매핑하여** 개발자가 
SQL 쿼리를 직접 작성하지 않아도 데이터베이스에 접근할 수 있도록 지원합니다. 

**runtimeOnly 'com.h2database:h2’
runtimeOnly :**
Gradle에서 라이브러리 의존성을 추가할 때 사용하는 키워드 중 하나로, 
런타임 시에만 해당 라이브러리가 필요한 경우에 사용됩니다. 
즉, 빌드 시에는 해당 라이브러리가 필요하지 않지만, 실행 시에만 필요한 경우 사용됩니다.

**`com.h2database:h2`**는 자바 기반의 관계형 데이터베이스 관리 시스템(RDBMS)인 
H2 Database를 제공하는 라이브러리입니다.
****

H2 Database는 인메모리 데이터베이스(in-memory database)를 지원하므로, 
개발 및 테스트용 데이터베이스로 매우 유용합니다.

****인메모리 데이터베이스(In-memory database)는 디스크나 파일 시스템 대신 주 메모리(RAM)에 
데이터를 저장하고, 이를 활용하여 데이터를 조회하고 수정하는 데이터베이스입니다.

인메모리 데이터베이스는 디스크에 저장되지 않기 때문에 데이터베이스의 I/O 작업이 없어 빠른 속도로
데이터를 처리할 수 있습니다. 또한, 메모리에 저장되기 때문에 영속성을 가지지 않아서 애플리케이션이
종료되면 데이터가 모두 사라지는 휘발성 데이터베이스입니다.

yml 파일 설정 하기 (기본 설정)

server:
  port: 8080
  servlet:
    encoding:
      charset: utf-8
      force: true
      


spring:
  mvc:
    view:
      prefix: /WEB_INF/view/
      suffix: .jsp
  datasource:
    url: jdbc:h2:mem:testdb;MODE=MySQL
    driver-class-name: org.h2.Driver
    username: sa
    password:  
  
  h2:
    console:
      enabled: true
  output:
    ansi: 
      enabled: always

'Spring boot > spring boot 앱 만들어 보기 2 단원' 카테고리의 다른 글

bank app 화면 구현(1)  (0) 2023.04.17
bank app MyBatis 설정  (0) 2023.04.17
bank app - h2 db 초기 값 설정  (0) 2023.04.17
bank app 모델링  (0) 2023.04.17
패키지 설정  (0) 2023.04.17

Interceptor 란 Filter와 매우 유사한 형태로 존재 하지만, 차이점은 Spring Context에 등록 된다. AOP와 유사한 기능을 제공 할 수 있으면, 주로 인증 단계 를 처리하거나, Logging 를 하는데 사용한다. 이를 선/후 처리 함으로써, business logic과 분리 시킨다.

Spring Boot에서 인터셉터(Interceptor)는 요청을 처리하기 전과 후에 실행되는 코드로,
**컨트롤러(Controller)에 도달하기 전과 후에 사용자 지정 작업을 수행할 수 있습니다**.
인터셉터는 로깅, 인증, 인가, 데이터 변환 등의 공통 작업을 수행하는 데 사용됩니다.

인터셉터를 구현하려면
**`org.springframework.web.servlet.HandlerInterceptor`** 
인터페이스를 구현하는 클래스를 작성해야 합니다. 이 인터페이스에는 세 가지 메서드가 있습니다

1. **`preHandle()`**: 컨트롤러가 실행되기 전에 호출되는 메서드입니다. 
이 메서드는 요청을 가로채서 필요한 작업을 수행한 후, 
요청 처리가 계속되어야 하는지 여부를 결정합니다. 
반환 값이 **`true`**이면 요청 처리가 계속되며, **`false`**인 경우 요청 처리가 중단됩니다.

2. **`postHandle()`**: 컨트롤러 실행 후 호출되지만, 
뷰(View)가 렌더링되기 전에 호출되는 메서드입니다. 
이 메서드는 컨트롤러가 요청을 처리한 후에 필요한 후 처리 작업을 수행하는 데 사용됩니다.

3. **`afterCompletion()`**: 요청 처리가 완료된 후, 
즉 뷰 렌더링이 완료된 후에 호출되는 메서드입니다. 
이 메서드는 자원을 정리하거나 추가 로깅 등의 작업을 수행하는 데 사용됩니다.

인터셉터를 구현한 후에는 **`WebMvcConfigurer`** 인터페이스를 구현하는 구성 클래스를 작성하여 
인터셉터를 등록해야 합니다. 이 구성 클래스에서 **`addInterceptors()`** 메서드를 
오버라이드하여 인터셉터 인스턴스를 등록할 수 있습니다.

예를 들어 다음과 같이 인터셉터를 등록할 수 있습니다

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustomInterceptor());
    }
}

이렇게 하면 인터셉터가 Spring Boot 애플리케이션에서 활성화되어 요청 처리 전,
후에 실행됩니다. 필요에 따라 인터셉터를 여러 개 등록하거나, 
특정 경로 또는 패턴에만 적용할 수도 있습니다.
인터셉터 구현 순서 

1. HandlerInterceptor 를 구현한 클래스를 직접 정의 한다. 
2. WebMvcConfigurer 인터페이스를 구현한 클래스를 정의하고 1 번에서 만든 클래스를 
등록 해주어야 한다.

예시 코드 : 컨트롤러 만들어 주기

package com.example.demo10.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class UserController {
		
	@GetMapping("/loginPage")
	public String loginPage() {
		return "login.html";  // 내부에서 이동 
	}
	
	/**
	 * 인증된 사용자만 들어 올 수 있게 막을 예정
	 * AuthInterceptory 동작 시킬려면 주소 설계를 /auth/** 
	 */
	@GetMapping("/auth/infoPage") 
	public String infoPage(HttpServletRequest request) {
		// 테스트
		// todo  
		// 1. 아래 부분 삭제 예정
		// 2. 인터셉터가 동작 하도록 주소 변경 예정
		
//		HttpSession session = request.getSession();
//		Object principal = session.getAttribute("principal");
//		if(principal == null) {
//			return "redirect:/loginPage";
//		}
		
		System.out.println("여기 코드 실행 할려면 로그인 되야 함");
		// 상대 위치 들었음 -- 현재 그 시점에서 맞게 설계 
		// 위치에서 상대 경로 찾을지 절대 경로 찾을지 결정 
		return "/info.html";
	}
	
}

인터셉터 만들어 주기

package com.example.demo10.handler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

// AuthInterceptor 는 request 요청이  
// 앞에 /auth/** 로 요청한 주소에서만 동작 하도록 설계 할 예정
@Component // IoC 처리 확인 
public class AuthInterceptor implements HandlerInterceptor {
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
	
		// 세션 여부 확인 
		HttpSession session = request.getSession();
		Object principal = session.getAttribute("principal");
		
		if(principal == null) {
			System.out.println("인증 안된 사용자");
			response.sendRedirect("/loginPage");
			return false; 
		}
		
		return true;
	}
	
}

인터셉터 등록하기

package com.example.demo10.handler;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

	@Autowired // DI 적용 
	private AuthInterceptor authInterceptor;
	
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		
		// 규칙 
		// 주소요청이 /auth/** 이 붙으면 AuthInterceptor 동작 
		// 하도록 구현 처리 
		registry.addInterceptor(authInterceptor)
				.addPathPatterns("/auth/**");
		
	}
}

시나리오 코드 1

package com.example.demo8.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // IoC 대상
public class PublicController {
	
	// http:localhost:8080/hello
	@GetMapping("/hello")
	public String hello() {
		System.out.println("Controller 에서 메서드 실행");
		return "hello";
	}
}
package com.example.demo8.handler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * 
 * 인터셉터 만들기 
 * 1. HandlerInterceptor 구현 처리
 * 2. 3 가지 메서드 기억  
 */
// 어노테이션 사용하지 않았음 !!! 
public class LoggingInterceptor implements HandlerInterceptor {
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		
		System.out.println("----------------");
		System.out.println("Request URI : " + request.getRequestURI());
		System.out.println("Request Method : " + request.getMethod());
		
		return true; 
	}
	
	
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
	}
	
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
	}
	
}
package com.example.demo8.handler;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 
 * @author 
 * 1. WebMvcConfigurer 구현 처리 하기 
 * 2. addInterceptors 를 구현 해야 한다. 
 */
@Configuration // 1개 이상 빈으로 등록 해야 할 때 
public class WebMvcConfig implements WebMvcConfigurer {
	
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		// 우리가 직접 만든 인터셉터 구현클래스를 등록 처리 합니다.
		registry.addInterceptor(new LoggingInterceptor());
	}
}

코드를 DI 사용하는 걸로 변경해 보자.

package com.example.demo8.handler;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 
 * @author 
 * 1. WebMvcConfigurer 구현 처리 하기 
 * 2. addInterceptors 를 구현 해야 한다. 
 */
@Configuration // 1개 이상 빈으로 등록 해야 할 때 
public class WebMvcConfig implements WebMvcConfigurer {
	
	// DI를 사용하는 코드로 변경해 보자.
	@Autowired  // DI 적용 
	private LoggingInterceptor loggingInterceptor;
	
//	public WebMvcConfig(LoggingInterceptor loggingInterceptor) {
//		this.loggingInterceptor = loggingInterceptor;
//	}
	
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		// 우리가 직접 만든 인터셉터 구현클래스를 등록 처리 합니다.
		registry.addInterceptor(loggingInterceptor);
	}
}

'Spring boot' 카테고리의 다른 글

Spring boot - Interceptor - 2(활용)  (1) 2023.04.17
스프링 부트 JSP 연결  (0) 2023.04.13
spring boot execption(예외처리)  (0) 2023.04.13
Spring Boot Validation  (0) 2023.04.13
어노테이션 종류  (0) 2023.04.13
JSP 를 사용하기 위해서는 직접 의존성 추가 해주어야 한다.
의존성 설정 후에 직접 jsp 넣을 폴더 구조를 tomcat 인식하는 폴더 구조로 만들어 주어야 한다.

implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'

코드를 추가해주고 저걸 눌러줘서 불러와서 세팅될때까지 기다린다.

yml 설정 (엄격한 문법 규칙)

server:
  port: 8080
  servlet:
    encoding:
      charset: utf-8
      force: true
      
#스프링 JSP 사용 설정 
spring:
  mvc:
    view:
      prefix: /WEB-INF/view/
      suffix: .jsp

폴더 생성하기

폴더를 보기와 같이 생성해야 오류없이 사용할수있다.


컨트롤러 만들기

package com.example.demo7.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller // IoC 처리 됨
public class UserController {
	
	// http://localhost:8080/
	// http://localhost:8080/user
	@GetMapping({"/", "user"})
	public String userPage(Model model) {
		// yml 파일 설정 
		// prefix : /WEB-INF/view/
		// subfix : .jsp 
		
		// /WEB-INF/view/user.jsp <-- 가 완성 됨 
		// viewResolver 가 동작해서 페이를 찾고 리턴
		
		// 데이터를 내려 보내고 싶다면
		
		model.addAttribute("principal", "헬로우~~");
		return "user";
	}
}
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>여기는 user.jsp 파일입니다. ${principal}</h1>
</body>
</html>

 

'Spring boot' 카테고리의 다른 글

Spring boot - Interceptor - 2(활용)  (1) 2023.04.17
Spring boot - Interceptor - 1  (0) 2023.04.13
spring boot execption(예외처리)  (0) 2023.04.13
Spring Boot Validation  (0) 2023.04.13
어노테이션 종류  (0) 2023.04.13
학습 목표 
스프링 부트에서 예외 처리 기술을 알아보자 

1.  @RestControllerAdvice,
2.  @ControllerAdvice 
3. 전체적인(공통) 예외 처리 기법을 알자 
4. 특정 예외 처리 기법을 알자 
5. 특정 예외 클래스들을 재정의 해 보자. 
6. 에러 클래스를 직접 만들어 모범적인 사례로 응답 처리 해보자.

의존성 추가 예정 - Validation

plugins {
	id 'java'
	id 'org.springframework.boot' version '2.7.10'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}


tasks.named('test') {
	useJUnitPlatform()
}

시나리오 코드 1

package com.example.demo6.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo6.dto.User;


@RestController
@RequestMapping("/api")
public class ApiController {
	
	// 문제 -  유효성 검사 동작하도록 코드를 변경 하시오 
	@GetMapping("/user")
	public User get(@RequestParam String name,
			@RequestParam Integer age) {
		User user = new User();
		user.setName(name);
		user.setAge(age);
		return user;
	}
	
}

요청 시 오류를 발생 시켜 보세요

시나리오 코드 2

package com.example.demo6.handler;

import java.net.BindException;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice // IoC 대상된다 
// @ControllerAdvice // 페이지 리턴 오류시 작동  
public class GlobalControllerAdvice {
	
	// 모든 예외를 여기서 처리 하고 싶다면
	@ExceptionHandler(value = Exception.class)
	public ResponseEntity<?> exception(Exception e) {
		
		System.out.println("=============");
		System.out.println(e.getClass());
		System.out.println(e.getMessage());
		System.out.println("==============");
		
		return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); 
	}
	
	// 특정 예외를 잡아서 다르게 응답 처리 하고 싶다면 !!! 
	@ExceptionHandler(value = MethodArgumentNotValidException.class)
	public ResponseEntity<?> methodArgumentNotValidException(MethodArgumentNotValidException e) {
		System.out.println("잘못된 값을 나에게 전달 했어");
		return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); 
	}
	
	// 특정 예외를 잡아서 다르게 응답 처리 하고 싶다면 !!! 
	@ExceptionHandler(value = HttpMessageNotReadableException.class)
	public ResponseEntity<?> httpMessageNotReadableException(HttpMessageNotReadableException e) {
		System.out.println("잘못된 형식 : 제이슨 문법 아직도 모르니??");
		return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); 
	}
	
	// 특정 예외를 잡아서 다르게 응답 처리 하고 싶다면 !!! 
	@ExceptionHandler(value = BindException.class)
	public ResponseEntity<?> bindException(BindException e) {
		System.out.println("GET 방식으로 값을 던질 때 잘못 보냈네!!");
		return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); 
	}
	
}

있어 보이게 응답 내려주기

package com.example.demo6.handler;

import java.net.BindException;
import java.util.ArrayList;
import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.example.demo6.dto.CustomError;

@RestControllerAdvice // IoC 대상된다 
// @ControllerAdvice // 페이지 리턴 오류시 작동  
public class GlobalControllerAdvice {
	
	// 모든 예외를 여기서 처리 하고 싶다면
	@ExceptionHandler(value = Exception.class)
	public ResponseEntity<?> exception(Exception e) {
		
		System.out.println("=============");
		System.out.println(e.getClass());
		System.out.println(e.getMessage());
		System.out.println("==============");
		
		return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); 
	}
	
	// 특정 예외를 잡아서 다르게 응답 처리 하고 싶다면 !!! 
	@ExceptionHandler(value = MethodArgumentNotValidException.class)
	public ResponseEntity<?> methodArgumentNotValidException(MethodArgumentNotValidException e) {
		List<CustomError> errorList = new ArrayList<>();
		e.getAllErrors().forEach(error -> {
			String field = error.getCode();
			String message = error.getDefaultMessage();
			CustomError customError = new CustomError();
			customError.setField(field);
			customError.setMessage(message);
			errorList.add(customError);
		});
		
		System.out.println("잘못된 값을 나에게 전달 했어");
		return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorList); 
	}
	
	// 특정 예외를 잡아서 다르게 응답 처리 하고 싶다면 !!! 
	@ExceptionHandler(value = HttpMessageNotReadableException.class)
	public ResponseEntity<?> httpMessageNotReadableException(HttpMessageNotReadableException e) {
		System.out.println("잘못된 형식 : 제이슨 문법 아직도 모르니??");
		return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); 
	}
	
	// 특정 예외를 잡아서 다르게 응답 처리 하고 싶다면 !!! 
	@ExceptionHandler(value = BindException.class)
	public ResponseEntity<?> bindException(BindException e) {
		System.out.println("GET 방식으로 값을 던질 때 잘못 보냈네!!");
		return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); 
	}
	
}

'Spring boot' 카테고리의 다른 글

Spring boot - Interceptor - 1  (0) 2023.04.13
스프링 부트 JSP 연결  (0) 2023.04.13
Spring Boot Validation  (0) 2023.04.13
어노테이션 종류  (0) 2023.04.13
AOP 개념 살펴 보기  (0) 2023.04.13

Validation 에 이해 
Bean Validation API 사용 (어노테이션 기반) 
AOP 개념 적용 

Validation 이란 프로그래밍에 있어서 가장 필요한 부분 중에 하나 입니다. 
특히 자바에서는 null 값에 대해서 접근 하려고 할 때 Null Point Exception이
발생하기 때문에 이러한 부분을 방지 하기 위해서 미리 검증을 하는 과정을 Validation 이라고 합니다.

1. 검증해야 할 값이 많은 경우 코드의 길이가 길어 진다.
2. 구현에 따라서 달라 질 수 있지만 핵심 기능과의 분리가 필요 하다.
3. 흩어져 있는 경우 어디에서 검증을 하는지 알기 어러우며, 재사용의 한계가 있다.
4. 구현에 따라 달라 질 수 있지만, 검증 Logic이 변경 되는 경우 
테스트 코드 등 참조하는 클래스에서 Logic이 변경되어야 하는 부분이 발생 할 수 있다.

 

공식 사이트 확인

[https://beanvalidation.org/2.0-jsr380](https://beanvalidation.org/2.0-jsr380)

1. gradle dependecies 추가 하기
2. implementation 'org.springframework.boot:spring-boot-starter-validation'

3. build.gradle로 와서 추가해준다

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
}

눌러서 다운로드 해서 인풋 해줘야한다
package com.example.demo5.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo5.dto.User;

@RestController
@RequestMapping("/api")
public class ApiController {
	
	// method : post 
	// http://localhost:8080/api/user
	@PostMapping("/user")
	public ResponseEntity<User> user(@RequestBody User user) {
		// spring boot 요청시 데이터 기본 파싱 전략 key=value 구조
		// dto <--- object mapper 동작을 해서 자동 파싱해서 처리해 준다.
		// 유효성 검사 - 예전 방식 
		if(user.getAge() < 1 || user.getAge() > 100) {
			// 잘못된 입력값을 확인 
			return ResponseEntity
					.status(HttpStatus.BAD_REQUEST).body(user);
		}
		// 서비스 만들어 (로직) ---> DAO 던저서 (DB insert) 
		// 정방향 ---> 역방향으로 돌아 온다. --> 응답 처리 
		
		// builder 패턴으로 User 객체 만들어 보기 
		User user2 = User.builder()
				.email("티모")
				.age(100)
				.build(); // 마지막에 build() 반드시 호출
		
		System.out.println(user);
		return ResponseEntity.ok(user); 
	}
	
	// 두번째 연습 
	// AOP 기반인 Validation 라이브러리 활용 하기 
	
}

AOP 기반에 validation 처리 해보기

DTO 에 이메일 유효성 검사를 해보자.

package com.example.demo5.dto;


import javax.validation.constraints.Email;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
	private String name;
	private int age;
	@Email
	private String email;
	private String phoneNumber;

}

: POST 방식일 때와 GET 방식을 때 유효성 검사 방법은 약간 달라요~

반드시 @Valid 어노테이션을 선언 해야 한다.

package com.example.demo5.controller;

import javax.validation.Valid;

import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo5.dto.User;

@RestController
@RequestMapping("/api")
public class ApiController {
	
	// method : post 
	// http://localhost:8080/api/user
	@PostMapping("/user")
	public ResponseEntity<User> user(@RequestBody User user) {
		// spring boot 요청시 데이터 기본 파싱 전략 key=value 구조
		// dto <--- object mapper 동작을 해서 자동 파싱해서 처리해 준다.
		// 유효성 검사 - 예전 방식 
		if(user.getAge() < 1 || user.getAge() > 100) {
			// 잘못된 입력값을 확인 
			return ResponseEntity
					.status(HttpStatus.BAD_REQUEST).body(user);
		}
		// 서비스 만들어 (로직) ---> DAO 던저서 (DB insert) 
		// 정방향 ---> 역방향으로 돌아 온다. --> 응답 처리 
		
		// builder 패턴으로 User 객체 만들어 보기 
		User user2 = User.builder()
				.email("티모")
				.age(100)
				.build(); // 마지막에 build() 반드시 호출
		
		System.out.println(user);
		return ResponseEntity.ok(user); 
	}
	
	// 두번째 연습 
	// AOP 기반인 Validation 라이브러리 활용 하기 
	// 1. GET 방식일 때 사용 방법과 POST 방식일 때 사용법이 약간 다르다
	// 반드시 Valid 선언을 해주어 한다. 
	@PostMapping("/user2")
	public ResponseEntity<User> user2(@Valid @RequestBody User user) {
		// AOP 기반에 valid 라이브러리를 활용하면 공통적으로 들어가야 
		// 하는 부분에 코드를 분리 시킬 수 있다. 
		return ResponseEntity.ok(user); 
	}
}

두 번째 시나리오 message 직접 설정해 보기 !!!!

validation 어노테이션의 활용

package com.example.demo5.dto;


import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
	@NonNull
	private String name;
	@Min(10)
	@Max(100)
	private int age;
	@Email(message = "넌 이메일 형식도 모르니??")
	private String email;
	private String phoneNumber;
}
- **`@Nullable`**: 해당 변수, 메서드 반환값, 
파라미터 등이 **`null`**일 수 있음을 나타냅니다. 즉, 
이 어노테이션이 붙은 요소는 **`null`**일 수 있으므로, 
**`null`** 예외를 방지하기 위한 null 체크가 필요합니다.
- **`@NotNull`**: 해당 변수, 메서드 반환값, 
파라미터 등이 **`null`**일 수 없음을 나타냅니다. 
이 어노테이션이 붙은 요소는 항상 **`null`**이 아니므로, **`null`** 체크가 필요하지 않습니다.

**`@NonNull`**은 해당 변수, 메서드 반환값, 파라미터 등이 **`null`**
일 수 없음을 나타냅니다. 따라서, 이 어노테이션이 붙은 요소는 항상 **`null`** 이 아니므로, 
**`null`**  체크가 필요하지 않습니다.

Validation 심화 과정 - 직접 예외 클래스를 컨트롤 할 수도 있다. (POST 일 때 많이 사용)

1 단계

package com.example.demo5.controller;

import javax.validation.Valid;

import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo5.dto.User;

@RestController
@RequestMapping("/api")
public class ApiController {
	
	// method : post 
	// http://localhost:8080/api/user
	@PostMapping("/user")
	public ResponseEntity<User> user(@RequestBody User user) {
		// spring boot 요청시 데이터 기본 파싱 전략 key=value 구조
		// dto <--- object mapper 동작을 해서 자동 파싱해서 처리해 준다.
		// 유효성 검사 - 예전 방식 
		if(user.getAge() < 1 || user.getAge() > 100) {
			// 잘못된 입력값을 확인 
			return ResponseEntity
					.status(HttpStatus.BAD_REQUEST).body(user);
		}
		// 서비스 만들어 (로직) ---> DAO 던저서 (DB insert) 
		// 정방향 ---> 역방향으로 돌아 온다. --> 응답 처리 
		
		// builder 패턴으로 User 객체 만들어 보기 
		User user2 = User.builder()
				.email("티모")
				.age(100)
				.build(); // 마지막에 build() 반드시 호출
		
		System.out.println(user);
		return ResponseEntity.ok(user); 
	}
	
	// 두번째 연습 
	// AOP 기반인 Validation 라이브러리 활용 하기 
	// 1. GET 방식일 때 사용 방법과 POST 방식일 때 사용법이 약간 다르다
	// 반드시 Valid 선언을 해주어 한다. 
	@PostMapping("/user2")
	public ResponseEntity<User> user2(@Valid @RequestBody User user) {
		// AOP 기반에 valid 라이브러리를 활용하면 공통적으로 들어가야 
		// 하는 부분에 코드를 분리 시킬 수 있다. 
		return ResponseEntity.ok(user); 
	}
	
	// 와일드 카드 -> ? 사용가능 
	@PostMapping("/user3")
	public ResponseEntity<?> user3(@Valid @RequestBody User user,
			BindingResult bindingResult) {
		// bindingResult 클래스를 배워 보자. 
		// bindingResult 가 @Valid 에 대한 결과 값을 가지고 있다. 
		if(bindingResult.hasErrors())  {
			// 에러 발생 
			// 필드 - 어떤 필드에서 에러 발생 
			// 메세지 - 내용을 반환 처리 
			StringBuilder sb = new StringBuilder();
			bindingResult.getAllErrors().forEach(error -> {
				System.out.println(error.getCode());
				System.out.println(error.getDefaultMessage());
				System.out.println(error.getArguments());
				System.out.println(error.getObjectName());
				sb.append("field : " + error.getCode());
				sb.append("\n");
				sb.append("message : " + error.getDefaultMessage());
				sb.append("\n");
			});
			
			return ResponseEntity
					.status(HttpStatus.BAD_REQUEST)
					.body(sb.toString());
		}
		// 정상 처리 
		
		return ResponseEntity.ok(user); 
	}
}

GET 방식일 때 유효성 검사 하기

package com.example.demo5.controller;

import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo5.dto.User;

@RestController
@Validated // GET 방식일 때 반드시 선언 해주어야 유효성 검사를 시작 한다.
public class UserController {
	
	// GET 방식일 때 파라미터 앞에 어떤 유효성 검사를 할 지 
	// 당연히 지정해 주어야 한다. 
	
	// http://localhost:8080/user?name=홍&age=1
	// GET 방식일 때 valid 처리 
	@GetMapping("/user")
	public User user(@Size(min = 2) @RequestParam String name, 
			@NotNull @Min(1) @RequestParam Integer age) {
		User user = new User();
		user.setName(name);
		user.setAge(age);
		return user;
	}
	
	// http://localhost:8080/user2?name=홍&age=1
	// 문제 GET 유효성 검사 직접 만들어 보기 
	@GetMapping("/user2")
	// object mapper 통해서 파싱 처리 하고 싶다면 
	public User user2(@Validated User user) {
		
		return user;
	}
	
}

 

'Spring boot' 카테고리의 다른 글

스프링 부트 JSP 연결  (0) 2023.04.13
spring boot execption(예외처리)  (0) 2023.04.13
어노테이션 종류  (0) 2023.04.13
AOP 개념 살펴 보기  (0) 2023.04.13
제어의 역전과 의존 주입(Ioc /DI )  (0) 2023.04.10

'Spring boot' 카테고리의 다른 글

spring boot execption(예외처리)  (0) 2023.04.13
Spring Boot Validation  (0) 2023.04.13
AOP 개념 살펴 보기  (0) 2023.04.13
제어의 역전과 의존 주입(Ioc /DI )  (0) 2023.04.10
Response 와 MIME TYPE 에 이해  (0) 2023.04.10
관점 지향 프로그래밍 

AOP(Aspect-Oriented Programming)는 OOP(Object-Oriented Programming)의 
보완적인 개념으로, 프로그램을 구성하는 여러 모듈에서 공통적으로 사용하는 코드를 
분리하여 재사용성과 유지 보수성을 향상 시키는 프로그래밍 기법입니다.

Spring Boot에서 AOP를 사용하면, 프로그램의 여러 지점에서 반복적으로 사용되는 기능(로깅, 보안 등)을 
모듈화 하여 코드 중복을 줄이고, 코드 가독성과 유지 보수성을 향상 시킬 수 있습니다. 
AOP는 프로그램의 핵심 기능에 영향을 미치지 않으면서도 부가적인 기능을 제공할 수 있습니다.


AOP는 Aspect와 Advice를 사용하여 구현하며, @Aspect 어노테이션과 @Before, @After, @Pointcut 
등의 어노테이션을 사용하여 정의합니다.
execution 표현식은 다음과 같은 구조를 가집니다.
@Pointcut("execution(public void com.example.demo.controller..*.*(..))")

execution([접근제한자 패턴] [리턴타입 패턴] [패키지 패턴] [클래스 이름 패턴].[메서드 이름 패턴]([파라미터 패턴]))

*: 0개 이상의 문자를 나타냅니다.
..: 0개 이상의 하위 패키지를 나타냅니다.
+: 서브 타입(subtype)을 나타냅니다.
(..): 파라미터가 0개 이상인 메서드를 나타냅니다.

execution(* com.example.demo.controller.*.*(..))은
com.example.demo.controller 패키지에 있는 모든 클래스의 모든 메서드를 선택합니다.
이때, *은 클래스 이름, 메서드 이름, 반환 타입, 파라미터 타입, 예외 타입을 모두 나타내므로,
해당 패키지에 속한 모든 클래스의 모든 메서드가 선택됩니다.

'Spring boot' 카테고리의 다른 글

Spring Boot Validation  (0) 2023.04.13
어노테이션 종류  (0) 2023.04.13
제어의 역전과 의존 주입(Ioc /DI )  (0) 2023.04.10
Response 와 MIME TYPE 에 이해  (0) 2023.04.10
REST API 정리  (0) 2023.04.10
Ioc 개념에 대해 이해 
DI 개념에 대해 이해
IoC (Inversion Of Control) 
1. 스프링에서는 일반적으로 Java 객체를 new 로 생성하여 개발자가 관리하는 것이 아닌 
spring Container 에게 모두 맡긴다. 
2. 개발자에게서 실행에 제어권을 프레임워크로 권한이 넘어 갔다라는 의미로 제어의 역전이라고 한다. 
3. IoC 에 대상은 싱글톤으로 관리 됩니다.
DI (Dependency Inject ) 의존 주입 
1. 필요할 때 spring container 에서 가져와서 사용한다 

의존성으로 부터 격리 시켜 코드 테스트가 용이하다. 루즈 커플링이 가능하다. 
코드를 확장하거나 변경할 때 영향을 최소화 한다. (추상화)
IoC는 소프트웨어 디자인 패턴 중 하나로, 컴퓨터 프로그램의 제어 흐름이 뒤바뀌는 것을 의미합니다. 
일반적인 프로그램의 제어 흐름은 개발자가 코드를 작성하여 제어합니다. 
하지만 IoC를 사용하면, 제어 흐름의 일부 또는 전부를 프레임워크가 제어하게 됩니다. 
이를 통해 프로그램의 유연성과 재사용성이 높아질 수 있습니다.

**DI는 IoC의 한 형태로, 클래스가 자신이 사용할 객체를 직접 생성하지 않고, 
외부에서 이를 주입받아 사용하는 것을 의미합니다**. DI를 사용하면 클래스 간의 의존성을 줄이고, 
클래스의 재사용성과 유연성을 높일 수 있습니다.

즉, IoC는 프로그램의 제어 흐름을 뒤바꾸는 개념이고, 
DI는 클래스가 의존하는 객체를 외부에서 주입받아 사용하는 방식을 말합니다. 
DI는 IoC를 구현하는 방법 중 하나입니다.

따라서, IoC와 DI는 밀접한 관련이 있지만, 서로 다른 개념입니다. 
IoC는 프로그램의 제어 흐름을 뒤바꾸는 개념이고, DI는 클래스가 의존하는 객체를 외부에서 
주입받아 사용하는 방식을 말합니다.

DI 예제 만들어 보기 + 전략 패턴

인터페이스 선언

package ch02;

public interface IEncoder {
	// URL 인코딩, Base64 인코딩
	// 문자형 데이터 (바이터리 타입을 문자열을 Base64 형태)
	String encode(String message);
}

구현 클래스 만들기 - 1

package ch02;

import java.util.Base64;

public class Base64Encoder implements IEncoder {

	@Override
	public String encode(String message) {
		// 인코딩 -> Base64 형식으로 처리 
		String resultEncode = 
				Base64.getEncoder()
				.encodeToString(message.getBytes());
		return resultEncode;
	}

}

구현 클래스 만들기 - 2

package ch02;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class UrlEncoder implements IEncoder {
	
	@Override
	public String encode(String message) {
		try {
			return URLEncoder.encode(message, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null; 
		}
	}
}

중간 클래스 만들기

package ch02;

public class Encoder {
	
	private IEncoder iEncoder;
	
	// DI 의존 주입 설정 + 전략 패턴 
	public Encoder(IEncoder iEncoder) {
		this.iEncoder = iEncoder;
	}
	
	// setter 까지 만들면 전략 패턴 완선 
	public void setiEncoder(IEncoder iEncoder) {
		this.iEncoder = iEncoder;
	}

	// 기능 
	public String encode(String message) {
		return iEncoder.encode(message);
	}
	
}

결과 사용법

package ch02;

public class MainTest1 {

	public static void main(String[] args) {
		
		// 중간 클래스 만들었음 ! 
		String url = "www.naver.com/books/id?=100";
		
		// IEncoder 생성자 타입 --> 의존 주입 설계 
		// Base64Encoder <-- IEncoder 타입으로 바라볼 수 있다.
		// URlEncoder <-- IEncoder 타입으로 바라볼 수 있다.
		
		// 하고싶은 인코더 클래스를 주입만 하면 그 구현 클래스에 맞게 
		// 기능을 처리할 수 있다. 
		
		Encoder encoder = new Encoder(new UrlEncoder());
		String result1 = encoder.encode(url);
		System.out.println("result1 : " + result1);
		
		encoder.setiEncoder(new Base64Encoder());
		System.out.println(encoder.encode("반가워"));
		
		
//		Encoder encoder2 = new Encoder(new Base64Encoder());
//		String result2 = encoder2.encode(url);
//		System.out.println("result2 : " + result2);
//		
//		Encoder encoder3 = new Encoder(new MyEncoder());
//		String result3 = encoder3.encode(url);
//		System.out.println("result3 : " + result3);
	}

}

'Spring boot' 카테고리의 다른 글

어노테이션 종류  (0) 2023.04.13
AOP 개념 살펴 보기  (0) 2023.04.13
Response 와 MIME TYPE 에 이해  (0) 2023.04.10
REST API 정리  (0) 2023.04.10
DELETE 방식에 이해 및 실습  (0) 2023.04.10

+ Recent posts