728x90

홈화면 추가

웹브라우저 url에 http://localhost:8080을 입력해서 접속하면 위의 HomeController의 home메소드가 실행된다.

Return "home"은 tempaltes의 home.html을 반환한다는 의미이다.

 

 

home.html

members/new 로 가게하는 회원가입 과 /members 로 이동시키는 회원 목록 이 있다.

home.html 출력화면

위 파일 목록을 보면 원래 home의 디폴트는 static/index.html 이였다.

home의 디폴트가 home.html이 되었는데 그 이유는 요청이 들어올 때, 스프링 컨트롤러에서 해당 요청에 해당하는 메소드를 찾고 해당하는것이 없을 경우 static내에서 파일을 찾아 출력해주기 때문이다.

위의 코드에서는 컨트롤러에 @GetMapping("/") 에 해당하는 home 메소드가 존재하기 때문에 해당 메소드를 실행시켜 home.html을 반환하는 것이다.

 

회원가입

/member/new로 Get 요청이 들어오는 경우 실행되는 createForm 메소드이다. members/createMemberForm.html

을 반환한다.

createMemberForm.html

이름을 입력받아 회원가입하고 해당 데이터를 /member/new 경로에 post방식으로 전송한다.

createMemberForm.html 출력

등록을 누를경우 해당 데이터가 /member/new 경로로 post 데이터 전송을 한다.

name을 받기위해 Controller 디렉토리에 추가해준 MemberForm이다.

createMemberForm에서 name을 반환할 경우 해당 값을 받기 위한 클래스이다.

 

create메소드

 

createMemberForm에서 post보낸 데이터를 받는 메소드이다.

post 전송방식에 매핑하기 위해  @PostMapping을 이용하고 createMemberForm에서 받은 정보가 MemberForm form으로 전송되고 해당 정보를 새로 생성한 member객체에 넣어 memberService.join을 통해 메모리에 저장한다.

해당 동작이 끝나면 home 화면으로 redirect 시켜준다.

 

회뭔 목록 조회

memberService.findMembers를 통해 저장된 회원들의 정보를 모두 가져온다.

model 객체에 addAttribute를 통해 가져온 정보를 model객체에 저장하고 해당 정보를 members/memberList에 반환한다.

members/memberList

Thymeleaf를 사용해 모든 회원들의 정보를 출력시킨다.

${member.id}는 member.getId() 메소드의 결과를 반환하고, ${member.name}은 member.getName() 메소드의 결과를 반환하여 출력한다.

 

3명의 회원을 등록하고 members 로 get요청하여 memberList.html을 출력한 결과이다.

728x90

'Programming > Spring' 카테고리의 다른 글

스프링 빈과 의존관계  (0) 2022.01.06
회원 관리 예제  (0) 2021.12.30
웹개발 기초  (0) 2021.12.21
728x90

MemberController에서 MemberService를 통해 회원가입을 하고 MemberService를 통해서 데이터를 조회할 수 있어야 한다.

MemberController와 MemberService는 의존관계이다.

MemberController가 MemberService를 의존한다.

 

스프링이 시작할 때 스프링 컨테이너에 @Controller, @Service, @Repository 등의 어노테이션을 가진 클래스 객체들을 생성하고 스프링 빈으로 등록해서 관리한다.

 

스프링 컨테이너 : 스프링에서 객체를 관리하는 것. 객체의 생명주기를 관리하고 컨테이너에 담겨있는 객체들을 스프링 빈(Bean)이라고 부른다.

 

@Controller, @Service, @Repository 어노테이션이 존재하는 클래스들의 경우 객체를 스프링 빈(Bean)으로 등록해 스프링 컨테이너에서 관리한다.

 

위처럼 MemberService() 객체를 new로 할당해서 사용할 수도 있지만, Spring 컨테이너에 저장된 빈을 불러서 사용하면된다.

new를 통해 객체를 생성해서 할당할 경우, 모든 Controller가 각각의 Service 객체를 사용하게 된다.

-> 자원이 낭비되며 비효율 적이다.

스프링 컨테이너에는 각 객체들이 하나만 빈으로 등록되어 해당 빈을 공유하는 형태로 사용하기 때문에 new를통한 객체 할당을 통해서 사용하는것보다 훨씬 효율적으로 사용이 가능하다.

