purplely88's home

시작에 앞서

9장을 완료해주세요.

 

데이터 바인딩 개요

데이터 바인딩이란 화면 입력항목과 오브젝트 필드와의 맵핑을 뜻합니다.

또한, 화면으로부터 전달받은 값을 필드 데이터형에 맞춰서 변환도 해줍니다.

 

스프링에서는 데이터 바인딩을 어느 정도 자동으로 시행해줍니다.

예를 들어, 화면에서 텍스트 항목에 입력한 수치를 오브젝트의 int형으로 변환해줍니다.

대신, 데이터 형 변환이 어려울 경우도 있습니다. 그런 경우에는 어노테이션을 사용하는 것으로 데이터 바인딩을 할 수 있습니다. 

어떤 방식으로 쓰는 거에는 이다음 실제 예를 들어서 설명하겠습니다.

 

데이터 바인딩 코딩

그러면 실제로 만들어보겠습니다.

데이터 바인딩에 필요한, 화면에서 컨트롤러 클래스에 오브젝트를 넘기는 방법에 대해서 배우겠습니다.

만들기 전에, 먼저 처리 개요에 대해서 설명하겠습니다.

로그인 화면에서는 아무것도 하지 않기 때문에, 유저 등록 화면의 처리 개요를 보도록 하겠습니다.

 

1,2의 처리

로그인 화면에서 유저 등록 화면으로 전이합니다. 그때에 유저 등록용 폼 클래스의 인스턴스(SignupForm)를 화면에 전달합니다.

 

3,4,5의 처리

유저 등록 버튼을 클릭하면, 폼 클래스의 인스턴스를 컨트롤러 클래스에 전달합니다.

폼 클래스의 인스턴스를 받은 후에는 /login에 리다이렉트 합니다.

유저 등록은 이루어지지 않습니다. 

(실제 등록은 데이터 베이스 쪽에서 다루겠습니다.)

 

즉, 유저 등록 화면에서 유저 등록용 폼 클래스에 데이터 바인딩을 합니다.

먼저 폼 클래스를 작성해보도록 하겠습니다. 

 

SignupForm.java

 

 

포인트 1

@DateTimeFormat 어노테이션을 필드에 붙임으로써, 화면으로 전달받은 문자열을 날짜 형식으로 변환해줍니다.

또한, pattern속성에 어떤 포맷으로 데이터를 전달받을지를 지정합니다.

 

데이터 바인딩용 어노테이션 @DateTimeFormat 외에도 @NumberFormat이라는 어노테이션이 있습니다.

사용방법은 구글링 하시면 쉽게 아실 수 있으십니다.

 

다음은 화면에서 전달받은 폼 클래스를 컨트롤러 클래스에서 받을 수 있도록 해보겠습니다.

 

SignupController.java

 

포인트 1

인수 폼 클래스에 @ModelAttribute 어노테이션을 붙임으로써, 자동으로 Model 클래스에 등록(addAttribute)해줍니다.

즉, 아래의 내용과 같습니다.

 

@GetMapping("/signup")

public String getSignUp(SignupForm form, Model model) {

  // 폼 클래스를 Model에 등록

  model.addAttribute("SignupForm", form);

}

 

또한, @ModelAttribute를 붙일 경우, 디폴트 값으로는 클래스 명의 앞글자가 소문자로 바꾼 문자열이 키 명으로 등록됩니다.

샘플 코드의 경우에는 "signupForm"이라고 하는 키 명에 등록됩니다.

혹시나 키 명을 바꾸고 싶을 경우에는 @ModelAttribute("<키명>")과 파라미터를 지정합니다.

 

포인트 2

데이터 바인딩 결과를 받을 경우에는 메서드의 인수에 BindingResult 클래스를 추가합니다.

이 클래스의 hasError() 메서드에 데이터 바인딩에 실패했는지를 알 수 있습니다.

또한, 이후에 등장할 밸리데이션(입력 체크)에 에러가 발생할 경우에도 이 BindingResult 클래스의 hasError() 메서드에 실패했는지 알 수 있습니다.

 

