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

舞乐 VOLER

舞动我人生

使用Git协同工作

Feb 18, 2016

本文没有深层次的分析Git的工作原理,而是从实用角度,关注常用命令,简单分析了执行命令时发生的具体动作。

建立本地仓库

从远端仓库克隆数据到本地仓库

1
git clone https://github.com/jemoii/Koenigspress.git Koenigspress

执行git clone命令,默认将远端仓库命名为origin

  • 拉取远端仓库的所有数据;
  • 创建一个指向远端仓库master分支的指针,并在本地将其命名为origin/master
  • 创建一个跟踪origin/master的本地master分支。

添加-o away选项,即执行

1
git clone -o away https://github.com/jemoii/Koenigspress.git Koenigspress

远端仓库被命名为away,相应的本地指向远端仓库master分支的指针被命名为away/master

回到上一步,可以通过git remote show origin查看远端仓库的相关信息,这里的origin即远端仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git remote
origin

$ git remote show origin
* remote origin
  Fetch URL: https://github.com/jemoii/Koenigspress.git
  Push  URL: https://github.com/jemoii/Koenigspress.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

管理本地分支

现在执行git branch optimize_thememaster上新建optimize_theme分支,执行git checkout optimize_theme切换到optimize_theme分支上。

可以使用git checkout -b optimize_theme合并上述两步操作,执行git branch显示本地仓库分支列表,*标识当前位于optimize_theme分支

1
2
3
4
5
6
$ git checkout -b optimize_theme
Switched to a new branch 'optimize_theme'

$ git branch
  master
* optimize_theme

提交更新

修改文件后,执行git add .将修改内容添加到暂存区,接着执行git commit -m '提交主题优化'将修改内容提交到本地仓库

可以使用git commit -a -m '提交主题优化'合并上述两步操作。

现在使用git push origin optimize_theme将修改内容推送到远端仓库,远端仓库新建分支optimize保存修改内容

1
2
3
4
5
6
$ git push origin optimize_theme
Username for 'https://github.com': jemoii
Password for 'https://jemoii@github.com':
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/jemoii/Koenigspress.git
 * [new branch]      optimize_theme -> optimize_theme

现在执行git remote show origin查看远端仓库信息

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git remote show origin
* remote origin
  Fetch URL: https://github.com/jemoii/Koenigspress.git
  Push  URL: https://github.com/jemoii/Koenigspress.git
  HEAD branch: master
  Remote branches:
    master         tracked
    optimize_theme tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local refs configured for 'git push':
    master         pushes to master         (up to date)
    optimize_theme pushes to optimize_theme (up to date)

可以看到除了master分支,现在本地分支optimize_theme跟踪origin/optimize_theme

更新本地仓库

如果有另外一个协同者与我们一样克隆了远端仓库的数据到他的本地仓库,新建fix_bug分支,类似的将修改内容推送到远端仓库,且在我们执行git push前将修改内容合并到远端仓库的master分支,现在远端仓库master分支上的内容与我们执行git clone时相比发生了变化,我们再执行git push时会提示需要先将协同者的修改内容合并到我们的分支上。

首先执行git checkout master切换到master分支,执行git fetch origin

  • 从远端仓库获取本地仓库没有的数据,移动origin/master指针指向更新后的位置;
  • 创建一个指向远端仓库fix_bug分支的指针,并在本地将其命名为origin/fix_bug,本地不会新建fix_bug分支,只创建不可编辑的origin/fix_bug指针;

现在执行git merge origin/master将更新的数据合并到本地仓库的master分支,接着执行git checkout optimize_theme重新切换到optimize_theme分支,执行git merge master将更新的数据合并到本地仓库的optimize_theme分支。

现在执行git push origin optimize_theme可以将我们的修改内容推送到远端仓库。

回到上一步,在master分支上使用git pull可以合并git fetch origingit merge origin/master两步操作。

关于git merge

协同开发中创建工作分支的两种方式,第一种是前面提到的在本地仓库新建工作分支,执行git push时在远端仓库创建对应的工作分支;另一种是在远端仓库新建工作分支,执行新建/更新时在本地仓库创建指向远端仓库工作分支的指针,接着执行类似如下的命令,

1
git checkout -b fix_bug origin/fix_bug

在本地仓库创建fix_bug分支,跟踪origin/fix_bug,执行结束后切换到fix_bug分支。

让CAS服务端跑起来

Dec 11, 2015

参考资料:安装 CAS 服务器

注释

1、下载方式

http://central.maven.org/maven2/org/jasig/cas/cas-server-webapp/页面选择合适的版本,下载对应目录下的cas-server-webapp-version.war。

2、初始登录

在/WEB-INF/deployerConfigContext.xml中的

1
2
3
4
5
6
7
<bean id="primaryAuthenticationHandler" class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
    <property name="users">
        <map>
            <entry key="casuser" value="Mellon"/>
        </map>
    </property>
</bean>

配置了初始认证方式,这里的username/password即casuser/Mellon。

修改认证方式
配置CAS服务端通过查询数据库的方式认证用户。

