Skip to content

Latest commit

 

History

History
1926 lines (1288 loc) · 87.6 KB

File metadata and controls

1926 lines (1288 loc) · 87.6 KB

Spring Framework

OOP


위로

Object-Oriented Programming

객체지향 패러다임이 나오기 이전의 패러다임들부터 간단하게 살펴보자.

패러다임의 발전 과정을 보면 점점 개발자들이 편하게 개발할 수 있도록 개선되고 있다는 것을 알 수 있다.

가장 먼저, 순차적, 비 구조적 프로그래밍이 있다. 말 그대로 순차적으로 코딩하는 것이다.

필요한 게 있으면 계속 순서대로 추가해가며 구현하는 방식이다. 직관적이야 하겠지만 점점 규모가 커지면 어떻게 될까?

이러한 순차적, 비 구조적 프로그래밍에서는 goto문을 활용한다.

만약 이전에 작성했던 코드가 다시 필요하면 그 곳으로 이동하기 위한 것이다. 규모가 커지면 커질수록 goto문을 무분별하게 사용하게 되고, 마치 스파게티 처럼 베베 꼬이게 된다.(스파게티 코드). 나중에 코드가 어디가 어떻게 연결되어 있는지 확인하기 조차 어렵게 되는 문제점이 발생한다.

오늘날 공부하면서 **goto문은 사용하지 않는게 좋다!**라는 말을 들어봤을 것이다. goto문은 장기적으로 봤을 때 도움이 되지 않는 방식이라는 것은 자명하기 때문이다.

이런 문제점을 해결하기 위해 탄생한 것이 바로 절차적, 구조적 프로그래밍이다.

반복될 가능성이 있는 것들을 재사용이 가능한 함수(프로시저)로 만들어 사용하는 프로그래밍 방식이다.

여기서 '절차'라는 의미는 **함수(프로시저)**를 뜻하고, 구조는 모듈을 뜻한다. 모듈이 함수보다 더 작은 의미긴 하지만, 요즘은 큰 틀로 같은 의미로 쓰인다.

프로시저란?

반환값(리턴)이 따로 존재하지 않는 함수를 말한다.

예를들면, printf와 같은 함수는 반환값을 얻기 위한 것보단, 화면에 출력하는 용도로 쓰이는 함순데, 이와 같은 함수를 프로시저라 부른다. (정확히는 printfint형을 반환하긴 하지만, 목적 자체는 프로시저에 가깝다.)

하지만 이런 절차적 프로그래밍에도 문제점이 존재하는데, **너무 추상적**이라는 것이다.

실제로 사용되는 프로그램들은 추상적이지만은 않다. 함수는 논리적 단위로 표현되지만, 실제 데이터에 해당하는 변수나 상수 값들은 물리적 요소로 연결되어 있기 때문이다.

도서 관리 프로그램이 있다고 가정해보자.

책에 해당하는 자료형(필드)를 구현해야 한다. 또, 책과 관련된 함수도 구현해야 한다. 구조적인 프로그래밍에서는 이들을 따로 만들어야 한다.

결국 많은 데이터를 만들어야 할 때, 구분하기 힘들고 비효율적으로 코딩할 가능성이 높아진다.

책에 대한 자료형, 책에 대한 함수가 물리적으로 같이 있을 수 는 있지만(같은 위치에 기록)

논리적으로는 함께할 수 없는 구조가 구조적 프로그래밍이다.

따라서, 이를 한 번에 묶기 위한 패러다임이 탄생한다.

바로 객체지향 프로그래밍이다.

우리가 VO를 만들때와 같은 형태다.

클래스마다 필요한 필드르 선언하고, gettersetter로 구성된 모습으로 해결한다. 바로 특정한 개념의 함수와 자료형을 함께 묶어서 관리하기 위해 탄생한 것이다.

가장 중요한 점은, 객체 내부에 자료형(필드)와 함수(메소드)가 같이 존재하는 것이다.

이제 도서관리 프로그램을 만들 때, 책의 제목, 저자, 페이지와 같은 **자료형**과 읽기, 예약하기 등 **메소드**를 이라는 객체에 한번에 묶어서 저장하는 것이 가능해졌다.

이처럼 가능한 모든 물리적, 논리적 요소를 하나의 객체로 만드려는 것이 객체지향 프로그래밍이라 할 수 있다.

객체지향 프로그래밍을 사용하게 되면, 객체간의 독립성이 생기고 중복코드의 양이 줄어드는 장점이 있따. 또한, 독립성이 확립되면 유지보수에도 도움이 될 것이다.

특징

객체 지향의 패러다임이 생겨나면서 크게 4가지 특징을 갖추게 되었다.

이 4가지 특징을 잘 이해하고 구현해야 객체를 통한 효율적인 프로그래밍이 가능해진다.

1. 추상화

Abstraction

필요로 하는 속성이나 행동을 추출하는 작업

추상적인 개념에 의존하여 설계해야 유연함을 갖출 수 있다.

즉, 세부적인 사물들의 공통적인 특징을 파악한 후 하나의 집합으로 만들어내는 것이 추상화이다.

아우디, 벤츠, BMW는 모두 '자동차'라는 공통점이 있다.
자동차라는 추상화 집합을 만들고, 자동차들이 가진 공통적인 '특징'들을 만들어 활용한다.

예를 들면, 현대와 같은 다른 자동차 브랜드가 추가될 수 있다.
이 때 추상화로 구현해 두면 다른 곳의 코드는 수정할 필요 없이 추가되는 부분만 새로 생성해주면 된다.

- 특징

  • 추상클래스 - abstract의 의미
    • 미완성된 클래스, 모든 내용이 구현이 되어있지 않은 클래스로 완성되지 않았으므로 객채생성을 할 수 없다.
    • 메소드의 body가 구현되지 않은 메소드를 갖고 있는 클래스(추상메소드)
  • 추상 메소드를 선언하는 방법
    • 접근제어자 abstract 리턴타입 메소드명(매개변수 list 1, 2, 3, ...)
    • 추상메소드가 정의된 클래스는 미완성된 추상클래스가 되므로, 일반클래스와 다르다.
    • 따라서, 추상클래스를 정의하는 경우 클래스 선언부에도 abstract을 추가해야 한다.
  • 추상 클래스의 특징
    • 일반메소드와 추상메소드 모두 정의할 수 있다.
    • 내가 기능을 작성하는게 아니라 하위클래스에서 기능을 한다.
    • 추상메소드가 한 개라도 정의되어 있는 클래스는 반드시 abstract을 추가해야 한다.
    • 추상클래스는 인스턴스화 할수 없다.(객체 생성 불가능)
    • 추상클래스(abstract클래스)를 상속하면 에러가 발생한다?
    • AbstractSub클래스가 abstract메소드를 갖고있는 AbstractSuper클래스를 상속하면서 AbstarctSub클래스도 추상클래스로 변경된 것이다.
    • [해결방법]
      • 상위클래스로 사용될 목적으로 만들어진 클래스라면 클래스 선언부에 abstract을 추가한다.
      • 모든 "추상메소드"를 반드시 오버라이딩해야 한다.
  • 추상클래스와 추상메소드를 사용하는 이유?
    • 다형성을 정의하기 위해
    • 상위클래스로 사용하기 위한 목적(객채생성을 문법적으로 제한하기 위해)
    • 하위클래스에서 반드시 재정의해야하는 메소드를 문법적으로 정의하여 반드시 재정의하도록 하기위해

2. 캡슐화

Encapsulation

낮은 결합도를 유지할 수 있도록 설계하는 것

쉽게 말하면, 한 곳에서 변화가 일어나도 다른 곳에 미치는 영향을 최소화 시키는 것을 말한다.

객체가 내부적으로 기능을 어떻게 구현하는지 감추는 것이다.

결합도가 낮게 만들어야 하는 이유가 무엇일까?

여기서 결합도(coupling)이란, 어떤 기능을 실행할 때 다른 클래스나 모듈에 얼마나 의존적인가를 나타내는 정도이다.

즉, 독립적으로 만드어진 객체들간의 의존도가 최대한 낮게 만드는 것이 중요하다. 객체들 간의 의존도가 높아지면 굳이 객체지향으로 설계하는 의미가 옅어지기 때문이다.

우리는 소프트웨어 공학에서 객체 안의 모듈 간의 요소가 밀접한 관련이 있는 것으로 구성하여 응집도를 높이고 결합도를 줄여야 요구사항 변경에 대처하는 좋은 설계 방법이라고 배운다.

이것이 바로 **캡슐화**와 연관된 부분이라고 할 수 있다.

그렇다면 캡슐화는 어떻게 높은 응집도낮은 결합도를 갖게 할까?

바로 정보 은닉을 활용하는 것이다.

외부에서 접근할 필요가 없는 것들은 **private**으로 접근하지 못하도록 제한을 두는 것이다.

객체안의 필드를 선언할 때는 private으로 선언하라는 말이 바로 이 때문이다.

3. 상속

일반화 관계(Generalization)이라고도 하며, 여러 개체들이 지닌 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립하는 과정이다.

일반화(상속)은 또 다른 캡슐화다.

자식 클래스를 외부로부터 은닉하는 캡슐화의 일종이라고 말할 수 있다.

하위 객체는 상위 객체(부모)의 특징을 물려 받는데, 이 상위 객체의 메소드나 변수를 구현하는가 그대로 사용하는가에 따라서 **상속**의 형태가 갈리게된다.

  1. extends
    • 부모에서 선언 / 정의 를 모두하며 자식은 메소드 / 변수를 그대로 사용할 수 있음
  2. implements (interface 구현)
    • 부모 객체는 선언만 하며 정의(내용)은 자식에서 오버라이딩(재정의)해서 사용해야 함
  3. abstract
    • extends와 interface의 혼합으로. extends하되 몇 개는 추상 메소드로 구현되어 있음
# 요약
1. `extends`는 일반 클래스와 abstract 클래스 상속에 사용되고, `implements`는 interface상속에 사용된다.
2. class가 class를 상속받을 땐 extends를 사용하고 interface가 interface가 상속 받을 땐 extneds를 사용한다.
3. class가 interface를 사용할 땐 implements를 써아햐고
4. interface가 class를 사용할 땐 implements를 쓸 수 없다.
5. extends는 클래스 한 개만 상속 받을 수 있다.(단일상속)
6. extends 자신 클래스는 부모 클래스의 기능을 사용한다.
7. implements는 여러 개 사용 가능하다. (다중상속 해법)
8. implements는 설계 목적으로 구현이 가능하다.
9. implements한 클래스는 implements의 내용을 다 사용해야 한다.

extends는 클래스를 확장하는 것이고 implements는 인터페이스를 구현하는 것이다.
인터페이스와 보통 클래스의 차이는 인터페이스는 정의한 메소드를 구현하지 않아도 된다.
인터페이스를 상속받는 클래스에서 인터페이스에 정의된 메소드를 구현하면 된다.

아까 자동차를 예로 들어 추상화를 설명했다. 여기에 추가로 대리 운전을 하는 사람 클래스가 있다고 생각해보자.

