💡 해당 글은 『스프링 입문을 위한 자바 객체지향의 원리와 이해』 도서를 학습하며 도서의 내용을 참고하여 본인의 언어로 표현한 글입니다.
해당 장에서는 스프링 삼각형이라 불리는 IoC/DI, AOP, PSA에 대해 간단하게 다룬다. 스프링을 전문적으로 다루는 도서가 아니기에, 상세한 정보는 포함되어있지 않다. 『추후 토비의 스프링』 도서를 통해 내용을 보완할 예정이다.
☁️ IoC/DI
IoC(Inversion of Control), DI(Dependency Injection)
IoC(제어의 역전)과 DI(의존성 주입)은 한 데 묶여 설명된다.
프로그래밍에서 의존성이란?
한 객체가 다른 객체를 컴포지션으로 가지거나, 메서드를 통해 호출하거나, 파라매터로 넘겨받는 등 객체 간의 통신이 생기는 모든 것을 의존성이라고 생각한다. 객체 끼리 어떻게 의존할 건지, 어떤 객체를 의존할 건지는 오롯이 해당 객체의 역할이었다. 즉, 객체가 자신의 의존성을 관리했었다.
스프링에서는 이 의존관계를 스프링이 관리한다. 객체 스스로가 제어하던 의존관계를 자신이 아닌 외부(여기서는 프레임워크)가 제어하게 되는 이 상황을 IoC 제어의 역전이라고 한다.
스프링은 의존관계에 대한 제어권을 가져가고 의존성 주입이라는 편리함을 제공한다. 객체 내부에서 특정 객체를 new 하는 행위는 객체끼리의 강한 의존성을 가지게 한다. 이는 즉, SOLID의 OCP와 DIP에 위반된다.
따라서 스프링은 추상화를 의존하는 객체에게 의존 객체를 주입해주는 방법을 제공한다. bean으로 관리되는 객체를 생성자의 파라매터로 넘겨(생성자 주입)주거나 setter를 사용(setter 주입)하거나 인스턴스 변수에 바로 할당(필드 주입)해준다.
이로써 스프링을 사용하는 객체는 추상화를 의존하고, 의존관계를 스프링이 주입해주는 OCP, DIP를 성립하는 구조가 완성된다. 의존성 주입은 생성자 주입을 추천한다. “프로그램에서는 한번 주입된 의존성을 계속 사용하는 경우가 더 일반적이다” 라고 도서에 기재되어 있는데, 더 많은 이유가 있다. 다음에 정리해보도록 하겠다. setter 주입과 필드 주입은 필드의 객체 참조 변수가 final일 수 없다.
Spring에서는 아래와 같이 DI를 지원한다. 전략패턴를 조금 변경한 Template Callback 패턴을 사용한다.
먼저 스프링 컨텍스트에 의존성을 주입하거나, 주입받을 객체를 bean으로 등록한다.
// di.xml
<context:annotation-config/>
<bean id="tire" class="KoreaTire.class"></bean>
<bean id="otherTire" class="AmericaTire.class"></bean>
<bean id="car" class="Car.class"></bean>
다음 자바 코드를 아래와 같이 작성한다.
// Tire.java
public interface Tire {
String getBrand();
}
// KoreaTire.java
public class KoreaTire implements Tire {
@Override
public String getBrand() {
retrun "한국 타이어";
}
}
public class AmericaTire implements Tire {
@Override
public String getBrand() {
retrun "미국 타이어";
}
}
// Car.java
public class Car {
private final Tire tire;
@Autowired
public car(Tire tire) { // 생성자 주입
this.tire = tire;
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
뭔가 데코레이터 패턴이랑 유사한 구조가 되었다.
코드를 실행하는 메인 메서드를 작성해보자.
public class Driver {
public static void main(String[] args) {
ApplicationContext context
= new ClassPathXmlApplicationContext("di.xml", Driver.class);
Car car = context.getBean("car", Car.class) // Car
System.out.println(car.getTireBrand()); // 장착된 타이어: 한국 타이어
}
}
우리는 의존성을 주입해주는 어떤 코드도 작성하지 않았다, 심지어 tire 객체를 생성하지도 않았다.
스프링 컨텍스트에 등록해놓은 “car”아이디를 가진 Car
인스턴스, “tire”아이디를 가진 KoreaTire
인스턴스는 스프링 컨텍스트에서 싱글톤으로 관리되며 @Autowired
어노테이션을 통해 의존성이 주입되었다.
xml을 통해 인스턴스를 관리하고 의존성을 주입해주는 방식은 배포 서버에 JRE만 설치되어 있는 상황에 소스 코드의 수정 없이 주입해주는 인스턴스를 변경할 수 있다는 장점이 있다.
의존성을 주입해주는 어노테이션은 두 가지가 있다.
@Autowired
: 스프링에서 지원, 타입 우선 매칭, 이후 id 매칭, @Qulifier를 통한 id 지정@Resource
: javax에서 지원, id 우성 매칭, 이후 타입 매칭, name 어트리뷰트로 id 지정
☁️ AOP
AOP는 Aspect-Oriented Programming의 약어로 관점을 의미한다.
비즈니스 로직에서 공통적으로 수행해야하는 로깅, 보안 등의 요소를 횡당 관심사(Cross cutting Concern)이라고 하며, 이를 한 데 묶어 관리하고 실행시킬 수 있다. 메서드를 실행시키는 데 있어 결과는 그대로 반환하지만 대리자를 통해 추가적인 로직을 수행한다. 프록시 패턴이다, AOP는 프록시 패턴으로 구현된다.
스프링의 코드로 AOP를 적용하면 아래와 같다.
@Aspect
public class MyAspect {
// Before, After, AferReturning, AfterThrowing, Around
// [접근 제한자] 리턴타입 [패키지&클래스] 메서드이름(파라매터) [throws 예외]
@Before("excution(public void runSomething())")
pubic void before(JoinPoint joinPoint) {
// 공통 로직
}
}
AOP에서 사용하는 용어를 정리해보자
- JoinPoint: 스프링이 관리하듣 빈의 모든 메서드
- Pointcut: 어디에 - 어느 JoinPoint에 Aspect를 적용할까요?
- Advice: 언제 무엇을? - 실행위치 + 구현
- Aspect: 관점 - 1개 이상의 Advice + 1개 이상의 Pointcut
- Advisor: 하나의 Advice + 하나의 Pointcut (deprecated)
☁️ PSA
PSA(Portable Service Abstraction). 일관성 있는 추상화이다.
스프링에서는 서비스 추상화를 위한 다양한 어댑터를 제공한다. 예를들어 JDBC는 서비스를 추상화 한 어댑터이다. 우리가 MySQL 혹은 Oracle 등의 서로 다른 데이터베이스를 사용하더라도 이 JDBC라는 어댑터를 가지고 일관적인 방식으로 코드를 작성할 수 있는 것이다.
스프링은 OXM, ORM, 캐시, 트랜잭션 등 다양한 기술에 대한 PSA를 제공한다.
'JAVA > 자바 객체지향의 원리와 이해' 카테고리의 다른 글
06. 스프링이 사랑한 디자인 패턴 (0) | 2024.01.20 |
---|---|
05. 객체 지향 설계 5원칙 - SOLID (0) | 2024.01.18 |
04. 자바가 확장한 객체 지향 (0) | 2024.01.18 |
03. 자바와 객체지향 (0) | 2024.01.17 |
02. 자바의 절차적/구조적 프로그래밍 - JVM의 메모리 (0) | 2024.01.17 |