0.說明
本博客記錄的是如何顯示SpringMVC框架中所有使用@RequestMapping注解標注的方法.
由於項目需要,web框架使用SpringMVC.前端\客戶端\后端是分開的不同組的人,所以不可避免的要編寫\更新大量的接口說明文檔.這大大降低了效率,因此實現了顯示SpringMVC中所有接受請求的方法的信息的功能.
總體思想:從Spring容器中找到所有加了RequestMapping注解的方法,並且集中顯示.
1.用處
顯示SpringMVC中所有加了@RequestMapping注解的方法信息
2.實現
2.0.環境說明
使用maven開發,別的不多說了.直接上pom
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework.version}</version> <scope>test</scope> </dependency>
其中
<org.springframework.version>3.2.13.RELEASE</org.springframework.version>
底層的spring core之類的也是3.2.13.RELEASE
2.1.顯示所有請求信息
細心的同學可能發現了.在springMVC項目啟動的時候,會出現很多的信息.
其中,和咱們這個文章相關的就是這個類輸出的信息了:
2015-05-18 12:18:47,253 INFO org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/guide/display/search],methods=[GET],params=[],headers=[],consumes=[],produces=[application/json;charset=UTF-8],custom=[]}" onto public java.lang.String com.renren.toro.waltz.web.controller.guide.DisplayController.search()
2015-05-18 12:18:47,253 INFO org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/guide/display],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.renren.toro.waltz.web.controller.guide.DisplayController.index()
2015-05-18 12:18:47,253 INFO org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/guide/display/detail],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.renren.toro.waltz.web.controller.guide.DisplayController.detail(int)
2015-05-18 12:18:47,255 INFO org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/logout],methods=[GET],params=[],headers=[],consumes=[],produces=[application/json;charset=UTF-8],custom=[]}" onto public java.lang.String com.renren.toro.waltz.web.controller.LogoutController.logout(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
還有很多,只是截取了一段.
這個RequestMappingHandlerMapping 類會輸出所有的Map信息.
下面介紹信息中的數據
這里以這一條輸出作為實例:
2015-05-18 12:18:47,255 INFO org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/logout],methods=[GET],params=[],headers=[],consumes=[],produces=[application/json;charset=UTF-8],custom=[]}" onto public java.lang.String com.renren.toro.waltz.web.controller.LogoutController.logout(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
序號 | 名稱 | 說明 |
1 | /logout |
對應的接受請求的url |
2 | methods |
接受請求的方法 |
3 | params |
請求的參數 |
4 | headers |
請求的header信息 |
5 | consumes |
接受請求的類型 |
6 | produces |
返回的數據類型 |
后邊會輸出對應的類和方法信息:
onto public java.lang.String com.renren.toro.waltz.web.controller.LogoutController.logout(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
因此從這里入手.
我這里使用了annotation-driven,所以可以直接
@Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping;
這樣就可以在一個handler方法中獲取所有的添加了@RequestMapping的方法了.
具體如下:
1 // request methods 2 Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods(); 3 Set<RequestMappingInfo> keySet = handlerMethods.keySet(); 4 5 for (RequestMappingInfo requestMappingInfo : keySet) { 6 // 請求路徑 7 String path = requestMappingInfo.getPatternsCondition().toString(); 8 9 // 請求方法 10 String requestMethod = requestMappingInfo.getMethodsCondition().toString(); 11 18 // 返回header類型 19 String responseType = requestMappingInfo.getProducesCondition().toString(); 20 21 RequestMethodItem item = new RequestMethodItem(); 22 23 item.setPath(path);26 item.setMethod(handlerMethod.toString().replace(" ", "<br>")); 27 item.setResponseType(responseType); 28 29 items.add(item); 30 }
這里需要寫一個Item對象來存儲這些信息,方便一起"打包"傳給jsp頁面,方便顯示,由於只是一個POJO,就不帖代碼了.
2.2.顯示請求所對應的handler方法信息
可以使用requestMappingInfo中的getMethodsCondition()方法獲取Controller類
從Map<RequestMappingInfo, HandlerMethod>這個Map中,使用對應的RequestMappingInfo作為key到這個map中查找對應的HandlerMethod
我們來具體看一下這個類:
HandlerMethod
/** * Encapsulates information about a handler method consisting of a {@linkplain #getMethod() method} * and a {@linkplain #getBean() bean}. Provides convenient access to method parameters, * method return value, method annotations. * * <p>The class may be created with a bean instance or with a bean name (e.g. lazy-init bean, * prototype bean). Use {@link #createWithResolvedBean()} to obtain a {@link HandlerMethod} * instance with a bean instance resolved through the associated {@link BeanFactory}. * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 */
一個包含了getMethod和getBean的類.
里邊有用的方法:
getMethodParameters() 獲得參數列表
通過這個方法,可以獲得這個方法所對應的參數信息.該方法返回的是:MethodParameter[]
2.3.顯示handler方法的參數信息
遍歷2.2中說到的MethodParameter[] 可以獲得具體的每一個參數的信息
使用其中的方法:
getParameterName(); 參數名
getParameterType(); 參數類型
getParameterAnnotations(); 參數注解
一切似乎就這樣美妙的完成了.但是,學習過java class 規范的同學應該記得,javac在編譯的時候,會抹去參數名稱信息;
那么,getParameterName()獲取到的會是什么呢?
看注釋,果然如我所料:
注意returns描述的括號中的內容.
有可能是null,如果參數名稱沒有在class文件中,或者在開始時沒有設置ParameterNameDiscoverer.
查看javac -help
輸出如下:
果斷加上-g參數再進行一次編譯
在maven中這個參數應該加在org.apache.maven.plugins插件配置中
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerArguments> <!-- show all debug info --> <g /> </compilerArguments> </configuration> </plugin>
在complierArguments中添加一個<g/>
那么,第一個問題解決了,參數名稱信息已經包含在了class文件中了.
第二個問題,怎么在開始的時候指定ParameterNameDiscoverer?
查遍google stackoverflow無果...
spring doc也翻了翻,沒找到,無奈,只能debug.查調用方.
思路如下,在接受參數的時候,如果不加@RequestParam注解,spring也能實現參數注入.一頓折騰.發現spring直接在spring-core中使用了asm的代碼來操作字節碼.最終完成了讀取class文件中對應方法的的參數名
而初始化ParameterNameDiscoverer的代碼在org.springframework.web.method.support.InvocableHandlerMethod類中找到了使用的方式.
直接聲明了一個本地變量,使用LocalVariableTableParameterNameDiscoverer,實現類;然后調用initParameterNameDiscovery方法完成了初始化.
仔細看過LocalVariableTableParameterNameDiscoverer里邊有很多虛擬機內緩存的實踐,有大量的map結構用於存儲數據,進而減少字節碼操作.
鑒於這里只要springmvc容器啟動,就不會出現class文件變動的情況,所以在本地變量中加上了static.
至此,就完成了顯示所有@RequestMapping的信息的工作.
2.4.擴展功能
但是,由於只知道了對應的handlerMethod的參數,類,請求方式等信息,還是會出現可能的歧義,不夠明確的指出接口的作用和意義.
所以定義了一個注解
@Inherited @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WaltzDocument { /** * 注釋內容,推薦使用html標簽進行編輯<br> * 2015年5月7日:下午4:27:30<br> * <br> * * @return */ String info(); /** * 返回給頁面的參數,推薦使用html標簽進行編輯 <br> * 2015年5月16日:下午4:05:32<br> * <br> * * @return */ String[] params() default ""; /** * 接口提供者 <br> * 2015年5月7日:下午4:27:43<br> * <br> * * @return */ String author(); /** * 接口狀態 <br> * 2015年5月7日:下午4:34:06<br> * <br> * * @return * <pre> * </pre> */ Status status(); public static enum Status { /** * 假接口 */ fake, /** * 開發 */ developing, /** * 開發完成 */ done, ; } }
在掃描對應的handlerMethod的同時,獲取對應的注解:
handlerMethod.getMethodAnnotation(WaltzDocument.class);
之后把這個注解中對應的參數一同設置到Item中.方便顯示.
同時還結合了javadoc的功能,可以直接跳轉到具體方法的javadoc頁面查看具體說明文檔.
頁面上,結合一下Bootstrap和datatables就可以完成分頁\篩選\搜索\排序的功能,進一步方便使用.
最終效果:
3.思考
3.1.spring中"虛擬機內"緩存的使用
在spring中,有很多的"虛擬機內換存",所謂虛擬機內緩存,利用虛擬機內的內存空間來實現一些變量的存儲,當調用查詢方法的時候,先使用查詢的key在對應的緩存map中進行查找,這樣可以大大降低底層IO操作的次數和頻度.像本文中提到的LocalVariableTableParameterNameDiscoverer類,下層的獲取方法參數名的方式是使用asm處理字節碼.使用ClassReader和LocalVariableTableParameterNameDiscoverer提供的內部類ParameterNameDiscoveringVisitor來實現字節碼操作.
數據要求不可變:這種虛擬機內緩存,對數據也是有一定的要求的.要數據的key不可變.試想,調用put方法是的hash值是A0,而想要get時hash值卻變成了A1,這樣就不能正確的獲取數據,反而會造成緩存map越來越大,map的命中率越來越低和不斷的擴容.最終導致的是大量無用內存占用和性能下降,最差情況會到導致OOM.
緩存對象大小要求有限:這種虛擬機內緩存只是用於數量有限(放置於map中的對象小於內存大小要求)的情況,試想,如果是一個無限大小的數據集合要做緩存,那最終的結構就是OOM了.否則要考慮使用一些軟連接,虛連接形式的對象聲明來避免這個問題了.
3.2.java參數抹去
jvm在執行方法的時候,實際上是不需要知道參數的名稱的,jvm關心的只是參數的類型.所以,javac抹去了所有的參數信息,替換成paramOfString0....
測試代碼如下:
public class Test { public static void main( String[] mainArgs) { System.out.println("beenoisy"); } public static void test( int thisIsAIntArg, String thisIsAObjectArg) { System.out.println("Test.test()"); } }
javac Test.java編譯出來的字節碼:
而同樣的代碼,添加了-g參數,就會帶上參數名稱信息,相信這是對class文件壓縮的一種體現,但是卻某種程度上的削弱了jvm運行時的一些功能.
同樣,java對集合類型的泛型也是采取抹去處理的.在thinking in java中也說過,java的泛型不是真正的泛型.估計也是類似的考量.
3.3.怎樣提高前后端開發效率
盡量減少無用功.
代碼是最好的文檔,然文檔跟着代碼改變.
一體化的文檔體系\bug追蹤體系\需求分派體系對於效率提升十分重要.
當然,這個小工具還是開發開始階段緊急弄出來的,很多地方沒有很好的遵循規范,后期如果有時間,考慮做成一個通用工具來集成到spring中.
最后,附上完整代碼:
Controller:
1 import java.util.Arrays; 2 import java.util.Collections; 3 import java.util.List; 4 import java.util.Map; 5 import java.util.Set; 6 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.core.LocalVariableTableParameterNameDiscoverer; 9 import org.springframework.core.MethodParameter; 10 import org.springframework.stereotype.Controller; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 import org.springframework.web.method.HandlerMethod; 13 import org.springframework.web.servlet.ModelAndView; 14 import org.springframework.web.servlet.mvc.method.RequestMappingInfo; 15 import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 16 17 import com.google.common.collect.Lists; 18 import com.renren.toro.waltz.common.annotation.WaltzDocument; 19 import com.renren.toro.waltz.common.annotation.WaltzDocument.Status; 20 import com.renren.toro.waltz.common.interceptor.performance.CountTime; 21 import com.renren.toro.waltz.dev.controller.support.RequestMethodItem; 22 import com.renren.toro.waltz.dev.controller.support.RequestMethodParameter; 23 24 /** 25 * 顯示所有請求 <br> 26 * 2015年5月6日:下午5:03:51 27 * 28 * @author Keen <br> 29 */ 30 @Controller 31 @RequestMapping("dev/param") 32 public class ParamController { 33 @Autowired 34 private RequestMappingHandlerMapping requestMappingHandlerMapping; 35 36 private static LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); 37 38 @RequestMapping("") 39 @WaltzDocument(info = "參數顯示頁面", author = "ziyi.wang", status = Status.done) 40 @CountTime 41 public ModelAndView exe() { 42 // items 43 List<RequestMethodItem> items = Lists.newArrayList(); 44 45 // request methods 46 Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods(); 47 Set<RequestMappingInfo> keySet = handlerMethods.keySet(); 48 49 for (RequestMappingInfo requestMappingInfo : keySet) { 50 // 請求路徑 51 String path = requestMappingInfo.getPatternsCondition().toString(); 52 53 // 請求方法 54 String requestMethod = requestMappingInfo.getMethodsCondition().toString(); 55 56 // Controller的處理方法 57 HandlerMethod handlerMethod = handlerMethods.get(requestMappingInfo); 58 59 // 參數 60 MethodParameter[] methodParameters = handlerMethod.getMethodParameters(); 61 62 // 返回header類型 63 String responseType = requestMappingInfo.getProducesCondition().toString(); 64 65 List<RequestMethodParameter> parameters = Lists.newArrayListWithExpectedSize(methodParameters.length); 66 for (MethodParameter methodParameter : methodParameters) { 67 // 參數名稱 68 // 如果沒有discover參數會是null.參考LocalVariableTableParameterNameDiscoverer 69 methodParameter.initParameterNameDiscovery(discoverer); 70 String parameterName = methodParameter.getParameterName(); 71 72 // 參數類型 73 Class<?> parameterType = methodParameter.getParameterType(); 74 75 // 參數注解 76 Object[] parameterAnnotations = methodParameter.getParameterAnnotations(); 77 78 // 注解 79 String annoation = Arrays.toString(parameterAnnotations); 80 81 RequestMethodParameter parameter = new RequestMethodParameter(); 82 parameter.setAnnoation(annoation); 83 parameter.setName(parameterName); 84 parameter.setType(parameterType.toString()); 85 parameters.add(parameter); 86 } 87 88 WaltzDocument documentAnnotation = handlerMethod.getMethodAnnotation(WaltzDocument.class); 89 90 RequestMethodItem item = new RequestMethodItem(); 91 92 item.setPath(path); 93 item.setRequestMethod(requestMethod); 94 item.setParameters(parameters); 95 item.setMethod(handlerMethod.toString().replace(" ", "<br>")); 96 item.setDocument(documentAnnotation); 97 item.setResponseType(responseType); 98 99 items.add(item); 100 } 101 102 Collections.sort(items); 103 104 ModelAndView mav = new ModelAndView("dev/param"); 105 mav.addObject("items", items); 106 return mav; 107 } 108 }
POJO:
1 import java.util.List; 2 3 import com.renren.toro.waltz.common.annotation.WaltzDocument; 4 5 /** 6 * 條目 <br> 7 * 2015年5月7日:下午3:26:15 8 * 9 * @author Keen <br> 10 */ 11 public class RequestMethodItem implements Comparable<RequestMethodItem> { 12 private String path; 13 private String requestMethod; 14 private String method; 15 private String responseType; 16 private List<RequestMethodParameter> parameters; 17 private WaltzDocument document; 18 19 public String getPath() { 20 return path.replace("[", "").replace("]", ""); 21 } 22 23 public void setPath( 24 String path) { 25 this.path = path; 26 } 27 28 public String getRequestMethod() { 29 return requestMethod; 30 } 31 32 public void setRequestMethod( 33 String requestMethod) { 34 this.requestMethod = requestMethod; 35 } 36 37 public String getMethod() { 38 String a = getA(); 39 return method + a; 40 } 41 42 private String getA() { 43 String methodName = method.split("<br>")[2]; 44 45 String classPath = methodName.split("\\(")[0]; 46 47 String[] packageSplit = classPath.split("\\."); 48 StringBuilder sb = new StringBuilder(); 49 for (int i = 0; i < packageSplit.length; i++) { 50 sb.append(packageSplit[i]); 51 if (i < packageSplit.length - 1) { 52 if (i == packageSplit.length - 2) { 53 sb.append(".html#"); 54 } else { 55 sb.append("/"); 56 } 57 } 58 } 59 60 sb.append("("); 61 String methodPath = methodName.split("\\(")[1]; 62 sb.append(methodPath.replace(",", ", ")); 63 64 String href = sb.toString(); 65 String a = "<br>\n<a href='http://svn.d.xiaonei.com/toro/waltz/renren-toro-waltz-web/trunk/doc/" + href 66 + "' target='_blank'>詳細接口文檔</a>"; 67 return a; 68 } 69 70 public void setMethod( 71 String method) { 72 this.method = method; 73 } 74 75 public List<RequestMethodParameter> getParameters() { 76 return parameters; 77 } 78 79 public void setParameters( 80 List<RequestMethodParameter> parameters) { 81 this.parameters = parameters; 82 } 83 84 public WaltzDocument getDocument() { 85 return document; 86 } 87 88 public void setDocument( 89 WaltzDocument document) { 90 this.document = document; 91 } 92 93 public String getResponseType() { 94 return responseType; 95 } 96 97 public void setResponseType( 98 String responseType) { 99 this.responseType = responseType; 100 } 101 102 public String getResponseParams() { 103 WaltzDocument document = this.getDocument(); 104 if (document == null) { 105 return ""; 106 } 107 String[] params = document.params(); 108 StringBuilder sb = new StringBuilder(); 109 for (int i = 0; i < params.length; i++) { 110 sb.append(params[i]).append("<hr>\n"); 111 } 112 113 return sb.toString(); 114 } 115 116 @Override 117 public String toString() { 118 return "Item [path=" + path + ", requestMethod=" + requestMethod + ", method=" + method + ", responseType=" 119 + responseType + ", parameters=" + parameters + ", document=" + document + "]"; 120 } 121 122 @Override 123 public int compareTo( 124 RequestMethodItem o) { 125 return this.getPath().compareTo(o.getPath()); 126 } 127 }
1 /** 2 * 參數 <br> 3 * 2015年5月7日:下午3:26:31 4 * 5 * @author Keen <br> 6 */ 7 public class RequestMethodParameter { 8 private String name; 9 private String annoation; 10 private String type; 11 12 public String getName() { 13 return name; 14 } 15 16 public void setName( 17 String name) { 18 this.name = name; 19 } 20 21 public String getAnnoation() { 22 return annoation; 23 } 24 25 public void setAnnoation( 26 String annoation) { 27 this.annoation = annoation; 28 } 29 30 public String getType() { 31 return type; 32 } 33 34 public void setType( 35 String type) { 36 this.type = type; 37 } 38 39 @Override 40 public String toString() { 41 return "Parameter [name=" + name + ", annoation=" + annoation + ", type=" + type + "]"; 42 } 43 }
jsp
1 <%@ page language="java" pageEncoding="UTF-8"%> 2 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 3 <%@ page import="java.util.Arrays"%> 4 <html> 5 <head> 6 <title>Waltz 接口查看頁</title> 7 8 <!-- 新 Bootstrap 核心 CSS 文件 --> 9 <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css"> 10 11 <!-- 可選的Bootstrap主題文件(一般不用引入) --> 12 <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap-theme.min.css"> 13 14 <!-- jQuery文件。務必在bootstrap.min.js 之前引入 --> 15 <script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script> 16 17 <!-- 最新的 Bootstrap 核心 JavaScript 文件 --> 18 <script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js"></script> 19 20 21 <!-- datatables --> 22 <script type="text/javascript" charset="utf8" src="http://cdn.datatables.net/1.10.7/js/jquery.dataTables.min.js"></script> 23 <script type="text/javascript" charset="utf8" src="http://cdn.datatables.net/plug-ins/1.10.7/integration/bootstrap/3/dataTables.bootstrap.js"></script> 24 25 26 </head> 27 <body> 28 <table id="paramTable" class="table table-striped table-hover table-bordered table-condensed display"> 29 <thead> 30 <tr> 31 <th>No.</th> 32 <th>docment</th> 33 <th>author</th> 34 <th>status</th> 35 <th>path</th> 36 <th>requestMethod</th> 37 <th>responseType</th> 38 <th>responseParams</th> 39 <th>controllerMethod</th> 40 <th>parameters</th> 41 </tr> 42 43 </thead> 44 <tbody> 45 <c:forEach items="${items}" var="i" varStatus="x"> 46 <tr> 47 <td>${x.count}</td> 48 <td>${i.document.info()}</td> 49 <td>${i.document.author()}</td> 50 <td>${i.document.status()}</td> 51 <td>${i.path}<br><a href='http://xxx.com${i.path}' target='_blank'>跳轉查看</a></td> 52 <td>${i.requestMethod}</td> 53 <td>${i.responseType}</td> 54 <td>${i.responseParams}</td> 55 <td>${i.method}</td> 56 <td> 57 <c:if test="${i.parameters.size() > 0 }"> 58 <table class="table table-striped table-hover table-bordered table-condensed"> 59 <thead> 60 <tr> 61 <th>parameterName</th> 62 <th>parameterType</th> 63 <th>parameterAnnotations</th> 64 </tr> 65 </thead> 66 <tbody> 67 <c:forEach items="${i.parameters}" var="p"> 68 <tr> 69 <td>${p.name}</td> 70 <td>${p.type}</td> 71 <td>${p.annoation}</td> 72 </tr> 73 </c:forEach> 74 </tbody> 75 </table> 76 </c:if> 77 </td> 78 </tr> 79 </c:forEach> 80 </tbody> 81 <tfoot> 82 <tr> 83 <th>No.</th> 84 <th>docment</th> 85 <th>author</th> 86 <th>status</th> 87 <th>path</th> 88 <th>requestMethod</th> 89 <th>responseType</th> 90 <th>responseParams</th> 91 <th>controllerMethod</th> 92 <th>parameters</th> 93 </tr> 94 </tfoot> 95 </table> 96 97 <script type="text/javascript"> 98 $(document).ready(function() { 99 // Setup - add a text input to each footer cell 100 $('#paramTable tfoot th').each( function () { 101 var title = $('#paramTable thead th').eq( $(this).index() ).text(); 102 $(this).html( '<input type="text" placeholder="Search '+title+'" />' ); 103 }); 104 105 // DataTable 106 var table = $('#paramTable').DataTable({ 107 paging: false 108 }); 109 110 // Apply the search 111 table.columns().eq(0).each(function(colIdx){ 112 $('input', table.column(colIdx).footer()).on('keyup change',function () { 113 table.column( colIdx ).search( this.value ).draw(); 114 }); 115 }); 116 }); 117 </script> 118 119 </body> 120 </html>