스프링 입문 강의 내용 정리
31 Jan 2022인프런 ‘스프링 입문’ 강의 정리
프로젝트 생성
- spring initializr에서 간단하게 스프링 프로젝트 생성 가능
- 프로젝트 구조
- gradle: gradle에서 사용하는 폴더
- src: main, test 폴더가 구분되어 있음
- src/main: java, resources 폴더가 있으며 java 안에 실제 소스 코드가 있음
- src/resources: 자바 파일 외의 파일 존재
- src/test: test code
- build.gradle: spring boot에서 기본 설정 제공
- 프로젝트 실행
- main/src/java/프로젝트 폴더의 자바 파일의 main 실행
@SpringBootApplication
어노테이션에 의해 스프링 부트 앱이 내장된 톰캣 웹 서버를 띄우면서 실행됨
- tip
- Preferences - Build, Execution, Deployment - Build Tools - Gradle 에서
Build and run using
,Run tests using
을 Gradle에서IntelliJ IDEA
로 바꾸면 실행 속도가 더 빠름
- Preferences - Build, Execution, Deployment - Build Tools - Gradle 에서
라이브러리
- 스프링 프로젝트를 생성할 때 추가한 라이브러리는 적지만 External Libraries를 클릭하면 많은 라이브러리가 받아져 있는 것을 확인할 수 있음
- IntelliJ의 오른쪽 툴바에서 Gradle을 선택하고 Dependencies를 보면 라이브러리의 종속 관계 확인 가능
- Gradle이 의존관계가 있는 라이브러리를 함께 다운로드
- spring-boot-starter-web
- 내장 tomcat(WAS)
- spring-webmvc: 스프링 웹 MVC
- spring-boot-starter
- spirng-core 포함
- spirng-boot-starter-loging: logback, slf4j 포함
- spring-boot-starter-test
- junit: 테스트 프레임워크
- assertj: 테스트 코드 라이브러리
- mockito: mock 라이브러리
View 환경 설정
- welcome page
- 도메인 통해 들어올 때 첫 화면
- index.html 파일을 첫 화면으로 띄움
- thymeleaf 템플릿 엔진 사용
- 아래 코드를 작성하고 앱을 실행시켜 http://localhost:8080/hello 로 들어가면 hello.html의 화면을 보여줌
@Controller
public class HelloController {
@GetMapping("hello")
public String hello(Model model) {
model.addAttribute("data", "hello!");
return "hello";
}
}
// resources/templates/hello.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p th:text="'안녕하세요. ' + ${data}"></p>
</body>
</html>
- 동작 과정
- 브라우저에서 localhost:8080/hello 호출
- 내장된 톰캣 서버를 거쳐 스프링 컨테이너에서 @GetMapping(“hello”)를 찾아 HelloController의 hello 메서드로 오게 됨
viewResolver
가 hello 메서드의 반환값인 hello의 이름과 같은 html 파일을 resources/templates 폴더에서 찾아 렌더링- 스프링 부트 템플릿 엔진의 기본 viewName 매핑은
resources:templates/{viewName}.html
빌드 & 실행
- 터미널에서 명령어를 실행한다. (mac OS 기준)
- build 명령어를 실행하고 나면 같은 이름의 폴더가 생성
- build/libs 폴더 안에 jar 파일이 생성
- {projectName}-0.0.1-SNAPSHOT.jar
- {projectName}-0.0.1-SNAPSHOT-plain.jar
- clean 명령어로 build 폴더 삭제
project>./gradlew build
project>cd build/libs
project/build/libs>java -jar {projectName}-0.0.1-SNAPSHOT.jar
project>./gradlew clean
- gradle 2.5 버전부터 빌드할 때 2가지 버전 jar 생성
- plain jar는 의존성을 포함하지 않고 소스코드의 클래스 파일과 리소스파일만 포함
- plain jar를 생성하지 않도록 설정하고 싶을 때는 build.gradle에 아래 설정 추가
jar {
enabled = false
}
정적 콘텐츠
- 스프링 부트는 기본적으로
static
폴더에서 정적 콘텐츠를 서빙한다- 또는
public
,resources
,META-INF/resources
등의 폴더
- 또는
- src/main/resources/static 폴더에 html 파일을 추가하고 앱을 실행
- localhost:8080/{htmlFileName}.html 을 요청하면 해당 html 파일 반환
- 동작
- 브라우저가 요청
- 내장된 톰켓 서버가 요청을 받고 스프링 컨테이너에서 관련 컨트롤러를 찾음
- 컨트롤러를 찾지 못하면 resources 내부에서 찾아서 html 반환
MVC & Template engine
- MVC: Model, View, Controller
- 관심사에 따라 나눔
@Controller
public class HelloController {
@GetMapping("hello-mvc")
public String helloMvc(@RequestParam(value = "name", required = false) String name, Model model) {
model.addAttribute("name", name);
return "hello-template";
}
}
// main/resources/templates/hello-template.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello Mvc</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p th:text="'안녕하세요. ' + ${name}">Hello </p>
</body>
</html>
- 동작
- 브라우저에서 localhost:8080/hello-mvc 요청
- 내장 톰캣 서버를 거쳐 스프링 컨테이너에서 HelloController로 가게 됨
- 파라미터로 받은 name 값을 모델에서 name이라는 키의 값에 추가
- virewResolver가 반환값인 hello-template와 같은 html을 template 폴더에서 찾아 렌더링 후 반환
API
@ResponseBody
: 응답 body에 직렬화된 응답값 반환
@Controller
public class HelloController {
@GetMapping("hello-string")
@ResponseBody
public String helloString(@RequestParam("name") String name) {
return "hello " + name;
}
}
- 앱을 실행한 후 localhost:8080/hello-string?name=guest 로 들어가면 화면에서 hello guest 확인 가능
- 개발자 도구를 통해 elements에서 <body> 영역에서 메서드의 응답값을 볼 수 있음
@Controller
public class HelloController {
@GetMapping("hello-api")
@ResponseBody
public Hello helloApi(@RequestParam("name") String name) {
Hello hello = new Hello();
hello.setName(name);
return hello;
}
static class Hello {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- JSON 형식으로 데이터 전달
- 동작
- 브라우저에서 요청
- 내장된 톰켓 서버에서 스프링 컨테이너로 전달
- 해당 controller를 찾음
- @ResponseBody가 있을 때 반환값이 문자열이면 그대로 반환하고 객체일 경우 JSON 형식으로 반환
- HttpMessageConverter 중에서 문자열일 때는 StringHttpMessageConverter, 객체일 때는 MappingJackson2HttpMessageConverter를 거침
- 응답값 반환
- 클라이언트의 HTTP Accept 헤더와 서버의 반환 타입 정보를 조합해서 HttpMessageConverter가 결정됨
Component scan & DI
- 스프링 앱이 실행되면
@Controller
,@Service
,@Repository
,@Component
등의 어노테이션이 붙은 객체의 인스턴스를 생성해서 스프링 컨테이너에 넣어둠 - 스프링 컨테이너에서 스프링 빈을 관리
- controller, service, repository 등에서 다른 객체를 가져올 때
@Autowired
어노테이션을 사용하면 스프링 컨테이너에서 빈을 가져옴- @Autowired를 통한 DI는 해당 객체도 스프링 컨테이너에 빈으로 등록되어 있어야 하므로 위의 어노테이션이 있어야 함
- 생성자가 한 개만 있으면 @Autowired 생략 가능
- Dependency Injection
- 스프링이 의존성을 주입해 줌
- 스프링이 스프링 컨테이너에 빈을 등록할 때 기본 타입은 싱글톤
- 하나만 등록해서 모두 공유해서 사용
- 같은 스프링 빈은 모두 같은 인스턴스
- 싱글톤 말고 다른 타입으로 설정 가능
- component scan, 자동 의존관계 설정
- @Controller, @Service, @Repository 어노테이션은 모두 @Component 어노테이션을 포함하고 있음
- @Component 어노테이션이 있으면 스프링 빈으로 자동으로 등록됨
@SpringBootApplication
어노테이션이 있는 클래스가 속한 패키지와 하위 패키지가 기본적인 컴포넌트 스캔의 대상- @SpringBootApplication 어노테이션은 @ComponentScan 어노테이션을 가지고 있음
자바 코드로 직접 스프링 빈 등록하기
- 스프링 빈을 설정하는 자바 클래스 생성
@Configuration
,@Bean
어노테이션 사용- @Bean 어노테이션을 가진 객체를 스프링 빈으로 등록
- 구현 클래스를 변경해야 할 때 변경이 편리함
@Configuration
public class SampleConfig {
@Bean
public SampleService sampleService() {
return new SampleService(sampleRepository());
}
@Bean
public SampleRepository sampleRepository() {
return new SampleRepository();
}
}
순수 JDBC
- 환경 설정
- build.gradle 에 라이브러리 추가
dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' runtimeOnly 'com.h2database:h2' }
- 스프링부트 DB 연결 설정 추가 (h2로 설정)
spring.datasource.url=jdbc:h2:tcp://localhost/~/test spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa
- build.gradle 에 라이브러리 추가
-
JDBC repository 구현
- SQL 작성
- java.sql 패키지를 이용해 DB와 연결하고 작업이 완료된 후, 자원을 반환하는 과정까지 긴 코드를 작성해야 함
- 예외가 많아서 try, catch 구분을 잘 작성해야 함
- 현재는 사용하지 않는 방식
통합 테스트
- 테스트 클래스에
@SpringBootTest
어노테이션 추가하면 스프링 컨테이너와 함께 테스트 실행 @Transactional
어노테이션을 추가하면 테스트 전에 트랜잭션을 시작하고 완료 후에는 롤백
JdbcTemplate
- 순수 JDBC와 환경 설정 동일
- 더 간결하지만 SQL은 직접 작성해야 함
- org.springframework.jdbc.core 패키지 이용
JPA
- 기존의 반복 코드와 기본적 SQL도 JPA가 실행해줌
- SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임 전환 가능
- 개발 생산성 향상
- 환경 설정
spring-boot-starter-data-jpa
는 jdbc 관련 라이브러리도 포함show-sql
: JPA가 생성하는 쿼리 출력ddl-auto
: 테이블을 자동으로 생성하는 기능
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
# resources/application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.jpa.show-sql=true
spring.jp.hibernate.ddl-auto=none
@Entity
: 도메인 객체에 어노테이션 추가해야 함
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
- EntityManager 에 의해 동작
- 항상 트랜잭션이 있어야 하므로 service 계층에
@Transactional
어노테이션 추가해야 함
Spring Data JPA
- 인터페이스만으로도 개발 가능
- 기본 CRUD, pagination 제공
- 단순 반복 코드가 줄어들어 핵심 비즈니스 로직에 집중 가능
- JpaRepository가 구현체를 만들고 자동으로 스프링 빈으로 등록
public interface SpringDataJPARepository extends JpaRepository<User, Long> {
}
AOP(Aspect Oriented Programming)
- AOP가 필요한 상황
- 모든 메소드의 호출 시간을 측정할 때
- 회원 가입 시간, 회원 조회 시간을 측정할 때
- 공통 관심 사항(cross-cutting cocnern) vs 핵심 관심 사항(core concern)
- AOP가 필요한 기능은 공통 관심 사항에 속함
- 핵심 비즈니스 로직과 공통 관심 사항의 로직이 섞이면 유지보수가 어려움
- 별도의 공통 로직으로 만들기도 어려움
- 변경할 때 모든 로직을 찾아 변경해야 함
- 공통 관심 사항과 핵심 관심 사항을 분리
- 동작
- AOP가 적용된 인스턴스가 호출되면 스프링에서 프록시 객체 생성
- joinPoint.proceed() 를 통해 실제 인스턴스 로직 실행