2020. 11. 26. 00:31ㆍ스프링프레임워크
1. 프레임워크 개념
1-1. 프레임워크의 등장 배경
프레임워크의 소프트웨어 관점에서 핵심은 '아키텍처에 해당하는 골격코드'라고 할 수 있음.
애플리케이션을 개발할 때 가장 중요한 것이 애플리케이션의 구조를 결정하는 아키텍처인데, 이 아키텍처에 해당하는 골격 코드를 프레임워크가 제공함.
프레임워크는 애플리케이션을 개발할 때 기본이 되는 뼈대나 틀을 제공하여서, 개발자에게 모든 것을 위임하는 것이 아니라 애플리케이션의 기본 아키텍처는 프레임워크가 제공하고, 그 뼈대에 살을 붙이는 작업만 개발자가 하도록 함.
1-2. 프레임워크의 장점
빠른 구현시간
쉬운 관리
개발자들의 역량 획일화
검증된 아키텍처의 재사용과 일관성 유지
2. 스프링 프레임워크
2-1. 스프링 탄생배경
스프링 프레임워크는 로드 존슨(Road Johnson)이 2004년에 만든 오픈소스 프레임워크
스프링 프레임워크가 등장하기 이전에 자바 기반의 엔터프라이즈 애플리케이션은 대부분 EJB(Enterprise Java Beans)로 개발되었지만, EJB는 스펙이 너무 복잡해서 학습에 많은 시간이 필요하기도 했고, EJB를 제대로 사용하려면 디자인 패턴에 대한 이해가 필수적이라는 것 등등 문제점들이 있었음
반면에, 스프링 프레임워크는 평범한 POJO(Plain Old Java Object)를 사용하면서도 EJB에서만 가능했던 많은 일을 가능하도록 지원함
POJO(Plain Old Java Object)란?
POJO란 평범한 옛날 자바 객체를 의미
POJO에 대한 좀 더 쉬운 이해를 위해 반대로 POJO가 아닌 클래스가 무엇인지 생각해보면, 대표적인 Not POJO클래스가 Servlet클래스가 있음
Servlet클래스는 우리 마음대로 만들 수 없으며, 반드시 Servlet에서 요구하는 규칙에 맞게 클래스를 만들어야 실행할 수 있음
POJO는 클래스를 구현하는 데 특별한 규칙이 없는 단순하고 가변운 객체
2-2. 스프링 프레임워크의 특징
경량(Lighteight)
제어의 역행(Invertion of Control)
관점지향 프로그래밍(Aspect Oriented Programming, AOP)
컨테이너(Contatiner)
2-3. IOC(Inversion of Control) 컨테이너
기존의 서블릿이나 EJB 기술에서도 이미 사용해온 컨테이너의 개념은 대부분 비슷한 구조와 동작 방식을 가지고 있으므로 서블릿 컨테이너를 통해 스프링 컨테이너의 동작 방식을 유추할 수 있음
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class HelloServlet
*/
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public HelloServlet() {
//super();
System.out.println("===> HelloServlet 객체 생성");
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//// TODO Auto-generated method stub
//response.getWriter().append("Served at: ").append(request.getContextPath());
System.out.println("doGet()메서드호출");
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello.do</url-pattern>
</servlet-mapping>
</web-app>
위 코드는 스프링 컨테이너의 이해를 위한 서블릿 컨테이너를 구동하는 예제를 작성해본 것인데,
xml설정은 '/hello.do'라는 URL요청을 전송하면 , hello라는 이름으로 등록된 'HelloServlet'클래스를 찾아 객체를 생성하고 실행하게 하는 설정이고 작성된 'HelloServlet'프로그램을 실행하면 아래와 같은 메시지가 콘솔에 출력되며, 'web.xml'설정대로 객체가 생성되고 실행되는 것을 확인할 수 있음
- WEB-INF/web.xml 파일을 로딩하여 구동
- 브라우저로부터 '/hello.do'요청 수신
- 'HelloServlet' 클래스를 찾아 객체를 생성하고 doGet()메소드 호출
- doGet()메소드 실행 결과를 클라이언트 브라우저로 전송
컨테이너는 자신이 관리할 클래스들이 등록된 XML 설정 파일을 로딩하여 구동하고 클라이언트의 요청이 들어오는 순간 XML 설정 파일을 참조하여 객체를 생성한 뒤, 객체의 생명주기를 관리함
스프링 컨테이너 역시 서블릿 컨테이너와 유사하게 동작하므로 위에 살펴본 요소들과 비슷한 요소들이 존재함
제어의 역행(IoC)은 결합도와 관련된 개념으로 이해할 수 있는데, 기존에 자바 기반으로 애플리케이션을 개발할 때, 객체를 생성하고 객체를 생성하고 객체들 사이의 의존관계를 처리하는 것에 대한 책임은 전적으로 개발자에게 있었음
즉, 개발자가 어떤 객체를 생성할지 판단하고 객체 간의 의존관계 역시 소스코드로 표현해야 했음
하지만 제어의 역행이라는 것은 이런 일련의 작업들을 소스코드로 처리하지 않고 컨테이너로 처리하는 것을 의미
제어의 역행을 이용하면 소스에서 객체 생성과 의존관계에 대한 코드가 사라져 결과적으로 낮은 결합도의 컴포넌트를 구현할 수 있게 함.
2-3-1. 결합도(Coupling)가 높은 프로그램
결합도라 하나의 클래스가 다른 클래스와 얼마나 많이 연결되어 있는지를 나타내는 표현이며, 결합도가 높은 프로그램은 유지보수가 어려움
package polymorphism;
public class SamsungTV {
public void powerOn() {
System.out.println("SamsungTV---전원 켠다.");
}
public void powerOff() {
System.out.println("SamsungTV---전원 끈다");
}
public void volumeUp() {
System.out.println("SamsungTV---소리 올린다.");
}
public void volumeDown() {
System.out.println("SamsungTV---소리 내린다.");
}
}
package polymorphism;
public class LgTV {
public void turnOn() {
System.out.println("LgTV---전원 켠다.");
}
public void turnOff() {
System.out.println("LgTV---전원 끈다");
}
public void soundUp() {
System.out.println("LgTV---소리 올린다.");
}
public void soundDown() {
System.out.println("LgTV---소리 내린다.");
}
}
'SamsungTV'객체를 생설하여 메서드를 호출하는 'TVUser'클래스 작성 후 실행
package polymorphism;
public class TVUser {
public static void main(String[] args) {
SamsungTV tv = new SamsungTV();
tv.powerOn();
tv.powerOff();
tv.volumeUp();
tv.volumeDown();
}
}
'LgTV'객체를 생설하여 메서드를 호출하는 'TVUser'클래스 작성 후 실행
package polymorphism;
public class TVUser {
public static void main(String[] args) {
LgTV tv = new LgTV();
tv.turnOn();
tv.turnOff();
tv.soundUp();
tv.soundDown();
}
}
SamsungTV와 LgTV는 메소드 시그니처(signature)가 다르므로 TVUser코드 대부분을 수정해야 TV를 교체할 수 있음
현재 상태에서는 두 TV클래스가 같은 메소드를 가지게끔 강제할 어떤 수단도 없음.
만약 TVUser와 같은 클라이언트 프로그램이 하나가 아니라 여러 개라면 유지보수는 더욱 더 힘들 것이며, TV교체를 결정하기가 어려움.
2-3-2. 다형성(Polymorphism) 이용하기
결합도를 낮추기 위해서 다양한 방법을 사용할 수 있겠지만, 가장 쉽게 생각할 수 있는 것이 객체지향 언어의 핵심 개념인 다형성(Polymorphism)을 이용하는 것임
다형성을 이용한 예제를 실행해보기 위해 아래와 같은 'TV'인터페이스를 추가하여 코드를 작성 후 'TVUser'클래스를 실행
package polymorphism;
public interface TV {
public void powerOn();
public void powerOff();
public void volumeUp();
public void volumeDown();
}
package polymorphism;
public class SamsungTV implements TV{
@Override
public void powerOn() {
System.out.println("SamsungTV---전원 켠다.");
}
@Override
public void powerOff() {
System.out.println("SamsungTV---전원 끈다");
}
@Override
public void volumeUp() {
System.out.println("SamsungTV---소리 올린다.");
}
@Override
public void volumeDown() {
System.out.println("SamsungTV---소리 내린다.");
}
}
package polymorphism;
public class LgTV implements TV{
@Override
public void powerOn() {
System.out.println("LgTV---전원 켠다.");
}
@Override
public void powerOff() {
System.out.println("LgTV---전원 끈다");
}
@Override
public void volumeUp() {
System.out.println("LgTV---소리 올린다.");
}
@Override
public void volumeDown() {
System.out.println("LgTV---소리 내린다.");
}
}
package polymorphism;
public class TVUser {
public static void main(String[] args) {
// SamsungTV 객체생성 후 메서드 호출
// LgTv 메서드를 사용하려면 SamsungTV객체만 LgTV객체로 변경해 주면됨
SamsungTV tv = new SamsungTV();
tv.powerOn();
tv.powerOff();
tv.volumeUp();
tv.volumeDown();
}
}
위와 같이 다형성을 이용하여 클래스를 작성 시 인터페이스를 구현한 클래스들은 공통적인 메서드를 구현하도록 강제할 수 있고 'TVUser'클래스에서 메소드를 호출하는 소스코드를 모두 변경하지 않아도 'SamsungTV' 객체생성하는 코드를 'LgTV'객체로 변경만 하면 'LgTV'클래스에 작성된 메서드들을 호출이 가능함
2-3-3. 디자인 패턴 이용하기
결합도를 낮추기 위한 또 다른 방법으로 디자인 패턴을 이용하는 방법이 있음.
앞에서 살펴본 다형성을 이용한 방법은 메소드를 호출할 때 인터페이스를 이용함으로써 좀 더 쉽게 TV를 교체할 수 있었음. 하지만 이 방법 역시 TV를 변경하고자 할 때, TV클래스 객체를 생성하는 소스를 수정해야만 함.
TV를 교체할 때 클라이언트 소스를 수정하지 않고 TV를 교체할 수 만 있다면 유지보수는 더욱 편리해질 것.이를 위해서 factory패턴을 적용해야 하는데, factory패턴은 클라이언트에서 사용할 객체 생성을 캡슐화하여 TVUser와 TV사이를 느슨한 결합 상태로 만들어 줌.
'Factory'패턴이 적용된 'BeanFactory' 클래스를 추가
package polymorphism;
public class BeanFactory {
public Object getBean(String beanName) {
if(beanName.equals("samsung")) {
return new SamsungTV();
} else if(beanName.equals("lg")) {
return new LgTV();
}
return null;
}
}
'BeanFactory'클래스의 getBean()메소드는 매개변수로 받은 beanName에 해당하는 객체를 생성하여 리턴.
이제 이 'BeanFactory'클래스를 이용하여 사용할 TV객체를 획득하도록 'TVUser'클래스를 수정.
package polymorphism;
public class TVUser {
public static void main(String[] args) {
BeanFactory factory = new BeanFactory();
System.out.println(args[0]);
TV tv = (TV)factory.getBean(args[0]);
tv.powerOn();
tv.powerOff();
tv.volumeUp();
tv.volumeDown();
}
}
'TVUser'클래스를 실행시 명령행 매개변수를 전달하지 않으면 위와 같은 예외가 발생함.
'TVUser'클래스에서 마우스 오른쪽 버튼 클릭 > Run As > Run Configurations를 선택 .
Arguments 탭을 선택하고 "lg" 혹은 "samsung"을 입력하고 Run 버튼을 클릭하면 정상적으로 실행됨
출처 : SPRING QUICK START 스프링 퀵 스타트(교재) [루비페이퍼]