딸기말차
[Spring] 1. DI, IOC, AOP 본문

엔코아 플레이데이터(Encore Playdata) Backend 2기 백엔드 개발 부트캠프 (playdata.io)
백엔드 개발 부트캠프
백엔드 기초부터 배포까지! 매력있는 백엔드 개발자 포트폴리오를 완성하여 취업하세요.
playdata.io
1. Framework vs Library
라이브러리와 프레임워크의 차이는 프로그램을 개발할 때 주도권이 누구에게 있는가에 있다.
프레임워크는 주도권을 스스로가 쥐고 있으며 개발자는 프레임워크 내부에서 필요한 코드를 구현한다.
반면에 라이브러리는 개발자가 라이브러리를 가져다 쓰면서 전체적인 흐름을 만들기 때문에 주도권이 개발자에게 있다고 볼 수 있다.
즉, 프레임워크는 개발자가 마음대로 기존 구조를 변경하거나 코드를 변경해서 사용할 수 없는 단점이 존재하지만, 개발의 틀을 제공하기 때문에 개발을 편리하게 할 수 있게 해주는 장점 또한 존재한다.
2. Spring Bean
스프링 빈이란, 스프링 컨테이너가 관리하는 자바 객체로 개발자가 아니라 컨테이너에 의해 생명주기가 관리되는 객체를 의미한다.
즉, 개발자가 new 키워드를 통해 생성하는 것이 아니라 스프링 컨테이너에 빈 등록을 마치면, 언제든지 필요할 때 등록 된 빈을 가져다 쓸 수 있게 되는 것이다.
결국 Bean은 ApplicationContext가 알고 있는 객체이며, ApplicationContext가 생성하고 관리하는 객체를 의미한다.
빈을 생성하기 위해서는 우선,
1. 개발자가 사용할 공통 객체 생성
2. @Bean 혹은 xml을 통해 사용할 객체를 Bean으로 등록
3. XML의 경우 등록한 객체를 자바쪽으로 가져오기위해 xmlBeanFactory 사용
Bean은 생성 될 때 SingleTon 형태로 스프링 컨테이너 내부에 등록이 되게 된다.
1. 싱글톤 ?
우리가 자바 내에서 객체를 생성해 사용하려면 일반적으로 new 키워드를 통해 메모리에 객체를 생성하고, 사용한다.
하지만 이 경우 단점이 존재하는데, 대용량 트래픽이 있는 서비스를 배포하는 상황에서 클라이언트의 요청에 의해 객체를 사용할 때마다 메모리에 객체를 생성한다면, 메모리가 트래픽을 버티지 못하고 오버플로우가 나버리게 된다.
때문에 SingleTon 패턴이라는 방식을 통해, 객체를 컨테이너에 올릴 때 딱 한번만 생성하고, 이를 Bean으로 등록해 사용할 때마다 메모리에 생성하는 것이 아닌 컨테이너에 등록되어 있는 객체를 재사용하는 해결책이 존재한다.
1. DB에 데이터를 넣기 위한 양식(DTO)을 작성할 때마다
2. 객체를 NEW로 만드는 것은 너무나 비효율적이다.
3. 이 경우 하나의 객체를 재사용하는 것이 메모리 낭비를 압도적으로 줄일 수 있다.
4. 이 방식을 채택하면, 대용량 서비스를 배포할 때 메모리의 효율성이 많이많이 올라간다.
5. 때문에 스프링은 모든 Bean을 싱글톤 형태로 만들어 컨테이너에 저장한 후 컨테이너에서 관리를 한다.
3. DI & IOC
1. DI (Dependency Injection) ?
public class Test1 {
private Test2 test2 = new Test2();
}
해당 코드를 보면, Test1 클래스 내부에 Test2 클래스를 선언하여 사용하고 있다.
이 경우를 Test1 클래스가 Test2 클래스를 의존하고 있다고 이야기한다. 또한 현재 상황에서는 개발자가 직접 new 키워드를 통해 Test1 클래스 내부에 Test2 클래스를 생성했기 때문에, 개발자가 직접 의존 관계를 주입했다고 할 수 있다.
2. IOC (Inversion Of Control) ?
우리는 앞으로 스프링을 사용하며, 개발자가 만든 객체를 Bean 형태로 스프링 컨테이너에 등록해 사용하게 된다.
public class Test1 {
private Test2 test2 = ac.getBean("test2");
}
즉, 의존 관계 주입의 주체가 더이상 개발자가 아니라 컨테이너로 변경되게 된다. 이를 객체의 제어권이 역전 됐다고 표현하고, IOC 라고 한다.
3. DI 방법
1) 생성자 주입
생성자를 통해 의존성을 주입하는 방식으로, 해당 코드는 XML로 되어 있기 때문에 헷갈릴 수 있지만, 매개변수를 사용하는 생성자를 떠올리면 쉽게 이해할 수 있다.
<beans>
// PersonService personService2;
<bean id="personService2" class="com.lhs.spring.ex1.PersonServiceImpl">
// personService2 = new PersonServiceImpl("이순신");
<constructor-arg value="이순신"></constructor-arg>
</bean>
</beans>
2) Setter 주입
setter를 통해 데이터를 주입하는 방식으로, setter를 사용하는 자바코드를 연상하면 쉽게 이해할 수 있다.
<beans>
// PersonService personService1 = new PersonServiceImpl();
<bean id="personService1" class="com.lhs.spring.ex1.PersonServiceImpl">
// personService1.setName("홍길동");
<property name="name">
<value>홍길동</value>
</property>
</bean>
</beans>
3) 두 방식의 차이점
setter를 통한 데이터 주입은 객체 생성 이후 setter를 통해 데이터를 주입하기 때문에 외부에서 해당 객체에 접근 가능하다는 의미고, 추후 데이터의 변경 가능성이 존재한다는 뜻이다.
반면 생성자를 통한 데이터 주입은 객체 생성 시에만 데이터를 주입할 수 있기 때문에, 외부에서 해당 객체에 접근해도 값을 변경할 수 없다. 때문에 Spring은 생성자 주입 방식을 권장하고 있다.
4. 강한 결합 & 약한 결합
1. 강한 결합 (Class-Class)
강한 결합이란, 객체의 결합도가 너무 높아 변경이 어려운 코드를 의미한다. 즉, Class와 Class간 직접적으로 연결되어 있는 경우를 의미한다.
public class Controller {
// 구현체 직접 사용, 생성
ServiceImpl service = new ServiceImpl();
ArrayList<Integer> lst = new ArrayList<>();
// 불가능한 코드
ArrayList<Integer> lst2 = new LinkedList<>();
}
2. 약한 결합 (Class - Interface - Class)
강한 결합과 달리 선언부를 Interface로 만들어, 구현체를 언제든지 갈아 끼울 수 있는 형태를 의미한다.
public class Controller {
// 인터페이스 = new 구현체
Service service = new ServiceImpl();
// 가능한 코드
List<Integer> lst = new ArrayList<>();
List<Integer> lst2 = new LinkedList<>();
}
3. XML 표현
이 코드의 동작과정은 다음과 같다.
1. 개발자의 의도는 MemberServiceImpl 내에 MemberDAOImpl을 요소로 등록하고 싶다.
2. 그러기 위해선 필요한 재료인 MemberDAOImpl 또한 컨테이너 내에 Bean으로 등록되어 있어야 한다.
3. MemberService 인터페이스의 구현체인 MemberServiceImpl을 memberService라는 이름의 Bean으로 등록한다.
4. MemberDAO 인터페이스의 구현체인 MemberDAOImpl을 memberDAO라는 이름의 Bean으로 등록한다.
5. 등록 된 memberDAO Bean을 참조하여, memberService Bean 내에 요소를 등록한다.
<beans>
// MemberService memberService = new MemberServiceImpl();
<bean id="memberService" class="com.lhs.spring.ex2.MemberServiceImpl">
// memberService.memberDAO = memberDAO;
<property name="memberDAO" ref="memberDAO"></property>
</bean>
// MemberDAO memberDAO = new MemberDAOImpl();
<bean id="memberDAO" class="com.lhs.spring.ex2.MemberDAOImpl"></bean>
</beans>
즉, 약한 결합일수록 기능의 확장성이 올라가고, 강한 결합일 수록 기능의 확장성이 떨어지는 것을 의미한다.
때문에 우리는 설계를 할 때 최대한 약한 결합을 지향하며 설계를 해야한다.
5. AOP
우리가 객체를 만드는 이유는, 해당 객체를 사용해 기능을 구현하기 위해서이다.
기존 객체 지향 프로그래밍 (OOP)에서 의미하는 기능구현은 사실, 모든 객체에 공통으로 들어가는 공통기능과 메인기능을 굳이 구별하지 않고, 그저 동작할 수 있는 기능구현 자체를 의미했다.
하지만 이렇게 개발을 진행하다보니, 공통으로 들어가는 코드의 양이 너무 많아지게 되었고, 때문에 AOP라는 개념이 등장하게 되었다.
1. 관점 지향 프로그래밍 (AOP) ?
우리는 기능을 만들 때, 기본적으로 단일 책임 원칙(SRP)을 지켜야 한다.
기능을 구현할 때 해당 메서드나 클래스는 하나의 책임만 지켜야하는데, 개발을 하다보니 공통기능과 핵심기능이 함께 존재하게 되었고, 때문에 단일 책임 원칙을 지키기가 힘들어졌다.
그래서 공통 기능을 분리하여, 메인 기능이 실행되기 전 (before), 실행 된 후 (after), 전 후 둘다 (invoke) 를 선택해 공통 기능을 붙일 수 있는 AOP를 사용하게 되었다.
1. aspect
공통 기능 클래스의 내부 기능 메서드를 의미한다. ex) invoke()
2. advice
공통 기능 클래스 자체를 의미한다. ex) LoggingAdvice
3. joinpoint
기능 실행 전(before) or 후(after) or 전 + 후(invoke) 와 같이 언제 공통 기능을 붙일지를 의미한다.
method 결합점만 제공한다 = 클래스 레벨에는 공통 기능을 붙일 수 없다. 메서드 레벨에만 가능하다.
4. pointcut
공통 기능을 붙일 대상을 지정한다. ex) <property name="interceptorNames"> 로 가져온 메서드들
5. target
공통 기능을 붙일 클래스를 지정한다. ex) <property name="target" ref="calcTarget"></property>
6. weaving
proxy를 통해 공통 기능 클래스 + 메인 기능 클래스를 붙이는 것을 의미한다.
2. 공통기능을 실행 시키는 시점
기존 기능에서 공통기능을 분리한거 까진 좋은데, 분리한 공통기능을 언제 실행 시켜 메인 기능에 붙여 놓을 수 있을까?
대표적으로 다음과 같은 세가지 방법이 존재한다.
1. 내가 만든 기능 클래스가 생성되는 시점에 공통 기능 클래스도 함께 생성
2. 기능 클래스가 컴파일 되는 시점에 공통 기능 클래스도 함께 컴파일해서 실행
3. 기능 클래스가 런타임(실행) 되는 시점에 공통 기능 클래스도 함께 실행
Spring은 이 중 3번 방식을 채택하여 사용하고 있다.
3. 기능 클래스, 공통 기능 클래스를 붙이는 방법
우리는 Spring에서 컨테이너에 객체를 Bean으로 등록해 사용한다. 이때, 객체의 원본을 등록하는게 아니라 사본을 만들어 등록하게 된다.
즉, 사본을 만들어 등록하기 때문에 컨테이너 내에서 여러 Bean을 합치던, 삭제하던 원본 객체 내용은 변화가 없다는 뜻이다.
이렇게 코드의 안전성을 보장하며, 컨테이너 내에서 proxy를 사용해 공통 기능과 메인 기능을 합치는 작업을 진행한다.
이를 Proxy Pattern 이라고 한다.
4. Proxy 사용 실습
1) 메인 기능 클래스
public class Calculator {
public Calculator() {
}
public void add(int x, int y) {
System.out.println("결과 : " + (x + y));
}
public void subtract(int x, int y) {
System.out.println("결과 : " + (x - y));
}
public void multiply(int x, int y) {
System.out.println("결과 : " + (x * y));
}
public void divide(int x, int y) {
System.out.println("결과 : " + (x / y));
}
}
2) 공통 기능 클래스
public class LoggingAdvice implements MethodInterceptor {
public LoggingAdvice() {
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("[메서드 호출 전 : LoggingAdvice");
System.out.println(invocation.getMethod() +" 메서드 호출 전");
Object object = invocation.proceed(); // 실행 시점
System.out.println("[메서드 호출 후 : loggingAdvice");
System.out.println(invocation.getMethod() + " 메서드 호출 후");
return object;
}
}
3) XML 을 통한 Proxy 사용
이 코드의 동작과정은 다음과 같다.
1. 메인 기능 클래스인 Calculator를 calcTarget 이라는 이름의 Bean으로 등록한다.
2. 보조 기능 클래스인 LoggingAdvice를 logAdvice 라는 이름의 Bean으로 등록한다.
3. Proxy를 사용하기 위해 ProxyFactoryBean을 proxyCal이란 이름의 Bean으로 등록한다.
4. proxyCal 내에서 메인 기능인 calcTarget을 target이라는 요소 이름으로 선언한다.
5. target 내에 존재하는 여러 메서드들을 가져오기 위해 interceptorNames 를 사용한다.
6. 가져온 메인 기능메서드에 보조 기능 메서드를 붙이기 위해, value로 보조 기능 클래스의 Bean이름을 사용한다.
<beans>
// Calculator calcTarget = new Calculator();
<bean id="calcTarget" class="com.lhs.spring.ex1.Calculator"></bean>
// LoggingAdvice logAdvice = new LoggingAdvice();
<bean id="logAdvice" class="com.lhs.spring.ex1.LoggingAdvice"></bean>
// ProxyFactoryBean proxyCal = new ProxyFactoryBean();
<bean id="proxyCal" class="org.springframework.aop.framework.ProxyFactoryBean">
// proxyCal.target = calcTarget;
<property name="target" ref="calcTarget"></property>
// 유사 코드 : String[] save = request.getParameterNames();
// add, subtract, multiply, divide
<property name="interceptorNames">
// list = { add, subtract, muliply, divide }
<list>
// 가져온 메서드에 invoke 붙이기
<value>logAdvice</value>
</list>
</property>
</bean>
</beans>
6. 27일차 후기
스프링을 드디어 시작하게 되었고, 프레임워크가 지원해주는 주요 기능인 Bean, DI, IOC에 대해 알아보는 시간이었다.
기존에는 해당 부분을 자바로 구현하며 공부를 했었지만 강사님의 수업은 XML을 통해 해당 부분을 진행하였다.
때문에 오타가 나기 쉬워 틀리기 쉽다는 단점이 존재하긴 했지만, 여태 프레임워크를 사용할 때 자동으로 해주던 설정부분이 어떻게 XML로 구성되어 있는지 알게되었다.
'Bootcamp > Spring' 카테고리의 다른 글
| [Spring] 6. STS (0) | 2023.08.19 |
|---|---|
| [Spring] 5. Transaction, Annotation (0) | 2023.08.17 |
| [Spring] 4. Spring Mybatis (0) | 2023.08.16 |
| [Spring] 3. Spring JDBC, Mybatis (0) | 2023.08.14 |
| [Spring] 2. MVC (0) | 2023.08.12 |