반응형

 

gradle을 이용해서 validation jar파일을 의존성 추가 해준다.

 

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-devtools'
    implementation 'org.springframework.boot:spring-boot-starter-validation:2.6.6'

 

 

implementation 'org.springframework.boot:spring-boot-starter-validation:2.6.6'

 

 


Parameter validation

클래스에 @Validated를 추가하고, 매개변수에 @Size를 지정하여 유효성 검증을 할 수 있다.

 

@RestController
@RequestMapping("/api")
@Validated
public class GetController {
    
    @GetMapping("/get")
    public String val(@Size(max = 5) @RequestParam String name){
        return name;
    }

 

 

 

DTO validation

 

dto model 객체에 validation 어노테이션을 이용해 유효성 검증을 진행할 객체 속성에 추가해준다.

예외가 발생했을 경우 message를 이용해서 사용자에게 검증 실패를 알릴 수 있다.

 

@Getter
@Setter
@ToString
@NoArgsConstructor
public class Dto {

    @Size(min = 2, max = 10, message = "이름은 2~10자 여야 합니다.")
    private String name;
    @Email(message = "이메일 형식이 맞지 않습니다.")
    private String Email;


}

 

 


 

validation.BindingResult 객체를 이용해서 dto model에 적용한 유효성 검증이 알맞게 들어왔는지

인터셉터처럼 동작해서 확인해줄 수 있다.

 

hasErrors()를 이용해서 에러가 발견된다면 모든 에러 메세지를 StringBuilder에 추가한 후,

ResponseEntity 를 이용해 결과를 반환하게끔 했다.

 

BindingResult는 AOP처리해줄 수 있다.

 

@RestController
@RequestMapping("/val")
public class VController {

    @PostMapping("/dto")
    public ResponseEntity createDto(@Valid @RequestBody Dto dto, BindingResult bindingResult){
        StringBuilder sb = new StringBuilder();

        if(bindingResult.hasErrors()){
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            allErrors.forEach((error) -> {
                sb.append(error.getDefaultMessage()).append(" ");
            });
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(sb.toString());
        }

        System.out.println(dto);
        return ResponseEntity.status(HttpStatus.CREATED).body(dto);
    }

 

 


 

@AssertTrue

직접 유효성검증 함수를 만들어줄 수 있다.

true면 정상적으로 동작하고, false면 HTTPSTATUS.400 error가 발생한다.

 

@AssertTrue(message = "이름은 6자 이하여야 합니다.")
public boolean isNameValidation(){
    if(this.name.length() >= 7)
        return false;
    return true;
}

 

 


 

@RestControllerAdvice

Controller일 경우 @ControllerAdvice

Global하게 지정할 경우 클래스에 어노테이션 추가

Global로 동작하지 않게 basePackage, baseClass 지정가능

 

@ExceptionHandler 

value에 지정된 예외가 발생하면 해당 메서드가 동작한다.

매개변수로 예외를 조작할 수 있다.

컨트롤러에 메서드를 만들어서 해당 컨트롤러에서만 동작하도록 할 수 있다.

 

@RestControllerAdvice
public class GlobalAdvice {

    @ExceptionHandler(value = Exception.class) // 예외 잡기
    public ResponseEntity ex(Exception e){
        e.printStackTrace();

        return ResponseEntity.status(HttpStatus.OK).body("예외잡기");
    }
}

 

 

 

 

예외를 일부러 발생시켰다.

@RestController
@RequestMapping("/adv")
public class AController {

