스프링 입문 강의 내용 정리

인프런 ‘스프링 입문’ 강의 정리

프로젝트 생성

  • 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로 바꾸면 실행 속도가 더 빠름

라이브러리

  • 스프링 프로젝트를 생성할 때 추가한 라이브러리는 적지만 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>
  • 동작 과정
    1. 브라우저에서 localhost:8080/hello 호출
    2. 내장된 톰캣 서버를 거쳐 스프링 컨테이너에서 @GetMapping(“hello”)를 찾아 HelloController의 hello 메서드로 오게 됨
    3. viewResolver가 hello 메서드의 반환값인 hello의 이름과 같은 html 파일을 resources/templates 폴더에서 찾아 렌더링
    4. 스프링 부트 템플릿 엔진의 기본 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 파일 반환
  • 동작
    1. 브라우저가 요청
    2. 내장된 톰켓 서버가 요청을 받고 스프링 컨테이너에서 관련 컨트롤러를 찾음
    3. 컨트롤러를 찾지 못하면 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>
  • 동작
    1. 브라우저에서 localhost:8080/hello-mvc 요청
    2. 내장 톰캣 서버를 거쳐 스프링 컨테이너에서 HelloController로 가게 됨
    3. 파라미터로 받은 name 값을 모델에서 name이라는 키의 값에 추가
    4. 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 형식으로 데이터 전달
  • 동작
    1. 브라우저에서 요청
    2. 내장된 톰켓 서버에서 스프링 컨테이너로 전달
    3. 해당 controller를 찾음
    4. @ResponseBody가 있을 때 반환값이 문자열이면 그대로 반환하고 객체일 경우 JSON 형식으로 반환
    5. HttpMessageConverter 중에서 문자열일 때는 StringHttpMessageConverter, 객체일 때는 MappingJackson2HttpMessageConverter를 거침
    6. 응답값 반환
  • 클라이언트의 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
    
  • 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가 필요한 기능은 공통 관심 사항에 속함
    • 핵심 비즈니스 로직과 공통 관심 사항의 로직이 섞이면 유지보수가 어려움
    • 별도의 공통 로직으로 만들기도 어려움
    • 변경할 때 모든 로직을 찾아 변경해야 함
  • 공통 관심 사항과 핵심 관심 사항을 분리
  • 동작
    1. AOP가 적용된 인스턴스가 호출되면 스프링에서 프록시 객체 생성
    2. joinPoint.proceed() 를 통해 실제 인스턴스 로직 실행