on
[Spring] 스프링 프레임워크, 객체지향프로그래밍, SOLID, 다형성...
[Spring] 스프링 프레임워크, 객체지향프로그래밍, SOLID, 다형성...
스프링 프레임워크
스프링의 핵심은 자바 언어의 큰 특징인 객체 지향 언어 라는 특징을 살려서 애플리케이션을 개발할 수 있게 도와준다는 점이다.(스프링은 자바 언어 기반의 프레임워크다)
객체 지향 프로그래밍
객체 지향 프로그래밍은 객채들의 협력을 통해서 데이터를 처리할 수 있도록 만드는 프로그래밍이다.
객체 지향 언어를 배우면, 기본적으로 배우는 추상화, 캡슐화, 상속, 다형성을 배웠을 것이다. 여기서 다형성이 객체 지향 프로그래밍의 특징을 살리는 가장 핵심적인 아이디어이다. 다형성은 유연하고 변경이 용이하게 설계를 할 수 있도록 하기 때문이다. 각 객체들이 책임과 역할이 잘 구분되어 있다면, 해당 역할의 변경이 유연하게 이뤄질 수 있기 때문이다.
다형성
다형성을 지니고 세상을 바라보는 관점은 역할과 구현을 분리한다는 것이다.
역할과 구현을 분리하면, 설계 대상을 단순하고 유연하게 만들기 때문에 변경도 편리해진다. 이로 인한 장점으로는
클라이언트의 관심사는 대상의 역할이다. 클라이언트는 대상의 내부 구조를 몰라도 된다. 클라이언트는 대상의 내부 구조, 심지어 대상 자체가 변경된다 하더라도 영향을 받지 않는다.
자바의 다형성은 보통은 인터페이스의 오버라이딩을 생각하면 이해하기 쉽다. 구현은 해당 구현체에 맡기기만 하고, 클라이언트는 해당 인터페이스의 기능 역할만 관심을 두고, 사용하기만 하면 된다.
다형성의 장점으로 인해, 확장 가능한 설계가 가능하다.
예를 들어, 의사선생님이 수술을 하고 있다고 상상해보자. 단순하게 의사선생님과 간호사선생님이 계신다고 생각해보자.
집도하는 의사선생님은 환자의 수술에만 집중을 하기 위해서 많은 간호사선생님들이 어시스트를 해주는 것을 흔히 볼 수 있다.
의사선생님이 "메스"라고 하면, 간호사 선생님이 준비된 메스를 건네고, 의사선생님은 메스를 쥐고 수술을 한다.
이때, 의사선생님이 메스가 독일에서 만들어졌는지, 한국에서 만들어졌는지에 대한 관심은 없고, 그냥 메스의 "역할" 에만 관심이 있을 뿐이다.
좋은 객체지향 설계 5원칙 (SOLID)
S ingle responsibility principle (S)
O pen/closed principle (O)
L iskov substitution principle (L)
I nterface segregation principle (I)
D ependency inversion principle (D)
Single Responsibility Principle(SRP)
하나의 클래스는 하나의 책임만 가져야 한다는 것이다. 하나의 책임의 기준은 해당 클래스가 변경으로 인한 파급 효과를 생각하면 좋다. 파급효과가 적을 수록 SRP 원칙을 잘 지켰다고 볼 수 있다.
Open/closed principle(OCP)
개방 폐쇄의 원칙으로써, 확장에는 개방 / 변경에는 폐쇄 하라는 것이다. 즉 다형성을 활용하라는 것이다.
위의 예시 중 "메스"를 사용하는데, 독일산 메스가 그립감이 안좋다고, 한국산으로 달라고 가정해보자(
말도 안되는거 알고 있다
)
간호사는 사용하던 독일산 메스 에서 국산 메스로 바꾸기만 하면 된다. (메스의 역할 구현은 동일하니까)
Liskov substitution principle(LSP)
리스코프 치환 원칙은 하위 타입의 인스턴스로 바꿀 때 프로그램의 정확성을 깨지 않는 것을 말한다. 인터페이스를 구현한 구현체를 믿고 사용하기 위해서, 하위클래스는 인터페이스 규약을 다 지켜야 한다.
예를 들어, 메스라고 해서 받았는데 메스의 기능이 석션(혈액 같은 것을 흡입하는 기기)이면 안되는 것과 같은 이치이다.
ISP(Inteface segregation principle)
범용 인터페이스 하나로 기능을 거대하게 만드는 것 보다 여러 개의 인터페이스로 분할하는 것을 말한다. 인터페이스에 많은 기능을 담을수록, 인터페이스의 역할이 불분명해지기 때문에, 여러 인터페이스로 역할을 명확히 하고, 대체 가능성을 높이는 것이다.
DIP(Dependency inversion principle)
프로그래머는 구현 클래스에 의존하는 것이 아니라, 추상화에 의존해야 한다는 원칙이다. 즉, 역할에 의존해야 한다는 것이다.
예를 들어, 의사가 'xxx 회사꺼 메스 주세요' 가 아니라 '메스 주세요' 라고 해야 한다는 것이다. 왜냐하면, 역할이 아닌 구현체에 의존하게 되면, 변경이 아주 어려워지기 때문이다.
스프링 과 스프링 빈(Bean)
처음 스프링이 객체지향을 지원해주는 프레임워크라고 언급했듯이, 스프링 프레임워크는 IoC(Inversion of Control), DI(Dependency Inject)와 DI 컨테이너를 통해서 다형성의 OCP와 DIP를 가능하도록 지원한다.
IoC(Inversion of Control)이란?
프로그램의 제어 흐름을 직접 에어하는 것이 아니라, 외부에서 제어의 흐름을 관리하는 것을 제어의 역전(IoC) 라고 한다.
DI(Dependency Injection)란?
의존 관계 주입은 외부에서 동적인 객체 인스턴스의 의존 관계에 따라서 실제 구현 객체를 생성하고, 생성된 객체의 주소값을 클라이언트에 전달해서 클라이언트와 실제 의존 관게를 연결 시키는 것을 의존 관계 주입이라고 한다.
DI 컨테이너(IoC 컨테이너)
객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 DI 컨테이너 또는 IoC 컨테이너라고 말한다.
스프링에는 관리하고자 하는 객체를 스프링 빈(Bean) 객체를 통해서 관리한다. 그리고 이러한 빈들을 관리하는 곳이 스프링 컨테이너이다.
빈(Bean) 생성 및 등록
스프링 프레임워크에서 스프링 컨테이너는 최상위에 BeanFactory 와 BeanFactory를 상속한 ApplicationContext 가 있다. ApplicationContext 는 BeanFactory 이외에도 빈을 관리할 때 필요한 조회 등 수많은 부가기능(환경변수, 애플리케이션 이벤트, 리소스조회, 메시지 국제화 등)을 지원하기 때문에, 실질적으로 우리가 사용하는 것은 ApplicationContext 를 주로 사용한다.
어노테이션을 이용한 빈 등록
기본적으로 스프링은 어노테이션을 스캔해서 설정을 구성하는데, 빈을 등록하기 위해서는 빈으로 생성하고 싶은 메서드에 @Bean 어노테이션을 붙이면 된다. 이외에도 @Controller, @Service, @Repository 등의 어노테이션도 해당 기능을 수행하는 빈 등록 어노테이션이다. 빈을 등록하는 설정파일에는 해당 클래스에 @Configuration 을 등록해서 해당 파일이 설정정보를 담당하는 자바파일임을 스프링에 알려주어야 한다.
XML 기반 빈 등록
스프링은 XML을 기반으로 설정 정보를 작성할 수 있다. XML 파일에 태그를 이용해, 해당 태그의 내용을 기반으로 스프링이 빈으로 등록해서 스프링 컨테이너에서 관리한다. 그러나 현재는 어노테이션을 이용해서 자바코드로 설정을 구성하는 것이 일반화되어서 많이 사용되지는 않는다고 한다.
이외에도 Groovy 같은 다른 파일 형식도 지원을 한다.
이렇게 다양한 형식을 지원할 수 있는 이유는 BeanDefinition 으로 추상화를 했기에 가능하다. BeanDefinition 은 각 빈당 하나씩 메타정보가 생성되어, 메타정보를 기반으로 스프링 컨테이너에 등록을 한다.
빈과 싱글톤
스프링 컨테이너가 클라이언트의 요청마다 빈을 생성해서 각기 다른 빈 객체를 생성해서 반환한다면, 메모리 낭비가 심각할거는 예측이 가능하다. 그렇기 때문에 스프링 컨테이너는 기본적으로 싱글톤 디자인 패턴을 사용한다.
public class Singleton{ private static final Singleton instance = new Singleton(); public static Singleton getInstance() { return this.instance; } private Singleton() {} public void login() { ... } }
위는 싱글톤 디자인 패턴을 적용한 객체의 예제이다. 보다시피, 싱글톤은 private 생성자를 통해서 해당 클래스의 new를 통한 객체 생성을 막아놓는 것을 볼 수 있다. 하지만, 싱글톤 패턴을 그대로 사용하기에는 인터페이스가 아닌 구현체에 의존을 하기 때문에 DIP 원칙을 위배한다고 볼 수 있다. 그래서 싱글톤 패턴은 안티패턴이라고 부르기도 한다.
그렇다면 객체 지향 설계를 지원하도록 돕는 스프링의 기본 이념을 역행하는 싱글톤으로 빈을 관리하기에는 무리가 있어보인다.
스프링 컨테이너
스프링은 싱글톤의 문제점을 해결하기 위해서 싱글톤 컨테이너를 통해 빈을 관리한다. 스프링 컨테이너는 빈 객체를 직접 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리하는 싱글톤 컨테이너 역할을 한다. 그래서 구현체에 싱글톤 패턴을 위한 코드를 작성하지 않아도 자동으로 싱글톤으로 관리할 수 있게 된것이다.
그런데, 다음과 같은 경우 싱글톤이 유지될까? 싱글톤이 깨질까?
public class AppConfig { @Bean public A a() { return new aImpl(aaa()); } @Bean public B b() { return new bImpl(aaa(), bbb()); } @Bean public AAA aaa() { return new aaaImpl(); } }
빈 A와 빈 B는 둘 다 공통적으로 AAA 빈을 주입받아서 생성된다. 그리고, AAA 빈을 주입받기 위해서는 aaa() 메서드를 통해서 주입을 받는데, 빈 A와 빈 B가 만들어지는 동안 각각 aaa() 메서드를 호출하는데, 그렇다면 싱글톤이 깨지게 되는 것 아닐까 하는 의구심이 들 수 있다.
이럴 때, 스프링에서는 바이트 조작 코드를 이용해서 싱글톤을 보장할 수 있도록 한다고 한다.
의존성 주입을 위해, aaa() 메서드가 처음으로 호출 될 경우에는 해당 로직을 이용해서 새롭게 생성해서 빈으로 등록한다.
그리고 다음 aaa() 메서드가 호출될 때는, 이미 빈으로 등록되어 있는 AAA 빈 객체가 있는지 조회해서 등록되어 있는 객체를 주입한다고 한다.
실제로, @Configuration을 통해 등록한 AppConfig(라고 가정)설정 파일이 스프링이 실행되면서, AppConfig@CGLIB 같이 바이트가 조작된 객체가 생성되며, 내부에는 빈을 조회해서 존재하면 해당 빈 객체를 반환할 수 있도록 생성하는 코드 로직이 추가된다.
from http://devcabinet.tistory.com/38 by ccl(A) rewrite - 2021-12-04 14:27:36