프로젝트를 진행하면 다양한 라이브러리를 이용하게 되는데, 이 라이브러리를 가져오고 관리해주는 툴이다. 최근에는 Gradle을 주로 이용하는 추세이다.
뒤에 괄호가 있는 버전은 정식 버전이 아니다.
build.gradle 파일을 통해 프로젝트를 생성한다.
plugins {
id 'org.springframework.boot' version '2.3.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
} //라이브러리를 어디서 가져오는지
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
자동으로 테스트 관련 라이브러리 ( Junit5 )를 가져온다.
dependencies 에 있는 것들이 해당 프로젝트가 쓰는 라이브러리들이며, gradle이 이 라이브러리를 사용하는데 필요한 하위 라이브러리들을 모두 자동으로 가져와서 관리한다.
spring-boot-starter-web
- spring-boot-starter-tomcat: 톰캣 (웹서버)
- spring-webmvc: 스프링 웹 MVC
spring-boot-starter-thymeleaf: 타임리프 템플릿 엔진(View)
spring-boot-starter(공통): 스프링 부트 + 스프링 코어 + 로깅
spring-boot
- spring-core
spring-boot-starter-logging
- logback, slf4j
스프링 부트에는 톰켓 서버가 내장돼있어서 자체적으로 서버를 띄운다.
웹 브라우저에서 서버로 요청이 들어오면 해당 url에 해당하는 컨트롤러를 찾는데, 이 컨트롤러가 존재하지 않을 시에 그 이름을 가진 정적 컨텐츠를 찾아서 응답한다.
요청 url을 매핑하는 컨트롤러에 있는 로직을 수행한다. 컨트롤러에서는 model에 데이터를 담아서 view에 반환하면 템플릿엔진이 view 를 수정해서 응답한다. 컨트롤러에서 String으로 반환하면 그 이름에 맞는 view를 자동으로 찾아서 수정한다.
view를 거치지 않고 데이터를 직접 반환하는 방식이다. 서버와서버간의 요청 응답에도 사용된다.
컨트롤러에 @ResponseBody어노테이션을 붙이면 API응답 방식으로 응답하게 되고 String을 반환하면 해당 문자열을 그대로 http body에 전달, 객체를 반환하면 그 객체를 클라이언트 요청 형식( HTTP Accept 헤더 )에 따라 변환해서 전달한다.( 기본은 Jason형식 )
예제의 경우 더 복잡한 예제를 이미 spring-mvc-1편에서 다뤘기 때문에 내용을 전부 다 정리하지는 않겠다.
이 예제의 경우 repository가 아직 선정이 안됐다는 가정하에 진행했기 때문에 repository를 인터페이스로 먼저 설계하고 그 구현체를 설계했다.
이렇게 설계했을 때 장점은 인터페이스에 맞게 구현돼있기 때문에 나중에 교체시에 이 형식에 맞게 설계만 하면 다른 코드를 많이 수정하지 않고 repository를 교체할 수 있어 변경에 용이하다.
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
이렇게 인터페이스의 각 요소는
반환타입 함수명(매개변수) 의 구조로 정의한다.
public class MemoryMemberRepository implements MemberRepository {
이 인터페이스를 상속하는 구현체는 implements 를 통해 상속한다.
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach() {
repository.clearStore();
}
@Test
public void save() {
//given
Member member = new Member();
member.setName("spring");
//when
repository.save(member);
//then
Member result = repository.findById(member.getId()).get();
assertThat(result).isEqualTo(member);
}
}
@AfterEach
이 애노테이션이 붙은 메소드는 각각의 테스트 실행이 종료될 때마다 실행된다.
( 반대는 @BeforeEach )
테스트 종료시마다 repository를 비워줌으로써 오류를 방지한다.
스프링 실행시에 @Component 애노테이션이 있으면 자동으로 bean으로 등록된다.
@Service, @Repository, @Controller 는 @Component 애노테이션을 내포한다.
생성자 주입 예시
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
이렇게 빈으로 등록된 객체들은 @Autowired를 통해 의존관계 자동 주입을 할 수 있고 그 대상이 되기도 한다.
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
@Configuration, @Bean 애노테이션을 통해 직접 의존관계를 주입하는 코드이다.
@Configuration 애노테이션이 붙어 있으면 스프링 실행시에 스프링이 해당 클래스를 확인하고 각 요소들을 Bean으로 등록한다.
* 참고: 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다.
그리고 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.
AOP: Aspect Oriented Programming
공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern) 분리
ex) 모든 메소드의 호출 시간 측정
시간 측정은 공통 관심 사항이지만 핵심 관심 사항은 아니다.