@ResponseBody
HTTP의 <body> </body> 내부에 직접 문자 내용을 반환시키는 역할을 한다. ‘viewResolver’ 대신에 **‘HttpMessageConverter’**가 동작한다. 본 문자처리시에는 ‘StringHttpMessageConverter’, 기본 객체처리시에는 ****‘MappingJackson2HttpMessageConverter’****가 ************************************************동작한다. byte 나 등등 기타 처리시에는 ‘**HttpMessageConverter’**가 기본 설정으로 등록되어 있다. 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합하여 HttpMessageConverter 가 선택된다.
클래스 의존관계
Controller(웹 MVC의 컨트롤러), Service(비지니스 로직을 구현), Repository (데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리) 데이터 저장소를 할당하기 전 단계에서 인터페이스를 이용하여 구현 클래스를 변경가능하도록 설계한다.
HashMap
Map 인터페이스를 구현하고, 키 값을 해시테이블에 저장하는 클래스이다. 해시 함수가 버킷사이에 요소를 적절하게 분산시킨다고 가정하면 get, put에 대해 일정한 시간 성능을 제공한다.
정렬되지 않은 컬렉터이기 때문에 추가되는 순서와 검색되는 순서는 다를 수 있다. 스레드로부터 안전하지 않으므로 외부 동기화가 없는 다중 스레드 환경에서 사용하기에는 적절하지 않다. 대안책으로 ConcurrentHashMap 클래스를 사용할 수도 있습니다.
<aside> 💡 Stream
컬렉션이나 배열에 저장된 데이터를 처리하기 위한 API이다. 특정 조건에 따라 Stream에서 요소의 하위 집합만 선택할 수 있는 필터링이다. filter() 스트림에 적용할 수 있으며 Stream에는 filter()를 통과하는 요소만 포함되게된다.
import java.util.Arra**ys;
import java.util.List**;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
// Create a list of integers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Create a stream from the list
Stream<Integer> stream = numbers.stream();
// Use the filter() method to select only the even numbers
Stream<Integer> evenNumbers = stream.filter(x -> x % 2 == 0);
// Print the even numbers to the console
evenNumbers.forEach(System.out::println); // Outputs 2 4 6 8 10
}
}
</aside>
<aside> 💡 Assertions(어설션)
프로그램의 특정 지점에서 True일 것으로 예상되는 조건을 지정하는 명령문이다. condition을 테스트하여 False가 나오면 알려준다. assertions을 사용하는 것은 코드의 올바름을 판단하는데 확신을 준다.
‘assertEquals’는 예상 출력이 실제 출력과 일치하는지 여부를 테스트하는 데 사용돤다.
테스트 목적에서만 사용해야 하며, 실제 프로그램 코딩에서는 사용해선 안된다.
int expected = 5;
int actual = someMethod();
assertEquals(expected, actual);
</aside>
assertThat 은 어설션과 마찬가지로 JUnit 테스트 프레임워크의 메서드 중 하나이다. 예상값을 매개인수로 사용하고 isEqualTo 같이 예상 값과 동일한지 확인하는 메서드와 함께 사용된다.
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import org.junit.Test;
public class MyTest {
@Test
public void testSomething() {
int actual = doSomething();
int expected = 10;
assertThat(actual, is(equalTo(expected)));
}
}
@AfterEach
명시된 메서드는 테스트 메서드 실행 후에 반드시 실행된다. 그와 반대로 테스트 메서드 실행 전에 만드시 실행되는 @BeforeEach 도 존재한다.
Spring 내용
service단은 서비스 의존, repository는 조금 더 개발자론 적으로 네이밍한다. Test 내용 given, when, then 단으로 세분단으로 나누어서 진행하는것이 보기좋다.
@Test
void join() {
// given
Member member = new Member();
member.setName("hello");
// when
Long saveId = memberService.join(member);
// then
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
중복 회원 검증 로직을 만들어서 정상적으로 작동했을때를 확인하는 것이 테스트 목표가 아님을 인지하고 예외를 생각해보는 것이 중요하다.
객체지향 설계 5대 원칙
로버트 마틴이 제시한 객체 지향 설계 5가지 원칙을 정리한 것이다. 결국 객체 지향의 핵심은 다형성이고, 다형
- 단일 책임의 원칙 (SRP)
- 하나의 모듈은 한 가지 책임을 가져야 한다. 이것은 모듈이 변경되는 이유가 한가지이어야 함을 의미한다. 해당 모듈이 여러 대상, 액터들에 대한 책임을 가져서는 안된다는 뜻이다. 단일 책임의 원칙은 변경이 필요한 대상을 명확히 알 수 있다는 장점을 가진다. 이러한 원칙은 규모가 커지면 커질수록 극대화된다.
- 개방 폐쇄의 원칙 (OCP)객체가 알아야 하는 지식이 많아지면 결합도가 높아진다. 결합도가 높아질수록 OCP 원칙을 지키는 구조를 설계하기가 어려워진다.
- 확장에 대해 열려있고, 수정에 대해서는 닫혀있어야 한다는 뜻이다. 즉, 요구사항이 변경될 때 새로운 동작을 추가하여 기능을 확장시킬 수 있어야 한다는 뜻이며, 기존의 코드를 수정하지 않고 동작을 추가하거나 변경할 수 있어야 한다는 뜻이다. 개방 폐쇄 원칙을 지키기 위해서는 추상화에 의존해야 한다.
- 리스코프 치환의 원칙 (LSP)자식 클래스가 부모 클래스를 대체하기 위해서는 부모 클래스에 대한 클라이언트의 가정을 준수해야 한다는 것을 강조한다.
- 하위 타입은 상위 타입을 대체할 수 있어야 한다. 해당 객체를 사용하는 클라이언트는 상위 타입이 하위 타입으로 변경되어도, 차이점을 인식하지 못한 채 상위 타입의 퍼블릭 인터페이스를 통해 서브 클래스를 사용할 수 있어야 한다.
- 인터페이스 분리 원칙 (ISP)예를 들어 파일 읽기/쓰기 기능을 갖는 구현 클래스가 있는데 어떤 클라이언트는 읽기 작업 만을 필요로 한다면 별도의 읽기 인터페이스를 만들어 제공해주는 것이다.
- 객체가 높은 응집도의 작은 단위로 설계됐다고 해도, 목표가 각각 다른 클라이언트가 있다면 인터페이스를 통해 적절하게 분리해 줄 필요가 있다. 이를 인터페이스 분리 원칙이라고 한다. 다시말해 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것이다. 궁극적으로 모든 클라이언트가 자신의 관심에 맞는 Public Interface만을 접근하여 불필요한 간섭을 최소화 할 수 있다.
- 의존관계 역전 원칙 (DIP)
- 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안되며, 저수준 모듈에서 정의한 추상 타입에 의존해야 한다. 객체 지향 프로그래밍 사이에서는 메세지를 주고 받기 위해서 의존생이 생기는데, 이것이 올바른 관계를 위한 원칙에 해당한다**. 다시말해 추상화에 의존하며, 구체화에는 의존하지 않는 원칙을 뜻한다.**
IoC, DI, 컨테이너
기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성, 연결, 실행했지만 AppConfig의 등장으로 클라이언트 구현 객체는 자신의 로직을 실행하는 역할만 담당하게 됐다. 따라서, 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라고 한다.
/*
일반적인 제어권: 자기가 사용할 의존성은 자기가 만들어서 사용
*/
@Service
public class CarService {
private CarRepository carRepository = new CarRepository();
}
/*
Inversion Of Control
다른 누군가가 의존성을 밖에서 준다.(제어권의 역전)
의존성을 주입해주는 일:Dependency Injection(일종의 IOC)
*/
@Service
public class CarService {
// CarRepository를 사용은 하지만 만들지는 않는다.
private CarRepository carRepository;
/*
생성자를 통해서 받아온다.
따라서 의존성을 관리하는 일은 CarService가 하는 일이아니다. 누군가 밖에서 해주는 것이다.
*/
public CarService(CarRepository carRepository){
this.carRepository = carRepository;
}
}
@Bean
컨테이너 안에 들어있는 객체들, 컨테이너에 담겨있기 때문에 사용하려면 불러와야한다. 여러 Annotation을 사용해서 일반적인 객체를 bean으로 등록할 수 있다. 객체를 Bean으로 등록할 때 아무 어노테이션을 붙이지 않으면 싱글톤 scope로 등록된다.
동일한 타입의 Bean들 중에 @Primary annotation을 붙여주면 그 bean이 우선권을 가지게 된다.
자바 코드로 직접 Spring @Bean을 등록하는 방법
우리는 @Repositroy, @Service 같은 어노테이션을 사용하여 bean을 등록하는 경우가 많았는데, 이번엔 기본적으로 어노테이션 없이 @Bean을 등록하는 방법을 알아보려고 한다.
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService();
}
}
@Configuration 을 사용하고, @Bean 으로 어노테이션을 설정해주면 스프링 컨테이너에 할당한다. MemberService가 컨테이너에 할당된다. MemberService() 내부에 memberRepository 구현체를 지정해주어야 MemberService와 Repository를 스프링빈에 등록한다. Spring bean에 등록된 repositroy를 MemberServcie에넣어준다.
두 개의 방법에 각각 장단점이 존재한다. 런타임중에 바꿀 일이 없기때문에 Setter는 사용하지 않는다. ( 실행중에 동적으로 바꿀일은 전무함 ) 실무에서는 정영화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다. 정형화 되지 않았거나, 상황에 따라 구현 클래스를 변경하면 Bean을 통해 설정을 등록한다.
DI 의존관계 주입
의존관계는 정적인 클래스의 의존관계와, 실행 시점에서 결정되는 동적인 인스턴스 의존관계 둘을 분리해서 생각해야한다.
정적인 클래스 의존관계
- 클래스가 사용하는 import 코드만 보고도 판단할 수 있다.
- 실행시키지 않아도 분석할 수 있다.
동적인 객체 인스턴스 의존 관계
- 애플리케이션의 실행 시점에서 실제 생성된 객체 인스턴스의 참조가 연결된 의존관계이다.
- 런타임때 외부에서 실제 구현 객체를 생성, 전달해서 클라이언트와 서버가 실제 연결되는 것을 의존관계 주입이라고 한다.
스프링 빈과 의존관계
@Controller
public class MemberController {
}
스프링 자체적으로 컨테이너라는 보관함이 생성된다. ( @Controller 어노테이션 )
@Controller
public class MemberController {
private final MemberService memberService = new MemberService();
}
컨트롤러와 서비스를 연결할때 사용하는 어노테이션이 @Autowired
흐름도
@Autowired
생성자, setter 등을 사용하여 의존성 주입을 하려 할 때, 해당 bean을 찾아서 주입해주는 어노테이션이다. required 값을 true나 false로 줄 수 있다. true인 경우 의존성주입에 필요한 객체가 무조건 bean으로 등록되어 있어야 한다. default 값은 true이다.
@Transactional
테스트 케이스에 어노테이션을 붙일 경우, 시작 전에 트랜잭션을 시작하고 테스트 완류 후에 항상 롤백한다. 따라서 데이트베이스에 테스트때 변경한 데이터가 남지 않아서 다음 데이터에 영향을 주지 않는다.
DAO
DB 데이터를 조회허가너 조작하는 기능을 전담하는 객체 DB 접근 로직과 비지니스 로직을 분리하기 위해서 사용
DTO
계층간의 데이터 교환을 위한 객체
로직을 가지지 않는 순수 데이터 객체(getter,setter)
VO
DTO와 동일개념, Read Only(수정불가) getter,setter 이외로 추가 로직 포함 가능
레이어드 아키텍쳐
레이어드 아키텍쳐란 Spring을 구성하고 있는 레이어들 그 자체를 뜻한다. 하나의 레이어가 자신 고유의 역할을 수행하고, 인접한 다른 레이어에게 무언가를 요청하고 응답한다. 시스템 전체를 수정하지 않고 특정한 레이어의 기능을 개선, 교체가 가능하기 때문에 재사용성이 좋고 유지 보수에 좋다.
Presentation Layer Service Layer Persistence Layer
Controller & Pages | → Servcie → | Repository |
Display Entity | ← Mapper → | JPA Entity |
- Presentation Layer View를 담당하는 부분, 클라이언트와 직접적으로 맞닿는 부분이다.
- Application Layer ( ⇒ Service Layer ) 비지니스 핵심 로직을 처리하는 부분으로, 하나의 트랜잭션으로 구성되어 작동한다.
- Persistence Layer 데이터 관련한 처리를 담당하는 부분이다.
'SpringBoot > Spring 김영한님' 카테고리의 다른 글
김영한 Spring MVC (1) - 쓰레드, SSR, CSR, Servlet (0) | 2023.03.20 |
---|---|
Spring 김영한 - 스프링 입문 (4) (0) | 2023.01.16 |
Spring 김영한 - 스프링 입문 (3) (0) | 2023.01.05 |
Spring 김영한 - 스프링 입문 (2) (0) | 2022.12.29 |
Spring 김영한 - JPA (1) (1) | 2022.12.23 |