simDev1234
심플하고 차분하게
simDev1234
전체 방문자
오늘
어제
  • 분류 전체보기
    • Computer Science
      • Basic Math
      • Data Structure
      • Algorithm
      • Database
      • OS
    • Language
      • Java
      • Kotlin
      • SQL
    • Framework
      • Spring
      • Orm&Mapper
      • 프로젝트로 스프링 이해하기
      • 스프링 라이브러리
    • Infra
      • Cloud
      • Docker
      • Redis
      • AWS, Azure
      • Device
    • Etc
      • CleanCoding
    • Git,Github

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 참조타입
  • JVM메모리구조
  • 컨트롤러
  • controllerTest
  • 404
  • 자바메모리구조
  • 자바프로그램
  • 자바
  • 스프링
  • 참조변수
  • 자바프로그래밍
  • null
  • scanner #next() #nextLine()

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
simDev1234

심플하고 차분하게

[LMS 만들기] @Controller 와 @RestController 정확히 알기, AJAX로 Json 데이터 전송하기, Principle(로그인 정보)
Framework/프로젝트로 스프링 이해하기

[LMS 만들기] @Controller 와 @RestController 정확히 알기, AJAX로 Json 데이터 전송하기, Principle(로그인 정보)

2022. 10. 8. 03:03

|  Today I learned

1. @Controller와 @RestController의 차이

- 앞에서는 내가 @Controller와 @RestController에 대해 차이를 정리할 때, 잘못된 정보를 가지고 있었다.

- @Controller로도 @RestController처럼 Json 타입의 데이터를 보내는 건 가능하다.

- 그럼에도 굳이 둘을 나눠서 쓰는 건, 사용 목적에 따라 쓰임새를 나누어두기 위함으로 보인다.

 

* 아래 내용 원본 출처 : https://mangkyu.tistory.com/49

(1) @Controller : 주로 View를 반환한다. Model로 Data-binding 가능

  @Controller
View를 반환할 때 Data를 반환할 때
목적 @Controller는 주로 View를 반환하기 위해 사용하지만, @ResponseBody로 Data를 반환할 수는 있다.
동작 1. Client는 URI 형식으로 request를 전송
2. DispatcherServlet이 HandlerMapping을 찾음
3. HandlerMapping을 통해 요청을 Controller로 위임
4. Controller는 요청 처리 후 ViewName을 반환
5. DispatcherServlet은 View Resolver를 통해
    ViewName에 해당되는 View를 찾아 사용자에게 반환
1. Client는 URI 형식으로 request를 전송
2. DispatcherServlet이 HandlerMapping을 찾음
3. HandlerMapping을 통해 요청을 Controller로 위임
4. Controller는 요청 처리 후 객체를 반환
5. 객체는 Json으로 serialize되어 Client에 반환
비고 기본적으로 template안에 있는 html을 View로 반환 @ResponseBody를 통해, Json 형태로 데이터를 반환
* 스프링은 HTTP accept 헤더와 Controller 반환 타입을 조합하여 적합한 HttpMessageConverter를 선택해 처리한다.

출처:https://codingnotes.tistory.com/28

 

 

@Controller
@RequiredArgsConstructor
public class MemberController{
	
    private final MemberService memberService;
    
    // View를 반환할 때
    @GetMapping(value = "/member/list")
    public String memberListView(Model model, @RequestParam("memberType") String memberType) {
    	
        List<MemberDto> memberList = memberService.findByMemberType(memberType);
        
        model.addAttribute("memberList", memberList);
        
        return "member/list";
    }
    
    // Data를 반환할 때
    @GetMapping(value = "/member")
    public @ResponseBody ResponseEntity<Member.Response> getMemberDetail(@RequestParam("id") String id){
    	
        return ResponseEntity.ok(memberService.findById(id));
        
    } 
    
}

 

(2) @RestController

- @Controller에 @ResponseBody가 추가된 형태와 완전히 동일하다.

