1.简介
DWR是Direct Web Remoting的简写,它是一套RPC库,使服务器端的Java和浏览器端的Javascript能够方便地互相调用。官网地址:http://directwebremoting.org/dwr/index.html
DWR能够生成Javascript,使浏览器能够像调用本地API一样调用服务器端的Java API。它能够序列化任何数据类型,如Collections, POJOs, XML和二进制数据,例如图像和PDF文件。
使用Reverse AJAX, DWR能够让Java调用客户端API来更新任意页面。DWR支持Comet,Polling和Piggyback三种方式来推送内容到浏览器。
下图展示了DWR如何基于javascript事件更新页面的下拉列表
下图展示利用Reverse Ajax,服务器端能够监控不同客户端在打开哪些页面,将手工或者使用Java API生成的javascript发送给它们。
官网上能够下载用于演示的war包,里面有一些常见的功能演示。本文仅从学习的角度,自己从头搭建使用DWR动态更新Table的环境,完成后页面如图
2.环境搭建
环境介绍
windows 7,使用XAMPP中的Tomcat服务器,开发环境是eclipse
1).使用eclipse建立一个Dynamic Web Project。
2).下载相关jar包,包括dwr.jar、commons-logging-1.0.4.jar、log4j-1.2.12.jar, dwr.jar对commons-logging有依赖。下载完后放到WEB-INF\lib目录,修改build path包含上述jar包。
3).编辑web.xml和dwr.xml,文件位于WEB-INF\目录下
web.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 id="dwr">
<display-name>DWR (Direct Web Remoting)</display-name>
<description>A Simple Demo DWR</description>
<listener>
<listener-class>org.directwebremoting.servlet.DwrListener</listener-class>
</listener>
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<display-name>DWR Servlet</display-name>
<description>Direct Web Remoter Servlet</description>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<!-- This should NEVER be present in live -->
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>accessLogLevel</param-name>
<param-value>CALL</param-value>
</init-param>
<!-- Remove this unless you want to use active reverse ajax -->
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
<!-- By default DWR creates application scope objects when they are first
used. This creates them when the app-server is started -->
<init-param>
<param-name>initApplicationScopeCreatorsAtStartup</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
</web-app>
dwr.xml
<?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" scope="application">
<param name="class" value="com.example.dwr.reverseajax.PeopleTable"/>
</create>
<convert match="com.example.dwr.people.Person" converter="bean"/>
<!-- resources not in this war file: java.util.Date -->
<create creator="new" javascript="JDate">
<param name="class" value="java.util.Date"/>
<exclude method="getHours"/>
<auth method="getMinutes" role="admin"/>
<auth method="getMinutes" role="devel"/>
<filter class="org.directwebremoting.filter.ExtraLatencyAjaxFilter"/>
</create>
<!-- this is a bad idea for live, but can be useful in testing -->
<convert converter="exception" match="java.lang.Exception"/>
<convert converter="bean" match="java.lang.StackTraceElement"/>
</allow>
</dwr>
web.xml里面定义了DWR的servlet,包含一些初始化参数,这些参数不是必须的,有的仅仅是为了调试方便,比如debug和accessLogLevel。
dwr.xml可以看出,它的主要作用就是定义java类和javascript对象的映射关系。
4).编辑Java Code,实际上就是实现了Runnable接口,每10秒钟随机生成一个Person记录,推送到前端
PeopleTable.java
package com.example.dwr.reverseajax;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.directwebremoting.Browser;
import org.directwebremoting.ScriptSession;
import org.directwebremoting.ScriptSessionFilter;
import org.directwebremoting.ServerContextFactory;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.impl.DaemonThreadFactory;
import org.directwebremoting.ui.dwr.Util;
import org.directwebremoting.util.Logger;
import com.example.dwr.people.Person;
public class PeopleTable implements Runnable {
Logger log = Logger.getLogger(this.getClass());
/**
* Constructor - Creates a thread pool that runs every 10 seconds.
*/
public PeopleTable() {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(
1, new DaemonThreadFactory());
executor.scheduleAtFixedRate(this, 1, 10, TimeUnit.SECONDS);
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
updateTableDisplay();
}
public void updateTableDisplay() {
log.error("enter updateTableDisplay");
// Get the current page.
String page = ServerContextFactory.get().getContextPath()
+ "/index.html";
// Create a new AttributeScriptSessionFilter which will look for an
// attribute on the ScriptSession
ScriptSessionFilter attributeFilter = new AttributeScriptSessionFilter(
SCRIPT_SESSION_ATTR);
// Update the page, filters ScriptSessions using attributeFilter. If the
// SCRIPT_SESSION_ATTR
// has not been set on the ScriptSession the page in question will not
// receive updates.
Browser.withPageFiltered(page, attributeFilter, new Runnable() {
@Override
public void run() {
// Creates a new Person bean.
Person person = new Person(true);
// Creates a multi-dimensional array, containing a row and the
// rows column data.
String[][] data = { { person.getId(), person.getName(),
person.getAddress(), person.getAge() + "",
person.isSuperhero() + "" } };
// Call DWR's util which adds rows into a table. peopleTable is
// the id of the tbody and
// data contains the row/column data.
Util.addRows("peopleTable", data);
}
});
}
/**
* Called from the client to add an attribute on the ScriptSession. This
* attribute will be used so that only pages (ScriptSessions) that have set
* this attribute will be updated.
*/
public void addAttributeToScriptSession() {
ScriptSession scriptSession = WebContextFactory.get()
.getScriptSession();
scriptSession.setAttribute(SCRIPT_SESSION_ATTR, true);
}
/**
* Called from the client to remove an attribute from the ScriptSession.
* When called from a client that client will no longer receive updates
* (unless addAttributeToScriptSession) is called again.
*/
public void removeAttributeToScriptSession() {
ScriptSession scriptSession = WebContextFactory.get()
.getScriptSession();
scriptSession.removeAttribute(SCRIPT_SESSION_ATTR);
}
/**
* This is the ScriptSessionFilter that will be used to filter out all
* ScriptSessions unless they contain the SCRIPT_SESSION_ATTR attribute.
*/
protected class AttributeScriptSessionFilter implements ScriptSessionFilter {
public AttributeScriptSessionFilter(String attributeName) {
this.attributeName = attributeName;
}
/*
* (non-Javadoc)
*
* @see
* org.directwebremoting.ScriptSessionFilter#match(org.directwebremoting
* .ScriptSession)
*/
@Override
public boolean match(ScriptSession session) {
Object check = session.getAttribute(attributeName);
return (check != null && check.equals(Boolean.TRUE));
}
private final String attributeName;
}
private final static String SCRIPT_SESSION_ATTR = "SCRIPT_SESSION_ATTR";
}
Person.java
package com.example.dwr.people;
import java.util.Random;
import org.directwebremoting.datasync.ExposeToString;
@ExposeToString
public class Person {
private String id;
private String name;
private String address;
private int age;
private boolean superhero;
private static int nextId = 1;
private static final Random random = new Random();
public Person() {
this.id = getNextId();
}
public Person(boolean withRandom) {
if (withRandom) {
this.name = RandomData.getFullName();
this.address = RandomData.getAddress();
this.age = RandomData.getAge();
this.superhero = (random.nextInt(100) == 1);
}
this.id = getNextId();
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return this.address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isSuperhero() {
return this.superhero;
}
public void setSuperhero(boolean superhero) {
this.superhero = superhero;
}
public String toString() {
return this.name;
}
public static synchronized String getNextId() {
return "P" + nextId++;
}
}
RandomData.java
package com.example.dwr.people;
import java.util.Random;
public class RandomData {
private static final Random random = new Random();
private static final String[] FIRSTNAMES = { "Fred", "Jim", "Shiela",
"Jack", "Betty", "Jacob", "Martha", "Kelly", "Luke", "Matt",
"Gemma", "Joe", "Ben", "Jessie", "Leanne", "Becky", "William",
"Jo", "Jane", "Joan", "Jerry", "Jason", "Martin", "Mark", "Max",
"Mike", "Molly", "Sam", "Shane", "Dwane", "Diane", "Anne", "Anna",
"Bill", "Jack", "Thomas", "Oliver", "Joshua", "Harry", "Charlie",
"Dan", "Will", "James", "Alfie", "Grace", "Ruby", "Olivia",
"Emily", "Jessica", "Sophie", "Chloe", "Lily", "Ella", "Amelia",
"Kimberly", "Owen", "Rhys", "Layla", "Jonny", "Darren", "Laura",
"Bridget", "Carl", "Josie" };
private static final String[] SURNAMES = { "Sutcliffe", "MacDonald",
"Duckworth", "Smith", "Wisner", "Jones", "Nield", "Turton",
"Trelfer", "Wilson", "Johnson", "Daniels", "Jones", "Wilkinson",
"Wilton", "Jackson" };
private static final String[] ROADS1 = { "Amaranth", "Apricot", "Aqua",
"Aquamarine", "Beige", "Bronze", "Buff", "Burgundy", "Cerise",
"Chestnut", "Cobalt", "Coral", "Cream", "Cyan", "Denim",
"Eggplant", "Fuchsia", "Grey", "Gold", "Indigo", "Ivory", "Jade",
"Khaki", "Lemon", "Lilac", "Linen", "Magenta", "Magnolia",
"Maroon", "Mustard", "Ochre", "Olive", "Orange", "Orchid", "Peach",
"Pear", "Pink", "Ruby", "Scarlet", "Silver", "Sepia", "Tangerine",
"Taupe", "Tan", "Teal", "Torquise", "Ultramarine", "Violet",
"Wheat", "Green", "Red", "Yellow", "Brown", "Blue", "Black",
"White", "Yellow" };
private static final String[] ROADS2 = { "Close", "Drive", "Street",
"Avenue", "Crescent", "Road", "Place", "Way", "Croft", "Lane" };
private static final String[] TOWNS = { "San Mateo", "San Francisco",
"San Diego", "New York", "Atlanta", "Sandford", "York", "London",
"Coventry", "Exeter", "Knowle", "Rhyl", "Stamford" };
public static String getPhoneNumber(boolean isUS) {
String phoneNumber;
if (isUS) {
phoneNumber = "+1 (" + random.nextInt(9) + random.nextInt(9)
+ random.nextInt(9) + ") " + random.nextInt(9)
+ random.nextInt(9) + random.nextInt(9) + " - "
+ random.nextInt(9) + random.nextInt(9) + random.nextInt(9)
+ random.nextInt(9);
} else {
phoneNumber = "+44 (0) 1" + random.nextInt(9) + random.nextInt(9)
+ random.nextInt(9) + " " + random.nextInt(9)
+ random.nextInt(9) + random.nextInt(9) + random.nextInt(9)
+ random.nextInt(9) + random.nextInt(9);
}
return phoneNumber;
}
public static String getFirstName() {
return FIRSTNAMES[random.nextInt(FIRSTNAMES.length)];
}
public static String getSurname() {
return SURNAMES[random.nextInt(SURNAMES.length)];
}
public static String getFullName() {
return getFirstName() + " " + getSurname();
}
public static String getAddress() {
String housenum = random.nextInt(399) + 1 + " ";
String road1 = ROADS1[random.nextInt(ROADS1.length)];
String road2 = ROADS2[random.nextInt(ROADS2.length)];
int townNum = random.nextInt(TOWNS.length);
String town = TOWNS[townNum];
return housenum + road1 + " " + road2 + ", " + town;
}
public static String[] getAddressAndNumber() {
String[] reply = new String[2];
String housenum = random.nextInt(399) + 1 + " ";
String road1 = ROADS1[random.nextInt(ROADS1.length)];
String road2 = ROADS2[random.nextInt(ROADS2.length)];
int townNum = random.nextInt(TOWNS.length);
String town = TOWNS[townNum];
reply[0] = (housenum + road1 + " " + road2 + ", " + town);
reply[1] = getPhoneNumber(townNum < 5 ? true : false);
return reply;
}
public static int getAge() {
return random.nextInt(80);
}
public static float getSalary() {
return Math.round(10.0F + 90.0F * random.nextFloat()) * 1000;
}
}
注意:需要修改eclipse的default output folder为:dwrtest/WEB-INF/classes。
5).到这里,应该可以访问DWR的测试页面了(必须在web.xml里面配置了debug才能访问测试页面)。在浏览器中输入 http://localhost:8080/dwrtest/dwr/index.html如果一切OK的话,应该显示如下图
这些就是在Javascript端能够调用的Java API,点击PeopleTable进入
这个页面告诉我们,如果要使用提供的API,需要在web页面中包含前两个js文件,这两个URL相当于DWR提供的服务,本地并没有对应的文件。
6).完成html和javascript代码。
我们只有一个页面, 新建html文件命名为index.html并放到工程的根目录下
index.html
<!DOCTYPE html>
<html>
<head>
<title>Reverse Ajax Table Update</title>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
<script type='text/javascript' src='../dwrtest/dwr/engine.js'> </script>
<script type='text/javascript' src='../dwrtest/dwr/util.js'> </script>
<script type='text/javascript' src='../dwrtest/dwr/interface/PeopleTable.js'> </script>
<script type='text/javascript' src='../dwrtest/js/onload.js'> </script>
<link rel="stylesheet" type="text/css" href="../dwrtest/generic.css" />
</head>
<body>
<h1>Reverse Ajax Table Update</h1>
<div id="tabContents">
<div id="demoDiv">
<div id="error"></div>
<input type="button" id="enable" value="Enable page updates"
οnclick="addAttributeToScriptSession();" /> <input type="button"
id="disable" value="Disable page updates"
οnclick="removeAttributeToScriptSession();" />
<p>
Server status: <span id="pollStatus"></span>
</p>
<table>
<thead>
<th>Id</th>
<th>Name</th>
<th>Address</th>
<th>Age</th>
<th>Is Superhero?</th>
</thead>
<tbody id="peopleTable"></tbody>
</table>
</div>
</div>
</body>
</html>
onload.js
window.οnlοad=function()
{
dwr.engine.setActiveReverseAjax(true); // Initiate reverse ajax polling
dwr.engine.setErrorHandler(errorHandler); // Called when a call and all retry attempts fail
dwr.engine.setPollStatusHandler(updatePollStatus); // Optional function to call when the reverse ajax status changes (e.g. online to offline)
updatePollStatus(true); // Optional - We are online right now! Until DWR determines we are not!
dwr.engine.setNotifyServerOnPageUnload(true); // Optional - When the page is unloaded, remove this ScriptSession.
PeopleTable.updateTableDisplay(); // Make a call to the server to begin updating the table!
addAttributeToScriptSession(); // Make a remote call to the server to add an attribute onto the ScriptSession which will be used in determining what pages receive updates!
}
function errorHandler(message, ex) {
dwr.util.setValue("error", "Cannot connect to server. Initializing retry logic.", {escapeHtml:false});
setTimeout(function() { dwr.util.setValue("error", ""); }, 5000)
}
function updatePollStatus(pollStatus) {
dwr.util.setValue("pollStatus", pollStatus ? "Online" : "Offline", {escapeHtml:false});
}
// Make a remote call to add an attribute on the ScriptSession.
// Only clients that have this attribute set will receive updates.
function addAttributeToScriptSession() {
PeopleTable.addAttributeToScriptSession();
}
// Make a remote call to remove an attribute from the ScriptSession.
// Clients that call this will no longer receive updates (unless addAttributeToScriptSession is called again).
function removeAttributeToScriptSession() {
PeopleTable.removeAttributeToScriptSession();
}
7)访问
http://localhost:8080/dwrtest/,大功告成
3. 配置log4j
如果是生产环境,通常需要配置log4J。
新建log4j.xml并放到WEB-INF\classes\目录下,内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %c{1} - %m%n"/>
</layout>
</appender>
<appender name="dwrLogFile" class="org.apache.log4j.FileAppender">
<param name="File" value="d:/tools/xampp/tomcat/webapps/dwrtest/log/dwrAccess.log"/>
<param name="Append" value="true"/>
<param name="Threshold" value="DEBUG"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%c] %m%n"/>
</layout>
</appender>
<appender name="otherFile" class="org.apache.log4j.FileAppender">
<param name="File" value="d:/tools/xampp/tomcat/webapps/dwrtest/log/other.log"/>
<param name="Append" value="true"/>
<param name="Threshold" value="DEBUG"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%c] %m%n"/>
</layout>
</appender>
<!-- All application exceptions/errors will be written here -->
<category name="org.directwebremoting.log.accessLog">
<priority value="INFO"/>
<appender-ref ref="dwrLogFile" />
</category>
<!-- All DWR startup information will be written here -->
<category name="org.directwebremoting.log.startup">
<priority value="DEBUG"/>
<appender-ref ref="dwrLogFile" />
</category>
<!-- All DWR script information will be written here -->
<category name="org.directwebremoting.log.scripts">
<priority value="DEBUG"/>
<appender-ref ref="dwrLogFile" />
</category>
<!-- All DWR session information will be written here -->
<category name="org.directwebremoting.log.session">
<priority value="DEBUG"/>
<appender-ref ref="dwrLogFile" />
</category>
<!-- All other messages will be written here, including exceptions internal to DWR -->
<root>
<priority value="DEBUG" />
<appender-ref ref="otherFile" />
</root>
</log4j:configuration>
附:eclipse工程截图