라즈베리파이반

라즈베리파이 등 컴퓨터계열 게시판입니다.

제목자바 프레임워크 : 스프링(Spring)2022-08-20 09:52
작성자user icon Level 4

88x31.png


1. 스프링(Spring)


스프링(Spring)은 자바 엔터프라이즈 개발을 위한 오픈소스 경량 애플리케이션 프레임워크 입니다. 파이썬에는 Django, 자바스크립트에는 Nodejs를 통해 웹서버를 개발한다면, 자바에서는 Spring을 사용하여 웹서비스를 만들 수 있습니다. 우리나라에서는 공공기관의 웹 서비스 개발 시 사용하는 전자정부 표준프레임워크의 기반 기술로 스프링을 사용하고 있습니다. 


스프링이 등장하기 전에는 자바 진영에서는 엔터프라이즈 자바빈즈(Enterprise JavaBeans, EJB)라는 자바 엔터프라이즈 에디션(Java EE)으로 어플리케이션을 개발했습니다. 그러나 시간이 지남에 따라 기술 복잡도가 커져 개발이 어려워졌으며, 무엇보다도 EJB에 의존성이 컸습니다.


자바는 대표적인 객체 지향 언어입니다. 객체 지향 언어의 큰 장점은 상속을 할 수 있고 상속을 통해 다형성을 가질 수 있다는 점인데, EJB를 사용하려면 EJB에서 요구하는 객체를 상속 받아야 하므로 다형성을 포기해야만 합니다.


자바 진영에서는 EJB를 사용하던 이 시기를 자바의 겨울에 비유하였으며, 스프링이 등장하고서 겨울이 가고 봄이 찾아오게 될 것이라는 의미로 스프링이라는 이름이 지어졌다고 합니다.



2. 스프링 프레임워크(Spring framework)


스프링(Spring)은 하나의 프로젝트가 아니라 여러 프로젝트로 구성된 멀티 플랫폼 입니다. 스프링 프레임워크, 스프링 부트, 스프링 MVC 등 다양한 프로젝트가 존재하는데, 여기서 가장 기본이 되는 프로젝트가 스프링 프레임워크(Spring framework) 입니다. 스프링 프레임워크는 약 20개의 모듈로 구성됩니다.


img01


이중에서도 핵심 모듈은 코어 컨테이너(Core Container) 입니다. 코어 컨테이너는 IoC 패턴을 통해 Beans라 불리는 스프링 객체에 대한 라이프사이클(Lifecycle)을 관리합니다.



3. 스프링의 핵심


스프링의 특징은 POJO(Plain Old Java Object)를 통해 개발하면서 EJB의 컨테이너 시스템을 사용하는 것에 있습니다.


POJO는 마틴 파울러, 레베카 파슨스, 조시 맥켄지에 의해 처음 쓰여진 용어입니다.


 우리는 사람들이 자기네 시스템에 보통의 객체를 사용하는 것을 왜 그렇게 반대하는지 궁금하였는데, 간단한 객체는 폼 나는 명칭이 없기 때문에 그랬던 것이라고 결론지었다. 그래서 적당한 이름을 하나 만들어 붙였더니, 아 글쎄, 다들 좋아하더라고.

- 마틴 파울러

아래 코드에서 작성한 Student 처럼 순수한 속성(Attribute)과 세터(Setter)게터(Getter) 정도로만 이루어진 단순한 자바 객체를 POJO라고 합니다.

public class Student {

    

    private long number;

    private String name;

    private String gender;


    public void setNumber(long number) {

        this.number = number;

    }


    public void getNumber() {

        return this.number;

    }


    public void setName(String name) {

        this.name = name;

    }


    public void getName() {

        return this.name;

    }


    public void setGender(String gender) {

        this.gender= gender;

    }


    public void getGender() {

        return this.gender;

    }

}


POJO는 다음과 같은 것을 하면 안됩니다.


(1) 미리 지정된 클래스를 extends

(2) 미리 정의된 인터페이스를 implement

(3) 미리 정의된 어노테이션을 include


