Framework - Spring

MisCaminos·2021년 3월 8일
0

Server & Web

목록 보기
11/23

framework의 개념:

software의 architecture에 해당하는 골격코드
뼈대/ 틀의 역할
(application을 개발할때에 가장 중요한것이 전체 application의 구조를 결정하는 architecture인데, 이 architecture에 해당하는 골격 코드를 framework가 제공함. 개발자는 이 뼈대에 살을 붙이는 작업을 한다.)

framework의 장점들

-잘 만들어진 framework를 사용하면, application에 대한 분석, 설계, 구현능력 향상 & 코드의 재사용성이 증가함.
-빠른 구현 시간: architecture 골격 코드가 주어지기때문에, 개발자는 business logic(model) 부분에만 집중하면된다. 제안된 시간안에 더 많은 기능을 구현할 수 있다.
-쉬운 관리:
같은 framework가 적용된 application들은 architecture관리가 같다. 그래서 유지보수에 들어가는 인력 + 시간을 줄일 수 있다.
-개발자들의 역량 획일화:
숙련&신입 개발자들은 경력/지식 다르지만 비슷한 코드를 생성할 수 있다.
관리자 입장에서 개발 인력을 더 효율적으로 구성할 수 있다.
-검증된 architecture 재사용과 일관성 유지, 시스템 개발 후 시간이 지나도 유지보수 과정에서 architecture 왜곡, 변형일어나지 않는다.

회사 내 자체 framework가 있을 수도 있다.
Spring을 기반으로 만들 framework일 것이다. spring을 이해하고있어야함.

Spring framework의 특징:

Spring framework는 많은 디자인 패턴이 적용되어 배포됨으로 framework 이용하는 것 자체가 design pattern을 사용하는 것임. Spring framework은 open source이다.

1. lightweight

spring framework는 특별하게 따로 API를 사용하지않아도 된다. 객체간의 관계를 구성할 때 기존에 존재하는 Java library를 사용하기때문에 가볍다.

2. 제어 역행 (IoC Inversion of Control)

객체 생성 + 의존관계 만드는 제어권이 Java application에서 container로 바뀐다. 기존에는 application에서 new keyword+생성자로 객체 생성 또는 setter로 값을 주입 했었다.
그러나 spring framework에서는 제어권이 application -->> container로 바뀌어서 "제어 역행"이다.

Servlet을 사용하는 경우, Servlet Container가 객체의 생성부터 생명주기(Life Cycle)를 전담하게된다. (TomCat이 알아서 객체를 만들어서 내가 사용할 수 있는것이다)
객체의 생성에서부터 생명주기의 관리까지 모든 객체에 대한 제어권이 바뀌었다는 것을 의미한다.

3. 의존성 관리(DI Dependency Injection)


IOC가 아닌 경우, (회색)하나의 객체가 new를 통해 다른(흰색) 객체를 만든다. 흰색이 각자의 역할을 수행하는데에, 회색에 의존하지는 않는다.

이와 다르게 Spring framework에서는 IOC가 적용되어 의존성이 존재한다. 다른 객체들(흰색)이 회색 객체로인해 자동으로 만들어지고, 값이 주어진다. 의존성(Dependency)이라는 것은 하나의 객체가 다른 객체 없이 제대로 된 역할을 할 수 없다는 의미이다. 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 자동으로 관리한다

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

transaction이나 logging, security와 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우, 해당 기능을 분리해서 관리한다. 실행 시, 분리했던 기능들을 합쳐서 각 모듈에서 제 역할을 수행하도록 하는것이다.

공통 모듈의 반복적인 코드를 줄일 수 있다. 핵심 비지니스 로직에만 집중할 수 있는 방법을 제공한다.

5. 컨테이너

컨테이너는 특정 객체의 생성 + 관리를 담당한다. 그리고 객체의 운용에 필요한 기능을 제공한다.

컨테이너는 보통 서버안에 포함되어 배포 및 구동된다. 대표적인 컨테이너로는 servlet container가 있다. Servlet container는 tomcat server에 포함되어있고 servlet 객체를 생성하고 관리한다.

Application운용에 필요한 객체를 생성하고 객체 간의 의존 관계를 관리한다.
spring container = IOC container
container안에서 DI & AOP를 운용한다.

IOC Container

spring framework가 어떻게 동작하는지 이해하려면 container가 동작하는 방식을 이해해야한다.

Servlet Container를 통해 spring container 동작방식을 유추할 수 있다.

Spring Legacy Project

아래와 같은 type의 Project를 생성해서 spring container 동작방식을 확인해보았다.

Project Type: Spring Legacy Project (-> Simple Spring Utility Project)
Name: HelloApp
Package: spring.sts.sample
Library: 프로젝트 생성시 관련 Spring 라이브러리가 자동으로 다운로드됨.