    @GetMapping("/get")
    public String get() throws Exception{
        throw new Exception();
    }
}

 

 

 

예외가 글로벌Advice, @ExceptionHandler에 의해 처리되고, Body에 지정한 값을 응답하며 정상 동작한다.

 

 

 


 

반응형
반응형

 

배열을 컬렉션 프레임워크로 변환

 

 

방법 1

 

int[] arr = {1,2,3,4,5};
List<Integer> list1 = new ArrayList<>();

for(int i = 0 ; i<arr.length; i++)
    list1.add(arr[i]);

 


 

방법 2

 

int[] arr = {1,2,3,4,5};
List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList());

int 기본형 배열은 boxed()가 필요하다.

 

 

String[] strArr = {"K", "O", "M"};
List<String> strList = Arrays.stream(strArr).collect(Collectors.toList());

일반적인 객체 배열은 boxed()가 필요하지 않다.

 

 

IntStream is = IntStream.of(1,2,3,4);
List<Integer> list2 = is.collect(Collectors.toList()); // 컴파일에러

그 이유는 Arrays.stream()을 이용해서 기본형 배열을 스트림으로 바꾸게 되면, 타입이 IntStream이 된다.

IntStream을 다시 Stream<T>와 같은 형태로 만들기 위해 boxed()를 사용한다.

 


 

컬렉션 프레임워크를 배열로 변환

 

방법 1

 

List<Integer> list = Arrays.asList(1,2,3,4,5);
int[] ar = new int[list.size()];

for(int i = 0; i<list.size(); i++)
    ar[i] = list.get(i);

 

방법 2

 

List<Integer> list = Arrays.asList(1,2,3,4,5);
int[] arr = list.stream().mapToInt(e->e).toArray();

배열을 컬렉션 프레임워크로 바꿀때와 마찬가지로 Stream<T>를 IntStream으로 변환 후 toArray()

 

 

 

 

 


 

반응형
반응형

 

 

이전에 진행했던 장고 프로젝트를 재가동 시키기로 했다.

 

 


 

(2003, "can't connect to mysql server on 'localhost' ([winerror 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다)")

 

 

정말 간단한 오류였는데 프로퍼티 키값을 대문자로해야 오류가 나지 않는다.

 

 

 


 

 

 

장고는 데이터베이스 스키마를 소스파일로 작성하여

연동된 데이터베이스에 테이블을 만들어 줄 수 있다.

또한 작성된 migration 파일은 버전관리가 가능하다.

 

 

오랜만에 실행하면 적용되지 않은 migrations가 있다고 알려준다.

 

 

python manage.py makemigrations

- models.py에 변경 사항이 있다면 마이그레이션 파일을 만들어 준다.

 

python manage.py migrate

- 데이터베이스에 테이블을 마이그레이션 파일을 보고 맞게 생성해준다.

 

 

 


 

 

 

python manage.py runserver

-서버 실행

 

 

이전에 만들어둔 웹이 잘 보인다.

 


 

반응형
반응형

 

쿠키와 세션 - 서버에서 정보 자체를 갖고있다. 쿠키의 세션ID 매칭을 확인

JWT - 서버에서 정보의 위조만 검사한다.

 

 


쿠키와 세션

 

로그인 유지를 위해선 세션과 쿠키를 활용해야 한다.

하지만 HTTP는 기본적으로 stateless하다.

 

Stateful

채팅과 같이 지속적으로 연결되어있는 상태를 stateful (지속적 연결)이라고 하고,

HTTP에서 이러한 stateful과 같은 효과를 주는 것이 쿠키와 세션

 


 

기존에는 쿠키만 사용했는데, 로그아웃을 안 하면 브라우저가 종료돼도 남아있기 때문에 문제가 되었다.

세션을 도입하여 브라우저가 종료되면 자동 로그아웃되게 했다. (기간 지정 가능)

 

서버에 중요 데이터를 보관하는 것이 보안적으로도 우수

 


 

{세션 ID : Session}으로 서버에 저장하고, 쿠키에 세션 ID를 담아서 브라우저에게 응답

브라우저는 다음부터 쿠키와 함께 서버에 요청.

서버는 쿠키를 열어보고 세션 ID를 확인, 매칭 되는 Session을 사용한다.

 

 

톰캣의 SESSION 구조

{ 세션Id : {key : value} }

한번 더 감싸 져 있기 때문에 key:value는 여러 개가 가능하다. 여러 값을 저장할 수 있다.

 

 

JSESSION은 쿠키 이름 (세션 ID를 갖고 있다.)

 

세션id KEY VALUE
BK291823LAK USERNAME CHA
92J1K20D892 USERNAME PARK
FKDJ209DJF3 USERNAME SOO
F92JD93H1KD USERNAME MYOUNG

서버상에서는 세션 ID로 이미 분리된 상태이기 때문에, KEY가 똑같아도 전부 구분된다.

 

 


 

JWT

 

 Http 헤더에 JSON 토큰을 넣은 후 서버는 헤더에 포함되어 있는 JWT 정보를 통해 인증.

서버는 유저 정보를 갖고 있지 않고, 비밀키만 갖고 있기 때문에 부담이 적다. (stateless)

 

 

세 부분으로 구성

(1) Header

토큰 유형: JWT

암호방식: (HS256 또는 RSA) 

 

(2) Payload   (Map 형태)

유저 정보,

만료 시간

 

(3) Signature

해시알고리즘(

Header +

Payload +

Secret key

) = 시그니처 값

 

 

주의점

기본적으로 토큰은 공개되어지면 안된다. (HTTPS)

토큰으로 악용할 가능성이 있다.

 

JWT의 중점은 위변조 관점, 탈취될 가능성이 있으므로 시간 제한이 있는 Access Token 사용

(1)Header, (2)Payload 는 암호화가 아니다. (Base 64 encoder)

인가에 사용되는 부분은 (3)Signature

 

HTTPS덕에 정보를 보긴 어렵다.

그래도 중요한 정보는 저장하지 말자. (비밀번호가 있다면 토큰이 만료되도 접근할 가능성 존재)

 

 

 

인증 방식

Header + Payload + SecretKey가 (3) Signature이 나오나 확인한다.

 

인증 흐름

(1) + (2) + Secret key 이용해 해시 암호화해서 Signature를 만들고, 유저에게 전달한다.

 

토큰이 다시 왔을 때는 (1) + (2)와 서버의 Secret Key로 Signature를 만들어 본다.

유저가 보내온 Signature 값과 같은지 확인한다. 

일치한다면 서버에서 만들어낸 토큰이고, 위조가 없는 것이니 사용한다.

 

 

