显示标签为“技术用”的博文。显示所有博文
显示标签为“技术用”的博文。显示所有博文

2009年5月7日星期四

继续折腾:通过Web方式修改Linux用户密码的PHP脚本

搞了一晚上,现在有点头晕,可能写的不是很清楚,有些遗漏,有问题请留言,谢谢。

昨天协会到了一台服务器,在我的强烈要求下跑了Linux,然后有人提出来可不可以在上面跑个Apache+PHP给小朋友们做主页玩,我觉得这个想法不错于是开搞。最开始是计划用proftpd进行文件上传的,但是配置了半天,发现使用mysql后端的proftpd性能不是一般的差- -||,进行个ls都要等5秒以上,Google无果,以前也没有接触过这玩意儿,所以决定换回相对熟悉的vsftpd,外加apache的userdir模块,然后使用系统帐号进行控制(我知道vsftpd也能用mysql做后端,但是一样没用过)。
但是只用系统帐号就存在一个密码更改的问题,总不能人家每次想改个密码都要告诉我手工改吧(不想给他们开ssh权限)。于是,下面这个玩意儿出现了。
首先是代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh_cn" lang="zh_cn">
<head>
<title>修改登录密码</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<?php
$u = $_POST['u'];
$o = $_POST['o'];
$p = $_POST['p'];
$r = $_POST['r'];
$error = 'no error';
if ($u){
echo '<pre>';
if($p!=$r){
echo 'ERROR: Password not match, please check your input.';
//}else if($u!=$_SERVER['PHP_AUTH_USER']){
// echo 'ERROR: Unable to change other\'s password.';
}else if(!$p){
echo 'ERROR: New Password is empty!';
}else if(strpbrk($p,"\r\n")||strpbrk($u,"\r\n")){
echo 'ERROR: Account name and password should not contain \r or \n!';
}else{
echo "Tying to change password for {$u}...\n";
if(!pam_auth($u,$o,$error)){
echo "ERROR: $error\n";
}else{
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a file to write to
);

$cwd = NULL;
$env = NULL;

$process = proc_open('sudo /usr/sbin/chpasswd', $descriptorspec, $pipes, $cwd, $env);
if (is_resource($process)) {
fprintf($pipes[0],"%s:%s\n",$u,$p);
fclose($pipes[0]);

echo stream_get_contents($pipes[1]),"\n";
fclose($pipes[1]);

echo stream_get_contents($pipes[2]),"\n";
fclose($pipes[2]);

$return_value = proc_close($process);

echo "Command returned $return_value";
}
}
}
echo '</pre>';
}else{
$u=$_SERVER['PHP_AUTH_USER'];
}
?>
<form method=POST><table>
<tr><td colspan=2 align='center'>修改用户口令</td></tr>
<tr><td>用户名: </td><td><input type=text name='u' value='<? echo $u; ?>'/></td></tr>
<tr><td>原密码: </td><td><input type=password name='o'/></td></tr>
<tr><td>新密码: </td><td><input type=password name='p'/></td></tr>
<tr><td>重复密码:</td><td><input type=password name='r'/></td></tr>
<tr><td colspan=2 align='center'><input type=submit value='确认'>  <input type=reset value='取消'></td></tr>
</table></form>
</body>
</html>

然后讲一下如何配置:
首先,这里用到了一个php扩展:pam,我的服务器是ubuntu的,装这个东西时也废了不少功夫,首先apt-get install php-pear 然后还要装几个dev库(php5-dev libpam-dev)然后就可以用pear pam下载编译这个叫pam的扩展了。
装好这个还要设置:新建/etc/pam.d/php,内容从login文件里面复制就可以了,或者你可以试试他说明文档中提到的那个:

# /etc/pam.d/php
#
# note: both an auth and account entry are required

auth sufficient /lib/security/pam_pwdb.so shadow nodelay
account sufficient /lib/security/pam_pwdb.so

这个我试过,好像不能用,不知道是不是版本的问题。
然后麻烦的事情来了,要设置其他用户的密码,必然需要root权限,假如我们给予了apache的用户某些特权,必然会对整个系统的安全造成影响,尤其是我现在还允许用户上传自己的PHP,而PHP的disable_functions只能全局设置,所以我觉得从根本上吧用户和特权基本分类:使用多个Apache实例,每个实例使用不同的php配置。
设置参考这个帖子:http://blog.datajelly.com/company/blog/46-multiple-apache-instances-in-ubuntu.html
我是这样设置的:新的apache只监听SSL的443端口,使用web_admin这个低权账户登录系统(而用户使用的旧apache使用www-data),最小化配置(关闭所有无用的modules),关闭php的安全设置。在独立Php配置时使用了PhpIniDir这个httpd.conf的参数,用过win32下的apache+php的应该熟悉。

使用visudo修改/etc/sudoer这个文件,在文件的最后添加:
web_admin ALL=NOPASSWD: /usr/sbin/chpasswd

注意这里一定要用visudo这个东西修改,不要直接打开!
这样就赋予了web_admin这个用户使用root权限运行/usr/sbin/chpasswd的权限了,比直接设置sid之类的方法要安全一些

这样一来,整个系统就大概可以投入使用了。

2009年2月28日星期六

建立Ubuntu APT镜像服务器

由于学校的网络环境特殊,造成部分Linux用户无法正常上网更新,于是在学校内搭建了一个Apt的镜像服务器,现在吧搭建过程交代一下:
首先先装好了一个ubuntu server系统,装了个lighttpd服务器(个人喜好而已,其实真正使用时应该用apache)

#apt-get install lighttpd

然后是apt-mirror

#apt-get install apt-mirror

配置apt-mirror:

#pico /etc/apt/mirror.list

文件内容:

############# config ##################
#
# set base_path /var/spool/apt-mirror
#
# if you change the base path you must create the directories below with write privileges
#
# set mirror_path $base_path/mirror
# set skel_path $base_path/skel
# set var_path $base_path/var
# set cleanscript $var_path/clean.sh
# set defaultarch
set nthreads 40
set _tilde 0
#
############# end config ##############

deb http://mirrors.163.com/ubuntu/ intrepid main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ intrepid-security main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ intrepid-updates main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ intrepid-proposed main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ intrepid-backports main restricted universe multiverse

clean http://mirrors.163.com/ubuntu/

其实就是source.list的内容,加上一头一尾。
因为需求比较少,我就没加deb-src,可以节省不少空间。另外,默认是20线程的,我改成了40线程。
修改好了以后就可以建立镜像了:

#apt-mirror

根据你的网速,这个下载过程会很久(因为要下载的东西很多,我的配置文件需要下载26G左右的数据)。我等了6个小时左右完成了这个操作。
下载的文件保存在/var/spool/apt-mirror/ 下一步就是在lighttpd的web文件夹建立一个link过去就可以了

#ln -s /var/spool/apt-mirror/mirror/mirrors.163.com /var/www/ubuntu

这样,一个apt镜像就可以访问了,客户端只需要修改它的/etc/apt/source.list就可以了,替换一下服务器地址就行。

另外,还要建立一个定期任务,让apt-mirror自动更新:
修改/etc/cron.d/apt-mirror:

0 4 * * * apt-mirror /usr/bin/apt-mirror > /var/spool/apt-mirror/var/cron.log


另外,/var/spool/apt-mirror/var/clean.sh可以清理掉一些没用的文件,每隔一段时间运行一下就行。