Project 구성

HelloApp이라는 프로젝트를 구성하면, 아래 그림과 같이 directory가 생성된다.

src/main/java 폴더에 Java class들을 저장하고, src/main/resources 폴더에는 Java class외의 파일들을 저장한다. src/main/resources 폴더에는 ioc에서 각 컨테이너가 객체들을 관리하기위해 필요한 설정 정보를 xml 파일형태로 저장한다. src/main/resources/META-INF/spring에 "app-context.xml과 같은 파일이 자동 생성된다.

프로젝트 directory의 맨 아래에 있는 pom.xml에서는 properties 태그안에 spring framework version을 수정+저장해서 원하는 spring framework으 다운받을 수 있다.

클래스들간의 결합도가 높은 유형에서부터 낮은 유형까지 각각 실습을 해보면서 코드의 생성 및 유지 보수가 어떻게 더 수월해 지는지 확인 할 수 있었다.

  1. high coupling 유형의 Classes
    (실습@ package:coupling)
  2. polymorphism 활용
    (실습@ package:polymorphism)
    Interface로 추상적인 타입을 생성해서 구체화된 객체들은 추상 method들을 각자에 맞게 재정의하여(override) 사용하도록.
  3. design pattern으로 결합도 낮추기
    (실습@ package:factory)
    TV interface의 추상화에 추가로 factory 패턴을 적용한다. Factory 패턴을 적용하면 클라이언트에서 사용할 객체 생성을 캡슐화하여 TVUser와 TV 사이를 느슨한 결합 상태로 만들수 있다.
    TV를 교체할 때, 클라이언트 소스(TVUser.java)를 수정하지 않고 TV를 교체 한다면 유지보수는 더욱 편해진다. Factory는 객체를 만들어주는 공장이다. BeanName에 주어진 이름대로 구분해서 객체를 생성한다.

BeanFactory.java:

package factory;
 
public class BeanFactory {
  public static TV getBean(String beanName) {
    if(beanName.equals("samsung")) {
      return new SamsungTV();
    }else if(beanName.equals("lg")) {
      return new LgTV();
    }
    return null;
  }
}

TVUser.java:

package factory;

public class TVUser {
	public static void main(String[] args) {
		TV tv = BeanFactory.getBean(args[0]);	
		tv.powerOn();
		tv.volumeUp();
		tv.volmeDown();
		tv.powerOff();
    }
}
  1. Spring IOC 이용
    (실습@ package:ioc)
    3번에서 factory로 bean 객체 생성방식을 활용한것을 기반으로, spring IOC 방식을 추가할 수 있다.
    IOC 컨테이너를 활용하는 방식에서는 각 컨테이너에서 관리할 객체들을 위한 별도의 설정파일을 사용한다.
    src/main/resources에 아래와 같은 경로에 "app-context.xml"과 같은 설정파일을 만들거나 자동으로 생성된다. 아래 app-context.xml에서는 bean 객체생성 방식으로 LgTV 객체를 컨테이너가 자동으로 생성하게 설정했다.

app-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-3.0.xsd">
 
<description>Example configuration to get you started.</description>
 
<!--  <context:component-scan base-package="spring.sts.sample" /> -->
<bean id="tv" class="ioc.LgTV"></bean>
 
</beans>

TVUser.java:

package ioc;
 
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
 
public class TVUser {
 
  public static void main(String[] args) {
// 1. Spring container 구동
// app-context.xml안에 <bean>태그 안에 선언한 내용을 보고 객체를 생성해준다.
// GenericXmlApplicationContext instance 생성시, 바로 init() 호출 & bean에 정의한 class 변수를 초기화한다
    AbstractApplicationContext factory = 
        new GenericXmlApplicationContext("META-INF/spring/app-context.xml");
    
//2. Spring 컨테이너로부터 필요한 객체를 요청(Lookup)한다
// getBean으로 가져올때에 object 형태이기때문에, TV 형으로 casting해주어야함.
    TV tv = (TV)factory.getBean("tv");
    tv.powerOn();
    tv.powerOff();
    tv.volumeUp();
    tv.volumeDown();
    
 //3.Spring 컨테이너를 종료한다.
    factory.close();
  }
 
}

아래 그림과 같이 app-context.xml에 정의해놓은 Bean 정보에 따라 컨테이터가 자동으로 객체(Bean)을 생성한다. Bean 객체를 application 구동시 언제, 어떤 방식으로, 어떤 property를 갖추어서 생성할지를 app-context.xml에 정의해 놓는 것이다. TVUser는 getBean()메소드를 통해 생성해둔 TV 객체를 가져다가 사용하면 된다. 각 TV Class의 메소드를 호출할 수 있다.