- 따라서, 웹에 뷰를 전달할 때는 @Controller를 쓰고,

  api 형태로 데이터를 가공해서 JSON으로 전달할 땐 @RestController를 쓴다.

  @Controller @RestController 
목적 주로 View를 반환하기 위함 JSON 형태로 객체 데이터를 반환하기 위함
동작 1. Client는 URI 형식으로 request를 전송
2. DispatcherServlet이 HandlerMapping을 찾음
3. HandlerMapping을 통해 요청을 Controller로 위임
4. Controller는 요청 처리 후 ViewName을 반환
5. DispatcherServlet은 View Resolver를 통해
    ViewName에 해당되는 View를 찾아 사용자에게 반환
1. Client는 URI 형식으로 request를 전송
2. DispatcherServlet이 HandlerMapping을 찾음
3. HandlerMapping을 통해 요청을 Controller로 위임
4. Controller는 요청 처리 후 객체를 반환
5. 객체는 Json으로 serialize되어 Client에 반환
비고 기본적으로 template안에 있는 html을 View로 반환 @Controller + @ResponseBody

- 아래 두 가지 방식에 대해서, 첫번째 방식의 경우, Client가 예상하는 HTTP Status를 설정해줄 수 없다.

- 두번째 방식의 경우에는 HttpStatus를 알 수 있지만 ok() 메소드만으로만 알 수 있다.

- 세번째 방식을 사용하면 HttpStatus를 명시적으로 볼 수 있다.

@RestController
@RequiredArgsConstructor
public class MemberController{
	
    private final MemberService memberService;
    
    // 첫번째 방식
    @GetMapping(value = "/member")
    public Member.Response getMemberDetail(@RequestParam("id") String id){
    	
        return memberService.findById(id);
        
    } 
    
    // 두번째 방식
    @GetMapping(value = "/member")
    public @ResponseBody ResponseEntity<Member.Response> getMemberDetail(@RequestParam("id") String id){
    	
        return ResponseEntity.ok(memberService.findById(id));
        
    } 
    
    // 세번째 방식
    @ResponseStatus(value = HttpStatus.OK)
    @GetMapping(value = "/member")
    public Member.Response getMemberDetail(@RequestParam("id") String id){
    	
        return memberService.findById(id);
        
    }
    
}

 

2. ajax를 사용하는 방법

- 국비지원 과정을 들을 때, jqeury ajax에 대해 아주 간단히 보고 넘어갔었다.

- 오늘 수업에서는, ajax를 쓰기 위한 다른 방법, axios에 대해서 배웠는데, 둘 다 쓰기 나름일 것 같다.

rf.  ajax는 비동기식 처리(요청의 응답을 기다리지 않고 다음을 요청 <->동기식처리)를 한다.

   * 동기식 처리 : 요청의 응답을 기다린 후 응답이 오면 다음 요청을 한다.(멀티캐스팅 불가, 데이터 손실X)
   * 비동기식 처리 : 요청의 응답을 기다린 후 응답이 오지 않아도 다음 요청을 한다.(멀티태스킹 가능, 데이터 손실 가능)

ajax 제이쿼리 axios
공통점 비동기식 처리를 하는 ajax
차이점 제이쿼리 문법 자바스크립트 promise 패턴
tutorial https://www.w3schools.com/jquery/jquery_ajax_get_post.asp https://github.com/axios/axios

(1) 제이쿼리 ajax

//jQuery Ajax - Request
let id = $('#id').val();

$.ajax({
    url      : '/course/detail'
    data     : {'id' : id},
    cotentType : 'application/json',    // request data type
    dataType : 'json',                  // response data type
    success  : function(res_data){
        
    },
    error    : function(err){
        //alert(err.responseText);
    }
});

(2) axios를 이용한 방법 *여기서는 CDN 방식으로 import했다.

1. 아래 링크에서 axios 다운로드 또는 CDN방식으로 가져오기
https://github.com/axios/axios

