오늘은 스프링부트 프로젝트 강의 7강부터 10강까지 들어보았다.
7강 ~ 9강까지는 가게 목록과 가게 상세 내역을 만드는 강의였다.
현재까지 만들어져 있는 코드 공유
*EatgoApplication
package kr.co.fastcampus.eatgo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EatgoApplication {
public static void main(String[] args) {
SpringApplication.run(EatgoApplication.class, args);
}
}
*Restaurant
package kr.co.fastcampus.eatgo.domain;
public class Restaurant {
private final Long id;
private final String name ;
private final String address;
public Restaurant(Long id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public String getInformation() {
return (name + " in "+ address);
}
}
*RestaurantRepository
package kr.co.fastcampus.eatgo.domain;
import java.util.ArrayList;
import java.util.List;
public class RestaurantRepository {
private List<Restaurant> restaurants = new ArrayList<>();
public RestaurantRepository(){
restaurants.add(new Restaurant(1004L,"Bob zip", "Seoul"));
restaurants.add(new Restaurant(2020L,"Cyber Food","Seoul"));
}
public List<Restaurant> findAll() {
return restaurants;
}
public Restaurant findById(Long id) {
return restaurants.stream()
.filter(r-> r.getId().equals(id))
.findFirst()
.orElse(null);
}
}
*RestaurantController
package kr.co.fastcampus.eatgo.interfaces;
import kr.co.fastcampus.eatgo.domain.Restaurant;
import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class RestaurantController {
private RestaurantRepository repository = new RestaurantRepository();
@GetMapping("/restaurants")
public List<Restaurant> list(){
List<Restaurant> restaurants = repository.findAll();
return restaurants;
}
@GetMapping("/restaurants/{id}")
public Restaurant detail(@PathVariable("id") Long id){
Restaurant restaurant = repository.findById(id);
return restaurant;
}
}
*WelcomeController
package kr.co.fastcampus.eatgo.interfaces;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WelcomeController {
@GetMapping("/")
public String hello(){
return "Hello, world!!!";
}
}
*EatgoApplicationTests
package kr.co.fastcampus.eatgo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class EatgoApplicationTests {
@Test
void contextLoads() {
}
}
*RestaurantTest
package kr.co.fastcampus.eatgo.domain;
import org.junit.jupiter.api.Test;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
public class RestaurantTests {
@Test
public void creation(){
Restaurant restaurant = new Restaurant(1004L,"Bob zip", "Seoul");
assertThat(restaurant.getId(), is(1004L));
assertThat(restaurant.getName(), is("Bob zip"));
assertThat(restaurant.getAddress(), is("Seoul"));
}
@Test
public void information(){
Restaurant restaurant = new Restaurant(1004L,"Bob zip", "Seoul");
assertThat(restaurant.getInformation(), is("Bob zip in Seoul"));
}
}
*RestaurantControllerTests
package kr.co.fastcampus.eatgo.interfaces;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@WebMvcTest(RestaurantController.class)
public class RestaurantControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void list() throws Exception {
mvc.perform(get("/restaurants"))
.andExpect(status().isOk())
.andExpect(content().string(
containsString("\"id\":1004")
))
.andExpect(content().string(
containsString("\"name\":\"Bob zip\"")
));
}
@Test
public void detail() throws Exception {
mvc.perform(get("/restaurants/1004"))
.andExpect(status().isOk())
.andExpect(content().string(
containsString("\"id\":1004")
))
.andExpect(content().string(
containsString("\"name\":\"Bob zip\"")
));
mvc.perform(get("/restaurants/2020"))
.andExpect(status().isOk())
.andExpect(content().string(
containsString("\"id\":2020")
))
.andExpect(content().string(
containsString("\"name\":\"Cyber Food\"")
));
}
}
10강 의존성 주입
<추가공부>
의존성 주입이란?
출처: 위키백과
소프트웨어 엔지니어링에서 의존성 주입은 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉이다. "의존성"은 예를 들어 서비스로 사용할 수 있는 객체이다. 클라이언트가 어떤 서비스를 사용할 것인지 지정하는 대신, 클라이언트에게 무슨 서비스를 사용할 것인지를 말해주는 것이다. "주입"은 의존성(서비스)을 사용하려는 객체(클라이언트)로 전달하는 것을 의미한다. 서비스는 클라이언트 상태의 일부이다. 클라이언트가 서비스를 구축하거나 찾는 것을 허용하는 대신 클라이언트에게 서비스를 전달하는 것이 패턴의 기본 요건이다.
의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것이다. 이는 가독성과 코드 재사용을 높혀준다.
의존성 주입은 광범위한 역제어 테크닉의 한 형태이다. 어떤 서비스를 호출하려는 클라이언트는 그 서비스가 어떻게 구성되었는지 알지 못해야 한다. 클라이언트는 대신 서비스 제공에 대한 책임을 외부 코드(주입자)로 위임한다. 클라이언트는 주입자 코드를 호출할 수 없다. 그 다음, 주입자는 이미 존재하거나 주입자에 의해 구성되었을 서비스를 클라이언트로 주입(전달)한다. 그리고 나서 클라이언트는 서비스를 사용한다. 이는 클라이언트가 주입자와 서비스 구성 방식 또는 사용중인 실제 서비스에 대해 알 필요가 없음을 의미한다. 클라이언트는 서비스의 사용 방식을 정의하고 있는 서비스의 고유한 인터페이스에 대해서만 알면 된다. 이것은 "구성"의 책임으로부터 "사용"의 책임을 구분한다.
의도
의존성 주입은 다음과 같은 문제를 해결한다.
-
어떻게 애플리케이션이나 클래스가 객체의 생성 방식과 독립적일 수 있는가?
-
어떻게 객체의 생성 방식을 분리된 구성 파일에서 지정할 수 있는가?
-
어떻게 애플리케이션이 다른 구성을 지원할 수 있는가?
객체를 필요로하는 클래스 내에서 직접 객체를 생성하는것은 클래스를 특정 객체에 커밋하는 것이고 이후에 클래스로부터 독립적으로(클래스의 수정 없이) 인스턴스의 생성을 변경하는것이 불가능하기 때문에 유연하지 못하다. 이는 다른 객체를 필요로하는 경우 클래스를 재사용할 수 없게하며, 실제 객체를 모의 객체로 대체할 수 없기 때문에 클래스를 테스트하기 힘들게한다.
클래스는 더 이상 객체 생성에 대한 책임이 없으며, 추상 팩토리 디자인 패턴에서처럼 팩토리 객체로 생성을 위임할 필요가 없다.
아래의 UML 클래스 및 시퀀스 다이어그램을 참고.
개요
5세의 의존성 주입
스스로 냉장고로 가서 무언가를 가져올 때 문제가 발생할 수 있다. 문을 열어 놓고 갈수도 있고, 엄마나 아빠의 물건을 가져갈 수도 있다. 심지어 있지도 않은 물건이나 유효기간이 지난 것을 찾고 있을 수도 있다.
필요한 것을 정의해야한다. "나는 점심이랑 같이 마실 뭔가가 필요해", 그 후에 뭔가를 가져왔는지 확인하고 앉아서 먹으면 된다.
John Munsch, 28 October 2009.
의존성 주입은 프로그램 디자인이 느슨하게 커플링되도록하고 의존관계 역전 원칙과 단일 책임 원칙을 따르도록 클라이언트의 생성에 대한 의존성을 클라이언트의 행위로부터 분리하는 것이다. 이는 클라이언트가 의존성을 찾기 위해 그들이 사용하는 시스템에 대해 알도록 하는 서비스 로케이터 패턴과 정반대되는 것이다.
의존성 주입의 기본 단위인 주입은 새롭거나 관습적인 메커니즘이 아니다. "매개변수 전달"과 동일하게 동작한다. 주입으로써 "파라미터 전달"은 클라이언트를 세부 사항과 분리하기 위해 수행되고 있는 부가적인 의미를 전달한다.
또한 주입은 전달을 제어하는 대상(클라이언트가 아님)에 대한 것이며 전달의 수행 방식-참조 또는 값 전달-과 독립적이다.
의존성 주입은 네 가지 역할을 포함한다.
-
사용될 서비스 객체
-
사용하는 서비스에 의존하는 클라이언트 객체
-
클라이언트의 서비스 사용 방법을 정의하는 인터페이스
-
서비스를 생성하고 클라이언트로 주입하는 책임을 갖는 주입자
비유하자면,
-
서비스 - 전기, 가스, 하이브리드 또는 디젤 자동차
-
클라이언트 - 엔진에 상관 없이 동일하게 차를 사용하는 운전자
-
인터페이스 - 운전자가 기어와 같은 엔진의 세부 사항을 이해할 필요가 없도록 보장해주는 자동변속기
-
주입자 - 아이에게 어떤 차를 사줄지 결정하고 구매해준 부모
사용될 수 있는 모든 객체는 서비스로 여겨진다. 다른 객체를 사용하는 모든 객체는 클라이언트로 여겨진다. 이름은 객체가 무엇을 위한 것인지와 무관하며 한 번의 주입에서 객체가 하는 역할과 관련이 있다.
인터페이스는 클라이언트가 의존성으로 예상하는 타입이다. 쟁점은 무엇을 접근할 수 있게 하는가이다. 이는 실제로 서비스에 의해 구현된 인터페이스 타입일 수도 있지만 추상 클래스 또는 DIP를 위반하고 테스팅을 위한 동적 디커플링을 희생하긴 하지만 concrete 서비스 자체일 수도 있다. 클라이언트가 인터페이스를 구성하거나 확장하는 것처럼 구체적으로 다룰 수 없도록 알지 못하게 하는 것이 요구된다.
클라이언트는 의존성의 특정 구현에 대한 구체적인 지식이 필요 없다. 인터페이스의 이름과 API만 알면 된다. 결과적으로 인터페이스가 변경되더라도 클라이언트는 수정할 필요가 없어진다. 하지만 인터페이스가 클래스로 존재하다가 인터페이스 타입으로(그 반대의 경우에도) 리팩토링될 경우 클라이언트는 다시 컴파일이 필요할 수 있다. 이는 클라이언트와 서비스가 독립적으로 발행될 경우 중요하다. 이 불행한 커플링은 의존성 주입으로 해결할 수 없다.
주입자는 클라이언트에게 서비스를 소개한다. 종종 클라이언트를 생성하기도 한다. 주입자는 객체를 클라이언트와 같이 처리하고 나중에 다른 클라이언트의 서비스로 처리하여 매우 복잡한 객체 그래프를 함께 연결할수도 있다. 주입자는 실제로 함께 동작하는 많은 객체가 될 수 있지만 클라이언트는 될 수 없다. 주입자는 다음과 같은 많은 이름들로 참조될 수 있다: assembler, provider, container, factory, builder, spring, construction code, or main.
의존성 주입은 모든 객체가 구성과 동작을 분리하도록 요구하는 하나의 규율로 적용될 수 있다. 구성을 수행하는 DI 프레임워크에 의지하면 new 키워드의 사용을 금지하거나 덜 엄격하게 값 객체의 직접 생성을 허용할 수 있다.
분류
역제어(IoC)는 DI보다 더 일반적이다. 간단하게 말하자면 IoC는 호출을 요구하는 대신 다른 코드가 호출할 수 있게 함을 의미한다. DI가 없는 IoC의 한 예로 템플릿 메소드 패턴이 있다. 여기서 다형성은 서브클래싱, 즉, 상속을 통해 달성된다.
의존성 주입은 컴포지션을 통해 IoC를 구현하므로 종종 전략 패턴과 동일하지만, 전략 패턴의 의도는 객체의 수명동안 의존성을 교환할 수 있도록 하는 것이고, 의존성 주입에서는 단일 의존성 인스턴스만 사용할 수 있도록 하는 것이다. 여전히 다형성은 유지되지만 위임과 컴포지션을 통해 이루어진다.
의존성 주입 프레임워크
CDI와 그 구현물인 Weld, Spring, Guice, Play framework, Salta, Glassfish HK2, Dagger, Managed Extensibility Framework(MEF)와 같은 애플리케이션 프레임워크는 의존성 주입을 지원하지만 필수는 아니다.
장점
-
의존성 주입은 클라이언트의 구성 가능성을 유연하게 해준다. 클라이언트의 행위는 고정되어 있다. 클라이언트는 클라이언트가 기대하는 고유한 인터페이스를 지원하는 모든 것을 할 수 있다.
-
의존성 주입을 통해 시스템의 구성 세부 사항을 외부의 구성 파일에서 사용하여 리컴파일 없이 시스템을 재 구성할 수 있다. 분리된 구성은 컴포넌트의 여러 구현을 요구하는 다양한 상황을 위해 작성될 수 있다. 이는 국한되어 있지는 않지만 테스팅을 포함한다.
-
의존성 주입은 코드의 동작에서의 어떠한 변경도 요구하지 않으므로 리팩터링으로써 레거시 코드에도 적용할 수 있다. 클라이언트는 더 독립적이며 테스트에 속하지 않은 다른 객체를 가장하는 stubs 또는 모의 객체를 사용해 독립된 유닛 테스트가 더 쉬워지는 결과를 얻는다.
-
의존성 주입을 통해 클라이언트는 사용해야하는 모든 구체적인 구현에 대한 지식을 제거할 수 있다. 디자인 변경이나 결함의 영향으로부터 클라이언트를 독립하는데 도움을 주며, 이는 재사용성, 테스트가능성, 유지가능성을 향상시킨다.
구조
UML 클래스 및 시퀀스 다이어그램
A sample UML class and sequence diagram for the Dependency Injection design pattern.
의존성 주입의 이점
-
코드 재사용을 높여서 작성된 모듈을 여러 곳에서 소스코드의 수정 없이 사용할 수 있다.
적용 유형
마틴 파울러는 다음과 같은 세 가지의 의존성 주입 패턴을 제시하였다.
-
생성자 주입 : 필요한 의존성을 모두 포함하는 클래스의 생성자를 만들고 그 생성자를 통해 의존성을 주입한다.
-
세터(Setter)를 통한 주입 : 의존성을 입력받는 세터(Setter) 메소드를 만들고 이를 통해 의존성을 주입한다.
-
인터페이스(Interface)를 통한 주입 : 의존성을 주입하는 함수를 포함한 인터페이스를 작성하고 이 인터페이스를 구현하도록 함으로써 실행시에 이를 통하여 의존성을 주입한다.
즉,
의존관계 는 둘 이상의 객체가 서로 협력하는 것이다.
ex) 두 객체 중 A는 B에 의존 => A는 B를 사용 => B의 변화가 A에 영향을 미침
우리가 지금까지 만든 프로젝트 코드 중 Controller 는 Repository에 의존한다.
우리는 Repository를 생성한 후 Controller 에 Repository를 연결하였다.
이 과정을 다른곳에서 한다면 어떨까?
이게 바로 Spring IoC Container 가 하는 일이다.
Spring IoC Container 는
@Component
@Autowired
를 지원한다.
<추가공부>
Spring Framework, Bean
출처: https://exponential-e.tistory.com/39?category=862311
Bean
Spring IoC container가 관리하는 객체, container 내부에 만들어져 있는 객체를 의미
(IoC container == Application Context, IoC 형태의 코드들을 동작하도록 도와줌)
Bean은 Spring에서 핵심이 되는 개념 중 하나입니다.
설명만 보면 container라는 곳에 bean을 생성해 두고 쓰는 것을 알 수 있습니다.
생성해두고 쓴다는 것은 thread pool, connection pool, String pool 등 다양한 곳에서 유사한 개념을 가진 기능들을 볼 수 있습니다.
낯설게만 느껴지신다면 위의 것들과 비슷한 역할을 한다고 생각하고 접근하시면 좋을 것 같습니다. :)
그렇다면 왜 미리 생성해두고 쓰며, IoC container는 무엇일가요?
우선 Bean에 대해 자세히 알아봅시다.
Bean은 간단하게 java의 객체라고 생각하시면 됩니다.
객체라고 다 Bean이냐?라고 하면 그것은 잘못된 이야기입니다.
Bean은 component annotation이 설정되어 IoC Container에 등록된 객체를 의미합니다.
(component annotation: @Service, @Controller, @Repository, @Configuration...)
Component scan을 통해 해당 객체들은 Bean으로 등록되고, 반드시 SpringBootApplication 하위 패키지나 클래스에 존재해야 합니다.
-> 그렇지 않은 경우는 빈을 찾지 못하는 예외가 발생합니다. (빈으로 등록이 안됨)
Bean으로 등록하는 이유는 어떤 장점이 있기 때문이겠죠. 그 장점에 대해서도 간략하게 소개합니다.
1. Spring IoC container -> Bean으로 등록한 객체를 singleton scope로 관리.
Bean scope
- Singleton, Prototype
- Singleton (IoC 기본 설정): Application 전반에 걸쳐 해당 bean의 instance가 하나만 존재
- 하나의 같은 객체(container에 이미 생성된)만 사용하여 효율적인 개발이 가능.
- 런타임시 성능 최적화에도 유리
- Prototype: 매번 새로운 instance를 만들어 사용
(scope에 대한 자세한 내용은 추후 관련 포스팅으로 보완하겠습니다.)
2. 의존성 주입(DI) 가능, 결합도 ↓
객체지향 코드에서 의존성 주입은 상당히 매력적인 부분이라 생각합니다.
일반적으로 의존성에 대한 제어권은 해당 의존성을 사용하는 class에서 갖고 있는데요.
IoC(Inversion of Control) 즉, 제어를 역전시켜 의존성을 밖에서 선언하여 Bean으로 주입 받는 다는 것이 DI, IoC의 이야기입니다.
class에서 자신이 사용할 기능은 직접 만들어 쓴다는 것이 일반적인 의존성입니다.
코드를 구성한다면 아래와 같이 가능합니다.
OwnerController.java
class OwnerController{
private OwnerRepository repository = new OwnerRepository();
// Repository에 정의될 기능 (Query를 통한 데이터 조작 등.)
}
하지만 Spring 개발을 Bean을 통한 의존성을 주입한다면,
Repository(Service)에서 선언한 기능들을 가져와 Controller에서 동작을 구현하고 view로 넘겨줍니다.
OwnerController.java
class OwnerController{
@Autowired
private OwnerRepository repository;
// repository 사용
}
(@Autowired를 통해 주입 받는 방법 말고 다른 방법도 존재합니다.)
Repository는 따로 Interface로 구현되어있다고 가정했습니다.
이렇게 사용처에서 직접 구현하는 것이 아닌 외부에서 생성해두고 가져다 쓰는 것을 의존성 주입이라고 합니다.
이러한 코드를 작성하게 된다면 결합도는 낮아지고, 그만큼 프로그램 수행과 구체적 구현의 분리를 가능하게 합니다.
즉, 코드의 수정도 간편해지고, 프로그램의 모듈성 또한 높아지겠죠.
3. Life cycle interface 지원
Spring IoC Container에 포함된 Bean만 적용 가능.
-> 이를 통한 부가적인 기능 생성 가능.
[정리]
Bean이란,
IoC container(= Application Context)에서 관리하는 객체를 지칭합니다.
Component Annotation 설정이 되어있는 객체는 Component scan을 통해 IoC container 내부의 bean으로 등록됩니다.
Component scan은 SpringBootApplication 하위 패키지 및 클래스에만 적용되며, 그 외의 객체는 '@Component'를 지정해도 등록이 불가능합니다.
Bean으로 등록하면, 아래와 같은 장점들이 존재했습니다.
- Bean's scope = singleton / DI / Life cycle Interface
패스트캠퍼스 강의: https://bit.ly/3ilMbIO
Java 웹 개발 마스터 올인원 패키지 Online. | 패스트캠퍼스
자바 기초문법부터 프로젝트 실습까지 Java 문법부터 스프링/스프링부트, 트렌디한 기술인 JPA까지 모두 배우는 온라인 강의입니다.
www.fastcampus.co.kr
'언어공부 > JAVA&SPRING' 카테고리의 다른 글
[패스트캠퍼스 수강 후기] 자바 인강 100% 환급 챌린지 25회차 미션 (0) | 2020.09.03 |
---|---|
[패스트캠퍼스 수강 후기] 자바 인강 100% 환급 챌린지 24회차 미션 (0) | 2020.09.02 |
[패스트캠퍼스 수강 후기] 자바 인강 100% 환급 챌린지 22회차 미션 (0) | 2020.08.31 |
[패스트캠퍼스 수강 후기] 자바 인강 100% 환급 챌린지 21회차 미션 (0) | 2020.08.30 |
[패스트캠퍼스 수강 후기] 자바 인강 100% 환급 챌린지 20회차 미션 (0) | 2020.08.29 |
댓글