2008年11月6日星期四

C++表达式计算

又是给某人写的作业...

#include
#include
#include
#include
#include
#include
#include
using namespace std;
//栈内优先级
map isp;
//入栈优先级
map icp;
//分离表达式
list Tokenize(const string& str,const string& delimiters){
list tokens;
string::size_type delimPos = 0, tokenPos = 0, pos = 0;
if(str.length()<1) return tokens;
while(1){
delimPos = str.find_first_of(delimiters, pos);
tokenPos = str.find_first_not_of(delimiters, pos);
if(string::npos != delimPos){
if(string::npos != tokenPos){
if(tokenPos tokens.push_back(str.substr(pos,delimPos-pos));
}else{
tokens.push_back("");
}
}else{
tokens.push_back("");
}
pos = delimPos+1;
} else {
if(string::npos != tokenPos){
tokens.push_back(str.substr(pos));
} else {
tokens.push_back("");
}
break;
}
}
return tokens;
}
inline bool isNumber(char c){
return c=='.'||'0'<=c&&c<='9';
}
inline bool isSymbol(string op) {
return op=="+"||op=="-"||op=="*"||op=="/"||op=="%"||op=="^"||op=="("||op==")";
}
inline bool isSpace(char c){
return c==' '||c=='\t'||c=='\n'||c=='\r';
}
//格式化&分离表达式
list toExpressionArray(string exp){
string fexp;
const char* data = exp.data();
int idx = 0;
int len = exp.length();
for(;idx char c = data[idx];
if(isNumber(c)){
fexp.push_back(c);
}
else if(!isSpace(c)){
fexp.push_back(' ');
fexp.push_back(c);
fexp.push_back(' ');
}
}
return Tokenize(fexp," ");
}

//中序换前序
list m2l(list m) {
list l;
stack s;
string op;
string y;
s.push("#");
for(list::const_iterator i = m.begin();i!=m.end();i++){
op = *i;
if(op=="")
continue;
if(!isSymbol(op)){
l.push_back(op);
}else{
if(icp[op]>isp[s.top()]){
s.push(op);
}else if(op==")"){
y = s.top();
s.pop();
while(y!="("){
l.push_back(y);
y = s.top();
s.pop();
}
}else{
y = s.top();
s.pop();
while(icp[op] l.push_back(y);
y = s.top();
s.pop();
}
s.push(y);
s.push(op);
}//if2
}//if1
}//for
while(s.top()!="#"){
l.push_back(s.top());
s.pop();
}
return l;
}
//计算
double _calc(double o1, double o2, char op) {
switch(op){
case '+':{
return o1+o2;
}
case '-':{
return o1-o2;
}
case '*':{
return o1*o2;
}
case '/':{
return o1/o2;
}
case '^':{
return pow(o1,o2);
}
}
return 0;
}
//计算前序表达式
double calc(list in){
stack sk;
for(list::const_iterator i = in.begin();i!=in.end();i++){
string op = *i;
if(!isSymbol(op)){
sk.push(atof(op.data()));
}else{
double o2 = sk.top();
sk.pop();
double o1 = sk.top();
sk.pop();
char o = op.data()[0];
double r = _calc(o1,o2,o);
sk.push(r);
}
}
return sk.top();
}
//没用
void printList(list l){
int x=0;
for(list::const_iterator i = l.begin();i!=l.end();i++){
cout<<++x<<":"<<*i< }
}
int main()
{
isp["+"]=3;
isp["-"]=3;
isp["*"]=5;
isp["/"]=5;
isp["%"]=5;
isp["^"]=5;
isp["("]=1;
isp[")"]=6;
isp["#"]=0;

icp["+"]=2;
icp["-"]=2;
icp["*"]=4;
icp["/"]=4;
icp["%"]=4;
icp["^"]=4;
icp["("]=6;
icp[")"]=1;
icp["#"]=0;

string exp;
cout<<"Exp:";
cin>>exp;
list l1 = toExpressionArray(exp);
printList(l1);
list l2 = m2l(l1);
printList(l2);
double r = calc(l2);
cout<<"Result:"< return 0;
}

2008年11月4日星期二

超长字段数据库删除重复记录

今天接到一个CASE,在遥远的德国有一个1.1GB的MYSQL数据库,里面只有一个表,表里记录了若干很大的text字段和Blob字段,要求把某个text字段的重复项删除。(表里有上百万的记录,需要排除重复的字段平均大小在1K左右,带索引)
表结构如下:

DROP TABLE IF EXISTS `cert_db`.`certs`;
CREATE TABLE `cert_db`.`certs` (
`id` int(10) unsigned NOT NULL auto_increment,
`ServerName` varchar(128) default NULL,
`SerialNumber` blob NOT NULL,
`Version` int(10) unsigned NOT NULL,
`NotBefore` datetime NOT NULL,
`NotAfter` datetime NOT NULL,
`CertData` blob NOT NULL,
`IPK_DUMP` blob,
`IPK_N` blob,
`IPK_E` blob,
`Signature` blob NOT NULL,
`SigAlgName` varchar(50) default NULL,
`SigAlgOID` varchar(50) default NULL,
`SigAlgParams` blob,
`SubjectDN` text,
`IssuerDN` text,
`CertDump` text,
`SPK_DUMP` blob,
`SPK_N` blob,
`SPK_E` blob,
`ReallyBAD` char(1) default NULL,
`X509Dump` blob NOT NULL,
PRIMARY KEY (`id`),
KEY `IDX_CERTDN` USING HASH (`SubjectDN`(383))
) ENGINE=InnoDB AUTO_INCREMENT=4515 DEFAULT CHARSET=gbk;

第一次尝试使用如下语句:

delete from certs
where subjectdn in (
select subjectdn from certs
group by
subjectdn
having
count(subjectdn) > 1
)
and id not in (
select min(id) from certs
group by
certs
having
count(certs)>1
)

结果在本地的4000条记录上测试时就华丽的让MYSQL 挂掉了。。
然后使用md5进行索引,分步骤删除,成功:

#hash all
DROP TABLE IF EXISTS hash_all;
create table hash_all(
select id,md5(subjectdn) as dnhash from certs
);
#find dupclited
DROP TABLE IF EXISTS hash_dup;
create table hash_dup(
select id,dnhash from hash_all
group by
dnhash
having
count(dnhash)>1
);
#list rows to delete
DROP TABLE IF EXISTS hash_del;
create table hash_del(
select h.id,h.dnhash from hash_all h,hash_dup d
where
h.id!=d.id
and
h.dnhash=d.dnhash
);
#delete
delete c from certs c,hash_del d where c.id=d.id;
#then clean up
DROP TABLE IF EXISTS hash_all;
DROP TABLE IF EXISTS hash_dup;
DROP TABLE IF EXISTS hash_del;

2008年10月1日星期三

JFreeChart的实时曲线图的例子

好久没更新过技术相关的东西了,今天要开始写管理控制台,里面需要用到实时曲线图,决定使用JFreeChart做,这东西还真的挺好用,简单的几句代码就能搭建一个看起来不错的程序:-)
预览图:



/*
* RealTimeGraphPanel.java
*
* Created on 2008年10月1日, 下午2:04
*/
package cn.bearice.ipcontroller.manager.graph;

import cn.bearice.java.util.StopableThread;
import java.awt.BorderLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.time.Second;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;

