Spring overview
특징
- IOC (Inversion Of Control)
- DI (Dependency Injection)
- AOP (Aspect-Oriented Programming)
- PSA (Portable Service Abstraction)
IOC (Inversion Of Control)
직역하면 제어권의 역전으로 해석할 수 있다. 보통 어떤 객체(A)가 다른 객체(B)를 사용하고 있다면 A는 B에 dependency가 있다 라고 얘기하며, A 객체 내부에서 B객체를 생성하여(new) 사용할 수 있다.
1 | @Controller |
하지만 Spring은 IOC 특성을 가진다. 즉 Control(dependency를 생성)의 주체가 A class가 아닌 A class 외부이다.
1 | @Controller |
OwnerRepository를 외부에서 어떤 타이밍에 생성되어 들어오는지는 모르지만 OwnerController는 그걸 신경쓰지 않고 받아서 쓸 뿐이다. 이런식으로 코드를 작성하면 OwnerController와 OwnerRepository간의 dependency가 줄어들기 때문에 테스트 코드를 짜기에도 용이하고, repository를 바꾸거나 controller를 바꾸기도 용이한, 유연한 코드를 짤 수 있겠다.
이 내용만 보면 Inversion of dependency control이라고 생각할 수 있지만 dependency 외에 다른것도 inversion 되어있을 수 있다고 한다.
가령 Servlet을 예로 들면, 어떤 Servlet은 Servlet Container에 속해있으며, Container가 생성 하고, 클라이언트에서 요청이 올 때 실행되므로, Servlet 자신이 제어권을 쥐고 있는 것이 아닌 Container가 쥐고 있으며 이 경우에도 IOC 개념이 사용된다.
IOC 컨테이너
Spring framework는 IOC용 Container를 제공 해 준다. Container의 핵심 인터페이스는 Application Context(Bean Factory)이다.
하지만 Application Context는 직접 사용할 일은 없다. Application Context는 단지 뒤에 숨어서 사용자가 만든 IOC class를 Spring에서 동작할 수 있도록 해 준다.
예를 들면 위에서 생성한 OwnerController라는 class는 @Controller
라는 Annotation을 달고 있고 Application Context는 Annotation을 보고 인스턴스를 생성 해 준다. 그와 동시에 OwnerController의 parameter인 OwnerRepository 또한 생성하여 생성자에 넣어준다.
정리를 해보자.
- Container 내부에 생성된 객체들을 Bean이라고 한다.
- Container는 이 Bean들의 dependency를 관리해준다.
- 오로지 Bean만 관리한다.
Bean
Bean은 Spring IOC 컨테이너가 관리하는 객체이다.
- 보통 class에 @Controller, @Service, @Repository과 같은 Annotation이 붙어있으면 Bean이다.
- Annotation은 사용자가 붙이는 것이며, 이 객체들간의 dependency를 Container에서 관리해줘라 하는 표시와 같다.
- Intellij 기준 class 옆에 어떤 표시가 있으면 Bean으로 등록이 된 것이다.
- 오로지 Bean으로 등록된 객체만 dependency를 관리 해 준다.
▶ 어떻게 등록하지? - Component Scanning
1 | package com.example.demo |
- 보통 Spring project를 생성하면 위와 같이 main 메소드가 있는 Application file이 존재한다.
1 | @Target(ElementType.TYPE) |
- 위의 코드와 같이 SpringBootApplication 이라는 Annotation의 구현체를 보면 scanBasePackages, scanBasePackageClasses라는 메소드가 존재함을 볼 수 있다.
- 즉, Spring에서는 존재하는 모든 package 내의 모든 class에 대해서 component Annotation이 붙어있는 class들을 모두 찾아서 자동으로 Spring framework에 Bean으로 등록한다.
@Controller라는 Annotation도 내부 구현을 뜯어보면 @Component라는 Annotation이 붙어있으며, Spring 에서는 이것을 보고 Bean으로 등록하게 된다.
1 | @Target({ElementType.TYPE}) |
즉 사용자는 Spring의 Component라는 것을 알려줄 수 있는 Annotation을 잘 붙여주면 된다.
- @Controller
- @Repository
- @Service
DI (Dependency Injection)
Spring에서는 @Autowired, @Inject를 붙여서 DI를 사용할 수 있다. 하지만, 코드상에는 DI와 관련된 Annotaion은 찾아볼 수 없다. 그렇다면 어떻게 DI를 사용할까?
▶ Dependency Injection 조건
Spring에서는 @Autowired, @Inject가 없더라도, 아래와 같은 조건을 만족한다면 해당 dependency를 해당 Bean에 Inject 해 준다.
- 어떤 Bean이 존재하며
- 그 Bean에 생성자가 오직 하나만 존재하며
- 그 생성자의 Parameter로 받는 타입의 Bean이 존재
▶ Bean이 아닌 객체들은 어떻게 Container로 부터 의존성을 주입받을까?
@Autowired, @Injection을 생성자, 필드, Setter에 붙여준다.
- 생성자
보통 생성자에서 Inject를 받으며, 테스트 코드를 짤때도 dependency를 mocking하기 용이하다. - 필드
필드에 붙여줄 수 있지만, test code 작성시 mocking하여 넣어주기가 매우 힘들기 때문에 지양되는 방법이다. - Setter
Setter 또한 필드와 같은 이유로 지양되는 방법이기는 하나, 필요시에는 사용되며 다음과 같은 기준들로 Annotation을 붙여준다.- Setter가 있다는 것은 어떤 dependency를 setter를 통해 변경시키고 싶다는 의미로 볼 수 있다. 따라서 constructor 보다 Setter에 Annotation을 붙여 DI를 해주는 것이 자연스럽지 않는가 생각한다.
- 만약 Setter가 없고, 생성자나 필드가 존재한다면 Setter를 만들기보다는 그쪽에 Annotation을 두는것이 자연스럽다. 또한 setter를 굳이 만들어주는 것은, 바뀌지 않아야할 dependency를 바뀔 수 도 있도록 하는 것 이므로 굳이 Setter를 만드는 것은 좋지 않다.
AOP (Aspect-Oriented Programming)
AOP란 쉽게 표현하면 흩어진 코드를 한곳으로 모으는 코딩 기법이다. 예제를 살펴보자.
1 | // 흩어진 AAAA 와 BBBB |
위의 주석에 적혀있는 흩어진 AAAA 와 BBBB
코드를 살펴보면 AAAA라는 기능과 BBBB라는 기능이 여러 메소드마다 반복적으로 실행되고 있다. 이러한 흩어진 코드를 모아 놓은 AAAA 와 BBBB
와 같이 한곳으로 모아서 처리하는 방식을 AOP라고 할 수 있겠다.
이렇게 구현하는 기법은 크게 두가지가 있다
- Bytecode를 조작한다.
- class A, B가 컴파일된 bytecode에 AAAA, BBBB를 삽입한다.
- Proxy 패턴
- A라는 class를 상속을 받아 AProxy라는 class를 만든다.
- AProxy class는 a method를 override 해서 A의 a method를 실행하기 전에 AAAA를 실행시키고 A의 a method를 실행시키고 BBBB를 실행시키도록 한다.
Spring에서는 Proxy 패턴으로 AOP를 구현하며 framework 내부에서 자동으로 코드를 생성 해 준다.
AOP 적용 예제
위에 설명한 것과 비슷한 동작을 필요로 하는것이 보통 logging 이다. Logging 관련 Annotation이 붙어있는 method만 실행시 log를 남기는 예제 코드를 작성 해 보자.
- Annotation을 만든다.
1 | // 이 Annotation은 function에 붙여야 동작한다. |
- 사용할 메소드에 Annotation을 붙여준다.
1 | @Controller |
- Aspect 만들기
- Annotation은 주석과도 같은 것이다. 실제 기능은 없고, 저 Annotation과 mapping되는 Aspect를 만들어 줘야 한다.
- spring에서 Annotation과 aspect를 연결하려면 aspect가 bean이어야 한다.
1 | @Component |
위의 코드와 같이 jointPoint라는 작업이 수행 되기 전 후에 before, after 라는 작업을 수행하도록 하며, Annotation만 붙이면 모든 메소드에서 저러한 작업을 가능하게 해 주는 것이 AOP 개념과 관련된 일을 한다고 보면 되겠다. Before -> 원래 task -> after 순서로 작업이 수행된다.
@Around(“@Annotation(LogExecutionTime)”)
부분에서 왜 @Around의 인자값으로 String이 들어가는지 의문이 들 수 있는데, String으로 어떤 메소드가 실행 될 것인지 패턴을 정의해 줄 수 있다. 예를 들면 execution(* set*(..))
이렇게 set으로 시작하는 method에서만 실행 할 수도 있고, execution(* com.xyz.service..*.*(..))
이렇게 service 패키지와 하위 모든 메소드들에서 실행 시킬 수 있다.
이런식으로 Annotation과 Aspect를 만들고 나면 나머지 작업들은 spring에서 magically 처리 해준다.
PSA (Portable Service Abstraction)
쉬운 말로 잘 만든 인터페이스라고 얘기 할 수 있다. 예시를 들어 설명 해 보자.
나의 코드 — ( 확장성이 좋지 못한 코드 or 특정 기술에 특화되어 있는 코드)
오른쪽에 코드와 나의코드가 직접적인 연결이 있다면, 코드가 바뀌거나, 기술이 바뀔때 마다 나의 코드의 영향을 주기 때문에 계속해서 수정을 해야한다.나의 코드 — PSA (Interface) — ( 확장성이 좋지 못한 코드 or 특정 기술에 특화되어 있는 코드 )
인터페이스가 중간에 있다면, 확장성이 좋지 못한 코드 or 특정 기술에 특화되어 있는 코드와 내 코드와 직접적인 연관이 없고 인터페이스만 사용하면 되기 때문에 어떤 변경이 있더라도 내 코드에는 영향이 없어진다.
1 | @Controller |
위와 같은 코드를 보자. @Controller나 @GetMapping은 Servlet과 전혀 상관이 없이 만들어졌다. 저 Annotation을 통해서 해당 기능을 실행하는 주체는 Servlet이 될 수도 있고, netty가 될 수 있다. 우리는 어떤 기술이 뒤에서 사용되고 있는지는 전혀 알 필요 없이 단지 Spring이 제공하는 interface들만 잘 알고 적절하게 사용하면 된다.
▶ PSA - @Transactional
@Transactional는 위에서 만든 LogExecutionTime Annotation과 같이 aop 이다. 그러므로 당연히 Transactional 기능을 처리하는 TransactionalAspect가 존재한다.
이 Aspect에서는 기술에 독립적인 PlatformTransactionManager라는 인터 페이스를 사용하여 구현 해 두었다.
Platform Transaction Manager는 JpaTransacionManager, DatasourceTransactionManager, HibernateTransactionManager 와 같은 구현체 들이 존재하지만 Transactional Aspect의 코드는 바뀌지가 않는다. 단지 PSA를 쓰고 있기 때문에!
Spring에서는 자동으로 JpaTransacionManager를 bean으로 등록 하며, Transactional Aspect는 이를 injection 받아서 해당 기능을 수행하게 될 것이다.
▶ PSA - @Cacheable | @CacheEvict | …
이 Annotation이 붙으면 캐쉬 관련 설정이 가능 해 진다. @Transactional과 동일하게, Cache 관련 Annotation도 Aspect를 가지며, 그 Aspect에서는 CacheManager라는 PSA를 사용하게 된다.
CacheManager는 JCacheManager, ConcurrentMapCacheManager 등등의 구현체가 있지만 @Transactional과 동일하게 Spring에서 자동으로 injection을 해 줄 뿐이고, 단지 Aspect에서는 CacheManager라는 PSA(interface)를 가져다 쓸 뿐이다!
▶ PSA - 웹 MVC
웹 MVC는 @Controller와 @GetMapping Annotation을 이용하여 구현된다.
1 | @Controller |
이 코드 (@Controller, @GetMapping)은 내부적으로 Servlet을 쓰는지, Reactive를 사용하는지 전혀 알수가 없다. 단지 저 Annotation들이 구현하고 있는 Aspect는 웹 관련 PSA를 사용하여 해당 기능을 구현할 뿐이고, 우리는 그것을 신경쓸 필요가 없어졌다.
Spring의 전반적으로 중요한 요소들을 정리했다. 이걸 보고 바로 실무에 적용할 수는 없겠지만, 적어도 이런 개념이 있다면 Spring의 수 많은 기능중 내가 필요한 것들을 잘 찾고 이해하며 쓸 수 있을 듯 하다.