이 떄, 자동차의 자식 클래스에 해당하는 벤츠, BMW, 아우디 등은 캡슐화는 통해 은닉해둔 상태이다.

사람 클래스의 관점으로는 구체적인 자동차의 종류가 숨겨져 있는 상태이다. 대리 운전자 입장에서는 자동차의 종류가 어떤 것인지는 운전하는 데 크게 중요하지 않다.

새로운 자동차들이 추가된다고 해도, 사람 클래스는 영향을 받지 않는 것이 중요하다. 그러므로 캡슐화를 통해 사람 클래스 입장에서는 확인할 수 없도록 구현하는 것이다.

이처럼, 상속 관계에서는 단순히 하나의 클래스 안에서 속성 및 연산들의 캡슐화에 한정되지 않는다. 즉, 자식 클래스 자체를 캡슐화하여 '사람 클래스'와 같은 외부에 은닉하는 것으로 확장되는 것이다.

이렇게 자식 클래스를 캡슐화 해두면, 외부에선 이러한 클래스들에 영향을 받지 않고 개발을 이어갈 수 있는 장점이 있다.

- 상속 재사용의 단점

  1. 상위 클래스(부모 클래스)의 변경이 어려워 진다.
    • 부모 클래스에 의존하는 자식 클래스가 많을 때, 부모 클래스의 변경이 필요하다면 이를 의존하는 자식 클래스들이 영향을 받게 된다.
  2. 불필요한 클래스가 증가할 수 있다.
    • 유사기능 확장 시, 필요 이상의 불필요한 클래스를 만들어야 하는 상황이 발생할 수 있다.
  3. 상속이 잘못 사용될 수 있다.
    • 같은 종류가 아닌 클래스의 구현을 재사용하기 위해 상속을 받게 되면, 문제가 발생할 수 있다.
    • 상속 받는 클래스가 부모 클래스와 IS-A 관계가 아닐 때 이에 해당한다.

- 해결책

객체 조립(Compostion), 컴포지션이라고 부른다.

객체 조립은, 필드에서 다른 객체를 참조하는 방식으로 구현된다.

상속에 비해 비교적 런타임 구조가 복잡해지고, 구현이 어려운 단점이 존재하지만 변경 시 유연함을 확보하는 데 장점이 매우 크다.

따라서 같은 종류가 아닌 클래스를 상속하고 싶을 때는 객체조립을 우선적으로 적용하는 것이 좋다.

그럼 상속은 언제 사용해?

  • IS-A 관계가 성립할 때
  • 재사용 관점이 아닌, 기능의 확장 관점일 때

- 특징

  • 상속관계에서 **멤버변수**가 갖는 특징

    1. 상속관계에서는 상위클래스에서 정의된 멤버변수를 하위클래스에서 사용할 수 있다.

      (하위클래스 참조 변수를 통해서 접근할 수 있다.)

    2. 상위클래스의 변수와 동일한 이름의 변수를 하위클래스에 정의하는 경우 하위클래스의 멤버변수가 우선순위가 높다.

    3. 부모클래스의 변수를 액세스 하려면 super를 이용해서 접근한다.

      • this - 자기자신의 객체
      • super - 부모 객체
    4. private으로 선언된 변수는 상속관계에 있다고 하더라도 하위클래스에서 접근할 수 없다.

    5. 상위클래스는 하위클래스의 일반적인 내용을 정의하기 위해서 사용하는 클래스이므로 주로 하위클래스를 생성해서 사용한다.

    6. 상위클래스를 쓰는게 아니라 하위클래스(자식)를 쓰는 것이다.

  • 상속관계에서 **메소드**가 갖는 특징

    1. 상속관계에서는 상위클래스에 정의된 메소드를 하위클래스에서 사용할 수 있다.

      (하위클래스 참조 변수를 통해서 접근할 수 있다.)

    2. 상위클래스에 정의된 메소드와 동일한 메소드를 하위클래스에서 정의하고 사용할 수 있다. 이런 경우 하위클래스의 메소드가 호출된다.

      • 메소드를 재정의 했으면 부모보다 재정의한 메소드가 우선인식된다.

      • 상위클래스에 선언된 메소드와 동일한 메소드를 하위클래스에 정의하는 것을 메소드 재정의

        (메소드 오버라이딩)이라고 한다.

        • 메소드 선언부가 부모클래스의 메소드 선언부와 반드시 일치해야한다.

          => 메소드명, 매개변수 갯수, 매개변수 타입, 리턴타입이 모두 동일해야한다

    3. 부모클래스의 메소드를 사용하고 싶은 경우 super로 호출한다.

    4. 모든 클래스의 첫째줄에는 자동으로 object가 상속되어 있다.

  • 상속관계에서 **생성자**가 갖는 특징

    1. 클래스의 모든 생성자메소드의 첫 번째 문장에는 부모클래스의 기본 생성자를 호출하는 명령문이 생략되어 있다.
      • 부모클래스도 메모리에 할당되어야 사용할 수 있으므로
      • 부모의 생성자를 호출하는 방법은 super(...)
        • super()는 부모의 매개변수 없는 기본 생성자
      • 이미 다른 생성자를 호출하는 경우에는 super()를 할 수없다.
        • this()를 호출하는 경우 super를 호출할 수 있다.
    2. 모든 클래스의 최상위 클래스는 java.lang.Object이다.
      • 자바에서 실행되는 모든 객체가 갖는 공통의 특징을 정의한 클래스로 상속받고 있는 클래스가 없는 경우 컴파일러가 자동으로 상속하도록 한다.
    3. 부모클래스에 정의되어 있는 멤버변수가 값을 셋팅해야 하는 경우에도 하위클래스에서 전달될 수 있도록 정의한다.
      • super(값1, 값2, .....)를 통해 접근한다

4. 다형성

Polymorphism

서로 다른 클래스가 객체가 같은 메세지를 받았을 때 각자의 방식으로 동작하는 능력

상속, 오버라이딩, 추상클래스, 객체의 형번환과 같은 개념들의 총체적 융합이다.

객체지향의 핵심과도 같다.

다형성은, 상속과 함께 활용할 때 큰 힘을 발휘한다. 코드를 간결하게 해주며 유연함을 갖추게 해준다.

부모 클래스의 메소드를 자식 클래스가 Overriding(오버라이딩)해서 자신의 역할에 맞게 활용하는 것이 다형성이다.

다형성을 사용하면, 구체적으로 현재 어떤 클래스 객체가 참조되는 지는 무관하게 프로그래밍하는 것이 가능하다.

상속관계에 있으면, 새로운 자식 클래스가 추가되어 부모 클래스의 함수를 참조해오면 되기 때문에 다른 클래스는 영향을 받지 않게 된다.

- 특징

  • 부모타입의 변수로 선언하면 모두 받을 수 있다.

  • type이 Parent라면 Parent밖에 접근 못한다.

  • 동일한 타입(상위 타입)의 변수를 선언 => 실행시점에 다양한 객체가 실행될 수 있게끔 =>

    => 다양한 객체의 다양한 메소드 실행(상속관계에 있어야 한다.)

  • 유지보수하고 추가된 것들을 반영하기 위해

  • 시스템의 모듈화

  • 이 모든것의 베이스는 "다형성"을 적용할 수 있기 때문이다. - API가 가장 좋은 예시.

  • 결국 상속관계, 클래스 설계를 잘 해야한다.

  • 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하기위해

+ 인터페이스

추상메소드(상수도 포함)만 정의하는 특별한 클래스

  • 인터페이스는 **interface**키워드를 이용해서 정의

  • 인터페이스는 추상메소드만 정의하는 특별한 클래스

    • public abstract이 생략 가능
    • 상속을 받으면 자동으로 추가된다.
  • 인터페이스가 인터페이스를 상속할 수 있다.(extends 이용)

    • 하위 인터페이스가 상위인터페이스의 추상메소드를 상속받는다.
  • 클래스가 인터페이스를 상속할 수 있다.(implements 이용)

    • 인터페이스를 상속받는 클래스가 이미 다른클래스를 상속하는 경우에 extends를 먼저 정의하고 implements를 정의해야 한다.
  • 인터페이스는 여러개를 상속할 수 있다. 즉, 다중 상속이 가능하다.

    • implements 뒤에서 인터페이스를 정의할 때, " , " 로 구분해서 나열
  • 클래스와 인터페이스들을 상속받는 하위클래스는 모든 클래스와 인터페이스의 하위로 인식된다.

    (상속받는 모든 클래스, 인터페이스의 하위 타입이 된다.) - 하위 메소드들에게 스펙을 제시.

  • 원래 자바는 단일상속만 가능하지만, 인터페이스를 이용해 다중상속을 구현한다.

사용목적

  1. 다중 상속을 허용하고 다형성을 적용할 수 있도록 한다.
  2. 기본적으로 구현해야 하는 기능이 무엇인지 정의하기 위한 목적

객체지향 설계 과정

  • 제공해야 할 기능을 찾고 세분화 한다. 그리고 그 기능을 알맞은 객체에 할당한다.
  • 기능을 구현하는 데 필요한 데이터를 객체에 추가한다.
  • 그 데이터를 이용하는 기능을 넣는다.
  • 기능은 최대합 캡슐화하여 구현한다.
  • 객체 간에 어떻게 메소드 요청을 주고받을 지 결정한다.

객체지향 설계 원칙

SOLID 라고 부르는 5가지 설계 원칙이 존재한다.

  1. SRP(Single Responsibility) - 단일 책임 원칙
    • 클래스는 단 한개의 책임을 가져야 한다.
    • 클래스는 변경하는 이유는 단 한개여야 한다.
    • 이를 지키지 않으면 한 책임의 변경에 의해 다른 책임과 관련된 코드에 영향이 갈 수 있다.
  2. OCP(Open-Closed) - 개방 폐쇄 원칙
    • 확장에는 열려있어야 하고, 변경에는 닫혀 있어야 한다.
    • 기능을 변경하거나 확장할 수 있으면서, 그 기능을 사용하는 코드는 수정하지 않는다.
    • 이를 지키지 않으면 instanceof와 같은 연산자를 사용하거나 다운 캐스팅이 일어난다.
  3. LSP(Liskov Subsitution) - 리스코프 치환 원칙
    • 상위 타입의 객체르르 하위 타입의 객체로 치환해도, 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.
    • 상속관계가 아닌 클래스들을 상속 관계로 설정하면, 이 원칙이 위배된다.
  4. ISP(Interface Segregation) - 인터페이스 분리법칙
    • 인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.
    • 각 클라이언트가 필요로 하는 인터페이스들을 분리함으로써, 각 클라이언트가 사용하지 않는 인터페이스에 변경이 발생하더라도 영향을 받지 않도록 만들어야 한다.
  5. DIP(Dependency Inversion) - 의존 역전 법칙
    • 고수준 모듈을 저수준 모듈의 구현에 의존해서는 안된다.
    • 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.
    • 즉, 저수준 모듈이 변경돼도 고수준 모듈을 변경할 필요가 없는 것이다.

참조