 - 유저는 Secret Key를 모르기 때문에 Payload를 위조해도 서버와 일치하는 Signature를 만들 수 없다.

 - JWT의 인가 과정은 서버에서 만든 토큰이 맞는지, 데이터가 위조되지 않았는지를 확인하는 것이다.

 

 


 

RSA를 사용하면,

Header + Payload를 개인 키로 암호화하여 Signature를 생성, 토큰을 유저에게 준다.

유저가 다시 서버에게 토큰을 주면, 서버는 공개 키로 Signature를 복호화를 한다.

 

복호화된 Signature(Header + Payload)를 전달받은 Header, Payload와 일치하는지 확인한다.

두 값이 일치하면 서버의 개인 키로 서명한 위조가 안된 토큰이 맞다.

 

 - 유저는 개인 키를 모르므로 Header, Payload가 일치하는 암호화 Signature를 만들 수 없다.

 


 

Refresh Token, Access Token

로그인 시 JWT 토큰을 두개를 생성해서 사용자에게 주는 방식EXP 기간이 다르기 때문에 해시 값은 완전히 다르다.

 

기존에는 Access Token만 사용

 - 토큰은 유효 시간이 짧으면 재 로그인으로 불편하고, 유효 시간이 길면 탈취의 위험이 있다.

 - 토큰 요청시마다 새로 발급하면, 서버에 부담을 준다.

 

Refresh Token을 추가로 사용

 - Refresh Token은 유효기간이 길다. Access Token 만료 시 새로운 Access Token의 발급으로 사용된다.

