[Spring] 토비의 스프링 Vol.2 7장 스프링의 기타 기술과 효과적인 학습 방법 (Vol.2 完)

Shiba·2023년 12월 7일
0

🍀 스프링 정리

목록 보기
21/21
post-thumbnail

📗 스프링의 기타 기술과 효과적인 학습 방법

스프링은 자바 엔터프라이즈 개발에 사용되는 다양한 기술 영역을 지원하는 방대한 기능을 제공함. 7장에서는 이러한 기능을 어떻게 효과적으로 이해하고, 학습하고, 적용할 수 있을지에 대해 설명하도록 하겠다.

📖 스프링 기술과 API를 효과적으로 학습하는 방법

스프링은 일관된 방식으로 개발된 프레임워크. - DI에 기반을 두고 만들어져 있음

📝 빈으로 등록되는 스프링 클래스와 DI

어떤 오브젝트가 빈으로 사용된다는 의미

  • 다른 빈에 의해 DI되어서 사용되는 서비스
    - 스프링에는 독립적으로 존재할 수 있는 빈은 없다. - 적어도 하나의 의존관계
  • 다른 빈이나 정보에 의존하고 있다
    - 대부분의 빈은 자신이 클라이언트가 되어 다른 빈 오브젝트를 사용하도록 만들어져있다.

빈을 잘 활용하려면 위의 두 가지 특징을 잘 파악해야 한다. 빈의 이 두 가지 특징을 알고 싶다면 빈으로 등록된 클래스의 구현 인터페이스와 프로퍼티 목록을 살펴보자.

=> 가장 좋은 스프링 학습 방법은 스프링이 제공하는 빈 클래스를 위의 두 가지 관점으로 살펴보는 것!

위의 학습 방법으로 앞서 배웠었던 DataSourceTransactionManager 클래스를 살펴보자.

구현 인터페이스 분석

구현 인터페이스가 무엇인지 파악하는 방법에는 두 가지가 존재

  • 스프링 API 문서에서 DataSourceTransactionManager 항목을 찾아보는 것
  • IDE에서 클래스의 타입 계층구조를 보는 기능을 사용하는 것

스프링 API 문서에서 DataSourceTransactionManager가 무슨 일을 하는지 알 수 있다.
DataSourceTransactionManager가 구현하고 있는 PlatformTransactionManager는 다음과 같이 소개되어 있다.

'PlatformTransactionManager는 스프링의 트랜잭션 인프라스트럭처의 핵심 인터페이스다, 애플리케이션은 이 인터페이스를 직접 사용할 수 있다. 하지만 API로 사용되는 것이 주요 용도는 아니다. 보통, 애플리케이션에서 TransactionTemplate나 AOP를 통한 트랜잭션 경계설정 방식을 통해 사용된다.'

PlatformTransactionManager의 소개를 통해 DataSourceTransactionManager가 어디서 어떤 용도로 쓰이는지 파악이 가능하다.

여기서 끝내지말고, PlatformTransactionManager를 구현하고 있는 다른 클래스들은 어떤 게 있는지 살펴보는 것도 좋다. 사용하는 IDE에서 PlatformTransactionManager를 찾아 타입 계층구조 창을 열어보면 된다.

PlatformTransactionManager의 서브인터페이스도 두 개가 보인다.
서브 인터페이스는 구현 클래스들을 다시 특정 그룹으로 구분할 수 있는 공통 특징을 정의하는 경우에 사용된다. DataSourceTransactionManager는 ResourceTransactionManager도 구현하고 있으니, 이 인터페이스의 특징도 살펴보면 좋을 것이다.

◼ 프로퍼티 분석

인터페이스를 통해 DataSourceTransactionManager 빈이 어떤 기능을 제공하는지, 어디에서 사용되는지를 파악했다면 다음은 빈의 클래스의 설정 방법과 확장 방법을 살펴볼 차례다.

DataSourceTransactionManager는 다음과 같이 다양한 프로퍼티 수정자 메소드를 제공한다.