2. 위 링크에서 추천해준 방식에 따라 axios 작성
-- 링크의 예시
axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
<script>

    $(document).ready(function(){
    
        $('#submitForm').on('submit',function(){

			// url, parameter 객체
            let $thisForm = $(this);
            let url = '/api/course/req.api';
            let parameter = {
                id : $thisForm.find('input[name=id]').val()
            };
            
            // axios 포맷에 맞춰 작성
            axios.post(url, parameter).then(function(response){

            }).catch(function(err){

            });

            return false;

        });
        
    });
    
</script>

브라우저Network
브라우저Network

 

3. Spring Security의 Principle

- Spring Securiy에는 Principle이라는 인터페이스가 있다

* 출처 : https://codevang.tistory.com/273

출처:https://codevang.tistory.com/273

.

출처:https://codevang.tistory.com/273

- (Spring Securiy에 대해서는 이론적인 부분을 차후 보강해나갈 예정)

- 가볍게 이해하기로는, 최종 인증 정보- Authentication - 의 상위 인터페이스이다.

- Principal을 통해서 하위 객체들의 메소드를 사용할 수 있는데, 그 중 하나가 getName() 으로 이는 User ID를 말한다.

- 한 번 해보니까, Pricipal은 로그인 세션이 끝날 때 null로 오기도 해서, 에러가 나타날 수 있었다.

- 이에 대해서 아래 블로그에서는 다른 메소드를 사용할 수 있다고 안내하는데, 이 부분은 차후 확인해보려고 한다.

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=phrack&logNo=80202619173 

/* 수강 신청 */
@PostMapping("/api/course/req.api")
public ResponseEntity<?> courseReq(Model model,
                                   @RequestBody TakeCourseInput parameter,
                                   Principal principal) {

    parameter.setUserId(principal.getName()); // 로그인 Id 추가

    boolean result = courseService.req(parameter); // 수강 신청

    if (!result) {
        return ResponseEntity.badRequest().body("수강신청에 실패하였습니다.");
    }

    return ResponseEntity.ok().body(parameter);
}

 

4. 비즈니스 로직 InValidation ? - Exception? or Not

- Account(RestAPI) 프로그램 계좌 등록 로직에선, Custom Exception에서 Enum타입의 ErrorCode를 전달했는데

- LMS 프로그램 수강신청 로직에선 마지막에 ServiceResult라는 클래스를 통해 invalid message를 전달했다.

- 막바지에 이렇게 코드를 수정하는 이유에 대해 강사분은,

  (예상에 따라) 수강신청 시 신청이 안 되는 상황은 Error로 보기 어렵다고 판단했기 때문이라 말했다.

- 그러며 모든 상황에 대해 ResponseEntity.ok() 처리를 하고, body에 message를 전달했다.

- 이에 따라 결과적으로는 '강좌가 없는 경우',  '이미 신청한 경우'에 수강신청을 하면 Response Header에 200이 반환됐다

- 사용자 입장에선 사실 200이 뜨건, 500이 뜨건, 400이 뜨건 잘 돌아가면 별 문제가 없다고 생각하겠지만,

  서비스를 제공하는 관점에서 진정한 의미의 예외상황과 아닌 상황을 나누기 위해 이렇게 하지 않았을까 싶다.

package com.example.fastlms.course.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ServiceResult {

    boolean result;
    String message;

}

- 이 방식으로 하게 되면 아래 예시 코드처럼, 신청이 안료되지 않을 상황의 메세지를 서비스에서 받아서

/**
 * 수강 신청
 * - 수강 신청하려는 강좌가 없거나, 수강 신청을 이미 한 경우, 실패응답
 * - 수강 신청하려는 강좌id을 포함하여 수강 신청 정보 업데이트
 * @param parameter
 * @return
 */
