-
Spring Boot [01. About Spring Boot]JAVA/Spring 2022. 1. 6. 19:11
1. Java, Spring, Spring Boot
Java : 객체지향적 프로그래밍 언어
- 스프링의 근간이 되는 언어
- 스프링은 자바뿐만이 아니라 코틀린, 그루비로도 사용할 수 있음. 그러나 스프링 자체가 대부분 자바로 만들어져 있기에 (98.5%) 보통 자바를 사용함
Spring : 기업용 어플리케이션을 만드는데 사용 가능한 오픈소스 프레임워크
- 자바(한글)를 이용해서 어플리케이션(책)을 만들기 위해 활용하는 프레임워크 (여러 틀이 담겨져 있는 템플릿)
-> 어플리케이션을 만들기 위해 필요한 틀을 제공! - 자바로 기업용 웹 어플리케이션을 만들기 시작하며 다양한 프레임워크가 나왔지만 다루기 어렵다는 단점이 존재했음
-> 자바는 더 이상 쓰기 어려운 언어다, 망했다 라는 이야기가 나올 정도 - 이러한 단점을 해소하기 위해 등장한 프레임워크가 '스프링 프레임워크' (자바에 봄이 옴...!)
- 스프링 내에는 다양한 라이브러리가 포함되어 있으며, 그 중에서 적합한 툴을 사용하여 어플리케이션을 구성해야 함
Spring Boot: 스프링 기반에서 자주 사용되는 설정으로 손쉽게 개발할 수 있게 해주는 상위 프레임워크
- 스프링 개발이 진행될수록 사용자는 비슷한 방식으로 활용하고 프로젝트를 설정함
매번 프로젝트를 시작할 때마다 설정을 똑같이 찍어낼 필요가 있을까...? (Boiler Plate) 하는 의문에서 시작 - 그렇게 만들어진 것이 '스프링 부트'
- Boot라 명명이 된 이유는 컴퓨터가 'Boot' 됐을 때 사용자가 뭐든 사용할 수 있는 상태가 되는 것처럼 스프링이 Boot 되었을 때 바로 개발을 시작할 수 있는 환경을 갖춘다는 의미에서 Boot로 이름을 붙임
- 스프링 부트에는 톰캣과 같은 WAS가 내장이 되어있어서 WAS를 설치하고 경로를 설정하는 등의 복잡한 과정을 거칠 필요가 없음
2. 스프링 프레임워크의 핵심 기술
Core(DI, IoC) : 스프링의 밑바탕이 되는 기술
AOP : 다양한 공통 기능을 제어할 수 있는 기술
Validation, Data binding : 요청값에 대한 유효성 검사를 진행하거나 요청값들을 객체(DTO)에 담는 기술
Resource : 외부 URL혹은 URI(외부 자원) 에 접근 시 주로 사용하는 기술
SpEL (Spring Expression Language) : 설정값들을 외부에서 주입받을 때 주로 사용하게 되는 기술
Null-Safety : Java 이용 시 발생하는 Null에 대한 처리를 더 안전하게 할 수 있도록 하는 기술
디자인 철학
- 모든 기능에서 다양한 모듈을 사용 가능. 심지어 외부 모듈도 활용 가능함.
-> 너무 높은 자유도로 인해 스프링 사용을 어렵게 만들 수도 있음 - 계속해서 추가 개발이 이루어지고 있는 프레임워크
- 이전 버젼과의 강력한 호환성을 추구
-> 너무 많은 레거시로 인해 코드가 복잡해짐 - 높은 코드 품질을 유지하려 하며 API 디자인을 섬세하게 한다.
-> 스프링 코드 자체가 하나의 좋은 참고 소스가 된다.
높은 자유도와 함께 계속 발전하는 고품질의 다양한 모듈을 포함한 프레임워크. 하지만, 높은 자유도 때문에 진입 장벽이 높다.
3. IoC(inversion of Control) & DI(Dependency Injection)
IoC나 DI는 레고와 같은 것
-> 스프링(IoC/DI)이 바닥판처럼 깔려있고,
사용자는 그 위에서 멋진 조합(Class와 Method 등을 이용한 어플리케이션)을 만들면 된다.Bean?
- Java에서의 JavaBean
- 데이터를 저장하기 위한 구조체로 자바 빈 규약이라는 것을 따르는 구조체.
- private 프로퍼티와 getter/setter로만 데이터에 접근함. - 스프링에서의 Bean
- 스프링 IoC 컨테이너에 의해 생성되고 관리되는 객체.
-> 컨테이너 : 여러 Bean과 기능들을 담고 있으며 BeanFactory라고도 불림
- 자바에서처럼 new Object(); 로 생성하지 않는다.
- 각각의 Bean들끼리는 서로를 편리하게 의존할 수 있음.
- 모든 Class를 Bean으로 등록할 필요는 없다. Class의 쓰임새에 따라서 달라짐.
스프링 컨테이너 개요
- ApplicationContext 인터페이스를 통해 제공되는 스프링 컨테이너는 Bean 객체의 생성 및 Bean들의 조립(상호 의존성 관리)을 담당한다.
-> Bean 객체에 설정값들을 입히는 등 Bean들의 관리담당자와도 같음. - Bean의 등록
- 과거에는 xml로 설정을 따로 관리하여 등록함
-> Runtime에서만 오류를 확인할 수 있었고 직접 모든 설정들을 타이핑해야했기에 불편했음
- 현재는 annotation 기반으로 손쉽게 Bean을 등록
-> @Bean, @Controller, @Service - Bean 등록 시 정보
- Class 경로
- Bean의 이름
-> 기본적으로는 Class의 원래 이름에서 첫 문자만 소문자로 변경 : accountService, userDao
-> 원하는 경우에 변경이 가능함
- Scope : Bean을 생성하는 규칙
-> singleton : 컨테이너에 단일로 생성
-> prototype : 작업 시마다 Bean을 새로 생성하고 싶을 경우
-> request : http 요청마다 새롭게 Bean을 생성하고 싶은 경우 - Bean LifeCycle callback
- callback : 어떤 이벤트가 발생하는 경우 호출되는 메서드
- lifecycle callback
-> Bean을 생성하고 초기화하고 파괴하는 등 특정 시점에 호출되도록 정의된 함수
- 주로 많이 사용되는 콜백
-> @PostConstruct : 빈 생성 시점에 필요한 작업을 수행
-> @PreDestroy : 빈 파괴(주로 어플리케이션 종료) 시점에 필요한 작업을 수행
4. AOP (Aspect Oriented Programming, 관점 지향 프로그래밍)
AOP?
- 특정한 함수 호출 전이나 후에 공통적인 처리가 필요할 때 도와주는 기술
- 로깅 : 특정 함수가 호출될 때 자세한 로깅을 원할 때
- 트랜잭션 : 내부적으로 AOP 기법을 사용하여 트랜잭션의 시작과 끝을 처리
- 인증 : 특정 영역에 대해 추가적인 인증이 필요하다면 AOP 사용 - OOP로 처리하기에 다소 까다로운 부분을 AOP라는 처리 방식을 도입하여 손쉽게 공통 기능을 추가/수정/삭제할 수 있도록 함.
- AOP가 코드의 분석을 어렵게 할 수 있기 때문에 보수적으로 사용하는 경우가 많다.
AOP의 기본 개념들
- Aspect
여러 클래스나 기능에서 공통적으로 AOP를 넣어줘야 하는 케이스에 대한 모듈.
AOP에서 가장 많이 활용되는 부분은 @Transactional (트랜잭션 기능) 이다. - Advice
AOP에서 실제로 적용하는 기능 - Join Point
Aspect된 기능들을 넣을 수 있는 지점 - Point Cut
Join Point 중에서 해당 Aspect를 적용할 대상을 뽑을 조건식
-> 정규식과는 다르다. - Target Object
Advice가 적용될 대상 Object - AOP Proxy
대상 Object에 Aspect를 적용하는 경우 Advice를 덧붙이기 위해 하는 작업을 AOP Proxy라고 함.
주로 CGLIB(Code Generation Library, 실행 중에 실시간으로 코드를 생성하는 라이브러리) 프록시를 사용하여 프록싱 처리를 한다.
AspectJ 지원
- AspectJ는 AOP를 제대로 사용하기 위해 꼭 필요한 라이브러리
- 기본적으로 제공되는 Spring AOP로는 다양한 기법(Pointcut 등)을 사용할 수 없음
Aspect의 생성
package org.xyz; import org.aspectJ.lang.annotation.Aspect; @Aspect @Component // Compnent를 붙인 것은 해당 Aspect를 스프링의 Bean으로 등록하여 사용하기 위함 public class UsefulAspect { }
Pointcut 선언
package org.xyz; import org.aspectJ.lang.annotation.Aspect; @Aspect @Component // Compnent를 붙인 것은 해당 Aspect를 스프링의 Bean으로 등록하여 사용하기 위함 public class UsefulAspect { // 해당 Aspect의 Advice가 적용될 Join Point를 찾기 위한 패턴/조건 생성 // 이를 포인트 컷 표현식이라고 부름 @Pointcut("execution(* transfer(..))") private void anyOldTransfer() {} }
Pointcut 결합
package org.xyz; import org.aspectJ.lang.annotation.Aspect; @Aspect @Component // Compnent를 붙인 것은 해당 Aspect를 스프링의 Bean으로 등록하여 사용하기 위함 public class UsefulAspect { @Pointcut("execution(public * * (..))") private void anyOldTransfer() {} // public 메서드 대상 포인트 컷 @Pointcut("within(com.xyz.myapp.trading.*))") private void inTrading() {} // 특정 패키지 대상 포인트 컷 @Pointcut("anyOldTransfer() && inTrading()") private void tradingOperation() {} // 위의 두 조건을 and(&&) 조건으로 결합한 포인트 컷 }
Advice 정의
포인트컷들을 활용하여 포인트컷의 전/후/주변에서 실행될 액션을 정의함
Before Advice
dataAccessOperation()이라는 미리 정의된 포인트 컷의 바로 전에 doAccessCheck가 실행됨
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect Public class BeforeExample { @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()") public void doAccessCheck() { // ... } }
After Returning Advice
dataAccessOperation()이라는 미리 정의된 포인트컷에서 return이 발생된 후 실행
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect Public class BeforeExample { @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()") public void doAccessCheck() { // ... } }
Around Advice
businessService()라는 포인트컷의 전/후에 필요한 동작을 추가함
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect Public class BeforeExample { @Around("com.xyz.myapp.CommonPointcuts.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch 스톱워치의 시작 시간 Object retVal = pjp.proceed(); // stop stopwatch 스톱워치의 종료 시간 return retVal; } }
5. Validation in Spring
Validation?
- 유효성 검사
- 주로 사용자 또는 서버의 요청 내용에서 잘못된 내용이 있는지 확인하는 단계를 의미함.
Validation의 종류
- 학문적으로 접근 시 여러 세부적인 단계들이 존재하지만 실제로 개발자가 주로 챙겨야 하는 검증은 크게 두 종류로 나뉜다.
- 데이터 검증
- 필수 데이터의 존재 유무
- 문자열의 길이나 숫자형 데이터의 경우 값의 범위
- 이메일, 신용카드 번호 등 특정 형식에 맞춘 데이터 - 비즈니스 검증
- 서비스의 정책에 따라 데이터를 확인하여 검증
-> 배달앱인 경우, 배달 요청을 할 때 해당 주문건이 결제 완료 상태인지 확인
- 경우에 따라 외부 API를 호출하거나 DB의 데이터까지 조회하여 검증한다.
Spring의 Validation
스프링은 웹 레이어에 종속적이지 않은 방법으로 Validation을 하려고 의도하고 있으며 주로 아래 두가지 방법을 활용하여 Validation을 진행함 (둘 다 데이터 검증에 가까움)
Java Bean Validation
JavaBean 기반으로 간편하게 개별 데이터를 검증
가장 많이 활용되는 방법 중 하나이며, 아래 코드처럼 JavaBean 내에 어노테이션으로 검증 방법을 명시함
public class MemberCreationRequest { @NotBlank(message="이름을 입력해주세요.") @Size(max=64, message="이름의 최대 길이는 64자 입니다.") private String name; @Min(0, "나이는 0보다 커야 합니다.") private int age; @Email("이메일 형식이 잘못되었습니다.") private int email; // the usual getters and setters... }
위처럼 요청 dto에 어노테이션으로 명시 후 아래처럼 @Valid 어노테이션을 해당 @RequestBody에 달게 되면, Java Bean Validation을 수행한 후 문제가 없을 때만 메서드 내부로 진입이 된다.
-> 검증 중 실패가 발생하면? : MethodArgumentNotValidException이 발생
@PostMapping(value = "/member") public MemberCreationResponse createMember( @Valid @RequestBody final MemberCreationRequest memberCreationRequest) { // member creation logics here... }
Spring Validator 인터페이스 구현을 통한 validation
public class Person { private String name; private int age; // the usual getters and setters... }
위처럼 person이라는 javaBean 객체가 있을 때, 아래는 해당 인스턴스에서만 활용되는 validator이다.
인터페이스에 있는 두 개의 메서드는 아래와 같은 역할을 한다.
- supports : 이 validator가 동작할 조건을 정의, 주로 class의 타입을 비교
- validate : 원하는 검증을 진행한다.
public class personValidator implements Validator { /** * This Validator validates only Person instances */ public boolean supports(Class clazz) { return Person.class.equals(clazz); } public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmpty(e, "name", "name.empty"); Person p = (Person) obj; if (p.getAge() <0) { e.rejectValue("age", "negativvalue"); } else if (p.getAge() > 110) { e.rejectValue("age", "too.darn.old"); } }
Validation 수행 시 주의사항 및 패턴
주의사항
- validation이 너무 여러 군데에서 이루어지면 테스트 및 유지보수성이 떨어짐
- 중복된 검증 : 정책 변경 시에 모든 중복 코드를 수정해야 함
- 다른 검증 : 여러 군데서 다른 정책을 따르는 검증이 수행될 수 있음 - validation은 가능한 로직 초기에 수행 후 실패 시에 exception을 던지는 편이 편리함
실무 활용 패턴
- 강사님의 주 사용 패턴
- 요청 dto에서 Java Bean Validation으로 단순 데이터(유무, 범위, 형식 등)를 1차 검증
- 로직 초기에 2차로 비즈니스 검증 수행 후 실패 시에는 Custom Exception(ErrorCode, ErrorMessage를 입력)해서 예외를 던지도록 하고 예외 처리하여 응답 생성 - Spring Validator의 주관적 장단점
- 장점 : Java Bean Validation에 비해서 조금 더 복잡한 검증이 가능함.
- 단점 : Validation을 수행하는 코드를 찾기가 (상대적으로) 어렵다.
완전히 데이터만 검증하는 것이 아니기 때문에 일부 비즈니스적인 검증이 들어가는 경우가 있다.
-> 이러한 경우 비즈니스 검증 로직이 여러 군데로 흩어지기 때문에 잘못된 검증을 수행할 가능성이 높다.
7. Data Binding
사용자나 외부 서버의 요청 데이터를 특정 도메인 객체에 저장해서 우리 프로그램에 대한 Request에 담아주는 것을 의미한다.
Converter<S, T> Interface
S(Source)라는 타입을 받아서 T(Target)이라는 타입으로 변환해주는 interface
package org.springframework.core.convert.converter; public interface Converter<S, T> { T convert(S source); }
Formatter
특정 객체와 String 간의 변환을 담당
아래 샘플 코드는 Date와 String 간의 변환을 수행하는 Formatter.
- print : API 요청에 대한 응답을 줄 때, Date 형식으로 된 데이터를 특정 locale에 맞춘 String으로 변환
- parse : API 요청을 받아올 때, String으로 된 "2021-01-01 13:15:00" 같은 날짜 형식의 데이터를 Date로 변환하도록 함
package org.springframework.format.datetime public final class DateFormatter implements Formatter<Date> { public String print(Date date, Locale locale) { return getDateFormat(locale).format(date); } public Date parse(String formatted, Locale locale) throws ParseException { return getDateFormate(locale).parse(formatted); } }
Formatter도 Converter와 마찬가지로 Spring Bean으로 등록하면 자동으로 ConversionService에 등록시켜주기 때문에 필요(요청/응답 시 해당 데이터 타입이 있는 경우)에 따라 자동으로 동작한다.
'JAVA > Spring' 카테고리의 다른 글
Spring Boot [02.Spring Resource와 SpEL] (0) 2022.01.18 패스트캠퍼스 챌린지 19일차 (0) 2021.11.19 패스트캠퍼스 챌린지 18일차 (0) 2021.11.18 패스트캠퍼스 챌린지 17일차 (0) 2021.11.17 패스트캠퍼스 챌린지 16일차 (0) 2021.11.16