https://github.com/gyoogle/tech-interview-for-developer/blob/master/Computer%20Science/Software%20Engineering/Object-Oriented%20Programming.md

https://velog.io/@hkoo9329/%EC%9E%90%EB%B0%94-extends-implements-%EC%B0%A8%EC%9D%B4

Servlet


위로

클라이언트의 요청을 처리하기 위한 기술.

또, 요청에 대한 결과를 반환하는 Servlet 클래스의 구현 규칙을 지킨 자바 웹 프로그래밍 기술

간단히 말해서, 서블릿이란 자바를 사용해 웹을 만들기 위해 필요한 기술이다.

즉, 클라이언트가 어떠한 요청을 하면 그에 대한 결과를 다시 전송 해주어야 하는데, 이러한 역할을 하는 자바 프로그램 인 것이다.

클라이언트로부터 요청이 전달되면 서버에서 실행되며 DB연동이나 서버의 자원을 액세스하여 만들어진 결과를 다시 클라이언트로 응답한다.

이 클라이언트의 요청을 인식하고 실행하도록 하기 위해서는 서블릿은 정해진 규칙대로 작성되어야 하고, 서버가 서블릿을 찾아 실행될 수 있도록 정해진 위치에 작성되어야 한다.

정해진 위치?

표준화된 폴더 구조안에 있는 classes 폴더(서블릿 디렉토리)

89752786-b3c1be80-db10-11ea-94da-d4bb100c5636

  • 특징
    • 클라이언트의 요청에 대해 동적으로 작동하는 웹 어플리케이션 컴포넌트
    • HTML을 사용하여 요청에 응답한다
    • Java Thread를 이용해 동작한다.
    • MVC 패턴에서 Controller로 이용된다.
    • HTTP 프로토콜 서비스를 지원하는 javax.servlet.http.HttpServlet 클래스를 상속받는다.
    • UDP보다 처리 속도가 느리다.
    • HTML 변경시 Servlet을 재컴파일해야 하는 단점이 있다.

일반적으로 웹 서버는 정적인 페이지만을 제공하기 때문에 동적인 페이지를 제공하기 위해서 웹 서버는 다른 곳에 도움을 요청해 동적인 페이지를 작성해야 한다.

동적인 페이지로는 임의의 이미지만을 보여주는 페이지와 같이 사용자가 요청한 시점에 페이지를 생성해서 전달해주는 것을 의미한다.

여기서 웹서버가 동적인 페이지를 제공할 수 있도록 도와주는 어플리케이션이 서블릿이며, 동적인 페이지를 생성하는 어플리케이션이 CGI이다.

서블릿 동작 방식

image-20210609133452649

  1. 사용자(클라이언트)가 URL을 입력하면 HTTP Request가 Servlet Container로 전송한다.
  2. 요청을 전송받은 Servlet Container는 HTTPServletRequest, HttpServletResponse 객체를 생성한다.
  3. web.xml을 기반으로 사용자가 요청한 URL이 어느 서블릿에 대한 요청인지 찾는다.
  4. 해당 서블릿에서 service 메소드를 호출한 후 클라이언트의 GET, POST 여부에 따라 doGet() 또는 doPost()를 호출한다.
  5. doGet() or doPost() 메소드는 동적 페이지를 생성한 후 HttpServletResponse 객체에 응답을 보낸다.
  6. 응답이 끝나면 HTTPServletRequest, HttpServletResponse 두 객체를 소멸시킨다.

서블릿 컨테이너

서블릿을 관리해주는 컨테이너

ex) Tomcat

우리가 서버에 서블릿을 만들었다고 해서 스스로 작동하는 것이 아니고, 서블릿을 관리해주는 것이 필요한데 그러한 역할을 하는 것이 서블릿 컨테이너이다.

서블릿이 어떠한 역할을 수행하는 정의서라고 하면, 서블릿 컨테이너는 그 정의서를 수행한다고 볼 수 있다.

서블릿 컨테이너는 클라이언트의 요청(Request)를 받아주고, 응답(Respone)할 수 있도록, 웹 서버와 소켓으로 통신하며 대표적인 예로 **톰캣(Tomcat)**이 있다.

톰캣은 실제로 웹 서버와 통신하며 JSPServlet이 작동하는 환경을 제공해준다.

역할

  1. 웹 서버와의 통신 지원
    • 서블릿 컨테이너는 서블릿과 웹 서버가 손쉽게 통신할 수 있도록 해준다.
    • 일반적으로 우리는 소켓을 만들고 listen, accept 등을 해야 하지만 서블릿 컨테이너는 이러한 기능을 API로 제공하여 복잡한 과정을 생략할 수 있게 한다.
    • 그래서 개발자가 서블릿에 구현애야 할 비즈니스 로직에 대해서만 초점을 두게끔 한다.
  2. 서블릿 생명주기(Life Cycle) 관리
    • 서블릿 컨테이너는 서블릿의 탄생과 죽음을 관리한다.
    • 서블릿 클래스를 로딩하여 인스턴스화 하고, 초기화 메소드를 호출하고, 요청이 들어오면 적절한 서블릿 메소드를 호출한다.
    • 또한, 서블릿이 생명을 다 한 순간에는 적절하게 Garbage Collection을 진행하여 편의를 제공한다.
  3. 멀티쓰레드 지원 및 관리
    • 서블릿 컨테이너는 요청이 들어올 때마다 새로운 자바 쓰레드를 하나 생성하는데, HTTP 서비스 메소드를 실행하고나면, 쓰레드는 자동으로 죽게 된다.
    • 원래는 쓰레드를 관리해야 하지만 서버가 다중 쓰레드를 생성 및 운영해주니 쓰레드의 안정성에 대해 걱정하지 않아도 된다.
  4. 선언적인 보안 관리
    • 개발자는 보안에 관련된 내용을 서블릿 또는 자바 클래스에 구현해 놓지 않아도 된다.
    • 일반적으로 보안관리는 XML 배포 서술자에 기록하므로, 보안에 대해 수정할 일이 생겨도 자바 소스 코드를 수정하여 다시 컴파일 하지 않아도 보안관리가 가능하다.

생명주기

image-20210609134925916

  1. 클라이언트의 요청이 들어오면 컨테이너는 해당 서블릿이 메모리에 있는지 확인하고, 없을 경우 init() 메소드를 호출하여 적재한다. init() 메소드는 처음 한번만 실행되기 때문에, 서블릿의 쓰레드에서 공통적으로 사용해야 하는 것이 있다면 오버라이딩하여 구현하면 된다. 실행 중 서블릿이 변경될 경우, 기존 서블릿을 파괴하고 init()을 통해 새로운 내용을 다시 메모리에 적재한다.
  2. init()이 호출된 후 클라이언트의 요청에 따라서 service() 메소드를 통해 요청에 대한 응답이 doGet()doPost()로 분기된다. 이 때, 서블릿 컨테이너가 클라이언트의 요청이 오면 가장 먼저 처리하는 과정으로 생성된 **HttpServletRequest, HttpServletResponse**에 의해 request와 response 객체가 제공된다.
  3. 컨테이너가 서블릿에 종료 요청을 하면 destroy() 메소드가 호출되는데 마찬가지로 한번만 실행되며, 종료시에 처리해야 하는 작업들을 destroy() 메소드를 오버라이딩하여 구현하면 된다.

서블릿 작성 규칙

  1. 표준화된 폴더 구조 안에서 서블릿 디렉토리에 저장되어야 된다. - classes 폴더

    • ex) C:\iot\setup\java\work\webwork.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\serverweb\WEB-INF\classes에 작성되어야 한다. => 이 위치에 서블릿 클래스가 없으면 못 찾는다.
  2. public 클래스로 작성해야 한다.

    • 서버가 찾아서 실행해야 하므로
  3. 서블릿클래스를 상속해야 한다.

    • 서버가 우리가 작성한 서블릿 클래스를 찾아서 생성하고 호출하기 위해서는 서버가 인식할 수 있는(서버가 사용할 수 있는) 타입이어야 하므로 서버가 서버에 등록된 타입으로 서블릿 클래스를 작성한다.
    • Servlet(인터페이스)이 => 아래 클래스서블릿들을 상속해야 한다.
      • GenericServlet(클래스) - 일반적인 내용을 담고 있는 서블릿
      • HttpServlet(클래스) - http프로토콜에 특화된 내요을 담고 있는 서블릿
      • MyServlet - 내가 하고 싶은 일을 담고 있는 서블릿(개발자가 만드는 서블릿)
    • 컨테이너 설정 : properties - build path - add library - tomcat9.0 를 설정한다.
  4. 서버가 호출하는 메소드가 오버라이딩 해야 한다.

    • 서블릿 클래스는 일반 클래스를 사용하는 방법처럼 객체생성해서 사용하는 클래스가 아니다.
    • 서블릿이 호출되면 서버가 서블릿 객체를 생성하고 적절한 시점에 따라 정해진 메소드를 자동으로 호출한다.
    • 즉, 서블릿의 _LifeCycle_을 서버가 관리한다.
    • LifeCycle ? : 객체를 생성하고 소멸하는 것
    • 서버가 적절한 시점에 따라 자동으로 메소드를 호출할 때 원하는 작업을 처리하기 위해서는 서버가 호출하는 메소드를 오버라이딩해서 내가 원하는 내용을 기술해야 한다.

    [오버라이딩할 메소드]

    - init : 서블릿이 초기화될 때 호출

    - service : 클라이언트가 요청을 하면 호출되는 메소드

    => 클라이언트의 요청을 처리할 수 있는 내용을 기술

    => 요청방식의 구분없이 모두 호출

    ex) 로그인, 게시판 목록보기, 회원가입....

    - doGet : service와 동일하게 동작하며 클라이언트가 get방식으로 요청하는 경우에만 호출

    - doPost : service와 동일하게 동작하며 클라이언트가 post방식으로 요청하는 경우에만 호출

    - destroy : 서블릿 객체가 소멸될 때 (메모리에서 해제될 때) 호출

  5. 서블릿을 등록

    • 서버가 서블릿을 찾아서 실행할 수 있도록 서블릿을 web.xml에 등록
    • web.xml? : 서블릿에 대한 내용을 서버에 등록하는 설정파일
    1. 서블릿 등록
    • 사용할 서블릿이 어떤 클래스인지 정의
    <servlet>
          <servlet-name>서블릿의 이름(별칭)</servlet-name>
          <servlet-class>실제 사용할 서블릿클래스(패키지 포함)</servlet-class>
    </servlet>

    ex) basic패키지에 작성한 FirstServlet을 first라는 이름으로 등록

    <servlet>
          <servlet-name>First</servlet-name>
          <servlet-class>basic.FirstServlet</servlet-class>
    </servlet>
    1. 서블릿 매핑
    • 서블릿을 어떤 url로 요청할지 등록
    <servlet-mapping>
          <servlet-name>미리등록한 서블릿의 이름</servlet-name>
          <url-pattern>요청url(반드시 /나 .으로 시작)</url-pattern> 
    </servlet-mapping>

    ex) 위에서 등록한 first서블릿을 /first.multi로 요청

    <servlet-mapping>
          <servlet-name>first</servlet-name>
          <url-pattern>/first.html</url-pattern> //path이름은 내맘대로
    </servlet-mapping>

    => 이렇게 요청하면 first라는 서블릿을 호출할건데 그 first는 basic.FirstServlet에서 만들어진 것이고 그건 basic에 있다.