setTransactionSynchronizationName()
setTransactionSynchronization()
setDefaultTimeout()
setNestedTransactionAllowed()
setVaildateExistingTransaction()
setGlobalRollbackOnParticipationFailure()
setFailEarlyOnGlobalRollbackOnly()
setRollbackOnCommitFailure()
-> DataSourceTransactionManager를 빈으로 등록할 때, <property> 태그를 통해 수정이 가능한 프로퍼티들

그렇다면 이러한 프로퍼티를 모두 알고 활용해야 할까? 꼭 그런건 아니다. dataSource처럼 잘 알려진 프로퍼티 외에는 대부분 디폴트 설정으로 충분하다. 이러한 프로퍼티를 살펴봐야 하는 이유는 이 빈 클래스의 동작방식을 이해하는데 도움이 되기 때문이다. 물론 필요하면 언제든지 빈 설정을 통해 디폴트 값을 수정할 수도 있다.

◼ DI/확장 포인트 분석

빈 클래스의 프로퍼티 중에서 인터페이스 타입의 프로퍼티를 보면 해당 구현 클래스의 확장 포인트라고 생각하면 된다. DataSourceTransactionManager가 DataSource라는 인터페이스를 통해 다른 빈을 참조한다는 건 DI의 모든 기능을 활용할 수 있는 확장 포인트를 갖고 있다고 이해할 수 있다.

이미 스프링은 DataSource 인터페이스와 DI 구조를 활용할 수 있는 많은 기능을 제공하고 있다.
- DelegatingDataSource

스프링은 DelegatingDataSource를 기반으로 하는 다양한 데코레이터 또는 프록시를 제공한다. 대표적인 몇 가지만 살펴보자

▪ LazyConnectionDataSourceProxy

트랜잭션 매니저와 실제 DataSource 사이에서 DB 커넥션 생성을 최대한 지연시켜주는 기능을 제공
- 공유해서 사용되는 중요한 리소스를 최대한 늦게 이용하도록 만들어주는 전형적인 프록시 패턴

<bean id="dataSource" class="...LazyConnectionDataSourceProxy">
	<property name="targetDataSource" ref="realDataSource" />
</bean>
<bean id="realDataSource" class="...">...</bean>

▪ AbstractRoutingDataSource

다중 DataSource에 대한 라우팅을 제공하는 프록시
- 여러 개의 DataSource가 존재하지만 DAO나 트랜잭션 매니저에는 하나의 DataSource만 존재하는 것처럼 사용하도록 만들어야 할 때 사용
- 추상 클래스이므로 상속을 통해 기능을 추가하고 사용해야 함
- 스프링은 AbstractRoutingDataSource의 서브클래스로 IsolationLevelDataSourceRouter를 제공

public class ReadOnlyRoutingDataSource extends AbstractRoutingDataSource {
	protected Object determineCurrentLookupKey() {
    	boolean readOnly =
        	TransactionSynchronizationManager.isCurrentTransactionReadOnly();
        return readOnly ? "READONLY" : "READWRITE";
    }
}

위 클래스를 빈으로 등록하고 룩업키에 대해 DataSource 빈을 지정해주기만 하면 된다.

<bean id="dataSourceRouter" class="...ReadOnlyRoutingDataSource">
	<property name="targetDataSources">
      <map>
        	<entry key="READWRITE" value-ref="masterDataSource" />
        	<entry key="READONLY" value-ref="readOnlyDataSource" />
      </map>
  	</property>
  	<property name="defaultTargetDataSource" ref="masterDataSource" />
</bean>
<bean id="masterDataSource" class="...">...</bean>
<bean id="readOnlyDataSource" class="...">...</bean>

📖 IoC 컨테이너 DI

📝 BeanPostProcessor와 BeanFactoryPostProcessor

가장 많이 사용되는 IoC 컨테이너의 확장 포인트는 바로 빈 후처리기와 빈 팩토리 후처리기 두 가지이다.

◼ BeanPostProcessor

빈 후처리기의 인터페이스는 다음과 같이 정의된다.

public interface BeanPostProcessor {
	Object postProcessBeforeInitialization(Object bean, String beanName)
    		throws BeansException;
   	Object postProcessAfterInitialzation(Object bean, String beanName)
    		throws BeansException
}