 - 클라이언트는 브라우저 스토리지에 저장한다.

 

 

서버에서는 Access Token이 만료된 것이 확인되면, 

Refresh Token을 달라고 응답을 내려준다.

 

클라이언트는 Access Token이 만료되면 Refresh Token도 같이 줘야 한다.

두개의 Token을 전부 갖고 있음을 증명해야하기 때문.

Refresh Token이 만료되지 않았다면, Access Token을 재발급해준다.

둘다 만료라면 재로그인 필요.

 

 

 

 

 

 


Spring Security

 

JWT 

권한 관리가 필요없다면 SecurityContext에 Authentication 객체를 안넣어도 된다.

Security Context에 Authentication을 넣는 이유는, 스프링 시큐리티의 권한 관리 사용을 위해

 

인증 시 DB 접근 -> 토큰 생성 -> 토큰 반환

인가 시 토큰 검증 -> 토큰 Signature 정상 -> Security Context에 Authentication 객체 담는다.

 

 

세션

세션 안에 있는 Security Context에서 꺼내와서 SecurityContextHolder에 담는다.

 

 

 

https://www.inflearn.com/questions/558844

 

jwt 토큰방식에서의 세션 미사용 질문 - 인프런 | 질문 & 답변

강의에서 jwt방식을 예로 드셨던 세션정책Stateless에서 세션을 사용하지 않는다고 말씀하셨습니다. 궁금한 점은  jwt토큰방식을 사용하더라도 세션은 사용하는 것이 아닐까라는 의문입니다.  이

www.inflearn.com

 

반응형

'프로그래밍 > Security' 카테고리의 다른 글

웹 요청 정리  (0) 2022.10.20
[쿠키] Javascript로 쿠키에 직접 접근, HttpOnly, CORS  (0) 2022.10.19
[Web 보안] XSS, CSRF  (0) 2022.08.25
[Spring Security] 로그인 구현  (0) 2022.08.24
반응형

빌드 도구 gradle 사용

 

공통사항

 

application.properties - DB연결

spring.datasource.url=jdbc:mysql://localhost:3307/스키마명?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul
spring.datasource.username=유저명
spring.datasource.password=유저비밀번호

 

build.gradle - mysql 종속성 추가

dependencies {
    implementation 'mysql:mysql-connector-java:8.0.29'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'

 


 

JPA 연동

 

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 


 

myBatis 연동

 

build.gradle

implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'

 

application.properties - mapper 패키지 추가 // db snake_case와 java camelCase 매핑 

mybatis.type-aliases-package = com.example.project.mapper
mybatis.configuration.map-underscore-to-camel-case=true

 

반응형
반응형

JPA 관계 설정

자바 관점에서 생각하지 말고, RDB 관점에서도 생각해야한다.

 

JPA에서는 연관 관계의 주인이라는 개념이 있다.

연관 관계의 주인이란 외래 키를 관리하는 주체이다.

 

 

관점의 불일치

(1)

RDB에서는 한 곳에서 외래키를 갖고 있는데 자바에서는 양방향 매핑시 양쪽 모두 서로의 Entity를 갖고 있음

RDB에서는 해당 필드(칼럼)이 없는데 자바에는 존재

관점이 다르기 때문에 발생하는 문제로 RDB 외래키를 관리해주는 Entity를 누구로 할지 결정해야한다.

 

(2)

자바는 단방향 매핑이면 참조타입이 없는 한쪽에서 다른쪽으로 접근하는 것이 불가능,

RDB는 외래키를 이용해서 조인하면 서로 접근이 가능

 

 

 

Q&A

무조건 외래 키가 있는 쪽이 연관 관계의 주인 ?

 - 대부분의 경우엔 그렇다. 하지만 외래키가 없는 Entity가 관계의 주인이 될 수 있다.

 

외래키가 없는 곳을 굳이 연관 관계의 주인으로 지정하는 이유?

 - 편하다. 외래 키가 없음에도 연관 관계 CRUD가 완벽 동작

 

원래 외래키가 있는 곳에서의 CRUD는?

 - 양방향 매핑이라면 전부 정상 동작한다. 외래 키가 없는 곳에서 CRUD를 하기 위함이다.

 

단점은 없는지?

 - 외래 키 설정을 위한 추가 update 쿼리가 동작한다. (성능 저하)

 

mappedBy로 외래 키가 있는 반대편을 지정하는 이유?

 - 코드 가독성 측면에서 수많은 Entity를 볼 때 양방향 매핑이 돼있음을 알림,

   JPA에게 반대편 Entity가 주인임을 알림(매핑 테이블 생성과 검색을 방지),

   외래 키가 있는 곳에서만 외래 키를 관리하여 복잡도를 줄이기 위함

 

 


 

✔ 단방향 관계

한쪽만 매핑 어노테이션을 설정한 것

@ManyToOne, OneToMany 알맞게 설정하고

@JoinColumn으로 외래키를 갖고 있는 쪽을 가르킨다.

 

 

(1) 다대일 

RDB와 JAVA 관점을 일치시키는 방식.

RDB 외래 키가 있는 곳을 연관 관계의 주인으로 설정

 

(2) 일대다 

RBD와 JAVA 관점이 불일치되는 방식.

외래 키가 없는 곳에만 매핑 어노테이션 설정

외래키가 있는 Entity에는 매핑 어노테이션을 설정하지 않는다. 단순히 long boardId

RDB 외래키가 없는 곳을 연관 관계의 주인으로 설정

 

다쪽에 있는 외래 키를 수정하기 위해 추가적인 update쿼리가 동작한다.

이 방식보다는 차라리 양방향 매핑을 사용하므로, 많이 사용하는 방식은 아니다.

 

 

 

* 양방향에서의 관계 주인 (CRUD의 가능 여부)

✔ 양방향 관계

양쪽이 매핑 어노테이션을 설정한 것

모든 테이블을 양방향 관계로 설정하면 복잡성이 증가하므로 필요할 때만 양방향 관계 지정

 

사실 단방향 만으로도 Entity 간의 관계 설정은 완료된 것이다.

(RDB는 한쪽만 외래키를 갖는 식으로 완벽히 동작해왔다.)

 

 

 

✔ 외래 키가 없는 Entity에서 반대쪽 Entity의 외래 키를 인식하게 하는 방법은 두 가지

(1) mappedBy 

외래 키는 반대쪽 Entity가 갖고 있음을 알려준다.

 

mappedBy가 없으면?

RDB에서 외래 키가 없는 테이블에 외래 키를 생성하게 된다. (조회 필드를 외래 키로 잘못 인식해버린다.)

JPA가 양방향인 것을 모르고 해당 필드가 외래키로 인식되어 매핑 테이블을 생성하고, 그곳에서 관계 정보를 찾으려고 한다.

JPA가 만든 양쪽 Entity id를 갖는 매핑 테이블에는 당연히 아무런 정보가 들어가 있지 않아 찾지 못한다.

 

 

 

(2) JoinColumn 

반대쪽 Entity를 가리키면 연관 관계의 주인을 외래 키가 없는 곳으로 설정

이 방식을 권장하지 않는 이유는 추가적인 쿼리를 소모하게 된다.

두 Entity의 데이터를 새로 추가시킨다고 했을 때 외래키가 없는 Entity로 save()하면

 

Entity1 insert ---> Entity2 insert ---> 외래키가 있는 Entity를 update

 

insert 만으로 끝날 동작을, 반대편 Entity 외래 키를 update 하며 추가적인 쿼리가 소모된다.

성능을 희생하고, 편의를 증가

두 Entity 모두 CRUD가 가능해진다.

 


 

양방향 매핑 두 가지 방법

 

(1) JoinColumn(외래 키가 있는 곳에 설정), mappedBy(외래키가 없는 곳에 설정)

       -> mappedBy가 있는 Entity는 외래키를 관리하지 않는다.

 

연관 관계의 주인을 외래키가 있는 곳으로 설정, 권장되는 방식

외래키가 존재하는 Entity에서 연관 Create, Select, Update, Delete가 가능

mappedBy 쪽에서도 cascade 설정하면 Insert 빼고는 다 전이된다.

 

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
}         

 

 

 

 

(2) JoinColumn(외래 키가 없는 곳에 설정)

 

연관 관계의 주인을 외래키가 없는 곳으로 설정, 편의를 위해 성능을 희생시키는 방식

양쪽 모두 Create, Select, Update, Delete가 가능 (복잡도 증가)

 

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}

 