xml파일은 다른 플랫폼(파이썬, 닷넷 등등등) 에서 사용하기 위해서 만들어진 문서이고 다소 무거운 단점이 있음. => 그래서 나온게 JSON

서블릿 요청 방법

특징

  1. get : 요청할 때 입력하는 내용이 url 뒤에 추가되어 전송되는 방식(요청메세지 헤더에 추가)

    • 클라이언트가 입력하는 내용이 그대로 노출된다.
    • 전송할 수 있는 데이터의 크기에 제한이 있다.
    • 서버의 데이터를 가져오기

    ex) 게시판 목록 확인하기, 상품정보 가져오기, 검색하기

  2. post : 요청메시지 body에 추가되어 전송되므로 클라이언트에 노출되지 않지만 툴을 이용해서 확인하면 확인할 수 있으므로 암호화해서 전송해야 한다.

    • 보낼 수 있는 데이터 크기에 제한이 없다.
    • 서버의 값을 클라이언트가 원하는 값으로 update하는 경우

    ex) 회원등록(insert문 실행), 회원정보 수정하기(update문 실행), 파일업로드, 메일쓰기

  3. 클라이언트가 전달하는 요청 메시지에서 클라이언트의 입력 정보를 추출하기

    1) 요청

    [요청객체]
    -   ServletRequest (상위)
        -   HttpServletRequest (하위)
    • 클라이언트가 요청 메시지를 서버로 전달하면 여러 가지 클라이언트의 정보가(클라이언트가 입력한 데이터, 쿠키, 세션정보, 클라이언트의 IP, Port...) 서버로 전달된다.
    • 서버는 이 데이터를 가지고 요청객체(요청객체를 만들면서 전달받은 데이터를 요청객체에 셋팅하는 작업을 수행한다.)를 생성한다.
    • http프로토콜에 특화된 내용은 HttpServletRequset에서 찾는다.
    • 일반적인 내용은 ServletRequest에서 찾는다.

    2) 요청정보 추출

    ~~/serverweb/login.do?id=lee&pass=1234

    • id : 파라미터 name
    • lee : 파라미터 value

get 요청

  1. 주소표시줄에 입력하고 요청

    <a href="http://localhost:8088/serverweb/first.multi">받은편지함(하이퍼링크 요청)</a>
    <a href="http://70.12.115.65:8088/serverweb/first.multi">받은편지함(하이퍼링크 요청)</a>
    <a href="/serverweb/first.multi">받은편지함(하이퍼링크 요청)</a>
  2. 하이퍼링크 클릭

    <a href="http://서버ip:port:contextpath/서블릿요청url">하이퍼링크</a>
    <a href="/contextpath/서블릿요청url">하이퍼링크</a>
  3. 태그에서 method속성을 "get"으로 설정하고 submit버튼 선택

    • action속성에서 설정한다
    • form태그를 정의하면서 method속성을 생략하면 get방식으로 요청
    • submit버튼을 눌러서 요청하면 태그의 action속성에 정의한 서블릿이 요청되며 내부에 정의한 모든 양식태그들의 name과 value가 서블릿으로 전달된다.

post 요청

  1. 태그에서 method속성을 "post"으로 설정하고 submit버튼 선택

    • action속성에서 설정한다

    • form태그를 정의하면서 method속성을 생략하면 get방식으로 요청

    • submit버튼을 눌러서 요청하면

      태그의 action속성에 정의한 서블릿이 요청되며

      내부에 정의한 모든 양식태그들의 name과 value가 서블릿으로 전달된다.

JSP


위로

Java Server Page

Java코드가 들어가 있는 HTML 코드

Life Cycle은 서블릿과 비슷하다.

서블릿은 자바소스코드 속에 HTML 코드가 들어가는 형태인데, JSP는 이와 반대로 HTML 소스코드 속에 자바 소스코드가 들어가는 구조를 갖춘 웹 어플리케이션 프로그래밍 기술이다.

클라이언트의 요청에 대해 동적 컨텐츠를 생성해서 응답결과를 만들어줄 때 사용하는 기술로 html문서에 화면을 작성하는 방법과 동일하게 작성하면 된다.

실행이 될 때 WAS 내부에 있는 JSP 컨테이너에 의해 서블릿으로 변환되서 실행이 되므로 자바코드를 사용할 수 있는 것이다.

스크립트 요소

  1. 스크립트릿 (Scriptlet)

    <% %>
    • 자바코드를 작성할 수 있는 스크립트 요소
    • 웹 브라우저로 보내는 것이 아니라 웹 서버에서 실행되는 부분이다.
    • 문장의 끝에 반드시 세미콜론(;)을 붙여야 한다.
    • 스크릿트릿 요소는 여러번 반복해서 정의할 수 있지만 지양한다.
    • 서블릿이 공유하는 데이터를 꺼내서 출력하기 위해 사용된다.
    • .java 파일에서 할 수 있는 모든 작업을 할 수 있다.(메소드 선언, 클래스 선언 제외)
    • java.lang 패키지 빼고 모두 import 해주어야 한다.
    • 스크립트릿 내부에서 정의하는 변수는 모두 jspService() 메소드의 지역변수로 추가해야 한다.
  2. 선언문

    <%! %>
    • .jsp 파일이 서블릿으로 변환될 때 서블릿 클래스의 멤버로 작성될 메소드나 변수를 정의
    • 잘 안쓴다.
  3. 표현식

    <%= %>
    • 동적으로 만들어진 컨텐츠를 구성하는 값을 출력하기 위해 사용하는 스크립트 요소

    • 서블릿으로 변환될 때 out.print()의 내부에 매개변수로 추가되므로 세미콜론(;)를 추가하지 않는다.

      // 오류상황 예시
      <%= "test" ;%>   ----> out.print("test";); // error !
    • 표현식은 값을 출력하기 위해서 사용하므로 사용할 수 있는 타입이 제한적이다.

    • 기본형, String, 앞의 나열한 타입을 반환하는 메소드 호출문 연산 시 사용한다.

POJO


위로

Plain Old Java Object

오래된 방식의 간단한 자바 오브젝트 라는 말로서, Java EE 등 중량 프레임워크들을 사용하게 되면서 점점 무거운 객체를 만들게 된 것에 반발해서 생겨난 개념이다.

오래된 방식의 간단한 자바 오브젝트가 무엇일까?

쉽게 말하자면, 특정 '기술'에 종속되어 동작하는 것이 아닌 순수한 자바 객체를 일컫는다.

예를들어, ORM(Object Relationship Mapping)이 새롭게 등장했을 때를 생각해보자. ORM 기술을 사용하고 싶다면 ORM을 지원하는 ORM 프레임워크를 사용해야 한다. (대표적으로 Hibernate라는 프레임워크가 있다.) 만약, 자바 개체가 ORM 기술을 사용하기 위해서 **Hibernate**프레임워크를 직접 의존하는 순간, 이는 POJO라고 할 수 없다.

특정 기술에 종속되었기 때문이다.

POJO를 지향해야 하는 이유

스프링 프레임 워크 이전에는 원하는 엔터프라이즈 기술이 있다면 그 기술을 직접적으로 사용하는 객체를 설계했다. 그리고 이러한 개발 방식이 만연했고, 특정 기술과 환경에 종속되어 의존하게 된 자바 코드는 가독성이 떨어져 유지보수에 어려움이 생긴 것이다.

또한, 특정 기술의 클래스를 상속받거나, 직접 의존하게 되어 확장성이 매우 떨어지는 단점이 있었다.

이 말은 객체지향의 화신인 자바가 객체지향 설계의 장점들을 잃어버리게 된 것이다.

그래서, POJO라는 개념이 등장했다. 본래 자바의 장점을 살리는 오래된 방식의 순수한 자바객체 말이다.

Spring에서의 POJO

스프링 프레임워크는 IoC(Inversion of Control, 제어의 역전) 컨테이너 안에서 POJO를 구성 및 관리하는 것이 가장 핵심으로 POJO를 매우 잘 다루는 프레임워크가 바로 스프링 프레임워크이다.

Java EE 등을 사용할 때에 비해서 특정 인터페이스를 구현하거나 상속할 필요가 없고 라이브러리를 지원하기에 용이하며 객체 또한 가벼운 것이 특징이다.

특정 기술을 사용하고 싶다면?

스프링이 POJO를 유지하면서 Hibernate를 사용할 수 있는 이유? -> PSA

Hibernate는 스프링 개발에서 많이 사용되고 있는 기술이다. 특정 기술에 종속적이면 POJO가 아니라면서 스프링에서는 어떻게 가능한걸까?

바로 스프링에서 정한 표준 인터페이스가 있기 때문이다.

스프링 개발자들은 ORM이라는 기술을 사용하기 위해서 **JPA**라는 표준 인터페이스를 정해두었다.

그리고 이제 여러 ORM 프레임워크들은 이 JPA라는 표준 인터페이스 아래, 구현되어 실행된다. 이것이 스프링이 새로운 엔터프라이즈 기술을 도입하면서도 POJO를 유지하는 방법이다.

이런 방법을 스프링의 PSA라고 이야기한다.

진정한 POJO란

토비의 스프링에서는 진정한 POJO를 아래와 같이 정의했다고 한다.

그럼 특정 기술규약과 환경에 종속되지 않으면 모두 POJO라고 말할 수 있는가? 많은 개발자가 크게 오해하는 것 중의 하나가 바로 이것이다. ...(중략)... 진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다.

참조

https://siyoon210.tistory.com/120 https://siyoon210.tistory.com/120

MVC 패턴


위로

M, V, C가 나뉘어서 서버를 구성하는 모델을 모델2라고 한다. 모델 1과 모델2의 차이도 있지만 여기서는 넘어가겠다.

M, V, C의 역할

  • Model(모델)
    • 컨트롤러가 호출할 때, 요청에 맞는 역할을 수행한다.
    • 비즈니스 로직을 구현하는 영역으로 응용프로그램에서 데이터를 처리하는 부분이다.
    • 비즈니스 로직이란 업무에 필요한 데이터처리를 수행하는 응용프로그램의 일부이다.
    • DB에 연결하고 데이터를 추출하거나 저장, 삭제, 업데이트, 변환 등의 작업을 수행한다.
    • 상태 변화가 있을 때 컨트롤러와 뷰에 통보해 후속 조치 명령을 받을 수 있도록 한다.
    • 애플리케이션의 상태(data)를 나타내고 일반적으로 **POJO**로 구성된다.
    • Java Beans
  • Contoller(컨트롤러)
    • 일종의 조정자라고 할 수 있다.(View - Model 사이의 인터페이스 역할)
    • 클라이언트의 요청을 받았을 때, 그 요청에 대해 실제 업무를 수행하는 모델 컴포넌트를 호출한다.
    • 또한, 클라이언트가 보낸 데이터가 있다면 모델에 전달하기 쉽게 데이터를 가공한다.
    • 모델이 업무를 마치면 그 결과를 뷰에게 전달한다.
    • Controller -> Service -> DAO -> DB
  • View(뷰)
    • 컨트롤러부터 받은 모델의 결과값을 가지고 사용자에게 출력할 화면을 만드는 일을 한다.
    • 만들어진 화면을 웹브라우저에 전송하여 웹브라우저가 출력하게 하는 것이다.
    • 화면에 표시되는 부분으로 추출한 데이터나 일반적인 텍스트 데이터를 표시하거나 입력폼 또는 사용자와의 상호작용을 위한 인터페이스를 표시하는 영역이다.
    • JSP
    • JSP 이외에도 Thymeleaf, Groovy, Freemarker 등 여러 템플릿 엔진이 존재

