显示标签为“JAVA”的博文。显示所有博文
显示标签为“JAVA”的博文。显示所有博文

2009年4月18日星期六

netbeans试用

说说自己用过的几个JAVA IDE

开始做JAVA以来基本上都是用eclipse,以前对ORACLE痴迷的那阵子,也用用jdeveloper,这两天试了试sun的netbeans,感觉也很不错。

整体上来说,eclipse是三者里面功能最弱的,jdeveloper是最强,后者的功能强到和vs.net差不多,仅仅拖拽就能完成一些小型项目了,而netbeans则介于两者之间。使用eclipse的最大的好处是用的人很多,插件很多,jdeveloper的优势在于其跟ORACLE数据库绑定的非常紧密(最新版本对weblogic的支持也很好),而netbeans对多语言支持的很好比如ruby,c/c++,而且自带的例子也很好,很便于学习。

在JAVA SE来说,netbeans的支持最好,使用netbeans做出来的app程序在各个平台上都跟本地程序差不多,而且响应很迅速,传说永中office就是用netbeans做的,另外开发方式也是类似vs.net式的。而jdeveloper也是使用awt/swing做的,也可以很好,只是外观是ORACLE独有的那种格式,当然了开发方式也是拖拽式的。eclipse基本就不支持做本地程序。

在web开发方面来说,eclipse的web编辑器几乎就是一个纯文本编辑器,在这方面netbeans也差不多,而jdeveloper几乎是所见即所得的开发方式,跟dreamwaver差不多,而且支持很多ORACLE自定义的标签(当然也包括struts)。

在JAVAEE来说,jdeveloper很强,跟vs.net有的一拼,netbeans也很好,eclipse需要自己配置,不过由于国人习惯盗版,打了myeclipse插件之后也还挺好用的。其实根本就不用去盗版myeclipse,weblogic自带一个开发工具workshop也很好,不在myeclipse之下,习惯盗版的哥们可以考虑用用(不用输入licence)。

运行速度来说,eclipse最快,当然是在不包括myeclipse插件的情况下,netbeans也很快,jdevloper最慢。

个人选择的话我的顺序是netbeans,eclipse,jdeveloper,jdev主要是跟ORACLE数据库绑定的太紧了。

2009年4月12日星期日

使用ORACLE UCP

ORACLE UCP是指Universal Connecti了n Pool for JDBC,其实说白了就是ORACLE实现了一个数据库连接池,类似阿帕奇的commons-pool,使用这个东西可以提高JDBC程序的性能,并且不受应用服务器限制。

使用UCP需要:
  • JDK 1.6
  • Oracle JDBC thin driver supporting JDK 1.6
  • Oracle Universal Connection Pool library
以下是一段样例代码,链接的ORACLE数据库中自带的HR用户。

import java.sql.*;
import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

public class UcpConnection {
public static void main(String args[]) throws SQLException {
try
{
//Creating a pool-enabled data source
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
//Setting connection properties of the data source
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
pds.setURL("jdbc:oracle:thin:@//localhost:1521/XE");
pds.setUser("hr");
pds.setPassword("hr");
//Setting pool properties
pds.setInitialPoolSize(5);
pds.setMinPoolSize(5);
pds.setMaxPoolSize(10);
//Borrowing a connection from the pool
Connection conn = pds.getConnection();
System.out.println("\nConnection borrowed from the pool");
//Checking the number of available and borrowed connections
int avlConnCount = pds.getAvailableConnectionsCount();
System.out.println("\nAvailable connections: " + avlConnCount);
int brwConnCount = pds.getBorrowedConnectionsCount();
System.out.println("\nBorrowed connections: " + brwConnCount);
//Working with the connection
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select user from dual");
while(rs.next())
System.out.println("\nConnected as: "+rs.getString(1));
rs.close();
//Returning the connection to the pool
conn.close();
conn=null;
System.out.println("\nConnection returned to the pool");
//Checking the number of available and borrowed connections again
avlConnCount = pds.getAvailableConnectionsCount();
System.out.println("\nAvailable connections: " + avlConnCount);
brwConnCount = pds.getBorrowedConnectionsCount();
System.out.println("\nBorrowed connections: " + brwConnCount);
}
catch(SQLException e)
{
System.out.println("\nAn SQL exception occurred : " + e.getMessage());
}
}

UCP的配置可以参考UCP的手册,其他的用法,这篇文章里就不详细说明了,OTN上有文章讲的很详细,想看的话,点击这里
}

2009年3月24日星期二

新版的下载类

import java.io.*;
import java.net.*;
import java.util.*;

// This class downloads a file from a URL.
class Download extends Observable implements Runnable {
// Max size of download buffer.
private static final int MAX_BUFFER_SIZE = 1024;

// These are the status names.
public static final String STATUSES[] = {"Downloading",
"Paused", "Complete", "Cancelled", "Error"};

// These are the status codes.
public static final int DOWNLOADING = 0;
public static final int PAUSED = 1;
public static final int COMPLETE = 2;
public static final int CANCELLED = 3;
public static final int ERROR = 4;

private URL url; // download URL
private int size; // size of download in bytes
private int downloaded; // number of bytes downloaded
private int status; // current status of download

// Constructor for Download.
public Download(URL url) {
this.url = url;
size = -1;
downloaded = 0;
status = DOWNLOADING;

// Begin the download.
download();
}

// Get this download's URL.
public String getUrl() {
return url.toString();
}

// Get this download's size.
public int getSize() {
return size;
}

// Get this download's progress.
public float getProgress() {
return ((float) downloaded / size) * 100;
}

// Get this download's status.
public int getStatus() {
return status;
}

// Pause this download.
public void pause() {
status = PAUSED;
stateChanged();
}

// Resume this download.
public void resume() {
status = DOWNLOADING;
stateChanged();
download();
}

// Cancel this download.
public void cancel() {
status = CANCELLED;
stateChanged();
}

// Mark this download as having an error.
private void error() {
status = ERROR;
stateChanged();
}

// Start or resume downloading.
private void download() {
Thread thread = new Thread(this);
thread.start();
}

// Get file name portion of URL.
private String getFileName(URL url) {
String fileName = url.getFile();
return fileName.substring(fileName.lastIndexOf('/') + 1);
}

// Download file.
public void run() {
RandomAccessFile file = null;
InputStream stream = null;

try {
// Open connection to URL.
HttpURLConnection connection =
(HttpURLConnection) url.openConnection();

// Specify what portion of file to download.
connection.setRequestProperty("Range",
"bytes=" + downloaded + "-");

// Connect to server.
connection.connect();

// Make sure response code is in the 200 range.
if (connection.getResponseCode() / 100 != 2) {
error();
}

// Check for valid content length.
int contentLength = connection.getContentLength();
if (contentLength < size ="="" size =" contentLength;" file =" new" stream =" connection.getInputStream();" status ="=""> MAX_BUFFER_SIZE) {
buffer = new byte[MAX_BUFFER_SIZE];
} else {
buffer = new byte[size - downloaded];
}

// Read from server into buffer.
int read = stream.read(buffer);
if (read == -1)
break;

// Write buffer to file.
file.write(buffer, 0, read);
downloaded += read;
stateChanged();
}

/* Change status to complete if this point was
reached because downloading has finished. */
if (status == DOWNLOADING) {
status = COMPLETE;
stateChanged();
}
} catch (Exception e) {
error();
} finally {
// Close file.
if (file != null) {
try {
file.close();
} catch (Exception e) {}
}

// Close connection to server.
if (stream != null) {
try {
stream.close();
} catch (Exception e) {}
}
}
}