객체 사이의 의존 관계를 스프링 설정 파일에 등록된 정보를 바탕으로 컨테이너가 자동으로 처리해 준다.

의존성 설정을 바꾸고 싶을때 프로그램 코드를 수정하지 않고 스프링 설정파일만 수정하여 변경사항을 적용할 수 있어 결합도는 낮아지고 유지보수가 향상된다.

Spring XML 설정방법

1. < beans > 루트 엘리먼트

스프링 컨테이너는 < bean > 저장소에서 해당 XML 설정 파일을 참조하여 < bean >생명주기를 관리하고 여러가지 서비스를 제공한다.
< bean >,< description >,< alias >,< import >등 네개의 자식엘리먼트로 사용할 수 있다.

2. < import > 엘리먼트

트랜잭션 관리, 예외처리, 다국어처리등 다양한 설정을 각각의 설정파일로 나누어 설정후 하나로 통합할 때 < import >엘리먼트를 사용한다. < import > 태그를 이용하여 여러 스프링 설정파일을 포함하여 한 파일에 작성하는 것과 같은 효과를 낼 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
        https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                http://www.springframework.org/schema/context 
                https://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <import resource = "context-datasource.xml"/>
    <import resource = "context-transaction.xml"/>
</beans>

3. < bean > 엘리먼트

스프링 설정 파일에 클래스를 등록할 때 사용한다. id, class 속성을 사용한다. class속성으로 객체를 생성한다. id 속성값은 자바의 식별자 규칙을 따른다. id와 같은 기능을 하는 속성으로 name이 있다. name은 자바 식별자 규칙을 따르지 않는 문자열도 허용한다. id, name속성값은 전체 스프링 파일내에서 유일해야 한다.

4. < bean >의 속성

■ init-method

멤버변수 초기화를 위해서 init-method 속성이 필요하다. 스프링 컨테이너는 설정 파일(app-context.xml)에 등록된 클래스를 객체 생성할 때 default constructor(기본 생성자)를 호출하기때문에 init-method를 따로 속성으로 지정해야한다.

(Servlet container가 init()을 통해 작동하는 방식과 비슷하다. Servlet 컨테이너는 web.xml 파일에 등록된 Servlet 클래스의 객체를 생성할 때 기본생성자만 인식하기때문에 Servlet객체의 멤버변수를 초기화를 위해서는 Controller Servlet 클래스에서 init()를 재정의하여 사용했었다.)

app-context.xml:

<beans ...>
   <bean id="tv" class="ioc.SamsungTV" init-method="initMethod"></bean>
</beans>

ioc.SamsungTV.java:

  //초기화 메소드 추가
  public void initMethod() {
    System.out.println("객체 초기화 작업 진행");
    //여기에 SamsungTV 클래스의 맴버 변수들을 초기화하는 여러가지 코드가 작성될것이다 
  }

■ destroy-method
객체를 삭제하기 직전에 호출할 임의의 메소드를 지정할 수 있다.

app-context.xml:

<beans ... >
   <bean id="tv" class="ioc.SamsungTV" init-method="initMethod" destroy-method="destroyMethod"></bean>
</beans>

ioc.SamsungTV.java:

  public void destroyMethod() {
    System.out.println("객체삭제 전 처리할 작업");
  }

■ lazy-init
컨테이너를 구동하면 컨테이너가 구동되는 시점에 스프링 설정파일에 등록된 < bean >들을 생성하는 즉시 로딩(pre-loading)방식으로 동작한다.

당장/ 자주 사용하지 않는 bean까지 모두 생성하면 메모리를 많이 차지하여 시스템에 부담을 줄수있다. lazy-init 속성을 사용하면 < bean >이 사용되는 시점에서 객체를 생성한다.

아래 코드와 같이 lazy-init = "true"로 설정하여 < bean >을 미리 생성하지 않고 클라이언트가 요청시점에서 생성한다.

<beans ... >
   <bean id="tv" class="ioc.SamsungTV" lazy-init="true"></bean>
</beans>

■ scope
< bean >객체가 하나만 또는 getBean()호출시마다 생성할 수 있도록 설정하는 방법이다.
< bean scope="singleton" > 하나만 생성된다. 기본값이다.
< bean scope="prototype" > getBean()호출시마다 객체 생성

app-context.xml:

<beans ... >
   <bean id="tv" class="ioc.SamsungTV" scope="prototype"></bean>
</beans>

TVUser.java:

    TV tv2 = (TV)factory.getBean("tv");
    TV tv3 = (TV)factory.getBean("tv");
    TV tv4 = (TV)factory.getBean("tv");

Dependency Injection의 구현

1. 생성자 Injection

매개변수 생성자를 호출해서 의존관계를 설정할 수 있다.
app-context.xml 설정파일에서 < bean > 요소의 하위 요소인 < constructor-arg >를 사용한다.

