Day 76. Spring Framwork 1 : 세팅 및 기본 개념 ( DL, IOC )

ho_c·2022년 6월 9일
0

국비교육

목록 보기
60/71
post-thumbnail

세미 프로젝트가 끝나고, 오늘부터는 파이널을 위한 프레임워크 수업에 들어간다. 얼마나 기다려왔던지.. 매우 설렌다. 그럼 오늘 수업으로 넘어가자.

📝목차

  1. 스프링 프레임워크란?
  2. 스프링 프레임워크 세팅
  3. 스프링 프로젝트 생성 ( 프로젝트 선택→입력→패키지 )
  4. 스프링 프레임워크 기본
  5. 스프링의 핵심

1. 스프링 프레임워크란?

먼저 간단하게 스프링 프레임워크에 대해서 알아보자.

스프링 이전에는 자바 ORM표준인 EJB(Enterprise Java Beans)를 사용해서 개발했는데, 이때 Beans는 Java Class들을 의미한다. 즉, EJB는 기업용(Enterprise) 자바 라이브러리이다.

매우 훌륭한 표준이었지만, 너무 무거운 라이브러리였고 실무에 투입할 수 있는 수준이 장비와 진입 장벽이 너무 높았다. 그 결과 흔히 말하는 “EJB Hell”이 존재했다.

그리고 그 지옥에서 올라온 헬보이가 한 명 있었으니, 바로 “로드 존슨”이다. EJB의 지옥에서 분노를 느낀 그는 스프링 프레임워크의 전신을 자신의 책과 3만줄 가량의 오픈소스로 풀어버린다. 이 반대편에는 JPA도 같이 등장했지만 그건 다른 결이니 따로 빼도록하고..

무튼 이 오픈소스를 보고 감명받은 유겐 휠러와 얀 카로프의 제의로 오늘날의 스프링 프레임워크가 탄생하게 된다.

이런 스프링은 현재 5.3.20버전까지 출시되었고, 이클립스에서는 Spring Tools 플러그인으로 쉽게 설치해서 사용할 수 있다.


2. 스프링 프레임워크 세팅

이클립스에서 스프링 프레임워크를 쓰는 것은 그렇게 어렵지가 않다. 앞서 말했듯이 스프링 플러그인을 제공해주기 때문에, 라이브러리 불러오고 옮겨담고 이런 번잡한 과정이 생략된다.

STS 다운로드
Help → Market Place → STS 검색

검색을 하게 되면, 두 개의 버전이 나오는데 4.x버전은 Spring Boot를 중심으로 하는 버전이다. 우리는 legacy한 스프링 프레임워크를 사용할 것이기 때문에 3.x버전과 그 밑에 ‘Spring Tools 3 Add-on for Spring Tools 4.3.9.22 RELEASE’를 설치한 뒤 업데이트 해주면 된다.


3. 스프링 프로젝트 생성

평소에 하던 것처럼 프로젝트 생성을 해준다.
Spring → Spring Legacy Project 클릭

그 다음, 프로젝트명 입력창이 나오는데 원하는 이름을 적고 하단의 Spring MVC Project를
선택한 뒤 Next를 눌러 준다.

넘어가면 패키지명 입력창이 나온다. 이때, 패키지명엔 관례가 있다.
예를 들어 우리가 기존에 사용했던 Controllers, DAO, DTO 이런 패키지명을 “기업.팀or프로젝트명.애플리케이션”에 맞춰 명명한다.
Ex : samsung.manageTeam.controllers


4. 스프링 프레임워크 기본

지난 시간까지 JSP, Servlet, Tomcat을 이용해서 우리는 동적 웹 프로젝트를 개발했다. 그걸로도 충분히 가능했는데, 왜 우리는 “스프링 프레임워크”를 배워야 할까?

이에는 다음 4가지 이유가 있다.

1) 코드의 획일화

개발은 주로 함께 하는 “협업”의 형태를 띄고 있다. 그래서 여러 사람이 모이는데, 그 과정에서 만약 서로 다른 코드 스타일과 테크닉을 사용한다면 어떻게 될까?