생성자에 @Autowired 어노테이션을 사용하면 스프링 컨테이너에 빈으로 존재하는 MemberService 객체를 연결해 준다.

-> MemberController가 memberService를 의존하게 된다. (Dependency Injection : 의존성 주입)

 

@Controller 어노테이션이 존재하는 MemberController 클래스는 스프링이 시작할 때 자동으로 스프링 컨테이너에 스프링 빈으로 등록된다.

@Autowired 어노테이션을 통해 스프링 빈을 연결하는 경우 해당 객체또한 스프링 컨테이너의 스프링빈으로 등록되어 있어야한다. 즉 위 코드에서는 memberService가 스프링 빈으로 등록되어 있어야 한다는 것이다.

@Service 어노테이션의 경우 @Controller와 마찬가지로 스프링이 시작할때 컴포넌트 스캔에 의해 스프링 빈으로 등록된다. MemberService에서도 생성자에서 @Autowired를 사용하는데 이때 사용되는 MemberRepository 또한 스프링 빈으로 등록되어 있어야한다.

@Repository 어노테이션도 스프링 시작시 스프링 빈으로 등록시켜준다.

 

코드들은 위 그림과 같이 의존관계가 연결된다.

 

 

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

@Controller, @Service, @Repository와 같이 어노테이션으로 스프링 빈을 등록하는 경우는 컴포넌트 스캔 방식에 해당한다. @Component 어노테이션이 스프링 빈으로 등록하는 어노테이션이지만, @Controller, @Service, @Repository 어노테이션의 내부에 @Component가 존재해 세가지 어노테이션 모두 스프링 빈으로 등록된다.

 

스프링이 시작할 때 @Component 어노테이션들을 스캔해서 스프링 빈으로 등록하는 것을 컴포넌트 스캔이라고 한다.

@Autowired는 스프링빈을 연결하는 역할을 한다.(의존 관계 설정)

 

@Component스캔의 범위는 @SpringBootApplication 어노테이션이 존재하는 클래스의 패키지와 해당 패키지의 하위패키지에서만 작동한다.

@SpringBootApplication 어노테이션 내부에 @ComponentScan 어노테이션이 존재한다.

 

**스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본적으로 싱글 톤 등록을 한다.**

싱글 톤 : 하나만 등록하여 해당 객체를 공유한다.

예를 들어 MemberController의 경우도 하나의 객체만 컨테이너에 스프링 빈으로 등록해서 공유한다. MemberController 객체가 여러개 스프링빈으로 등록되지않는다.

 

자바 코드로 직접 스프링 빈 등록하기

@Service, @Repository, @Autowired 어노테이션을 제거한 후 코드를 작성한다.

SpringConfig 파일을 새로 하나 생성한다.

@Configuration 어노테이션은 설정 파일을 만들기 위한 어노테이션이며, Bean을 등록하기 위한 어노테이션이다.

@Configuration 클래스 내부에 @Bean 어노테이션 작성시 해당 객체를 Bean에 등록한다는 의미를 가진다.

위 코드는 MemberService와 MemoryMemberRepository 객체를 Bean으로 등록하는 코드이다.

 

위코드를 통해 코드로 직접 스프링 빈을 등록한 경우에도 아래처럼 구조가 가능해진다.

스프링이 시작될 때, memberRepository와 memberService를 스프링 컨테이너에 올려 빈으로 등록한다.

MemberService 생성자에 파라미터로 memberRepository를 넣어주는데 이때 들어가는 memberRepository는 빈으로 등록된 memberRepository이다.

 

자바코드로 직접 등록할 때에도 Controller는 컴포넌트 스캔으로 올라가야 하기 때문에 @Controller 어노테이션을 꼭 써줘야한다. 또한 Controller 생성자에서 스프링 빈 객체를 가져올 때도 컴포넌트 스캔 방법인 @Autowired 어노테이션을 작성해주어야한다.

 

Dependency Injection의 3가지 방법

1.생성자 주입(생성자에 @Autowired 어노테이션 사용)

2.필드주입(필드에 @Autowired 어노테이션 사용)

3.Setter 주입(set 메소드가 public으로 사용되며 어디서든 호출이 가능해져 개발중 문제가 생길 수 있다.)

 

