내배캠 TIL 30일차

오병택·2025년 3월 31일

내배캠

목록 보기
52/73

학습 요약

schedulev2 lv0, Spring 숙련 강의

schedulev2 lv0


Spring 숙련 - 1주차

객체 지향 설계

SOLID 원칙

객체 지향 설계의 5가지 기본 원칙, 소프트웨어 설계에서 유지보수성, 확장성, 유연성을 높이기 위한 지침을 제공

SOLID 원칙의 종류

  • 단일 책임 원칙 SRP(Single Responsibility Principle)
    • 하나의 클래스는 하나의 책임만 가져야 함
  • 개방 폐쇄 원칙 OCP(Open Closed Principle)
    • 소프트웨어 요소는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 함
  • 리스코프 치환 원칙 LSP(Liskov Substitution Principle)
    • 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 함
  • 인터페이스 분리 원칙 ISP(Interface Segregation Principle)
    • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 나음
  • 의존관계 역전 원칙 DIP(Dependency Inversion Principle)
    • 구체적인 클래스에 의존하지 말고, 인터페이스나 추상 클래스에 의존하도록 설계해야 함

Spring의 핵심 개념

Spring Container

  • Spring으로 구성된 애플리케이션에서 객체(Bean)를 생성, 관리, 소멸하는 역할을 담당
  • 애플리케이션 시작 시, 설정 파일이나 Annotation을 읽어 Bean을 생성하고 주입하는 모든 과정을 컨트롤

Java의 객체 생성

  • 사용하는 클래스에서 직접 생성

Spring Container의 역할

  • 객체(Bean)를 생성 및 관리하고 의존성을 주입하는 역할을 담당

  • 객체를 직접 생성하는 경우, 객체 간의 의존성 및 결합도 ⬆

    • OCP, DIP 위반
  • Spring Container를 사용하면 인터페이스에만 의존하는 설계가 가능

    • OCP, DIP 준수

Spring Container의 종류

  • BeanFactory
    • Spring Container의 최상위 인터페이스
    • Spring Bean을 관리하고 조회
  • ApplicationContext
    • BeanFactory의 확장된 형태(implements)
    • Application 개발에 필요한 다양한 기능을 추가적으로 제공
      • 국제화, 환경변수 분리, 이벤트, 리소스 조회
    • 일반적으로 이것을 사용

Spring Bean

  • Spring 컨테이너가 관리하는 객체를 의미
  • 자바 객체 자체는 특별하지 않지만, Spring이 이 객체를 관리하는 순간부터 Bean이 됨
  • Spring은 Bean을 생성, 초기화, 의존성 주입 등을 통해 관리

  • Bean은 new 키워드 대신 사용하는 것
  • Spring Container가 제어

Spring Bean의 특징

  1. Spring 컨테이너에 의해 생성되고 관리
  2. 기본적으로 Singleton으로 설정
  3. 의존성 주입(DI)을 통해 다른 객체들과 의존 관계를 맺을 수 있음
  4. 생성, 초기화, 사용, 소멸의 생명주기를 가짐

Bean 등록 방법

XML, Java Annotation, Java 설정파일 등을 통해 Bean으로 등록할 수 있음

1. XML

<beans>
    <!-- myBean이라는 이름의 Bean 정의 -->
    <bean id="myBean" class="com.example.MyBean" />
</beans>
public class MyApp {
    public static void main(String[] args) {
        // Spring 컨테이너에서 Bean을 가져옴
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyService myService = context.getBean("myBean", MyBean.class);
        myService.doSomething();
    }
}

2. Annotation
- @ComponentScan
@ComponentScan은 Spring이 특정 패키지를 스캔하여 @Component가 붙은 클래스들을 자동으로 빈(Bean)으로 등록

@ComponentScan은 여기 경로에 있는 것들을 등록해줄꺼야라는 느낌이고 실제 등록은

ApplicationContext context = new AnnotationConfigApplicationContext(~~.class);