포인트 3

데이터 바인딩에 실패할 경우에는 BindingResult의 hasError() 메서드에 false가 반환됩니다.

위 코드에서는 데이터 바인딩에 실패했을 경우, 유저 등록 화면으로 돌아옵니다.

그 경우에는 getSignUp 메서드를 호출하고 있습니다.

이유는 라디오 버튼용 변수를 초기화해주기 때문입니다.

 

다음으로 유저 등록용 html을 수정합니다.

 

포인트 1

th:object 속성을 사용함으로써, Model에 등록된 오브젝트를 받는 것이 가능합니다.

위 코드로 말하자면, SignupForm 클래스를 받고 있습니다.

 

사용 방법은 다음과 같습니다.

th:oject="${<ModelAttribute의 키 명>}"

 

th:object를 붙인 태그 안에는 th:field에 그 오브젝트명을 생략할 수 있습니다.

th:field를 사용하면, 오브젝트 안에 필드를 취득할 수 있습니다.

 

th:field 사용법 첫 번째

th:field="${<ModelAttribute의 키 명. 필드명>}"

이것은 th:object속성을 쓰지 않았을 경우의 사용법입니다. 이 방법으로 위 코드를 다시 작성해본다면 아래와 같습니다.

th:field="${signupForm.userId}"

 

필드가 하나밖에 사용되지 않을 경우에는 th:object를 작성하지 않아도 값을 취득, 송신이 가능하다는 것입니다.

 

th:field 사용법 두 번째

th:field="${<필드명>}"

th:object가 붙은 태그 안에 있으면, 오브젝트 명을 생략할 수 있습니다.

이것은 위 코드와 같은 방법입니다. 화면에서 보낼 필드가 많을 경우에 유효한 방법입니다.

종래 필드가 늘 수 있다는 생각을 한다면 이 방법을 추천합니다.

 

포인트 2

에러 메시지를 정리해서 일람 표시하기 위해서는 라디오 버튼과 같은 th:each 속성을 사용합니다.

정리하지 않고 개별 메시지로 표시하는 방법에 대해서는 밸리데이션(입력 체크)에서 설명하겠습니다.

 

실행

스프링 부트를 실행하신 다음에 신규 등록 화면으로 들어오셔서 등록 정보를 입력합니다.

정상 등록일 시 로그인화면으로 전이
컨트롤러 클래스에서 작성한 println 내용

이것으로 두 가지가 가능해졌습니다.

1. 화면에서 폼 클래스에 값을 전달한다.

2. 폼 클래스를 컨트롤러 클래스에 전달한다.

 

하지만, 또 문제가 있습니다.

데이터 바인딩에 실패했을 경우입니다. 

예를 들어, 연령에 문자열이 입력되어 있다던지, 생일에 연도만 입력해보겠습니다.

 

이대로라면 메시지가 영어이면서 내용도 적절치 않습니다.

다음으로 에러 메시지를 수정해보도록 하겠습니다.

'프로그래밍 > 스프링 부트' 카테고리의 다른 글

9. 데이터 바인딩 & 밸리데이션 (1/4)  (0) 2020.06.20
8. DI (4/4)  (0) 2020.06.18
7. DI (3/4)  (0) 2020.06.16
6. DI (2/4)  (0) 2020.06.16
5. DI (1/4)  (0) 2020.06.14

시작

직접 WEB 애플리케이션(로그인 화면)을 제작해보면서 데이터 바인딩이 무엇인지, 밸리데이션이 무엇인지 알아보겠습니다.

 

데이터 바인딩

화면에 입력한 유저 ID와 패스워드 등을 오브젝트로 받는 방법에 대해 설명합니다.

 

밸리데이션 (입력 체크)

유저 등록 시에 밸리데이션 방법에 대해서 설명합니다.

 

라이브러리 추가

이번부터는 Bootstrap이라고 하는 CSS 프레임워크를 사용합니다.

Bootstrap을 사용함으로써 나름 이쁜 화면을 간단히 만드는 것이 가능합니다.