가장 좋은 방법은 생성자 주입이다.(생성자에 @Autowired 어노테이션을 통해 빈 객체를 연결하는것)

 

실무에서는 주로 Controller, Service, Repository와 같은 코드들은 컴포넌트 스캔을 사용한다고 한다. 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하는 경우 설정을 통해 코드로 직접 스프링 빈으로 등록한다고 한다.

 

MemberRepository의 경우 Memory를 사용하는 구현체에서 DB를 사용하는 구현체로 바꿀 예정이기 때문에 컴포넌트 스캔이아닌 코드로 직접 빈에 등록하는 코드를 남겨둔다.

 

@Autowired를 통해 Dependency Injection을 하는 경우 MemberController와 MemberService 와 같이 스프링이 관리하는 객체 즉 스프링 빈 객체들에 한해서만 가능하다. 스프링 빈으로 등록되지 않은 객체들은 @Autowired 사용이 불가능하다.

 

728x90

'Programming > Spring' 카테고리의 다른 글

웹 MVC 개발  (0) 2022.01.08
회원 관리 예제  (0) 2021.12.30
웹개발 기초  (0) 2021.12.21
728x90

비즈니스 요구사항 정리 및 설계

데이터 : 회원ID, 이름

기능 : 회원 등록, 조회

 

백엔드 계층 구조

컨트롤러 : MVC 컨트롤러 역할

서비스 : 도메인 객체를 이용한 핵심 비즈니스 로직이 구현됨

도메인 : 회원, 주문, 쿠폰 등과 같이 데이터베이스에 저장되고 관리되는 비즈니스 도메인 객체

리포지토리 : 데이터베이스에 접근하여 도메인 객체를 DB에 저장하고 관리하는곳

 

아래 프로그램에서는 DB없이 메모리를 저장공간으로 사용한다.

 

도메인

데이터인 회원 ID와 name이 들어간 Member객체이다.

회원 도메인과 리포지토리 만들기

MemberRepository interface이다.

리포지토리는 도메인 객체를 DB에 저장하고 관리하는곳이기 때문에 domain.Member를 import한다.

이후 Member들을 DB에 저장하거나 관리하는 메소드들을 선언한다.

DB에 따라 해당 인터페이스를 상속받아 구현한다.

 

Memory를 DB로 구현한 MemoryMemberRepository

MemberRepository를 상속받아 MemoryMemberRepository를 구현한다.

해당 인터페이스의 save, findById, findByName, findAll 메소드들을 해당 클래스에서 구현한다.

1씩 sequence를 더하면서 id값을 부여, store(Map)라는 임시저장소(Memory DB)에 member 저장

 

findById : id값과 일치하는 id를 store에서 탐색

Null이 반환될 수 있기 때문에 Optional.ofNullable로 탐색

 

 

findByName :  name값을 기반으로 store에 저장된 값 탐색

stream()의 경우 for문으로 모든원소에 접근하듯이 모든 원소들에대해 접근한다고 생각하면 된다.

filter를 통해 name과 일치하는 멤버들을 걸러내고, findAny()를 통해 걸러진 값을 반환한다.

즉 store에 저장된 값중 이름이 name과 일치하는 경우를 반환한다.

 

모든 값들을 Member List로 반환한다.

 

회원 리포지토리 Test코드 작성

개발한 기능들을 main메서드를 실행시켜 테스트하는 경우 시간이 오래걸리고, 한번에 모두 테스트하기 어렵다는 단점이 있기 때문에 테스트코드를 생성하여 메소드들을 테스트하는것이 좋다.

test내에 리포지토리 패키지를 생성하여 test클래스를 작성해준다.

@Test 어노테이션을 사용하여 해당 메소드가 테스트 메소드임을 명시해준다.

위처럼 해당 테스트 메소드를 구현한뒤 해당 메소드만 실행시켜 테스트가 가능하다.

save를 실행시킬경우 해당 테스트코드 내에서 오류가 발생하거나 실패를 하게되면 해당 실행에서 경고를 반환한다.

테스트코드가 성공할경우 아래처럼 정상 종료가 된다.

