随笔-229  评论-18  文章-0  trackbacks-0
  2009年6月16日
1)Sun的JVM在实现Selector上,在Linux和Windows平台下的细节。 2)Selector类的wakeup()方法如何唤醒阻塞在select()系统调用上的细节。 先给大家做一个简单的回顾,在Windows下,Sun的Java虚拟机在Selector.open()时会自己和自己建立loopback的TCP链接;在Linux下,Selector会创建pipe。这主要是为了Selector.wakeup()可以方便唤醒阻塞在select()系统调用上的线程(通过向自己所建立的TCP链接和管道上随便写点什么就可以唤醒阻塞线程) 我们知道,无论是建立TCP链接还是建立管道都会消耗系统资源,而在Windows上,某些Windows上的防火墙设置还可能会导致Java的Selector因为建立不起loopback的TCP链接而出现异常。 而在我的另一篇文章《用GDB调试Java程序》中介绍了另一个Java的解释器——GNU的gij,以及编译器gcj,不但可以比较高效地运行Java程序,而且还可以把Java程序直接编译成可执行文件。 GNU的之所以要重做一个Java的编译和解释器,其一个重要原因就是想解释Sun的JVM的效率和资源耗费问题。当然,GNU的Java编译/解释器并不需要考虑太多复杂的平台,他们只需要专注于Linux和衍生自Unix System V的操作系统,对于开发人员来说,离开了Windows,一切都会变得简单起来。在这里,让我们看看GNU的gij是如何解释Selector.open()和Selector.wakeup()的。 同样,我们需要一个测试程序。在这里,为了清晰,我不会例出所有的代码,我只给出我所使用的这个程序的一些关键代码。 我的这个测试程序中,和所有的Socket程序一样,下面是一个比较标准的框架,当然,这个框架应该是在一个线程中,也就是一个需要继承Runnable接口,并实现run()方法的一个类。(注意:其中的s是一个成员变量,是Selector类型,以便主线程序使用) //生成一个侦听端 ServerSocketChannel ssc = ServerSocketChannel.open(); //将侦听端设为异步方式 ssc.configureBlocking(false); //生成一个信号监视器 s = Selector.open(); //侦听端绑定到一个端口 ssc.socket().bind(new InetSocketAddress(port)); //设置侦听端所选的异步信号OP_ACCEPT ssc.register(s,SelectionKey.OP_ACCEPT); System.out.println("echo server has been set up ......"); while(true){ int n = s.select(); if (n == 0) { //没有指定的I/O事件发生 continue; } Iterator it = s.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey) it.next(); if (key.isAcceptable()) { //侦听端信号触发 …… …… …… …… …… …… } if (key.isReadable()) { //某socket可读信号 …… …… …… …… …… …… } it.remove(); } } 而在主线程中,我们可以通过Selector.wakeup()来唤醒这个阻塞在select()上的线程,下面是写在主线程中的唤醒程序: new Thread(this).start(); try{ //Sleep 30 seconds Thread.sleep(30000); System.out.println("wakeup the select"); s.wakeup(); }catch(Exception e){ e.printStackTrace(); } 这个程序在主线程中,先启动一个线程,也就是上面那个Socket线程,然后休息30秒,为的是让上面的那个线程有阻塞在select(),然后打印出一条信息,这是为了我们用strace命令查看具体的系统调用时能够快速定位。之后调用的是Selector的wakeup()方法来唤醒侦听线程。 接下来,我们可以通过两种方式来编译这个程序: 1)使用gcj或是sun的javac编译成class文件,然后使用gij解释执行。 2)使用gcj直接编译成可执行文件。 (无论你用那种方法,都是一样的结果,本文使用第二种方法,关于gcj的编译方法,请参看我的《用GDB调试Java程序》) 编译成可执行文件后,执行程序时,使用lsof命令,我们可以看到没有任何pipe的建立。可见GNU的解释更为的节省资源。而对于一个Unix的C程序员来说,这意味着如果要唤醒select()只能使用pthread_kill()来发送一个信号了。下面就让我们使用strace命令来验证这个想法。 下图是使用strace命令来跟踪整个程序运行时的系统调用,我们利用我们的输出的“wakeup the select”字符串快速的找到了wakeup的实际系统调用。
posted @ 2009-06-16 14:50 华梦行 阅读(36) | 评论 (0)编辑 收藏
很早就听说tomcat6使用nio了,这几天突然想到一个问题,使用nio代替传统的bio,ThreadLocal岂不是会存在冲突?   如果读者有socket的编程基础,应该会接触过堵塞socket和非堵塞socket,堵塞socket就是在accept、read、write等IO操作的的时候,如果没有可用符合条件的资源,不马上返回,一直等待直到有资源为止。而非堵塞socket则是在执行select的时候,当没有资源的时候堵塞,当有符合资源的时候,返回一个信号,然后程序就可以执行accept、read、write等操作,这个时候,这些操作是马上完成,并且马上返回。而windows的winsock则有所不同,可以绑定到一个EventHandle里,也可以绑定到一个HWND里,当有资源到达时,发出事件,这时执行的io操作也是马上完成、马上返回的。一般来说,如果使用堵塞socket,通常我们时开一个线程accept socket,当有socket链接的时候,开一个单独的线程处理这个socket;如果使用非堵塞socket,通常是只有一个线程,一开始是select状态,当有信号的时候马上处理,然后继续select状态。  按照大多数人的说法,堵塞socket比非堵塞socket的性能要好。不过也有小部分人并不是这样认为的,例如Indy项目(Delphi一个比较出色的网络包),它就是使用多线程+堵塞socket模式的。另外,堵塞socket比非堵塞socket容易理解,符合一般人的思维,编程相对比较容易。     nio其实也是类似上面的情况。在JDK1.4,sun公司大范围提升Java的性能,其中NIO就是其中一项。Java的IO操作集中在java.io这个包中,是基于流的阻塞API(即BIO,Block IO)。对于大多数应用来说,这样的API使用很方便,然而,一些对性能要求较高的应用,尤其是服务端应用,往往需要一个更为有效的方式来处理IO。从JDK 1.4起,NIO API作为一个基于缓冲区,并能提供非阻塞O操作的API(即NIO,non-blocking IO)被引入。  BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。   这个时候,问题就出来了:我们非常多的java应用是使用ThreadLocal的,例如JSF的FaceContext、Hibernate的session管理、Struts2的Context的管理等等,几乎所有框架都或多或少地应用ThreadLocal。如果存在冲突,那岂不惊天动地?    后来终于在Tomcat6的文档(http://tomcat.apache.org/tomcat-6.0-doc/aio.html)找到答案。根据上面说明,应该Tomcat6应用nio只是用在处理发送、接收信息的时候用到,也就是说,tomcat6还是传统的多线程Servlet,我画了下面两个图来列出区别:   tomcat5:客户端连接到达 -> 传统的SeverSocket.accept接收连接 -> 从线程池取出一个线程 -> 在该线程读取文本并且解析HTTP协议 -> 在该线程生成ServletRequest、ServletResponse,取出请求的Servlet -> 在该线程执行这个Servlet -> 在该线程把ServletResponse的内容发送到客户端连接 -> 关闭连接。      我以前理解的使用nio后的tomcat6:客户端连接到达 -> nio接收连接 -> nio使用轮询方式读取文本并且解析HTTP协议(单线程) -> 生成ServletRequest、ServletResponse,取出请求的Servlet -> 直接在本线程执行这个Servlet -> 把ServletResponse的内容发送到客户端连接 -> 关闭连接。 实际的tomcat6:客户端连接到达 -> nio接收连接 -> nio使用轮询方式读取文本并且解析HTTP协议(单线程) -> 生成ServletRequest、ServletResponse,取出请求的Servlet -> 从线程池取出线程,并在该线程执行这个Servlet -> 把ServletResponse的内容发送到客户端连接 -> 关闭连接。    从上图可以看出,BIO与NIO的不同,也导致进入客户端处理线程的时刻有所不同:tomcat5在接受连接后马上进入客户端线程,在客户端线程里解析HTTP协议,而tomcat6则是解析完HTTP协议后才进入多线程,另外,tomcat6也比5早脱离客户端线程的环境。   实际的tomcat6与我之前猜想的差别主要集中在如何处理servlet的问题上。实际上即使抛开ThreadLocal的问题,我之前理解tomcat6只使用一个线程处理的想法其实是行不同的。大家都有经验:servlet是基于BIO的,执行期间会存在堵塞的,例如读取文件、数据库操作等等。tomcat6使用了nio,但不可能要求servlet里面要使用nio,而一旦存在堵塞,效率自然会锐降。   所以,最终的结论当然是tomcat6的servlet里面,ThreadLocal照样可以使用,不存在冲突
posted @ 2009-06-16 14:30 华梦行 阅读(32) | 评论 (0)编辑 收藏
  2009年6月10日
我就拿一个房子来做一个比方吧,服务器好比就是一幢房子,黑客最直接的方式就是带着一些撬锁的工具,去把房子的锁给撬掉,然后夺门而入,这种方式被称为服务器入侵。还有一类就是它直接撬大门锁撬不开,它就把这个房子的窗打破,从窗子里面钻进去,来进行破坏,这种方式叫做网站入侵。还有一类就是黑客带着一只训练有素的小猴子,让小猴子爬到房子的房顶,从烟囱里面钻进去,然后把大门打开,这种方式叫做特洛伊木马入侵。还有一类就是我们前面讲到那个事件的DDOS攻击这个技术,这个相当于黑客带着一大帮人过来把房子的大门给堵住了,让房子里面的人出不来,让外面的人也进不去,这就是DDOS攻击。
posted @ 2009-06-10 10:40 华梦行 阅读(37) | 评论 (0)编辑 收藏
  2009年6月8日
SELECT DATEADD(mm,DATEDIFF(mm,0,getdate()),0) //首先选出当前月,然后把他转换为日期 select (2009-1900)*12 select DATEDIFF(mm,0,getdate())
posted @ 2009-06-08 14:39 华梦行 阅读(35) | 评论 (0)编辑 收藏
  2009年3月26日
 #define   WINVER   0x0050  
#define   WINVER   0x0500,这个表示是为Windows   2000编译,不保证Windows   98/NT4可以正常运行  

Windows   Server   2003  
  WINVER>=0x0502  
     
  Windows   XP    
  WINVER>=0x0501  
     
  Windows   2000  
  WINVER>=0x0500  
     
  Windows   NT   4.0  
  WINVER>=0x0400  
     
  Windows   Me  
  WINVER>=0x0500  
     
  Windows   98  
  WINVER>=0x0410  
     
  Windows   95  
  WINVER>=0x0400   
     
posted @ 2009-03-26 22:15 华梦行 阅读(8) | 评论 (0)编辑 收藏
  2009年3月24日

 // TODO: Add your message handler code here and/or call default
 /*HDC hdc;
 hdc=::GetDC(m_hWnd);
 MoveToEx(hdc,m_ptOrigin.x,m_ptOrigin.y,NULL);
 LineTo(hdc,point.x,point.y);
 ::ReleaseDC(m_hWnd,hdc);*/
 /*CDC *pDC=GetDC();
 pDC->MoveTo(m_ptOrigin);
 pDC->LineTo(point);
 ReleaseDC(pDC);*/

 //CClientDC dc(this);
 /*CClientDC dc(GetParent());
 dc.MoveTo(m_ptOrigin);
 dc.LineTo(point);*/

 //CWindowDC dc(this);
 //CWindowDC dc(GetParent());
 /*CWindowDC dc(GetDesktopWindow());
 dc.MoveTo(m_ptOrigin);
 dc.LineTo(point);*/
 /*CPen pen(PS_DOT,1,RGB(0,255,0));
 CClientDC dc(this);
 CPen *pOldPen=dc.SelectObject(&pen);
 dc.MoveTo(m_ptOrigin);
 dc.LineTo(point);
 dc.SelectObject(pOldPen);*/
// CBrush brush(RGB(255,0,0));

 /*CBitmap bitmap;
 bitmap.LoadBitmap(IDB_BITMAP1);
 CBrush brush(&bitmap);*/
 /*CClientDC dc(this);
 //dc.FillRect(CRect(m_ptOrigin,point),&brush);
 CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
 CBrush *pOldBrush=dc.SelectObject(pBrush);
 dc.Rectangle(CRect(m_ptOrigin,point));
 dc.SelectObject(pOldBrush);*/
 m_bDraw=FALSE;
 CView::OnLButtonUp(nFlags, point);

posted @ 2009-03-24 16:25 华梦行 阅读(6) | 评论 (0)编辑 收藏
  2009年3月19日

#include <iostream.h>
#include <string>
char* strToBinary(int x);
char* strToHex(int i);
char* transToGKB(char *t);

int main ()

{
char *p=strToBinary(233);
//cout<<p;

//cout<<strToHex(233);
cout<<transToGKB("商");
return 0;
}
char* transToGKB(char *t){
 int res=0;
   int intlen;
   intlen=strlen(t);
   if(intlen>1){
    char *result=new char[5];
   int i=0;
 if(0>t[0]){
    res=256+t[0];
  }
 char *p1=strToHex(res);
 if(0>t[1]){
  res=256+t[1];
 }
 char *p2=strToHex(res);
 result=p1;
 result[2]=p2[0];
 result[3]=p2[1]; 
 result[4]='\0'; 
 return result;
 }
   else{
    //if(t[0]>64)
    char * p=new char[3];
    p=strToHex(t[0]);
    p[2]='\0';
    return  p;
   }

}

 

//数字转为二进制(255以内的正数)
char* strToBinary(int i){
 char *result=new   char[9];
 int n=1;
 int m;
 int c=0;
 int j=8;
 for(c=0;c<8;c++){
  m=i%2;
  j=j-1;
  i=n=i/2;
  if(n>=0){
   if (m>0){
    result[j]='1';
   }else
   {
    result[j]='0';
   } 
  }
  
 }
 result[8]='\0';
 
// cout<<result;
 return result;
}
//数字转为十六进制(255以内的正数)
char* strToHex(int i){
 char *result=new   char[3];
 int n=1;
 int m;
 int c=0;
 int j=2;
 for(c=0;c<2;c++){
  m=i%16;
  j=j-1;
  i=n=i/16;
  if(n>=0){
   if (m>0){
    if (m==1){
                  result[j]='1';
    }
    else if (m==2){
     result[j]='2';
    }
    else if (m==3){
     result[j]='3';
    }
    else if (m==4){
     result[j]='4';
    }
    else if (m==5){
     result[j]='5';
    }
    else if (m==6){
     result[j]='6';
    }
    else if (m==7){
     result[j]='7';
    }
    else if (m==8){
     result[j]='8';
    }
    else if (m==9){
     result[j]='9';
    }
    else if (m==10){
     result[j]='A';
    }
    else if (m==11){
     result[j]='B';
    }
    else if (m==12){
     result[j]='C';
    }
    else if (m==13){
     result[j]='D';
    }
    else if (m==14){
     result[j]='E';
    }
    else if (m==15){
     result[j]='F';
    }
   }else
   {
    result[j]='0';
   } 
  }
 }
 result[2]='\0';
 return result;
}

posted @ 2009-03-19 16:37 华梦行 阅读(24) | 评论 (0)编辑 收藏
  2009年3月17日

#include <iostream.h>

#include <string>
void yihuo(char *t,int n, int m);
void  myToBinary();
void transToGKB(char *t);
void  myToHex();
int main(){
//cout<<"GOOD";
int i=122;
int j=233;
int m=j^i;
 char t[128]={0xC9,0xCC,0xC9,0xCC,0xC9,0xCC,'\0',0xC9,0xCC,0xC9,0xCC};
yihuo(t,0, 2);
// transToGKB(t);
//cout<<m;
//yihuo(2, 3);
//myToHex();
// myToBinary();
cout<<endl;
return 0;
}
//进制之间的转换
  // 字符串传为16进制
void  myToHex(){
 char c[] = "CC";
 unsigned long tt= strtoul(c, NULL, 16);
cout<<strtol(c, NULL, 16);

}

// 字符串传为16进制
void  myToBinary(){
 char c[] = "10000000";
// unsigned long tt= strtoul(c, NULL, 2);
 cout<<strtol(c, NULL, 2);
 
 
}
//汉字的转换, 16进制转换为汉字
void transToGKB(char *t){

 
 // char *t="商户";
 // CharN
 //如果是负数,则转为正整数
 // if(0>t[0]){
 //    res=256+t[0];
 // }
 int j=t[0];
 cout<<t<<endl;

}


void yihuo(char *t,int n, int m) {

 char *tt="商户说的算";
    //转成数字并且保存到数组,然后求异或

 //求的长度
const int mylen=strlen(tt)+1;
char mysplit[1000];
//循环对这个整形数组进行赋值
int i=0;
for(i<0;i<mylen-1;i++){
 if (tt[i]<0){
mysplit[i]=256+tt[i];
 }else
 {
mysplit[i]=tt[i];
 }
}
int result=mysplit[n-1];
int j;
for(j=n;j<n+m;j++){
 result=result^mysplit[n];
 cout<<"L"<<endl;
cout<<mysplit[n];
 cout<<"M"<<endl;

}

//进行遍历求异或
mysplit[mylen-1]='\0';
cout<<mysplit;

cout<<"ee";
if(result<0)
result=256+result;
cout<<result;
 //int j=t[0];
// cout<<t<<endl;

 }

 

posted @ 2009-03-17 17:47 华梦行 阅读(21) | 评论 (0)编辑 收藏
  2009年3月14日
Integer.parseInt(String.valueOf(o));
posted @ 2009-03-14 15:21 华梦行 阅读(61) | 评论 (0)编辑 收藏
  2009年3月11日

Private Sub UserControl_ReadProperties(PropBag As PropertyBag)


 Text1.Text = PropBag.ReadProperty("RecordSource", _
       m_def_recordSource)
  Text2.Text = PropBag.ReadProperty _
   ("ConnectionString", m_def_connectionString)


End Sub

posted @ 2009-03-11 22:27 华梦行 阅读(9) | 评论 (0)编辑 收藏
  2009年3月10日

int main(void)
{
   int m=4;
   int nn;
   int  *n;
   int *s;
   int *p;
   int *q;
   n=&m;
  
  nn=n;
   q=n;
  s=nn;
   printf("%08x",*s);


   return 0;
}

posted @ 2009-03-10 21:45 华梦行 阅读(4) | 评论 (0)编辑 收藏

0xFFFFFF20  数据输入缓冲区
0xFFFFFF24  输出数据缓冲区   
0xFFFFFF28  控制寄存器

posted @ 2009-03-10 16:51 华梦行 阅读(5) | 评论 (0)编辑 收藏
1.程序段:程序段为程序代码在内存中的映射.一个程序可以在内存中多有个副本.
2.初始化过的数据:在程序运行值初已经对变量进行初始化的
3.未初始化过的数据:在程序运行初未对变量进行初始化的数据
4.堆(stack):存储局部,临时变量,在程序块开始时自动分配内存,结束时自动释放内存.存储函数的返回指针.
5.栈(heap):存储动态内存分配,需要程序员手工分配,手工释放.
 

# include <stdio.h>

int g1=0, g2=0, g3=0;

intmax(int i)
{
    int m1=0,m2,m3=0,*p_max;
    static n1_max=0,n2_max,n3_max=0;
    p_max =(int*)malloc(10);
    printf("打印max程序地址\n");
    printf("in max: 0x%08x\n\n",max);
    printf("打印max传入参数地址\n");
    printf("in max: 0x%08x\n\n",&i);
    printf("打印max函数中静态变量地址\n");
    printf("0x%08x\n",&n1_max);//打印各本地变量的内存地址
    printf("0x%08x\n",&n2_max);
    printf("0x%08x\n\n",&n3_max);
    printf("打印max函数中局部变量地址\n");
    printf("0x%08x\n",&m1);//打印各本地变量的内存地址
    printf("0x%08x\n",&m2);
    printf("0x%08x\n\n",&m3);
    printf("打印max函数中malloc分配地址\n");
    printf("0x%08x\n\n",p_max);//打印各本地变量的内存地址

    if(i)return 1;
    elsereturn 0;
}

int main(int argc,char**argv)
{
staticint s1=0, s2, s3=0;
int v1=0, v2, v3=0;
int*p;    
p =(int*)malloc(10);

printf("打印各全局变量(已初始化)的内存地址\n");
printf("0x%08x\n",&g1);//打印各全局变量的内存地址
printf("0x%08x\n",&g2);
printf("0x%08x\n\n",&g3);
printf("======================\n");
printf("打印程序初始程序main地址\n");
printf("main: 0x%08x\n\n", main);
printf("打印主参地址\n");
printf("argv: 0x%08x\n\n",argv);
printf("打印各静态变量的内存地址\n");
printf("0x%08x\n",&s1);//打印各静态变量的内存地址
printf("0x%08x\n",&s2);
printf("0x%08x\n\n",&s3);
printf("打印各局部变量的内存地址\n");
printf("0x%08x\n",&v1);//打印各本地变量的内存地址
printf("0x%08x\n",&v2);
printf("0x%08x\n\n",&v3);
printf("打印malloc分配的堆地址\n");
printf("malloc: 0x%08x\n\n",p);
printf("======================\n");
    max(v1);
printf("======================\n");
printf("打印子函数起始地址\n");
printf("max: 0x%08x\n\n",max);
return 0;
}

 

这个程序可以大致查看整个程序在内存中的分配情况:
可以看出,传入的参数,局部变量,都是在栈顶分布,随着子函数的增多而向下增长.
函数的调用地址(函数运行代码),全局变量,静态变量都是在分配内存的低部存在,而malloc分配的堆则存在于这些内存之上,并向上生长

posted @ 2009-03-10 15:40 华梦行 阅读(21) | 评论 (0)编辑 收藏

#include <stdio.h>
#include <string.h>
hello(){
char *hello="dddd大点的";
int i;
for(i=0;i<strlen(hello);i++){
printf("%s\n",&hello[i]);
}
}
void testStr(){
int i=0;
 for(i=0;i<128;i++)
 {
printf("%c",(char)i);
 }
}
void testmy(){
 char *hello="??大点的";

 char hellodd[]={hello};
 unsigned char test= hellodd[2];
 if(test>137){

 printf("大于%u",test);
 }else
 {
  printf("小于");
 }
//putchar((char)hello[5]);
printf("字符:%d \n",hellodd[2]);
printf("%d",strlen( hellodd));

}
//相当于substring
teststrcopy(){
char *s="到的得到";
char d[]={"  "};
//strncpy(d,s+0,2);
strncpy(d,s,2);
printf("%s\n",d);
}
int main(void){
//testmy();
 //teststrcopy();
 return 0;
}


 

posted @ 2009-03-10 15:23 华梦行 阅读(3) | 评论 (0)编辑 收藏

void testmy(){
 char *hello="??大点的";

 char hellodd[]={hello};
 unsigned char test= hellodd[2];
 if(test>137){

 printf("大于%u",test);
 }else
 {
  printf("小于");
 }
//putchar((char)hello[5]);
printf("字符:%d \n",hellodd[2]);
printf("%d",strlen( hellodd));

}

posted @ 2009-03-10 13:48 华梦行 阅读(6) | 评论 (0)编辑 收藏
  2009年3月9日

用记事本编辑修改一下,选择后面的空格得去掉

posted @ 2009-03-09 14:20 华梦行 阅读(5) | 评论 (0)编辑 收藏
  2009年3月5日
function launch(program){
(new ActiveXObject('WScript.Shell')).Run(program);void(0);
}
posted @ 2009-03-05 12:00 华梦行 阅读(11) | 评论 (0)编辑 收藏
  2009年2月15日
req.getDispatcher("/a/b/c.jsp").forward(req,res);
posted @ 2009-02-15 15:36 华梦行 阅读(57) | 评论 (0)编辑 收藏
  2009年2月13日
try{  
          ……      
          con.setAutoCommit(false);  
          ……     //执行你的任务  
          ……  
          con.commit();  
          con.setAutoCommit(true);  
   
  }catch(SQLException   ex)   {  
          System.err.println("SQLException:   "   +   ex.getMessage());  
                  if   (con   !=   null)   {  
      try   {  
          System.err.print("Transaction   is   being   ");  
          System.err.println("rolled   back");  
          con.rollback();  
        }   catch(SQLException   excep)   {  
  System.err.print("SQLException:   ");  
  System.err.println(excep.getMessage());  
  }  
  }  
  }
finaly{
try{
if(con   !=   null){
con.close();
}
setAutoCommit(false)之后,你必须手工调用commit   或者   rollback来确认事务是提交还是回滚  
  最好再调用setAutoCommit(true)来关闭事务状态再close连接
posted @ 2009-02-13 11:42 华梦行 阅读(186) | 评论 (0)编辑 收藏
  2009年2月7日

带有finally的方法的反编译之后

throw  exception;    这个语句。
posted @ 2009-02-07 15:16 华梦行 阅读(63) | 评论 (0)编辑 收藏
仅列出标题  下一页