postProcessBeforeInitialization() 메소드는 빈 오브젝트가 처음 만들어지고 아직 초기화 메소드가 호출되기 이전에 실행됨. 만들어진 빈 오브젝트와 이름을 제공해주는데, 윈한다면 주어진 빈 오브젝트를 바꿔치기할 수 있다.

@Autowired@Inject 같은 애노테이션을 이용해서 정의된 빈 의존관계를 적용해주는 것도 BeanPostProcessor를 구현한 빈이 해주는 작업이다.

AOP의 동작원리인 자동 프록시 생성기도 역시 BeanPostProcessor를 구현한 빈이다.

◼ BeanFactoryPostProcessor

BeanFactoryPostProcessor는 빈 오브젝트가 아니라 빈 팩토리에 대한 후처리를 가능하게 하는 확장 포인트 인터페이스다.

public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
    		throws BeansException;
}

컨테이너는 빈 팩토리 후처리기가 있으면 postProcessBeanFactory()에 빈의 모든 메타정보를 담고 있는 자기 자신(ConfigurableListableBeanFactory 타입)을 보내준다.
이를 이용해서 등록된 빈의 메타정보 자체를 조작할 수 있다. 프로퍼티 값을 추가로 설정하는 수준이 아니라, 아예 새로운 빈을 등록할 수도 있다.

@Configuration에 의해 빈을 등록할 수 있는 이유가 바로 BeanFactoryPostProcessor가 있기 때문이다.
- 스캐닝과 XML을 통해 빈이 등록되면 스프링 컨테이너는 BeanFactoryPostProcessor를 호출
- 이 중에서 ConfigurationClassPostProcessor가 바로 @Configuration과 @Bean이 붙은 클래스 정보를 이용해서 새로운 빈을 추가해주는 기능을 담당

📖 SpEL

자바에는 다양한 종류의 표현식 언어가 존재.
스프링 3.0이전에는 다양한 표준 EL이나 오픈소스 EL을 사용해왔는데, 스프링 3.0부터는 좀 더 강력하고 유연하면서도 스프링에 잘 접목돼서 사용할 수 있는 스프링 전용 EL을 직접 제공하기 시작 - SpEL

가장 기본적으로 SpEL이 적용된 경우빈 설정파일이나 애노테이션을 이용해 프로퍼티 값을 지정할 때다. 빈 프로퍼티 값에 사용되는 SpEL에서는 다른 빈 오브젝트를 직접 참조할 수 있다. 이를 활용해서 다른 빈의 프로퍼티 값을 사용하는 표현식을 만들 수 있다.
또한, <spring:eval> 태그에서 SpEL을 이용한 표현식을 사용해서 모델 오브젝트의 값을 가공해 출력할 수 있다. 컨버전 서비스도 함께 적용될 수 있기 때문에 기존의 JSP EL보다 편리하다.

이 두가지 사용법 외에 SpEL을 사용하려면 직접 SpEL API를 사용해 표현식을 파싱하는 코드를 작성해야 한다.
가장 활용 가능성이 높은 것은 AOP중에서도 @AspectJ를 이용해 특정 애노테이션이 붙은 메소드나 클래스를 선별하는 포인트컷을 적용한 경우

📝 SpEL 사용 방법

SpEL을 코드에서 사용하려면 기본적으로 두 가지 인터페이스를 활용해야 한다.

  • ExpressionParser
    - 표현식을 파싱하는 기능이 정의되어 있는 인터페이스
    - 구현 클래스가 두 가지 존재하는데 모두 SpEL용. 주로 SpelExpressionParser() 사용
ExpressionParser parser = new SpelExpressionParser();
  • Expression
    - 파서에 의해 해석된 표현식 정보를 가진 오브젝트의 타입
Expression ex = parser.parseExpression("1+2");
int result = (Integer)ex.getValue();

파서로 바로 파싱을 안하고 Expression 타입의 오브젝트로 만든 후 가져온 이유
- Expression 오브젝트를 재사용 가능하도록 하기 위해

📖 OXM

스프링 OXM은 서비스 추상화가 적용된 기술
스프링 OXM 추상화를 적용하는 방법은 두 가지가 존재

  • 코드에서 직접 마샬러 빈을 가져와 Marshaller 인터페이스를 이용해서 XML과 오브젝트 사이의 변경 작업을 수행하는 것
  • 웹 환경에서 XML 뷰나 XML 메시지 컨버터를 만들 때 활용

