회원 웹 기능 - 홈 화면 추가

홈 컨트롤러 추가

  • controller>HomeController.java 생성

회원 관리용 홈

  • resources>templates>home.html 생성

회원 웹 기능 - 등록

회원 등록 폼 개발

회원 등록 폼 컨트롤러

  • controller>MemberController.java 에 코드 추가 - @GetMapping(value = "/members/new")
 

회원 등록 폼 HTML

  • resources>templates>members>createMemberForm.html 생성

 

회원 등록 기능 개발

회원 등록 컨트롤러 - 웹 등록 화면에서 데이터를 전달 받을 폼 객체

  • controller>MemberForm.java 생성

회원 컨트롤러에서 회원을 실제 등록하는 기능

  • controller>MemberController.java 에 코드 추가 - @PostMapping(value = "/members/new")

 

✏️ @GetMapping : 서버의 리소스를 조회할 때

✏️ @PostMapping : 서버에 리소스를 등록(저장)할 때


회원 웹 기능 - 조회

회원 컨트롤러에서 조회 기능

  • controller>MemberController.java 에 코드 추가 - @GetMapping(value = "/members")

회원 리스트 HTML

  • resources>templates>members>memberList.html 생성

 

스프링 빈과 의존관계

스프링 빈(Bean)

스프링 컨테이너에 의해 관리되는 자바 객체(재사용 가능한 소프트웨어 컴포넌트)

 

의존성 주입: DI(Dependency Injection)

객체 의존관계를 외부에서 넣어주는 것

 

  • DI 방식
    1. 필드 주입
    2. setter 주입
    3. 생성자 주입
// 1. 필드 주입
@Autowired private MemberService memberService;


// 2. setter 주입
private MemberService memberService;

@Autowired
public void setMemberService(MemberService memberService) {
    this.memberService = memberService;
}


// 3. 생성자 주입
private final MemberService memberService;

@Autowired
public MemberController(MemberService memberService) {
    this.memberService = memberService;
}

 

‼️ 의존관계가 실행 중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입 권장


스프링 빈을 등록하는 2가지 방법

 

1. 컴포넌트 스캔과 자동 의존관계 설정

회원 컨트롤러를 생성하고 의존관계 추가하기

  • Controller - controller>MemberController.java

✏️ @Autowired스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌 / 생성자가 1개만 있으면 생략 가능

‼️ @Autowired를 통한 DI는 helloController, memberService 등과 같이 스프링이 관리하는 객체에서만 동작, 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않음

package hello.hello_spring.controller;

import hello.hello_spring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}

 

➡️ 이렇게 객체 의존관계를 외부에서 넣어주는 것을 DI (Dependency Injection)

이전 테스트에서는 개발자가 직접 주입했고, 여기서는 @Autowired를 통해 스프링이 주입해줌

 

 

컴포넌트 스캔 원리

  • @Component 애노테이션이 있으면 스프링 빈으로 자동 등록

 

  • @Component를 포함하는 애노테이션
    • @Controller
    • @Service
    • @Repository

 

회원 서비스 스프링 빈 등록

@Service
public class MemberService {

    private final MemberRepository memberRepository;

    // 외부에서 memberRepository를 넣어주도록 변경
    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

 

회원 리포지토리 스프링 빈 등록

@Repository
public class MemoryMemberRepository implements MemberRepository{

 

 

스프링 빈 등록 이미지

  • memberService 와 memberRepository 가 스프링 컨테이너에 스프링 빈으로 등록
  • 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때기본으로 싱글톤으로 등록(유일하게 하나만 등록해서 공유)
    ➡️ 같은 스프링 빈이면 모두 같은 인스턴스
  • 설정으로 싱글톤이 아니게 설정할 수 있지만특별한 경우를 제외하면 대부분 싱글톤을 사용

2. 자바 코드로 직접 스프링 빈 등록

  • 회원 서비스와 회원 리포지토리의 @Service, @Repository, @Autowired 애노테이션 제거하고 진행
  • java>hello.hello_spring>SpringConfig.java 생성
package hello.hello_spring;

import hello.hello_spring.repository.MemoryMemberRepository;
import hello.hello_spring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

 

✔️ 실무에서 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용

✔️ 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록

회원관리 예제 - 백엔드 개발

비즈니스 요구사항 정리

  • 데이터: 회원ID, 이름
  • 기능: 회원 등록, 조회
  • 아직 데이터 저장소 선정 x -> 가상의 시나리오

 

일반적인 웹 애플리케이션의 계층 구조

