Website Logo. Upload to /source/logo.png ; disable in /source/_includes/logo.html

舞乐 VOLER

舞动我人生

使用DWR实现消息推送

Nov 21, 2015

dwr支持如下3种模式的消息推送:

  • Polling,浏览器每隔一段时间向服务器发出请求,查看是否有更新的内容;
  • Comet,
  • Piggyback,服务器等待浏览器下一次发出请求时,将更新的内容合并到响应一起返回。

默认使用Piggyback模式,使用Polling/Comet模式需要额外的配置。

DWR简单入门介绍了基于DWR构建项目。

使用Maven构建项目,在pom.xml中添加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<repositories>
  <repository>
      <!-- Please consider setting up your own on-site repository proxy such 
          as with Nexus and pointing the url element below at that instead -->
      <id>oss-sonatype-snapshots</id>
      <name>OSS Sonatype Snapshots Repository</name>
      <url>http://oss.sonatype.org/content/repositories/snapshots</url>
      <releases>
          <enabled>false</enabled>
      </releases>
      <snapshots>
          <enabled>true</enabled>
      </snapshots>
  </repository>
</repositories>

<dependencies>
...
  <dependency>
      <groupId>org.directwebremoting</groupId>
      <artifactId>dwr</artifactId>
      <version>3.0.0-SNAPSHOT</version>
  </dependency>
  <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
  </dependency>
    <!-- 使用Tomcat需要额外添加的依赖包 -->
  <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>catalina</artifactId>
      <version>6.0.44</version>
  </dependency>
</dependencies>

使用的容器是Tomcat(与使用Jetty的配置不同),在web.xml中添加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<servlet>
  <servlet-name>dwr-invoker</servlet-name>
  <servlet-class>org.directwebremoting.server.tomcat.DwrCometProcessor</servlet-class>
  <init-param>
      <param-name>debug</param-name>
      <param-value>true</param-value>
  </init-param>
  <init-param>
      <!-- 2.0 RC3之前的参数名为pollAndCometEnabled -->
      <param-name>activeReverseAjaxEnabled</param-name>
      <param-value>true</param-value>
  </init-param>
  <init-param>
      <param-name>maxWaitAfterWrite</param-name>
      <param-value>1000</param-value>
  </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>dwr-invoker</servlet-name>
  <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

dwr.xml中的配置

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN"
    "http://getahead.org/dwr/dwr30.dtd">

<dwr>
  <allow>
      <create creator="new" javascript="messagePush">
          <param name="class" value="me.util.MessagePushUtil" />
      </create>
  </allow>
</dwr>

采用的调试方式是,打开接收页面(receiver.jsp)和发送页面(sender.jsp),在发送页面将消息发送至服务端,由服务端将消息推送至接受页面。JSP文件中使用JQuery替代了dwr提供的<script type='text/javascript' src='/dwr/util.js'></script>

