openfire插件开发(plugin sevlet)入门
来源:互联网 发布:sqlserver date tochar 编辑:程序博客网 时间:2024/05/24 08:33
近来几天为了写一个openfire的小插件可谓脑袋都大了。。。自己又是初学小菜鸟一个,学习过程算是非常的磕磕绊绊。这里记录下来仅供后来的同学参考,也少走些弯路。勤能补拙,期望大家多多支持,共同学习,共同进步。
1.plugin插件开发
1.1简单插件入门(简单plugin结构)
在结构方面我最初也是很头疼,给那么多图反而更容易把人搞混淆。我们还是用文字加配图说明吧。
新建一个 java project ,这里命名为 test_plugin。(new -> java project -> finish)然后把src删除或重命名为 src/plugins/test/src/java (即与openfire原生插件保持一致)。若是删除src的话,就新建 Source Folder命名为 src/plugins/test/src/java (new -> Source Folder )。然后项目目录下就会自动生成对应的 src文件夹及层级结构。
同样,在工程项目中新建文件夹(new ->folder)命名 build ,lib .其中build下边新建 build.properties 和 build.xml文件,用以ant编译生成插件。llib文件存放openfire等jar库。这里将openfir.jar + sevlet.jar拷贝至lib目录并add to buid path.
(servlet其实可以不用,仅导入openfire.jar即可)
1.2 build文件
build文件用于编译和打包,很多像我这样的小白基本不会用代码打包编译。网上有推荐利用源码包里边的build来编译打包,这也是一种方法,这里我使用的是jooho大大写好的build文件。我稍微做了些修改。贴上两个文件源码
uild.properties
tomcat.home=D:/Program Files/tomcat-5.0.28# If you want to build a plugin, please set the value to the plugin name.# Note:The plugin name must is the same as the plugins folder.plugin.name=test(tomcat_home是你自己的tomcat安装/解压路径,plugin name是插件名)
build.xml
<project name="Webapp Precompilation" default="openfire-plugins" basedir="."><!-- 全局变量设置 --><property file="build.properties" /><!-- 插件源码位置 --><property name="plugin.path" value="../src/plugins/${plugin.name}/src" /><!-- web应用输出目录 --><property name="webapp.output.path" value="../src/plugins/${plugin.name}/bin" /><!-- java servlet相关文件编译jar存放位置 --><property name="java.jar.dir" value="${webapp.output.path}/java-dist"/><!-- jsp servlet编译后jar存放位置 --><property name="jsp.jar.dir" value="${webapp.output.path}/jsp-dist/lib"/><!-- 定义java servlet编译打包的Jar包名称 --><property name="java.jar" value="${java.jar.dir}/plugin-${plugin.name}.jar"/><!-- 定义jsp servlet编译打包的Jar包名称 --><property name="jsp.jar" value="${jsp.jar.dir}/plugin-${plugin.name}-jsp.jar"/><!-- jsp servlet配置到web.xml中 --><property name="plugin.web.xml" value="${webapp.output.path}/jsp-dist/web.xml"/><!-- 编译jsp 并生成相关jar、xml文件 --><target name="jspc"><taskdef classname="org.apache.jasper.JspC" name="jasper2"><classpath id="jspc.classpath"><pathelement location="${java.home}/../lib/tools.jar" /><fileset dir="${tomcat.home}/bin"><include name="*.jar" /></fileset><fileset dir="${tomcat.home}/server/lib"><include name="*.jar" /></fileset><fileset dir="${tomcat.home}/common/lib"><include name="*.jar" /></fileset><!-- <fileset dir="D:/Workspace/openfire/build/lib"> <include name="**/*.jar" /> </fileset--></classpath></taskdef><!-- 编译jsp -> servlet class --><jasper2 javaEncoding="UTF-8" validateXml="false" uriroot="${plugin.path}/web" outputDir="${webapp.output.path}/jsp-dist/src" package="com.qiao.univer.blog.plugin.${plugin.name}" /><!-- 编译后的servlet class 配置到web.xml文件中 --><jasper2 validateXml="false" uriroot="${plugin.path}/web" outputDir="${webapp.output.path}/jsp-dist/src" package="com.qiao.univer.blog.plugin.${plugin.name}" webXml="${plugin.web.xml}"/></target><!-- 编译jsp 并将其打jar包 --><target name="compile"><mkdir dir="${webapp.output.path}/jsp-dist/classes" /><mkdir dir="${webapp.output.path}/jsp-dist/lib" /><mkdir dir="${webapp.output.path}/jsp-dist/src" /><javac destdir="${webapp.output.path}/jsp-dist/classes" optimize="off" encoding="UTF-8" debug="on" failonerror="false" srcdir="${webapp.output.path}/jsp-dist/src" excludes="**/*.smap"><classpath><pathelement location="${webapp.output.path}/jsp-dist/classes" /><fileset dir="${webapp.output.path}/jsp-dist/lib"><include name="*.jar" /></fileset><pathelement location="${tomcat.home}/common/classes" /><fileset dir="${tomcat.home}/common/lib"><include name="*.jar" /></fileset><pathelement location="${tomcat.home}/shared/classes" /><fileset dir="${tomcat.home}/shared/lib"><include name="*.jar" /></fileset><fileset dir="${tomcat.home}/bin"><include name="*.jar" /></fileset></classpath><include name="**" /><exclude name="tags/**" /></javac><jar jarfile="${jsp.jar}" basedir="${webapp.output.path}/jsp-dist/classes" /></target><!-- 将java servlet打包成jar --><target name="java-jar"><mkdir dir="${java.jar.dir}"/><jar jarfile="${java.jar}"><fileset dir="../bin" includes="**/*.class"/></jar></target><!-- 生成可部署的插件包 --><target name="plug-jar"><!-- 插件插件包相关lib、 web目录 --><mkdir dir="${webapp.output.path}/${plugin.name}/lib"/><mkdir dir="${webapp.output.path}/${plugin.name}/web/WEB-INF"/><!-- 复制jsp servlet的jar和java servlet的相关jar包到插件包的lib目录下 --><copy file="${java.jar}" todir="${webapp.output.path}/${plugin.name}/lib"/><copy file="${jsp.jar}" todir="${webapp.output.path}/${plugin.name}/lib"/><!-- 将相关的图片、帮助文档、修改日志等文件复制到插件目录下 --><copy todir="${webapp.output.path}/${plugin.name}"><fileset dir="${plugin.path}" includes="*.*"/></copy><copy todir="${webapp.output.path}/${plugin.name}/web"><fileset dir="${plugin.path}/web"><include name="*"/><include name="**/*.*"/><exclude name="**/*.xml"/><exclude name="**/*.jsp"/></fileset></copy><!-- jsp servlet的web复制到插件目录下 --><copy file="${plugin.web.xml}" todir="${webapp.output.path}/${plugin.name}/web/WEB-INF"/><copy todir="${webapp.output.path}/${plugin.name}/web"><fileset dir="${plugin.path}/web" includes="**/*.xml"/></copy><!-- 将国际化相关资源文件复制到插件目录下 <copy file="${webapp.output.path}/bin/i18n" todir="${webapp.output.path}/${plugin.name}"/> --><!-- 产生可部署插件包 --><jar jarfile="${webapp.output.path}/${plugin.name}.jar"><fileset dir="${webapp.output.path}/${plugin.name}" includes="**/**"/></jar></target><!-- 生成没有Web资源的可部署插件包 --><target name="java-plug-jar"><!-- 插件插件包相关lib、 web目录 --><mkdir dir="${webapp.output.path}/${plugin.name}/lib"/><!-- 复制java servlet的相关jar包到插件包的lib目录下 --><copy file="${java.jar}" todir="${webapp.output.path}/${plugin.name}/lib"/><!-- 将相关的图片、帮助文档、修改日志等文件复制到插件目录下 --><copy todir="${webapp.output.path}/${plugin.name}"><fileset dir="${plugin.path}" includes="*.*"/></copy><!-- 产生可部署插件包 --><jar jarfile="${webapp.output.path}/${plugin.name}.jar"><fileset dir="${webapp.output.path}/${plugin.name}" includes="**/**"/></jar></target><!-- 清理生成的文件 --><target name="clean"><delete file="${webapp.output.path}/${plugin.name}.jar"/><delete dir="${webapp.output.path}/${plugin.name}"/><delete dir="${webapp.output.path}/jsp-dist"/><delete dir="${webapp.output.path}/java-dist"/></target><target name="all" depends="clean,jspc,compile"/><target name="openfire-plugin" depends="jspc,java-jar"/><target name="openfire-plugins" depends="all,java-jar,plug-jar"/><target name="openfire-plugin-java" depends="clean,java-jar,java-plug-jar"/></project>(这里有两点请注意修改
1.3 其他文件结构
在source folder下新建包(例 com.qiao.test.plugin),此包即为上边要修改的地方。
新建TestPlugin 类并实现Plugin接口 中的 init 和 destroy方法:
TestPlugin .java
import java.io.File;import org.jivesoftware.openfire.XMPPServer;import org.jivesoftware.openfire.container.Plugin;import org.jivesoftware.openfire.container.PluginManager;public class TestPlugin implements Plugin {private XMPPServer server;@Overridepublic void initializePlugin(PluginManager manager, File pluginDirectory) {server = XMPPServer.getInstance();System.out.println("qiao init Plugin!");System.out.println(server.getServerInfo());}@Overridepublic void destroyPlugin() {System.out.println("qiao destroy Plugin!");}}
changelog.htmllogo_small.giflogo_large.gif readme.html 和plugin.xml文件,其中前面是个可以从openfire源码中拷贝
changelog.html是修改日志;logo_small.gif是插件图标;plugin.xml是我们配置插件的文件,这个很重要。
配置 plugin.xml
<?xml version="1.0" encoding="UTF-8"?><plugin><!-- Main plugin class 这里是最重要滴,就是你的插件的全路径--><class>com.qiao.test.plugin.TestPlugin</class> <!-- Plugin meta-data --><name>TestPlugin</name><description>This is testplugin.</description><author>qiao</author> <version>1.0</version><date>10/05/20134</date><url>http://localhost:9090/openfire/plugins.jsp</url><minServerVersion>3.4.1</minServerVersion><licenseType>gpl</licenseType> <adminconsole> </adminconsole></plugin>注意上面的class的配置,那个配置是最为重要的,配置的是插件的全路径;name是插件的名称,安装后的插件名称;author是插件作者;lincenseType是协议;adminconsole是配置插件关联的页面的,这里用不上
至此,整个框架大概就是这样:
编写ant命令,打可部署jar包。如果你不懂ant命令也没关系,你总知道java的基本常用的dos命令。只不过ant就是将dos转换成一个可重复多次调用的命令行。我们这里就是用上边的build.xml利用ant来编译和打包。
以下是jooho大大的话,小白可以略过。
(注意:这里我没有编写编译java代码到class的步骤,我是直接使用MyEclipse自动编译的bin/class的。如果你没有用MyEclipse或Eclipse,那么你需要将src中的Java代码编译class。
这里需要配置tomcat的目录,我这里是5.0.28的版本。我用tomcat6有些问题,这里主要是用tomcat中的lib库,帮助我们编译jsp。还需要配置你当前工程的所在目录,也就是工程在Eclipse中的目录位置。最后你需要配置插件的名称和插件在工程中的所在目录,这个是在打包的时候,需要将其他的html、image、xml等资源导入的jar内。
因为这里的插件是不带jsp的,所以我们执行clean、java-jar、java-plugin-jar。也就是openfire-plugin-java这个命令即可。执行命令后,你可以看到工作空间的工程目录下多了目录和文件。)
1.4 插件打包部署
我们右键点击build.xml选择 run as -> ant build..
选择openfire-plugin-java
等待打包完成
打包完成后刷新工程项目,会看到多出来个bin文件夹
打包成功的test.jar部署到openfire服务器,部署有两种方法:
1.直接拷贝至openfire安装目录下的plugins的文件夹内,自启动后它会自动解压
2.登录openfire管理界面,选择插件,上传
在openfire启动的情况下,访问http://localhost:9090/plugin-admin.jsp页面,点击页面下方的upload plugin完成插件上传操作。
插件按照成功后,访问http://localhost:9090/plugin-admin.jsp页面你就可以看到安装好的插件了。
测试插件。启动openfire,就可以看到插件的运行结果如下:
2.含有servlet和JSP的插件开发
2.1 serlet开发及配置
新建一个TestServlet.java 继承HttpServlet
TestServlet.java
import java.io.IOException;import java.io.PrintWriter;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.jivesoftware.admin.AuthCheckFilter;public class TestServlet extends HttpServlet {private static final long serialVersionUID = 1L;private static final String SERVICE_NAME = "test/*";@Overridepublic void init() throws ServletException {super.init();AuthCheckFilter.addExclude(SERVICE_NAME);}@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/plain"); PrintWriter out = response.getWriter(); System.out.println("请求TestServlet GET Method"); out.print("请求TestServlet GET Method"); out.flush(); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/plain"); PrintWriter out = response.getWriter(); System.out.println("请求TestServlet GET Method"); out.print("请求TestServlet POST Method"); out.flush(); }@Overridepublic void destroy() {super.destroy();AuthCheckFilter.removeExclude(SERVICE_NAME);}}
其中 AuthCheckFilter.addExclude(SERVICE_NAME); 是为了避免登录验证,如果没有这一段代码。每次访问TestServelt都会出现跳转到登录界面的情况。
web-custom.xml
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"><web-app> <servlet> <servlet-class>com.qiao.test.plugin.servlet.TestServlet</servlet-class> <servlet-name>TestServlet</servlet-name> </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/servlet</url-pattern> </servlet-mapping></web-app>
2.2 Jsp开发及配置
test-demo.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html> <head> <title>test service: 你好openfire</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="pageID" content="test-service"/> </head> <body> <h3>test server jsp!! <a href="/plugins/test/servlet">TestServlet</a></h3> <div class="jive-contentBoxHeader">jive-contentBoxHeader</div> <div class="jive-contentBox">jive-contentBox</div> <div class="jive-table"> <table cellpadding="0" cellspacing="0" border="0" width="100%"> <thead> <tr> <th> sss</th> <th nowrap>a</th> <th nowrap>b</th> </tr> </thead> <tbody> <tr> <td align="center">asdf</td> <td align="center">asdf</td> <td align="center">asdf</td> </tr> <tr class="jive-even"> <td align="center">asdf</td> <td align="center">asdf</td> <td align="center">asdf</td> </tr> <tr class="jive-odd"> <td align="center">asdf</td> <td align="center">asdf</td> <td align="center">asdf</td> </tr> </tbody> </table> </div> </body></html>
其中最重要的一点就是:<meta name="pageID" content="sample-service"/>这个pageID。这里的是固定的,后面的content对应我们plugin.xml的内容(等下看看plguin.xml的配置)。然后可以适当的看下里面table的 属性和样式,因为很多时候会在jsp中显示内容,且用table布局的。
plugin.xml
<?xml version="1.0" encoding="UTF-8"?><plugin><!-- Main plugin class 这里是最重要滴,就是你的插件的全路径--><class>com.qiao.test.plugin.TestPlugin</class> <!-- Plugin meta-data --><name>TestPlugin</name><description>This is testplugin.</description><author>qiao</author> <version>1.0</version><date>10/05/20134</date><url>http://localhost:9090/openfire/plugins.jsp</url><minServerVersion>3.4.1</minServerVersion><licenseType>gpl</licenseType> <adminconsole> <tab id="tab-server"> <sidebar id="sidebar-server-settings"> <item id="test-service" name="Test Service" url="test-demo.jsp" description="Click is trigger blog plugin" /> </sidebar> </tab> </adminconsole></plugin>
注意,这里是jsp里边的pageID
2.3 运行ant,发布插件
3.Servlet拓展问题及解决
3.1.servlet登录验证问题
那怎么单独对自己插件的url进行排除呢?接着往下看!
有两种方法:修改openfire的web.xml或使用AuthCheckFilter.addExclude方法其中一种即可
1)修改openfire的web.xml
这个web.xml出现在openfire的源码很多地方,了解正确的配置方法很重要。下面展示不同路径下的web.xml修改说明,取其一即可。
a. 修改/openfire_src/src/web/WEB-INF/web.xml,修改完后,记得还要用ant编译一次并重启才能生效。
b. 修改/openfire_src/target/openfire/plugins/admin/webapp/WEB-INF/web.xml
修改完后,直接重启即可生效,本方法同样适用于安装版的openfire
- <filter>
- <filter-name>AuthCheck</filter-name>
- <filter-class>org.jivesoftware.admin.AuthCheckFilter</filter-class>
- <init-param>
- <param-name>excludes</param-name>
- <param-value>
- popplugin/*,login.jsp,index.jsp?logout=true,setup/index.jsp,setup/setup-*,.gif,.png,error-serverdown.jsp,setup/clearspace-integration-prelogin.jsp
- </param-value>
- </init-param>
- </filter>
url的匹配规则比较复杂,有兴趣的同学可以去看AuthCheckFilter类源码,如果要测试自己的pattern对不对,可以使用org.jivesoftware.admin.AuthCheckFilterTest类执行JUnitTest试试,如自己加上一段:
- assertTrue(AuthCheckFilter.testURLPassesExclude("popplugin/sendmessage", "popplugin*"));
- assertTrue(AuthCheckFilter.testURLPassesExclude("popplugin/sendmessage", "popplugin/sendmessage"));
2)使用AuthCheckFilter.addExclude方法
调用该方法和配置web.xml的效果是完全一样的,个人建议用此方法,因为本方法绿色安全、无污染,不修改和破坏原生openfire代码。
这个方法可以在servlet的init方法或plugin的initializePlugin方法中调用。
个人建议在servlet的init方法中调用,因为servlet的init方法比plugin的initializePlugin方法更早初始化,这个问题在“插件初始化顺序”会讲到。
使用完后记得removeExclude一下,有始有终嘛。
- @Override
- public void init() throws ServletException
- {
- System.out.println("UserMgrServlet init....");
- AuthCheckFilter.addExclude("popplugin/usermgr*");
- AuthCheckFilter.addExclude("popplugin/usermgr/*");
- }
- @Override
- public void destroy()
- {
- System.out.println("UserMgrServlet destroy....");
- // Release the excluded URL
- AuthCheckFilter.removeExclude("popplugin/usermgr*");
- AuthCheckFilter.removeExclude("popplugin/usermgr/*");
- }
注意:这种方式仅限于servlet的访问,如果访问插件的jsp/html页面,页面会报NullPointException异常,解决办法见下面的说明
3.2.servlet仍然需要登录验证或者报NullPointException空指针异常
java.lang.NullPointerExceptionat org.jivesoftware.openfire.admin.decorators.main_jsp._jspService(main_jsp.java:195)at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:97)at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:547)at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:480)at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:119)at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:520)at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:227)at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:941)at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:409)at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:186)at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:875)at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)at org.eclipse.jetty.server.Dispatcher.include(Dispatcher.java:195)at com.opensymphony.module.sitemesh.filter.PageFilter.applyDecorator(PageFilter.java:156)at com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:59)at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1330)at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:478)at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:119)at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:520)at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:227)at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:941)at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:409)at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:186)at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:875)at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:250)at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:149)at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:110)at org.eclipse.jetty.server.Server.handle(Server.java:349)at org.eclipse.jetty.server.HttpConnection.handleRequest(HttpConnection.java:441)at org.eclipse.jetty.server.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:919)at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:582)at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:218)at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:51)at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:586)at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:44)at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:598)at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:533)at java.lang.Thread.run(Unknown Source)
插件的页面不使用openfire控制台的页面框架
安装版的openfire的话,修改/openfire/plugins/admin/webapp/WEB-INF/decorators.xml文件
源代码版的,要修改/openfire_src/src/web/WEB-INF/decorators.xml文件
示例:
- <excludes>
- <pattern>/plugins/test/*</pattern>
- <pattern>/setup/setup-completed.jsp*</pattern>
- <pattern>/setup/setup-ldap-server_test.jsp*</pattern>
- <pattern>/setup/setup-ldap-user_test.jsp*</pattern>
- <pattern>/setup/setup-ldap-group_test.jsp*</pattern>
- <pattern>/setup/setup-clearspace-integration_test.jsp*</pattern>
- <pattern>/setup/setup-admin-settings_test.jsp*</pattern>
- <pattern>/login.jsp*</pattern>
- <pattern>/plugin-icon.jsp*</pattern>
- <pattern>/js/jscalendar/i18n.jsp*</pattern>
- </excludes>
这样,插件popplugin目录下的所有路径的jsp页面及js,css等,都能被自由的访问了。
至此,海阔天空任鸟飞了。。
- openfire插件开发(plugin sevlet)入门
- openfire 插件开发入门
- eclips plugin插件开发入门
- eclips plugin插件开发入门
- openfire plugin 编译单个插件
- Openfire插件开发(1)
- openfire插件开发(2)
- Openfire JSP Plugin 开发笔记
- openfire 插件入门学习
- openfire插件入门学习
- jquery 插件(plugin)开发
- Cordova 插件开发(plugin)
- openfire 实现自己的插件plugin类
- openfire 实现自己的插件plugin类
- Sevlet入门
- Openfire插件开发
- Openfire插件开发
- Openfire 插件开发记录
- 数据表的创建与管理
- MFC中Combo Box 的用法总结
- 黑马程序员 (高新技术)反射技术的深入理解
- 引用返回左值
- leetcode difficulty and frequency distribution chart
- openfire插件开发(plugin sevlet)入门
- 区别dequeueReusableCellWithIdentifier 和dequeueReusableCellWithIdentifier: forIndexPath
- opencv 滤波
- POJ 1236 Network of Schools 强连通
- opengl shader 入门 超详细
- leetCode解题报告5道题(七)
- HDU 3530 单调队列
- 几道数学题
- 百鸡问题