  • 컨트롤러: 웹 MVC의 컨트롤러 역할
  • 서비스: 핵심 비즈니스 로직 구현
  • 리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
  • 도메인: 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장되고 관리됨

 

클래스 의존관계

  • 인터페이스로 구현 클래스를 변경할 수 있도록 설계
  • 개발 진행을 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용

 


회원 도메인, 리포지토리, 서비스 개발

  • Domain - domain>Member.java
  • Repository interface - repository>MemberRepository.java
  • Repository 메모리 구현체 - repository>MemoryMemberRepository.java
  • Service - service>MemberService.java

✔️ Service는 비즈니스에 의존적으로 설계(join, login 등)

 

✏️ Optional - 값이 null 일 때, null 그대로 반환하는 대신 optional로 감싸서 반환

✏️ implements 후 option + enter - @Override 가능

 


회원 리포지토리, 서비스 테스트 케이스 작성

 

개발한 기능을 실행해서 테스트 할 때, 1) main 메서드를 통해서 실행, 2) 웹 애플리케이션의 컨트롤러를 통해 실행

 

⚠️ 문제점 ⚠️

  1. 준비하고 실행하는데 오래 걸림
  2. 반복 실행하기 어려움
  3. 여러 테스트를 한번에 실행하기 어려움

➡️ JUnit이라는 자바의 프레임워크로 테스트를 실행해서 문제 해결

 

✔️ 테스트 클래스 생성 방법

  1. src>test>java 하위 폴더에 직접 테스트 클래스 생성
  2. 클래스에서 command + shift + t -> 테스트 클래스 생성

 

✔️ given / when / then 으로 테스트 케이스 작성하기

  • given: 주어진 상황
  • when: 실행
  • then: 결과

 

✏️ @AfterEach: 각 테스트가 종료될 때마다 메모리 DB에 저장된 데이터를 삭제

@AfterEach
public void afterEach() {
    repository.clearStore();
}

‼️ 한번에 여러 테스트를 실행하면 메모리 DB에 직전 테스트 결과가 남을 수 있어, 이전 테스트로 인해 다음 테스트가 실패할 가능성 존재

‼️ 모든 테스트는 각각 독립적으로 실행되어야 함

‼️ 테스트 순서에 의존관계가 있는 것은 좋은 테스트가 아님

 

✏️ @BeforeEach: 각 테스트 실행 전에 호출되어 테스트가 서로 영향이 없도록 항상 새로운 객체를 생성하고, 의존관계도 새로 맺어줌

@BeforeEach
public void beforeEach() {
    memberRepository = new MemoryMemberRepository();
    memberService = new MemberService(memberRepository);
}

 

정적 컨텐츠

스프링 부트 정적 컨텐츠 기능을 자동으로 제공 - static

https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-static-content

 

  • resources/static/hello-static.html 생성
<!DOCTYPE HTML>
<html>
<head>
    <title>static content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
정적 컨텐츠 입니다.
</body>
</html>

 

  • 실행 - http://localhost:8080/hello-static.html 

 

정적 컨텐츠 이미지

 

- 웹 브라우저에서 주소를 입력하면 내장 톰캣 서버에서 요청을 받고, hello-static.html의 요청이 왔다고 스프링 부트한테 넘김

- 스프링 부트는 먼저 컨트롤러에서 hello-static 찾음

- 없으면 resources/static 에서 hello-static.html 찾아서 웹 브라우저에 반환


MVC와 템플릿 엔진

  • MVC: Model, View, Controller

 

  • Controller - helloController.java 에 추가
@Controller
 public class HelloController {
     @GetMapping("hello-mvc")
     public String helloMvc(@RequestParam("name") String name, Model model) {
         model.addAttribute("name", name);
         return "hello-template";
     }
}

 

  • View - resources/templates/hello-template.html 생성
<html xmlns:th="http://www.thymeleaf.org">
<body>
<p th:text="'hello ' + ${name}">hello! empty</p>
</body>
</html>

 

  • 실행 - http://localhost:8080/hello-mvc?name=spring 

MVC, 템플릿 엔진 이미지

- 웹 브라우저에서 주소를 입력하면 내장 톰캣 서버에서 요청을 받고, hello-mvc의 요청이 왔다고 스프링 부트한테 넘김

- 컨트롤러에 hello-mvc 가 매핑이 되어있으므로 hello-mvc 메서드 호출

