스프링 주요 기술

스프링 삼각형과 설정정보

  • 스프링을 이해하기 위해서는 POJO를 기반으로한 스프링 삼각형이라 불리는 3대 프로그래밍 모델에 대한 이해가 필수적입니다.
  • 스프링 삼각형을 구성하는 3대 프로그래밍 기술이란 Ioc/DI, AOP, PSA가 있습니다.

1. IoC/DI

의존성이란

  • 스프링의 IoC라고 부르는 DI를 알아보기 전에, 먼저 프로그래밍에서 의존성이랑 개념은 다음과 같습니다.

    자동차는 내부적으로 타이어를 가진다.
    
    = Car 객체는 new Tire()를 가진다.
    
    = Car가 Tire에 의존한다.
    
  • 즉, 프로그램에서 의존성이란 전체가 부분에 의존하며 new 를 통해 관계가 표현되는 것을 말합니다.

  • 조금 더 깊이 들어가서 의존하는 객체(전체)와 의존되는 객체(부분) 사이에 집합 관계나 구성 관계로 구분지을 수 도 있는데, 아무튼 전체가 부분에 의존한다는 개념입니다.

    • 집합 관계 : 부분이 전체와 다른 생명 주기를 가질 수 있습니다. (ex) 집 - 냉장고)

    • 구성 관계 : 부분이 전체와 같은 생명 주기를 가질 수 있다. (ex) 사람 - 심장)

  • 다음 예시는 자동차와 타이어에 대한 의존성을 나타낸 코드입니다.

    • 자동차 클래스 생성자 내부에서 타이어에 대한 의존관계가 일어났습니다.
interface Tire() {  
    String getBrand();  
}  
​  
public class KoreaTire implements Tire {  
    public String getBrand() {  
        return "코리아 타이어";  
    }  
}  
​  
public class AmericaTire() implements Tire {  
    public String getBrand() {  
        return "아메리카 타이어";  
    }  
}
public class Car {  
    Tire tire;  
 
    public Car() {  
        this.tire = new KoreaTire();  
     // this.tire = new AmericaTire();  
    }  
    
    public String getTireBrand() {  
        return "장착된 타이어 : " + tire.getBrand();  
    }  
}
  
public class CarFactory {  
    public static void main(String[] args) {  
        Car car = new Car();  
        System.out.println(car.getTireBrand());  
    }  
}

의존성 주입

  • 주입이란 말은 **외부에서**라는 의미를 내포하고 있는 것으로, 자동차 내부에서 타이어를 생산하는 것이 아니라 외부에서 생상된 타이어를 자동차에 장착하는 작업을 주입이라 할 수 있습니다.

  • 의존성 주입하는 방법에는 생성자를 통한 의존성 주입과 setter 함수를 통한 의존성 주입이 있습니다.

  1. 생성자를 통한 의존성 주입

    • 생성자를 통한 의존성 주입을 하기 위해서는 생성자 인자로 객체를 주입받으면 됩니다.
public class Car {  
    Tire tire;  
        
    public Car(Tire tire) {  
     this.tire = tire;  
    }  
        
    puvlic String getTireBrand() {  
     return "장착된 타이어 : " + tire.getBrand();  
    }  
}

public class CarFactory {  
    public static void main(String[] args) {  
     Tire tire = new KoreaTire();  
     //Tire tire = new AmericaTire();  
        
     Car car = new Car(tire);  
     System.out.println(car.getTireBrand());  
    }
}
  1. setter 함수를 통한 의존성 주입
    • 마찬가지로 setter 함수에서 인자를 통해 객체를 주입받으면 된다.
public class Car {  
    Tire tire;  
        
    public void setTire(Tire tire) {  
     this.tire = tire;  
    }  
        
    puvlic String getTireBrand() {  
     return "장착된 타이어 : " + tire.getBrand();  
    }  
}
      