양방향 매핑 시에는 순환 참조, 순환 삭제 등 신경 써줘야 할 사항들이 생겨 복잡도가 증가한다.

그래서 mappedBy로 조회만 하고, 나머지는 연관 관계의 주인으로 처리하는 방식이 권장된다.

 

https://docs.jboss.org/hibernate/stable/annotations/reference/en/html/entity.html#entity-mapping-association

 

Chapter 2. Mapping Entities

In Section 2.4.5.1, “Lazy options and fetching modes” we have seen how to affect the fetching strategy for associated objects using the @Fetch annotation. An alternative approach is a so called fetch profile. A fetch profile is a named configuration a

docs.jboss.org

 

 


Cascade

연관관계 Entity에 대한 영속성 전이 설정

dafault는 아무것도 설정되어있지 않다.

 

persist - 연관 Entity를 영속성 콘텍스트에 manage 되게 전이시킨다.

merge - 연관 Entity를 update 할 수 있게 한다.

detach - 연관 Entity를 영속성 콘텍스트에서 관리하지 않는다.

remove - 연관 Entity를 함께 제거한다.

 

 

 

orphanRemoval

연관 Entity만 삭제하고 싶을 때 사용한다.

 

set...(null)을 할 경우 외래키가 없는 것으로 update 된다.

이때 자동으로 연관 설정이 없는 Entity를 DB에서 제거하고자 할 때 사용한다. 

(cascade는 set..(null)로 동작하지 않는다.)

 

 


Entity를 영속성 컨텍스트에서 사용할 때 연관 Entity 설정 (fetch type)

 

LAZY

가장 먼저 lazy를 사용하기 위해선 Entity가 영속성 컨테이너에 관리되어지고 있어야 한다.

연관 관계 Entity가 필요할 때만 get()으로 연관 Entity를 select를 통해 나중에 가져온다.

 

EAGER

연관 관계 Entity를 영속성 컨테이너로 전부 즉시 가져온다.

 

LAZY가 보편적 권장 사항

 

 

 

 

✔ N+1 

1대 N 관계에서 1인 Entity를 findAll()했을 경우 Many Entity와 조인해서 한번에 가져올 것을 기대한다.

 

하지만, 실제 동작은 1인 Entity를 먼저 select 한 후, 1인 Entity의 id를 이용해서

매번 from N where Entity_id = id(1인 Entity의 id값)로 N 테이블에서 여러 번의 select로 id값을 확인한다.

 

[1회] select로 id 개수 확인 ---> [N회] id 개수만큼 select 동작

 

 

여러 번의 select보다 조인을 사용하고 싶을 때

1.  jpql - join fetch 이용

(join과의 차이: 연관 Entity도 한번에 가져온다)

2.  @EntityGraph에서 join 할 Entity 지정

(중복 데이터가 있을 수 있다. distinct사용)

 

 

 

 

✔ sql select의 실행 횟수 vs 조인된 데이터의 양 

두 가지를 고려하여 어떤 것이 더 효율적인가를 고려해야 한다.

 

sql select의 실행 횟수가 너무 많다면, DB를 반복적으로 접근해서 IO가 발생, 느려진다. -> 조인을 하는 선택을 한다.

가져온 데이터의 양이 너무 과하면 -> sql select를 필요한 것만 수행, 연관 Entity 정보만 가져오는 것을 선택한다.

 


참고

 

https://catsbi.oopy.io/f3204fd9-954c-44d7-ab18-2ca56000c5e5

 

다양한 연관관계 매핑

연관관계 매핑시 고려사항 3가지

catsbi.oopy.io

 

반응형
반응형

WAS

톰캣이다.