테스트코드는 항상 해당 코드를 실행시키고 검증까지 완료해야한다. save 메소드의 경우 Member 데이터 생성과 save함수 실행, 이후 findById를 통해 해당 id를 검색하고 assertThat(member).isEqualTo(result)를 통해 검증을 완료했다.

 

findByName 테스트 코드

findByName을 검증하기 위해 데이터를 만들고 findByName을 통해 result를 반환받고 assertThat(result).isEqualTo(member1)을 통해 검증했다.

 

findAll() 테스트 코드

2개의 member를 생성 및 save하고 findAll()을 실행한다.

이후 findAll()의 반환의 결과가 2개 인지 확인하여 검증한다.

 

테스트코드의 경우 모든 메소드들을 한꺼번에 실행하여 테스트하는것도 가능하다.

여러 메소드를 한꺼번에 실행할 경우 각 메소드들이 순서와 상관없이 실행된다.

각 메소드들이 실행되면서 같은 데이터를 save하는 경우 에러가 발생한다.(중복 저장 예외)

이러한 문제를 해결하기 위해 아래의 코드를 추가해준다. 

@AfterEach는 해당 클래스내의 각 메소드들이 실행을 끝낼때 마다 실행되게하는 어노테이션이다.

 

MemoryMemberRepository의 clearStore메소드

위 메소드를 통해 각 메소드들이 끝날 때마다 저장소를 비워준다.

-> 중복 저장 예외를 방지할 수 있다.

 

위의 개발과정은 MemoryMemberRepository를 모두 구현하고, 테스트 코드를 통해 해당 클래스의 메소드들을 검증 했다. 해당 개발 과정을 뒤집어 테스트 코드를 먼저 작성하고 검증이 완료된 후 메소드를 구현할 경우 테스트 주도개발(TDD)이라고 한다.

 

MemberService 메소드 작성

Join() 메소드

join 내부에서 member중복을 검사하는 코드를 작성한 뒤 외부의 새로운 메소드로 생성했다.

ifPresent를 통해 findByName의 반환값이 존재한다면 throw new IllegalStateExceptioon("이미 존재하는 회원입니다.")를 실행시킨다.

ifPresent는 값이 존재할 경우 내부 로직을 실행시키는 기능을 제공한다.

 

위와 같이 MemberService 코드를 구현했다.

회원가입, 전체 회원조회, 회원ID를 통한 회원 조회 등 비즈니스적인 메소드들이 구현된다.

 

MemberService 테스트 코드 구현

위처럼 테스트코드의 메소드명을 한글로 지정해도 상관없다.

 

테스트 코드를 구현할 때 given-when-then 구성으로 코드를 작성하는것이 좋다.

given : 주어지는 것

when : 실행했을 때

then : 결과

 

어떤 것이 주어진 상황에서 코드를 실행했을 때 결과가 이렇게 나와야 한다는 과정을 정리하여 작성하는 방법이다.

given-when-then 의 회원가입 테스트코드

멤버가 given으로 주어지고 join메소드를 실행했을 때 검증을 위해 findOne을 통해 나오는 결과가 무엇인지 확인하는 과정이다.

 

중복 회원가입 예외 테스트코드

assertThrows는 두번째 인자로 주어진 로직 실행시 첫번째 인자에 해당하는 예외가 발생하는지 검사하는 함수입니다.

첫번째 인자가 IllegalStateException이 아닌 NullPointerException이라면 assertThrows에서 에러를 반환한다.

해당 로직에서 정상적으로 IllegalStateException 을 발생시키면 해당 예외를 예외객체 e에 저장하고,

assertThat을 통해 해당 예외 메세지를 "이미 존재하는 회원입니다." 와 비교하여 검증을 완료한다.

 

Service테스트 코드에서도 테스트별로 멤버들을 생성하고 저장하기 때문에 아래 코드를 넣어준다.

 

MemberService의 생성자 코드

MemberService의 생성자 인자로 memberRepository가 주어지며 각 MemberService마다 memberRepository를 별도로 갖게 된다.

 

테스트코드에서의 MemberService

@BeforEach를 통해 모든 메소드들이 실행되기전 해당 코드가 실행되도록 했다.

테스트코드 메소드들이 실행될 때 마다 MemoryMemberRepository()를 생성하고 서비스를 따로 생성해주며 서비스 객체 생성때마다 memberRepository를 생성해준다.