public class CarFactory {  
    public static void main(String[] args) {  
        Tire tire = new KoreaTire();  
      //Tire tire = new AmericaTire();  
        
        Car car = new Car();  
        car.setTire(tire);  
        System.out.println(car.getTireBrand());  
    }
}
  • 앞서 살펴본 것 처럼 의존성을 주입을 통해 더 이상 자동차가 KoreaTire/AmericaTire를 선택하여 의존관계를 맺을지 결정하지 않아도 됩니다.

  • 즉, 자동차가 생산될 때 어떤 타이어를 생산해서 장착할지를 자동차가 고민하고 결정하는 것이 아니라, 외부에서(공장이라든지) 어떤 타이어를 장착할지 결정하여 주입시키는 것입니다.

  • 이러한 방법이 현실세계와 비교하여 생각해보면 더 자연스럽고 유연성있다고 볼 수 있습니다.


스프링의 의존성 주입

  • 스프링은 생성자나 setter를 통한 의존성 주입 방법을 모두 지원하고 있으며, 위의 방법들과 다른 점은 자동차나 타이어 객체를 생성하고 의존관계를 맺는 과정이 별도로 xml 파일로 빠져있다는 것입니다.
public class CarFactory {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("example.xml");  
     
        Car car = context.getBean("car", Car.class);  
        Tire tire = context.getBean("tire", Tire.class);  
     
        car.setTire(tire);  
     
        System.out.println(car.getTireBrand());  
    }  
}
  • 마찬가지로 현실세계와 비교해서 보면, 공장에서 직접 자동차와 타이어를 생산하여 장착하는 것이 아니고, 다른 종합 쇼핑몰과 같은 곳에서 각각 구매해와서 장착시키고 있는 겁니다.

  • 종합 쇼핑몰과 같은 역할을 하는 xml 파일을 이용하는 이점은 자동차에 어떤 타이어를 장착할 지 변경하는 과정에서 재컴파일없이 xml 내용만 바꿔주면 되기 때문이다.

<bean id="tire" class="test.KoreaTire"/>
<bean id="tire" class="test.AmericaTire"/>
  • 한 단계 더 나아가, 의존 관계를 맺는 것까지 xml 파일에서 설정하도록 해보겠습니다.

  • 현실세계와 비교해 말하면, 종합 쇼핑몰에서 아예 타이어가 장착된 자동차를 구매해오는 것으로, 이제는 자동차와 타이어 생산과 장착이 종합쇼핑몰에서 모두 하게됩니다.

<bean id="koreaTire" class="test.KoreaTire"/>  
<bean id="americaTire" class="test.KoreaTire"/>  
<bean id="car" class="Car">  
    <property name="tire" ref="koreaTire" />  
 // <property name="tire" reg="americaTire"/>  
</bean>
public class CarFactory {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("example.xml");  
     
        Car car = context.getBean("car", Car.class);  
     
        System.out.println(car.getTireBrand());  
    }  
}
  • 여기까지가 거의 자동차와 타이어의 의존성을 외부에서 결정하고있다고 볼 수 있습니다.

  • 그런데, 자동차와 타이어의 생산이나 의존관계 xml 명시한 것처럼, 프로젝트 내의 모든 클래스들의 정보를 작성하는 것은 굉장히 귀찮고 관리하기 어려워보입니다.

  • 따라서, 이를 애노테이션을 통해 대체할 수 있으며, 대표적으로 @Component나 @Autowire 등이 있습니다.


2. AOP

  • AOP란 Aspect-Oriented Programming의 약자로, 관점 지향 프로그래밍이라 합니다.

  • DI가 의존성을 주입하는 것이였다면, AOP는 어떤 로직을 주입하는 것이라고 볼 수 있습니다.

  • 그 어떤 로직이란 것이 여러 부분에서 반복/중복되는 로직을 말하는 것으로, 다음과 같은 예시를 들 수 있습니다.

    • 다음 예시에서는 밑줄 친 부분을 제외하고는, 다른 데이터베이스 연동하는 로직에서 공통인 부분입니다.

    • 이런 공통적인 부분을 횡단 관심사라고 하고, 밑줄 친 부분을 핵심 관심사이라 합니다.

    • 이를 조금 너프하게 정리해보면, 로직 = 횡단 관심사 + 핵심 관심사 라고 볼 수 있습니다.