📝 Marshaller/Unmarshaller 인터페이스

Marshaller는 오브젝트를 XML로 변환하는 기능을 추상화한 인터페이스
- 지원 타입인지 확인하는 supports를 제외하면 변환 메소드 하나뿐인 간단한 인터페이스

public interface Marshaller {
	boolean supports(Class<?> clazz);
    void marshal(Object graph, Result result) throws IOException,
    	XmlMappingException;
}

marshal()메소드에 XML로 변환할 오브젝트 그래프의 루트 오브젝트를 전달하고 javax.xml.transform.Result 타입의 결과로 리턴받음

Unmarshaller는 XML 소스로부터 오브젝트를 변환해주는 기능을 정의한 인터페이스

public interface Unmarshaller {
	boolean supports(Class<?> clazz);
    Object unmarshal(Source source) throws IOException, XmlMappingException;
}

javax.xml.transform.Source 타입의 XML 소스를 파라미터로 넣으면 그에 매핑되는 오브젝트 그래프의 루트 오브젝트를 돌려줌

📝 OXM 기술 어댑터 클래스

Marshaller와 Unmarshaller 추상 인터페이스를 구현해서 각 OXM 기술과 연결해주는 어댑터 클래스는 총 다섯 가지가 존재
- Castor, JAXB, XMLBeans, JiBX, XStream
각 이름 뒤에 Marshaller를 붙이면 지원클래스가 됨
하나의 어댑터 클래스가 Marshaller와 Unmarshaller를 모두 구현하고 있음

<bean id="myService" class="...">
	<property name="marshaller" ref="castorMarshaller" />
  	<property name="unmarshaller" ref="castorMarshaller" />
</bean>

두 가지 모두 동일한 빈 레퍼런스를 주입하므로 같은 오브젝트이지만 용도가 다르니 별개의 프로퍼티로 주입받는 것이 좋다.

❗ 스프링이 마샬러/언마샬러를 인터페이스를 통해 각 기술에 독립적인 XML-오브젝트 변환 기능을 제공하지만, 세부 매핑정보가 적절히 만들어지지 않으면 엉뚱한 결과를 초래할 수 있음

📖 리모팅과 웹 서비스, EJB

자바 엔터프라이즈에는 다양한 종류의 리모팅 기술이 존재

리모팅 : 원격 시스템과 스프링 애플리케이션이 연동해서 동작하게 해주는 기술
두 가지로 구분 가능
- 스프링 애플리케이션이 클라이언트 시스템에게 원격 서비스를 제공하는 것
- 다른 원격 시스템의 서비스를 이용하는 것

스프링이 직접 지원하는 리모팅 기술
- RMI, 스프링 HTTP Invoker, Hessian, Burlap, JAX-RPC, JAX-WS, JMS, RESTful

📝 익스포터와 프록시

스프링 리모팅의 기본적인 구성은 템플릿 방식의 클라이언트만 제공하는 RESTful을 제외하면 거의 동일
- 스프링은 서비스를 제공할 때나 사용할 때나 모두 인터페이스를 이용해야 함

◼ 익스포터

익스포터 : 서비스를 제공할 때 원격 요청을 받아서 특정 인터페이스를 구현한 서비스 빈에게 요청을 전달해주는 빈

보통 익스포터는 원격 요청을 처리하는 서블릿 등을 통해 HTTP 요청을 전달받고 이를 해석한 후에 미리 설정을 통해서 등록된 인터페이스를 이용해 서비스 빈을 호출
익스포터 빈은 대부분 서비스 빈의 존재와 구현 인터페이스를 모름
- 서비스 인터페이스를 함께 제공해줘서 어떤 식으로 메소드를 호출해야 하는지 파악할 수 있게 해줘야 함

<bean name="/remoting/userservice"
      class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
  	<property name="service" ref="userService" />
  	<property name="serviceInterface"
              value="com.epril.myproject.service.UserService" />
</bean>

◼ 프록시

프록시: 원격 시스템에 있는 오브젝트를 대신해서 클라이언트 오브젝트의 호출을 받고, 이를 원격 오브젝트에 전송해서 결과를 가져와 클라이언트 오브젝트에게 돌려주는 역할을 맡은 빈 오브젝트

