1.職責鏈路模式
1.1UML圖
1.2 職責鏈路模式的概念
為了解耦的任務校驗,將校驗物件連成一個鏈,沿著這個鏈進行訪問,直到有一個物件處理位置;
1.3 優點
1.按照一定的順序執行判斷;
2.避免校驗物件之間耦合關係;
3.不用擔心沒有程式碼沒有執行到;
2.職責鏈路模式在過濾器(Filter)中的使用
1.原始碼檢視
1.ApplicationDispatcher
這段程式碼總共做了三件事:1.過濾器鏈建立;2.過濾鏈逐個過濾;3.釋放過濾鏈資源;
private void invoke(ServletRequest request, ServletResponse response,
State state) throws IOException, ServletException {
//。。。。。。。。前面的程式碼省略
// Get the FilterChain Here
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); //建立過濾器校驗鏈條
// Call the service() method for the allocated servlet instance
try {
// for includes/forwards
if ((servlet != null) && (filterChain != null)) {
filterChain.doFilter(request, response); //進行過濾器校驗
}
// Servlet Service Method is called by the FilterChain
} catch (ClientAbortException e) {
//。。。。。。。省略中間錯誤判斷程式碼
}
// Release the filter chain (if any) for this request
try {
if (filterChain != null)
filterChain.release();//釋放過濾器資源
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
wrapper.getLogger().error(sm.getString("standardWrapper.releaseFilters",
wrapper.getName()), e);
// FIXME: Exception handling needs to be similar to what is in the StandardWrapperValue
}
//。。。。。。。。。後面的程式碼省略
}
2.ApplicationFilterFactory(過濾鏈條建立過程)
從下面可以看出主要是一下操作:
1.初始化ApplicatFilterChain 過濾器校驗鏈;
2.從上下文環境中,獲取之前配置的過濾器資料;
3.將符合URL,serveletName的過濾器配置到ApplicationFilterChain中
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
// If there is no servlet to execute, return null
if (servlet == null)
return null;
// Create and initialize a filter chain object 初始化鏈式物件
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
// Acquire the filter mappings for this Context 獲取過濾器配置的上下文
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return filterChain;
// Acquire the information we will need to match filter mappings
DispatcherType dispatcher =
(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
String requestPath = null;
Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain 將符合需求的過濾器加入到過濾鏈中
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Add filters that match on servlet name second
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Return the completed filter chain
return filterChain;
}
3.ApplicationFilterChain(過濾鏈增加的具體過程)
這個方法比較簡單:1.陣列擴容;2.增加新的過濾器;
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];//過濾器儲存的實體類
private int pos = 0;//當前過濾位置
private int n = 0;//儲存的過濾器的總數
public static final int INCREMENT = 10;
void addFilter(ApplicationFilterConfig filterConfig) {
// Prevent the same filter being added multiple times
for(ApplicationFilterConfig filter:filters)
if(filter==filterConfig)
return;
if (n == filters.length) {
ApplicationFilterConfig[] newFilters =
new ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters;
}
filters[n++] = filterConfig;
}
4.ApplicationFilterChain 的doFilter方法
處理過程:
1.獲取pos位置的過濾器;
2.Filter執行,將當前過濾鏈物件,作為引數進行傳遞;
3.pos過濾器後移1位進行呼叫,直到pos大於總過濾器位置;
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<Void>() {
@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 {
internalDoFilter(request,response);
}
}
//實際處理過濾任務的方法
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];//pos預設是從0開始的,呼叫後+1
try {
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.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;
}
//。。。。。。。。。。省略部分程式碼
}
2.UML圖(上面的鏈式呼叫的圖,如有錯誤還請指出)
3.手寫一個通用校驗鏈
業務需求:前端傳來資料,動態SQL拼接,判斷SQL各個部分是否資料有問題;
1.定義介面
/**
* 引數校驗鎖鏈管理
* @param
* @author lpf
*/
public interface CheckChain{
public abstract void doCheck(Param param) throws Exception;
}
2.對過濾引數進行約束
public interface Param<T extends Param> {
public abstract <T> T get();
}
3.定義過濾介面
@Service
public interface CheckFilter<T extends Param> {
/**
* 引數校驗方法
* @param chain
* @return
*/
public abstract void checkParam(Param<T> param, CheckChain chain) throws Exception;
}
4.預設鏈式校驗實現類
/**
* 預設鏈式檢查
*/
public class DefaultCheckChain implements CheckChain {
/**
*
*/
private ParamCheckWapper[] wappers = new ParamCheckWapper[0];
private static final int INCREMENT = 10;
private int n = 0;
private int pos = 0;
//進行鏈式檢查
@Override
public void doCheck(Param filed) throws Exception {
if(pos < n){
ParamCheckWapper wapper = wappers[pos++];
CheckFilter paramCheck = wapper.getParamCheck();
Assert.notNull(paramCheck,"鏈式類不能為空");
paramCheck.checkParam(filed,this);
}
}
/**
* 增加要進行過濾處理的類
* @param checkWapper
*/
public void addCheck(ParamCheckWapper checkWapper){
for(ParamCheckWapper wapper : wappers){
if(wapper == checkWapper){return;} ;
}
if(n == wappers.length){
ParamCheckWapper[] newWappers = new ParamCheckWapper[n + INCREMENT];
System.arraycopy(wappers, 0, newWappers, 0, n);
wappers = newWappers;
}
wappers[n++] = checkWapper;
}
}
5.過濾實現類(可以有多個)
/**
* select引數校驗
* @author lpf
* @since 2019-11-08
*/
public class SelectParamCheck implements CheckFilter<CheckParam> {
/**
* 引數校驗
* @param param
* @param chain
*/
@Override
public void checkParam(Param<CheckParam> param, CheckChain chain) throws Exception{
CheckParam checkParam = param.get();
List<SelectField> selects = checkParam.getSelect();
List<String> columns = checkParam.getColumnList();
//對select引數進行校驗
selects.forEach(select -> {
String filed = select.getFiled().toLowerCase();
boolean flag = columns.contains(filed);
if(!flag) throw new RuntimeException(select.getFiled()+"不存在,請重新整理頁面重新選擇查詢欄位!!!");
});
}
6.過濾類註冊(可以通過yml配置反射生成,或者通過手動註冊)
@Service
public class SearchConfigService {
/**預設檢查鏈*/
private static DefaultCheckChain checkChain ;
/**過濾鏈路表配置*/
static{
checkChain = new DefaultCheckChain();
//引數檢查器
ParamCheckWapper selectParamCheck = new ParamCheckWapper(new SelectParamCheck(),"SelectParamCheck");
ParamCheckWapper groupParamCheck = new ParamCheckWapper(new GroupbyParamCheck(), "groupParamCheck");
ParamCheckWapper conditionParamCheck = new ParamCheckWapper(new ConditionParamCheck(), "conditionParamCheck");
ParamCheckWapper orderbyParamCheck = new ParamCheckWapper(new OrderbyParamCheck(), "orderbyParamCheck");
//引數連結串列增加過濾類
checkChain.addCheck(selectParamCheck);
checkChain.addCheck(groupParamCheck);
checkChain.addCheck(conditionParamCheck);
checkChain.addCheck(orderbyParamCheck);
}
/**
* 引數校驗
*/
public void doCheck(Param param) throws Exception {
checkChain.doCheck(param);
}
以上,就是職責鏈路模式的簡單使用,可以通過泛型進行程式碼剝離,後續涉及到鏈式校驗的時候就可以通過限制引數進行多樣使用。降低程式碼的耦合度;
至此,職責鏈路設計模式的介紹就結束了;