책임 연쇄 패턴
- 요청을 처리할 수 있는 여러 개의 처리 객체를 연결하여 요청을 처리할 수 있는 객체가 요청을 처리할 때까지 차례로 전달하는 행동 디자인 패턴
- 해당 패턴은 다음과 같은 상황에서 유용하게 사용됨
- 여러 객체가 요청을 처리해야 할 때: 요청을 처리할 수 있는 객체가 여러 개 있고, 각 객체가 자신이 처리할 수 없는 요청은 다음 객체로 전달하는 경우
- 객체 간의 결합도를 낮춰야 할 때: 요청을 보내는 객체와 요청을 처리하는 객체 간의 결합도를 낮춰서 더 유연한 코드를 작성하고자 할 때
- 동적으로 요청 처리 객체를 변경할 수 있어야 할 때: 실행 중에 요청 처리 객체를 동적으로 변경할 수 있도록 해야 할 때

주요 구성 요소
1. Handler
- 요청을 처리하는 인터페이스 혹은 추상 클래스
- 다음 Handler를 가리키는 참조를 가지고 있음
2. Concrete Handler
- Handler를 구현하는 구체적인 클래스
- 요청을 처리할 수 있으면 처리하고, 그렇지 않으면 다음 Handler에게 요청을 전달
3. Client
- 요청을 생성하고 체인의 첫 번째 Handler에게 요청을 전달하는 객체
책임 연쇄 패턴 동작 과정
- 클라이언트는 요청을 생성하고 체인의 첫 번째 처리자에게 전달
- 각 처리자는 요청을 처리할 수 있는지 검사
- 처리할 수 있으면 요청을 처리하고, 체인은 종료
- 처리할 수 없으면 요청을 다음 처리자에게 전달
- 체인의 끝까지 요청이 전달되며, 끝까지 요청을 처리할 수 있는 객체가 없다면 요청은 처리되지 않음
책임연쇄 패턴 구현 예시
1. Handler 추상 클래스
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class Handler { | |
protected Handler nextHandler; | |
public void setNextHandler(Handler nextHandler) { | |
this.nextHandler = nextHandler; | |
} | |
public abstract void handleRequest(String request); | |
} |
2. ConcreteHandler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ConcreteHandler1 extends Handler { | |
@Override | |
public void handleRequest(String request) { | |
if (request.equals("요청1")) { | |
System.out.println("ConcreteHandler1이 요청을 처리합니다."); | |
} else if (nextHandler != null) { | |
nextHandler.handleRequest(request); | |
} | |
} | |
} | |
public class ConcreteHandler2 extends Handler { | |
@Override | |
public void handleRequest(String request) { | |
if (request.equals("요청2")) { | |
System.out.println("ConcreteHandler2가 요청을 처리합니다."); | |
} else if (nextHandler != null) { | |
nextHandler.handleRequest(request); | |
} | |
} | |
} |
3. 클라이언트 코드
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Client { | |
public static void main(String[] args) { | |
Handler handler1 = new ConcreteHandler1(); | |
Handler handler2 = new ConcreteHandler2(); | |
handler1.setNextHandler(handler2); | |
handler1.handleRequest("요청1"); | |
handler1.handleRequest("요청2"); | |
handler1.handleRequest("알 수 없는 요청"); | |
} | |
} |

