스프링 주요 기술
스프링 삼각형과 설정정보
- 스프링을 이해하기 위해서는 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 함수를 통한 의존성 주입이 있습니다.
-
생성자를 통한 의존성 주입
- 생성자를 통한 의존성 주입을 하기 위해서는 생성자 인자로 객체를 주입받으면 됩니다.
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()); } }
- 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, 등 다양한 기술들을 위한 어댑터를 제공하여, 어떤 기술이든지 공통되는, 일관성 있는 방식으로 코드를 작성할 수 있습니다.
-
이처럼 서비스 추상화를 해주면서 일관성 있는 방식으로 구현하게끔 해준다는 의미로 일관성 있는 서비스 추상화라 말합니다.
댓글
댓글 쓰기