Facade Pattern 이란?
하위 시스템의 복잡도를 감추는 동시에 그 전체 기능을 사용하기 쉬운 단순한 인터페이스를 제공하는 패턴입니다.
즉, 복잡한 비즈니스 로직을 상위 레벨의 인터페이스로 캡슐화해서 하위 시스템에 더 쉽게 접근할 수 있게 하며 보통 연관된 메서드 호출을 메소드 하나로 묶어 순서대로 실행시킵니다. 퍼사드 패턴을 싱글 추상 팩토리 패턴이라고도 부릅니다.
Facade Pattern이 쓰이는 경우
- 클래스 드라이버 같은 API를 만들 때
- 네트워크 호출을 줄일 때. 퍼사드 패턴은 하위 시스템을 여러 차례 호출하는 반면 클라이언트는 퍼사드 패턴을 딱 한 번만 호출합니다.
- 보안과 단순함 측면에서 애플리케이션 내부 실행흐름을 캡슐화 할 때
Facade Pattern의 장점
- 클라이언트가 하위 시스템을 알 필요가 없으므로 결합도가 낮아집니다.
- 코드의 유지보수성, 관리성이 좋아집니다.
- 로직을 다시 사용할 수 있어 기능을 재사용할 일이 많아집니다.
- 여러번 실행해도 호출하는 메서드는 동일하기 때문에 일관된 서비스가 보장됩니다.
- 연관된 메서드를 한 메서드로 묶어 호출하므로 비즈니스로직이 덜 복잡해집니다.
- 테스트할 수 있고 mock을 쓸 수 있는 패턴으로 구현이 됩니다.
Facade Pattern이 적용된 java HttpSession
Controller api에서 session을 사용하는 코드
@GetMapping("/inquiryBalance")
public ApiResult<InquiryBalanceReq> inquiryBalance
(@RequestBody InquiryBalanceReq inquiryBalanceReq, HttpSession session) {
return success(inquiryBalanceReq);
}
위 코드 처럼 컨트롤러에서 HttpSession을 파라미터로 받게되면
DispatcherServlet의 doService 메소드 안에서
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
flashMapManager가 null이 아닌 경우 AbstractFlashMapManager 클래스의 retrieveAndUpdate 메소드안에 retriebeFlashMaps (AbstractFlashMapManager를 상속 받은 SessionFlashMapManage의 retriebeFlashMaps 메소드)메소드가 실행이됩니다.
SessionFlashMapManager의 retrieveFlashMap 메소드
@Override
@SuppressWarnings("unchecked")
@Nullable
protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
// RequestFacade의 getSession 호출
HttpSession session = request.getSession(false);
return (session != null ? (List<FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE) : null);
}
retrieveFlashMap 메소드를 보면 request.getSession(false)메소드가 실행되는것을 볼 수 있는데, 이 메소드가 실행되면서 RequestFacade의 getSession이 호출 되고 이 안에서 Request의 getSession이 호출 되면서 실질적인 HttpSession의 getSession이 실행되고 그 안에서 SessionFacade이 만들어지게 됩니다.
밑의 코드를 보시면 이해가 더 잘 되실겁니다.
RequestFacade의 getSession
@Override
public HttpSession getSession(boolean create) {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
if (SecurityUtil.isPackageProtectionEnabled()){
return AccessController.
doPrivileged(new GetSessionPrivilegedAction(create));
} else {
// Request의 getSession을 호출
return request.getSession(create);
}
}
Request의 getSession
@Override
public HttpSession getSession(boolean create) {
Session session = doGetSession(create);
if (session == null) {
return null;
}
return session.getSession();
}
StandardSession의 getSession
@Override
public HttpSession getSession() {
if (facade == null) {
// Session의 Facade 패턴 적용
if (SecurityUtil.isPackageProtectionEnabled()) {
facade = AccessController.doPrivileged(new PrivilegedNewSessionFacade(this));
} else {
facade = new StandardSessionFacade(this);
}
}
return facade;
}
이러한 과정을 거쳐 Facade Pattern이 적용된 Session을 컨트롤러 파라미터로 받아서 사용할 수 있게 됩니다.
위 사진을 보시면 session 파라미터가 StandardSessionFacade 객체인 것을 확일 할 수 있습니다.
간단하게 Facade Pattern을 사용하면 좋은 예를 보여드리도록 하겠습니다.
StandardSessionFacade의 invalidate() 메소드를 보시면 StandardSession의 invalidate() 메소드를 호출하고 있습니다.
@Override
public void invalidate() {
session.invalidate();
}
StandardSession의 invalidate() 메소드의 내부 로직
사실 invalidate() 내부 로직은 상당히 복잡하게 동작하게 됩니다.
이렇게 Facade Pattern은 하위 시스템의 복잡도를 감추는 동시에 그 전체 기능을 사용하기 쉽도록 합니다.
@Override
public void invalidate() {
if (!isValidInternal()) {
throw new IllegalStateException
(sm.getString("standardSession.invalidate.ise"));
}
// Cause this session to expire
expire();
}
@Override
public void expire() {
expire(true);
}
public void expire(boolean notify) {
// Check to see if session has already been invalidated.
// Do not check expiring at this point as expire should not return until
// isValid is false
if (!isValid) {
return;
}
synchronized (this) {
// Check again, now we are inside the sync so this code only runs once
// Double check locking - isValid needs to be volatile
// The check of expiring is to ensure that an infinite loop is not
// entered as per bug 56339
if (expiring || !isValid) {
return;
}
if (manager == null) {
return;
}
// Mark this session as "being expired"
expiring = true;
// Notify interested application event listeners
// FIXME - Assumes we call listeners in reverse order
Context context = manager.getContext();
// The call to expire() may not have been triggered by the webapp.
// Make sure the webapp's class loader is set when calling the
// listeners
if (notify) {
ClassLoader oldContextClassLoader = null;
try {
oldContextClassLoader = context.bind(Globals.IS_SECURITY_ENABLED, null);
Object listeners[] = context.getApplicationLifecycleListeners();
if (listeners != null && listeners.length > 0) {
HttpSessionEvent event =
new HttpSessionEvent(getSession());
for (int i = 0; i < listeners.length; i++) {
int j = (listeners.length - 1) - i;
if (!(listeners[j] instanceof HttpSessionListener)) {
continue;
}
HttpSessionListener listener =
(HttpSessionListener) listeners[j];
try {
context.fireContainerEvent("beforeSessionDestroyed",
listener);
listener.sessionDestroyed(event);
context.fireContainerEvent("afterSessionDestroyed",
listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
try {
context.fireContainerEvent(
"afterSessionDestroyed", listener);
} catch (Exception e) {
// Ignore
}
manager.getContext().getLogger().error
(sm.getString("standardSession.sessionEvent"), t);
}
}
}
} finally {
context.unbind(Globals.IS_SECURITY_ENABLED, oldContextClassLoader);
}
}
if (ACTIVITY_CHECK) {
accessCount.set(0);
}
// Remove this session from our manager's active sessions
manager.remove(this, true);
// Notify interested session event listeners
if (notify) {
fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
}
// Call the logout method
if (principal instanceof TomcatPrincipal) {
TomcatPrincipal gp = (TomcatPrincipal) principal;
try {
gp.logout();
} catch (Exception e) {
manager.getContext().getLogger().error(
sm.getString("standardSession.logoutfail"),
e);
}
}
// We have completed expire of this session
setValid(false);
expiring = false;
// Unbind any objects associated with this session
String keys[] = keys();
ClassLoader oldContextClassLoader = null;
try {
oldContextClassLoader = context.bind(Globals.IS_SECURITY_ENABLED, null);
for (String key : keys) {
removeAttributeInternal(key, notify);
}
} finally {
context.unbind(Globals.IS_SECURITY_ENABLED, oldContextClassLoader);
}
}
}
'ComputerScience > Design Pattern' 카테고리의 다른 글
Proxy Pattern (0) | 2023.03.13 |
---|---|
Adaptor Pattern (0) | 2023.03.06 |
Strategy Pattern (0) | 2023.02.20 |
디자인 패턴 - 디자인 원칙 (0) | 2023.02.14 |
Builder Pattern (0) | 2023.02.01 |