책임 연쇄 패턴 장단점
장점
- 클라이언트 코드를 변경하지 않고 새로운 핸들러를 체인에 추가 가능 (SOLID의 OCP 원칙)
- 각각의 체인은 자신이 해야 하는 일만 수행할 수 있음 (SOLID의 SRP 원칙)
- 체인은 다양한 방법으로 구성 가능
단점
- 디버깅을 위해서는 핸들러를 타고타고 들어가야 하기 때문에 트러블 슈팅이 다소 어려울 수 있음
실무에서 쓰이는 책임 연쇄 패턴
1. 자바의 서블릿 필터
- 서블릿 필터(Servlet Filter)는 자바 웹 애플리케이션에서 클라이언트의 요청과 서버의 응답을 인터셉트하여 전처리 또는 후처리 작업을 수행하는 기능을 제공
- 서블릿 필터는 책임 연쇄 패턴을 적용한 대표적인 예로, 여러 필터가 체인 형태로 연결되어 순차적으로 요청을 처리
서블릿 필터 동작 원리
- 클라이언트의 요청이 필터 체인으로 전달됨: 클라이언트가 서블릿이나 JSP와 같은 웹 리소스에 요청을 보내면, 이 요청은 필터 체인(Filter Chain)으로 전달
- 필터 체인이 요청을 처리함: 필터 체인은 등록된 필터들을 순차적으로 호출하며 요청을 처리하며 각 필터는 요청을 전처리하거나 후처리 할 수 있음
- 서블릿 또는 JSP가 요청을 처리함: 필터 체인을 통해 모든 필터가 요청을 처리한 후, 최종적으로 서블릿이나 JSP가 요청을 처리
- 응답이 필터 체인으로 전달됨: 서블릿이나 JSP가 응답을 생성한 후, 이 응답은 다시 필터 체인을 통해 클라이언트로 전달하며 각 필터는 응답을 후처리 할 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.servlet.Filter; | |
import javax.servlet.FilterChain; | |
import javax.servlet.FilterConfig; | |
import javax.servlet.ServletException; | |
import javax.servlet.ServletRequest; | |
import javax.servlet.ServletResponse; | |
import java.io.IOException; | |
public class LoggingFilter implements Filter { | |
@Override | |
public void init(FilterConfig filterConfig) throws ServletException { | |
// 초기화 작업 | |
} | |
@Override | |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | |
throws IOException, ServletException { | |
System.out.println("LoggingFilter: Request received"); | |
chain.doFilter(request, response); | |
System.out.println("LoggingFilter: Response sent"); | |
} | |
@Override | |
public void destroy() { | |
// 정리 작업 | |
} | |
} |
2. 스프링 시큐리티 필터
- 스프링 시큐리티는 여러 필터들을 체인 형태로 구성하여 요청과 응답을 처리하며, 이는 책임 연쇄 패턴을 적용한 대표적인 사례
- 스프링 시큐리티는 여러 개의 보안 필터들을 FilterChainProxy라는 하나의 메인 필터로 묶어 관리하며 FilterChainProxy는 클라이언트의 요청이 들어올 때 등록된 보안 필터들을 순차적으로 실행
스프링 시큐리티 필터 주요 구성 요소
- FilterChainProxy: 스프링 시큐리티의 중심 필터로, 여러 보안 필터를 체인 형태로 관리하고 실행
- SecurityFilterChain: 여러 보안 필터의 집합으로, 특정 요청 패턴에 대해 적용될 필터들을 정의
- Security Filters: 각 보안 필터는 특정 보안 기능을 담당
- ex) 인증, 권한 부여, 세션 관리 등을 수행
스프링 시큐리티 동작 과정
- 클라이언트의 요청이 FilterChainProxy로 전달
- FilterChainProxy는 요청에 맞는 SecurityFilterChain을 지정
- 지정된 SecurityFilterChain에 정의된 보안 필터들이 순차적으로 실행
- 각 필터는 요청을 처리하고, 필요에 따라 다음 필터로 요청을 전달
- 모든 필터가 요청을 처리한 후, 최종적으로 서블릿 또는 다른 웹 리소스가 요청을 처리
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
@EnableWebSecurity | |
public class SecurityConfig { | |
@Bean | |
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | |
http | |
.authorizeRequests(authorizeRequests -> | |
authorizeRequests | |
.antMatchers("/api/**").permitAll() | |
.anyRequest().authenticated() | |
) | |
.formLogin(formLogin -> | |
formLogin | |
.loginPage("/login") | |
.permitAll() | |
) | |
.logout(logout -> | |
logout | |
.permitAll() | |
); | |
return http.build(); | |
} | |
@Bean | |
public UserDetailsService userDetailsService() { | |
var user = User.withUsername("user") | |
.password("{noop}password") // {noop}은 비밀번호를 인코딩하지 않음을 나타냄 | |
.roles("USER") | |
.build(); | |
var admin = User.withUsername("admin") | |
.password("{noop}admin") | |
.roles("ADMIN") | |
.build(); | |
return new InMemoryUserDetailsManager(user, admin); | |
} | |
} |
참고
코딩으로 학습하는 GoF의 디자인 패턴 - 백기선 강사님
반응형
'Design Pattern' 카테고리의 다른 글
[디자인 패턴] 인터프리터 패턴 (Interpreter Pattern) (0) | 2024.06.30 |
---|---|
[디자인 패턴] 중재자 패턴 (Mediator Pattern) (0) | 2024.06.30 |
[디자인 패턴] 프록시 패턴 (Proxy Pattern) (0) | 2024.06.29 |
[디자인 패턴] 플라이웨이트 패턴 (Flyweight Pattern) (0) | 2024.06.29 |
[디자인 패턴] 퍼사드 패턴 (Facade Pattern) (0) | 2024.06.29 |