-
CS 스터디 - SpringCS 2023. 6. 20. 02:24
💎 <spring> 이란 ?
문맥마다 다르게 사용된다.
- Spring DI container
- Spring framework
- Spring Boot, framework 모두 포함한 생태계 자체
💎 Spring 왜 만들었을까? 핵심 컨셉은?
"좋은 객체 지향 어플리케이션을 개발할 수 있게 도와주는 프레임워크"
자바 언어 기반의 프레임워크로써, 자바의 가장 큰 특징인 객체 지향 언어로써 가지는 강력한 특징을 살려내는 프레임워크
EJB(Enterprise Java Bean)가 컨테이너 역할 수행. 학습하기 어려워질정도로 복잡
-> 더 경량화되고 간소화된 컨테이너가 필요
-> EJB : 기업환경의 시스템을 구현하기 위한 서버 측 컴포넌트 모델이다. 일반적으로 업무 로직을 가지고 있는 서버 어플리케이션을 EJB라고 한다.
비지니스 로직을 탑제한 부품을 "Enterprise Bean"이라고 불린다. Database처리, Transaction처리등의 시스템 서비스를 이용한 로직을 감추고 있는 부품을 "컨테이너"라고 불린다.
= POJO(Plain Old Java Object)를 사용하는 경량화된 컨테이너인 Spring Framework가 만들어지게 된다.
경량이 너무 궁금해서 찾아봤다.
" Spring은 매우 가볍다. jar파일로 구성된 여러 모듈로 구성되어있고 이 몇개의 jar파일만 있으면 개발, 실행이 모두 가능하다. 또한 애초에 가벼운 OJO형태 객체를 관리하여 EJB 객체 관리하는 프레임워크보다 가볍다."
💎 springboot
-> 스프링을 편리하게 사용할 수 있도록 지원한다. 스프링 프레임워크와 별도로 사용할 수 없다! 편리하게 사용할 수 있는 기능을 제공하는 기술이다.
왜 사용하는가?
#1 )
단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성한다. -> Tomcat같은 웹 서버를 내장, 웹 서버 별도 설치 필요 x
이전에는 스프링 -> 빌드를 하고 톰캣 서버에 별도로 또 설치하고 빌드 된 결과물 띄우고... (여러모로 복잡했던 과거)
#2 )
손쉽게 starter 종속성을 제공한다. spring-boot-~~-starter / 라이브러리 여러 개 묶어서 한 번에 잘 가져올 수 있음
#3 )
스프링 프레임워크와 맞는 외부 라이브러리 버전을 알아서 지정해서 가져온다.
💎 객체 지향
다형성
- 같은 이름의 메소드가 클래스 혹은 객체에 따라 다르게 동작하도록 구현될 수 있다 -> Overriding
-> 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다.
- 클라이언트를 변경하지 않고도 서버의 구현 기능을 유연하게 변경할 수 있는 것이 다형성의 본질
= 확장 가능한 설계, 유연하고 변경이 용이
한계점 )
인터페이스 자체가 변하면 클라이언트/서버 모두 큰 변경 -> 인터페이스를 안정적으로 설계하는 것이 중요하다.
" 스프링 컨테이너는 다형성을 활용하여 역할과 구현을 편리하게 다루도록 지원 "
부연설명더보기유연하고 변경이 용이하다?
자동차가 바뀌어도 운전자는 운전이 가능하다(차를 바꿔도 운전면허 새로 딸 필요 없다)
자동차 인터페이스(역할)따라 k3, 그랜저 등등을 구현했기 때문이다.
왜? 운전자를 위해 이렇게 만든것이다!
운전자(클라이언트)는 내부 구조 바뀌어도 그냥 운전 가능하다.
다른 대상으로 변환이 가능하고, 완전 새로운 차가 나와도 자동차 역할 그대로 수행하면 된다.
따라서 무한 확장 가능, 클라이언트에 영향 안주고 새로운 차 만들기 가능이라는 장점이 생긴다!
역할(인터페이스) / 구현(인터페이스 구현한 클래스, 객체) 분리
-> 클라이언트는 대상의 역할만 알면 된다. 내부 구조 몰라도 되고, 구조가 바뀌든(전기차로 바뀌든) 대상 자체가(k3든 테슬라든) 바뀌든 영향 받지 않는다. 즉, 유연하고 변경이 용이해진다.
-> 객체 설계 시 역할을 먼저 부여하고 역할을 수행하는 구현 객체를 만든다.
💎 좋은 객체 지향 설계의 원칙?
SRP : 단일 책임 원칙. 하나의 책임이 모호하므로 변경이 있을 때 파급효과가 적다면 SRP를 잘 지킨 것
OCP : 개방 폐쇄 원칙. 인터페이스를 구현한 클래스를 만든다 (기존 코드 변경된 건 아님) -> 새로운 기능 구현 (하지만 확장은 됨)더보기구현 객체를 변경하려면 클라이언트 코드를 변경해야 한다.. (MemberService 내부에서 갈아끼움)
MemberService.java
MemberRepository m = new MemoryMemberRepository(); 또는 // new JdbcMemberRepository();
=> 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자 필요
LSP : 리스코프 치환 원칙. 하위 클래스는 인터페이스의 규약을 다 지켜야 한다. (단순 컴파일을 넘어, 만약 자동차 인터페이스 엑셀 -> 앞으로 가는것 이렇게 규약이 되어 있으면 뒤로가게 구현하면 컴파일 오류는 없으나 LSP 깨진 것으로 간주)
ISP : 인터페이스 분리 원칙. 특정 클라이언트를 위한 인텉페이스 여러 개가 범용 인터페이스 하나보다 낫다. 인터페이스 명확해지고 대체 가능성 높아짐더보기자동차 -> 운전 / 정비로 분리
사용자 -> 운전자 / 정비사로 분리
이러면 정비 인터페이스가 변해도 운전자 클라이언트에 영향 주지 않음
DIP : 의존관계 역전 원칙. 추상화에 의존하고 구체화에는 의존하지 말자. 구현 클래스에 의존할 것이 아닌, 인터페이스에(역할) 의존하라
ex ) 자기가 맡은 배역(역할, 인터페이스)에 의존해야지 실제 구현체(상대 배우)에 의존하면 배우 바뀌었을 때(구현 객체가 바뀌면) 연기 못함 == 변경이 아주 어려워진다.더보기MemberRepository m = new MemoryMemberRepository(); // 얘는 지금 직접 구현 클래스를 선택하는 것
MemberRepository라는 인터페이스에도 의존하지만, 구현 클래스(MemoryMemberRepostiory)에도 의존한다.
=> 스프링은 DI , DI 컨테이너를 통해 클라이언트 코드 변경 없이 기능 확장이 가능하다.
💎 IoC : 제어의 역전
= 개발자가 작성한 객체나 메서드의 제어를 개발자가 아니라 외부에 위임하는 설계 원칙
클라이언트 구현 객체가 스스로 필요한 서버 구현 객체 생성, 연결, 실행
운전자 클래스가 직접 자기가 운전할 차량 객체를 직접 생성 -> 구현 객체가 프로그램의 제어 흐름을 직접 조종
=> 제어의 역전 ~ 컨테이너가 직접 객체의 생성부터 소멸까지 생명주기를 관리하기 시작. 제어권이 개발자가 아닌, 프레임워크에게 있다.
이 제어의 역전 덕분에 AOP, DI 등이 가능해진다. (의존성 주입은 제어의 역전이 구현된 한가지 예시)
💎 DI : 의존성 주입
의존성이란 무엇인가?
객체가 파라미터나 리턴값 또는 지역변수 등으로 다른 객체를 참조하는 것을 의미한다.
-> 의존성은 최소화되어야 한다. 한 객체가 다른 객체에 의존한다 = 다른 객체가 변할 때 변경이 전파될 수 있다(의존성 전이)
정적인 의존관계?
- 애플리케이션 실행하지 않고도 분석할 수 있는 것 (import문 보고 알 수 있는 것들)
동적인 의존관계?
- 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존관계
의존관계 주입?
외부에서 실제 구현객체 생성하고 클라이언트에 전달하여 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입이라고 한다.
ex ) 주문 ~ 고정 할인 객체 / 차등 할인 객체 외부에서 생성하고, 클라이언트에 참조값을 전달하여 연결
그렇다면, 의존관계 주입은 왜 사용하는가?
- 클라이언트 코드를 변경하지 않고 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다
- 정적인 클래스 의존관계를 변경하지 않고 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.
의존관계 주입에는 어떤 방법이 있는가?
💎 생성자 주입(생성자에 @autowired)
- 생성자를 호출한 시점에 딱 한번만 호출되는 것이 보장되기 때문에 불변/필수 의존관계에 사용
- 딱 생성자를 통해서만 주입되고, 외부에서 수정할 수 없다.
왜 생성자 주입이 가장 권장 ?
- 기본적으로 애플리케이션 종료시점까지 의존관계 변경할 일이 잘 없다. -> 생성자 주입은 객체 생성시 1번만 호출, 불변하게 설계가 가능하다.
- 생성자에 들어가는 값이 누락되면 컴파일 단계부터 오류 잡음 -> 누락 방지 효과도 있음 (필드에 final 키워드 사용 가능)
- 다른 방법들은 생성자 이후 호출되기 때문에 필드에 final 키워드 사용 불가
💎수정자 주입 ~ Setter메소드 활용
- setXXX(필드명 camel표기법) 사용하여 주입, set메소드에도 @Autowired 붙여야 동작한다.
- 선택적인것, 변경 가능성이 있는 의존관계에 사용.
- @Autowired(required = false)를 통해 주입할 대상이 없어도 동작할 수 있게끔 지정도 가능.
- setter등으로 밖에서 바꿀 수 있게끔 열어두면 누군가 건드려서 바뀔 위험 크다. 꼭 필요한 경우 아니라면 사용하지 말자
💎 필드 주입
- 필드에 바로 @Autowired 로 넣는 것
- 외부에서 값을 바꿀 수 없기 때문에 setter 만들어서 테스트 돌려야하는 단점이 존재.
- 순수한 자바 코드로의 테스트를 만들 수 없기 때문에 권장되지 않음. (스프링 컨테이너가 직접 필드를 넣어주는것이기 때문에 스프링 컨테이너가 관리하는 걸 가져와야 @Autowired가 되니까 DI 프레임워크가 없으면 돌아가지 않는다. )
- 애플리케이션 테스트(@SpringBootTest) 이런데에서 테스트 할 때(컨테이너 띄우고 할때) 괜찮다. 누가 갖다 쓸일 없는 코드 + Configuration클래스처럼 스프링에서만 쓰는 경우일 때 제한적으로 사용.
💎 일반 메서드 주입
- 아무 메서드에서나 @Autowired 사용도 가능.@Autowired public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; }
- 생성자 주입, 수정자 주입안에서 주로 다 해결하기 때문에 일반적으로 사용하지 않는다.
- 의존관계 자동 주입은 스프링 빈이어야 동작한다는 점 잊지 말것! (@Autowired)
IoC 컨테이너, DI 컨테이너
- 객체를 생성하고 관리하며 의존관계를 연결해 주는 역할을 하는 것
- 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라고 한다. (스프링 외에도 많다!)
- 어셈블러, 오브젝트 팩토리 등으로 불리기도 한다.
💎 Spring Container, Spring Bean
@Configuration : 애플리케이션의 설정정보 담는 파일, 스프링은 이 어노테이션이 붙은 파일을 설정 정보로 사용한다.
@Bean : 객체를 반환하는 메서드에 붙여준다. 이 어노테이션이 붙은 메서드가 반환하는 객체를 스프링 컨테이너(ApplicationContext)에 등록하며, 등록된 객체를 스프링 빈이라고 한다. 기본적으로 메서드 이름과 동일하게 빈 생성, 빈 이름끼리 겹치면 안된다.
-> 참고로 이 방법을 사용하면 일일이 빈을 등록하다 누락될 위험도 있으므로 @ComponentScan 설정정보에 붙인다. @Bean 안해놔도 컴포넌트 스캔의 대상이 되어 자동으로 스프링 빈을 등록해준다.
SpringContainer 스프링 컨테이너
ApplicationContext ac = new AnnotationConfigApplicationContext(파일.class)
* 참고로 xml, 자바 다 가능, 최근은 자바를 더 활용 -> 스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화하여 사용하기 때문에 다양한 설정형식 지원할 수 있다!
* BeanFactory : 스프링 컨테이너 최상위 인터페이스, 스프링 빈 관리하고 조회하는 역할
* ApplicationContext : BeanFactory 기능 모두 상속받아 제공 + 더 필요한 부가 기능들을 제공(어플리케이션 이벤트, 환경변수, 리소스 조회 등등...)
-> 이 2가지를 주로 스프링 컨테이너라고 한다.
* 스프링 컨테이너 내부 빈 저장소에 빈 이름 + 객체 저장해둔다.
SpringBean 스프링 빈
스프링 컨테이너에 등록된 객체
💎 SpringContainer와 Singleton
- 싱글톤패턴이란, 클래스의 하나의 인스턴스만 존재하는 것을 보장하는 디자인패턴.
웹 어플리케이션은 여러 요청들이 동시다발적으로 들어오는데, 이 때마다 객체를 계속 new로 생성한다면 굉장히 비효율적이고, GC의 부담도 커지고 메모리 낭비도 심하다.
따라서, 해당 객체가 1개만 생성되고, 공유하도록 설계하자. => 싱글톤 패턴을 활용한다.
물론, 수많은 단점이 있다.
싱글톤 패턴은 구현하는 코드도 들어가고, 의존관계상 클라이언트가 구체 클래스에 의존하여 OCP원칙을 위반한다.
테스트도 어려우며, 내부 속성을 변경하거나 초기화어렵고... 결론적으로 유연성이 떨어진다.
SpringContainer는 이런한 문제점을 해결하며, 객체 인스턴스를 싱글톤으로 관리한다.
(스프링 빈이 기본적으로 싱글톤, 요청시마다 새로운 객체 생성해서 반환하는 기능도 제공한다)
-> 스프링은 싱글톤 레지스트리라는 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다.
싱글톤 레지스트리의 장점은 평범한 자바 클래스를 싱글톤으로 활용하게 해준다는 점이다. IoC 방식의 컨테이너를 사용해 제어권을 컨테이너에게 넘겨 싱글톤 방식으로 만들어져 관리되게 할 수 있다. 싱글톤 레지스트리 덕분에 애플리케이션 클래스라도 public 생성자를 가질 수 있으며 테스트하기도 편리해진다.
= 객체지향적 설계 방식과 원칙을 적용하는데 제약이 없다.
장점 / 주의점
Spring Container는 알아서 객체를 싱글톤으로 처리한다. 있는 객체를 그대로 재활용하기 때문에 매 요청이 들어올 때마다 객체를 생성해야하는 수고로움이 없다.
하지만, 싱글톤 패턴을 활용하든 스프링같은 싱글톤 컨테이너를 사용하든 하나만 만들어서 객체를 공유하기 때문에(인스턴스가 하나만 존재하므로) stateless하게 설계해야 한다. 스프링 빈의 필드에 공유값 설정하면 안 된다!
-> 가능하면 읽기만 가능하게, 지역변수 등을 활용
스프링 컨테이너는 어떻게 싱글톤을 보장하는가?
-> CGLIB 바이트코드 조작 라이브러리를 내부적으로 사용, AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고 그걸 스프링 빈으로 등록한다. 이름은 AppConfig지만, 실제 인스턴스는 CGLIB 어쩌구로 등록. 이게 싱글톤을 보장해준다.
-> CGLIB가 만약 빈이 이미 스프링 컨테이너에 등록되어 있다면 컨테이너에서 찾고, 등록 안 되어 있으면 기존 실제 코드를 실행, 생성해서 컨테이너에 등록한다.
== 그래서 스프링 빈은 하나만 생성되어 싱글톤이 보장된다.
Bean Scope?
- 스프링 빈이 생성되고, 존재하며 적용되는 범위
스프링 빈의 기본 스코프는 싱글톤이지만, 빈을 요청할 때 마다 새로운 오브젝트를 만드는 프로토타입 스코프, HTTP 요청이 생길때마다 생성되는 Request 스코프, 웹의 세션 스코프와 유사한 Session 스코프 등이 존재한다.
Bean LifeCycle
스프링 IoC 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 메소드 호출 → 사용 → 소멸 전 콜백 메소드 호출 → 스프링 종료
더보기추가로, @Configuration 없이 @Bean만 사용한다면 빈 등록은 가능한데 싱글톤은 깨진다. 항상 별다른 고민 없이 @Configuration 어노테이션을 통해 스프링 설정정보를 관리하자.
💎 Component Scan?
-> @Component, @Controller, @Service 등의 어노테이션이 붙은 대상들을 스캔
💎 프레임워크와 라이브러리 ?
프레임워크
'기본적인 형태와 필요한 기능을 갖추고 있으며, 이러한 뼈대를 바탕으로 사용자는 코드를 작성하여 애플리케이션을 개발. 앱/서버 구동, 메모리 관리, 이벤트 루프 등의 공통된 부분을 프레임워크가 관리하고, 사용자는 프레임워크가 정해준 방식대로 개발을 수행한다.'
'내가 작성한 코드를 제어하고, 대신 실행한다.'
'애플리케이션 코드가 프레임워크에 의해 사용된다. 제어의 흐름은 프레임워크가 쥐고 있고 사용자가 그 안에 필요한 코드를 작성'
프레임워크는 생산성을 높인다.
- 개발자가 더 로직에 집중할 수 있고 시스템을 일관성 있게 관리할 수 있다. 프레임워크가 제공하는 뼈대에 맞게 개발, 시스템 유지 보수 또한 용이하다.
라이브러리
라이브러리는 주로 소프트웨어를 개발할 때 컴퓨터 프로그램이 사용하는 비휘발성 자원의 모임이다. 여기에는 구성 데이터, 문서, 도움말 자료, 메시지 틀, 미리 작성된 코드, 서브루틴, 클래스, 값, 자료형 사양을 포함할 수 있다
라이브러리를 사용한다면 사용자가 필요할 때 설치하고 자신의 코드에서 직접 호출하여 능동적으로 사용한다. 내가 작성한 코드가 직접 제어의 흐름을 담당한다.
💎 maven과 gradle 차이가 무엇일까?
일단은 둘 다 빌드 관리 도구라는 점이 공통점이다.
Maven
- 자바용 프로젝트 관리도구, 아파치 라이센스로 배포되는 오픈소스 소프트웨어
- 프로젝트에 사용하는 라이브러리들을 관리해주는 도구이며, 사용하는 라이브러리와 연관된 라이브러리들까지 거미줄처럼 연동이 되어 관리가 된다.
- 네트워크를 통해 연관된 라이브러리까지 같이 업데이트 해줌
- pom : project object model 의 정보를 담는다. 프로젝트 정보, 빌드 설정, 빌드 환경, pom 연관정보(의존 프로젝트, 상위 프로젝트, 포함하는 하위 모듈 등)
Gradle
- 빌드, 프로젝트 구성 및 관리, 테스트, 배포 도구
- 안드로이드 앱 공식 빌드 시스템
- 빌드속도가 maven에 비해 빠르며 grooby (자바 가상머신에서 실행되는 스크립트 언어이며, jvm에서 동작하지만 소스코드 컴파일할 필요는 없음, 자바와 호환되고, 자바 클래스파일을 그대로 그루비로 사용가능 => 자바개발자가 사용하기 좋은 빌드관리도구)스크립트 기반
왜 Gradle로 넘어가는 추세일까?
- 스크립트 길이, 가독성 우세
- 빌드, 테스트 실행이 gradle이 더 빠르다.
=> gradle이 캐시를 사용하기 때문에 테스트를 반복할수록 maven과 속도 차이가 더 커지고, 의존성이 늘어날 수록 성능과 스크립트 품질 차이가 심해짐
💎 AOP : 관점지향 프로그래밍
- 공통 관심사항과 핵심 관심사항을 분리하는 것.
- 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화한다.
- 소스 코드 상에서 다른 부분에 반복해서 쓰는 코드들을 흩어진 관심사로 정의하고 이들을 Aspect로 모듈화한다.이로써 핵심적인 비즈니스 로직에서 분리가 가능하다.
- AOP 또한 스프링 빈에만 적용 가능하므로 Spring.Config파일에 Bean으로 등록하거나 @Component 어노테이션을 추가해줄 것.
🤔 공통 관심사와 핵심 관심사를 분리하는 것이란?
- 예를 들어, 모든 메소드의 수행시간을 측정하고 싶다 가정.
- 메소드의 수행시간을 측정하는 것은 모든 코드에 적용되는 공통적인 기능이므로, 각 비즈니스 로직에 똑같이 들어가게 된다.
- 이를 따로 "공통 관심사"로 정의하고 핵심 비즈니스 로직으로부터 분리한다.
용어들
Aspect : 흩어진 관심사들을 모듈화 한 것
Target : Aspect를 적용하는 곳 -> @Around가 타겟 메서드를 감싸서 특정 Advice를 실행하겠다는 의미
Advice : 어떤 일을 해야할지 부가기능을 담은 구현체
JointPoint : Advice가적용될 위치(메서드 진입지점, 생성자 호출시점 등)
PointCut : JointPoint의 상세한 스펙을 정의한 것
참고 블로그 / 강의'CS' 카테고리의 다른 글
CS 스터디 - 운영체제 Q&A(1) (0) 2023.08.10 CS 스터디 - 네트워크 Q&A (1) (0) 2023.08.03 CS 스터디 - 운영체제 (0) 2023.07.21 CS 스터디 - 네트워크 이론 (0) 2023.07.05 CS 스터디 - 데이터베이스 (0) 2023.06.14