(개발 현장에서도 많이 사용합니다.)

 

Bootstrap을 사용하는 방법에는 몇 가지 방법 있습니다만, 여기서는 제일 간단한 방법으로 webjars라는 라이브러리를 사용하여 Booststrap을 설정합니다.

 

webjars

webjars란, maven등과 같은 빌드 툴로, JavaScript나 Bootstrap 등을 간단하게 관리할 수 있는 라이브러리입니다.

webjars를 사용하기 위해선, pom.xml의 dependencies 태그 안에 아래의 두 개의 라이브러리를 추가합니다.

 

jQuery

pom.xml 에는 Bootstrap 외에도 jQuery 라이브러리를 추가합니다.

jQuery라는 것은 JavaScript 라이브러리입니다. JavaScript 코드를 그대로 쓰는 것을 보다 쉽게 알 수 있기 때문에 개발 현장에서도 많이 사용됩니다.

Bootstrap에 일부에 jQuery가 사용됩니다. 그로 인해 Bootstarp을 사용할 때에는 jQuery가 필요합니다.

 

pom.xml을 변경한 다음에는 maven 갱신을 시행합니다.

maven을 갱신하면 추가한 라이브러리를 다운로드합니다.

maven 갱신
OK 버튼을 눌러 갱신 시작

 

다운로드 완료 확인

 

bootstrap 내용물 확인

보시면 아시겠지만, Webjars 에는 Bootstrap 등과 같은 파일을 직접 다운로드하고 있습니다.

이 jar파일의 경로를 지정하는 것으로 css 파일이나 js 파일을 읽는 것이 가능합니다.

이 이후에 실제로 사용하는 방법에 대해서 설명하겠습니다.

 

화면 작성

데이터 바인딩, 밸리데이션의 기능을 코딩하기 전에 HTML 화면 먼저 작성해보겠습니다.

그 이유는 화면 작성과 데이터 바인딩 코딩을 같이해버리면 복수의 설명이 되기 때문에..

 

그러면 이제부터 로그인과 신규등록에 관한 화면을 만들어보겠습니다.

먼저 구성은 아래와 같습니다.

 

로그인, 신규등록 컨트롤러 두개 작성과 동시에 html도 작성.

LoginController.java

 

내용을 말씀드리자면, http://localhost:8080/login에 GET 메서드, POST 메서드로 HTTP 요청(리퀘스트)이 온다면, login폴더 안에 있는 login.html에 화면을 전이한다는 것입니다.

각 메서드에 지정한 반환 값은 html 파일입니다.

html 파일은 src/main/resources/template 에서부터 지정되며, 그 이유로 return login/login이라는 것은 src/main/resources/template/login이라는 폴더 안에 login.html을 말하는 것입니다.

 

다음으로는 로그인 화면을 만들어보겠습니다.

 

login.html

포인트 1

Bootstrap을 사용하기 위해서는 jQuery와 Bootstrap의 파일을 읽어드릴 필요가 있습니다.

그렇기 때문에 <head> 태그 안에 추가했습니다.

통상 css 나 javascript 파일은 src/main/resources 안에 두어 파일을 읽습니다.

하지만 webjars를 사용하면 다운로드한 라이브러리 안에 파일을 src/main/resources안에 둔 것과 같이 파일을 읽을 수 있습니다. 또한 타임 리프에서 src/main/resources 안에 파일을 읽는 방법은 다음과 같습니다.

css 파일

th:href="@{src/main/resources부터의 파일 경로}"

js 파일

th:href="@{src/main/resources부터의 파일 경로}"

또한 jQuery의 js 파일을 읽은 후에 Bootstrap의 js 파일을 읽는 것과 같이 되어있습니다.

Bootstrap 파일 안에 jQuery를 사용하기 때문에, 먼저 jQuery 자기 자신을 읽지 않으면 안 됩니다.

Bootstrap을 사용하면 디자인성이 높은 화면을 간단히 제작할 수 있습니다. 

사용 방법은 각 태그의 class속성에 Bootstrap에서 제공하는 몇 가지 클래스를 SET만 하면 됩니다.