MVC 구동원리

image-20210609151104276

Client - Server 구조로 요청을 하면 그에 맞는 응답을 하는 것을 기반으로 한다.

  1. 웹 브라우저가 웹 서버에 웹 애플리케이션 실행을 요청한다. (MVC구조가 WAS라고 보면 된다)
  2. 웹 서버는 들어온 요청을 처리할 수 있는 서블릿을 찾아서 요청을 전달한다.(Matching)
  3. 서블릿은 모델 자바 객체의 메서드를 호출한다.
  4. 데이터를 가공해서 값 객체를 생성하거나, JDBC를 사용해 데이터베이스와의 인터랙션을 통한 값 객체를 생성한다.
  5. 업무 수행을 마친 결과값을 컨트롤러에게 반환한다.
  6. 컨트롤러는 모델로부터 받은 결과값을 View에게 전달한다.
  7. JSP는 전달받은 값을 참조하여 출력할 결과 화면을 만들고 다시 컨트롤러에게 전달한다.
  8. 뷰로부터 받은 화면을 웹 서버에게 전달한다.
  9. 웹 브라우저는 웹 서버로부터 요청한 결과값을 받으면 그 값을 화면에 출력한다.

참조

https://asfirstalways.tistory.com/180

Spring MVC


위로

MVC 패턴을 적용한 Spring framework

Spring에는 개발자들이 일반적으로 사용할만한 기능과 운영 방식들이 편리하게 정의되어 있다.

Spring MVC에는 스프링이 제공하는 모든 기능을 잘 활용하기 위해서 스프링이 내가 작성한 자바 bean(객체)을 관리할 수있도록 해야 한다.

Spring Framework 내부에는 IOC 컨테이너가 존재해 내가 등록한 bean(객체)을 생성하고 관리해준다. -> 결합도(Coupling)을 낮출 수 있게 된다.

이를 위해 Dispatcher Servlet 이라는 Front-Controller 패턴의 Servlet을 가장 앞단에 둬서 컨테이너로 들어오는 모든 요청을 적절한 세부 컨트롤러로 작업을 위임해준다.

구성

  • DispatcherServlet
    • 클라이언트의 모든 요청을 처리하기 위해 첫 번째로 실행되는 서블릿
    • Dispatcher가 받은 요청은 HandlerMapping으로 넘어간다.
  • HandlerMapping
    • 클라이언트가 요청한 path를 분석해 어떤 컨트롤러를 실행해야 하는지 찾아서 DispatcherServlet으로 넘겨주는 객체
  • Controller
    • 클라이언트의 요청을 처리하는 객체
    • DAO의 메소드를 호출하는 기능을 정의
  • ModelAndView
    • Controller에서 DAO의 메소드를 실행결과로 만들어진 데이터에 대한 정보나 응답할 View에 대한 정보를 갖고 있는 객체
  • ViewResolver
    • ModelAndView에서 저장된 view의 정보를 이용해서 실제 어떤 view를 실행해야 하는지 정보를 넘겨주는 객체

스프링 MVC를 구축하면, 위 클래스 들이 자동으로 실행되며 일처리를 한다.

필요에 따라 ViewResolver나 HandlerMapping 객체를 다양하게 등록하고 사용할 수 있다.

Dispatcher-Servlet

서블릿 컨테이너에서 HTTP 프로토콜을 통해 들어오는 모든 요청을 프레젠테이션 계층의 제일 앞에 둬서 중앙집중식으로 처리해주는 프론트 컨트롤러(Front Controller)

클라이언트로부터 어떠한 요청이 오면 Tomcat과 같은 서블릿컨테이너가 요청을 받는 데, 이 때 **제일 앞에서 서버로 들어오는 모든 요청을 처리하는 Front Controller**를 Spring에서 정의하였고, 이를 Dispatcher-Servlet이라고 한다.

그래서 공통처리 작업을 DispatcherServlet이 처리한 후 적절한 세부 컨트롤러로 작업을 위임해준다.