- hello-mvc?name=spring 이므로 값은 spring - model(name:spring)

- retrun: hello-template이므로 viewResolver가 temlplates/hello-template.html을 템플릿 엔진에 넘김

- 템플릿 엔진이 랜더링해서 변환한 HTML 반환


API

  • @ResponseBody 문자 반환
@Controller
 public class HelloController {
     @GetMapping("hello-string")
     @ResponseBody
     public String helloString(@RequestParam("name") String name) {
         return "hello " + name;
     }
 }

- @ResponseBody를 사용하면 viewResolver를 사용하지 않음

 

  • 실행 - http://localhost:8080/hello-string?name=spring

- HTTP의 BODY에 문자 내용을 직접 반환

 

  • @ResponseBody 객체 반환
@Controller
public class HelloController {
	
    @GetMapping("hello-api")
    @ReponseBody
    public Hello helloApi(@RequestParam("name") String name) {
    	Hello hello = new Hello();
        hello.setName(name);
        return hello;
    }
    
    static class Hello {
    	private String name;
        
        pubilc String getName() {
        	return name;
        }
        
        public String setName(String name) {
        	this.name = name;
        }
    }
}

- Getter, Setter 필요

- @ResponseBody를 사용

 

  • 실행 - http://localhost:8080/hello-api?name=spring

- 객체를 반환하면 객체가 JSON으로 변환됨

 

 

@ResponseBody 사용 원리

- HTTP의 BODY에 문자 내용 직접 반환

- viewResolver 대신에 HttpMessageConverter 가 동작

- 기본 문자 처리(위의 문자 반환의 경우): StringHttpMessageConverter

- 기본 객체 처리(위의 객체 반환의 경우): MappingJackson2HttpMessageConverter

프로젝트 생성하기

✔️IDE: IntelliJ

 

스프링 부트 스타터 사이트로 스프링 프로젝트 생성

https://start.spring.io

  • 프로젝트 선택
    • Project: Gradle - Groovy
    • Spring Boot: 3.3.3
    • Language: Java
  • Metadata
    • Group: hello
    • Artifact: hello-spring
  • Dependencies
    • Spring Web
    • Thymeleaf(html을 만들어주는 템플릿 엔진)
더보기

Group - 기업명, 기업 도메인명 / Artifact - 빌드되면 나오는 결과물 = 프로젝트명

 

  • GENERATE > 압축 파일 풀기 > IntelliJ에서 파일 Open

 

프로젝트 실행 및 빌드

동작 확인

  • 기본 메인 클래스 실행
    - @SpringBootApplication: 스프링부트 애노테이션으로 톰캣이라는 웹서버를 내장하고 있어서 스트링부트 실행
    - 스프링부트 스타터 사이트에서 프로젝트를 생성하면 @SpringBootApplication 이 작성된 상태로 프로젝트가 생성됨 

실행 로그에서 Tomcat 실행 확인 가능

 

  • http://localhost:8080로 이동하여 에러페이지로 간단하게 동작 확인


✚ IntelliJ로 자바 직접 실행

  • Gradle을 통해 실행하는 것이 기본 설정인데 이렇게 하면 실행속도가 느리므로 변경해주기
  • preferences > gradle > Build and run using, Run tests using -> IntelliJ IDEA 로 설정

 


✚ 라이브러리 살펴보기

  • 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 

테스트 라이브러리

  • spring-boot-starter-test
    • junit: 테스트 프레임워크
    • mockito: 목 라이브러리
    • assertj: 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리
    • spring-test: 스프링 통합 테스트 지원

View 환경설정

Welcom Page 만들기

  • src > main > resources > static > index.html 생성
<!DOCTYPE HTML>
<html>
<head>
    <title>Hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
Hello
<a href="/hello">hello</a>
</body>
</html>

✚ 스프링 부트가 제공하는 Welcome Page 기능

 

  •  java > hello.hello_spring > controller 패키지 생성 > helloController.java 생성
package hello.hello_spring.controller;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@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>

 

- data : model 에서의 key 값 (즉, hello!!)

 

 

동작 환경 그림

return:hello - templates 밑의 hello를 찾아서 랜더링

➡️ 컨트롤러에서 리턴 값으로 문자를 반환하면 뷰 리졸버(veiwResolver)가 화면을 찾아서 처리

- 스프링 부트 템플릿 엔진 기본 veiwName 매핑