평범한 예시로 싱글톤 자체를 구현하는 방식은 4가지이다. 근데 서로 구현 방식이 다르다면?
물론 프로젝트 개발은 되겠지만, 서로 작성한 코드가 다르기 때문에 1차적으로 병합 상황에서 2차적으론 유지 보수에 있어서 불리해진다.

따라서 인력이 곧 돈인 기업의 입장에서 코드 스타일을 획일화시키는 것은 필수적이다. 그리고 이를 스프링 프레임워크가 해준다.

즉, 스프링 프레임워크가 요구하는 기준(규칙)이 이미 정해져 있기 때문에 싱글톤도 MVC패턴도 같은 스타일로 통일시킬 수 있다.

2) 코딩 수준의 상향 평준화

앞서 말한 것처럼 스프링으로 개발하기 위해선 프레임워크가 정해놓은 방식을 따라야 한다. 그래서 초급 개발자 – 중급 개발자 간의 간극인 좁아진다. 물론 중급자 입장에서는 불편한 얘기겠지만, 그만큼 프레임워크를 의지하게 된다면 초급 개발자도 중급자 언저리의 퍼포먼스를 보여줄 수 있다는 것이 핵심이다.

3) 편의성

가장 핵심은 편리하다는 것이다. 일단 스프링 프레임워크가 지정한 규칙을 따르면 편하게 개발이 가능하다. 이런 점에서 (Controller, JDBC, 싱글톤)과 같은 기법들을 개발자는 인지하지 못한 채 편리하게 사용할 수 있다. (물론, 초급자 기준)

이는 우리가 처음 겪는 일이다. 세미 프로젝트에서 사용한 Tomcat(Servlet Container)이 스프링의 역할을 했다. 구체적으로 톰캣이 네트워크도 담당하고, 메모리 로딩 시 서블릿을 포함해 인스턴스를 생성했기 때문에 각 서블릿 간의 맵핑과 요청-응답이 가능했다는 것이다.

이처럼 스프링도 Spring Container로서 메모리 로딩 시, 인스턴스가 생성되고, 그 안에 여러 기능(부품)을 넣고 조립해두기 때문에 개발자가 불러다가 사용할 수 있고, 더 나아가 Maven을 사용해 라이브러리도 추가, 삭제를 편리하게 할 수 있다.

4) 유지 보수의 유리함

결국 앞선 3가지가 가리키는 것은 스프링 프레임워크를 사용하면, 프로그램 유지 보수에 유리함을 가질 수 있다는 것이다. 바로 이것이 스프링 프레임워크의 핵심이다.


스프링 포함 기능

Maven ( Build&Deployment 및 Library 관리 자동화 )

1) Build&Deployment

만들어진 프로그램은 배포하는 방식은 이클립스에 실행하는 것이 아니라, 설정파일(XML), 라이브러리, 소스 코드 등을 WAR파일로 Export해서 웹 서버에 올렸다.

이렇게 되면 프로그램이 바뀔 때마다 WAR를 생성하고 서버에 올리는 작업을 반복해야되는데, Maven은 이 과정을 버튼 하나로 줄여서 자동으로 서버에 배포해준다.

2) Library 관리

여태 라이브러리를 사용하려면 구글링하고 Maven Repo에서 찾아서 로컬에 저장하고 이를 이클립스에서 import한 뒤 사용하였다. 하지만 Maven을 사용하면 pom.xml 파일에 적어두는 것만으로 Maven이 중앙 레포에서 가지고 와서 업데이트 해준다.


5) 기본 세팅

그럼 이번에는 Spring 프로젝트의 기본 구조에 대해서 알아보자.

/main/java : 실제 사용하는 애플리케이션들이 들어간다.
/main/resources : 백엔드 애플리케이션에서 사용되는 자원이 들어간다.
( XML파일 → 스프링 가동을 위한 설정 파일 )
src : 이미지, CSS와 같은 프론트 자원이 들어가는 곳

