▶ SOLID 원칙
객체 지향 설계의 5가지 기본 원칙, 소프트웨어 설계에서 유지보수성, 확장성, 유연성을 높이기 위한 지침을 제공한다.
-SOLID 원칙의 종류
1.단일 책임 원칙 SRP(Single Responsibility Principle)
: 하나의 클래스는 하나의 책임만 가져야 한다.
클래스는 한 가지 기능에 집중해야 하며, 그 외의 기능을 담당하지 않아야 한다
2.개방 폐쇄 원칙 OCP(Open Closed Principle)
: 소프트웨어 요소는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다.
새로운 기능을 추가할 때 기존 코드를 수정하지 않고, 확장할 수 있도록 설계해야 한다.
3.리스코프 치환 원칙 LSP(Liskov Substitution Principle)
: 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다.
부모 클래스를 사용하는 곳에서 자식 클래스를 사용해도 프로그램의 동작에 문제가 없어야 한다.
4.인터페이스 분리 원칙 ISP(Interface Segregation Principle)
: 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
즉, 하나의 큰 인터페이스보다는 여러 개의 작은 인터페이스로 분리해야 한다.
5.의존관계 역전 원칙 DIP(Dependency Inversion Principle)
: 구체적인 클래스에 의존하지 말고, 인터페이스나 추상 클래스에 의존하도록 설계해야 한다.
💡 객체 지향의 핵심은 다형성에 있다. 하지만 다형성 만으로는 OCP, DIP를 지킬 수 없다.
▶ Spring과 객체 지향
Spring은 다형성 만으로는 해결하지 못했던 객체 지향 설계 원칙 중 OCP, DIP를 IOC, DI를 통해 가능하도록 만들어준다.
- Spring의 역할
- OCP, DIP 원칙을 지킬 수 있도록 도와준다.
- 코드의 변경 없이 기능을 확장할 수 있도록 만들어 준다.
- 개발자가 마치 레고 블록을 조립하듯이 원하는 구성 요소를 손쉽게 교체하고 결합할 수 있도록 만들어준다.
- 다시보는 Spring Framework 의 등장 배경
- OCP, DIP 원칙을 지키며 개발하면 개발자가 할일이 아주 많아진다.
- Framework로 만들어서 개발자가 편하게 개발할 수 있도록 만들어졌다.
- 개발자의 역할
- 이상적으로는 모든 설계를 인터페이스로 만들어야 코드가 유연하게 변경이 가능해진다.
- 정해진 비지니스 로직이나 사용할 기술이 없는 상황에서도 개발할 수 있는 장점이 생긴다.
- 이상적으로는 모든 설계를 인터페이스로 만들어야 코드가 유연하게 변경이 가능해진다.
💡 실무에서는 추상화 과정에서 비용(시간)이 발생하기 때문에 기능을 확장할 가능성이 없다면 구현 클래스를 직접 사용하고 추후 변경된다면 인터페이스로 리팩토링 하면된다.
▶ Spring Container
Spring으로 구성된 애플리케이션에서 객체(Bean)를 생성, 관리, 소멸하는 역할을 담당한다. 애플리케이션 시작 시, 설정 파일이나 Annotation을 읽어 Bean을 생성하고 주입하는 모든 과정을 컨트롤한다.
- 객체를 직접 생성하는 경우, 객체 간의 의존성 및 결합도가 높아진다. -> OCP, DIP 위반
- Spring Container를 사용하면 인터페이스에만 의존하는 설계가 가능해진다. -> OCP, DIP 준수
-Spring Container의 종류
- BeanFactory
- Spring Container의 최상위 인터페이스
- Spring Bean을 관리하고 조회한다.
- ApplicationContext
- BeanFactory의 확장된 형태(implements)
- Application 개발에 필요한 다양한 기능을 추가적으로 제공한다. 국제화, 환경변수 분리, 이벤트, 리소스 조회
💡 일반적으로 ApplicationContext를 사용하기 때문에 ApplicationContext를 Spring Container라 표현한다.
▶ Spring Bean
Spring Container가 생성하고 관리하는 Java 객체를 의미한다. 자바 객체 자체는 특별하지 않지만, Spring이 이 객체를 관리하는 순간부터 Bean이 된다. Spring은 Bean을 생성, 초기화, 의존성 주입 등을 통해 관리한다.
- Bean은 new 키워드 대신 사용하는 것이다.
- Spring Container가 제어한다.
-Spring Bean의 특징
- Spring 컨테이너에 의해 생성되고 관리된다.
- 기본적으로 Singleton으로 설정된다.
- 의존성 주입(DI)을 통해 다른 객체들과 의존 관계를 맺을 수 있다.
- 생성, 초기화, 사용, 소멸의 생명주기를 가진다.
-Bean 등록 방법
- XML, Java Annotation(@Component, @ComponentScan), Java 설정파일 등을 통해 Bean으로 등록할 수 있다.
▶ IOC / DI
- IoC는 객체의 제어권을 개발자가 아닌 Spring 컨테이너에게 넘기는 개념으로, Spring이 객체 생성과 관리를 담당한다.
- DI는 Spring이 객체 간의 의존성을 자동으로 주입해주는 기법이다.
- 의존관계 주입은 객체 간의 결합도를 낮추고 코드의 유연성과 테스트 가능성을 높여준다.
▶ 싱글톤 패턴(Singleton Pattern)
클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 디자인 패턴이다. 객체가 한번만 생성되어 리소스를 절약할 수 있다.
-싱글톤 패턴의 문제점
- 싱글톤 패턴을 구현하기 위한 코드의 양이 많다.
- 구현 클래스에 의존해야 한다.(DIP, OCP 위반)
- 유연성이 떨어져서 안티패턴으로 불리기도 한다.
-싱글톤 패턴의 주의점
객체의 인스턴스를 하나만 생성하여 공유하는 싱글톤 패턴의 객체는 상태를 유지(stateful)하면 안된다.
- 상태 유지(stateful)의 문제점 -> 데이터의 불일치나 동시성 문제가 발생할 수 있다.
💡 Spring Bean은 항상 무상태(stateless)로 설계를 해야한다. 아주 중요!
특정 클라이언트에 의존적인 필드가 있거나 변경할 수 있으면 안된다.
-Spring의 싱글톤 컨테이너
- Spring은 Web Application을 만들 때 주로 사용된다.
- Spring Container는 싱글톤 패턴의 문제점들을 해결하면서 객체를 싱글톤으로 관리한다.
- Spring Bean은 싱글톤으로 관리되는 객체이다.
💡 Spring이 Bean을 등록하는 방법은 기본적으로 싱글톤이다. 하지만, 요청할 때 마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.
▶ Spring Bean 등록
Spring이 특정 패키지 내에서 @Component, @Service, @Repository, @Controller 같은 Annotation이 붙은 클래스를 자동으로 검색하고, 이를 Bean으로 등록하는 기능이다. 개발자가 Bean을 직접 등록하지 않고도 Spring이 자동으로 관리할 객체들을 찾는다.
- @ComponentScan의 동작 순서
- Spring Application이 실행되면 @ComponentScan이 지정된 패키지를 탐색한다.
- 해당 패키지에서 @Component 또는 Annotation이 붙은 클래스를 찾습니다.
- 찾은 클래스를 Spring 컨테이너에 빈으로 등록합니다.
- 등록된 빈은 **의존성 주입(DI)**과 같은 방식으로 다른 빈과 연결됩니다.
-Spring Bean을 등록하는 방법에는 수동, 자동 두가지가 존재한다.
Spring Bean은 Bean의 이름으로 등록된다.
1.자동 Bean 등록(@ComponentScan, @Component)
- @Component 이 있는 클래스의 앞글자만 소문자로 변경하여 Bean 이름으로 등록한다.
- @ComponentScan 을 통해 @Component로 설정된 클래스를 찾는다.
2.수동 Bean 등록(@Configuration, @Bean)
- @Configuration 이 있는 클래스를 Bean으로 등록하고 해당 클래스를 파싱해서 @Bean 이 있는 메서드를 찾아 Bean을 생성한다. 이때 해당 메서드의 이름으로 Bean의 이름이 설정된다.
💡수동으로 Bean을 등록할 때는 항상 @Configuration과 함께 사용해야 Bean이 싱글톤으로 관리된다. CGLIB 라이브러리와 연관이 있다.
- 수동 Bean 등록이 자동 Bean 등록을 오버라이딩해서 우선권을 가진다.
- 의도한 결과라면 다행이지만, 아닌 경우(실수)가 대부분이다. → 버그 발생
- Spring Boot에서는 수동과 자동 Bean등록의 충돌이 발생하면 오류가 발생한다.
⛔ 주의사항
- 테스트 시 자동 빈 등록 충돌 conflict 패키지의 Bean Name을 지워주세요.
- 테스트 시 반드시 @SpringBootApplication 으로 실행해야 합니다.
💡Annotation 기반의 Spring에서는 자동 Bean 등록과 의존관계 주입을 사용하는 경우를 주로 사용한다. @Component 뿐만 아니라 @Controller, @Service, @Repository 등 자동으로 쉽게 등록할 수 있는 Annotation들을 지원하고 Spring Boot는 ComponentScan 방식을 기본으로 사용한다.
▶ 의존관계 주입
의존관계 주입을 하는 방법으로 생성자 주입, setter 주입, 필드 주입, 메서드 주입 총 4가지 방법이 존재한다.
-의존관계 주입 방법
@Autowired 는 의존성을 자동으로 주입할 때 사용하는 Annotation이다.
기본적으로 주입할 대상이 없으면 오류가 발생한다.(required = true)
1.생성자 주입
- 생성자를 통해 의존성을 주입하는 방법.
- 최초에 한번 생성된 후 값이 수정되지 못한다.[불변, 필수]
- 생성자가 하나인 경우 @Autowired 생략이 가능하다.(생성자가 두개인 경우 생략이 불가능하다. 둘중 어떤 생성자를 사용해야 하는지 Spring은 알지 못한다.)
2.Setter 주입
- Setter 메서드를 통해 의존성을 주입하는 방법
- 선택하거나, 변경 가능한 의존관계에 사용한다.(생성자 주입은 필수 값)
3.필드 주입
- 필드에 직접적으로 주입하는 방법(가장 추천되지 않음)
- 코드는 간결하지만 Spring이 없으면 사용할 수 없다. 사용하지 않아야 한다.
- Application의 실행과 관계 없는 @SpringBootTest 테스트 코드나 Spring에서만 사용하는 @Configuration 같은 곳에서 주입할 때 주로 사용한다.
4.일반 메서드 주입
- 생성자, setter 주입으로 대체가 가능하기 때문에 사용하지 않는다.
💡의존관계를 자동으로 주입할 객체가 Spring Bean으로 등록되어 있어야 @Autowired 로 주입이 가능하다.
▶ 생성자 주입
과거 setter, 필드 주입도 사용했지만 현재는 DI를 가지고 있는 대부분의 Framework가 생성자 주입 방식을 권장한다.
-불변(immutable) :
- 객체를 생성할 때 최초 한번만 호출된다.
- setter 주입을 사용하면 접근제어자가 public 으로 설정되어 누구나 수정할 수 있게된다.
-실수 방지 :
- 순수 Java 코드로 사용할 때(주로 테스트 코드) 생성자의 필드를 필수로 입력하도록 만들어준다.(NPE 방지)
- 컴파일 시점에 오류를 발생 시킨다. 즉, 실행 전에 오류를 알 수 있다.
-Spring Framework에 의존하지 않아도 객체 지향 특성을 가장 잘 사용하는 방법이다.
💡필드에 final 은 생성자 주입 방식만 사용할 수 있다. 나머지 주입 방식들은 모두 생성 이후에 호출되어 사용할 수 없다.
▶ @RequiredArgsConstructor
실제 Web Application을 개발하면 대부분이 불변 객체이고 생성자 주입 방식을 선택하게 된다. 이런 반복되는 코드를 편안하게 작성하기 위해 Lombok에서 제공하는 Annotation 이다.
- final 필드를 모아서 생성자를 자동으로 만들어 주는 역할
- Annotation Processor 가 동작하며 컴파일 시점에 자동으로 생성자 코드를 만들어준다.
- 생성자를 하나 만들고 @Autowired 를 사용한 코드와 똑같이 동작한다.
▶ @Primary
같은 타입의 Bean이 중복된 경우 해결하기 위해 사용하는 Annotation
@Primary로 지정된 Bean이 우선 순위를 가진다.
-실제 적용 사례
- Database가 (메인 MySQL, 보조 Oracle) 두개 존재하는 경우
- 기본적으로 MySQL을 사용할 때 @Primary를 사용하면 된다.
- 필요할 때 @Qualifier로 Oracle을 사용하도록 만들 수 있다.
- 동시에 사용되는 경우 @Qualifier 의 우선순위가 높다.
▶ Validation(검증)
특정 데이터(주로 클라이언트의 요청 데이터)의 값이 유효한지 확인하는 단계를 의미한다.
Controller의 주요한 역할 중 하나는 Validation 이다. HTTP 요청이 정상인지 검증한다.
- Validation의 역할
- 검증을 통해 적절한 메세지를 유저에게 보여주어야 한다.
- 검증 오류로 인해 정상적인 동작을 하지 못하는 경우는 없어야 한다.
- 사용자가 입력한 데이터는 유지된 상태여야 한다.
💡기본적으로 프론트, 서버, 데이터베이스 모두 검증을 꼼꼼하게 하는것이 바람직하다. Validation으로 수많은 Error와 문제들을 방지할 수 있다.
▶ BindingResult
Spring에서 기본적으로 제공되는 Validation 오류를 보관하는 객체이다. 주로 사용자 입력 폼을 검증할 때 많이 쓰이고 Field Error와 ObjectError를 보관한다. BindingResult 파라미터는 검증대상 파라미터 뒤에 위치해야만 한다. BindingResult에 오류가 보관되고 Controller는 정상적으로 호출된다.
- Errors 인터페이스를 상속받은 인터페이스이다.
- Errors 인터페이스는 에러의 저장과 조회 기능을 제공한다.
- BindingResult는 addError() 와 같은 추가적인 기능을 제공한다.
- Spring이 기본적으로 사용하는 구현체는 BeanPropertyBindingResult 이다.
💡ModelAttribute는 파라미터를 필드 하나하나에 바인딩한다. 파라미터에 Binding Result가 함께 있는 경우 만약 그중 하나의 필드에 오류가 발생하면 해당 필드를 제외하고 나머지 필드들만 바인딩 된 후 Controller가 호출된다.
▶ Bean Validation
특정 필드 검증의 경우 빈값, 길이, 크기, 형식 과 같은 간단한 로직이다. 이러한 로직들을 모든 프로젝트에 적용할 수 있도록 표준화 한 것이 Bean Validation이다.
-객체의 필드나 메서드에 제약 조건을 설정하여, 올바른 값을 가지고 있는지 검증하는 표준화된 방법
- Bean Validation은 기술 표준 인터페이스이다.
- 다양한 Annotation들과 여러가지 Interface로 구성되어 있다. Bean Validation(인터페이스) 구현체인 Hibernate Validator를 사용한다.
- Annotation을 적용시키는것 만으로 Validation을 아주 쉽게 적용할 수 있다.
- Controller에 개발자가 기본적인 검증 로직을 작성할 필요가 없어졌다.
- RestController의 @RequestBody에도 사용할 수 있다.
▶ Field Error
- @NotBlank
null을 허용하지 않는다.
공백(” “)을 허용하지 않는다. 하나 이상의 문자를 포함해야한다.
빈값(””)을 허용하지 않는다.
CharSequence 타입 허용, String은 CharSequence(Interface)의 구현체이다.
- @NotNull
null을 허용하지 않는다.
모든 타입을 허용한다.
- @NotEmpty
null을 허용하지 않는다.
빈값(””)을 허용하지 않는다.
CharSequence, Collection, Map, Array 허용
-Validator
단순히 Annotation을 선언해주면 검증이 완료되는 이유는 Validator(Validation을 사용하는것)가 존재하기 때문이다. Spring Boot는 validation 라이브러리를 설정하면 'org.springframework.boot:spring-boot-starter-validation'자동으로 Bean Validator를 Spring에 통합되도록 설정해준다.
- @Valid, @Validated 차이점
- @Valid 는 JAVA 표준이고 @Validated 는 Spring 에서 제공하는 Annotation이다.
- @Validated 를 통해 Group Validation 혹은 Controller 이외 계층에서 Validation이 가능하다.
- @Valid 는 MethodArgumentNotValidException 예외를 발생시킨다.
- @Validated 는 ConstraintViolationException 예외를 발생시킨다.
-에러 메세지
Spring의 Bean Validation은 Default로 제공하는 Message들이 존재하고 임의로 수정할 수 있다.
▶ Object Error
필드 단위가 아닌 객체 전체에 대한 오류를 나타낸다. 예를들어 두 필드 간의 관계를 검증할 때 ObjectError를 통해 해당 오류를 BindingResult에 기록할 수 있다.
▶ Bean Validation의 충돌
등록, 수정 API에서 각각 다른 Validation이 적용된다면?
- 해결방법
- 저장할 Object를 직접 사용하지 않고 SaveRequestDto, UpdateRequestDto 따로 사용한다.
- Bean Validation의 groups 기능을 사용한다.
▶ groups
Bean Validation의 groups 속성은 다양한 유효성 검사 시나리오를 정의할 때 사용된다. 동일한 객체에 대한 검증을 상황에 따라 다르게 적용하고 싶을 때 groups를 활용할 수 있다.
▶ groups VS DTO 분리
Bean Validation의 충돌이 발생하는 경우 대부분 DTO를 분리하는 방법이 적절하다.
- DTO 분리
- 실제로 간단한 프로젝트를 개발해보면 저장, 수정시 Request가 비슷한 경우가 있다.
- 각각의 장단점이 존재하지만 어설프게 하나로 합칠 경우 유지보수시 엄청난 경험을 할 수 있다.
- RequestDto가 변한다는건 해당 API의 스펙 자체가 변경되어 많은 수정이 발생한다.
- 실무에서는 거의 발생하지 않는 경우기 때문에 간단한게 아니라면 대부분 분리하도록 하자!
▶ @ModelAttribute, @RequestBody
@Valid, @Validated는 @ModelAttribute뿐만 아니라 @RequestBody에도 적용할 수 있다. @ModelAttribute는 요청 파라미터 혹은 Form Data(x-www-urlencoded)를 다룰 때 사용하고 @RequestBody 는 HTTP Body Data를 Object로 변환할 때 사용한다.
-@ModelAttribute와 @RequestBody 차이점
- @ModelAttribute
- 각각의 필드 단위로 바인딩한다.
- 특정 필드 바인딩이 실패하여도 나머지 필드는 정상적으로 검증 처리할 수 있다.
- 특정필드 변환 실패
- 컨트롤러 호출, 나머지 필드 Validation 적용
- @RequestBody
- 필드별로 적용되는것이 아니라 객체 단위로 적용된다.
- MessageConverter가 정상적으로 동작하여 Object로 변환하여야 Validation이 동작한다.
- 특정필드 변환 실패
- 컨트롤러 미호출, Validation 미적용
'개발 > 부트캠프' 카테고리의 다른 글
본캠프 : Spring 숙련_3주차 실습 (0) | 2025.02.06 |
---|---|
본캠프 : Spring 숙련_2주차 (0) | 2025.02.05 |
본캠프 : 개인 과제(일정 관리 앱 만들기) 트러블 슈팅 (0) | 2025.02.03 |
본캠프 : 개인 과제(일정 관리 앱 만들기)_ CRUD & 3 Layered Architecture 동작 과정 (0) | 2025.02.02 |
본캠프 : Spring 기초_특강2_@AllArgsConstructor & @RequiredArgsConstructor (0) | 2025.01.31 |