그로 인해 class="XXX"와 같이 되어있는 태그는 Bootstrap을 사용하고 있다고 생각해주세요.

 

포인트 2

타임 리프를 이용하여 앵커 태그를 사용할 경우에는 th:href 속성을 사용합니다.

사용하는 방법은 컨트롤러 클래스 안에 @GetMapping 어노테이션의 괄호 안의 문자열을 지정하는 거뿐입니다.

문자열을 지정할 경우에는 @{'/xxx'}라고 하는 형식으로 전이처를 지정합니다. 또한 앵커 태그이기 때문에 HTTP 요청(request)은 GET 메서드로 송신됩니다.

 

다음은 유저 등록 화면용 컨트롤러를 제작해보겠습니다.

 

SignupController.java

포인트 1

타임 리프에 라디오 버튼의 값을 동적으로 변경하기 위해서는 Map을 사용합니다.

그 Map에 들어간 키와 값을 화면에 표시하는 것이 가능합니다.

위 소스에서는 initRadioMarriage()라고 하는 메서드 안에 Map에 값을 넣습니다.

그리고 유저 등록 화면에 GET 요청(request)이 올 경우, Model 클래스에 Map을 등록합니다.

이러함에 있어 화면으로부터 Map의 값을 취득할 수 있게 됩니다.

타임 리프로 라디오 버튼을 만드는 방법은 차후에 설명하겠습니다.

 

포인트 2

리다이렉트를 할 경우, 메서드의 반환 값에 redirect:/전이처 경로를 지정합니다.

리다이렉트를 하면 전이처에 컨트롤러 클래스의 메서드가 호출됩니다.

위 소스의 경우 http://localhost:8080/login에 GET 메서드로 HTTP 요청(request)이 보내집니다.

그리고 LoginController의 getLogin 메서드가 호출됩니다.

 

위 코드를 다시 설명해드리자면, http://localhost:8080/signup에 GET 메서드로 HTTP 요청(request)이 온다면, signup.html (유저 등록 화면)으로 전이합니다.

 

다만, http://localhost:8080/signup에 POST 메서드로 HTTP 요청(request)이 온다면, 로그인 화면으로 리다이렉트 합니다. /login에 리다이렉트 하기 때문에 LoginController의 GET 메서드를 호출합니다.

 

다음은 유저 등록 화면을 작성해보겠습니다.

 

signup.html

포인트

라디오 버튼을 코딩하기 위해서는 th:each 속성을 사용합니다.

th:each 속성은 확장 for문과 같이 사용합니다. 

th:each

th:each="변수명 : ${<ModelAttribute의 키명>}" 형식으로 지정합니다. 

이것으로 Model에 등록된 값을 반복해서 호출하게 됩니다. 또한, th:each 가 붙은 태그 안에는 Model에 등록된 값을 변수명으로 간단히 취득할 수 있습니다.

위 코드에서는 th:each 속성이 붙은 div 태그 안에는 item이라고 하는 변수를 사용합니다.

이 item 변수 안에는 SignupController 클래스에서 취득한 Map이 들어가 있습니다.

th:text

th:text에는 화면에 표시할 문자열을 지정합니다.

위 코드에서는 Map 클래스의 키(기혼, 미혼)를 표시합니다.

th:value

th:value에는 화면으로부터 Controller 클래스에 보낼 값을 지정합니다.

위 코드에서는 Map 클래스의 값(true, false)을 보냅니다.

 

th:action

로그인 화면에서는 form 태그 안에 action="/login"이라는 형태로 요청(request)처 URL를 지정했습니다.

이것은 통상 HTML 사용법입니다. 

반면, 유저 등록 화면에서의 html에서는 th:action="@{/signup}"과 같이 요청(request)처 URL를 지정했습니다.

이것은 타임 리프의 사용 방법 중 하나입니다.

스프링 시큐리티를 사용하지 않는 경우에는 action="/login"과 같은 형태로 사용하여도 문제가 되지 않습니다.