를 통해서 컨테이너를 시작해주고 @ComponentScan을 통해 그때 등록해주는 것

  • ~~.class@Configuration이 선언된 클래스를 의미
  • 위 코드는 AnnotationConfigApplicationContext의 생성자로 전달하여 **해당 클래스에서 직접 정의한 빈(Bean)과 @ComponentScan으로 검색된 빈을 컨테이너에 등록하는 부분임

3. Java 설정파일(수동 등록)

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }
}
public class MyApp {
    public static void main(String[] args) {
        
        AnnotationConfigApplicationContext context = 
        new AnnotationConfigApplicationContext(AppConfig.class);
        
        MyService myService = context.getBean(MyService.class);
        
        myService.doSomething();
    }
}

IOC/DI

IOC(제어의 역전, Inversion Of Control)

  • 객체의 생성과 관리 권한을 개발자가 아닌 Spring 컨테이너가 담당하는 것
  • 기본적으로 개발자가 객체를 직접 생성하고 관리했지만, Spring에서는 컨테이너가 객체 생성, 주입, 소멸을 관리

IoC 개념

  1. 객체의 생성 및 생명주기 관리를 개발자가 직접 하는 것이 아니라 컨테이너가 담당
  2. 객체 간의 결합도를 낮춰 유연한 코드가 됨

DI(의존성 주입, Dependency Injection)

  • Spring이 객체 간의 의존성을 자동으로 주입해주는 것을 의미
  • 한 객체가 다른 객체를 사용할 때, 해당 객체를 직접 생성하지 않고 Spring이 주입해주는 방식
  • IOC를 구현하는 방식 중 하나

개발자가 직접 관리

  • 객체 간의 강한 결합을 유발
  • 클라이언트측 코드에 영향
    • 객체를 계속해서 생성해야 함

Spring Container가 관리(IOC, DI)

  • 클라이언트의 코드에 영향X
  • 다른 구현체를 구현하여 Bean으로 등록하면 자유롭게 변경이 가능
  • 의존성 주입(DI), 제어의 역전(IOC)을 통해 객체 간의 결합도를 낮추고 유연한 설계가 가능

Singleton Pattern

클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 디자인 패턴

싱글톤 패턴의 등장

Web Application은 불특정 다수의 고객이 동시에 많은 요청을 보내게 되면 요청을 할 때 마다 객체를 새로 생성되고 처리가 완료되면 소멸되어 메모리 낭비가 아주 심해지게 됨

싱글톤 패턴 적용

객체가 한번만 생성되어 리소스를 절약 가능

싱글톤 패턴의 문제점

싱글톤 패턴을 구현하기 위한 코드의 양이 많음
구현 클래스에 의존해야 함(DIP, OCP 위반)
ex)

public class MainApp {
    public static void main(String[] args) {
       // 첫 번째 싱글톤 인스턴스 요청, 구현클래스.getInstance();
        Singleton instance1 = SingletonImpl.getInstance();
        instance1.showMessage(); // 인스턴스 주소값 출력

        // 두 번째 싱글톤 인스턴스 요청, 구현클래스.getInstance();
        Singleton instance2 = SingletonImpl.getInstance();
        instance2.showMessage(); // 인스턴스 주소값 출력
        
        // 다른 구현체로 바꾸려면 DIP, OCP 위반
        Singleton instance3 = SingletonImplV2.getInstance();
        instance3.showMessage();
    }
}

Spring의 싱글톤 컨테이너

  • Spring Container는 싱글톤 패턴의 문제점들을 해결하면서 객체를 싱글톤으로 관리
  • Spring Bean은 싱글톤으로 관리되는 객체

싱글톤 패턴의 주의점

객체의 인스턴스를 하나만 생성하여 공유하는 싱글톤 패턴의 객체는 상태를 유지(stateful)하면 안됨

이유

  • 데이터의 불일치나 동시성 문제가 발생할 수 있음

Spring Bean 등록