// Notify observers that this download's status has changed.
private void stateChanged() {
setChanged();
notifyObservers();
}

}

想扩展一下这个类实现多线程下载,FTP下载和断点续传。

2009年3月23日星期一

编程的首要原则

转自云风的blog,原文请看这里

刘未鹏的 blog 上写了一篇 编程的首要原则(s)是什么? ,这段时间在我的 google reader 上被许多人分享。

我问自己,我目前的首要原则是什么?

其实想说的,那篇里都有人说了。如果非要说首要,我也认可最多人认可的:

KISS - Keep It Simple Stupid

不过对 DRY - Don’t Repeat Yourself 我反而认为是次要的,当然是在和 KISS 相冲突的时候。

如果换一句和 KISS 原则相当分量的话,我会说:不要用愚蠢的方法做事。很矛盾?Repeat Yourself 往往代表了一些愚蠢的方案,且并不 simple ,至少会付出更多的体力。我想,KISS 的最后一个 S 指的是大智若愚的愚,而自做聪明则是另一种愚蠢。

在 KISS 的大原则下,我想其实可以分出一些细节的东西,也是别人都提过的:

最近两年我对同事说的最多的几句话,“弄清你的问题是什么”,“你不一定需要解决这个问题” 。

因为什么都不做才是最简单的。要知道什么可以不做,必须了解你的问题。

面向对象以及复杂软件技术的滥用,或是找不到更 Simple 的方案解决问题(以性能、以需求等为借口去实现更复杂的方案)往往都是对需求了解不清,或者眼光太短。把手段当成了目的。(以为达到目的,必须采用某种手段,而如何应用这种手段就变成了目的)

同时,我觉得过度抽象也来源于对问题的认识不清。我还没想好后面要写什么,实现些什么,所以先利用“抽象” 把其它的部分搭起来。久而久之,不分析具体问题,先做抽象就变成了惯性。而抽象层本身往往是软件中最复杂的部分,离 KISS 原则最远的一块。

以下是个人的想法:
我想,KISS 的最后一个 S 指的是大智若愚的愚,而自做聪明则是另一种愚蠢。其实我现在手头就有一个项目,仅仅为了实现下载功能,就反反复复包装了17层,把抽象用到了令人乍舌的地步。记得有句话说,如果清楚自己要做什么,3层就够了,如果不清楚,17层也不够。个人感觉3层足够了,UI一层,逻辑一层,数据一层。

KISS原则说起来容易,做起来真的不容易。

2009年3月16日星期一

一个简单的HTML解析器

这个解析器还不能很好的工作,但是架子大概已经有了,慢慢完善吧。

import URLTest.URLUtil;

public class HtmlParser {
public String leader;
public String tag;
public String body;
public String end;
public String trailer;

public HtmlParser more;
public HtmlParser parts;

static String tags[]={"html","body","table","tr","td"};

public HtmlParser(String text){
this (text,tags,0,0);
}

public HtmlParser(String text, String[] tags2){
this (text,tags2,0,0);
}

public HtmlParser(String text, String[] tags2, int level, int offset) {
String lc=text.toLowerCase();
int startTag=lc.indexOf("<"+tags[level]);
int endTag=lc.indexOf(">",startTag)+1;
int startEnd=lc.indexOf(" int endEnd=lc.indexOf(">",startEnd)+1;
int startMore=lc.indexOf("<"+tags[level],endEnd);

if(startTag<0||endTag<0||startEnd<0||endEnd<0){
return;
}


leader=text.substring(0,startTag);
tag=text.substring(startTag,endTag);
body=text.substring(endTag,startEnd);
end=text.substring(startEnd,endEnd);
trailer=text.substring(endEnd);
System.out.println("leader is :"+leader);
System.out.println("tag is :"+tag);
System.out.println("body is :"+body);
System.out.println("end is :"+end);
System.out.println("trailer is :"+trailer);

if(level+1 parts=new HtmlParser(body,tags,level+1,offset+endTag);
body=null;
}

if(startMore>=0){
more=new HtmlParser(body,tags,level,offset+endEnd);
trailer=null;
}
}

public static void main(String[] args) {
HtmlParser hp=new HtmlParser(URLUtil.getHtml("http://www.google.com"));
}

}

2009年3月15日星期日

用JAVA实现下载

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * 

Title: 个人开发的API


 * 

Description: 将指定的HTTP网络资源在本地以文件形式存放


 * 

Copyright: Copyright (c) 2004


 * 

Company: NewSky


 * @author MagicLiao
 * @version 1.0
 */
public class HttpGet {

  public final static boolean DEBUG = true;//调试用
  private static int BUFFER_SIZE = 8096;//缓冲区大小
  private Vector vDownLoad = new Vector();//URL列表
  private Vector vFileList = new Vector();//下载后的保存文件名列表

  /**
   * 构造方法
   */
  public HttpGet() {

  }

  /**
   * 清除下载列表
   */
  public void resetList() {
    vDownLoad.clear();
    vFileList.clear();
  }

  /**
   * 增加下载列表项
   *
   * @param url String
   * @param filename String
   */
  public void addItem(String url, String filename) {
    vDownLoad.add(url);
    vFileList.add(filename);
  }

  /**
   * 根据列表下载资源
   */
  public void downLoadByList() {
    String url = null;
    String filename = null;
    
    //按列表顺序保存资源
    for (int i = 0; i < vDownLoad.size(); i++) {
      url = (String) vDownLoad.get(i);
      filename = (String) vFileList.get(i);

      try {
        saveToFile(url, filename);
      }
      catch (IOException err) {
        if (DEBUG) {
          System.out.println("资源[" + url + "]下载失败!!!");
        }
      }
    }

    if (DEBUG) {
      System.out.println("下载完成!!!");

    }
  }