하지만, 스프링 시큐리티를 사용할 경우에는 form태그의 th:action 속성을 사용하도록 해주세요.

관련 상세 내용은 추후에...

 

여기까지 작성이 완료되었으면 스프링 부트를 기동하여 아래의 URL을 입력해보겠습니다.

http://localhost:8080/login

 

로그인 버튼을 눌러도 아무 처리없이 로그인 화면으로 돌아옵니다.

신규등록은 여기로를 눌러보면...

이제 준비가 완료되었습니다.

다음 포스트부터는 본격적으로 데이터 바인딩, 밸리데이션에 대해서 설명해드리겠습니다.

'프로그래밍 > 스프링 부트' 카테고리의 다른 글

10. 데이터 바인딩 & 밸리데이션 (2/4)  (0) 2020.06.23
8. DI (4/4)  (0) 2020.06.18
7. DI (3/4)  (0) 2020.06.16
6. DI (2/4)  (0) 2020.06.16
5. DI (1/4)  (0) 2020.06.14

시작에 앞서

7장을 완료해주세요.

 

DI 라이프 사이클 관리 기능에 앞서

DI안에서는 인스턴스를 생성하고 그것을 변수에 넣는다는 것은 이제 충분히 이해하셨을 거라 봅니다.

하지만 DI 기능은 이거뿐만이 아닙니다.

 

DI에서는 라이프 사이클 관리도 행해지고 있습니다.

WEB 애플리케이션 개발하는 상에서는 꽤나 편리한 기능입니다만, 함정이 있습니다. (헉)

그럼 알아보도록 하겠습니다.

 

DI 라이프 사이클 관리

애초에 라이프 사이클 관리라는 것이 무엇이 나면, 인스턴스 생성과 파괴를 관리해주는 것입니다.

 

인스턴스를 생성할 때에 통상은 new를 붙여서 생성합니다. 반대로 인스턴스의 파괴는 변수에 null를 넣습니다.

사용하지 않게 된 변수에 null을 넣지 않으면, 메모리상에 그대로 남게 되어 낭비가 되기 때문입니다.

이것은 퍼포먼스에 영향을 준다는 지, 메모리 부족에서부터 시스템 정지까지도 일어날 수 있습니다.

 

그렇지만 @Autowired를 붙인 필드에 null을 일부러 넣을 필요는 없습니다.

스프링이 자동으로 해주기 때문에! 오홍

 

또한, Java에 Web 애플리케이션을 작성할 때에는 서블릿을 사용합니다.

서블릿을 사용할 때는 인스턴스를 Session 스코프나 Request 스코프에 등록합니다만, 이것도 스프링에서 해줍니다.

 

하지만, 그 인스턴스가 언제 파괴되는지에 대한 것을 꼭 파악해 둘 필요는 있습니다.

 

스코프

Session 스코프나 Request 스코프라고 하는 것은 인스턴스에 유효기간을 말합니다.

Java의 로컬변수를 상상해보세요. if문안에 선언한 변수는 if 안이 스코프이기 때문에, if문 밖에서는 사용할 수 없습니다.

 

각 스코프의 유효기간은 아래와 같습니다.

 

Session 스코프

유저가 로그인할때부터 로그아웃하기까지의 유효기간입니다.

예를들어, 유저 정보나 그것과 연관된 권한과 같은 정보를 Session 스코프로 가지고 있습니다.

 

Request 스코프

HTTP의 요청(Request)이 유효기간입니다.

예를 들어, 유저가 등록 화면에 각 항목을 입력한 후, 등록 버튼을 누르면, 입력 값을 출력되는 결과 화면이 표시됩니다. 

그 경우에 유저 등록 화면부터 결과 화면까지가 Request 스코프의 범위가 됩니다.

 

그러면 어떻게 해서 라이프 사이클 관리를 하는지는 @Scope 어노테이션을 붙입니다.

그리고 그 어노테이션에 어떤 스코프를 등록할지를 지정합니다. 

예를 들어, 아래와 같이 씁니다.

 

@Scope 샘플

 

@Component

@Scope("prototype")