/test : 단위 테스트 프레임워크를 쓸 때 사용하는 폴더이다.
JRE System Library : 우리가 사용하는 JDK 내장 라이브러리가 모여있다.

Maven Dependencies(의존성) : 메이븐이 받아준 라이브러리이다.

앞서 설명한 것처럼 라이브러리를 사용하려면 인터넷에서 서치 후, 다운한 뒤 import했다.
이제는 pom.xml(Maven 설정파일)에 우리가 dependencies 파트에 사용할 라이브러리를 입력 후, 저장하면 Maven으로 전달되어, Maven이 중앙 레포에서 가지고 와서 업데이트한다.

<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
			<exclusions>
				<!-- Exclude Commons Logging in favor of SLF4j -->
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				 </exclusion>
			</exclusions>
		</dependency>
</dependencies>

추가 설정

<properties>
		<java-version>1.6</java-version>
		<org.springframework-version>5.3.20</org.springframework-version>
		<org.aspectj-version>1.6.10</org.aspectj-version>
		<org.slf4j-version>1.6.6</org.slf4j-version>
	</properties>
  • 기존 스프링 프레임워크가 3버전을 기준으로 설정되어 있고, 얼마 전 보안이슈가 있었기 때문에 최신버전으로 변경해준다. 이는 스프링 관련 라이브러리들의 버전 자체를 업그레이드 한 것이다.

  • 이클립스 보면 자바 버전이 1.6으로 되어있을 수 있다. 그러면 properties에서 Project Facets에서 변경하면 된다


5. 스프링의 핵심

스프링의 핵심은 유지-보수에 있어서 유연성을 확보하고 편의성을 제공 하는 것이다. 이는 스프링 프레임워크의 핵심 가치인 POJO를 통해서 알 수 있다.

이런 점에서 스프링은 Extends-implements와 같은 상속 관계를 사용하지 않는 것은 지향한다. 물론 상속은 자바에서도 고급 기술에 포함되지만, 객체 간의 독립성 확보와 제어가 어렵기에 권장하지 않는다.

따라서 스프링 프레임워크의 소스코드에선 Servlet을 사용하지 않고, 모두 자바 Class를 사용한다. 왜냐면 Servlet자체가 톰캣의 HTTPServlet을 상속받고 있기 때문이다.

무튼 스프링의 핵심은 DL&DI / IOC / AOP 이렇게 3가지가 있다. 오늘은 DL과 IOC만 먼저 살펴보고, 다음 시간에 DI를 살펴보고자 한다.

의존성?

DL을 먼저 이해하기 위해선 “의존성”에 대해서 알아봐야 한다. 즉, Dependency가 무엇인지 알아야 한다. 일단 국내 번역으로는 종속성이라고 하는데, 상황으로 보자면 ‘한 객체가 다른 객체의 메서드를 실행할 때’ 이를 의존 상태라고 할 수 있다.

즉, 한 객체가 구동되기 위해선 다른 객체가 필요하다는 관점에서 ‘의존성’이라 할 수 있고, 한 객체(의존 대상)가 다른 객체(의존 주체) 내에 종속된다는 관점에서 ‘종속성’이라고 할 수 있다.

이보다 더 쉬운 이해는 의존 대상이 의존 주체의 ‘부품’이라고 이해할 수 있다. 예를 들면 컴퓨터가 동작하기 위해선 CPU, RAM 등 다양한 부품이 필요한 것처럼 컴퓨터라는 의존 주체의 동작을 위해선 부품(의존 대상)이 필요한 것이다.

따라서 이 부품을 끼워주는 행위, 또는 의존 관계를 만들어주는 방법이 DL / DI 라고 할 수 있다.


DL : Dependency Lookup (의존성 검색)

앞선 의존성 이해를 바탕으로 보면, DL은 의존성을 검색getBean();을 통해서 부여하는 방식을 말한다. 그럼 왜 DL을 사용할까? 이를 이해하기 위해 다음 문제를 함께보자.

[ 문제 상황 ]

