責(zé)任鏈模式
顧名思義鹉究,責(zé)任鏈模式(Chain of Responsibility Pattern)為請求創(chuàng)建了一個接收者對象的鏈懂版。這種模式給予請求的類型冠场,對請求的發(fā)送者和接收者進行解耦炬丸。這種類型的設(shè)計模式屬于行為型模式。在這種模式中初嘹,通常每個接收者都包含對另一個接收者的引用及汉。如果一個對象不能處理該請求,那么它會把相同的請求傳給下一個接收者屯烦,依此類推坷随。
簡介
意圖 避免請求發(fā)送者與接收者耦合在一起,讓多個對象都有可能接收請求驻龟,將這些對象連接成一條鏈温眉,并且沿著這條鏈傳遞請求,直到有對象處理它為止翁狐。
主要解決 職責(zé)鏈上的處理者負責(zé)處理請求类溢,客戶只需要將請求發(fā)送到職責(zé)鏈上即可,無須關(guān)心請求的處理細節(jié)和請求的傳遞露懒,所以職責(zé)鏈將請求的發(fā)送者和請求的處理者解耦了闯冷。
何時使用 在處理消息的時候以過濾很多道。
如何解決 攔截的類都實現(xiàn)統(tǒng)一接口懈词。
關(guān)鍵代碼 Handler 里面聚合它自己蛇耀,在 handleRequest 里判斷是否合適,如果沒達到條件則向下傳遞坎弯。
純責(zé)任鏈與不純責(zé)任鏈
純:純責(zé)任鏈中的節(jié)點只有兩種行為纺涤,一處理責(zé)任躁倒,二將責(zé)任傳遞到下一個節(jié)點。不允許出現(xiàn)某一個節(jié)點處理部分或全部責(zé)任后又將責(zé)任向下傳遞的情況洒琢。
不純:允許某個請求被一個節(jié)點處理部分責(zé)任后再向下傳遞,或者處理完后其后續(xù)節(jié)點可以繼續(xù)處理該責(zé)任褐桌,而且一個責(zé)任可以最終不被任何節(jié)點所處理衰抑。
主要角色
- Handler(抽象處理者): 定義一個處理請求的接口,提供對后續(xù)處理者的引用
- ConcreteHandler(具體處理者): 抽象處理者的子類荧嵌,處理用戶請求呛踊,可選將請求處理掉還是傳給下家;在具體處理者中可以訪問鏈中下一個對象啦撮,以便請求的轉(zhuǎn)發(fā)
應(yīng)用實例
1谭网、紅樓夢中的"擊鼓傳花”。
2赃春、JS 中的事件冒泡愉择。
3、JAVA WEB 中 Apache Tomcat 對 Encoding 的處理织中,Struts2 的攔截器锥涕,jsp servlet 的 Filter。
優(yōu)點
1狭吼、降低耦合度层坠。它將請求的發(fā)送者和接收者解耦。
2刁笙、簡化了對象破花。使得對象不需要知道鏈的結(jié)構(gòu)。
3疲吸、增強給對象指派職責(zé)的靈活性座每。通過改變鏈內(nèi)的成員或者調(diào)動它們的次序,允許動態(tài)地新增或者刪除責(zé)任摘悴。
4尺栖、增加新的請求處理類很方便。
缺點
1烦租、不能保證請求一定被接收延赌。
2、系統(tǒng)性能將受到一定影響叉橱,而且在進行代碼調(diào)試時不太方便挫以,可能會造成循環(huán)調(diào)用。
3窃祝、可能不容易觀察運行時的特征掐松,有礙于除錯。
使用場景
1、有多個對象可以處理同一個請求大磺,具體哪個對象處理該請求由運行時刻自動確定抡句。
2、在不明確指定接收者的情況下杠愧,向多個對象中的一個提交一個請求待榔。
3、可動態(tài)指定一組對象處理請求流济。
Github項目描述
跳轉(zhuǎn)到我的責(zé)任鏈設(shè)計模式源碼
1.出行方式
travel包里主要對出行方式的責(zé)任鏈模式锐锣。跟進用戶身上的錢,在優(yōu)先級如飛機->火車->大巴的順序下選擇對應(yīng)的出行模式绳瘟。
public class Application {
public static void main(String[] args) {
Handler planeHandler = new PlaneHandler();
Handler trainHandler = new TrainHandler();
Handler busHandler = new BusHandler();
planeHandler.setNext(trainHandler);
trainHandler.setNext(busHandler);
planeHandler.handleRequest("老王", 40d);
planeHandler.handleRequest("張三", 140d);
planeHandler.handleRequest("李四", 240d);
planeHandler.handleRequest("吳老五", 340d);
}
}
抽象處理
@Data
public abstract class Handler {
/**
* 下一個鏈節(jié)點
*/
protected Handler next;
public abstract void handleRequest(String name, Double wallet);
}
具體的處理者(飛機雕憔、火車、大巴)
@Slf4j
public class PlaneHandler extends Handler {
private double price = 280d;
@Override
public void handleRequest(String name, Double wallet) {
if (price <= wallet) {
log.info("{}身上的錢可以坐飛機糖声。", name);
return;
}
if (Objects.nonNull(next)) {
next.handleRequest(name, wallet);
return;
}
log.info("{}錢不夠斤彼,只能徒步啦", name);
}
}
@Slf4j
public class TrainHandler extends Handler {
private double price = 149.99d;
@Override
public void handleRequest(String name, Double wallet) {
if (price <= wallet) {
log.info("{}身上的錢只能坐火車。", name);
return;
}
if (Objects.nonNull(next)) {
next.handleRequest(name, wallet);
return;
}
log.info("{}錢不夠蘸泻,只能徒步啦", name);
}
}
@Slf4j
public class BusHandler extends Handler {
private double price = 59.99d;
@Override
public void handleRequest(String name, Double wallet) {
if (price <= wallet) {
log.info("{}身上的錢只能坐大巴畅卓。", name);
return;
}
if (Objects.nonNull(next)) {
next.handleRequest(name, wallet);
return;
}
log.info("{}錢不夠,只能徒步啦", name);
}
}
2.出行方式2蟋恬,參考Filter鏈的寫法
travel2包是對travel包的重新寫法翁潘。
public class Application {
public static void main(String[] args) {
HandlerChain chain = new HandlerChain();
Handler planeHandler = new PlaneHandler();
Handler trainHandler = new TrainHandler();
Handler busHandler = new BusHandler();
chain.addHandler(planeHandler);
chain.addHandler(trainHandler);
chain.addHandler(busHandler);
chain.handle("老王", 40d);
chain.handle("張三", 140d);
chain.handle("李四", 240d);
chain.handle("吳老五", 340d);
}
}
抽象處理者
public interface Handler {
void handleRequest(String name, Double wallet, HandlerChain chain);
}
具體處理者(飛機、火車歼争、大巴)
@Slf4j
public class PlaneHandler implements Handler {
private double price = 280d;
@Override
public void handleRequest(String name, Double wallet, HandlerChain chain) {
if (price <= wallet) {
log.info("{}身上的錢可以坐飛機拜马。", name);
chain.reuse();
return;
}
chain.handle(name, wallet);
}
}
@Slf4j
public class TrainHandler implements Handler {
private double price = 149.99d;
@Override
public void handleRequest(String name, Double wallet, HandlerChain chain) {
if (price <= wallet) {
log.info("{}身上的錢只能坐火車。", name);
chain.reuse();
return;
}
chain.handle(name, wallet);
}
}
@Slf4j
public class BusHandler implements Handler {
private double price = 59.99d;
@Override
public void handleRequest(String name, Double wallet, HandlerChain chain) {
if (price <= wallet) {
log.info("{}身上的錢只能坐大巴沐绒。", name);
chain.reuse();
return;
}
chain.handle(name, wallet);
}
}
責(zé)任鏈管理者
@Slf4j
public class HandlerChain {
private List handlerList = new ArrayList<>();
/**
* 維護當(dāng)前鏈上位置
*/
private int pos;
/**
* 鏈的長度
*/
private int handlerLength;
public void addHandler(Handler handler) {
handlerList.add(handler);
handlerLength = handlerList.size();
}
public void handle(String name, double wallet) {
if (CollectionUtils.isEmpty(handlerList)) {
log.error("有錢俩莽,但沒提供服務(wù),{}也估計就只能步行了乔遮。", name);
return;
}
if (pos >= handlerLength) {
log.error("身上錢不夠扮超,{}也估計就只能步行了。", name);
reuse();
return;
}
Handler handler = handlerList.get(pos++);
if (Objects.isNull(handler)) {
log.error("假服務(wù)蹋肮,{}也估計就只能步行了出刷。", name);
reuse();
return;
}
handler.handleRequest(name, wallet, this);
}
/**
* 鏈重新使用
*/
public void reuse() {
pos = 0;
}
}
學(xué)習(xí)Web項目的Filter
待補充…
補充補充遺留的Filter過濾器中的責(zé)任鏈處理。
本次主要是對Tomcat中的Filter處理簡單的梳理坯辩,如有不正確的地方馁龟,還望指出來,大家互勉漆魔,共進坷檩。
老項目大家可以在web.xml中配置filter却音,現(xiàn)使用Springboot后,也有兩種配置filter方式矢炼,通過創(chuàng)建FilterRegistrationBean的方式和通過注解@WebFilter+@ServletComponentScan的方式系瓢。
三個主要的角色
FIlter,不多介紹了句灌。
FilterChain servlet容器提供的開發(fā)調(diào)用鏈的過濾請求的資源夷陋。通過調(diào)用下一個filter實現(xiàn)過濾,在整體鏈上涯塔。
FilterConfig filter的配置器,在servlet容器在Filter初始化的時候傳遞信息清蚀。
具體的filter匕荸,主要說說Spring中的兩個抽象Filter,GenericFilterBean和OncePerRequestFilter枷邪。
前者主要是做init和destroy的操作榛搔,重點還是init方法,destroy只是空實現(xiàn)而已东揣。
后者主要是做真正的doFilter操作践惑,也是我們在Spring中創(chuàng)建Filter通常繼承的。
而ApplicationFilterChain就算Tomcat中的FilterChain實現(xiàn)嘶卧。
/**
* The int which is used to maintain the current position
* in the filter chain.
*/
private int pos = 0;
/**
* The int which gives the current number of filters in the chain.
*/
private int n = 0;
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
//安全相關(guān)的尔觉,暫不關(guān)注
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction() {
@Override
public Void run()
throws ServletException, IOException {
internalDoFilter(req,res);
return null;
}
}
);
} catch( PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException)
throw (ServletException) e;
else if (e instanceof IOException)
throw (IOException) e;
else if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new ServletException(e.getMessage(), e);
}
} else {
//真正的doFilter
internalDoFilter(request,response);
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
//pos 調(diào)用鏈中當(dāng)前連接點所在的位置
//n 調(diào)用鏈總節(jié)點長度
// Call the next filter if there is one
if (pos < n) {
//對節(jié)點進行自增 pos++
ApplicationFilterConfig filterConfig = filters[pos++];
try {
//當(dāng)前節(jié)點小于總長度后,從filter配置類中取出filter
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false”.equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
//真正的filter
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
//到了調(diào)用鏈結(jié)尾處芥吟,就真正調(diào)用servlet實例的servlet.service(request, response);
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege(“service”,
servlet,
classTypeUsedInService,
args,
principal);
} else {
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
/**
* Prepare for reuse of the filters and wrapper executed by this chain.
* 重復(fù)使用filter調(diào)用鏈侦铜,pos重設(shè)為0
*/
void reuse() {
pos = 0;
}
重點從ApplicationFilterChain中挑出幾個重要的方法拿出來分析下Filter的調(diào)用鏈,其實還有幾處沒有具體講到钟鸵,ApplicationFilterChain是合適創(chuàng)建的钉稍,F(xiàn)ilter是怎么加入到ApplicationFilterChain中的。這涉及到Tomcat是怎樣加載Content的棺耍,下次分析Tomcat的時候贡未,再來具體分析,它是如何運作的蒙袍,如何加載web.xml俊卤。
參考
Filter、FilterConfig害幅、FilterChain|菜鳥教程