그러나 실질적으로는 어노테이션 없이 프레임워크를 만드는 것은 불가능하므로 어노테이션이 추가되기 전까지 POJO였고 어노테이션이 제거된다면 POJO상태가 되는 객체도 POJO라고 합니다.


스프링의 또다른 특징은 컨테이너 시스템 입니다. 컨테이너는 EJB에서 사용했던 기술로, 컨테이너를 통해 객체를 제어하고 클라이언트가 필요할 때 컨테이너로부터 객체를 받아쓰는 형태입니다.  EJB가 단점만 있는 것은 아닙니다. 컨테이너를 도입함으로 인해 복잡한 비즈니스 로직으로부터 인프라를 분리하여 개발자가 비즈니스 로직에 집중할 수 있도록 만들었습니다. 


스프링은 이 컨테이너 기술을 도입하여 개발자가 비즈니스 로직에 집중할 수 있되, POJO를 통해 다형성을 가지는 것도 가능하도록 만들었습니다. 이런 기능을 가능하도록 만든 핵심 기술은 IOC/DI, AOP, PSA 입니다.


img04 


4. 제어의 반전(Inversion of Control, IOC)


IOC는 코드의 흐름을 제어하는 주체를 바꾸는 패턴을 말합니다.


일반적으로 객체의 라이프사이클은 아래 코드처럼 클라이언트가 관리합니다.

public class Client {


    public static void main(String[] args) {


        Student student = new Student();

        ...

        student.doStudy();

    }

}


클라이언트에서 new 키워드를 통해 Student 객체를 생성하고 doStudy 메소드를 호출함으로써 기능이 구현됩니다. 학생이 능동적으로 개인공부를 하는 것이라고 보면 됩니다.


그러나 IoC의 경우 객체의 생성과 호출 시점을 클라이언트가 관리하지 않습니다. 스프링 프레임워크의 경우 IoC 컨테이너가 객체의 생성과 호출 시점을 관리합니다. 이 경우 개발자는 비즈니스 로직이 언제 호출되어야 할 것인지 생각할 필요없이 비즈니스 로직만 잘 구현한다면 프로그램이 문제없이 돌아가게 됩니다.  언제 공부할지 생각할 필요없이 수업시간이 되면 공부를 한다고 보면 됩니다.


스프링 프레임워크를 통한 컨트롤러 코드 작성

@Controller

public class BoardController {


    @GetMapping("/Board")

    public String getBoarView(Board board) {

        ...

    }

}




5. 의존성 주입(Dependency Injection, DI)


DI는 IoC를 구현하는 패턴 중 하나입니다. 스프링에서는 DI를 통해 IoC를 구현합니다. 이름 그대로 외부에서 의존성 객체를 외부에서 주입시켜주는 패턴입니다.


아래 라면 클래스가 있다고 합시다.

public class Ramen {

    

    public void make() {

        boilWater();

        putNoodles();

        putIngredients();

        waiting();

        complete();

    }


    public void boilWater() {

        System.out.println("물을 끓입니다.");

    }


    public void putNoodles() {

        System.out.println("면을 넣습니다.");

    }


    public void putIngredients() {

        System.out.println("스프를 넣습니다.");

    };


    public void waiting() {

        System.out.println("3분을 기다립니다.");

    }


    public void complete() {

        System.out.println("라면 완성!");

    };

}


클라이언트는 다음과 같이 라면을 끓일 수 있습니다.

public class Client {

    private Ramen ramen = new Ramen();


    public void makeRamen() {

        ramen.make();

    }