의존도가 높은 코드들은 내부의 클래스와 메서드가 변경될 때, 하나가 바뀌면 연결된 모든 것을 바꾸고 재배포해야 한다. 이런 문제는 규모가 커질수록 심각해진다.

[ 해결 ]

이런 문제를 해결하기 위해서 기본적으로 ‘interface’와 ‘다형성’을 사용한다.

예를 들어 우리가 TV를 사용한다고 가정해보자

public class Main {
	public static void main(String[] args) {

		SamsunTV tv = new SamsungTV(); .	
		tv.powerOn();
		tv.powerOff();
		tv.channelDown();
		tv.channelUp();	
	}
}

그리고 삼성 TV는 다음과 같이 정의되어 있다.

public class SamsungTV {
	
	private int channel;
	private int volume;
	
	public SamsungTV() {
		System.out.println("SamsungTV 인스턴스가 생성되었습니다.");
	}
	
	
	
	public SamsungTV(int channel, int volume) {
		super();
		this.channel = channel;
		this.volume = volume;
	}


	public void powerOn() {};
	public void powerOff() {};
	public void channelUp() {};
	public void channelDown() {};
}

그런데 만약 여기서 LGTV로 바꿔야 한다면 어떻게 할까? LGTV는 다음과 같이 정의되어 있다.

public class LGTV  {
	
	private int channel;
	private int sound;
	
	public LGTV() {
		System.out.print("LGTV 인스턴스가 생성되었습니다.");
	}

	public void channelOn() {};
	public void channelOff() {};
	public void soundUp() {};
	public void soundlDown() {};	

}

LGTV는 삼성TV와 변수와 메서드가 모두 다르다. 그래서 만약 사용하는 TV를 변경할 경우 우리는 main의 코드 전체를 바꿔야한다. 이런 문제를 해결하기 위한 첫 번째가 바로 interface의 활용이다.

(1) 인터페이스로 메서드명을 고정

일단 두 TV는 모두 하나의 구현체이다. 그러나 이들의 ‘역할’ 즉, 클라이언트가 사용하는 TV의 역할은 변하지 않는다. 따라서 완전 추상체인 interface를 통해서 역할을 정의하여 구현되는 객체가 이를 따르게 하는 것이다.

public interface TV {
	// 추상 메서드는 실행부를 적지 않는다.
	// 이렇게 되면 이를 상속 받는 클래스들은 해당 메서드 명을 사용해야 한다. 
	public void powerOn();
	public void powerOff();
	public void channelUp();
	public void channelDown();
}

이렇게 되면 TV 인터페이스를 따르는 각 TV구현체들은 해당 메서드명을 따라해야된다. 이로 인해 최초의 메서드 명을 고정해줄 수 있다.

더욱이 사용자는 인터페이스를 마주할 뿐, 이를 따르는 구현체가 바뀌더라도 큰 영향을 받지 않는다.

(2) 다형성을 통한 용이성

더욱이 두 구현체(삼성, 엘지) 모두 TV인터페이스를 따르기 때문에 ‘다형성’을 사용할 수 있다.

public class Main {
	public static void main(String[] args) {

		TV tv = new SamsungTV(); // LGTV();도 상관없음
		tv.powerOn();
		tv.powerOff();
		tv.channelDown();
		tv.channelUp();	
	}
}

결과적으로 인터페이스를 사용하면, 코드의 변화를 최소화하여 의존성을 떨어트릴 수 있다. 이 방식을 jdbc에서도 알게 모르게 사용하였는데, 곧 Statement, Connection 등 모두 인터페이스였었다.

때문에 라이브러리 끌어오는 classforName이랑 그 접속하는 Connection만 바꾸면 다른 DB를 쓸 수 있다.


하지만 여전히 우리가 new를 사용하게 되면, 우리는 new가 되는 모든 부분을 수정해야한다.
그래서 이를 해결하기 위해 디자인 패턴 중에 하나인 ‘팩토리 패턴’과 main 매개변수를 활용해서 실행 시, 값을 전달해주는 것이다.