물론 Dispathcer-Servlet이 처리하는 URL 패턴을 지정 해줘야 하는데 일반적으로 /*.do와 같이 /로 시작하며 .do로 끝나는 URL패턴에 대해서 처리하라고 지정되어 있다.

흐름 & 장점

Spring MVC는 Dispatcher-Servlet이 등장함에 따라 web.xml의 역할을 상당히 축소시켜주었다.

기존에는 모든 서블릿에 대해 URL Mapping을 활용하기 위해 web.xml에 모두 등록해줘야 했지만, dispatcher-servlet이 해당 어플리케이션으로 들어오는 모든 요청을 핸들링해주면서 작업을 상당히 편리하게 할 수 있게 되었다.

그리고 이 서블릿을 이용한다면 MVC 역시 사용할 수 있게 되어 좋다.

image-20210609150000076

위 그림과 같이 Dispatcher Servlet이 모든 요청을 Controller로 넘겨주는 방식으 효율적으로 보이나, 모든 요청을 처리하다 보니 이미지나 HTML 파일을 불러오는 요청마저 전부 Controller로 넘겨버린다.

게다가, .jsp 파일 안의 Javascript나 StyleCSS 파일들에 대한 요청들까지도 모두 디스패처서블릿이 가로채는 까닭에 자원을 불러오지 못하는 상황도 발생할 수 있다.

이에 대한 해결책 두 가지가 있다.

첫 째로, 클라이언트의 요청을 2가지로 분리하여 구분하는 것이다.

  1. /appsURL로 접근하면 Dispatcher Serlvet이 담당한다.
  2. /resourcesURL로 접근하면 Dispatcher Servlet이 컨트롤할 수 없으므로 담당하지 않는다.

이러한 방식은 괜찮지만 상당히 코드가 지저분해지며 모든 요청에 대해 저런 prefix URL을 붙여주기 때문에 직관적인 설계가 될 수 없다.

두번째 방법으로 모든 요청을 컨트롤러에 등록하는 것인데, 이는 상당히 무식한 방법이다.

Spring은 이러한 문제들을 해결함과 동시에 편리한 방법을 제공해주는데, 바로 <mvc:resources />를 이용한 방법이다. 이것은 만약 디스패처 서블릿에 해당 요청에 대한 컨트롤러를 찾을 수 없는 경우, 2차적으로 설정된 경로에서 요청을 탐색하여 자원을 찾아내는 것이다.

이렇게 영역을 분리하면 효율적인 리소스 관리를 지원할 뿐 아니라 추후에 확장을 용이하게 해준다는 장점이 있다.

참조 : https://mangkyu.tistory.com/18?category=761302

IoC Container


위로

기본적으로 스프링의 IoC 컨테이너는 스프링 애플리케이션에서 'Object 생성', '관계 설정', '오브젝트 사용 및 제거' 등의 역할을 수행하는 컨테이너를 의미한다.

스프링에서는 '빈 팩토리', '애플리케이션 컨텍스트'라고도 하고, 아주 간단한 표현으로 Application Context 인터페이스를 구현한 클래스 오브젝트라고도 한다.

편의상 일반적으로 IoC컨테이너를 애플리케이션 컨텍스트라고 칭하고, 스프링을 개발할 때 이 애플리케이션 컨텍스트를 계층 구조로 만드는 경우가 많다.

애플리케이션 컨텍스트의 계층구조는 부모역할을 하는 **root-application context(루트 애플리케이션 컨텍스트)**와, **servlet-applicaiton context(서블릿 애플리케이션 컨텍스트)**로 구성하며 계층 구조 안에 모든 컨텍스트는 각자 독립적인 설정정보를 이용해서 Bean Object를 만들고 관리한다.

스프링 애플리케이션에서 애플리케이션 컨텍스트 계층구조를 구현할 때 사용하는 방법에는 아래와 같다.

  • root-application context
    • ContextLoaderListener 등록
    • 디폴트 설정 파일 위치가 아닌 변경시 <context-param>으로 변경
  • servlet-application context
    • DispatcherServlet 등록
    • 디폴트 설정 파일 위치가 아닌 변경시 <init-param>으로 변경

정리하자면,web.xml<context-param>에 해당하는 xml 파일에 의해 등록되는 빈은 루트 애플리케이션 컨텍스트에서 등록-관리 되는 빈이고, dispatcher-servlet.xml을 통해서 등록되는 빈은 서블릿 애플리케이션 컨텍스트에서 등록-관리되는 빈이다.

이 두 개의 IoC 컨테이너는 계층구조로 만들어진 별개의 IoC 컨테이너 인것이다.

그러면 왜 굳이 이렇게 IoC 컨테이너를 나누어서 사용하는가??

IoC 컨테이너의 종류

  • StaticApplicationContext
    • StaticApplicationContext는 코드를 통해 빈 메타정보를 등록하기 위해 사용한다.
    • 스프링의 기능에 대한 학습 테스트를 만들 때를 제외하면 실제로 사용되지 않는다.
    • 웹 관련 기능을 공부하며 학습 테스트로 검증하고 싶을 때는 StaticWebApplicationContext를 사용한다.
  • GenericApplicationContext
    • 실전에서 사용될 수 있는 모든 기능을 갖추고 있는 애플리케이션 컨텍스트이다.
    • StaticApplicationContext와 달리 xml파일과 같은 외부의 리소스에 있는 빈 설정 메타정보를 리더를 통해 읽어들여서 메타정보로 전환해 사용한다.
  • GenericXmlApplicationContext
    • GenericApplicationContext에서 XmlBeanDefinitionReader를 내장하고 있어, xml을 읽어 사용할때 편리하다.
  • WebApplicationContext
    • 스프링 애플리케이션에서 가장 많이 사용된다.
    • XML설정 파일을 사용하는 경우에는 XmlWebApplicationContext를 사용하며, 애노테이션을 사용한다면 AnnotationConfigWebApplicationContext를 사용한다.

웹 환경 애플리케이션 구조

image-20210610155159651

웹 환경에서의 스프링은 클라이언트 요청을 전부 받는 'FrontController', 'DispatcherServlet'을 제공한다.

'DispatcherServlet'은 자체적으로 Application Context를 생성하고 Root Application Context를 부모로 등록한다.

> Application Context

공통 기능을 할 수 있는 Bean 설정(DataSource, Service 등..)

각 서블릿에서 공유할 수 있는 Bean!!

  • Web Application 최상단에 위치하고 있는 Context
  • Spring ApplicationContext란 BeanFactory를 상속받고 있는 Context
  • Spring에서 root-context.xml, applicatoinContext.xml 파일은 Application Context 생성 시 필요한 설정정보를 담은 파일(Bean 선언 등..)
  • Spring에서 생성되는 Bean에 대한 IoC Container(또는 Bean Container)
  • 특정 Servlet 설정과 관계 없는 설정을 한다.(@Service, @Repository, @Configuration, @Component)
  • 서로 다른 여러 Servlet에서 공통적으로 공유해서 사용할 수 있는 Bean을 선언한다.
  • Appliction Context에 정의된 BeanServlet Context에서 정의된 Bean을 사용할 수 없다.

> Servlet Context

servlet.context.xml

Servlet 구성에 필요한 Bean 설정(Controller, Interceptor, HandlerMapping ..)

  • Servlet 단위로 생성되는 Context
  • Spring에서 servlet-context.xml 파일은 DispatcherServlet 생성 시에 필요한 설정 정보를 담은 파일 (Interceptor, Bean생성, ViewResolver 등...)
  • URL설정이 있는 Bean을 생성(@Controller, Interceptor)
  • Application Context를 자신의 부모 Context로 사용한다.
  • Application Context와 Servlet Context에 같은 id로 된 Bean이 등록 되는 경우, Servlet Container에 선언된 Bean을 사용한다.
  • Bean 찾는 순서
    1. Servlet Context에서 먼저 찾는다.
    2. 만약 Servlet Context에서 Bean을 못 찾은 경우 Application Context에 정의된 Bean을 찾는다.
  • Servlet Context에 정의된 BeanApplication Context에서 정의된 Bean을 사용할 수 있다.

> web.xml

서블릿 클래스는 JSP와 달리 설치 뿐 만 아니라, 등록을 하는 과정이 필요로 하다.

여기서 서블릿 클래스를 등록하는 곳의 이름을 Web Application depolyment descripor(DD-deplyment Descriptor)라고 하는데 이 역할을 하는 것이 바로 **web.xml**이다.

web.xml파일은 웹 애플리케이션 디렉터리 마다 딱 하나씩 만 존재할 수 있다.

DD는 WAS 구동 시 /WEB-INF 디렉토리에 존재하는 web.xml을 읽어 웹 애플리케이션의 설정을 구성하기 위해 존재한다.

IoC 컨테이너 계층구조

IoC 컨테이너는 계층구조로 구현할 수 있다. 각자 독립적으로 빈을 갖고 있으며, 자신의 어플리케이션 컨텍스트에 빈이 존재하지 않을 경우, 부모 어플리케이션 컨텍스트에서 빈을 찾는다.

중요한 점은 자식 어플리케이션 컨텍스트에서는 탐색하지 않는다는 점이다.

미리 만들어진 어플리케이션 컨텍스트의 설정을 그대로 가져다가 사용하면서 그 중 일부 빈만 설정을 변경하고 싶다면, 어플리케이션 컨텍스트를 두 개 만들어서 하위 컨텍스트에서 바꾸고 싶은 빈들을 설정해줘도 된다.

일반적인 Web Application의 IoC컨테이너 구성

image-20210610155556723

image-20210610155858020

DispatcherServlet은 자체적으로 ApplicationContext를 생성하고 사용한다. 이를 ServletContext라고도 부른다.

이외에도 RootApplicationContext가 하나 존재하는데, 이는 스프링 외의 기술을 사용하는 Ajax Engine, JSP 등에서 Spring IoC의 기능을 사용할 수 있도록 하기 위함이다.

스프링 밖의 어디서라도 WebApplicationContextUtils.getWebApplicationContext(ServletContext sc) 을 호출하면 RootApplicationContext를 가져올 수 있다.

참조

https://jaehun2841.github.io/2018/10/21/2018-10-21-spring-context/#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0

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

Component Scan


위로

xml 설정파일에 서 모든 빈을 으로 일일이 등록해줘야 하는 것은 매우 번거로운 일이다

그래서 등장한것이 Component Scan이다.

test.xml

<context:component-scan base-package="com.multi.app" />

위와 같이 내부 filter 태그가 없다면, base-package에 지정한 패키지에서부터 모든 하위 패키지를 scanning해서 빈을 등록하도록 한다.

모든 클래스를 빈으로 등록하는 것은 아니고, @Component Annotation이 붙은 클래스를 빈으로 등록한다.

ApplicationContext.xml

<context:component-scan base-package="com.myapp.core, com.myapp.app">
    <!-- Component-scan대상에서 @Controller annotation Class는 제외한다. -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 
</context:component-scan>

Servlet-Context.xml

<context:component-scan base-package="com.myapp.app" use-default-filters="false">
    <!-- Component-scan대상은 @Controller annotation Class만 scan한다. -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 
</context:component-scan>

위를 보면 Servlet-Context.xml 설정 시, use-default-filters 속성을 false로 처리 하였다.

use-default-filters 속성은 원래 defaulttrue인데, @Component Annotation(@Controller, @Service, @Repository 등..)의 클래스를 자동으로 Bean으로 등록해주는 filter 속성이다.

따라서 위의 filter를 false로 변경하고 scan할 대상에 대한 Annotation만 include-filter에 추가하였다.

DI


위로

의존관계 주입

스프링 컨테이너가 지원하는 핵심 개념중 하나로, 설정파일을 통해 객체간의 의존관계를 설정

이는, 스프링에서만 사용되는 용어가 아니라 객체지향 프로그래밍에서 통용되는 개념이다.

강한 결합

객체 내부에서 다른 객체를 생성하는 것은 강한 결합도를 가지는 구조이다.

A클래스 내부에서 B라는 객체를 직접 생성하고 있다면, B객체를 C객체로 바꾸고 싶은 경우, A클래스도 수정해야 하는 방식이고, 이를 강한결합이라 한다.

느슨한 결합

객체를 주입받는 다는 것은 외부에서 생성된 객체를 인터페이스를 통해 넘겨받는 것이다.

이렇게하면 결합도를 낮출수 있고, 런타임 시에 의존관계가 결정되기 때문에 유연한 구조를 가진다.

SOLID의 원칙 중 O에 해당하는 Open Closed Principle을 지키기 위해서 디자인 패턴 중 전략패턴을 사용하게 되는데, 생성자 주입을 사용하게 되면 전략패턴을 사용하는 것이다.

스프링 프레임워크에서는 필드주입이나, 수정자 주입 방법 보다, 생성자 주입을 더 권장하고 있다. 그 이유를 알아보자.

생성자 주입

Constructor Injection

Spring Framework 4.3 버전 부터는 의존성 주입으로부터 클래스를 완벽하게 분리할 수 있다.

단일 생성자인 경우에는 @Autowired 어노테이션을 붙이지 않아도 되지만 생성자가 2개 이상인 경우에는 생성자에 어노테이션을 붙여주어야 한다.

@Component
public class MyExample {
    // final로 선언할 수 있는 보너스
    private final HelloService helloService;
    
    // 단일 생성자인 경우 추가적인 어노테이션이 필요 없다.
    public MyExample(HelloService helloService) {
        this.helloService = helloService;
    }
}

필드 주입

Field Injection

사용법이 매우 간단하다. 사용하고자 하는 필드에 @Autowired 어노테이션을 붙여주면 자동으로 의존성이 주입된다.

편리하기 때문에 가장 많이 접할 수 있는 방법이다.

@Component
public class MyExample {
    @Autowired
    private HelloService helloService;
}

수정자 주입

Setter Injection

수정자(Setter)를 이용한 주입 방법도 있다.

꼭 setter 메서드일 필요는 없다. 메서드 이름이 수정자 네이밍 패턴(setXXX)이 아니어도 동일한 기능을 하면된다.

그래도 일관성과 명확환 코드를 만들기 위해 정확한 이름을 사용하는 것을 추천한다.

@Component
public class MyExample {
    private HelloService helloService;
    
    @Autowired
    public void setHelloService(HelloService helloService) {
        this.helloService = helloService;
    }
}

대부분, 코드에서 @Autowired 어노테이션을 필드에 붙여 사용하는 필드 주입 코드를 많이 봤을 것이다. 이는 사용하기 편리하기 때문일 것인데, 스프링팀에는 생성자 주입 방법을 권장하고 있다. 그 이유는 무엇일까?

왜 생성자 주입을 권장할까

그렇다면 왜 생성자 주입 방법을 더 권장하는 이유는 무엇일까? @Autowired 어노테이션만으로 간단하게 의존성을 주입할 수 있는데 말이다. 필드 주입이나 수정자 주입 방법과 다르게 생성자 주입 방법이 주는 장점에 대해서 살펴보자.

> 순환참조 방지

개발을 하다 보면 여러 컴포넌트 간에 의존성이 생긴다. 그중에서도 A가 B를 참조하고, B가 다시 A를 참조하는 순환 참조도 발생할 수 있는데 아래 코드를 통해 어떤 경우인지 살펴보자.

우선 두 개의 서비스 레이어 컴포넌트를 정의한다. 그리고 서로 참조하게 한다. 조금 더 극단적인 상황(?)을 만들기 위해서 순환 참조하는 구조에 더불어 서로의 메서드를 순환 호출하도록 한다.

그러니까 빈이 생성된 후에 비즈니스 로직으로 인하여 서로의 메서드를 순환 참조하는 형태이다. 실제로는 이러한 형태가 되어서는 안되며, 직접적으로 서로를 계속해서 호출하는 코드는 더더욱 안된다. “순환 참조가 되면 이럴 수도 있구나~”라고 생각하자.

@Service
public class MyPlayService {
    // 순환 참조
    @Autowired
    private MyLifeService myLifeService;
    
    public void sayMyPlay() {
        myLifeService.sayMyLife();
    }
}
@Service
public class MyLifeService {
    // 순환 참조
    @Autowired
    private MyPlayService myPlayService;
    
    public void sayMyLife() {
        myPlayService.sayMyPlay();
    }
}

위 코드는 애플리케이션이 아무런 오류나 경고 없이 구동되고, 실제 코드가 호출되기 전까지 문제를 발견할 수 없다.

그렇다면 생성자 주입을 사용한 경우에는 어떻게 될까?

@Service
public class MyPlayService {
    private final MyLifeService myLifeService;
    
    public MyPlayService(MyLifeService myLifeService) {
        this.myLifeService = myLifeService;
    }
    
    // 생략
}
@Service
public class MyLifeService {
    private final MyPlayService myPlayService;
    
    public MyLifeService(MyPlayService myPlayService) {
        this.myPlayService = myPlayService;
    }
    
    // 생략
}

실행 결과는 BeanCurrentlyInCreationException이 발생하며 애플리케이션이 구동조차 되지 않는다. 따라서, 발생할 수 있는 오류를 사전에 알 수 잇다.

> 테스트에 용이

> 좋은 품질의 코드

> 불변성

정리

정리해보면 아래와 같은 이유로 필드주입이나 수정자 주입보다 생정자 주입의 사용이 권장된다.

  • 순환 참조를 방지할 수 있다.
    • 순환 참조가 발생하는 경우 애플리케이션이 구동되지 않는다.
    • NPE 방지
  • 테스트 코드 작성이 편리하다.
    • 단순 POJO를 이용한 테스트 코드를 만들 수 있다.
  • 나쁜 냄새를 없앤다.
    • 더 품질 좋은 코드를 만들 수 있다.
  • immutable 하다.
    • 의존성 주입이 필요한 필드를 final로 선언가능하다.
    • 실행 중에 객체가 변하는 것을 막을 수 있다.
    • 오류를 사전에 방지할 수 있다.

참조 : https://madplay.github.io/post/why-constructor-injection-is-better-than-field-injection

AOP


위로

Aspect Oriented Programming (측면/양상 지향적 프로그래밍)

" 공통의 관심사항을 적용해서 발생하는 의존 관계의 복잡성과 코드 중복을 해소 "

즉, 여러군데서 사용되는 중복되는 코드를 떼어내서 분리하고 각 메소드는 자신이 해야할 작업만 갖고 있자는 개념이다.

여기서 **여러군데서 사용되는 중복되는 코드(부가 기능)**가 AOP에서 말하는 Aspect이다.

각 클래스에서 공통 관심 사항을 구현한 모듈에 대한 의존관계를 갖기 보단, Aspect를 이용해 핵심 로직을 구현한 각 클래스에 공통 기능을 적용한다.

간단한 설정만으로도 공통 기능을 여러 클래스에 적용할 수 있는 장점이 있으며 핵심 로직 코드를 수정하지 않고도 웹 애플리케이션의 보안, 로깅, 트랜잭션과 같은 공통 관심사항을 AOP를 이용해 간단하게 적용가능하다.

image-20210610103947651

  • 핵심기능 : 업무 로직을 포함하는 기능

  • 부가기능 : 핵심 기능을 도와주는 부가적인 기능(로깅, 보안 등)

  • target : 핵심 기능을 담고 있는 모듈로 타겟은 부가기능을 부여한 대상

  • Advice : 타겟에 제공할 부가기능을 담고 있는 모듈

  • Joinpoint

    • Advice를 적용이 가능한 지점을 의미(before, after 등등)
    • 타겟 객체가 구현한 인터페이스의 모든 메서드는 Jointpoint가 된다.
  • Pointcut

    • Jointpoint의 부분집합으로, 실제로 Advice가 적용되는 Joinpoint를 나타냄.
    • Advice를 적용할 타겟의 메서드를 선별하는 정규표현식
    • 표현식은 execution으로 시작하고 메서드의 Signature를 비교하는 방법을 주로 이용한다.
  • Weaving

    • Pointcut에 의해서 결정된 타겟의 조인 포인트에 부가기능(Advice)를 삽입하는 과정
    • AOP가 핵심기능(Target)의 코드에 영향을 주지 않으면서 필요한 부가기능(Advice)을 추가할 수 있도록 해주는 핵심적인 처리과정
  • Aspect

    • 여러 객체에 공통으로 적용되는 공통 관심사항을 말함. 트랜잭션이나 보안 등이 Aspect의 좋은 예
    • AOP의 기본 모듈
    • Advice + Pointcut
    • Aspect는 싱글톤 형태의 객체로 존재한다.

AOP란?

객체지향의 기본원칙을 적용하여도 핵심기능에서 부가기능을 분리해서 모듈화하는 것은 매우 어렵다.

AOP는 애플리케이션에서의 관심사의 분리(기능의 분리), 핵심적인 기능에서 부가적인 기능을 분리한다.

분리한 부가기능을 Aspect라는 독특한 모듈형태로 만들어서 설계하고 개발하는 방법이다.

  • OOP를 적용하여도 핵심기능에서 부가기능을 쉽게 분리된 모듈로 작성하기 어려운 문제점을 AOP가 해결해준다고 볼 수 있다.

  • AOP는 부가기능을 Aspect로 정의하여 핵심기능에서 부가기능을 분리함으로써 핵심기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 개념이다.

Spring AOP 특징

  1. Spring은 프록시 기반 AOP를 지원한다.
    • Spring은 타겟(tartget) 객체에 대한 프록시를 만들어 제공한다.
    • 타겟을 감싸는 프록시는 실행시간(Runtime)에 생성된다.
    • 프록시는 어드바이스를 타겟 객체에 적용하면서 생성되는 객체이다.
  2. 프록시(Proxy)가 호출을 가로챈다.(Intercept)
    • 프록시는 타겟 객체에 대한 호출을 가로챈 다음 어드바이스의 부가기능 로직을 수행하고 난 후에 타겟의 핵심기능 로직을 호출한다.(전처리 Advice)
    • 또는 타겟의 핵심기능 로직 메서드를 호출한 후에 부가기능(어드바이스)을 수행하는 경우도 있다.(후처리 Advice)
  3. Spring AOP는 메서드 조인 포인트만 지원한다.
    • Spring은 동적 프록시를 기반으로 AOP를 구현하므로 메서드 조인포인트만 지원한다.
    • 핵심기능(타겟)의 메서드가 호출되는 런타임 시점에만 부가기능(어드바이스)을 적용할 수 있다.
    • 반면에 AspectJ같은 고급 AOP 프레임워크를 사용하면 객체의 생성, 필드값의 조회와 조작, static 메서드 호출 및 초기화 등 다양한 작업에 부가기능을 적용할 수 있다.

Spring AOP 구현방식

  1. XML 기반의 POJO 클래스를 이용한 AOP 구현
    • 부가기능을 제공하는 Advice 클래스를 작성한다.
    • XML 설정 파일에 <aop:config>를 이용해서 Aspect를 설정한다. (즉, Advice와 Pointcut을 설정하는 것)
  2. @Aspect 어노테이션을 이용한 AOP 구현
    • @Aspect 어노테이션을 이용해서 부가기능을 제공하는 Aspect 클래스를 작성한다.
    • 이 떄 Aspect 클래스는 어드바이스를 구현하는 메서드와 Pointcut을 포함한다.
    • XML 설정 파일에 <aop:aspectj-autoproxy/>를 설정한다.

Advice의 종류

  • Around 어드바이스
    • 타겟의 메서드가 호출되기 이전(before) 시점과 이후(after) 시점에 모두 처리해야 할 필요가 있는 부가기능을 정의
    • Jointpoint 앞과 뒤에서 실행되는 Advice
  • Before 어드바이스
    • 타겟의 메서드가 실행되기 이전(before) 시점에 처리해야 할 필요가 있는 부가기능을 정의
    • Jointpoint앞에서 실행되는 Advice
  • After Returning 어드바이스
    • 타겟의 메서드가 정상적으로 실행된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능을 정의
    • Jointpoint 메서드 호출이 정상적으로 종료된 뒤에 실행되는 Advice
  • After Throwing 어드바이스
    • 타겟의 메서드가 예외를 발생한 이후(after) 시점에 처리해야 할 필요가 있는 부가기능을 정의
    • 예외가 던져질때 실행되는 Advice

Advice 태그

  • <aop:before>
    • 메서드 실행전에 적용되는 어드바이스 정의
  • <aop:after-returning>
    • 메서드가 정상적으로 실행된 후에 적용되는 어드바이스 정의
  • <aop:after-throwing>
    • 메서드가 예외를 발생시킬 때 적용되는 어드바이스 정의
    • try-catch 블록에서의 catch와 비슷하다.
  • <aop:after>
    • 메서드가 정상적으로 실행되는지 또는 예외를 발생시키는지 여부에 상관없이 어드바이스를 저으이
    • try-catch-finally 블록에서 finally와 비슷하다.
  • <aop:around>
    • 메서드 호출 이전, 이후 예외발생 등 모든 시점에 적용 가능한 어드바이스 정의

프록시 패턴

String AOP는 프록시 패턴이라는 디자인 패턴을 사용해서 AOP의 효과를 낸다.

프록시 패턴을 사용하면 어떤 기능을 추가하려 할 때 기존 코드를 변경하지 않고 기능을 추가할 수 있다.

어떤 클래스가 Spring AOP의 대상이라면 그 기존 클래스의 빈이 만들어질 때 Spring AOP가 프록시(기능이 추가된 클래스)를 자동으로 만들고 원본 클래스 대신 프록시를 빈으로 등록한다.

그리고 원본 클래스가 사용되는 지점에서 프록시를 대신 사용한다.

아래 예제의 @Transactional 어노테이션이 이에 해당한다.

image-20210610112017582

@Transactional 어노테이션이 붙어있으면 OwnerRepository 타입의 프록시가 새로 만들어지고 Spring AOP에 의해 자동으로 생성되는 OwnerRepository의 프록시에는 @Transactional 어노테이션이 지시하는 코드가 삽입된다.

**@Transactional**에 의해 추가되는 기능은 다음과 같다.

JDBC에서 트랜잭션 처리를 하려면 SQL 실행문 앞뒤에 setAutoCommit()commit() / rollback() 코드가 항상 붙는데 @Transactional 어노테이션은 프록시에 자동으로 그 코드를 넣어서 반복, 중복되는 코드를 생략할 수 있게 한다.

이로 인해 개발자는 비즈니스 로직에만 집중할 수 있게 되는 것이다.

참조

https://atoz-develop.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-AOP-%EA%B0%9C%EB%85%90-%EC%9D%B4%ED%95%B4-%EB%B0%8F-%EC%A0%81%EC%9A%A9-%EB%B0%A9%EB%B2%95

https://shlee0882.tistory.com/206

Annotation


위로

소스코드에 @어노테이션의 형태로 표현하며, 클래스, 필드, 메소드의 선언부에 적용할 수 있는 특정기능이 부여된 표현법

애플리케이션의 규모가 커질수록, xml환결설정이 매우 복잡해지는데 , 이를 개선하기 위해 자바 파일에 어노테이션을 적용해서 개발자가 설정 파일 작업을 할때 발생시키는 오류를 최소화해주는 역할을 한다.

어노테이션의 사용으로 소스 코드에 메타데이터를 보관할 수 있고, 컴파일 타임의 체크 뿐 아니라 어노테이션 API를 사용해 코드 가독성도 높여줄 수 있다.

  • @Controller : Dispatcher-Servlet.xml에서 bean 태그로 정의하는 것과 같은 역할
  • @RequestMapping : 특정 메소드에서 요청되는 URL과 매칭시키는 어노테이션
  • @Autowired : 자동으로 의존성 주입(필드 주입)하기 위한 어노테이션
  • @Service : 비즈니스 로직 처리하는 서비스 클래스에 등록
  • @Repository : DAO에 등록

DAO & DTO & Entity


위로

image-20210610132928215

DAO

Data Access Object

repository package

image-20210610140801515

  • 실제로 DB에 접근하는 객체

    • Persistance Layer(DB에 dat를 CRUD하는 계층)이다.
  • Service와 DB를 연결하는 고리의 역할

  • SQL을 사용해 (개발자가 직접 코딩) DB에 접근한 후 적절한 CRUD API를 제공한다.

    • JPA 대부분의 기본적인 CRUD method를 제공하고 있다.

    • extends JpaRepository<User, Long>

    • public interface QuestionRepository extends CrudRepository<Question, Long> {
          
      }
  • "Object" 단위 -> (SQL을 이용한 CURD) -> DB의 "Record" 단위로 저장 되는 순서

    • Obejct와 Record 간의 miss match가 발생할 수 있는데, 이를 해결해줘야 한다.

DTO

Data Transfer Object

dto package

  • 계층간 데이터 교환을 위한 객체(Java Beans)이다.

    • DB에서 데이터를 얻어 Service나 Controller 등으로 보낼 때 사용하는 객체를 말한다.
    • 즉, DB의 데이터가 Presentation Logic Tier로 넘어오게 될 때는 DTO의 모습으로 바껴서 오고 가는 것이다.
    • 로직을 갖고 있지 않는 순수한 데이터 객체이며, getter/setter 메소드만을 갖는다.
    • 하지만 DB에서 꺼낸 값을 임의로 변경할 필요가 없기 때문에 DTO클래스에는 setter가 없다. (대신 생성자에서 값을 할당한다.)
  • **RequestResponse**용 DTO는 view를 위한 클래스

    • 자주 변경이 필요한 클래스
    • Presentation Model
    • toEntity() 메서드를 통해서 DTO에서 필요한 부분을 이용해 Entity로 만든다.
    • 또한 Controller Layer에서 Response DTO 형태로 Client에 전달한다.
  • VO(Value Object)??

    • VO는 DTO와 동일한 개념이지만 read only 속성을 가진다.
    • VO는 특정한 비즈니스 값을 담는 객체이고, DTO는 Layer간의 통신 용도로 오고가는 객체를 말한다.
  • 예시

    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public class UserDto {
      @NotBlank
      @Pattern(regexp = "^([\\w-]+(?:\\.[\\w-]+)*)@((?:[\\w-]+\\.)*\\w[\\w-]{0,66})\\.([a-z]{2,6}(?:\\.[a-z]{2})?)$")
      private String email;
    
      @JsonIgnore
      @NotBlank
      @Size(min = 4, max = 15)
      private String password;
    
      @NotBlank
      @Size(min = 6, max = 10)
      private String name;
    
      public User toEntity() {
          return new User(email, password, name);
      }
    
      public User toEntityWithPasswordEncode(PasswordEncoder bCryptPasswordEncoder) {
          return new User(email, bCryptPasswordEncoder.encode(password), name);
      }
    }

Entity Class

domain package

  • 실제 DB의 테이블과 매칭될 클래스

    • 즉, 테이블과 링크될 클래스임을 나타낸다.
    • Entity 클래스 또는 가장 Core한 클래스라고 부른다.
    • @Entity, @Column, @Id 등을 이용
  • 최대한 외부에서 Entity 클래스의 getter method를 사용하지 않도록 해당 클래스 안에서 필요한 logic method를 구현한다.

    • 단, Domain Logic만 가지고 있어야 하고 Presentation Logic을 가지고 있어서는 안된다.
    • 여기서 구현한 method는 주로 Service Layer에서 이용된다.
  • Entity Class와 DTO Class를 분리하는 이유??

    • View Layer와 DB Layer의 역할을 철저하게 분리하기 위해서

    • 테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 View와 통신하는 DTO 클래스 (Request / Response 클래스) 는 자주 변경되므로 분리해야 한다.

    • Domain Model을 아무리 잘 설계했다고 해도 각 View 내에서 Domain Model의 getter만을 이용해서 원하는 정보를 표시하기가 어려운경우가 종종 있다. 이런 경우 Domain Model 내에 Presentation을 위한 필드나 로직을 추가하게 되는데, 이러한 방식이 모델링의 순수성을 깨고 Domain Model 객체를 망가뜨리게 된다.

    • 또한 Domain Model을 복잡하게 조합한 형태의 Presentation 요구사항들이 있기 때문에 Domain Model을 직접 사용하는 것은 어렵다.

    • 즉, DTO는 Domain Model을 복사한 형태로, 다양한 Presentation Logic을 추가한 정도로 사용하며 Domain Model의 객체는 Persistent만을 위해서 사용한다.

    • 예시

      @Entity
      @Getter
      @AllArgsConstructor
      @NoArgsConstructor
      @EqualsAndHashCode
      @ToString
      public class User implements Serializable {
        private static final long serialVersionUID = 7342736640368461848L;
      
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @JsonProperty
        private Long id;
      
        @Column(nullable = false)
        @JsonProperty
        private String email;
      
        @Column(nullable = false)
        @JsonIgnore
        private String password;
      
        // @Override 
        // public boolean equals(Object o) { ... }
        // @Override
        // public int hashCode() { ... }
        // @Override
        // public String toString() { ... }

패키지의 전체 구조

image-20210610135314187

> Controller

  • 기능

    • 해당 요청 url에 따라 적절한 view와 mapping 처리
    • @Autowired Service를 통해 service와 method를 이용
    • 적절한 ResponseEntity(DTO)를 body에 담아 Client에 반환
  • @Controller

    • API와 view를 동시에 사용하는 경우 사용
    • 대신 API 서비스로 사용하는 경우에는 @ResponseBody를 사용하여 객체를 반환
    • view(화면) return이 주 목적이다.
    • 예시 1
    @Controller
    @RequestMapping("/")
    public class HomeController {
      @GetMapping
      public String home(HttpSession session) {
          if (!SessionUtil.getUser(session).isPresent()) {
              return "login";
          }
          return "index";
      }
    }
  • @RestContoller

    • view가 필요없는 API만 지원하는 서비스에서 사용 (Spring 4.0.1부터 제공한다)
    • @RequestMapping 메서드가 기본적으로 @ResponseBody 의미를 가정한다.
    • data(json, xml 등) return이 주 목적 : return ResponseEntity
    • 즉, @RestController = @Controller + @ResponseBody
    • 예시 2
    @RestController
    @RequestMapping("/api/users")
    public class ApiUserController {
      @Autowired
      private UserService userService;
    
      @PostMapping("/login")
      public ResponseEntity login(@RequestBody @Valid LoginDto loginDto, HttpSession session) {
          SessionUtil.setUser(session, userService.login(loginDto));
          return new ResponseEntity(HttpStatus.OK);
      }
    }

> Service

  • 기능

    • @Autowired Repository를 통해 repository의 method를 이용
    • 적절한 비즈니스 로직을 처리한다.
    • DAO로 DB에 접근하고 DTO로 데이터를 전달받은 다음, 비즈니스 로직을 처리해 적절한 데이터를 반환
  • 예시

    @Service
    public class UserService {
      @Autowired
      private UserRepository userRepository;
      @Resource(name = "bCryptPasswordEncoder")
      private PasswordEncoder bCryptPasswordEncoder;
      @Autowired
      private MessageSourceAccessor msa;
    
      public User save(UserDto userDto) {
          if (isExistUser(userDto.getEmail())) {
              throw new UserDuplicatedException(msa.getMessage("email.duplicate.message"));
          }
          return userRepository.save(userDto.toEntityWithPasswordEncode(bCryptPasswordEncoder);
      }
    }

> Repository(dao)

  • 기능

    • 실제로 DB에 접근하는 객체
    • Service와 DB를 연결하는 고리
    • SQL을 사용해서 DB에 접근한 후 CRUD API를 제공
      • JPA 대부분 기본적인 CRUD Method를 제공
  • 예시 (JPA 의 경우)

    public interface UserRepository extends JpaRepository<User, Long> {
        
    }

참조

https://gmlwjd9405.github.io/2018/12/25/difference-dao-dto-entity.html

JDBC


위로

JDBC는 자바에서 DB를 활용할 수 있도록 지원하는 API이다.

데이터 베이스 테이블과, 자바 객체 사이의 단순한 매핑을 간단한 설정을 통해 처리한다.

기존의 JDBC에서는 구현하고 싶은 로직마다 필요한 SQL문이 모두 달랐고, 이에 필요한 Connection, PrepareStatement, ResultSet 등을 생성하고 Exception 처리도 모두 해야 하는 번거로움이 존재했다.

때문에 Spring에서는 JDBC와 ORM 프레임워크를 직접 지원하기 때문에 따로 작성하지 않아도 모두 다 처리해주는 장점이 있다. (Spring-JDBC)

여기서는 Spring JDBC에 대해서 알아본다.

DataSource란?

DataSource는 JDBC 명세의 일부분이면서 일반화된 연결 팩토리이다.

즉, DB와 관계된 connection 정보를 담고 있으며, bean으로 등록하여 인자로 넘겨준다.

이 과정을 통해 Spring은 DataSource로 DB와의 연결을 획득한다.

  • DataSource는 JDBC Driver vendor(Mysql, Oracle 등) 별로 여러가지가 존재한다.

  • DataSource가 하는 일

    • DB Server와의 기본적인 연결
    • DB Connection Pooling 기능
    • 트랜잭션 처리
  • DataSource의 구현 예시

    • BasicDataSource
    • PoolingDataSource
    • SIngleConnectionDataSource
    • DriveerManagerDataSource
  • DataSource 설정 및 Bean 등록, 주입 방법

    1. DB와의 연결을 위한 DB Server에 관한 정보(Property)를 설정한다.

      (url, driver, username, password)

    2. 해당 property file에 있는 값을 place holder를 통해 DataSource의 속성으로 설정한 후 해당 BasicDataSource를 bean으로 등록한다.

      • Spring JDBC를 사용하려면 먼저, DB Connection을 가져오는 DataSource를 Spring IoC 컨테이너의 공유가능한 Bean으로 등록해야 한다.
    3. 생성된 BasicDataSource Bean을 Spring JDBC에 주입한다.

- DB Connection Pooling

  • 자바 프로그램에서 데이터베이스에 연결(Connection 객체를 얻는 작업)은 시갆이 많이 걸린다.
  • 만약, 일정량의 Connection을 미리 생성시켜 저장소에 저장했다가 프로그램에서 요청이 있으면 저장소에서 Connection을 꺼내 제공한다면 시간을 절약할 수 있는데, 이러한 프로그래밍 기법을 Connection Pooling이라 한다.
  • Connection Pooling을 사용하면 속도와 퍼포먼스가 좋아진다.

- Placeholder

  • 정보를 적어둔 곳에서 내용만 고치면 다른 모든 부분에서 변경된 내용이 적용된다.
  • 예를 들어, Parameter 정보들을 적어둔 Property file에서 특정 정보를 수정하면 변경된 정보가 placeholder를 통해 ${jdbc.password}에 주입된다.
  • 예를 들어, pom.xml에서 springframework-version을 4.2.5.RELEASE로 설정했으면 <groupID>org.springframework</groupID>에 해당하는 밑에 요소들은 ${org.springframework-verson}으로 적어주면 알아서 버전에 맞는 것이 적용된다.

참고

https://gmlwjd9405.github.io/2018/05/15/setting-for-db-programming.html