생성할 클래스를 bean 요소로 지정하고, 그의 속성으로 constructor-arg에 ref="speaker"를 지정한 후, AppleSpeaker클래스를 constructor-arg ref에 지정한 이름과 동일한 id를 가진 bean 요소로 추가하면, SamsungTV 객체 생성 시, 생성자를 통해 맴버변수인 AppleSpeaker 클래스 생성 및 해당 클래스 타입의 speaker를 초기화 할 수 있다.

app-context.xml:

<beans ... >
 <bean id="tv" class="ioc.SamsungTV" >
     <constructor-arg ref="speaker"></constructor-arg>
 </bean>
 <bean id="speaker" class="ioc.AppleSpeaker "></bean>
</beans>

ioc.SamsungTV.java:

package ioc;
 
public class SamsungTV implements TV {
  
  private AppleSpeaker speaker;
  
  public SamsungTV() {
    System.out.println(">>>SamsungTV(1) 객체 생성");
  }
  public SamsungTV(AppleSpeaker speaker) {
    System.out.println(">>>SamsungTV(2) 객체 생성");
    this.speaker = speaker;
  }
  ...
  public void volumeUp() {
    speaker.volumUp();
  }
 
  public void volumeDown() {
    speaker.volumDown();
  }
 
}

ioc.AppleSpeaker.java :

package ioc;
 
public class AppleSpeaker {
 
  public AppleSpeaker() {
     System.out.println(">>> AppleSpeaker 객체 생성");
  }
  public void volumUp() {
    System.out.println("AppleSpeaker----소리 올린다.");
  }
  public void volumDown() {
    System.out.println("AppleSpeaker----소리 내린다.");
  }
}

bean 요소아래에 constructur-arg 요소를 여러개 포함시켜서 여러개의 매개변수를 mapping할 수도 있다. 두번째 변수인 price와 같이 매개변수의 타입이 기본형인 경우에는 value를 사용한다. 첫번째 변수인 speaker와 같은 경우에는 AppleSpeaker클래스의 변수로서 hashcode를 가져가야하기때문에 ref를 사용하는것이다.

app-context.xml:

<bean id="tv" class="ioc.SamsungTV" >
     <constructor-arg ref="speaker"></constructor-arg>
     <constructor-arg value="2700000"></constructor-arg>
</bean>

ioc.SamsungTV.java:

package ioc;
 
public class SamsungTV implements TV {
  
  private AppleSpeaker speaker;
  private int price;
  
  public SamsungTV() {
    System.out.println(">>>SamsungTV(1) 객체 생성");
  }
  public SamsungTV(AppleSpeaker speaker, int price) {
    System.out.println(">>>SamsungTV(2) 객체 생성");
    this.speaker = speaker;
    this.price = price;
  }
  public void powerOn() {
    System.out.println("SamsungTV.....전원 켠다.(가격: "+price+")");
  }  
  public void volumeUp() {
    speaker.volumUp();
  }
 
  public void volumeDown() {
    speaker.volumDown();
  }
}

2. Setter Injection

클래스의 의존관계를 형성하기위해 setter method를 호출해서 처리할 수 있다. app-context.xml 설정 파일에서 bean 요소내에서 property 속성을 사용하면된다. (Since 클래스의 맴버변수 초기화는 생성자외에 setter를 통해서도 가능하기에, it makes sense that both ways exist!) setter를 통한 injection 방법은 다음과 같다.

app-context.xml:

<bean id="tv" class="ioc.SamsungTV" >
      <property name="speaker" ref="speaker"></property>
      <property name="price" value="2700000"></property>
</bean>

ioc.SamsungTV.java:

...
public void setSpeaker(AppleSpeaker speaker) {
    System.out.println(">>>setSpeaker() 호출");
    this.speaker = speaker;
  }
  public void setPrice(int price) {
    System.out.println(">>>setPrice() 호출");
    this.price = price;
  }
 ...

p-namespace를 사용하면 property 태그를 따로 작성하지않고, bean요소 작성시 한꺼번에 설정할 수 있다. 단, .xml파일 맨위에 "여기에서 p-namespace를 사용할겁니다"라고 미리 선언해두어야한다. < beans >에 xmlns:p="http://www.springframework.org/schema/p" 를 추가한다.

app-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
....
xmlns:p="http://www.springframework.org/schema/p"
...>
<bean id="tv" class="ioc.SamsungTV" p:speaker-ref="speaker"
p:price="2700000"> 
</bean>
<bean id="speaker" class="ioc.AppleSpeaker"></bean>
</beans>



Reference:

스프링 framework이란? & 스프링 XML설정 from Lectureblue

profile
Learning to code and analyze data

0개의 댓글