  /**
   * 将HTTP资源另存为文件
   *
   * @param destUrl String
   * @param fileName String
   * @throws Exception
   */
  public void saveToFile(String destUrl, String fileName) throws IOException {
    FileOutputStream fos = null;
    BufferedInputStream bis = null;
    HttpURLConnection httpUrl = null;
    URL url = null;
    byte[] buf = new byte[BUFFER_SIZE];
    int size = 0;
    
    //建立链接
    url = new URL(destUrl);
    httpUrl = (HttpURLConnection) url.openConnection();
    //连接指定的资源
    httpUrl.connect();
    //获取网络输入流
    bis = new BufferedInputStream(httpUrl.getInputStream());
    //建立文件
    fos = new FileOutputStream(fileName);

    if (this.DEBUG) 
  System.out.println("正在获取链接[" + destUrl + "]的内容...\n将其保存为文件[" + fileName + "]");

    //保存文件
    while ( (size = bis.read(buf)) != -1) 
      fos.write(buf, 0, size);
    
    fos.close();
    bis.close();
    httpUrl.disconnect();
  }

  /**
   * 设置代理服务器
   *
   * @param proxy String
   * @param proxyPort String
   */
  public void setProxyServer(String proxy, String proxyPort) {
    //设置代理服务器
    System.getProperties().put("proxySet", "true");
    System.getProperties().put("proxyHost", proxy);
    System.getProperties().put("proxyPort", proxyPort);

  }

  /**
   * 设置认证用户名与密码
   *
   * @param uid String
   * @param pwd String
   */
  //public void setAuthenticator(String uid, String pwd) {
   // Authenticator.setDefault(new MyAuthenticator(uid, pwd));
 // }