즉 각 테스트 메소드들이 각각의 memberService와 memberRepository를 갖게된다.

728x90

'Programming > Spring' 카테고리의 다른 글

웹 MVC 개발  (0) 2022.01.08
스프링 빈과 의존관계  (0) 2022.01.06
웹개발 기초  (0) 2021.12.21
728x90

인프런의 김영한님 Spring 강의 복습 및 정리

 

웹개발의 3가지 방법

1.정적 컨텐츠 : 서버의 동작 없이 요청한 HTML 파일을 그대로 내려주는것

2.MVC와 템플릿 엔진 : 서버에서 일부 동작을 통해 HTML을 가공하여 파일을 내려주는것 (JSP, PHP)

3.API : HTML로 파일을 내리는 것이아니라 JSON 형태의 데이터를 반환하는 방법

 

정적컨텐츠

스프링에서도 정적컨텐츠(Static Content) 기능을 제공한다.

 

서버 실행후 해당 파일 url 연결

해당 html 파일의 내용이 그대로 출력된다.

정적컨텐츠의 경우 요청한 html파일을 가공없이 그대로 출력해준다. 또한 동적 프로그래밍이 불가능하다.

 

MVC와 템플릿 엔진

MVC : Model, View, Controller

MVC 모델이 도입되기 이전에는 View에서 데이터가공까지 모두 처리하였다. ex) JSP, PHP

View는 화면과 관련된 일, 비즈니스 로직이나 서버 뒷단에 관련된 동작들은 Controller나 Back-end 비즈니스 로직에서 , View와 Controller, Back-end 에서 주고받는 데이터를 Model이라고 하는 데이터에 담아서 주고받는 구조로 동작한다.

 

Hello-mvc로 Parameter name을 가지고 GET 요청을 통해 서버에 요청하면 model객체에 addAttribute를 통해 파라미터로 받은 name값을 담아서 hello-template으로 전송한다. return의 목적지로 model 객체가 전송된다.

 

데이터를 전송할 때 사용하는 Model 객체의 경우 메소드의 파라미터로 선언만 해주면 Spring에서 만들어준다. Model 객체를 사용하기 위해 따로 메모리를 할당할 필요가 없으며 Spring에서 만들어준것을 사용하기만 하면된다.

addAttribute를 통해 저장된 값은 JSON형태이며 "name"이 key이고 파라미터로 전달받은 name이 value가 된다.

 

spring코드에서 반환되는 데이터가 전달되는 hello-template.html 이다.

Model 객체로 전달된 데이터의 key가 name인 value를 출력한다.

붉은색 선으로 밑줄 쳐져있는 name이 넘겨주는 파라미터 이름이고 푸른색 선이 가리키는 spring은 해당 파라미터의 값이된다.

전달받은 값을 addAttribute("name", name) 으로 Model 객체에 넣어주는데, key가 "name"이 되고 파라미터로 전달받은 값이 name에 들어가게된다. 즉 key : name, value : spring이 된다.

@RequestParam 어노테이션에 require=false를 넣어주는것은 해당 파라미터에 값이 없어도 실행되게 하는것이다.

파라미터 값을 채우지 않을 경우 default text인 hello! empty가 출력된다.

 

API

@ResponseBody 어노테이션을 사용할 경우 return에 의한 반환값이 그대로 HTTP response의 body에 삽입되어 HTML에 출력된다.

return "hello "+name 에 의해 name파라미터로 전송된 pw4ngc0가 삽입되어 출력됨을 알 수 있다.

 

API를 통한 객체 반환

Hello 객체에 name값을 넣어 반환한다.

객체를 반환할 경우 해당 객체의 데이터를 JSON형태로 출력해준다.

프론트에서 해당 객체를 가공하여 사용하는 것 같다.

 

@ResponseBody 어노테이션의 메소드가 객체를 반환할 경우 해당 객체를 JSON형태로 변환하여 HTTP 응답으로 넘겨준다.

728x90

'Programming > Spring' 카테고리의 다른 글

웹 MVC 개발  (0) 2022.01.08
스프링 빈과 의존관계  (0) 2022.01.06
회원 관리 예제  (0) 2021.12.30

+ Recent posts