@ComponentScan

@ComponentScan의 속성

  • basePackages: 특정 패키지를 스캔할 때 사용, 배열로 여러개를 선언할 수 있음
    • 예시: @ComponentScan(basePackages = {"com.example", "com.another"})
  • basePackageClasses: 특정 클래스가 속한 패키지를 기준으로 스캔할 수 있음
    • 예시: @ComponentScan(basePackageClasses = MyApp.class)
  • excludeFilters: 스캔에서 제외할 클래스를 필터링할 수 있음
    • 예시: @ComponentScan(excludeFilters = @ComponentScan.Filter(SomeClass.class))
  • includeFilters: 특정 조건에 맞는 클래스만 스캔하여 포함할 수 있음
    • 예시: @ComponentScan(includeFilters = @ComponentScan.Filter(Service.class))

Bean 충돌

같은 이름의 Bean 등록(자동 vs 자동)

@Component("이름")
public class v1 ~~
@Component("이름")
public class v2 ~~
  • ConflictingBeanDefinitionException 발생

같은 이름 (수동 Bean 등록 VS 자동 Bean 등록)

  • 수동 Bean 등록이 자동 Bean 등록을 오버라이딩해서 우선권을 가짐
  • Spring Boot에서는 수동과 자동 Bean등록의 충돌이 발생하면 오류가 발생

설정 변경(application.properties)

// true면 수동, 자동 Bean을 동시에 등록할 때 이름이 같으면 수동 Bean이 오버라이딩, false는 default값
spring.main.allow-bean-definition-overriding=true 

의존관계 주입

의존관계 주입을 하는 4가지 방법

의존관계 주입 방법

  • 의존관계 주입 방법
    • @Autowired 는 의존성을 자동으로 주입할 때 사용하는 Annotation
      • 기본적으로 주입할 대상이 없으면 오류가 발생(required = true)
  1. 생성자 주입
    • 생성자를 통해 의존성을 주입하는 방법
    • 최초에 한번 생성된 후 값이 수정X[불변, 필수]

ex)

// 생성자 주입 방식
@Component
public class MyApp {
		// 필드에 final 키워드 필수! 무조건 값이 있도록 만들어준다.
    private final MyService myService;

		// 생성자를 통해 의존성 주입, 생성자가 하나인 경우 생략 가능
    @Autowired
    public MyApp(MyService myService) {
        this.myService = myService;
    }
}
  1. Setter 주입
    • Setter 메서드를 통해 의존성을 주입하는 방법

ex)

@Component
public class MyApp {

    private MyService myService;

    // Setter 주입, MyService가 Spring Bean으로 등록되지 않은 경우에도 주입이 가능
    @Autowired 
    public void setMyService(MyService myService) {
        this.myService = myService;
    }
}
  • (required = false)를 옆에 붙여서 선택하거나, 변경 가능한 의존관계에 사용할 수도 있음(생성자 주입은 필수 값)
  1. 필드 주입
    • 필드에 직접적으로 주입하는 방법 (추천X)
@Component
public class MyApp {

    @Autowired
    private MyService myService;  // 필드에 직접 주입
    
}
  • 코드는 간결하지만 Spring이 없이 사용X
  • 외부에서 myService 값을 설정하거나 변경할 방법X
  • 순수 Java 코드로 사용X
  • 테스트 코드나 Spring에서만 사용하는 @Configuration 같은 곳에서 주입할 때 주로 사용
  1. 일반 메서드 주입
    • 생성자, setter 주입으로 대체가 가능하기 때문에 사용X

⚠ 의존관계를 자동으로 주입할 객체가 Spring Bean으로 등록되어 있어야 @Autowired 로 주입이 가능

생성자 주입 방식을 권장하는 이유

  • 객체를 생성할 때 최초 한번만 호출(불변 보장)

  • 실수 방지

    • 순수 Java 코드로 사용할 때(주로 테스트 코드) 생성자의 필드를 필수로 입력하도록 하여 NPE 방지시켜줌
    • 컴파일 시점에 오류를 발생시켜서 실행 전에 오류를 알 수 있음