接收页面(receiver.jsp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>
<%@ page isELIgnored="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="/css/easyui.css">
<link rel="stylesheet" type="text/css" href="/css/demo.css">
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript" src="/js/jquery.easyui.min.js"></script>
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/interface/messagePush.js'></script>
<title>消息推送</title>
</head>
<body>
  <h2>接受消息推送</h2>
</body>
<script>
  $(function() {
      dwr.engine.setActiveReverseAjax(true);
        dwr.engine.setNotifyServerOnPageUnload(true);
        messagePush.onPageLoad('{sessionScope.uid}');
  });

    function showMessage(autoMessage) {
      $.messager.show({
          title : "消息推送",
          msg : autoMessage,
          showType : 'slide',
          timeout : 5000
      });
  }
</script>
</html>

发送页面(sender.jsp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>
<%@ page isELIgnored="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="./css/easyui.css">
<link rel="stylesheet" type="text/css" href="./css/demo.css">
<script type="text/javascript" src="./js/jquery.js"></script>
<script type="text/javascript" src="./js/jquery.easyui.min.js"></script>
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/interface/messagePush.js'></script>
<title>消息推送</title>
</head>
<body>
  <h2>发送消息推送</h2>
  <div id="p" class="easyui-panel"
      style="width: 400px; height: 100x; padding: 10px;">
      <div>
          ID:<input id="uid" style="width: 40%; height: 25px">消息:<input
              id="msg" style="width: 40%; height: 25px"><br> <br> <a
              href="javascript:void(0)" style="width: 30%; height: 25px"
              onclick="sendMsg()">发送</a>
      </div>
  </div>
</body>
<script>
  function sendMsg() {
      messagePush.sendMessageAuto($('#uid').val(), $('#msg').val());
      $('#uid').val('');
      $('#msg').val('');
  }
</script>
</html>

me/util/MessagePushUtil.java中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package me.util;

import java.util.Collection;

import javax.servlet.ServletException;

import org.apache.log4j.Logger;
import org.directwebremoting.Browser;
import org.directwebremoting.ScriptBuffer;
import org.directwebremoting.ScriptSession;
import org.directwebremoting.ScriptSessionFilter;
import org.directwebremoting.WebContextFactory;

import me.message.MessagePushServlet;

public class MessagePushUtil {

  private static final Logger LOGGER = Logger.getLogger(MessagePushUtil.class);

  public void onPageLoad(String userID) {

      ScriptSession scriptSession = WebContextFactory.get().getScriptSession();
      // 工厂方法get()返回WebContext实例,通过WebContext获取servlet参数
      // ScriptSession与HttpSession类似
      scriptSession.setAttribute("uid", userID);

      MessagePushServlet mpServlet = new MessagePushServlet();
      try {
          mpServlet.init();
          LOGGER.info(String.format("消息推送初始化成功,uid:%s", userID));
      } catch (ServletException e) {
          LOGGER.error(String.format("消息推送初始化错误,uid:%s", userID));
      }

  }

  /**
  * 根据userID向指定用户推送消息
  * 
  * @param userID
  * @param autoMessage
  */
  public void sendMessageAuto(String userID, String autoMessage) {

      final String uid = userID;
      final String message = autoMessage;

      Browser.withAllSessionsFiltered(new ScriptSessionFilter() {
          // 实现过滤器中的match()方法
          public boolean match(ScriptSession session) {
              if (session.getAttribute("uid") == null)
                  return false;
              else
                  return (session.getAttribute("uid")).equals(uid);
          }
      }, new Runnable() {

          private ScriptBuffer script = new ScriptBuffer();

          public void run() {
              // 调用JSP中定义的showMessage()方法,实现消息的前端显示
              script.appendCall("showMessage", message);
              Collection<ScriptSession> sessions = Browser.getTargetSessions();
              for (ScriptSession scriptSession : sessions) {
                  scriptSession.addScript(script);
              }
              LOGGER.info(String.format("向用户推送消息,uid:%s,message:%s", uid, message));
          }
      });
  }
}

me/message/MessagePushServlet.java中的内容,覆盖DwrServlet中的init()方法实现ScriptSession监听器,在页面中加入engine.js时,ScriptSession创建,默认由org.directwebremoting.impl.DefaultScriptSessionManager管理,当发生unload事件时,DefaultScriptSessionManager会被通知销毁ScriptSession。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package me.message;

import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.directwebremoting.Container;
import org.directwebremoting.ServerContextFactory;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.event.ScriptSessionEvent;
import org.directwebremoting.event.ScriptSessionListener;
import org.directwebremoting.extend.ScriptSessionManager;
import org.directwebremoting.servlet.DwrServlet;

public class MessagePushServlet extends DwrServlet {

  private static final long serialVersionUID = 4298890285665323894L;
  private static final Logger LOGGER = Logger.getLogger(MessagePushServlet.class);

  @Override
  public void init() throws ServletException {

      Container container = ServerContextFactory.get().getContainer();
      // 工厂方法get()返回ServerContext实例
      ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
      ScriptSessionListener listener = new ScriptSessionListener() {
          public void sessionCreated(ScriptSessionEvent ev) {
              HttpSession session = WebContextFactory.get().getSession();

              String userID = (String) session.getAttribute("uid");
              LOGGER.info("a ScriptSession is created");
              ev.getSession().setAttribute("uid", userID);
          }

          public void sessionDestroyed(ScriptSessionEvent ev) {
              LOGGER.info("a ScriptSession is distroyed");
          }
      };
      manager.addScriptSessionListener(listener);
  }
}

运行前还要修改Tomcat下的server.xml中的配置

1
2
3
4
5
6
<!--
<Connector connectionTimeout="20000" port="8080" 
protocol="HTTP/1.1" redirectPort="8443"/>
-->
<Connector URIEncoding="UTF-8" connectionTimeout="20000" port="8080"
protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/>

运行项目,打开http://localhost:8080/receiver.jsp,页面加载时,调用

messagePush.onPageLoad(‘<%=session.getAttribute(“uid”)%>’);

调用服务端的me.util.MessagePushUtil.onPageLoad(String userID)方法,初始化成功(可以使用字符串,如"123456789",替换'<%=session.getAttribute(“uid”)%>‘)。打开http://localhost:8080/sender.jsp,填写刚才的uid(123456789)和消息内容,点击发送,调用

messagePush.sendMessageAuto($(‘#uid’).val(), $(‘#msg’).val());

调用服务端的me.util.MessagePushUtil.sendMessageAuto(String userID, String autoMessage)方法,调用receiver.jsp的showMessage(autoMessage)方法,可以在接收页面看到来自服务端推送的消息。

页面预览如下,

发送页面 接收页面

除此之外,在log4j.properties中添加类似的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
log4j.rootCategory=INFO, console
log4j.category.org.directwebremoting.log=INFO, dwr, console

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%5p] %d{yyyy-MM-dd HH:mm:ss} %l - %m%n

log4j.appender.dwr=org.apache.log4j.RollingFileAppender
log4j.appender.dwr.File=../logs/dwr.log
log4j.appender.dwr.MaxFileSize=100KB
log4j.appender.dwr.MaxBackupIndex=1
log4j.appender.dwr.layout=org.apache.log4j.PatternLayout
log4j.appender.dwr.layout.ConversionPattern=[%5p] %d{yyyy-MM-dd HH:mm:ss} %l - %m%n
参考资料

DWR3实现服务器端向客户端精确推送消息

DWR简单入门

Reverse Ajax

Logging

对Struts文档的注释 (三)

May 28, 2015

本文是依照参考文档学习时的总结,参考文档原文:Using Struts 2 TagsCoding Struts 2 Actions

Struts 2 url Tag

前面使用的servlet-api-2.5,而2.5中默认不支持EL,所以需要在index.jsp中添加,

1
<%@ page isELIgnored="false"%>

为了方便与JSTL标签对比,在pom.xml中添加,

1
2
3
4
5
6
7
8
9
10
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>
<dependency>
  <groupId>taglibs</groupId>
  <artifactId>standard</artifactId>
  <version>1.1.2</version>
</dependency>

.jsp中taglib指令,

1
2
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
1
2
3
4
5
6
7
<s:url action="hello" var="helloLink">
  <s:param name="userName">Bruce Phillips</s:param>
</s:url>

<c:url value="hello.action" var="hello">
  <c:param name="userName" value="Bruce Phillips" />
</c:url>

两者都会对URL编码,访问链接${helloLink}${hello}均为.../hello.action?userName=Bruce+Phillips

Struts 2 property tag
1
<s:property value="messageStore" />

如上使用property标签,返回MessageStore对象,默认调用其toString()方法,可以覆盖Object的toString()方法。

Processing Form Input In The Action Class
1
2
3
4
<s:form action="hello">
    <s:textfield name="userName" label="Your name" />
    <s:submit value="Submit" />
</s:form>

form标签得到的HTML源码为

1
2
3
4
5
6
7
8
9
10
11
12
13
<form id="hello" name="hello" action="/.../hello.action" method="post">
  <table class="wwFormTable">
      <tr>
          <td class="tdLabel"><label for="hello_userName" class="label">Your name:</label></td>
          <td><input type="text" name="userName" value="" id="hello_userName"/></td>
      </tr>
      <tr>
          <td colspan="2"><div align="right">
              <input type="submit" id="hello_0" value="Submit"/>
          </div></td>
      </tr>
  </table>
</form>

在HelloWorldAction.java中添加

1
2
3
4
5
6
7
8
9
private String userName;

public String getUserName() {
    return userName;
}

public void setUserName(String userName) {
    this.userName = userName;
}

使用setUserName(String userName)方法获得userName参数,类似于Servlet中

1
String userName = request.getParameter("userName");

如果未添加会提示类似错误,

com.opensymphony.xwork2.util.logging.jdk.JdkLogger error 严重: Developer Notification (set struts.devMode to false to disable this message): Unexpected Exception caught setting ‘userName’ on ‘class me.struts.ex.helloworld.action.HelloWorldAction: Error setting expression 'userName’ with value [‘Bruce Phillips ’, ]

在HelloWorld.jsp中使用,

1
<s:property value="userName" />

可以直接获得userName参数。

Servlet3.0引入的注解

May 28, 2015

Servlet3.0开始引入annotation用于部署,

  • @WebServlet用于定义servlet,
  • @WebListener用于定义监听器(对于不同类型的监听器又有细分),
  • @WebFilter用于定义过滤器,
  • @initParams用于定义初始化参数,
  • ……

这里给出一个使用@WebServlet的例子,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.IOException;

import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/annotation.do", initParams = {
      @WebInitParam(name = "newPattern", value = "annotation"),
      @WebInitParam(name = "oldPattern", value = "web.xml") })
public class CoreServlet extends HttpServlet {
  public void doGet(HttpServletRequest request, HttpServletResponse response)
          throws IOException {

      response.setContentType("text/html");
      response.getWriter().print(
              "Using " + getServletConfig().getInitParameter("newPattern")
                      + " not "
                      + getServletConfig().getInitParameter("oldPattern"));
  }
}

访问...\annotation.do,可以看到

Using annotation not web.xml

如果同时存在web.xml和注解,web.xml中指定的内容拥有更高的优先级。

前面@WebServlet指定了me.annotation.web.CoreServlet/annotation.do的映射关系,相当于在web.xml中添加,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<servlet>
  <servlet-name>me.annotation.web.CoreServlet</servlet-name>
    <servlet-class>me.annotation.web.CoreServlet</servlet-class>
    <init-param>
      <param-value>newPattern</param-value>
        <param-name>annotation</param-name>
    </init-param>
    <init-param>
      <param-value>oldPattern</param-value>
        <param-name>web.xml</param-name>
    </init-param>
</servlet>

<servlet-mapping>
  <servlet-name>me.annotation.web.CoreServlet</servlet-name>
    <url-pattern>/annotation.do</url-pattern>
</servlet-mapping>

<servlet-name>值不同认定为不同的映射关系。

可以在web.xml添加

1
<metadata-complete>true</metadata-complete>

禁用注解,缺省或值为false即可以使用注解。

参考资料:

JSR 340: Java Servlet 3.1 Specification

Package javax.servlet.annotation

对Hibernate文档的注释(一)

May 26, 2015

本文是依照参考文档学习时的总结,参考文档原文:1.1. Part 1 - The first Hibernate Application

The mapping file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="me.hibernate.ex.domain">
  <!-- all persistent entity classes need a mapping to a table in the SQL database -->
  <class name="Event" table="EVENTS">
      <!-- mapping the unique identifier property to the tables primary key -->
      <id name="id" column="EVENT_ID">
          <generator class="native" />
      </id>
      <property name="date" type="timestamp" column="EVENT_DATE" />
      <!-- without the column attribute, Hibernate by default uses the property name as the column name -->
      <property name="title" />
  </class>
</hibernate-mapping>

映射文件Event.hbm.xml等同于.sql文件。

Hibernate configuration

文档中使用HSQLDB作为数据库,这里使用MySQL作为数据库,需要修改数据库连接设置,

<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost/test</property>

<!-- SQL dialect -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

同时添加依赖包mysql-connector-java-bin.jar

Startup and helpers

文档中提供的代码与最新的hibernate-core不匹配,需要修改buildSessionFactory()方法中的try块为:

1
2
3
4
5
6
7
try {
  // Create the SessionFactory from hibernate.cfg.xml
  Configuration configuration = new Configuration();
  configuration.configure();
  ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
  return configuration.buildSessionFactory(serviceRegistry);
}

[Solved] HibernateException: Access to DialectResolutionInfo cannot be null when ‘hibernate.dialect’ not set中给出了详细的说明。

Loading and storing objects

文档中提供的代码使用args参数,这里将main函数简化为,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {

  Session session = HibernateUtil.getSessionFactory().getCurrentSession();
  session.beginTransaction();
  try {
      Event theEvent = new Event();
      theEvent.setTitle("Event");
      theEvent.setDate(new Date());
      session.save(theEvent);

      session.getTransaction().commit();
  } catch (Exception e) {
      if (session.beginTransaction() != null)
          session.beginTransaction().rollback();
  } finally {
      //session.close();
  }

}

使用getCurrentSession()开始Session,在事务结束后,Hibernate会自动释放Session,如果使用session.close();会提示错误,

Session was already closed

如果使用文档中提供的代码,可以参考Eclipse里如何给main函数里的args参数赋值

现在运行EventManager.java可以看到,

Hibernate: insert into EVENTS (EVENT_DATE, title) values (?, ?)

查询本地MySQL数据库,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> use test;
Database changed
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| events         |
+----------------+

mysql> describe events;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| EVENT_ID   | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| EVENT_DATE | datetime     | YES  |     | NULL    |                |
| title      | varchar(255) | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

mysql>
Class Configuration & Interface SessionFactory(4.3)

创建SessionFactory时,Configuration实例允许应用指定即将使用的性质和映射文件,默认情况下使用hibernate.properties指定的性质。SessionFactry`是不可变的,不受Configuration变化的影响。

1
2
3
public Configuration configure() throws HibernateException{
}
// 使用hibernate.cfg.xml指定的性质和映射关系

应用一般创建单一的ConfigurationSessionFactory实例,然后实例化多个会话线程服务客户端的请求。

Interface Session(4.3)

Session是Java应用与Hibernate之间主要的运行时接口,Session的生命周期受到逻辑事务开始与结束的限制。Session的主要功能是对实体类的实例进行新建、读取和删除操作。

实例的三个阶段:

  1. transient,未关联任何Session;
  2. persistent,与唯一Session关联;
  3. detached,位于persistent之后,未关联任何Session。

实例的三个阶段

Session抛出异常时,事务必须回滚。

正则表达式笔记(一)

May 24, 2015

  • 正则表达式是大小写敏感的

Hello
Hello, world!
hello, world!

  • 空格、缩进或换行都会影响匹配结果

Hello, world!
Hello, world!
Hello,world!

  • ^和$

^who
who is who
who$
who is who

  • ^、$和\需要转义

^$
$12$ \\-\ $25$
\$
$12$ \\-\ $25$
\$$
$12$ \\-\ $25$
\\\\
$12$ \-\ $25$

  • .匹配任意一个字符

.
Regular expressions are powerful!!!
……
Regular expressions are powerful!!!

  • .同样需要转义

.
O.K.
\\.
O.K.
\\..\\.
O.K.

  • 一系列字符被[]包含

[oyu]
How do you do?
[dH].
How do you do?
[owy][yow]
How do you do?

  • [C-K]等价于[CDEFGHIJK],[C-Ka-d2-6]等价于[CDEFGHIJKabcd23456]

  • [^CDghi45]与[CDghi45]匹配方式相反,[^W-Z]与[W-Z]匹配方式相反

  • 一系列字符串被()包含,被|分隔

(on|ues|rida)
Monday Tuesday Friday
(Mon|Tues|Fri)day
Monday Tuesday Friday
..(id|esd|nd)ay
Monday Tuesday Friday

参考资料

Regular Expressions Tutorial

对Struts文档的注释 (二)

May 23, 2015

本文是依照参考文档学习时的总结,参考文档原文:Hello World Using Struts 2

Create The Action Class HelloWorldAction

在Struts 2应用中,表单中输入的数据不是被提交给新的服务页面,而是提交给Java class来处理,这些Java class称为Action

1
2
3
4
5
//com.opensymphony.xwork2.ActionSupport

public String execute() throws Exception {
  returns Action.SUCCESS
}
Create The View HelloWorld
1
2
<s:property value="messageStore.message" />
<%--The message property of the MessageStore object in the Session context.--%>

可以类比JSP中的

1
2
<jsp:useBean id="messageStore" class="me.struts.ex.helloworld.model.MessageStore" scope="session" />
<jsp:getProperty name="messageStore" property="message" />
运行过程

在index.jsp中点击链接访问/hello.action

1
2
<a href="<s:url action='hello'/>">Hello World</a>
<a href="hello.action">Hello World</a>

类似于web.xml中

1
2
3
4
5
6
7
8
9
<filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>struts2</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

建立了链接/*org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter的映射关系;

struts.xml中

1
2
3
<action name="hello" class="me.struts.ex.helloworld.action.HelloWorldAction" method="execute">
  <result name="success">/HelloWorld.jsp</result>
</action>

建立了hello.actionme.struts.ex.helloworld.action.HelloWorldAction的映射关系。

接下来调用Action的execute方法,创建MessageStore对象,返回SUCCESS。返回为SUCCESS,就将HelloWorld.jsp作为响应返回。

1
<s:property value="messageStore.message" />

调用Action的getMessageStore方法返回MessageStore类的实例messageStore,显示messageStore的属性message。

对Struts文档的注释 (一)

May 23, 2015

本文是依照参考文档操作时遇到的一些问题,参考文档原文:Create Struts 2 Web Application Using Maven To Manage Artifacts and To Build The Application

Create A Java Web Application

新建Maven Project,选择maven-archetype-webapp,可以看到下方的提示为A simple Java web application,与参考文档保持一致,工程的artifactId填写为basic_struts(不需要再在pom.xml中添加finalName元素)。

1

Add index.jsp

新建工程的src/main/webapp目录下已经新建了index.jsp,但提示错误The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path,需要在pom.xml中添加

1
2
3
4
5
6
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>servlet-api</artifactId>
  <scope>provided</scope>
  <version>2.5</version>
</dependency>

在pom.xml中添加<dependency>配置时,可以在 http://mvnrepository.com/ 中查询,获取<version>。

最后提示,注意可能在路径、文件名上出现的错误,由此可能会在访问http://localhost:8088/Basic_Struts_Mvn/index.action时出现类似错误

There is no Action mapped for namespace [/] and action name [index]……

关于Maven的使用,可以参考前面的Maven安装、构建项目

参考资料

JSP报错:The superclass “javax.servlet.http.HttpServlet” was not found on the Java Build Path

DWR简单入门

May 16, 2015

Direct Web Remoting

DWR is a Java library that enables Java on the server and JavaScript in a browser to interact and call each other as simply as possible.

新建DWR工程

新建工程DwrEx,将下载的dwr.jar和依赖包commons-logging-1.2.jar放在/WEB-INF/lib目录下,

web.xml的内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<web-app xmlns = "http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation = "http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
  version = "2.4">

  <servlet>
      <display-name>DWR Servlet</display-name>
      <servlet-name>dwr-invoker</servlet-name>
      <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
      <init-param>
          <param-name>debug</param-name>
          <param-value>true</param-value>
      </init-param>
  </servlet>

  <servlet-mapping>
      <servlet-name>dwr-invoker</servlet-name>
      <url-pattern>/dwr/*</url-pattern>
  </servlet-mapping>

</web-app>

/WEB-INF目录下新建dwr.xml,内容为:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN"
    "http://getahead.org/dwr/dwr30.dtd">

<dwr>
  <allow>
    <create creator="new" javascript="JDate">
      <param name="class" value="java.util.Date"/>
    </create>
  </allow>
</dwr>

启动Tomcat,访问http://localhost:8088/DwrEx/dwr/,可以看到dwr.xml中配置的java.util.Date的所有方法;由于web.xml将参数debug初始化为true,可以在页面动态调试这些方法。

creator元素

<create ...>用于将服务端的Java类公开给客户端,一般结构为,

1
2
3
4
5
6
7
8
<allow>
  <create creator="..." javascript="..." scope="...">
    <param name="..." value="..."/>
    <exclude method="..."/>
    <include method="..."/>
  </create>
  ...
</allow>
  • creator属性设置为new,即使用Java类的new操作获得实例;
  • javascript属性指定从JS访问对象时使用的名称(避免使用JS关键字);
  • scope属性的默认值为page
  • includeexclude属性分别用于指定允许使用和限制使用的方法。
1
2
3
4
5
6
<allow>
  <create creator="new" javascript="Blah">
    <param name="class" value="java.util.Date"/>
  </create>
  ...
</allow>

java.util.Date公开给Javascript,使用Blah来访问对象,在JavaScript中调用Blah.toString(reply)时, 使用默认构造器实例化java.util.Date,调用toString()方法返回数据到JavaScript。

convert元素

<convert ...>用于在客户端和服务端之间序列化数据,将Java返回类型序列化为JS对象,以及将被传递的JS对象序列化为合适的Java对象

1
2
3
<convert converter="bean" match="com.example.Person">
  <param name="exclude" value="property1, property2"/>
</convert>
  • convertor属性设置为bean,将JavaBean对象(Person)转化为JavaScript对象;
  • include/exclude同样适用于convert,参数值为变量名而不是方法名。
1
2
3
4
5
6
7
8
9
10
11
public class Remoted {
  public void addToFriends(Person p) {
    // ...
  }
}

public class Person {
  public void setName(String name) { ... }
  public void setAge(int age) { ... }
  // ...
}

如果Remoted类已经配置为create,可以在JS中调用,

1
2
var p = { name:"Fred", age:21 };
Remoted.addToFriends(p);
参考资料

Getting Started with DWR

The Creators

Bean and Object Converters

Nginx简单入门

May 12, 2015

Nginx

nginx is an HTTP and reverse proxy server, a mail proxy server, and a generic TCP proxy server.

http://nginx.org/en/download.html 下载Windows平台下的压缩包,解压到C:/nginx目录,

使用行命令启动nginx,

1
2
cd C:/nginx
nginx

访问 http://localhost/ 会看到Welcome to nginx!,nginx正常工作。

在请求结束后,关闭nginx,

1
nginx -s quit

nginx是由解压目录下的/conf/nginx.conf配置文件中的指令控制,打开配置文件可以看到,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }
        #...
}

上下文给出了一个server的配置信息,主机监听来自80端口的请求,主机名为localhost,在我们访问http://localhost/时,URI匹配/,根目录为/html,默认访问目录下的index.html文件,也就是提示nginx启动成功的页面。

这里尝试自己搭建一个静态服务器,

C:/nginx目录下新建/data/www/index.html/data/images/nginx.png,配置信息如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# server for static content
server {
      listen          9828;
      server_name     localhost;

      location / {
          root    data/www;
          index   index.html;
      }

      location /images/ {
          root    data;
      }
}

由于Windows下的路径问题,所以同时修改

1
2
3
4
5
error_log  C:/nginx/logs/error.log;
error_log  C:/nginx/logs/error.log  notice;
error_log  C:/nginx/logs/error.log  info;

pid        C:/nginx/logs/nginx.pid;

在访问链接之前,执行

1
nginx -s reload

重载配置信息。

现在启动nginx可能会出现C:/nginx/logs/nginx.pid丢失的情况,可以执行

1
nginx -c C:/nginx/conf/nginx.conf

现在访问http://localhost:9828/,URI匹配/,可以看到/data/www/index.html显示的内容,访问http://localhost:9828/images/nginx.png,URI匹配/images/,即访问/data/images/nginx.png

参考资料

Beginner’s Guide

Redis客户端(Java)

May 05, 2015

Jedis

Jedis是Redis推荐的Java客户端。

Jedis-Getting started下载jedis-2.7.2.jar,以及从Download Apache Commons Pool下载依赖包commons-pool2-2.3.jar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisExample {
  public static void main(String[] args) {
      Set<String> sose = RedisSetsCase();

      Iterator<String> iterator = sose.iterator();
      while (iterator.hasNext())
          System.out.println(iterator.next());
  }

    //使用Redis Sorted Sets的例子
  public static Set<String> RedisSetsCase() {
      Set<String> sose = new HashSet<String>();

      @SuppressWarnings("resource")
      JedisPool pool = new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6379);
      Jedis jedis = null;

      try {
          jedis = pool.getResource();

            //类似于Redis的命令操作
          jedis.zadd("sose", 0, "car");
          jedis.zadd("sose", 0, "bike");
          sose = jedis.zrange("sose", 0, -1);
      } finally {
          if (jedis != null) {
              jedis.close();
          }
      }

      pool.destroy();

      return sose;
  }
}

执行结果:

1
2
bike
car

继续前面的Redis数据类型

Bitmaps

  • Bitmaps包含未操作命令;
  • setbit命令将bbit的某位置1或0,并返回该位的初始值;
  • getbit命令获取bbit某位的值;
1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> setbit bbit 0 1
(integer) 0
127.0.0.1:6379> setbit bbit 1 1
(integer) 0
127.0.0.1:6379> getbit bbit 0
(integer) 1
127.0.0.1:6379> getbit bbit 1
(integer) 1
127.0.0.1:6379> get bbit
"\xc0"
#获取bbit的值为十六进制c01100 0000
  • bitcount命令返回bbit中值为1的位数
1
2
127.0.0.1:6379> bitcount bbit
(Integer) 2
  • bitpos命令返回给定范围内第一个值为1或0的位,-1表示值不存在;
  • 可以添加参数设置bitpos命令的作用范围,范围是按字节划分,而不是比特位;
  • 结束范围缺省,默认为最后一个字节
1
2
3
4
5
6
7
8
9
#1100 0000
127.0.0.1:6379> bitpos bbit 1
(Integer) 0
127.0.0.1:6379> bitpos bbit 0
(Integer) 2
127.0.0.1:6379> bitpos bbit 1 1
(Integer) -1
127.0.0.1:6379> bitpos bbit 0 0 1
(Integer) 2