DB 커넥션 준비Statement 객체 세팅
try {
    DB 커넥션 연결
    Statement 객체 세팅
    insert / update / delete / select 실행
} catch ... {
    예외처리
} finaly {
    DB 자원반납
}
  • 즉! 공통적인 부분(횡단 관심사)을 분리하여 필요한 곳에다가 주입하는 것이 관점 지향 프로그래밍입니다.

AOP 동작 예시

  • 그렇다면 이번에는 횡단 관심사 로직을 언제 어떻게 주입하는지 간단한 예시로 보겠습니다.

  • 특정 메서드로 주입할 로직은 메소드 전체, 메소드 시작 직전, 메소드 종료 직전, 메소드 정상 종류 후, 메소드 예외발생 후 시점에 적용 할 수 있습니다.

public interface Person {  
    void runSomething();  
}

  
public class Boy implements Person {  
    public void runSomething() {  
        System.out.println("컴퓨터 게임을 한다.");  
    }  
}  
​  
public class Girl implements Person {  
    public void runSomething() {  
        System.out.println("음악을 듣는다.");  
    }  
}
public class Start {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("text/aop.xml");  
     
        Person hwan = context.getBean("boy", Person.class);  
        Person minji = context.getBean("girl", Person.class);  
     
        hwan.runSomething();  
        minji.runSomething();  
    }
}

실행결과)  
집에 들어왔다.  
컴퓨터 게임을 한다.  
집에들어왔다.  
음악을 듣는다
<aop:aspectj-autoproxy/>  
​  
<bean id="myAspect" class="test.MyAspect"/>  
<bean id="boy" class="test.Boy"/>
@Aspect // AOP에 사용할 클래스임을 의미  
public class MyAspect {  
    @Before("execution(public void test.Boy.runSomething())") // 메서드 시작직전에 실행 한단 의미  
    public void before(JointPoint jointPoint) {  
        System.out.println("집에 들어왔다.");  
    }   
}
  • 이렇게 AOP를 이용하면 로직에서 횡단 관심사(중복 코드가 될 수 있는 공통 로직)을 분리함으로써 다음과 같은 이점을 얻게 됩니다.

    • 중복 코드 제거

    • 유지 보수가 편리

    • 핵심 관심사에 집중

  • 이번엔 횡단 관심 로직은 어떻게 실행(주입) 되는지 보겠습니다.

  • 결론적으로 스프링 AOP는 프록시 패턴을 이용해 핵심 관심사에 주입하도록 동작합니다. 따라서, 횡단 관심사를 적용할 클래스는 인터페이스 기반으로 구현되어야 합니다. (이 부분은 조금 다를 수 있습니다. CGLib)

  • 위의 예시에서 보면, Start 클래스에서 hwan.runSomething()을 호출하면 프록시는 그 요청을 받고 진짜 hwan.runSomething()을 호출해줄 것입니다. 여기서, 프록시는 그냥 호출만 해주는 것이 아니라 어떠한 조작을 가할 수 있습니다.

  • 즉!, AOP는 인터페이스 기반으로 프록시 패턴을 이용하여 런타임시에 횡단 관심사(공통적인 부분)을 주입해주는 프로그래밍이라고 정리할 수 있습니다.


3. PSA : 일관성 있는 서비스 추상화

  • Portable Service Abstraction의 약자로, 바로 예제를 통해 이해해보겠습니다.

  • 서비스 추상화의 예로는 JDBC가 있는데, JDBC는 표준 스펙이 있기 때문에 어떤 DBMS를 사용하든 Connection, Statement, ResultSet을 이용해 공통된 방식으로 코드를 작성할 수 있습니다.

  • 어떤 종류에 상관없이 같은 방식으로 제어할 수 있는 이유는 어댑터 패턴을 활용했기 때문입니다.

  • 즉!, 표준스펙이라는 공통 인터페이스를 이용해 같은 기능을 하는 여러 구현체들을 제어하는 것입니다.

  • 스프링 OXM 기술만 하더라도 Castor, JAXB, 등 다양한 기술들을 위한 어댑터를 제공하여, 어떤 기술이든지 공통되는, 일관성 있는 방식으로 코드를 작성할 수 있습니다.

  • 이처럼 서비스 추상화를 해주면서 일관성 있는 방식으로 구현하게끔 해준다는 의미로 일관성 있는 서비스 추상화라 말합니다.

댓글