@RequiredArgsConstructor

  • final 필드를 모아서 생성자를 자동으로 만들어 주는 역할
  • Annotation Processor 가 동작하며 컴파일 시점에 자동으로 생성자 코드를 만들어줌

Spring Bean 등록 2강

@Qualifier, @Primary

같은 타입의 Bean이 중복된 경우 해결하기 위해 사용하는 Annotation

  • 같은 타입의 Bean 충돌 해결 방법
    1. @Autowired + 필드명 사용
      • @Autowired 는 타입으로 먼저 주입을 시도하고 같은 타입의 Bean이 여러개라면 필드 이름 혹은 파라미터 이름으로 매칭
public interface MyService { ... }

@Component
public class MyServiceImplV1 implements MyService { ... }

@Component
public class MyServiceImplV2 implements MyService { ... }

@Component
public class ConflictApp {

	// 필드명을 Bean 이름으로 설정
	@Autowired
	private MyService myServiceImplV2;
	...
}
  1. @Qualifier 사용
    • Bean 등록 시 추가 구분자를 붙여줌
    • 생성자 주입, setter 주입 사용 가능
@Component
@Qualifier("firstService")
public class MyServiceImplV1 implements MyService { ... }

@Component
@Qualifier("secondService")
public class MyServiceImplV2 implements MyService { ... }

@Component
public class ConflictApp {

		private MyService myService;

		// 생성자 주입에 구분자 추가
		@Autowired
		public ConflictApp(@Qualifier("firstService") MyService myService) {
				this.myService = myService;
		}
	
		// setter 주입에 구분자 추가
		@Autowired
		public void setMyService(@Qualifier("firstService") MyService myService) {
				this.myService = myService;
		}
	...
}
  1. @Primary 사용
    • 위에 @Primary를 붙여서 지정된 Bean이 우선 순위를 가짐

@Primary, @Qualifier를 동시 사용하면 @Qualifier가 우선순위 더 높음

수동 Bean 등록을 사용하는 경우

  • 외부 라이브러리 등록
  • 생성 로직 커스터마이징
  • 테스트 목적(가짜를 만들어서 테스트할 수 있음)

Validation

특정 데이터(주로 클라이언트의 요청 데이터)의 값이 유효한지 확인하는 단계를 의미

Validation의 역할

  1. 검증을 통해 적절한 메세지를 유저에게 보여주어야 함
  2. 검증 오류로 인해 정상적인 동작을 하지 못하는 경우는 없어야 함
  3. 사용자가 입력한 데이터는 유지된 상태여야 함

검증의 종류

  1. 프론트 검증
    • 해당 검증은 유저가 조작할 수 있음으로 보안에 취약
    • 보안에 취약하지만 그럼에도 꼭 필요
    ex)
    비밀번호에 특수문자가 포함되어야 한다면 즉각적인 alert 가능 → 유저 사용성 증가
  2. 서버 검증
    • 프론트 검증없이 서버에서만 검증한다면 유저 사용성이 떨어짐
    • API Spec을 정의해서 Validation 오류를 Response 예시에 남겨주어야 함
      • API 명세서를 잘 만들어야 그에 맞는 대응을 할 수 있음
    • 서버 검증은 선택이 아닌 필수
  3. 데이터베이스 검증
    • Not Null, Default와 같은 제약조건을 설정
    • 최종 방어선의 역할을 수행

BindingResult

  • Spring에서 기본적으로 제공되는 Validation 오류를 보관하는 객체
  • 주로 사용자 입력 폼을 검증할 때 많이 쓰이고 Field Error와 ObjectError를 보관
  • Errors 인터페이스를 상속받은 인터페이스
  • Errors 인터페이스는 에러의 저장과 조회 기능을 제공
  • BindingResult는 addError() 와 같은 추가적인 기능을 제공
  • Spring이 기본적으로 사용하는 구현체는 BeanPropertyBindingResult