그러나 이 역시, 클래스 간의 구조가 복잡해지고 많아지면 한계가 오기 때문에 최종 대안으로서 아예 코드 밖에서 생성되도록 하였다.

그래서 이제는 resource로 넘어가서 백엔드에서 사용할 자원을 문서로 만들어준다.

src/main/resource 로 넘어가서 Spring bean Configuration file을 생성한 뒤, context.xml 파일에 new를 입력해준다.

	<bean id="tv" class="kh.spring.tvs.SamsungTV"></bean> 
	<!-- == new SamsungTV(); -->

이렇게 되면 스프링이 실행 시, 자신의 컨테이너에 해당 객체를 생성하여 가지고 있는다.

public class Main {
	public static void main(String[] args) {
		
		AbstractApplicationContext ctx = new GenericXmlApplicationContext("context.xml"); // 이 코드가 스프링 컨테이너를 생성하는 코드이다. 이 시점에 이미 생성됨.
		
		TV tv = (TV)ctx.getBean("tv"); // 스프링 컨테이너는 형을 모르기 때문에 형변환을 해줘야 한다.
		
		// getBean() : Dependency Lookup 스프링한테 개발자가 필요한 인스턴스를 요구하는 문법들을 '의존성 검색'이라고 한다.
		
		tv.powerOn();
		tv.powerOff();
		tv.channelDown();
		tv.channelUp();
		
		
	}
}

AbstractApplicationContext ctx = new GenericXmlApplicationContext("context.xml")가 실행되면 스프링 컨테이너는 xml문서를 분석해서 그 내용을 모두 품고 메모리에 로딩 된다.

즉, 우리가 설정한 내용을 스프링 컨테이너가 메모리에 적재해주므로, 결론적으로 스프링 컨테이너를 생성하는 new를 지울 수 없다.

그리고 이제 DL(dependency Lookup)이 나온다. 앞서 객체를 스프링 프레임워크가 만들고 그 주소를 들고 있다고 하였다. 이때, 개발자는 객체에 대한 권한(생명주기 / 스코프 / 디자인 패턴)을 프레임워크에 넘어가게 된다.

이를 바로 IOC(제어의 역전)이라고 부르는데, 프레임워크로 넘어갈 경우 객체 생성 프로세스가 역전된다는 것도 인지하고 있어야 한다.

이런 상황에서 개발자들은 자신이 원하는 객체를 쓰기 위해 DL(의존성 검색)을 한다. 이에 컨테이너는 보편적인 DL인 getBean(String id);를 통해서 개발자에게 인스턴스를 반환하는데, 스프링은 해당 인스턴스의 자료형을 알 수 없어 형변환을 꼭 해줘야 개발자가 사용가능하다.

정리하자면 new라는 키워드가 자바 소스 안에 존재하게 되면 코드의 유지 보수가 어려워지기 때문에 스프링이 직접 객체를 생성하도록 그 권한을 개발자가 넘겼다.

그리고 그 객체를 사용하기 위해서 개발자는 DL(dependency)을 이용해 해당 객체를 끌어와 의존성을 부여한다.

*번외

이 과정에서 싱글톤도 같이 적용되는데, 안 쓰고 싶으면 xml에 scope=“prototype”넣어주면 된다.

< 요약 >

  • new를 사용하는 순간 의존성, 곧 객체 간의 결합도가 높아진다.
  • 결합도가 높으면 유지보수가 힘들다.
  • 이를 해결하기 위해 interface와 다형성을 써도 모자랐다.
  • 그래서 new를 줄이기 위해 팩토리 패턴과 main의 매개변수도 써봤지만 문제는 여전했다.
  • 즉, 개발자가 객체 생성권한을 가지면 필연적으로 결합도는 올라간다.
  • 따라서 이를 프레임워크에 넘겨, 프로그램이 실행되면서 만들어지고 각각 의존 관계가 자동으로 성립되게 만들었다.
  • 그 방식 중 하나가 검색을 통해서 의존성을 주입하는 DL이다.
profile
기록을 쌓아갑니다.

0개의 댓글