@Override
public ServiceResult req(TakeCourseInput parameter) {

    ServiceResult result = new ServiceResult();

    Optional<Course> optionalCourse
            = courseRepository.findById(parameter.getCourseId());

    if (!optionalCourse.isPresent()) {
        result.setResult(false);
        result.setMessage("강좌 정보가 존재하지 않습니다.");
        return result;
    }

    Course course = optionalCourse.get();

    // 이미 신청 한 경우
    String[] statusList = {TakeCourseCode.STATUS_REQ, TakeCourseCode.STATUS_COMPLETE};

    long count = takeCourseRepository.countByCourseIdAndUserIdAndStatusIn(
            course.getId(), parameter.getUserId(), Arrays.asList(statusList)
    );

    if (count > 0) {
        result.setResult(false);
        result.setMessage("이미 신청한 강좌 정보가 존재합니다.");
        return result;
    }

    takeCourseRepository.save(
            TakeCourse.builder()
                    .courseId(course.getId())
                    .userId(parameter.getUserId())
                    .payPrice(course.getSalePrice())
                    .status(TakeCourseCode.STATUS_REQ)
                    .build()
    );

    result.setResult(true);
    result.setMessage("");
    return result;
}

- 컨트롤러에서 ResponseEntity의 body에 넣어주는 것이 가능하다. 

/* 수강 신청 */
@PostMapping("/api/course/req.api")
public ResponseEntity<?> courseReq(Model model,
                                   @RequestBody TakeCourseInput parameter,
                                   Principal principal) {

    parameter.setUserId(principal.getName()); // 로그인 Id 추가

    ServiceResult result = courseService.req(parameter); // 수강 신청

    if (!result.isResult()) {
        return ResponseEntity.ok().body(parameter);
    }

    return ResponseEntity.ok().body(parameter);
}

 

5. Response 의 Header와 Body를 Json으로 전달하기

- 4번에서 잠시 보여주었던 로직에 따르면, ServiceResult의 Message는 Body에 담겨 Text 형식으로 전달되게 된다.

- 이걸 아래와 같이 Header에 Json타입으로 변환하여 Client로 전달하고 싶다.

{
  "header" : {
    "result": true,
    "message": ""
  },
  "body"   : {}
}

- 그럴 때는 Header 객체를 만들고 이걸 다시 객체로 싸면 된다.

>> 예를 들어 아래의 경우, ResponseResultHeader를 ResponseResult 안에서 사용했다.

package com.example.fastlms.common.model;

import lombok.*;

@Getter
@Setter
public class ResponseResultHeader {

    private boolean result;
    private String message;

    public ResponseResultHeader(boolean result) {
        this.result = result;
    }

    public ResponseResultHeader(boolean result, String message) {
        this.result = result;
        this.message = message;
    }
}
package com.example.fastlms.common.model;

import lombok.*;

@Getter
@Setter
public class ResponseResult {

    private ResponseResultHeader header;
    private Object body;

    public ResponseResult(boolean result, String message) {
        header = new ResponseResultHeader(result, message);
    }

    public ResponseResult(boolean result) {
        header = new ResponseResultHeader(result);
    }
}

- 이 상태에서, 컨트롤러에서 객체타입을 ResponseEntity의 body에 담아 보내주면 위의 json 구조와 같이 데이터가 전송된다.

/* 수강 신청 */
@PostMapping("/api/course/req.api")
public ResponseEntity<?> courseReq(Model model,
                                   @RequestBody TakeCourseInput parameter,
                                   Principal principal) {

    parameter.setUserId(principal.getName()); // 로그인 Id 추가

    ServiceResult result = courseService.req(parameter); // 수강 신청

    if (!result.isResult()) {

        ResponseResult responseResult
                = new ResponseResult(false, result.getMessage());

        return ResponseEntity.ok().body(responseResult);
    }

    return ResponseEntity.ok().body(true);
}

- 앞서서 본 axios 를 통해 비동기식으로 보낸 요청에 대한 응답에 대해 마처 처리를 하면 아래와 같이 될 수 있다.