파라미터에 BindingResult가 없는 경우

  • 오류가 발생하면 controller가 호출 되지 않음

파라미터에 BindingResult가 있는 경우

🚫 주의사항 : BindingResult 파라미터는 검증대상 파라미터 뒤에 위치

  • @ModelAttribute 필드 or 객체에 파라미터 바인딩 오류가 발생
    • BindingResult에 오류가 보관되고 Controller는 정상적으로 호출

@ModelAttribute는 파라미터를 필드 하나하나에 바인딩하기 때문에 파라미터에 Binding Result가 함께 있는 경우 만약 그중 하나의 필드에 오류가 발생하면 해당 필드를 제외하고 나머지 필드들만 바인딩 된 후 Controller가 호출


Bean Validation 1강

Bean Validation

특정 필드 검증의 경우 빈값, 길이, 크기, 형식 과 같은 간단한 로직이라 이 로직을 모든 프로젝트에 적용할 수 있도록 표준화 한 것

  1. Bean Validation은 기술 표준 인터페이스
  2. 다양한 Annotation들과 여러가지 Interface로 구성
    • Bean Validation(인터페이스) 구현체인 Hibernate Validator를 사용
  • Annotation을 적용시키는것 만으로 Validation을 아주 쉽게 적용할 수 있음
  • Controller에 개발자가 기본적인 검증 로직을 작성할 필요X
  • RestController의 @RequestBody에도 사용할 수 있음

Field Error

Bean Validation 적용

의존성 추가(build.gradle)

implementation 'org.springframework.boot:spring-boot-starter-validation'
  • @Range Annotation은 Hibernate Validator 에서만 동작
  • @NotBlank, @NotNull 은 validation 표준 인터페이스

Annotation 정리

  • @NotBlank
    1. null을 허용하지 않는다.
    2. 공백(” “)을 허용하지 않는다. 하나 이상의 문자를 포함해야한다.
    3. 빈값(””)을 허용하지 않는다.
    4. CharSequence 타입 허용
      • String은 CharSequence(Interface)의 구현체
  • @NotNull
    1. null을 허용하지 않는다.
    2. 모든 타입을 허용한다.
  • @NotEmpty
    1. null을 허용하지 않는다.
    2. 빈값(””)을 허용하지 않는다.
    3. CharSequence, Collection, Map, Array 허용

✔ import가 org.hibernate.validator로 시작하면 하이버네이트를 사용할 때만 제공되는 검증 기능으로 다른 구현체로 validator를 교체하였을 경우 동작X. 하지만 org.hibernate.validator를 대부분 사용

Validator

  • 단순히 Annotation을 선언해주면 검증이 완료되는 이유는 Validator(Validation을 사용하는것)가 존재하기 때문
  • Spring Boot는 validation 라이브러리를 설정하면 자동으로 Bean Validator를 Spring에 통합되도록 설정

@Valid, @Validated 차이점

  1. @Valid 는 JAVA 표준이고 @Validated 는 Spring 에서 제공하는 Annotation
  2. @Validated 를 통해 Group Validation 혹은 Controller 이외 계층에서 Validation이 가능
  3. @ValidMethodArgumentNotValidException 예외를 발생
  4. @ValidatedConstraintViolationException 예외를 발생

✔ Bean Validator는 바인딩에 실패한 필드는 Bean Validation을 적용하지 않음


Bean Validation 2강

에러 메세지

Spring의 Bean Validation은 Default로 제공하는 Message들이 존재하고 임의로 수정할 수 있음

Error Message

  1. Bean Validation을 적용하고 BindingResult에 등록된 검증 오류를 확인해보면 오류가 Annotation 이름으로 등록되어 있음