    public static void main(String[] args) {


        Client client = new Client();


        try {

            client.makeRamen();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}


라면이 정상적으로 끓여질 것입니다.


그런데 라면 클래스의 코드를 변경했다고 합시다.

public class Ramen {


    private String ingredient;    


    public Ramen(String ingredient) {

        this.ingredient = ingredient;

    }


    public void make() {

        boilWater();

        putNoodles();

        putIngredients();

        waiting();

        complete();

    }


    public void boilWater() {

        System.out.println("물을 끓입니다.");

    }


    public void putNoodles() {

        System.out.println("면을 넣습니다.");

    }


    public void putIngredients() {

        System.out.println("스프를 넣습니다.");

        System.out.println(ingredient + "을(를) 넣습니다.");

    };


    public void waiting() {

        System.out.println("3분을 기다립니다.");

    }


    public void complete() {

        System.out.println(ingredient + " 라면 완성!");

    };

}


라면에 재료 하나 추가했을 뿐인데 프로그램은 컴파일 에러가 발생할 것입니다. 클라이언트에서 라면 객체를 생성할 때 매개변수인 재료를 넣어주지 않았기 때문입니다.


즉, 라면 코드를 수정한다면 클라이언트 코드도 같이 수정해야 된다는 말이 됩니다. 이것은 클라이언트 내부에서 라면 객체를 생성하게 되어 클라이언트와 라면의 의존 관계가 고정되기 때문입니다.


클라이언트 코드를 아래와 같이 수정해보겠습니다.


public class Client {

    private Ramen ramen;


    public Client(Ramen ramen) {

        this.ramen = ramen;

    }    


    public void makeRamen() {

        ramen.make();

    }


    public static void main(String[] args) {


        Ramen ramen = new Ramen("김치");

        Client client = new Client(ramen);


        try {

            client.makeRamen();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}


수정하기 전에는 라면 객체를 클라이언트가 내부에서 직접 생성했지만 수정한 코드는 외부에서 라면을 주입받습니다. 즉, 올바른 라면이 주입된다면 클라이언트 클래스의 코드는 수정할 필요가 없습니다. 다시 말해서, 클라이언트와 라면의 의존 관계가 약화 되었으며, 라면의 생성 주체가 클라이언트 객체에서 메인 메소드로 바꼈습니다. (제어의 역전)


스프링은 이처럼 컨테이너가 관리하고 있는 객체를 다른 객체에 주입해 줌으로써 IoC를 구현하여 다형성을 확보했습니다.


스프링에서 다음과 같은 방식으로 DI를 합니다.


(1) 생성자 주입

(2) 세터(Setter) 주입

(3) 필드 주입


스프링에서는 이 중 생성자 주입을 권장하고 있습니다.



6. 관점 지향 프로그래밍(Aspect-Oriented Programming, AOP)


AOP는 관심사라 불리는 기능 단위를 분리하여 개발의 복잡성을 낮추기 위한 프로그래밍 패러다임 입니다.


img06 



객체 지향 프로그래밍(Object Oriented Programming, OOP)은 주요한 기능에 단일 책임 원칙(Single Responsibility Principle, SRP)에 따라 클래스를 분리합니다. 하지만 클래스를 설계하다보면 로깅이나 보안, 트랜잭션 처리 등 공통적인 부가 기능이 생기길 마련인데, 이 공통된 부가 기능을 따로 모듈화 하는 것을 AOP라고 합니다.


스프링에서 AOP는 프록시 패턴(Proxy Pattern)을 통해 구현됩니다.


호출이 오면 프록시가 대신 호출을 받고, 주요 기능 실행 전후로 부가 기능을 실행합니다.



7. 교체 가능한 서비스 추상화(Portable Service Astraction, PSA)


PSA는 환경이나 기술의 변경에 상관없이 일관된 방식으로 기술에 접근할 수 있게 해주는 설계원칙입니다. 스프링은 스프링을 통해 동작하는 라이브러리들이 POJO원칙을 지키게끔 PSA 형태로 추상화 되어있습니다. 즉, 복잡한 기술은 내부에 숨기면서 개발자에게는 편의성을 제공합니다.


예를 들어 트랜잭션 처리의 경우, JDBC를 통해 DB에 접근하던지 JPA를 통해 DB에 접근하던지에 상관없이 @Transactional이라는 어노테이션만 붙이면 놀랍게도 별도의 코드 없이도 트랜적션 서비스를 사용할 수 있게 됩니다. 

#스프링# 스프링 프레임워크# POJO# IoC/DI# 제어의 역전# 의존성 주입# AOP# 관점 지향 프로그래밍# PSA
댓글
자동등록방지
(자동등록방지 숫자를 입력해 주세요)