首先与Spring应用一样配置数据库,这里使用的是PostgreSQL数据库。

1
2
3
4
5
6
7
8
9
<bean id="casDataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="org.postgresql.Driver" />
    <property name="url" value="jdbc:postgresql://localhost:5432/jemoii" />
    <property name="username" value="postgres" />
    <property name="password" value="password" />
    <property name="maxActive" value="20" />
    <property name="maxIdle" value="10" />
    <property name="maxWait" value="-1" />
</bean>
建立新的认证方式jdbcAuthenticationHandler,
1
2
3
4
5
6
7
<bean id="jdbcAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
      <property name="dataSource" ref="casDataSource" />
      <property name="sql" value="select password from user_info where email = ?" />
      <property name="passwordEncoder">
          <bean class="me.voler.cas.AddSaltPasswordEncoder"/>
      </property>
</bean>
  • dataSource属性即前面配置的数据库;
  • sql属性是一条SQL语句,功能即根据登录使用的username查询password,根据实际的table做相应的修改;
  • passwordEncoder属性是实现了org.jasig.cas.authentication.handler.PasswordEncoder接口的Bean。

CAS本身提供了org.jasig.cas.authentication.handler.DefaultPasswordEncoder、org.jasig.cas.authentication.handler.PlainTextPasswordEncoder两种实现,这里根据实际的加密方式新建了me.voler.cas.AddSaltPasswordEncoder,即在rawPassword后面拼接自定义PASSWORD_SALT后再使用MD5加密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package me.voler.cas;

import org.jasig.cas.authentication.handler.DefaultPasswordEncoder;
import org.jasig.cas.authentication.handler.PasswordEncoder;

public class AddSaltPasswordEncoder implements PasswordEncoder {

    private static final String PASSWORD_SALT = "xx";

    @Override
    public String encode(String password) {
        DefaultPasswordEncoder encoder = new DefaultPasswordEncoder("MD5");
        return encoder.encode(password + PASSWORD_SALT);
    }

}
应用新的认证方式jdbcAuthenticationHandler

将deployerConfigContext.xml中id为authenticationManager的Bean的

1
2
3
4
5
6
<constructor-arg>
    <map>
        <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
        <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" />
    </map>
</constructor-arg>

修改为

1
2
3
4
5
6
<constructor-arg>
    <map>
        <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
        <entry key-ref="jdbcAuthenticationHandler" value-ref="primaryPrincipalResolver" />
    </map>
</constructor-arg>

添加修改认证方式依赖的jar,在/WEB-INF/lib目录下添加

  • commons-pool-version.jar
  • commons-dbcp-version.jar
  • cas-server-support-jdbc-version.jar

现在可以使用数据库中的username/password登录CAS服务端。

Apache POI使用示例

Dec 05, 2015

读取记事本(txt)中的内容时,一直出现乱码,原因在于记事本的默认编码是GBK,不是UTF-8

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package me.voler.jechat.core;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.extractor.ExcelExtractor;
import org.apache.poi.xssf.extractor.XSSFExcelExtractor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.xmlbeans.XmlException;

/**
 * 支持取.txt、.doc、.docx、.xls、.xlsx,5种格式文档的内容,以字符串的形式返回。
 * {@link http://poi.apache.org}
 * 
 */
public class DocumentExtraction {