프록시는 원격 서비스 내용이 정의된 인터페이스를 구현하고 있어야 한다. 그래서 이를 사용하는 빈 입장에서는 원격 호출이 일어나는지를 신경 쓰지 않고, 같은 컨테이너 안의 빈 오브젝트를 사용하듯 사용

<bean id="memberWebService"
      class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
  	<property name="serviceInterface"
              value="com.epril.myproject.service.MemberService" />
  	<property name="wsdlDocumentUrl"
              value="http://ws.remotecompany.com/MemberServiceEndPoint?WSDL" />
  	<property name="namespaceUri" value="http://remote/" />
  	<property name="serviceName" value="MemberService" />
  	<property name="portName" value="MemberServiceServiceEndpointPort" />
</bean>
<bean id="myBean" class="...">
  	<property name="memberService" ref="memberWebService" />
  	...
</bean>

📝 RESTful 서비스 템플릿

RESTful 클라이언트 기능은 여타 리모팅 기술과 사용 방법이 다름.
- 스프링 MVC를 통해 구현하므로, 리모팅에서는 원격 RESTful 서비스 사이트를 이용해 결과를 가져오는 클라이언트 기능만 제공
- 템플릿/콜백 방식의 템플릿을 이용
- HTTP 메소드(GET, POST, PUST, DELETE HEAD, OPTIONS)을 지원

📝 EJB 서비스 이용

EJB2나 EJB3으로 만들어진 컴포넌트가 있고 EJB 컨테이너에서 서비스되고 있다면 이를 스프링에서 사용 가능.
스프링은 EJB도 리모팅의 프록시와 비슷한 방식으로 접근하도록 해줌.

📖 태스크 실행과 스케줄링

📝 TaskExecutor 서비스 추상화

태스크 : 독립적인 스레드 안에서 동작하도록 만들어진 오브젝트를 독립적으로 실행 가능한 작업
- 스프링은 이러한 태스크를 다양한 방법으로 실행할 수 있도록 태스크 실행기 인터페이스를 제공

public interface TaskExecutor extends Exeutor {
	void excute(Runnable task);
}

스프링이 제공하는 주요한 TaskExecutor 구현 기술과 클래스를 살펴보자

ThreadPoolExecutor

스프링의 ThreadPoolExecutor는 JDK의 ThreadPoolExecutor에 대한 어댑터 클래스
- 가장 손쉽게 사용할 수 있는 대표적인 태스크 실행기
- 지정된 크기의 스레드 풀을 사용하며 작업 요청은 큐를 통해 관리
- JDK ThreadPoolExecutor의 corePoolSize, maxPoolSize, queueCapacity 같은 속성을 프로퍼티를 이용해 설정할 수 있다.

SimpleThreadPoolTaskExecutor

Quartz의 SimpleThreadPool을 이용해 만들어진 태스크 실행기
- Quartz 스케줄러와 독립적을 ㅗ사용되면서 동시에 Quartz의 작업에도 활용될 수 있다는 장점

WorkManagerTaskExecutor

CommonJ 작업관리자의 태스크 실행기에 대한 어댑터
- CommonJ : 웹로직과 웹스피어를 비롯해서 여러 WAS에서 제공하는 JavaEE 환경을 위한 비동기 작업관리자

📝 TaskScheduler

자바 엔터프라이즈 환경에서 사용되는 태스크는 스케줄링 방식으로 동작하는 경우가 대부분이며, 스케줄링 기능을 제공하는 다양한 구현 기술이 존재

스프링은 TaskExecutor와 마찬가지로 서비스 추상화 기법을 이용해 스케줄링 기술에 독립적인 사용이 가능한 추상화 서비스 인터페이스인 TaskScheduler 제공

TaskScheduler 인터페이스는 주어진 태스크를 조건에 따라 실행하거나 반복하는 작업을 수행.
- Trigger 인터페이스를 구현해서 좀 더 유연한 실행조건을 만들 수도 있음
대표적인 트리거 구현 클래스로 CronTrigger가 대표적

@Autowired TaskScheduler scheduler;
@Resource Runnable specialTask;