톰캣은 Java servlet을 관리한다.

 

톰캣외에도 WAS가 있지만 톰캣을 가장 많이 사용

 

 

역할

통신지원

멀티 스레드 관리

서블릿 생명주기 관리

세션 관리

 

동작방식

Request, Response 객체 생성 -> servlet에게 전달 /스프링/ spring dispatcher servlet 

 

 

 

용어 정리

 

✔ Socket

IP + Port

HTTP도 Socket을 사용함, Web Socket 통신이 아닐뿐임

 

✔ HTTP 통신

요청에 대한 응답을 주고 바로 통신을 해제

TCP 위에서 보편적으로 사용, UDP 위에서도 사용가능함 (Youtube QUIC)

 

✔ Web Socket 통신

HTTP와 달리 통신을 유지하며 실시간 통신 지원

처음 요청시 HTTP Get 으로 Upgrade:web socket 헤더를 추가하여 보냄

 

 

 

 Web Server

HTTP, HTTPS 통신을 위해 Server는 (IP + 80번, 443번)으로 socket 생성

정적인 데이터를 응답

 

✔ Web Container == Servlet Container

웹 컨테이너는  자바 서블릿과 상호작용한다.

동적인 처리를 위한 Servlet들이 모인 Container, JAVA에서는 동적 처리로 Servlet을 이용한다.

클라이언트의 URL에 맞는 Servlet을 매핑

(스프링에서는 Dispatcher Servlet 하나의 Servlet으로 URL에 맞는 Controller를 Handler Mapping)

 

  • 서블릿 객체의 생명주기 담당
  • 싱글톤 패턴으로 관리
  • 멀티 스레딩 지원

 

 

✔ Web Container Server == Web Application Server(WAS)

Web Container + Web Server

요청에 대한 동적인 데이터를 처리하여 응답

WAS에도 Web Server가 존재하지만 보안, 로드밸런싱, 정적데이터 처리를 위해 Web Server를 앞단에 하나 더 둠

 

 

 

 

 


JSP 라이프 사이클

 

  1.  클라이언트가 URI로 JSP 파일을 서버에 요청
  2.  톰캣이 서버에 해당 JSP 파일이 있다면 JSP파일 -> JAVA파일 -> JAVA파일 컴파일 -> 서버 메모리에 로드
  3.  init() 메서드 호출 -> service() 메서드 호출 -> HTTP response 로 응답

 

(jsp)자바파일의 변경이 없다면 -> 기존 메모리에 있는 instance service()로 바로 응답

(jsp)자바파일의 변경이 있다면 -> 기존 instance destroy() 메모리 해제 -> instance 새로 생성 -> init() -> service()

 

톰캣이 jsp 파일을 servlet instance로 만들어서 요청에대한 응답을 한다.

 


 

작성한 jsp 파일

 

 


 

톰캣이 생성한 jsp 파일.java

service() 메서드 일부

 

 

톰캣이 작성된 jsp 파일을 java class(servlet)으로 변환하고, service()메서드를 통해 요청을 처리한 후 응답한다.

 


Servlet

 

HTTP 의 request 를 처리하고 response를 돌려주는 동적인 처리를 위한 자바 기술

 

톰캣이 jsp를 class로 만들고 이 class는 요청을 처리하므로 사실 jsp는 servlet이다.

 - jsp보다 servlet이 먼저 등장했으며, servlet에서의 html 처리가 불편하여 jsp가 등장

 - jsp는 서버에서 데이터를 렌더링하여 html을 만들어서 전달하므로 Server Side Rendering(SSR)이라 한다.

 - 클라이언트에서 데이터를 받아 처리하는 것은 Client Side Rendering(CSR) - Vue, React

 

jsp를 이용하지 않고 직접 servlet에 HTML을 작성할 수도 있다.

 - 직접 작성한 servlet을 이용하게 되면 클라이언트의 요청을 jsp를 거치지 않고 servlet이 직접 받을 수 있다.

 

jsp파일은 view에 집중할 수 있게 되고,

작성한 servlet은 요청에 대한 로직을 처리하는 controller 역할만을 하게되어

jsp파일 내에서 혼합되어 처리되던 html코드와 자바 로직 코드 부분을 분리시켜 MVC 패턴을 적용시킬 수 있다.

 


 

Dispatcher Servlet

 

URL를 통해 서버에 요청이 오면 매핑된 servlet이 해당 요청을 받아준다.

이러한 요청에 맞는 servlet들을 일일이 작성해주어야 했는데 이 부분을 한곳에서 관리하고자 front controller라는 개념이 등장했다.

front controller는 한곳에서 URL과 controller를 매핑해주며, 요청에 위임할 controller에게 다시 요청한다.

spring framework은 dispatcher servlet으로 MVC패턴을 쉽게 구현할 수 있게 틀을 제공해준다.

 


 