- resource:templates/ + {ViewName} + .html


프로젝트 빌드하고 실행하기

  • 콘솔로 이동
./gradlew build

cd build/libs

java -jar hello-spring-0.0.1-SNAPSHOT.jar

 

  • 실행 확인하기

프로젝트 배포하기

  1. AWS RDS의 MySQL을 연결
    • 그 동안은 H2를 사용했기 때문에, 서비스를 내렸다 올리면 모든 데이터가 초기화되었음
      ➡️ 데이터를 클라우드에 저장하여 서비스를 껐다 켜도 데이터가 유지되도록 함
  2. AWS EC2에 배포
    • 누구나 서비스에 접속할 수 있게 하려면 원격으로 항상 작동하는 컴퓨터 필요
  3. 도메인 붙이고 카카오톡으로 공유하기
    • OG 태그: 카카오톡으로 링크를 공유할 때, 자동으로 이미지, 제목, 내용을 가져오게 함

 

 

AWS RDS 구매하고 MySQL 세팅하기

 

RDS 구매하기

  1. AWS에 접속하여 RDS 검색
  2. 데이터베이스 생성 > 표준 생성 > MySQL > 프리 티어 선택
  3. 설정 입력 - DB 인스턴스 식별자: 원하는 이름 작성 / 마스터 사용자 이름, 암호: DB 접속용으로 만들고 싶은 계정의 아이디, 비밀번호
  4. DB 인스턴스 크기, 스토리지 설정 그대로
  5. 연결 > 추가 연결 구성 탭 클릭 후 다음과 같이 설정
    - 퍼블릭 액세스 기능: "예" (이 설저이 되어 있어야 MySQL 연결 가능)
    - VPC 보안 그룹: "새로 생성"
    - 새 VPC 보안 그룹 이름: sprinboot-db-security
    - 가용 영역: 옵션 중 아무거나
  6. 추가 구성 > 초기 데이터베이스 이름에 "myselectshop" 입력 후 데이터베이스 생성 클릭

 

RDS 포트 열어주기

  1. 생성한 데이터베이스 클릭 > 연결 & 보안 > 보안 > VPC 보안 그룹의 springboot-db-security > 보안 그룹 ID 클릭
  2. 인바인드 규칙 편집 > 소스 > 위치 무관 -> 0.0.0.0, :/0 생성 후 "규칙 저장" 클릭

 

IntelliJ에서 확인

  1. RDS 앤드포인트 확인 후 드래그하여 복사
  2. <나만의 셀렉샵> 프로젝트 열기 > 우측 Database 탭 > Data Source > MySQL
    - Name: 생성한 데이터베이스 이름
    - Host: 앤드포인트
    - User: Username
    - Password: 비밀번호
    - Database: myselectshop
  3. Test Connection > 성공적으로 연결되면 OK

 

스프링부트를 MySQL과 연결하기

- 스프링부트 설정의 대부분은 application.properties 에서 관리

spring.datasource.url=jdbc:mysql://나의앤드포인트:3306/myselectshop
spring.datasource.username=나의USERNAME
spring.datasource.password=나의패스워드
spring.jpa.hibernate.ddl-auto=update

 

 

OG 태그

<meta property="og:title" content="00만의 셀렉샵">
<meta property="og:description" content="관심상품을 선택하고, 최저가 알림을 확인해보세요!">
<meta property="og:image" content="images/og_selectshop.png">

 

 

 

AWS EC2 준비하고 배포하기

 

SSH(Secure Shell Protocol)

- 다른 컴퓨터에 접속할 때 사용하는 프로그램으로 다른 프로그램보다 보안이 상대적으로 뛰어남

- 접속할 컴퓨터의 22번 포트가 열려있어야 접속 가능

- AWS EC2의 경우 이미 22번 포트가 열려있음

 

 

리눅스 명령어

ls: 내 위치의 모든 파일을 보여줌

pwd: 내 위치(폴더의 경로)를 알려줌

mkdir: 내 위치 아래에 폴더 하나 생성

cd [이동할 곳]: [이동랗 곳] 폴더로 이동

cd ..: 상위 폴더로 이동

cp -r [복사할 것] [붙여넣기 할 것]: 복사 붙여넣기

rm -rf: 삭제

sudo [실행할 명령어]: 관리자 권한으로 명령어 실행

sudo su: 관리가 권한으로 들어감 (나올 땐 exit)

 

 

AWS EC2 서버 구매 