$(document).ready(function(){
    $('#submitForm').on('submit',function(){

        if (!confirm("수강신청을 하시겠습니까?")){
            return false;
        }

        let $thisForm = $(this);
        let url = '/api/course/req.api';
        let parameter = {
            courseId : $thisForm.find('input[name=id]').val()
        };
        // javascript의 promise 패턴
        // async (비동기 방식)
        axios.post(url, parameter).then(function(response){

            response.data = response.data || {}; // Data를 객체로 초기화
            response.data.header = response.data.header || {}; // Header도 객체로 초기화

            if (!response.data.header.result) {
                alert(response.data.header.message);
                return false;
            }

            // 정상적일 때
            alert('강좌가 정상적으로 신청되었습니다.');
            location.href = '/';

        }).catch(function(err){
            console.log(err);
        });

        return false;

    });
});

+ JSON 응답과 요청 처리에 대한 포스팅 글

https://owin2828.github.io/devlog/2019/12/30/spring-16.html

 

[Spring] JSON 응답과 요청 처리 - 낮코밤코

1. Jackson 의존 설정 Jackson은 자바 객체와 JSON 형식 문자열 간 변환을 처리하는 라이브러리로 다음과 같이 pom.xml에 의존을 추가 com.fasterxml.jackson.core jackson-databind 2.9.4 com.fasterxml.jackson.datatype jackson-d

owin2828.github.io

 

|  추가적으로 정리한 사항

1. MVC 모델에서 사용했던 Annotation 정리

ㄴ 자주 났던 오류 : Request를 받아올 때에, Setter를 지정해주지 않았더니 값이 넘어오지 않았다.

★ 항상 클라이언트에게 데이터를 객체에 싸서 전달 받을 때에는 Setter를 지정해주자.

ㄴ 이펙티브 자바에서 제안한 사용법 : 롬복을 쓰려거든 @Data 사용을 하기 보다는 필요한 것만 뽑아 쓰자. 

    (@Data에는, @EquslsAndHashCode, @toString 도 있기 때문에 불필요한 메소드가 생길 수 있다.)

  domain/model controller service dto entity repository
  Model.Request
Model.Response
Controller Service Dto Entity Respository
  @Getter
@Setter

* Setter없음 
값을 못 받는다
@Controller
@RestController
@GetMapping..
@RequestParam
@PathVariable
@RequestBody
@ResponseBody

* post 맵핑 시,
@RequestBody는
@RestController에서만 쓸 수 있다.
@Service
@Transactional
@Getter
@Setter
@Builder
@AllArgsConstructer
@NoArgsConstructer

** 제약조건
@NotNull
@NotBlank
@Min
@Max
@Column
@Entity
@EnttiyListener
@Id
@GeneratedValue
@CreatedAt
@LastModifiedAt
@Repository

 

2.  헥사고날 디자인 패턴의 아주 기초적인 개념 

- 레이어드 디자인 패턴은 계층 형태로 이루어져 있어, MVC 각각의 클래스간 결합도가 높은 편이지만,

- 헥사고날 디자인 패턴의 경우, 의존성을 역전시켜 결합도를 낮추어 준다.

- 패키징을 어떻게 하는 지에 대해서는 좀 더 공부를 해봐야 알 것 같다.

https://www.youtube.com/watch?v=MKfSLrwLex8 

 

더보기

https://happy-coding-day.tistory.com/entry/%ED%97%A5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98Hexagonal-Architecture-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EA%B8%B0-%EB%AF%B8%EC%99%84%EC%84%B1

 

헥사고날 아키텍처(Hexagonal Architecture) 코드로 이해 해보기

EventsJdbcEntityRecordPublishedEventService 이 부분은 '만들면서 배우는 클린 아키텍쳐' 의 책을 보고 배운점과 느낌점을 설명합니다. 헥사고날 아키텍쳐란 무엇인까요? 헥사고날 아키텍쳐는 레이어드 아

happy-coding-day.tistory.com

https://www.jiniaslog.co.kr/article/view?articleId=1152 

 

[project] 기존 프로젝트 헥사고날 아키텍처로 리아키텍쳐링하기 - Jinia's LOG'