클라이언트가 서버에게 HTTP 요청을 하면

 

요청을 받게되면 Servlet Container가 HttpServletReqeust, HttpServletResponse 객체 생성

Dispatcher Servlet으로 해당 URL에 맞는 Controller를 찾아서 비즈니스 로직 수행

이때 Controller에서 HttpServletReqeust의 값을 이용하여 로직 처리

View Resolver로 해당 view를 찾아 HttpServletReqeust, HttpServletResponse에 결과를 View에 렌더링한 후 HTTP header, body 응답

 

 

 

 

 


참고

 

https://www.theserverside.com/feature/Understanding-How-the-Application-Servers-Web-Container-Works

 

Understanding How the Application Server's Web Container Works

To understand how to do proper application server development, you need to understand how the two basic containers of an application server work, namely the Web container and the EJB container. This article will take a look at how a client application inte

www.theserverside.com

 

반응형

'프로그래밍 > Servlet \ JSP' 카테고리의 다른 글

서블릿/JSP  (0) 2022.07.19
반응형

 

https://www.amitph.com/spring-entity-to-dto/

 

Convert Entity To DTO In Spring REST API

A tutorial on How to Entity to DTO Conversion and DTO to Entity Conversion in a Spring REST API manually as well as by using Model Mapper.

www.amitph.com

 

변환하는 방법을 소개하는 좋은 글이 있어서 남긴다.

 

  • 생성자를 사용하는 방법
  • 메서드를 사용하는 방법
  • ModelMapper를 사용하는 방법
  • MapStruct를 사용하는 방법

 

메서드를 사용하는 방법이 직관적

만약 변경해야할 필드가 많다면 ModelMapper, MapStruct를 사용하는 것이 용이할 것 같다.

 


MapStruct 사용

 

 

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'

 

gradle에서 mapstruct가 lombok과 이슈를 일으킨다.

lombok이 mapstruct보다 위에 있으면 해결된다.

 

 

 

 

@Mapper
public interface UserMapper {

    UserMapper mapper = Mappers.getMapper(UserMapper.class);

    // dto -> entity
    @Mapping(target = "userAge", source = "age")
    @Mapping(target = "name" , source = "name", defaultValue = "Park")
    UserEntity userDtoToEntity(UserDto userDto);


    @Mapping(target = "age", source = "userAge")
    UserDto userEntityToDto(UserEntity userEntity);

}

@Mapper 

구현체는 mapstruct가 만들어준다.

import할 때 Mybatis의 @Mapper와 구분

 

@Mapping

필드명 불일치를 해결할 수 있다.

defaultValue도 설정할 수 있다.

 

 

 

 

 

@Test
void UserDtoToEntity(){
    UserDto userDto = new UserDto(1, "cha", "email", 30);
    UserEntity userEntity = UserMapper.mapper.userDtoToEntity(userDto);

    System.out.println(userEntity);
}

 

 

 


참고

 

 

 

https://mapstruct.org/documentation/stable/reference/html/

 

MapStruct 1.5.2.Final Reference Guide

If set to true, MapStruct in which MapStruct logs its major decisions. Note, at the moment of writing in Maven, also showWarnings needs to be added due to a problem in the maven-compiler-plugin configuration.

mapstruct.org

 

 

http://modelmapper.org/getting-started/

 

ModelMapper - Getting Started

Getting Started This section will guide you through the setup and basic usage of ModelMapper. Setting Up If you’re a Maven user just add the modelmapper library as a dependency: org.modelmapper modelmapper 3.0.0 Otherwise you can download the latest Mode

modelmapper.org

 

 

https://blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=kbh3983&logNo=220988245343 

 

[JAVA] ModelMapper Customizing

주의!! 일부 버전에서 Custom Mapping 이 됬다 안됬다하는 버그가 있음 아오....하루종일을 날렸네........

blog.naver.com

 


 

반응형
반응형

IoC 컨테이너 - 객체가 자신이 의존할 객체를 직접 생성하지 않는다. 사용할 객체의 생성과 바인딩을 외부에 넘긴다.

의존할 객체의 결정권을 객체에 두지 않고, 스프링 프레임워크에 둔다.

 

DI - 두 클래스가 의존 관계에 있을 때 인터페이스를 사이에 두고 다형성을 통해 필요한 구현체(bean)를 외부에서 주입시킨다.

(스프링 IoC 컨테이너에 있는 Bean을 주입받는다.)

 


 

IOC 컨테이너에 클래스를 등록하여 관리하고 싶을 때 Bean을 생성하는 방식은 두 가지가 있다.

(어노테이션 이용)

  • @Bean을 사용하는 방법
  • @Component를 사용하는 방법

 

@Component

@Component는 클래스에 지정한다.