/**
*
* @author Bearice
*/
public class RealTimeGraphPanel extends javax.swing.JPanel {

private static final long serialVersionUID = 8250077948583513551L;
TimeSeriesCollection tcl;
ChartPanel cp;
JFreeChart jfc;
ArrayList tss = new ArrayList();
TimeSeries tts = new TimeSeries("Avg.", Second.class);

/** Creates new form RealTimeGraphPanel */
public RealTimeGraphPanel() {
tcl = new TimeSeriesCollection();
tcl.addSeries(tts);
addTimeSeries();
jfc = ChartFactory.createTimeSeriesChart("Random Numbers", "Time", "Value", tcl, true, true, false);
XYPlot xyplot = jfc.getXYPlot();
ValueAxis valueaxis = xyplot.getDomainAxis();
valueaxis.setAutoRange(true);
// valueaxis.setInverted(true);
valueaxis = xyplot.getRangeAxis();
valueaxis.setAutoRange(true);
cp = new ChartPanel(jfc);
initComponents();
}

public void addTimeSeries() {
TimeSeries timeSeries = new TimeSeries("Random" + tss.size(), Second.class);
tcl.addSeries(timeSeries);
tss.add(timeSeries);
}

public static void main(String[] args) {
JFrame frame = new JFrame("Test Chart");
final RealTimeGraphPanel rtgp = new RealTimeGraphPanel();
frame.getContentPane().add(rtgp, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);

frame.addWindowListener(new WindowAdapter() {

@Override
public void windowClosing(WindowEvent windowevent) {
System.exit(0);
}
});
}

/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
//
private void initComponents() {

jbtnStartStop = new javax.swing.JButton();
jPanel1 = (cp);
jbtnFixed = new javax.swing.JButton();
jbtnScaled = new javax.swing.JButton();
jbtnClear = new javax.swing.JButton();
jbtnAdd = new javax.swing.JButton();

setName("Form"); // NOI18N

jbtnStartStop.setText("Start");
jbtnStartStop.setName("jbtnStartStop"); // NOI18N
jbtnStartStop.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jbtnStartStopActionPerformed(evt);
}
});

jPanel1.setName("jPanel1"); // NOI18N

javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 261, Short.MAX_VALUE)
);

jbtnFixed.setText("Fixed");
jbtnFixed.setName("jbtnFixed"); // NOI18N
jbtnFixed.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jbtnFixedActionPerformed(evt);
}
});

jbtnScaled.setText("Scaled");
jbtnScaled.setName("jbtnScaled"); // NOI18N
jbtnScaled.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jbtnScaledActionPerformed(evt);
}
});

jbtnClear.setText("Clear");
jbtnClear.setName("jbtnClear"); // NOI18N
jbtnClear.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jbtnClearActionPerformed(evt);
}
});

jbtnAdd.setText("Add");
jbtnAdd.setName("jbtnAdd"); // NOI18N
jbtnAdd.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jbtnAddActionPerformed(evt);
}
});

javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jbtnStartStop)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jbtnFixed)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jbtnScaled)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jbtnClear)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jbtnAdd)
.addContainerGap(53, Short.MAX_VALUE))
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jbtnStartStop)
.addComponent(jbtnFixed)
.addComponent(jbtnScaled)
.addComponent(jbtnClear)
.addComponent(jbtnAdd))
.addContainerGap())
);
}//


class T1 extends StopableThread {

@Override
protected void loop() throws InterruptedException {
for (TimeSeries timeSeries : tss) {
double d = randomNum();
num = (num + d) / 2;
timeSeries.add(new Second(), d);
}
tts.add(new Second(), num);
Thread.sleep(1000);
}
double num;
Random rand = new Random();

private double randomNum() {
return rand.nextGaussian();
}
}
T1 t1;
private void jbtnStartStopActionPerformed(java.awt.event.ActionEvent evt) {
if (t1 == null) {
t1 = new T1();
t1.start();
jbtnStartStop.setText("stop");
} else {
t1.exit();
t1 = null;
jbtnStartStop.setText("start");
}
}

private void jbtnFixedActionPerformed(java.awt.event.ActionEvent evt) {
// cp.setAutoscrolls(true);
XYPlot xyplot = jfc.getXYPlot();
ValueAxis valueaxis = xyplot.getDomainAxis();
valueaxis.setFixedAutoRange(50000);
}

private void jbtnScaledActionPerformed(java.awt.event.ActionEvent evt) {
XYPlot xyplot = jfc.getXYPlot();
ValueAxis valueaxis = xyplot.getDomainAxis();
valueaxis.setFixedAutoRange(0);
}

private void jbtnClearActionPerformed(java.awt.event.ActionEvent evt) {
for (TimeSeries timeSeries : tss) {
timeSeries.clear();
}
}

private void jbtnAddActionPerformed(java.awt.event.ActionEvent evt) {
addTimeSeries();
}
// Variables declaration - do not modify
private javax.swing.JPanel jPanel1;
private javax.swing.JButton jbtnAdd;
private javax.swing.JButton jbtnClear;
private javax.swing.JButton jbtnFixed;
private javax.swing.JButton jbtnScaled;
private javax.swing.JButton jbtnStartStop;
// End of variables declaration
}

PS:里面用到了一个自己实现的StopableThread,代码如下:

/*
* Copyrights (C) 2008 Bearice (Bearice@Gmail.com)
* Release under GNU/GPL Version 2.
*/
package cn.bearice.java.util;

/**
* 可以停止的线程。
* @author Bearice
*/
public abstract class StopableThread extends Thread {

protected boolean exit = false;

/**
* 线程开始后,进入循环前调用。
*/
protected void before() {
//System.out.println("Starting thread: "+this);
}

/**
* 线程退出前,循环退出后调用。
*/
protected void after() {
// System.out.println("Ending thread: "+this);
}

/**
* 循环体代码。
* @throws java.lang.InterruptedException
*/
protected abstract void loop() throws InterruptedException;

/**
* 指示线程退出循环。
*/
public synchronized void exit() {
exit = true;
interrupt();
}

@Override
public final void run() {
before();
while (!exit) {
try {
loop();
} catch (InterruptedException ex) {
}
}
after();
}
}

2008年8月30日星期六

血的教训告诉我们:最好不要多线程共享一个数据库连接

今天遇到一个奇怪的问题,有一段程序,会被多个线程并行访问,因为没有涉及到数据库写操作,所以就偷懒共享了一个DAO,但是发现一旦并发线程超过2个,就会出现异常,而且还是很奇怪的异常,好像是说数据库没有响应。调试了半天没发现什么问题,后来把DAO对象设置成了ThreadLocal,问题消失了……看来用一个数据库连接最好不要被多个线程同时操作。
最近经常在多线程同步上出问题,上午的时候出现了一个锁死的问题:有一个调用在一个同步块里面锁死了(因为另一个线程异常退出了,没有给这个线程解锁)然后后面紧跟着所有线程都停在了同步块上。在Profiler里一片红红的,好不壮观。
话说今天这个锁死的解决多亏了jconsoler和jvisualvm,这两个工具是最新的几个JDK版本才提供的,他们提供了对Java应用程序的本地或远程的即使监视和分析的功能。jconsole可以把每个线程的运行状态和阻塞原因打印出来,配合jvisualvm的可视化线程图表就可以确定到底是那个(些)线程进行了锁死,并有针对性的进行修复。
再废话一句:这两个工具好像都是基于JMX的,所以说JMX真是个好物啊~~