public class SampleComponent {

 

}

 

빨간 글씨의 prototype은 Bean을 취득할 때마다 매회 새로운 인스턴스가 생겨나는 의미입니다.

※ prototype 외에도 singleton, session, sequest 등등 여러 가지가 있으니 검색!

 

@Component 외에도 @Bean이나 @Controller에서도 @Scope 어노테이션을 이용할 수 있습니다.

@Scope 어노테이션을 이용함으로써, 간단히 인스턴스 생성과 파괴가 가능해졌습니다.

 

DI 함정 첫 번째 - singleton

여기서부터는 스프링의 DI에 자주 있는 함정에 대해서 설명해보도록 하겠습니다.

먼저, singleton을 썼을 경우에 함정입니다.

 

스프링을 갓 배우기 시작하면, @Scope를 붙이는 것을 모른 채로 어노테이션을 만드는 경우가 있습니다.

특히 인터넷상에서는 간단하게 사용하는 방법을 배웠을 경우도 포함입니다.

 

@Scope를 붙이지 않으면, 인스턴스가 singleton에 작성됩니다.

singleton이란, Java의 디자인 패턴 중 한 가지입니다.

singleton이면 오브젝트의 인스턴스는 하나밖에 만들 수 없습니다.

 

singleton이 원인이 되어 버그가 발생되는 경우는 적습니다만, 아무리 조사해도 원인을 알지 못할 때에는

singleton이니까 발생한 것이 아닌가? 하는 의구심도 가질 필요가 있습니다.

 

※ @Controller, @Service, @Repository의 스코프는 통상 singleton으로 충분합니다.

 

DI 함정 두 번째 - 스코프의 차이

함정 두 번째는 스코프의 차이입니다.

예를 들어, singleton 스코프의 인스턴스가 prototype 스코프의 오브젝트를 가지고 있을 경우입니다.

 

prototype 스코프 샘플

@Component

@Scope("prototype")

public class PrototypeComponent {

 

}

 

singleton 스코프 샘플

@Component

public class SingletonComponent {

  @Autowired

  private PrototypeComponent component;

}

 

이렇게 함으로써 prototype 스코프를 설정한 Bean (PrototypeComponent)이 singleton스코프로 되어버립니다.

다른 예로는 request 스코프를 설정한 Bean을 session 스코프의 Bean에서 가질 때 위와 같은 상황이 되어버립니다.

 

스코프가 다른 Bean을 필드로써 가지는 경우는 주의가 필요합니다.

 

마무리

DI?

DI는 한 단어로 말해서 인스턴스 관리를 하는 것을 뜻한다.

인스턴스 관리는 인스턴스의 생성과 스코프 관리를 말한다.

@Controller와 같은 어노테이션이 붙은 클래스의 인스턴스를 DI 컨테이너에 등록한다.

DI컨테이너에 등록된 클래스를 Bean이라고 말한다.

@Autowired를 사용하면 DI 컨테이너로부터 getter로 인스턴스를 취득할 수 있다.

 

DI 실행 방법

1. 어노테이션 베이스

2. JavaConfig

3. xml

4. JavaConfig + 어노테이션 베이스

5. xml + 어노테이션 베이스

(주로 4번과 5번을 많이 사용한다.)

 

라이프 사이클 관리

DI에서는 인스턴스 스코프를 설정할 수 있다.

스코프는 @Scope 어노테이션에서 설정할 수 있다.

디폴트 값으로는 singleton으로 되며, 인스턴스가 하나밖에 생성되지 않는다.

스코프가 다른 인스턴스를 필드에서 가질 경우에는 주의가 필요하다.

'프로그래밍 > 스프링 부트' 카테고리의 다른 글

10. 데이터 바인딩 & 밸리데이션 (2/4)  (0) 2020.06.23
9. 데이터 바인딩 & 밸리데이션 (1/4)  (0) 2020.06.20
7. DI (3/4)  (0) 2020.06.16
6. DI (2/4)  (0) 2020.06.16
5. DI (1/4)  (0) 2020.06.14