티스토리 뷰

반응형

사실 지난 번에 포스팅한 '[JAVA:reflection] typesafe하게 특정 메서드 객체 가져오기 ' 나 '[JAVA] 현재 실행중인 메서드 가져오는 방법' 은 내가 어떤 문제를 해결하기 위해서 찾다가 알게 된 것이었다.

그 문제는 아래와 같다.


<문제>

우리는 보통 데이터를 캐싱된 데이터를 가져오거나 입력할 때,

1) 캐시 서비스 객체를 초기화하고, 

2) 캐시키를 만들고, 

3) 설정된 캐시팜에서 캐시키를 통해서 객체를 가져오고

4) 객체가 NULL이면 실제 서비스 객체를 통해서 데이터를 가져오고

5) 가져온 데이터를 캐시키에 맞게 캐시팜에 입력하고

6) 가져온 데이터를 리턴

하는 위와 같은 로직을 반복하고 있다. 이러한 중복되는 로직을 메서드로 추출하는 것이 문제이다.


문제를 해결하기 위해서 많은 고민을 해봤지만, 자바에서는 메서드를 넘기는 것이 굉장히 불편했다(수다스러웠다).


고민 중에 팀 내 선임 개발자이신 양 매니저님이 좋은 아이디어를 주셨다. 바로 자바 프록시였다. 예전에 해드퍼스트로 디자인 패턴 스터디를 했을 때, 공부를 했지만 써먹을 줄은 전혀 몰랐다. (이게 경험의 차이인가)


우선 위와 같은 로직을 수행하는 프록시의 구현체 ( CacheInvocationHandler implements InvocationHandler )를 구현했다. 어떤 서비스 객체로도 프록시를 생성할 수 있도록, 제네릭으로 구현했다. 

package util.cache;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* Created by reimaginer on 10/26/15.
*/
public class CacheInvocationHandler<T> implements InvocationHandler {
private T service;
private CacheOption option;

public CacheInvocationHandler(T service, CacheOption option) {
this.service = service;
this.option = option;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
//1. 캐시 서비스 선언
CacheService cacheService = new CacheServiceImpl(option.getCacheFarm());

//2. 캐시키를 통해서 데이터 가져온다 - 캐시 삭제 옵션이 있으면 안 가져온다
if (! option.isRemoveCache()) {
result = cacheService.get(option.getCacheKey());
}

//3. 캐시 서비스로 가져온 데이터가 null이면 실제 서비스 객체에서 데이터를 가져온다
if (result == null) {
result = method.invoke(service, args);
//4. 실제로 가져온 데이터를 캐시에 다시 넣는다
cacheService.set(option.getCacheKey(), result);
}

//5. 데이터를 반환한다
return result;
}
}


캐시 설정 같은 정보들을 담는 CacheOption이라는 객체도 만들었다. 좀 더 안전하게 옵션을 생성하기 위해서 생성자는 private으로 막고 create메소드를 여러 개 만들었다.(예를 들어, 모바일용 옵션은 createMobileOption() 같은 식으로 메서드를 만들었다.)

package util.cache;

/**
* Created by reimaginer on 10/26/15.
*/
public class CacheOption {
private String cacheKey;
private String cacheFarm;
private boolean removeCache;

private CacheOption(){};

public String getCacheKey() {
return cacheKey;
}
public String getCacheFarm() {
return cacheFarm;
}
public boolean isRemoveCache() {
return removeCache;
}

public static CacheOption createMobileOption(String cacheKey) {
CacheOption option = new CacheOption();
option.cacheKey = cacheKey;
option.cacheFarm = "mobile";
option.removeCache = false;

return option;
}
}


그리고 실제로 프록시를 만드는 메서드를 util성 메서드 (static)로 만들었다.

package util.cache;

import java.lang.reflect.Proxy;

/**
* Created by reimaginer on 10/26/15.
*/
public class CacheUtil {
public static <T> T getProxy(T service, CacheOption option) {

return (T) Proxy.newProxyInstance(service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new CacheInvocationHandler<T>(service, option));
}
}


그래서 이제 클라이언트는 쉽게 옵션을 생성하고, 캐싱이 필요한 서비스 객체를 프록시로 만들고, 메서드를 실행하면 보다 간단하게 캐싱을 할 수 있게 되었다.

- 테스트 서비스 인터페이스

package test;

/**
* Created by reimaginer on 10/26/15.
*/
public interface AppleService {
public String getAppleName(int code);
}

- 테스트 서비스 구현체

package test;

/**
* Created by reimaginer on 10/26/15.
*/
public class AppleServiceImpl implements AppleService {
public String getAppleName(int code) {
return "Mac";
}
}

- 캐싱된 테스트 서비스

package test;

import util.cache.CacheOption;
import util.cache.CacheUtil;

/**
* Created by reimaginer on 10/26/15.
*/
public class CacheTest {
public String getAppleName ( int code ) {
AppleService service = new AppleServiceImpl();
CacheOption option = CacheOption.createMobileOption("AppleName");

return CacheUtil.getProxy(service, option).getAppleName(123);
}
}


**생각해 봐야할 점

1. 장점

- 자바 프록시를 통해서 생각 보다 많은 일을 할 수 있다. 예를 들어서 캐싱 말고도 여러가지 기능을 데코레이터 처럼 추가할 수 있다. (잘은 모르지만 이게 AOP와 연관이 있다고 하는데, 더 공부해봐야 겠다.)

2. 단점

- 캐싱을 하려는 모든 서비스 객체는 interface가 있어야한다. 

- 프록시를 만드는 것이기 때문에 새로 만들어진 프록시는 내가 입력했던 서비스 객체의 모든 메서드를 사용할 수 있다. 따라서 진정 메서드에 접근을 제어하는 방법을 생각해 봐야 한다.

반응형
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함