2008年7月23日星期三

我发现我爱上nio了

Java从1.4版本以后提供的新特性:nio我以前一直没用过。写网络程序时一直喜欢用Socket+Stream的阻塞IO。但是这回接的任务要求很高并发和性能的网络交互处理……用原来的阻塞IO肯定不行(总不能一个链接就用一个专用线程处理吧……就是用线程池也架不住好几千的线程一起运行吧= =||,所以用了一下nio的非阻塞io模型,起初发现nio很麻烦,什么事情都要求手动同步,并且很容易出bug...经常是出了bug都不知道从哪里下手(因为程序逻辑和同步顺序的原因)……而且经常出现调用某个函数,必须使用某个特定 的线程的情况,处理异常不得不使用大量的回掉函数……
总之,吧某熊搞的是精疲力竭的……
不过自打用上了Mina,情况改变了(怎么感觉像托儿= =||)。Mian吧nio进行了包装,并对网络IO处理进行了分层处理,采用了事件化的io模型。感觉就和用vb的winsock控件一样,而且比winsock的要简单且功能丰富。再也不用为了大量的同步,回掉和异常处理而操心了,oh,yeah~
Mian是apache的开源项目,网站是http://mina.apache.org 有兴趣的不妨看看~

2008年7月22日星期二

多线程环境下的性能杀手:日志记录

今天晚上做了一个性能测试,发现在同等条件下,使用日志的处理速度比关闭所有日志慢了接近10被。
我的应用是一个基于Mian的多线程服务器程序,在打开日志时进行压力测试,发现Profiler的Thread图表基本上是万里河山一片红:
根据图例,红色代表线程正在等待某个监视器。但是我的事务代码基本上没有什么使用同步的东西啊……数据库连接也用ThreadLocal了,不应该出现这种情况啊。
经过检查,发现拖慢处理速度的就是平时经常使用的日志。因为Logger的方法都是同步的(或者从根本上说,System.out和System.err就是一个同步的输出流)所以造成线程在处理时不得不停下来等待其他线程完成日志输出。就算不用Logger,用System.out.println()也是一样的道理。
编辑日志属性,关闭所有日志以后,性能有了极大的提高,每秒能处理的请求数量提升了13倍左右。

2008年7月17日星期四

写代码一定要小心

今天写程序时遇到一个问题,调试了半天没头绪,结果最后发现是一个非常脑残的问题:
问题代码如下:
        PreparedStatement ps = conn.prepareStatement(
                "INSERT INTO certs"/* +
                "   [CertData] ," +
                "   [PK_DUMP] ," +
                "   [PK_N],"+
                "   [PK_E],"+
                "   [Signature] ," +
                "   [Version] ," +
                "   [SigAlgName] ," +
                "   [SigAlgOID] ," +
                "   [SigAlgParams] ," +
                "   [SubjectDN] ," +
                "   [IssuerDN] ," +
                "   [CertDump] ," +
                "   [ServerName]) "*/ +
                "VALUES (null , ? , ? , ? ,? , ? , ? , ? , ? , ? , ? , ? , ? , ?)");
        ps.setBytes(1, cert.getTBSCertificate());
        System.out.println(cert.getTBSCertificate().length);
        if (goodPk) {
            ps.setBytes(2, pk.getEncoded());
            System.out.println(pk.getEncoded().length);
        } else {
            ps.setBytes(2, null);
        }
        if (rsapk != null) {
            ps.setBytes(3, rsapk.getModulus().toByteArray());
            ps.setBytes(4, rsapk.getPublicExponent().toByteArray());
        } else {
            ps.setBytes(3, null);
            ps.setBytes(4, null);
        }
        ps.setBytes(5, cert.getSignature());
        System.out.println(cert.getSignature().length);
        ps.setInt(6, cert.getVersion());
        ps.setString(7, cert.getSigAlgName());
        System.out.println(cert.getSigAlgName().length());
        ps.setString(8, cert.getSigAlgOID());
        System.out.println(cert.getSigAlgOID().length());
        if (cert.getSigAlgParams() != null) {
            ps.setBytes(9, cert.getSigAlgParams());
            System.out.println(cert.getSigAlgParams().length);
        } else {
            ps.setBytes(9, null);
        }
        ps.setString(10, cert.getSubjectDN().toString());
        System.out.println(cert.getSubjectDN().toString().length());
        ps.setString(11, cert.getIssuerDN().toString());
        System.out.println(cert.getIssuerDN().toString().length());
        ps.setString(12, cert.toString());
        ps.setString(13, serverName);
        return ps.executeUpdate();
你能看出这段代码有什么问题么?无论怎么改,总是提示我SQL语法出错=,=
看出来了么?问题在这里:
        PreparedStatement ps = conn.prepareStatement(
                "INSERT INTO certs"/* +
                "   [CertData] ," +
                "   [PK_DUMP] ," +
                ...
                "   [ServerName]) "*/ +
                "VALUES (null , ? , ? , ? ,? , ? , ? , ? , ? , ? , ? , ? , ? , ?)");
吧列名注释掉以后,表名和VALUES关键字后面少了一个空格!,所以无论如何都会出错……
所以遇到什么奇怪的问题时不妨仔细看看代码……不要像我今天这么脑残~~

2008年7月6日星期日

某人的作业,有用的拿走

今天下午五分钟给某人赶制的作业一份,有需要的拿走。
关键字:java socket tcp 文件传输


import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.Socket;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;

/*
* Copyrights (C) 2008 Bearice (Bearice@Gmail.com)
* Release under GNU/GPL Version 2.
*/
/**
*
* @author Bearice
*/
public class FileTransport {

public static void send(String args[]) throws Exception {
String host = args[1];
int port = Integer.parseInt(args[2]);
System.out.printf("Connecting to %s:%d\n", host, port);
Socket socket = SocketFactory.getDefault().createSocket(host, port);
FileInputStream fis = new FileInputStream(args[3]);
int b;
System.out.println("Sending...");
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
while ((b = fis.read()) != -1) {
bos.write(b);
}
bos.close();
}

public static void recv(String args[]) throws Exception {
int port = Integer.parseInt(args[1]);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(args[2]));
System.out.printf("Listening on port %d\n", port);
Socket socket = ServerSocketFactory.getDefault().createServerSocket(port).accept();
int b;
InputStream is = socket.getInputStream();
System.out.println("Recving...");
while ((b = is.read()) != -1) {
bos.write(b);
}
bos.close();
}

public static void main(String[] args) throws Exception {
if (args.length == 4 && args[0].equalsIgnoreCase("send")) {
send(args);
} else if (args.length == 3 && args[0].equalsIgnoreCase("recv")) {
recv(args);
} else {
System.out.println("Usage:");
System.out.println("FileTransport [recv|send <host>] <port> <file>");
}
}
}

2008年7月5日星期六

使用Java进行双向认证的SSL链接以及使用OpenSSL生产证书链

这几天被SSL和证书搞得头昏脑胀的。不过还好终于把这个SSL搞定了。

用SSL进行双向身份验证意思就是在客户机连接服务器时,链接双方都要对彼此的数字证书进行验证,保证这是经过授权的才能够连接(我们链接一般的SSL时采用的是单向验证,客户机只验证服务器的证书,服务器不验证客户机的证书。而连接网上银行时使用的U盾就是用来存储进行双向验证所需要的客户端证书的)。