https://ap-northeast-2.console.aws.amazon.com/ec2/home?region=ap-northeast-2#Home:

 

AWS EC2 접속

  • Mac OS의 경우 ssh가 있어서 명령어로 AWS EC2에 바로 접근 가능
    # Keypair 접근 권한 바꿔주기 
    sudo chmod 400 받은키페어경로
    
    # SSH로 접속하기 
    ssh -i 받은키페어 ubuntu@AWS에적힌내아이피

 

배포 파일 빌드

  1. 프로젝트 실행 후 우측 탭 중 "Gradle" 선택
  2. Tasks > build > build 더블 클릭
  3. 좌측 build 폴더 > libs 아래 .jar 확장자로 끝나는 파일 생기면 빌드 성공

 

OpenJDK 설치

  • ubuntu 안에 설치
sudo apt-get update
sudo apt-get install openjdk-17-jdk
java -version

 

 

 

Filezilla 이용하여 배포 파일 업로드

  1. Filezilla 실행 후 왼쪽 상단 사이트 관리자 클릭
  2. 새 사이트 생성 후 연결
    - 프로토콜: SFTP - SSH File Transfer Protocol
    - 호스트: EC2 퍼블릭 IPv4 주소
    - 로그온 유형: 키 파일
    - 사용자: ubuntu
    - 키 파일: EC2 인스턴스 생성 시 생성한 .pem 파일
  3. 마우스로 드래그하여 .jar 파일 업로드

 

스프링부트 작동시키기

cd jar파일상위폴더

java -jar jar파일명.jar

 

더보기

⚠️ 오류 발생 ⚠️

 

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

 

‼️ 해결 방법 ‼️

lsof -i tcp:8080 으로 8080 포트 사용 중인 프로세스 확인

sudo kill -9 [PID] 로 해당 프로세스 종료

 

 


AWS에서 80, 8080 포트 열어주기

  • AWS EC2 에서도 자체적으로 포트를 열고 닫을 수 있게 관리하고 있음
    ➡️ AWS EC2 Security Group 에서 인바운드 요청 포트 열어줘야 함 
  • EC2 관리 콘솔 > 보안그룹 > 인바운드 규칙 편집 > 8080포트 추가
    - 80포트: HTTP 접속을 위한 기본 포트
    - 8080포트: 스프링부트 기본 포트

 

포트포워딩(Port Forwarding)

  • http 요청에서는 80포트가 기본이기 때문에 굳이 :80을 붙이지 않아도 자동으로 연결
  • 지금은 8080 포트에서 웹 서비스가 실행되고 있기 때문에, 매번 :8080 이라고 뒤에 포트 번호 입력해야함
  • 포트 번호를 입력하지 않아도 자동으로 접속되기 위해, 80포트로 오는 요청을 8080 포트로 전달하게 함 ➡️ 포트포워딩

 

포트 번호 없애기

  1. 돌아가고 있던 서비스 종료하고 터미널에 포트포워딩 룰 입력
  2. 다시 서비스 시작
    sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
  3. 포트 번호 입력 없이 웹 브라우저에서 접속
    java -jar JAR파일명.jar

 

 

SSH 접속을 끊어도 서버가 계속 돌게 하기

  • 현재 상황: SSH 접속을 끊으면 프로세스가 종료되면서 서버가 돌아가지 않음
  • 원격 접속을 종료하더라도 서버가 계속 돌아가게 하기
    # 아래의 명령어로 실행하면 된다
    nohup java -jar JAR파일명.jar &
  • 서버 종료하기(강제 종료)
    # 아래 명령어로 미리 pid 값(프로세스 번호)을 본다
    ps -ef | grep java
    
    # 아래 명령어로 특정 프로세스를 죽인다
    kill -9 [pid값]
  • 다시 켜기
    # 아래의 명령어로 실행하면 된다
    nohup java -jar JAR파일명.jar &​
  • SSH 접속 종료 후 다시 접속해보기

 

 

도메인 붙이고 카카오톡에 공유

  • 도메인 구매: 네임서버릉 운영해주는 업체에, IP와 도메인 매칭 유지비를 내는 것
  1. '가비아'에서 도매인 구매 후 관리툴 > 도메인 연결 > DNS 설정 클릭
  2. 호스트 이름: @, IP 주소: IP 주소 입력 후 10분 정도 대기
  3. 내 도메인으로 접근하여 접속하고 카카오톡으로 공유하기

 

+ Recent posts