Component Scan에 의해 해당 클래스가 bean으로 생성되어 IoC 컨테이너에 등록된다.

DI를 클래스 내부에서 @Autowired를 사용해서 setter주입, 생성자 주입, field주입 등으로 의존 관계를 만들어주어야 한다.

 

 

@Bean 

@Bean은 @Configuration클래스의 메서드에 지정한다.

수동으로 bean을 생성하는 방식이다. 

직접 설계한 클래스가 아니면 @Component 지정이 불가능하기 때문에 외부 라이브러리 클래스를 bean으로 등록할 때 사용한다.

 


 

반응형
반응형

 


 

메서드 참조란 말 그대로 다른 클래스의 메소드를 참조하여 사용한다입니다.

구현부를 빌려쓴다고 이해하시면 됩니다.

 

 

함수형 인터페이스가 클래스의 메서드 구현부를 빌려서 자신이 구현한 메서드인 것처럼 사용합니다.

 

 

람다식같은 경우는 직접 개발자가 구현부를 작성하는 것이고,

메서드 참조는 클래스를 빌려 쓰는 것입니다.

 

 

 

 

 

 

 

 

코드로 설명하겠습니다.

 


 

public class ProviderClass {
    public int providerFunc1(int x, int y){
        return x + y;
    }

    public int providerFunc2(int x, int y){
        return x - y;
    }

    public static int providerFunc3(int x, int y){
        return x * y;
    }

    public static int providerFunc4(int x, int y){
        return x / y;
    }
}

 

ProviderClass (메서드를 제공해줄 클래스)를 정의해두었습니다.

4개의 메소드를 정의해두었습니다.

 

3번 메소드, 4번 메서드는 static으로 선언해두었습니다.

 


 

@FunctionalInterface
public interface FuncInterface {
    public int shell(int x, int y);
}

 

메서드를 참조 사용하기 위해 껍데기 메서드가 있어야 합니다.

함수명 shell로 정의해두었습니다.

 

함수형 인터페이스 하나를 선언해두었습니다.

FuncInterface의 shell 메서드는 ProviderClass의 메서드의 구현부를 빌려서 사용할 겁니다.

 

껍데기 역할을 해줄 곳은 반드시 함수형 인터페이스여야 합니다.

함수형 인터페이스란 오로지 하나의 추상 메서드만을 갖고 있는 인터페이스를 말합니다.

 

하나의 추상 메서드만을 갖고 있어야 하는 이유는

여러 개의 추상 메서드를 갖고 있는 인터페이스가 다른 클래스의 메서드를 빌려 쓴다면,

타입의 혼란을 야기할 수 있기 때문에 반드시 하나의 추상 메서드만 있어야 합니다.

자바에서는 함수형 인터페이스가 아니면 메서드 참조를 할 수 없도록 막아둠

 

자바는 이러한 함수형 인터페이스를 제네릭으로 선언해 타입을 맞춰 API로 제공해줍니다.

java.util.function.*

 


 

public class mainClass {

    public static void main(String[] args) {

        // 메소드 제공 클래스
        ProviderClass providerClassInstance = new ProviderClass();

        // 제공받는 인터페이스
        FuncInterface funcInterface1 = providerClassInstance::providerFunc1; // 인스턴스 메소드는 "변수 명"으로 사용
        FuncInterface funcInterface2 = providerClassInstance::providerFunc2;
        FuncInterface funcInterface3 = ProviderClass::providerFunc3; // static 메소드는 "클래스 명"으로 사용
        FuncInterface funcInterface4 = ProviderClass::providerFunc4;

        // 참조하여 사용
        int sum = funcInterface1.shell(30,10); // 더하기
        int sub = funcInterface2.shell(30,10); // 빼기
        int mul = funcInterface3.shell(30,10); // 곱하기 (static)
        int div = funcInterface4.shell(30,10); // 나누기 (static)

        System.out.println(sum);
        System.out.println(sub);
        System.out.println(mul);
        System.out.println(div);
    }
}

 

인스턴스 메서드를 사용해야 할 때는

제공 클래스의 변수명::메서드명

 

클래스 메서드(static)를 사용할 때는

클래스 명::static 메서드 명

 

함수형 인터페이스 FuncInterface가 ProviderClass의 메서드들을 참조하면서

자신의 빈 껍데기 메서드 shell()로 ProviderClass에 구현된 메서드 기능을 참조하면서 자신의 것처럼 사용할 수 있습니다.

 

메서드를 참조하기 위해선 매개변수 타입, 리턴 타입, 개수가 일치해야 합니다.

 

 


 

메서드 참조: 함수형 인터페이스를 이용해서 다른 클래스의 메서드를 빌려쓰는 것

 


 

 

 

 

 

 

 

 

 

 

 

반응형

+ Recent posts