JDK里面内置了一个数字证书生产工具:keytool。但是这个工具只能生成自签名的数字证书。所谓自签名就是指证书只能保证自己是完整的,没有经过非法修改的。但是无法保证这个证书是属于谁的。其实用这种自签名的证书也是可以进行双向验证的(用keytool生成的自签名证书进行双向验证请看这里,向这位仁兄致意~:http://www.blogjava.net/stone2083/archive/2007/12/20/169015.html),但是这种验证有一个缺点:对于每一个要链接的服务器,都要保存一个证书的验证副本。而且一旦服务器更换证书,所有客户端就需要重新部署这些副本。对于比较大型的应用来说,这一点是不可接受的。所以就需要证书链进行双向认证。证书链是指对证书的签名又一个预先部署的,众所周知的签名方签名完成,这样每次需要验证证书时只要用这个公用的签名方的公钥进行验证就可以了。比如我们使用的浏览器就保存了几个常用的CA_ROOT。每次连接到网站时只要这个网站的证书是经过这些CA_ROOT签名过的。就可以通过验证了。

但是这些共用的CA_ROOT的服务不是免费的。而且价格不菲。所以我们有必要自己生成一个CA_ROOT的密钥对,然后部署应用时,只要把这个CA_ROOT的私钥部署在所有节点就可以完成验证了。要进行CA_ROOT的生成,需要OpenSSL(http://www.openssl.org/)。你也可以在http://www.slproweb.com/products/Win32OpenSSL.html找到Windows下的版本

安装好OpenSSL以后就可以生成证书链了,我写了一个BAT解决这些东西:



@echo off
:OpenSSL配置文件路径(如果出现“The organizationName field needed to be the same in the”之类的错误,请编辑此文件,修改CA_default节中的policy = policy_match为policy = policy_anything
set CONFIG="D:\OpenSSL\bin\openssl.cfg"
:服务器证书库密码
set PWD_SERVER_KS=serverks
:服务器证书密码
set PWD_SERVER_KEY=serverkey
:客户端证书库密码
set PWD_CLIENT_KS=clientks
:客户端证书密码
set PWD_CLIENT_KEY=clientkey
:有效期(天)
set VALIDITY=365
:服务器证书名
set SERVER_CERT_NAME=logon_server_private_key
:客户端证书名
set CLIENT_CERT_NAME=ipclient_private_key
:服务器证书库文件名
set SERVER_KEYSTORE_NAME=server.keystore
:客户端证书库文件名
set CLIENT_KEYSTORE_NAME=client.keystore

if not exist DemoCA (
mkdir DemoCA
mkdir DemoCA\certs
mkdir DemoCA\newcerts
echo CAFEBABE>DemoCA\serial
copy nul DemoCA\index.txt
)

if not exist ca.key (
echo Generating a ca root key file...
openssl req -new -x509 -keyout ca.key -out ca.crt -config %CONFIG%
) else (
echo ca.key already exists...
)

if not exist server.keystore (
echo Generating server's private key...
keytool -genkey -alias %SERVER_CERT_NAME% -validity %VALIDITY% -keyalg RSA -keysize 1024 -keystore %SERVER_KEYSTORE_NAME% -keypass %PWD_SERVER_KEY% -storepass %PWD_SERVER_KS%
) else (
echo server.keystore already exits...
)

if not exist client.keystore (
echo Generating client's private key...
keytool -genkey -alias %CLIENT_CERT_NAME% -validity %VALIDITY% -keyalg RSA -keysize 1024 -keystore %CLIENT_KEYSTORE_NAME% -keypass %PWD_CLIENT_KEY% -storepass %PWD_CLIENT_KS%
) else (
echo client.keystore already exits...
)

echo ========Finished key generation=========

if not exist %SERVER_CERT_NAME%.csr (
echo Generating server's singature request file...
keytool -certreq -alias %SERVER_CERT_NAME% -sigalg MD5withRSA -file %SERVER_CERT_NAME%.csr -keypass %PWD_SERVER_KEY% -storepass %PWD_SERVER_KS% -keystore %SERVER_KEYSTORE_NAME%
) else (
echo %SERVER_CERT_NAME%.csr already exits...
)

if not exist %CLIENT_CERT_NAME%.csr (
echo Generating client's singature request file...
keytool -certreq -alias %CLIENT_CERT_NAME% -sigalg MD5withRSA -file %CLIENT_CERT_NAME%.csr -keypass %PWD_CLIENT_KEY% -storepass %PWD_CLIENT_KS% -keystore %CLIENT_KEYSTORE_NAME%
) else (
echo %CLIENT_CERT_NAME%.csr already exits...
)

if not exist %SERVER_CERT_NAME%.crt (
openssl ca -in %SERVER_CERT_NAME%.csr -out %SERVER_CERT_NAME%.crt -cert ca.crt -keyfile ca.key -notext -config %CONFIG%
) else (
echo %SERVER_CERT_NAME%.crt already exits...
)

if not exist %CLIENT_CERT_NAME%.crt (
openssl ca -in %CLIENT_CERT_NAME%.csr -out %CLIENT_CERT_NAME%.crt -cert ca.crt -keyfile ca.key -notext -config %CONFIG%
) else (
echo %CLIENT_CERT_NAME%.crt already exits...
)

echo =========Finished ca root signaturing==========

echo Importing ca root certs into keystore...

keytool -import -v -trustcacerts -alias ca_root -file ca.crt -storepass %PWD_SERVER_KS% -keystore %SERVER_KEYSTORE_NAME%

keytool -import -v -trustcacerts -alias ca_root -file ca.crt -storepass %PWD_CLIENT_KS% -keystore %CLIENT_KEYSTORE_NAME%

echo Importing signatured keys...

keytool -import -v -alias %SERVER_CERT_NAME% -file %SERVER_CERT_NAME%.crt -keypass %PWD_SERVER_KEY% -storepass %PWD_SERVER_KS% -keystore %SERVER_KEYSTORE_NAME%

keytool -import -v -alias %CLIENT_CERT_NAME% -file %CLIENT_CERT_NAME%.crt -keypass %PWD_CLIENT_KEY% -storepass %PWD_CLIENT_KS% -keystore %CLIENT_KEYSTORE_NAME%

echo All done!

运行这个批处理,期间需要回答一些问题,然后就可以得到一些文件其中client.keystore是需要在客户端部署的,server.keystore是在服务器部署的。ca.*是root_ca的密钥文件。
然后可以用下面的代码测试:

/*
* Copyrights (C) 2008 Bearice (Bearice@Gmail.com)
* Release under GNU/GPL Version 2.
*/

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

/**
*
* @author Bearice
*/
public class NewClass extends Thread {

@Override
public void run() {
try {
sleep(100);
SSLContext ctx = SSLContext.getInstance("SSL");

KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

KeyStore ks = KeyStore.getInstance("JKS");
//KeyStore tks = KeyStore.getInstance("JKS");

ks.load(new FileInputStream("e:/certs/client.keystore"), "clientks".toCharArray());
//tks.load(new FileInputStream("e:/certs/tclient.keystore"), "clientks".toCharArray());

kmf.init(ks, "clientkey".toCharArray());
tmf.init(ks);

ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLSocketFactory factory = ctx.getSocketFactory();

SSLSocket socket = (SSLSocket) factory.createSocket("127.0.0.1", 4433);

showCerts(socket.getSession());

PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println("GET /index.html HTTP/1.0");
pw.println("Server: mail.google.com");
pw.println("Connection: close");
pw.println();
pw.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String ln;
while ((ln = in.readLine()) != null) {
System.err.println(ln);
}
} catch (Exception ex) {
Logger.getLogger(NewClass.class.getName()).log(Level.SEVERE, null, ex);
}
}

public static void showCerts(SSLSession session) {
X509Certificate cert = null;
try {
cert = (X509Certificate) session.getPeerCertificates()[0];
} catch (SSLPeerUnverifiedException e) {
e.printStackTrace();
System.err.println(session.getPeerHost() + " did not present a valid certificate");
//System.exit(1);
return;
}
System.out.println(session.getPeerHost() + " has presented a certificate belonging to" + "[" + cert.getSubjectDN() + "]\n" + "The certificate was issued by: \t" + "[" + cert.getIssuerDN() + "]");

}

public static void main(String[] args) throws Exception {
SSLContext ctx = SSLContext.getInstance("SSL");

KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

KeyStore ks = KeyStore.getInstance("JKS");
//KeyStore tks = KeyStore.getInstance("JKS");

ks.load(new FileInputStream("e:/certs/server.keystore"), "serverks".toCharArray());
//tks.load(new FileInputStream("e:/certs/tserver.keystore"), "serverks".toCharArray());

kmf.init(ks, "serverkey".toCharArray());
tmf.init(ks);

ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

SSLServerSocketFactory factory = ctx.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(4433);

serverSocket.setNeedClientAuth(true);

new NewClass().start();

SSLSocket socket = (SSLSocket) serverSocket.accept();
try {
socket.startHandshake();
} catch (Exception ex) {
System.out.println("Handshake failed: " + ex);
}

showCerts(socket.getSession());
socket.startHandshake();
PrintWriter out = new PrintWriter(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String ln;
while ((ln = in.readLine()) != null) {
System.out.println(ln);
if (ln.equals("")) {
break;
}
}
out.println("HTTP/1.1 200 OK");
out.println("Cache-Control: no-cache");
out.println("Pragma: no-cache");
out.println("Expires: Fri, 01 Jan 1990 00:00:00 GMT");
out.println("Content-Type: text/html; charset=UTF-8");
out.println("Date: Tue, 01 Jul 2008 11:56:42 GMT");
out.println("Server: BWS");
out.println("X-Powered-By: BWS");
out.println();
out.println("

hello world

");
out.close();
socket.close();
}
}


其他参考链接:
SSL-用Keytool和OpenSSL生成和签发数字证书
为高级 JSSE 开发人员定制 SSL

2008年7月3日星期四

Java在处理SSL时证书无效的问题,以及解决方法

Java内置了对HTTPS和SSL的支持,但是我在写客户端时遇到了一些问题,比如最大的问题就是在连接的服务器没有一个有效的(也就是经过根CA认证的)SSL证书时会报错。Google了一下,说有两种方式:一种是事先把目标证书导入Java的证书管理器里,另一种是强制忽略证书合法性检查。第一种方法不用对代码进行什么更改,只要运行一个命令就行了,但是缺点是每次服务器更换证书都要进行一次导入。
第二种方法需要更改一下代码,优点是可以应用于任何SSL证书,而且不用重复导入,缺点也很明显,降低了系统的安全性。
下面是第二钟方法的实例代码


try {
final SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(null, new TrustManager[]{new X509TrustManager() {

public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
//这里进行有效性检查,不抛出异常就算检查成功
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}, null);
sleep(100);
SSLSocketFactory factory = sslContext.getSocketFactory();
final SSLSocket socket = (SSLSocket) factory.createSocket("127.0.0.1", 4433);
SSLSession session = socket.getSession();
X509Certificate cert = null;
try {
cert = (X509Certificate) session.getPeerCertificates()[0];
} catch (SSLPeerUnverifiedException e) {
e.printStackTrace();
System.err.println(session.getPeerHost() + " did not present a valid certificate");
return;
}
System.out.println(session.getPeerHost() + " has presented a certificate belonging to" + "[" + cert.getSubjectDN() + "]\n" + "The certificate was issued by: \t" + "[" + cert.getIssuerDN() + "]");
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println("GET /index.html HTTP/1.0");
pw.println("Server: mail.google.com");
pw.println("Connection: close");
pw.println();
pw.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String ln;
while ((ln = in.readLine()) != null) {
System.err.println(ln);
}
} catch (Exception ex) {
Logger.getLogger(ServerLauncher.class.getName()).log(Level.SEVERE, null, ex);
}

浅析Java内部类与嵌套类

浅析Java内部类与嵌套类

Abstraction:

本文描述了Java语言的嵌入类、内部类的具体分类和行为表现。

1 概述

我们知道,Java是一种完全的面向对象的语言,作为对象的灵魂,类的种类是多种多样的。类大致可以分外部类和内部类两种,外部类就是我们通常使用的类,而内部类的使用要比外部类少的多,最常见的是GUI事件侦听器。内部类的应用虽然不多,但是如果能够有效的使用内部类,能达到事半功倍的效果。废话不多说,下面开始:

2 内部类和嵌套类:

要讨论内部类和嵌套类,首先要分清他们两者的区别于联系。

首先,内部类(Inner Classes)和嵌套类(Nested Classes)是指在一个类里面定义的另一个类。其次无论是内部类还是嵌套类,在编译时都被当作一个独立的整体。对于访问他们的其他对象来说(假如他们对这个类来说是可见的)他们的使用和我们通常用的类是一样的。

但是,内部类和嵌套类的区别在于:

1. 嵌套类是静态的,而内部类不是,也就是说嵌套类的实例化不需要外部类的实例。但是内部类是需要这个实例的。

2. 嵌套类可以任意声明静态成员,内部类不允许声明除了编译时常量以外的任何静态成员。这一限制也适用于静态初始化函数。

3. 嵌套类都是命名的,匿名的类声明不能声明运行时静态成员(不管声明是不是静态的)。

注意:类内部声明的接口都属于嵌入类。

下面我们来分别讨论一下他们的具体行为表现。

3 内部类

内部类是指在一个类里面以非静态形式声明的另一个类。内部类的实例化需要外部实例的存在。一个类可以拥有多个内部类,一个外部实例可以拥有多个内部类的实例。但是内部类有且只有一个外部实例,并且在实例化时就已经指定,无法更改。

3.1 内部类的分类

内部类根据访问权限不同可以分为以下一种类型:普通内部类和局部内部类。

普通内部类和局部内部类主要的区别在于作用域和访问权限的不同,普通内部类可以被所有人访问(只要访问控制符允许),而局部内部类的作用域更像一个变量,只能在定义它的函数内部被使用,其他人是无法使用这个类的。而且局部类可以访问定义它的函数中的final变量。

根据声明方式不同又可以分为:命名内部类和匿名内部类。命名内部类和匿名内部类的区别在于:

首先,命名类可以抽象的。匿名类不能为抽象,事实上,匿名内部类在编译时被隐形的声明为最终的(final)。

其次,命名类声明可以继承一个父类并实现多个接口,匿名类只能有一个父类(或者接口)。

再次,命名类可以被多次实例化,而匿名类只能在定义时被实例化一次。

最后,命名类可以声明多个构造函数并控制访问哪一个父类的构造函数,匿名类无法声明构造函数。

下面分别描述这些区别:

3.2 普通内部类

普通内部类是指在类成员定义中定义的类,这些类可以拥有访问控制符(public, private等)。如果访问控释符允许,则这些类可以被外面直接应用。普通命名内部类可以声明为一个接口,或者是抽象的。嵌入类其实是特殊的普通内部类,但由于其特殊性,故在下面独立讨论。

3.2.1 声明:

3.2.1.1 命名类:

class OuterClass{

//Outer class deflation
class InnerNamedClass{
//Inner class definition

}
}

3.2.1.2 匿名类:

声明匿名类时,可以使用一个类或者接口作为他的父类。

class OuterClass{
//Outer class deflation
Object unnamedObject = new Object(){
//Inner class definition
}
}

3.2.2 实例化:

3.2.2.1 命名类:

外部或静态方法:

OuterClass outerObject = new OuterClass();
OuterClass.InnerNamedClass innerObject = outerObject.new InnerNamedClass();

内部:

InnerNamedClass innerObject = new InnerNamedClass();

3.2.2.2 匿名类:

定义时即完成实例化

3.2.3 访问权限:

3.2.3.1 内部实例对外部实例的访问权限为:

外部类定义或继承的所有字段

3.2.3.2 外部实例对内部实例的访问权限为:

内部类定义或继承的所有字段

3.2.3.3 其他对象对内部实例的访问权限为:

若内部类不可见,则只能访问其超类定义的字段

若内部类可见,则可访问内部实例的非私有字段,具体情况与通常的类类似

3.2.4 备注:

内部类的外部实例是在构造时作为参数传给构造函数的,在SUN JDK中,保存外部实例的字段通常被声明为:

final OutterClass this$0;

3.3 局部内部类

局部内部类,是指在函数体内声明的类,这种类是局域性的,只在函数内声明后有效,他最大的特点是:可以访问定义它的函数中的final变量。

3.3.1 声明:

3.3.1.1 命名类:

class OuterClass{
//Outer class deflation
void aMethod(){
class InnerNamedClass{
//Inner class definition
}
}
}

3.3.1.2 匿名类:

class OuterClass{
//Outer class deflation
void aMethod(){
Object unnamedObject = new Object(){
//Inner class definition
}
}
}

3.3.2 实例化:

3.3.2.1 命名类:

局部类的作用域仅限定义该类的方法中,无法在外部实例化或访问,实例化方法与通常类相同

3.3.2.2 匿名类:

定义时即完成实例化

3.3.3 访问权限:

3.3.3.1 内部实例对外部实例的访问权限为:

外部类定义或继承的所有字段

定义该内部类的方法在定义类之前所定义的所有final变量

3.3.3.2 外部实例对内部实例的访问权限为:

在定义该内部类的方法中可以访问内部类定义或继承的所有字段

在外部实例的其他字段中只能访问其超类定义的字段

3.3.3.3 其他对象对内部实例的访问权限为:

只能访问其超类定义的字段

3.3.4 备注:

当试图访问一个final变量时,编译器实际上把该变量的值赋值给内部类的一个隐含字段中。编译器会自动的在构造函数中添加相应的参数。在SUN JDK中,这个隐含字段通常被命名为:val$varname

3.4 使用内部类的注意事项:

本节包括了一些使用内部类时需要注意的事项,这些事项对所有内部类都适用。

3.4.1 静态成员

首先需要注意的是,内部类不能非常量声明静态成员,任何静态成员的声明都会被当成编译错误,例如:

InnerClasses.java:12: 内部类不能有静态声明

static String a;

^

1 错误

先要了解更加详细的信息,请参考附录A

3.4.2 访问外部实例

要在一个内部实例中访问外部实例,请使用:

OuterClass.this

3.4.3 字段覆盖

在内部类中声明的与外部类同签名的字段将覆盖外部字段,要访问外部字段请使用外部实例对象前缀。

OuterClass.this.field

OuterClass.this.Method();

4 嵌套类

嵌套类(Nested Classes)其实是普通内部类的一种特殊形式,首先它的声明是静态的,这就表示了这个类不需要外部实例。也表示了他不能访问外部类的实例字段。但是相应的,嵌套类可以拥有非常量静态成员。事实上,JDK通常把嵌套类当成一个具有特殊名字的独立类。

另外,嵌套类还拥有一种特殊形式:匿名嵌套类。前面说过,匿名类都是内部类,但是匿名嵌套类是一个特例,从理论上讲,他既不属于嵌套类,也不属于内部类。匿名嵌套类 不允许拥有非常量静态成员,但是他也没有外部实例供访问。

4.1 声明:

4.1.1 命名类:

class OuterClass{

//Outer class deflation
static class StaticInnerNamedClass{
//Inner class definition

}
}

4.1.2 匿名类:

class OuterClass{
//Outer class deflation
static Object unnamedObject = new Object(){
//Inner class definition
}
}

4.2 实例化

4.2.1 内部

StaticInnerNamedClass staticInnerNamedObject = new StaticInnerNamedClass();

4.2.2 外部

OuterClass.StaticInnerNamedClass staticInnerNamedObject = new OuterClass.StaticInnerNamedClass();

4.3 访问权限:

4.3.1 内部实例对外部实例的访问权限为:

外部类定义或继承的所有静态字段

4.3.2 外部实例对内部实例的访问权限为:

与通常内部类相同

4.3.3 其他对象对内部实例的访问权限为:

与通常内部类相同

A. 附录(Appendix)

A) 为什么内部类不能声明静态成员?

关于这个问题,MS JLS没有给出一个明确的答案(还是我没找到么?)。后来我在Usenet的新闻组上发贴子问了一下,结果大概是这样的:

1) 匿名类使用非常量静态成员是不必要的,因为匿名类只有一个实例

2) 命名内部类如果允许使用静态 的话会使内部类显得过分独立,应该尽量使用外部类的设施。

(原帖见https://groups.google.com/group/comp.lang.java.programmer/browse_thread/thread/0f109738f2f089d9?hl=en#d61250f96cf8e96e)

Why inner classes can not have static members?

bearice@gmail.com

Feb 4, 10:15 pm

as title.

Lew

Feb 4, 11:07 pm

> bear...@gmail.com wrote:

> as title.

Cultural note: It's considered polite to repeat the question inside the body of the post to facilitate others' ability to read the conversation.

Inner classes can have static members, provided such members are compile-time constants.

Asking "why" the language specifies something is an exercise in telepathy, unless the language designers left some notes or blog entries or white papers behind explaining the rational, which they might have done. GIYF.

I know that static members of inner classes would confuse me. Would such a member only be static within the context of the immediately enclosing instance of some instances of the inner class, or would it apply to all instances of the inner class?

The problem is that inner classes are (generally) instantiated within an instance of their enclosing class. Inner classes are not "static" enough on their own for me to be comfortable with a "static" that operates across all enclosing instance contexts. I suppose I could get used to it if Java were defined that way, but it isn't. It's defined to make inner classes very dependent on their enclosing instances.

If you want static members, use non-inner nested classes, or avoid nested classes altogether.

Why is a static member of an inner class necessary for you? Why is an alternative idiom not acceptable?

Lew

Markus Tazl

Feb 4, 11:18 pm

wrote:

> As title.

Wow, a question in shorthand? :-)

Anyway, Sun's Docs are your friend. Explained here in detail:

http://java.sun.com/docs/books/tutorial/java/javaOO/nested.html, see

Topic: "Inner classes" at the end of the page

Best regards

Markus Tazl

bearice@gmail.com

Feb 5, 4:53 am

- Quoted text -

I see, sorry for my impolite way of asking. And thank all of you for answering me.

I'm not really need a static member in an inner class. just very curious about it when the complier tells me it is an error.

Thanks again.

Lew

Feb 5, 8:38 am

Well, the simple answer is because that's the way it's defined for the language. The rules explicitly allow only compile-time constants to be static members of inner classes. Non-inner nested classes do not have this restriction.

> Inner classes may not declare static initializers (§8.7) or member interfaces.

> Inner classes may not declare static members, unless they are compile-time constant fields (§15.28). ...

> Nested classes that are not inner classes may declare static members freely, in accordance with the usual rules of the Java programming language.

In practice the restriction causes no difficulties. If anything, it makes inner classes easier to deal with, as one can conceptualize the inner class as "belonging" entirely to the enclosing instances that instantiate it without wondering if static members should differ between different enclosing instances.

Language designers always face interesting decisions like this, such as what the value of the remainder operator '%' should be when the denominator is negative. Sometimes they get it wrong, as many feel Java did by not (yet) making generics reifiable. I have never encountered an authoritative explanation for why inner classes in Java cannot have non-compile-time-constant static members; OTOH I've never encountered a situation where the restriction caused trouble.

Inner classes are an odd duck anyway. They're useful for sure, because having a class than can access the members (even private ones) of its enclosing class instance sure helps out - it mitigates the lack of closures, for example.

However, one should always consider using a non-inner nested class or non-public top-level class before creating an inner class that does not need such access.

--

Lew

da...@dagon.net

Feb 5, 9:01 am

bear...@gmail.com wrote:

>Why inner classes can not have static members?

Lew wrote:

>Inner classes can have static members, provided such members are compile-time constants.

Static inner classes can have static members without such a restriction. This is legal:

class Test {

static class Foo {

static int a;

public static void setA(int newA) {

a = newA;

}

}

}

The difference between a static and non-static inner class is important. A static inner class is a fairly normal class, and behaves very similarly to other outer classes. A non-static inner class has an implicit pointer to an instance of it's enclosing class, so doesn't really have a static context to execute in.

Note: anonymous inner classes are always non-static.

--

Lew

Daniel Pitts

Feb 5, 9:57 am

Mark Rafn wrote:

The difference between a static and non-static inner class is important. A static inner class is a fairly normal class, and behaves very similarly to other outer classes. A non-static inner class has an implicit pointer to an instance of it's enclosing class, so doesn't really have a static context to execute in

Note: anonymous inner classes are always non-static.

I actually believe that's called a nested class and has different semantics.

Lew

Feb 5, 11:19 am

Daniel Pitts wrote:

> I actually believe that's called a nested class and has different semantics.

"Static inner class" is a contradiction. Inner classes are never static, in that they are *defined* as nested classes that do not use the "static" keyword.

Nested classes comprise both inner classes and static nested classes.

The relevant section of the JLS is linked up thread.

--

Lew

B) 内部类与嵌套类的命名规则

内部类和嵌套类在编译时被分开成独立的类,编译器对这些独立的类有相应的命名规则,下面就来描述一下这些规则:

B.1匿名类的命名规则

所有的匿名类统一按下列规则重命名:

$

其中,ID是这个匿名类在外部类中出现的位置,例如第一个匿名类就是1,第二个就是2。

B.2 命名类的命名规则

命名类重命名规则为:

$

其中InnerClass为内部类的类名

B.3 局部命名类的命名规则

局部命名类的命名规则为:

$

其中,ID是同名的局部类在外部类中出现的位置,例如第一个就是1,第二个就是2,InnerClass为内部类的类名

C) 一个完整的例子

interface PublicPrint{
void pubPrint();
}
class OuterClass{
public static final void print(String str){System.out.println(str);}
public String pubStr = "public string";
private String prvStr = "private string";
public static String staPubStr = "static public string";
private static String staPrvStr = "static private string";
//命名内部类 OuterClass$InnerNamedClass
public class InnerNamedClass implements PublicPrint{
public void pubPrint(){
print(getClass().getName());
print(pubStr);
print(prvStr);
print(staPubStr);
print(staPrvStr);
}
}
//匿名内部类 OuterClass$1
public PublicPrint innerUnnamedObject = new PublicPrint(){
public void pubPrint(){
print(getClass().getName());
print(pubStr);
print(prvStr);
print(staPubStr);
print(staPrvStr);
}
};
//命名嵌套类 OuterClass$StaticInnerNamedClass
public static class StaticInnerNamedClass implements PublicPrint{
public void pubPrint(){
print(getClass().getName());
print(staPubStr);
print(staPrvStr);
}
}
//匿名嵌套类 OuterClass$2
public static PublicPrint staticInnerUnnamedObject = new PublicPrint(){
public void pubPrint(){
print(getClass().getName());
print(staPubStr);
print(staPrvStr);
}
};
public PublicPrint defineNamedEmbedClass(final String arg){
//命名局部类 OuterClass$1InnerEmbedNamedClass
class InnerEmbedNamedClass implements PublicPrint{
public void pubPrint(){
print(getClass().getName());
print(pubStr);
print(prvStr);
print(staPubStr);
print(staPrvStr);
print(arg);
}
}
PublicPrint innerEmbedNamedObject = new InnerEmbedNamedClass();
//innerEmbedNamedObject.pubPrint();
return innerEmbedNamedObject;
}
public PublicPrint defineUnnamedEmbedClass(final String arg){
//匿名局部类 OuterClass$3
PublicPrint innerEmbedUnnamedObject = new PublicPrint(){
public void pubPrint(){
print(getClass().getName());
print(pubStr);
print(prvStr);
print(staPubStr);
print(staPrvStr);
print(arg);
}
};
//innerEmbedNamedObject.pubPrint();
return innerEmbedUnnamedObject;
}
}
class Runner{
public static void main(String[] args){
//外部实例
OuterClass outerObject = new OuterClass();
//命名内部类
OuterClass.InnerNamedClass innerObject = outerObject.new InnerNamedClass();
innerObject.pubPrint();
//匿名内部类
outerObject.innerUnnamedObject.pubPrint();
//命名嵌入类
OuterClass.StaticInnerNamedClass staticInnerNamedObject = new OuterClass.StaticInnerNamedClass();
staticInnerNamedObject.pubPrint();
//匿名嵌入类
OuterClass.staticInnerUnnamedObject.pubPrint();
//命名局部类
PublicPrint innerEmbedNamedObject = outerObject.defineNamedEmbedClass("Hello");
innerEmbedNamedObject.pubPrint();
//匿名局部类
PublicPrint innerEmbedUnnamedObject = outerObject.defineUnnamedEmbedClass("World");
innerEmbedUnnamedObject.pubPrint();
}
}

D) 参考文档

JLS: <>

HCJ: <>

E) 版权声明

Mails referenced in Appendix A belong to their authors.

Java TM, Sun, are trade mark of Sun Microsystems Corp.

This document is released under CDDL.

Copy rights © 2008 Bearice [MDSA Group]