에러 메세지 수정

  1. NotNull.Object.fieldName
    • Annotation의 message 속성 사용
  2. NotNull.fieldName(MessageSource)
    • 필드명에 맞춘 사용자 정의 Message
  3. NotNull.FieldType(MessageSource)
    • 필드 타입에 맞춘 사용자 정의 Message
  4. NotNull
    • Annotation 자체에 포함된 Default Message

Object Error

  • 필드 단위가 아닌 객체 전체에 대한 오류를 나타냄
  • 두 필드 간의 관계를 검증할 때 ObjectError를 통해 해당 오류를 BindingResult에 기록할 수 있음

@ScriptAssert

  • @ScriptAssert는 제약사항 때문에 사용X
  • 실무에서는 훨씬 복잡한 Validation들이 필요하지만 대응이 불가능

Java 코드로 구현하기

ex) 총 가격이 10000원 이상이여야 함.

if (result < 10000) {
            // Object Error
            bindingResult.reject("totalMin", new Object[]{10000, result}, "총 합이 10000 이상이어야 합니다.");
        }
        
// Error가 있으면 출력
        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return bindingResult.getAllErrors().get(0).getDefaultMessage();
        }

Bean Validation 3강

groups

  • Bean Validation의 groups 속성은 다양한 유효성 검사 시나리오를 정의할 때 사용
  • 동일한 객체에 대한 검증을 상황에 따라 다르게 적용하고 싶을 때 groups를 활용
// 저장용 group
public interface SaveCheck {

}

// 수정용 group
public interface UpdateCheck {

}

@Data
public class ProductRequestDtoV2 {

    // 저장, 수정 @NotBlank Validation 적용
    @NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
    private String name;

    // 사용하는 모든곳에서 @NotNull Validation 적용
    @NotNull
    // 저장만 @Range 반영
    @Range(min = 10, max = 10000, groups = SaveCheck.class)
    private Integer price;

}

@Slf4j
@RestController
public class ProductController {

    @PostMapping("/v2/product")
    public String save(
            // 저장 속성값 설정
            @Validated(SaveCheck.class) @ModelAttribute ProductRequestDtoV2 requestDtoV2
    ) {
        ~~~
    }

    @PutMapping("/v2/product/{id}")
    public String update(
            @PathVariable Long id,
            // 수정 속성값 설정
            @Validated(UpdateCheck.class) @ModelAttribute ProductRequestDto test
    ) {
       ~~~
    }
}

@Validated VS @Valid

  1. @Validated

    • 속성값이 존재
    • spring이 제공하는 Annotation
  2. @Valid

    • 속성값이 존재하지 않는다, groups 기능 지원X
    • groups 기능을 사용하려면 @Validated를 사용해야 함
    • Java 표준 Annotation

Bean Validation 4강

@ModelAttribute, @RequestBody

  • @ModelAttribute는 요청 파라미터 혹은 Form Data(x-www-urlencoded)를 다룰 때 사용
  • @RequestBody 는 HTTP Body Data를 Object로 변환할 때 사용
  1. @ModelAttribute
    • 각각의 필드 단위로 바인딩
    • 특정 필드 바인딩이 실패하여도 나머지 필드는 정상적으로 검증 처리할 수 있음
    • 특정필드 변환 실패
      • 컨트롤러 호출, 나머지 필드 Validation 적용
  2. @RequestBody
    • 필드별로 적용되는것이 아니라 객체 단위로 적용
    • MessageConverter가 정상적으로 동작하여 Object로 변환하여야 Validation이 동작
    • 특정필드 변환 실패
      • 컨트롤러 미호출, Validation 미적용

느낀 점

오늘은 숙련 강의도 다 듣고 과제를 시작하게 되었다. 그 전 과제와 약간씩 다른 부분들이 존재해서 조금 더 걸린 것 같다. 이렇게 밀린 TIL을 쓰면서 복습하고 과제로 실습을 하면 실력이 확실히 늘 것 같다. 앞으로 더 힘내서 해봐야 겠다. (☆▽☆)

profile
걱정하지 말고 일단 해봐!

0개의 댓글