  /**
   * 主方法(用于测试)
   *
   * @param argv String[]
   */
  public static void main(String argv[]) {

    HttpGet oInstance = new HttpGet();
try {
//增加下载列表(此处用户可以写入自己代码来增加下载列表)
       oInstance.addItem("
","");

//开始下载
oInstance.downLoadByList();
    }
    catch (Exception err) {
      System.out.println(err.getMessage());
    }

  }

}

使用JAVA抓取网页内容

今天碰见一个哥们问,怎么使用JAVA抓取网页内容,查了查资料,写了个demo。基本想法是,通过URL打开HTTPconnection,然后可以拿到一个输入流,然后把输入流读入到一个StringBuffer 。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class URLUtil {

public static String getHtml(String urlString) {
try {
StringBuffer html = new StringBuffer();
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
InputStreamReader isr = new InputStreamReader(conn.getInputStream());
BufferedReader br = new BufferedReader(isr);
String temp;
while ((temp = br.readLine()) != null) {
html.append(temp).append("\n");
}
br.close();
isr.close();
return html.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

public static void main(String[] args) {
System.out.println(URLUtil.getHtml("http://www.sina.com.cn"));
}
}

其中的问题是不能处理多媒体信息。

2009年3月4日星期三

性能问题来自什么

最近,看了看公司产品的源代码,从第一天看的时候,我就清醒的认识到这个产品会有很严重的性能问题,主要问题在以下几个方面:

1 documentum的dql语句会被转化为SQL语句在ORACLE中执行,根据我对这个产品的研究,所有的dql都没有对应的绑定变量方式,因此可能造成严重的性能问题和共享池碎片。

2 所有的全文检索都是使用like '*string*'型的查询来完成,这样的话数据库根本没有办法使用索引,完全使用蛮力来完成工作。

3 对很多方法都进行了过度包装,我曾经分析下载功能,发现居然包装了17层,每次看到这里,就想起《Unix编程思想》里的那句话,如果你很清楚你要做什么,那么分3层足够了,如果你不清楚自己要做什么,17层也没什么帮助。

4 历史代码问题,公司产品里积压了好多年的历史代码,不慢才怪。

解决方法:

1 继续寻找使用绑定变量的方法。
2 使用ORACLE全文检索技术来替代dql,或者看看like操作能不能被查询重写成contains操作。
3 重写代码。

2009年2月8日星期日

JAVA使用itext为PDF添加元数据

我查了下似乎itext不支持直接添加元数据,我的方法是根据原来的PDF生成一篇同样的文件,然后删除原来的PDF,再将新生成的文件改名。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;

public class PdfMetadataSetter {
public static boolean writeDocMetadata(String docName, Map Properties){
boolean isSuccess = false;
PdfReader reader=null;
PdfStamper stamp=null;

/*
* Infact i create a new file and write metadata to the file.
* after that i delete the old file and rename new file.
*/
try {
reader = new PdfReader(docName);
String newFileName=getNewFileName(docName);
stamp = new PdfStamper(reader,
new FileOutputStream(newFileName));

stamp.setMoreInfo((HashMap)Properties);
stamp.close();

File oldFile=new File(docName);
oldFile.delete();
File newFile=new File(newFileName);
newFile.renameTo(new File(docName));
} catch (Exception de) {
de.printStackTrace();
}finally{
if(reader!=null)
reader.close();
try {
if(stamp!=null)
stamp.close();
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return isSuccess;
}

private static String getNewFileName(String fileName){
String newFileName=fileName.substring(0,(fileName.lastIndexOf(".")))+"2.pdf";
return newFileName;
}
}

觉得这么做不太好,不过暂时没有更好的方法,先将就一下。

2009年2月5日星期四

在JAVA里使用POI为word文档添加元数据

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import java.util.Iterator;
import java.util.Map;

import org.apache.poi.hpsf.CustomProperties;
import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.MarkUnsupportedException;
import org.apache.poi.hpsf.NoPropertySetStreamException;
import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.UnexpectedPropertySetTypeException;
import org.apache.poi.hpsf.WritingNotSupportedException;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;


public class WordMetadataSetter {
public static boolean writeDocMetadata(String path,String docName, Map Properties) throws UnexpectedPropertySetTypeException, NoPropertySetStreamException, MarkUnsupportedException, WritingNotSupportedException {
boolean isSuccess = false;
File dataDir = new File(path);
File doc = new File(dataDir, docName);

/* Read a test document doc into a POI filesystem. */
POIFSFileSystem poifs;
try {
poifs = new POIFSFileSystem(new FileInputStream(doc));
DirectoryEntry dir = poifs.getRoot();
DocumentEntry siEntry = (DocumentEntry) dir.getEntry(SummaryInformation.DEFAULT_STREAM_NAME);
DocumentEntry dsiEntry = (DocumentEntry) dir.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME);

/*
* Read the summary information stream and the document summary
* information stream from the POI filesystem.
*
* Please note that the result consists of SummaryInformation and
* DocumentSummaryInformation instances which are in memory only. To
* make them permanent they have to be written to a POI filesystem
* explicitly (overwriting the former contents). Then the POI filesystem
* should be saved to a file.
*/
DocumentInputStream dis = new DocumentInputStream(siEntry);
PropertySet ps = new PropertySet(dis);
SummaryInformation si = new SummaryInformation(ps);
dis = new DocumentInputStream(dsiEntry);
ps = new PropertySet(dis);
DocumentSummaryInformation dsi = new DocumentSummaryInformation(ps);

/*
* Write all properties supported by HPSF to the summary information
* (e.g. author, edit date, application name) and to the document
* summary information (e.g. company, manager).
*/
CustomProperties customProperties = dsi.getCustomProperties();
if (customProperties == null)
customProperties = new CustomProperties();
setProperties(customProperties,Properties);

dsi.setCustomProperties(customProperties);
si.write(dir, siEntry.getName());
dsi.write(dir, dsiEntry.getName());

OutputStream out = new FileOutputStream(doc);
poifs.writeFilesystem(out);
out.close();
isSuccess=true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

return isSuccess;
}

private static void setProperties(CustomProperties customProperties,Map Properties){
if(customProperties==null||Properties==null)
throw new NullPointerException();
Iterator it=Properties.entrySet().iterator() ;
while(it.hasNext()){
Map.Entry entry=(Map.Entry) it.next() ;
String key=entry.getKey().toString();
String value=entry.getValue().toString();
customProperties.put(key,value);
}
}
}



import java.util.Date;
import java.util.Hashtable;
import java.util.Map;

import org.apache.poi.hpsf.MarkUnsupportedException;
import org.apache.poi.hpsf.NoPropertySetStreamException;
import org.apache.poi.hpsf.UnexpectedPropertySetTypeException;
import org.apache.poi.hpsf.WritingNotSupportedException;

import junit.framework.TestCase;

public class TestWordMetadataSetter extends TestCase {

private static final String POI_FS = "TestWriteWellKnown.doc";
private static final String POI_PATH = "C:\\temp";
private static final Map PROPERTIES = new Hashtable();

public TestWordMetadataSetter(){
PROPERTIES.put("author", "fangyuan");
PROPERTIES.put("create date", new Date());
PROPERTIES.put("num", "1");
}

public void testWriteDocMetadata() throws WritingNotSupportedException, UnexpectedPropertySetTypeException, NoPropertySetStreamException, MarkUnsupportedException{
boolean result=WordMetadataSetter.writeDocMetadata(POI_PATH, POI_FS, PROPERTIES);
assertEquals(result,true);
}

}

2009年1月6日星期二

在本机搭建Tomcat集群环境

1 下载tomcat和apache


2 Tomcat集群

将tomcat解压两份:

/home/fanng/Public/Server/apache-tomcat-6.0.14-A

/home/fanng/Public/Server/apache-tomcat-6.0.14-B


(1) 打开“apache-tomcat-6.0.14-A”文件夹下“ conf ”文件夹下的“ server.xml ”文件以及“apache-tomcat-6.0.14-B”文件夹下“ conf ”文件夹下的“ server.xml ”文件
a

(2) 找到 server.xml 配置文件中的“ Server ”配置项目,并进行修改。

<server port="8005" shutdown="SHUTDOWN">

<server port="10005" shutdown="SHUTDOWN">

<server port="20005" shutdown="SHUTDOWN">

说明:第一行为两个 Tomcat 修改前的情况,第二行为 A Tomcat 修改后的情况,第三行为 B Tomcat 修改后的情况。


(3) 找到 server.xml 配置文件中的相应“ Connector ”配置项目,并进行修改。

* 修改前内容如下:

<!-- Define an AJP 1.3 Connector on port 8009 -->

<connector port="8009" protocol="AJP/1.3" redirectport="8443">

A 中修改后内容如下:

<!-- Define an AJP 1.3 Connector on port 8009 -->

<connector port=" 10009 " protocol="AJP/1.3" redirectport=" 10043 ">

B 中修改后内容如下:

<!-- Define an AJP 1.3 Connector on port 8009 -->

<connector port=" 20009 " protocol="AJP/1.3" redirectport=" 10043 ">

提示:此步骤目的是修改 AJP Connector 端口。


(4)找到 server.xml 配置文件中的另一个相应“ Connector ”配置项目,并进行修改。

* 修改前内容如下:

<connector port="8080" protocol="HTTP/1.1" connectiontimeout="20000" redirectport="8443">

A 中修改后内容如下:

<connector port=" 10001 " protocol="HTTP/1.1" connectiontimeout="20000" redirectport=" 10043 ">

B 中修改后内容如下:

<connector port=" 20001 " protocol="HTTP/1.1" connectiontimeout="20000" redirectport=" 20043 ">

提示:此步骤目的是修改 HTTP Connector 端口,其中的“ 10001 ”与“ 20001 ”是未来通过浏览器访问集群中各个 Tomcat 实例的 HTTP 端口。


(5) 通过修改 Engine 配置选项,配置集群中每个 Tomcat 实例的名称。

* 修改前内容如下:

<!-- You should set jvmRoute to support load-balancing via AJP ie : <engine name = "Standalone" defaulthost = "localhost" jvmroute = "jvm1"><br />-->

<engine name="Catalina" defaulthost="localhost">

A 中修改后内容如下:

<engine name="Standalone" defaulthost="localhost" jvmroute=" Tomcat1 ">

<!-- You should set jvmRoute to support load-balancing via AJP ie : <engine name = "Catalina" defaulthost = "localhost"><br />-->

B 中修改后内容如下:

<engine name="Standalone" defaulthost="localhost" jvmroute=" Tomcat2 ">

<!-- You should set jvmRoute to support load-balancing via AJP ie : <engine name = "Catalina" defaulthost = "localhost"><br />-->


(6) 修改配置文件中的 Cluster 配置项目,对集群的各项参数进行设置。

* 修改前内容如下:

<cluster classname="org.apache.catalina.ha.tcp.SimpleTcpCluster">

A 中修改后内容如下:

<cluster classname="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelsendoptions="8">

<manager classname="org.apache.catalina.ha.session.DeltaManager" expiresessionsonshutdown="false" notifylistenersonreplication="true">

<channel classname="org.apache.catalina.tribes.group.GroupChannel">

<membership classname="org.apache.catalina.tribes.membership.McastService" address="228.0.0.4" port="45564" frequency="500" droptime="3000">

<receiver classname="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4000" autobind="100" selectortimeout="5000" maxthreads="6">

<sender classname="org.apache.catalina.tribes.transport.ReplicationTransmitter">

<transport classname="org.apache.catalina.tribes.transport.nio.PooledParallelSender">

</transport>

<interceptor classname="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector">

<interceptor classname="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor">

</interceptor>

<valve classname="org.apache.catalina.ha.tcp.ReplicationValve" filter="">

<valve classname="org.apache.catalina.ha.session.JvmRouteBinderValve">

<deployer classname="org.apache.catalina.ha.deploy.FarmWarDeployer" tempdir="/tmp/war-temp/" deploydir="/tmp/war-deploy/" watchdir="/tmp/war-listen/" watchenabled="false">

<clusterlistener classname="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener">

<clusterlistener classname="org.apache.catalina.ha.session.ClusterSessionListener">

</clusterlistener>

B 中修改后内容如下:

<cluster classname="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelsendoptions="8">

<manager classname="org.apache.catalina.ha.session.DeltaManager" expiresessionsonshutdown="false" notifylistenersonreplication="true">

<channel classname="org.apache.catalina.tribes.group.GroupChannel">

<membership classname="org.apache.catalina.tribes.membership.McastService" address="228.0.0.4" port="45564" frequency="500" droptime="3000">

<receiver classname="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4000" autobind="100" selectortimeout="5000" maxthreads="6">

<sender classname="org.apache.catalina.tribes.transport.ReplicationTransmitter">

<transport classname="org.apache.catalina.tribes.transport.nio.PooledParallelSender">

</transport>

<interceptor classname="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector">

<interceptor classname="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor">

</interceptor>

<valve classname="org.apache.catalina.ha.tcp.ReplicationValve" filter="">

<valve classname="org.apache.catalina.ha.session.JvmRouteBinderValve">

<deployer classname="org.apache.catalina.ha.deploy.FarmWarDeployer" tempdir="/tmp/war-temp/" deploydir="/tmp/war-deploy/" watchdir="/tmp/war-listen/" watchenabled="false">

<clusterlistener classname="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener">

<clusterlistener classname="org.apache.catalina.ha.session.ClusterSessionListener">

</clusterlistener>


提示:上述配置内容主要是对集群中各个 Tomcat 实例间进行通信的方式、端口以及 Session 共享算法的设置。本教程由于篇幅所限,不能一一详细介绍,有兴趣的读者可以参看 Tomcat 的官方文档,其中有非常详细的说明。


(7)分别启动两个tomcat,再分别在webapp/root目录下新建一个Hello.jsp

A out.print("Hello! This is A!!");

B out.print("Hello! This is B!!");


(8)打开浏览器分别输入:

http://localhost:10001/Hello.jsp

http://localhost:20001/Hello.jsp


3 使用apache做负载均衡

将下载的apache解压,进入解压后的目录。

./configure --enable-so --enable-mods-shared="proxy proxy_http proxy_ftp proxy_connect headers"

make

make install


然后编辑/usr/local/apache2/conf/httpd.conf

将loadmodule ssl_module ....

前的注释去掉


然后将以下加到文件末尾:

ProxyRequests Off


ProxyPass / balancer://myCluster/


<proxy>


BalancerMember ajp://localhost:10009 route=Tomcat1


BalancerMember ajp://localhost:20009 route=Tomcat2


</proxy>


./usr/local/apache2/bin/apachectl start


在浏览器里输入:

http://localhost/Hello.jsp

多刷新几遍,就可以看见A页面和B页面轮换出现。


至此Tomcat集群搭建成功。

2008年12月9日星期二

JAVA语言学校的危险性

转自CSDN,原文请看这里

下面的文章是More Joel on Software一书的第8篇。

我觉得翻译难度很大,整整两个工作日,每天8小时以上,才译出了5000字。除了Joel大量使用俚语,另一个原因是原文涉及“编程原理”,好多东西我根本不懂。希望懂的朋友帮我看看,译文有没有错误,包括我写的注解。

====================
JAVA语言学校的危险性
作者:Joel Spolsky
译者:阮一峰
原文: http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html
发表日期 2005年12月29日,星期四


如今的孩子变懒了。
多吃一点苦,又会怎么样呢?

我一定是变老了,才会这样喋喋不休地抱怨和感叹“如今的孩子”。为什么他们不再愿意、或者说不再能够做艰苦的工作呢。
当我还是孩子的时候,学习编程需要用到穿孔卡片(punched cards)。那时可没有任何类似“退格”键(Backspace key)这样的现代化功能,如果你出错了,就没有办法更正,只好扔掉出错的卡片,从头再来。

回想1991年,我开始面试程序员的时候。我一般会出一些编程题,允许用任何编程语言解题。在99%的情况下,面试者选择C语言。

如今,面试者一般会选择Java语言。

说到这里,不要误会我的意思。Java语言本身作为一种开发工具,并没有什么错。、

等一等,我要做个更正。我只是在本篇特定的文章中,不会提到Java语言作为一种开发工具,有什么不好的地方。事实上,它有许许多多不好的地方,不过这些只有另找时间来谈了。

我在这篇文章中,真正想要说的是,总的来看,Java不是一种非常难的编程语言,无法用来区分优秀程序员和普通程序员。它可能很适合用来完成工作,但是这个不是今天的主题。我甚至想说,Java语言不够难,其实是它的特色,不能算缺点。但是不管怎样,它就是有这个问题。

如果我听上去像是妄下论断,那么我想说一点我自己的微不足道的经历。大学计算机系的课程里,传统上有两个知识点,许多人从来都没有真正搞懂过的,那就是指针(pointers)和递归(recursion)。

你进大学后,一开始总要上一门“数据结构”课(data structure), 然后会有线性链表(linkedlist)、哈希表(hashtable),以及其他诸如此类的课程。这些课会大量使用“指针”。它们经常起到一种优胜劣汰的作用。因为这些课程非常难,那些学不会的人,就表明他们的能力不足以达到计算机科学学士学位的要求,只能选择放弃这个专业。这是一件好事,因为如果你连指针很觉得很难,那么等学到后面,要你证明不动点定理(fixed point theory)的时候,你该怎么办呢?

有些孩子读高中的时候,就能用BASIC语言在AppleII型个人电脑上,写出漂亮的乒乓球游戏。等他们进了大学,都会去选修计算机科学101课程,那门课讲的就是数据结构。当他们接触到指针那些玩意以后,就一下子完全傻眼了,后面的事情你都可以想像,他们就去改学政治学,因为看上去法学院是一个更好的出路[1]。关于计算机系的淘汰率,我见过各式各样的数字,通常在40%到70%之间。校方一般会觉得,学生拿不到学位很可惜,我则视其为必要的筛选,淘汰那些没有兴趣编程或者没有能力编程的人。

对于许多计算机系的青年学生来说,另一门有难度的课程是有关函数式编程(functionalprogramming)的课程,其中就包括递归程序设计(recursiveprogramming)。MIT将这些课程的标准提得很高,还专门设立了一门必修课(课程代号6.001[2]),它的教材(Structureand Interpretation of Computer Programs,作者为Harold Abelson和Gerald JaySussmanAbelson,MIT出版社1996年版)被几十所、甚至几百所著名高校的计算系机采用,充当事实上的计算机科学导论课程。(你能在网上找到这本教材的旧版本,应该读一下。)

这些课程难得惊人。在第一堂课,你就要学完Scheme语言[3]的几乎所有内容,你还会遇到一个不动点函数(fixed- pointfunction),它的自变量本身就是另一个函数。我读的这门导论课,是宾夕法尼亚大学的CSE121课程,真是读得苦不堪言。我注意到很多学生,也许是大部分的学生,都无法完成这门课。课程的内容实在太难了。我给教授写了一封长长的声泪俱下的Email,控诉这门课不是给人学的。宾夕法尼亚大学里一定有人听到了我的呼声(或者听到了其他抱怨者的呼声),因为如今这门课讲授的计算机语言是Java。

我现在觉得,他们还不如没有听见呢。

这就是争议所在。许多年来,像当年的我一样懒惰的计算机系本科生不停地抱怨,再加上计算机业界也在抱怨毕业生不够用,这一切终于造成了重大恶果。过去十年中,大量本来堪称完美的好学校,都百分之百转向了Java语言的怀抱。这真是好得没话说了,那些用“grep”命令[4]过滤简历的企业招聘主管,大概会很喜欢这样。最妙不可言的是,Java语言中没有什么太难的地方,不会真的淘汰什么人,你搞不懂指针或者递归也没关系。所以,计算系的淘汰率就降低了,学生人数上升了,经费预算变大了,可谓皆大欢喜。

学习Java语言的孩子是幸运的,因为当他们用到以指针为基础的哈希表时,他们永远也不会遇到古怪的“段错误”[5] (segfault)。他们永远不会因为无法将数据塞进有限的内存空间,而急得发疯。他们也永远不用苦苦思索,为什么在一个纯函数的程序中,一个变量的值一会保持不变,一会又变个不停!多么自相矛盾啊!

他们不需要怎么动脑筋,就可以在专业上得到4.0的绩点。

我是不是有点太苛刻了?就像电视里的“四个约克郡男人”[6](Four Yorkshiremen)那样,成了老古板?就在这里吹嘘我是多么刻苦,完成了所有那些高难度的课程?

我再告诉你一件事。1900年的时候,拉丁语和希腊语都是大学里的必修课,原因不是因为它们有什么特别的作用,而是因为它们有点被看成是受过高等教育的人士的标志。在某种程度上,我的观点同拉丁语支持者的观点没有不同(下面的四点理由都是如此):“(拉丁语)训练你的思维,锻炼你的记忆。分析拉丁语的句法结构,是思考能力的最佳练习,是真正对智力的挑战,能够很好地培养逻辑能力。”以上出自Scott Barker之口(http://www.promotelatin.org/whylatin.htm)。但是,今天我找不到一所大学,还把拉丁语作为必修课。指针和递归不正像计算机科学中的拉丁语和希腊语吗?

说到这里,我坦率地承认,当今的软件代码中90%都不需要使用指针。事实上,如果在正式产品中使用指针,这将是十分危险的。好的,这一点没有异议。与此同时,函数式编程在实际开发中用到的也不多。这一点我也同意。

但是,对于某些最激动人心的编程任务来说,指针仍然是非常重要的。比如说,如果不用指针,你根本没办法开发Linux的内核。如果你不是真正地理解了指针,你连一行Linux的代码也看不懂,说实话,任何操作系统的代码你都看不懂。

如果你不懂函数式编程,你就无法创造出MapReduce[7],正是这种算法使得Google的可扩展性(scalable)达到如此巨大的规模。单词“Map”(映射)和“Reduce”(化简)分别来自Lisp语言和函数式编程。回想起来,在类似6.001这样的编程课程中,都有提到纯粹的函数式编程没有副作用,因此可以直接用于并行计算(parallelizable)。任何人只要还记得这些内容,那么MapRuduce对他来说就是显而易见的。发明MapReduce的公司是Google,而不是微软,这个简单的事实说出了原因,为什么微软至今还在追赶,还在试图提供最基本的搜索服务,而Google已经转向了下一个阶段,开发世界上最大的并行式超级计算机——Skynet[8]的H次方的H次方的H次方的H次方的H次方的H次方。我觉得,微软并没有完全明白,在这一波竞争中它落后多远。

除了上面那些直接就能想到的重要性,指针和递归的真正价值,在于那种你在学习它们的过程中,所得到的思维深度,以及你因为害怕在这些课程中被淘汰,所产生的心理抗压能力,它们都是在建造大型系统的过程中必不可少的。指针和递归要求一定水平的推理能力、抽象思考能力,以及最重要的,在若干个不同的抽象层次上,同时审视同一个问题的能力。因此,是否真正理解指针和递归,与是否是一个优秀程序员直接相关。

如果计算机系的课程都与Java语言有关,那么对于那些在智力上无法应付复杂概念的学生,就没有东西可以真的淘汰他们。作为一个雇主,我发现那些100%Java教学的计算机系,已经培养出了相当一大批毕业生,这些学生只能勉强完成难度日益降低的课程作业,只会用Java语言编写简单的记账程序,如果你让他们编写一个更难的东西,他们就束手无策了。他们的智力不足以成为程序员。这些学生永远也通不过MIT的6.001课程,或者耶鲁大学的 CS323课程。坦率地说,为什么在一个雇主的心目中,MIT或者耶鲁大学计算机系的学位的份量,要重于杜克大学,这就是原因之一。因为杜克大学最近已经全部转为用Java语言教学。宾夕法尼亚大学的情况也很类似,当初CSE121课程中的Scheme语言和ML语言,几乎将我和我的同学折磨至死,如今已经全部被Java语言替代。我的意思不是说,我不想雇佣来自杜克大学或者宾夕法尼亚大学的聪明学生,我真的愿意雇佣他们,只是对于我来说,确定他们是否真的聪明,如今变得难多了。以前,我能够分辨出谁是聪明学生,因为他们可以在一分钟内看懂一个递归算法,或者可以迅速在计算机上实现一个线性链表操作函数,所用的时间同黑板上写一遍差不多。但是对于Java语言学校的毕业生,看着他们面对上述问题苦苦思索、做不出来的样子,我分辨不出这到底是因为学校里没教,还是因为他们不具备编写优秀软件作品的素质。PaulGraham将这一类程序员称为“Blub程序员”[9](www.paulgraham.com/avg.html)。

Java语言学校无法淘汰那些永远也成不了优秀程序员的学生,这已经是很糟糕的事情了。但是,学校可以无可厚非地辩解,这不是校方的错。整个软件行业,或者说至少是其中那些使用grep命令过滤简历的招聘经理,确实是在一直叫嚷,要求学校使用Java语言教学。

但是,即使如此,Java语言学校的教学也还是失败的,因为学校没有成功训练好学生的头脑,没有使他们变得足够熟练、敏捷、灵活,能够做出高质量的软件设计(我不是指面向对象式的“设计”,那种编程只不过是要求你花上无数个小时,重写你的代码,使它们能够满足面向对象编程的等级制继承式结构,或者说要求你思考到底对象之间是“has-a”从属关系,还是“is-a”继承关系,这种“伪问题”将你搞得烦躁不安)。你需要的是那种能够在多个抽象层次上,同时思考问题的训练。这种思考能力正是设计出优秀软件架构所必需的。

你也许想知道,在教学中,面向对象编程(object-orientedprogramming,缩写OOP)是否是指针和递归的优质替代品,是不是也能起到淘汰作用。简单的回答是:“不”。我在这里不讨论OOP的优点,我只指出OOP不够难,无法淘汰平庸的程序员。大多数时候,OOP教学的主要内容就是记住一堆专有名词,比如“封装”(encapsulation)和“继承”(inheritance)”,然后再做一堆多选题小测验,考你是不是明白“多态”(polymorphism)和“重载”(overloading)的区别。这同历史课上,要求你记住重要的日期和人名,难度差不多。OOP不构成对智力的太大挑战,吓不跑一年级新生。据说,如果你没学好OOP,你的程序依然可以运行,只是维护起来有点难。但是如果你没学好指针,你的程序就会输出一行段错误信息,而且你对什么地方出错了毫无想法,然后你只好停下来,深吸一口气,真正开始努力在两个不同的抽象层次上,同时思考你的程序是如何运行的。

顺便说一句,我有充分理由在这里说,那些使用grep命令过滤简历的招聘经理真是荒谬可笑。我从来没有见过哪个能用Scheme语言、 Haskell语言和C语言中的指针编程的人,竟然不能在二天里面学会Java语言,并且写出的Java程序,质量竟然不能胜过那些有5年Java编程经验的人士。不过,人力资源部里那些平庸的懒汉,是无法指望他们听进去这些话的。
再说,计算机系承担的发扬光大计算机科学的使命该怎么办呢?计算机系毕竟不是职业学校啊!训练学生如何在这个行业里工作,不应该是计算机系的任务。这应该是社区高校和政府就业培训计划的任务,那些地方会教给你工作技能。计算机系给予学生的,理应是他们日后生活所需要的基础知识,而不是为学生第一周上班做准备。对不对?

还有,计算机科学是由证明(递归)、算法(递归)、语言(λ演算[10])、操作系统(指针)、编译器(λ演算)所组成的。所以,这就是说那些不教C语言、不教Scheme语言、只教Java语言的学校,实际上根本不是在教授计算机科学。虽然对于真实世界来说,有些概念可能毫无用处,比如函数的科里化(functioncurrying)[11],但是这些知识显然是进入计算机科学研究生院的前提。我不明白,计算机系课程设置委员会中的教授为什么会同意,将课程的难度下降到如此低的地步,以至于他们既无法培养出合格的程序员,甚至也无法培养出合格的能够得到哲学博士PhD学位[12]、进而能够申请教职、与他们竞争工作岗位的研究生。噢,且慢,我说错了。也许我明白原因了。

实际上,如果你回顾和研究学术界在“Java大迁移”(Great Java Shift)中的争论,你会注意到,最大的议题是Java语言是否还不够简单,不适合作为一种教学语言。

我的老天啊,我心里说,他们还在设法让课程变得更简单。为什么不用匙子,干脆把所有东西一勺勺都喂到学生嘴里呢?让我们再请助教帮他们接管考试,这样一来就没有学生会改学“美国研究”[13](Americanstudies)了。如果课程被精心设计,使得所有内容都比原有内容更容易,那么怎么可能期望任何人从这个地方学到任何东西呢?看上去似乎有一个工作小组(Java taskforce)正在开展工作,创造出一个简化的Java的子集,以便在课堂上教学[14]。这些人的目标是生成一个简化的文档,小心地不让学生纤弱的思想,接触到任何EJB/J2EE的脏东西[15]。这样一来,学生的小脑袋就不会因为遇到有点难度的课程,而感到烦恼了,除非那门课里只要求做一些空前简单的计算机习题。

计算机系如此积极地降低课程难度,有一个理由可以得到最多的赞同,那就是节省出更多的时间,教授真正的属于计算机科学的概念。但是,前提是不能花费整整两节课,向学生讲解诸如Java语言中int和Integer有何区别[16]。好的,如果真是这样,课程6.001就是你的完美选择。你可以先讲Scheme语言,这种教学语言简单到聪明学生大约只用10分钟,就能全部学会。然后,你将这个学期剩下的时间,都用来讲解不动点。

唉。

说了半天,我还是在说要学1和0。

(你学到了1?真幸运啊!我们那时所有人学到的都是0。)

================
注解:
[1] 在美国,法学院的入学者都必须具有本科学位。通常来说,主修政治学的学生升入法学院的机会最大。
[2] 在麻省理工学院,计算机系的课程代码都是以6开头的,6.001表明这是计算机系的最基础课程。
[3] Scheme语言是LISP语言的一个变种,诞生于1975年的MIT,以其对函数式编程的支持而闻名。这种语言在商业领域的应用很少,但是在计算机教育领域内有着广泛影响。
[4] grep是Unix/Linux环境中用于搜索或者过滤内容的命令。这里指的是,某些招聘人员仅仅根据一些关键词来过滤简历,比如本文中的Java。
[5] 段错误(segfault)是segmentation fault的缩写,指的是软件中的一类特定的错误,通常发生在程序试图读取不允许读取的内存地址、或者以非法方式读取内存的时候。
[6] 《四个约克郡男人》(Four Yorkshiremen),是英国电视系列喜剧At Last the 1948 Show中的一部,与上个世纪70年代问世。内容是四个约克郡男人竞相吹嘘,各自的童年是多么困苦,由于内容太夸张,所以显得非常可笑。
[7] MapReduce是一种由Google引入使用的软件框架,用于支持计算机集群环境下,海量数据(PB级别)的并行计算。
[8] Skynet是美国系列电影《终结者》(Terminator)中一个控制一切、与人类为敌的超级计算机系统的名称,通常将其看作虚构的人工智能的代表。
[9] Blub程序员(Blub programmers)指的是那些企图用一种语言,解决所有问题的程序员。Blub是Paul Graham假设的一种高级编程语言。
[10] λ演算(lambda calculus)是一套用于研究函数定义、函数应用和递归的形式系统,在递归理论和函数式编程中有着广泛的应用。
[11] 函数的科里化(function currying)指的是一种多元函数的消元技巧,将其变为一系列只有一元的链式函数。它最早是由美国数学家哈斯格尔·科里(Haskell Curry)提出的,因此而得名。
[12] 在美国,所有基础理论的学科,一律授予的都是哲学博士学位(Doctor of Philosophy),计算机科学系亦是如此。
[13] 美国研究(American studies)是对美国社会的经济、历史、文化等各个方面进行研究的一门学科。这里指的是,计算机系学生不会因为课程太难被淘汰,所以就不用改学相对容易的“美国研究”。
[14] 参见http://www.sigcse.org/topics/javataskforce/java-task-force.pdf。
[15] J2EE是Java2平台企业版(Java 2 Platform,EnterpriseEdition),指的是一整套企业级开发架构。EJB(EnterpriseJavaBean)属于J2EE的一部分,是一个基于组件的企业级开发规范。它们通常被认为是Java中相对较难的部分。
[16] 在Java语言中,int是一种数据类型,表示整数,而Integer是一个适用于面向对象编程的类,表示整数对象。两者的涵义和性质都不一样。
(完)

2008年11月24日星期一

Berkeley DB JAVA EDTION

最近打算学习一下BDB JAVA EDTION,从OTN下载源码后,将源代码导入eclipse,然后加入junit,以及从sun的官方网站下载sun的JAVA EE参考实现并导入,就可以测试了,example,src,test三个文件夹里都是代码,example里是一些小例子,test里是针对源代码的测试方法,src里是源码,使用可以参考OTN上的文档,研究源码可以去这个哥哥的博客看看。另外已经有个哥哥翻译了一些BDBJE的文档在这里,万分感谢。

2008年10月28日星期二

小试ORACLE JDEVELOPER 11G

昨天下载了正式版的JDEV 11G,晚上忍不住兴奋,小试了一下,感觉比技术预览版的变化还是很大的(我下载了base版的,同时在windows和linux平台上试用了一下)。最大的区别在于以前JDEV内置了OC4J,都是使用OC4J来做默认的应用服务器,但是现在是试用weblogic server10.3,安装JDEV的时候会默认安装WEBLOGIC。感觉虽然WEBLOGIC做得很庞大,但是启动速度一点不比OC4J慢。看来以后ORACLE应该还是会走WEBLOGIC+JDEV的路线,而WORKSHOP和OAS肯能会逐渐被边缘化。

功能上跟技术预览版差不多,只是运行起来更稳定,消耗的内存更少。新版的JDEV集成了SPRING和STRUTS,ORACLE ADF,ORACLE TOPLINK(部署的时候可以更换成ECLIPSELINK,以前的版本与HIBERNATE冲突),对JSF的支持很好,并且集成了最新版的SQL DEV,自己觉得如果是做基于ORACLE的应用程序的话,那么JDEV是一个值得尝试的工具。

2008年10月14日星期二

ORACLE jdeveloper 11g放出

今日上OTN,发现正式版的ORACLE jdeveloper 11g已经放出了,可以点击这里下载,看样子好像只支持jdk1.6,功能上跟technology preview 4版的没什么大的区别,只是对web logic server的支持更好了,而且修正了好些bug。

相对于其他免费java ide(当然jdev可以通过插件的形式支持PHP开发,而且可以用于sql/plsql开发),jdev的功能是很强悍的,很适合从.net转过来的程序员学习JAVA之用(很多功能都可以通过拖放来完成,不需要写多少代码),并且开发速度很是迅速,应该不弱于vs.net。特别是对ORACLE 数据库中一些特殊的数据类型支持的很好(我说的特殊数据类型是指xmltype,geometry,etc)。并且ORACLE提供了JDEV SDK可以用于扩展JDEV,因此如果有什么特殊需求,也可以自己定制JDEV。当然由于JDEV不开源而且免费地比较晚,因此插件不是很多(准确的说叫做扩展模块)。但是相信由于ORACLE在数据库方面的优势和现在收购BEA后在中间件方面的实力,以后用JDEV的人会多起来。(嘿嘿,当然ORACLE还有一个基于eclipse的workshop,不知道他们会怎么处理两者关系。)

2008年8月26日星期二

读取CLOB字段

今天碰见读取CLOB问题,主要是展示的速度比较慢,查了一下Kris的博客,请点击这里
发现他对两种CLOB读取方法的比较很详细:
// 方法1
int read = 80; // only need the first 80 to show
start = System.currentTimeMillis();
s = lob.getSubString(1, read);
long s1 = System.currentTimeMillis() - start;

// 方法2
start = System.currentTimeMillis();
char[] cbuf = new char[read];
lob.getCharacterStream().read(cbuf);
long s2 = System.currentTimeMillis() - start;

比较的结果是方法一快一些,用时大概是方法二的一半左右。
另外,使用方法一,可以在AJAX里慢慢读数据,一次读取一定长度的字节,然后在页面缓存,这样的话应该可以加速LOB数据的展示。
还有看看BLOB怎么处理更好一些。

2008年8月11日星期一

String转CLOB

用下面的方法可以将JAVA的STRING 转化成CLOB类型,不过好像仅限于ORACLE,其他的数据库上我没有试过。
private CLOB getCLOB( String clobData,Connection conn )
throws Exception {
CLOB tempClob = null;
try {
// create a new temporary CLOB

tempClob = CLOB.createTemporary(getNativeConnection(conn) , false,
CLOB.DURATION_SESSION );
// Open the temporary CLOB in readwrite mode to enable writing
tempClob.open( CLOB.MODE_READWRITE );

// Get the output stream to write
Writer tempClobWriter = tempClob.getCharacterOutputStream( );
// Write the data into the temporary CLOB
tempClobWriter.write( clobData );
// Flush and close the stream
tempClobWriter.flush( );
tempClobWriter.close( );
// Close the temporary CLOB
tempClob.close( );
} catch ( Exception exp ) {
// Free CLOB object
throw exp;
//do something
}
return tempClob;
}

如果使用连接池来获得数据库连接,有可能需要将数据库连接进行一下转化,使用以下代码:
private static Connection getNativeConnection(Connection con) throws SQLException {
if (con instanceof DelegatingConnection) {
Connection nativeCon = ((DelegatingConnection) con).getInnermostDelegate();
// For some reason, the innermost delegate can be null: not for a
// Statement''''s Connection but for the Connection handle returned by the pool.
// We''''ll fall back to the MetaData''''s Connection in this case, which is
// a native unwrapped Connection with Commons DBCP 1.1.
return (nativeCon != null ? nativeCon : con.getMetaData().getConnection());
}
return con;
}