    /**
     * 取微软记事本中的内容,以字符串的形式返回。记事本中内容的编码为GBK
     * 
     * @param file
     * @return
     */
    public static String txt2String(File file) {
        StringBuffer buffer = new StringBuffer();
        try {
            InputStreamReader input = new InputStreamReader(new FileInputStream(file), "GBK");
            BufferedReader reader = new BufferedReader(input);

            String line = null;
            while ((line = reader.readLine()) != null) {
                buffer.append(line).append('\n');
            }
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return buffer.toString();
    }

    /**
     * 取Excel xlsx中的内容,以字符串的形式返回。{@link http://poi.apache.org}
     * 
     * @param file
     * @return
     */
    @SuppressWarnings("resource")
    public static String xlsx2String(File file) {

        XSSFWorkbook xlsxwb = new XSSFWorkbook();
        try {
            OPCPackage pkg = OPCPackage.open(new FileInputStream(file));
            xlsxwb = new XSSFWorkbook(pkg);
        } catch (InvalidFormatException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        ExcelExtractor extractor = new XSSFExcelExtractor(xlsxwb);
        extractor.setFormulasNotResults(true);
        extractor.setIncludeSheetNames(false);

        return extractor.getText();
    }

    /**
     * 取Excel xls中的内容,以字符串的形式返回。{@link http://poi.apache.org}
     * 
     * @param file
     * @return
     */
    @SuppressWarnings("resource")
    public static String xls2String(File file) {

        HSSFWorkbook xlswb = new HSSFWorkbook();
        try {
            POIFSFileSystem fileSystem = new POIFSFileSystem(new FileInputStream(file));
            xlswb = new HSSFWorkbook(fileSystem);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        ExcelExtractor extractor = new org.apache.poi.hssf.extractor.ExcelExtractor(xlswb);
        extractor.setFormulasNotResults(true);
        extractor.setIncludeSheetNames(false);

        return extractor.getText();
    }

    /**
     * 取Word doc中的内容,以字符串的形式返回。{@link http://poi.apache.org}
     * 
     * @param file
     * @return
     */
    public static String doc2String(File file) {
        StringBuffer buffer = new StringBuffer();

        WordExtractor extractor = null;
        POIFSFileSystem fileSystem;
        try {
            fileSystem = new POIFSFileSystem(new FileInputStream(file));
            extractor = new WordExtractor(fileSystem);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        String[] paragraphText = extractor.getParagraphText();
        for (String paragraph : paragraphText) {
            buffer.append(paragraph);
        }

        return buffer.toString();
    }

    /**
     * 取Word docx中的内容,以字符串的形式返回。{@link http://poi.apache.org}
     * 
     * @param file
     * @return
     */
    public static String docx2String(File file) {

        XWPFWordExtractor extractor = null;
        try {
            OPCPackage pkg = OPCPackage.open(new FileInputStream(file));
            extractor = new XWPFWordExtractor(pkg);
        } catch (InvalidFormatException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (XmlException e) {
            e.printStackTrace();
        } catch (OpenXML4JException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return extractor.getText();
    }

}

pom.xml中的依赖包配置,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
    ...
  <!-- xls -->
  <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi</artifactId>
      <version>3.12</version>
  </dependency>
  <!-- doc -->
  <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-scratchpad</artifactId>
      <version>3.12</version>
  </dependency>
  <!-- xlsx docx -->
  <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml</artifactId>
      <version>3.12</version>
  </dependency>
</dependencies>

使用Commons Email发送邮件以及与Spring的结合

Dec 04, 2015

使用Maven构建项目,在pom.xml中添加依赖包,

1
2
3
4
5
6
7
8
<dependencies>
    ...
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-email</artifactId>
        <version>1.3.3</version>
    </dependency>
</dependencies>

参考User guide,基于QQ邮箱发送文本邮件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
    Email email = new SimpleEmail();
    // 连接参数配置
    email.setHostName("smtp.qq.com");
    email.setSmtpPort(465);
    email.setAuthenticator(new DefaultAuthenticator("QQ No", "QQ Password");
    email.setSSLOnConnect(true);
    // 邮件相关内容
    try {
        email.setFrom("from@qq.com");
        email.setMsg("Hello, world!");
        email.addTo("to@example.com");
        email.send();
    } catch (EmailException e) {

    }
}

与使用JDBC连接数据库一样,前面的代码包含大量的模板操作,所以结合Spring,与JdbcTemplate类似,构建MailTemplate。在applicationContext-mail.xml中声明Bean,

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
<beans...>
    <bean
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:mail.properties</value>
            </list>
        </property>
    </bean>

    <bean id="simpleMail" class="org.apache.commons.mail.SimpleEmail">
        <property name="hostName" value="${me.mail.hostName}" />
        <property name="smtpPort" value="${me.mail.smtpPort}" />
        <property name="from" value="${me.mail.from}" />
        <property name="authenticator">
            <bean class="org.apache.commons.mail.DefaultAuthenticator">
                <constructor-arg value="${me.mail.authenticator.userName}" />
                <constructor-arg value="${me.mail.authenticator.password}" />
            </bean>
        </property>
    </bean>

    <bean id="mailTemplate" class="me.voler.jechat.core.MailTemplate">
        <property name="simpleMail" ref="simpleMail" />
    </bean>
</beans>

将连接参数添加到mail.properties,me.voler.jechat.core.MailTemplate即构建的MailTemplate,

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
package me.voler.jechat.core;

import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;

public class MailTemplate {

    private SimpleEmail simpleMail;

    public void send(String msg, String to) {
        this.send("【邮件】", msg, to);
    }

    /**
     * 
     * @param subject 邮件主题
     * @param msg 邮件正文
     * @param toList 收件人列表
     */
    public void send(String subject, String msg, String... toList) {

        try {
            simpleMail.setSubject(subject);
            simpleMail.setMsg(msg);
            simpleMail.addTo(toList);

            simpleMail.setSSLOnConnect(true);
            simpleMail.send();
        } catch (EmailException e) {
            e.printStackTrace();
        }

    }

    public SimpleEmail getSimpleMail() {
        return simpleMail;
    }

    public void setSimpleMail(SimpleEmail simpleMail) {
        this.simpleMail = simpleMail;
    }

}

使用构建的MailTemplate发送文本邮件,

1
2
3
4
5
6
7
@Autowired
@Qualifier("mailTemplate")
private MailTemplate mailTemplate;

public void sendEmptyMail() {
    mailTemplate.send("中文测试,English Test.", "to@example.com");
}

SimpleEmail的父类的属性sslOnConnect的set方法名为setSSLOnConnect,如果直接在applicationContext-mail.xml的simpleMailBean下配置<property name="sslOnConnect" value="${me.mail.sslOnConnect}" />会提示

Bean property is not writable or has an invalid setter method

使用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。