Goal 클린 아키텍쳐, 헥사고날 아키텍쳐에 대한 간략 이해 기존 프로젝트 아키텍쳐 분석 헥사고날 아키텍쳐로 리아키텍쳐링 Architecture Clean Architecture 로버트마틴의 클린아키텍쳐에서 저자는 소프

www.jiniaslog.co.kr

 

 

|  정리

- @Controller의 경우 주로 View를 반환하기 위해 사용하며,
  @RestController의 경우 주로 RestAPI에서 JSON타입의 데이터를 반환할 때 사용한다.

- Ajax를 통해 비동기처리 방식으로 JSON타입의 데이터를 전달할 수 있는데, 제이쿼리를 쓰거나 axios를 쓸 수 있다.

- Request parameter 데이터 타입 매핑
  - 일반 Form으로 전달 받은 x-www-form-urlencoded 타입은 일반 객체로 Mapping할수 있으며
  - ajax를 통해 전달 받은 Json 타입의 경우 @RequestBody를 사용해야 한다.

- @Controller를 통해서도 Data를 전달할 수 있는데 이때는 ResponseEntity를 통해 Response값을 싸주는 게 필요하다.
  ㄴ 추가적으로 @ResponseBody를 통해 body에 넣어 보내줄 수도 있다.

- @RestController는 @Controller에 @ResponseBody가 묶인 형태이며, Data를 전달 하는 방식은 @Controller와 동일하다.

- ResponseEntity를 통해 데이터를 보내게 되면 Status를 시각적으로 확인할 수 있다.
  다만, 이 방식 보다 @ResponseStatus(value = HttpStatus.OK) 를 쓰는게 더 가독성이 높을 때도 있으니 유념하자.

- Spring Security의 Principal 인터페이스는 Authentication의 상위 인터페이스로 사용자 정보를 담고 있다.
  getName() 메소드를 통해 사용자 아이디를 받을 수 있으나, 세션이 만료되면 null값이 올 수 있으므로 주의가 필요하다.

- 비즈니스 로직에 대한 validation 처리를 할 때 항상 Exception으로 처리를 하는게 능사는 아니다.
  지금과 같이 Client단에서 [수강신청]을 비동기처리를 할 때에 객체로 데이터를 묶어 사용자에게 상황을 안내하는게 더 도움이 될 수 있다. 
  ** Response를 객체로 묶어 ResponseEntity.ok().body()에 넣어주면 JSON으로 자동 변환되어 전달 된다.

 

[ 참고 및 출처 ]

부트캠프 수업 내용 정리

@Controller와 @RestController의 차이 

https://mangkyu.tistory.com/49

https://yeonyeon.tistory.com/257

스프링 Securiy로그인 Principal

https://codevang.tistory.com/273

 

'Framework > 프로젝트로 스프링 이해하기' 카테고리의 다른 글

[LMS 만들기] 회원 탈퇴, 강좌 관리, 파일 업로드  (0) 2022.10.10
[LMS 만들기] 회원정보 수정 - 우편번호 찾기, ajax와 Rest API  (0) 2022.10.10
[LSM 만들기] 강좌 목록 구현하기 - 스마트에디터, 등록/수정 동시 처리  (0) 2022.10.07
[LMS 만들기] 카테고리 수정, 삭제, 정렬  (0) 2022.10.06
[LMS만들기] 회원 상태 변경 및 비밀번호 초기화  (0) 2022.10.05
    'Framework/프로젝트로 스프링 이해하기' 카테고리의 다른 글
    • [LMS 만들기] 회원 탈퇴, 강좌 관리, 파일 업로드
    • [LMS 만들기] 회원정보 수정 - 우편번호 찾기, ajax와 Rest API
    • [LSM 만들기] 강좌 목록 구현하기 - 스마트에디터, 등록/수정 동시 처리
    • [LMS 만들기] 카테고리 수정, 삭제, 정렬
    simDev1234
    simDev1234
    TIL용 블로그. * 저작권 이슈가 있는 부분이 있다면 댓글 부탁드립니다.

    티스토리툴바