public void startSpecialTaskScheduler() {
	//날짜에 상관없이 매주 월~금까지 새벽 4시 30분에 동작하도록 설정
	scheduler.schedule(task, new CronTrigger("0 30 4 * * MON-FRI"));
}

ThreadPoolTaskScheduler

JDK의 ScheduledThreadPoolExecutor 스케줄러에 대한 어댑터
- ScheduledThreadPoolExecutor는 스레드 풀 방식의 태스크 실행기 기능도 함께 갖고있음.
- 별도의 스레드 관리 서비스를 두지 않는 경우에 간편하게 사용 가능

TimerManagerTaskScheduler

CommonJ의 TimerManager를 TaskScheduler로 추상화한 클래스
애플리케이션 레벨에서 스레드를 관리하는 것이 바람직하지 않다고 생각되면 고려

📝 task 네임스페이스

스프링은 task 스키마에 정의된 전용 태그를 통해 태스크 실행기와 스케줄러를 간편하게 등록할 수 있는 방법을 제공

◼ <task:executor>

<task:executor>는 ThreadPoolTasskExecutor 타입의 TaskExecutor 빈을 등록
- 애트리뷰트로 id와 pool-size, queue-capacity를 지정 가능

◼ <task:scheduler>

TaskScheduler 타입의 ThreadPoolTaskScheduler 빈을 등록
id와 pool-size 지정 가능

◼ <task:scheduled-tasks> 와 <task:scheduled>

스케줄러를 적용하려면 태스크마다 번거로운 작업을 해주어야 함
- 스프링에서는 <task:scheduled-tasks> 태그를 이용해 일반 빈의 메소드를 태스크로 활용하는 스케줄 등록 가능

<task:scheduled-tasks scheduler="myScheduler">
  	<task:scheduled ref="systemAdiminService"
        method="checkSystem" fixed-rate="5000"/>
  	<task:scheduled ref="resourceService" method="clearAll" cron="0 30 6 * * *"/>
</task:scheduled-tasks>

스케줄은 <task:scheduled> 태그를 통해 등록
- 특별한 조건을 사용할 것이 아니라면 대부분의 스케줄링 작업은 위와 같이 사용해도 문제가 없을 것이다.

📝 애노테이션을 이용한 스케줄링과 비동기 태스크 실행

◼ @Scheduled

XML설정 대신 태스크 역할을 맡을 메소드에 직접 스케줄 정보를 애노테이션을 통해 부여해서 스케줄이 적용되게 해줌

fixedDelay

이전 작업이 끝난 시점부터 일정 시간이 지난 후에 동작하도록 설정
- 시간 단위는 밀리 초

@Scheduled(fixedDelay=60000)
public void checkSystemStatus() { ... }

fixedRate

밀리초로 설정된 일정한 시간 간격으로 메소드가 실행되게 해줌
- 이전 메소드가 호출된 시점으로부터의 시간

@Scheduled(fixedRate=60000)
public void checkSystemStatus() { ... }

cron

cron 포맷을 사용해 스케줄 지정 가능
- 가장 유연하게 스케줄을 지정할 수 있는 방법

@Scheduled(cron="0 0 12 1 * MON-FRI")
public void checkSystemStatus() { ... }

◼ @Async

@Async가 부여된 메소드는 자동으로 비동기 방식으로 실행
- TaskExecutor를 코드로 사용하지 않고도 비동기 실행기 가능하게 해주는 편리한 애노테이션
- 메소드를 호출하면 바로 리턴됨
- 리턴 타입은 void 또는 Future 타입이여야 함

@Async
void complexWork(String s) { ... }

@Scheduled와 @Async를 사용하려면 다음 태그를 등록해서 애노테이션 방식의 스케줄링과 태스크 실행이 가능하도록 해야 함
- executor와 scheduler 애트리뷰트에는 @Scheduled와 Async가 사용할 태스크 실행기와 태스크 스케줄러 빈의 레퍼런스를 지정해야 함

<task:annotation-driven executor="executor" scheduler="scheduler"/>

❗ 캐시 추상화는 추후에 필요시 정리 (스프링 3.1 버전)

profile
모르는 것 정리하기

0개의 댓글