romkk-shi

Java实用经验总结--Swing篇

前言

本文前言部分为我的一些感想,如果你只对本文介绍的Java实用技巧感兴趣,可以跳过前言直接看正文的内容。

本文的写作动机来源于最近接给人家帮忙写的一个小程序,主要用于管理分期付款的货款的一系列管理,包括过期款的纪录,过期款利息的计算,为提前付款的用户提供一些返款奖励等等,这些与本文无关自不必细说。为了尽快完成任务,我自然选择了我用得最多的Java来实现。经过2周的劳动,顺利完成了任务,明天就可以去交差,但是这一刻我却忽然有些其他的想法。诚然这样的活原本属于体力劳动,类似的活我也做过不止一次,对于很多高人来说,没什么值得一提的,以前我也只是交差收钱了事,但这一次我却多了一些想法,使我不吐不快。

在程序的实现过程中,我遇到了个小问题,就是计算两个日期的差。由于以前常用的Date类的大多数方法都被标记为“deprecate”,所以我决定用Calender作为计算日期的主力。但是大多数参考书上都是由关于Calender的日期格式,Locale的设置,常量的含义等方面的讲解,却怎么也找不到这样一个简单却常用的任务怎么实现(注:这也不能怪我懒惰,作为这样一个程序来说,如果有正确且成熟的方法,谁还会去花大量时间仔细研究API呢?反正这个类可能在今后的几个月甚至几年都用不上,现在记住到时候也都忘了L)。于是在我google了好一阵之后,终于在某人的Blog上找到了用Calender计算日期差的方法。在那一刻我真有久旱逢甘雨之感。博主可能是一时兴起,也有可能是兴趣所在,但无论是什么原因,他的工作都为我提供了很大的方便。有了他的代码示例,我可以不再去逐个查找Java-Doc里面的API,然后挑出几个来尝试解决问题,最后再写个demo验证这一繁复的过程了。

再回想一下我完成这个程序的过程,由于以前做过一些类似的程序,我可以将里面的很多部分以直接应用到这个程序中,节省了大量的时间,让我可以更专注于核心业务的实现当中。然而或许是出于懒惰,或许是没有时间,又或许原来的是Blog没有多少人关注,我都没有将这些大多数人都可能会用得上的东西放到网上。

再联想一下国外开源工作者对中国程序员的评价—“只获取,不贡献”,就觉得人家说得十分对。自己就用着免费的J2SDK语言,免费的Eclipse,免费的JFreeChart,免费的JasperReport……,却从来没能够给人家贡献哪怕一行代码。这样也就算了,但是类似于一些力所能及的东西,例如可能每个Java程序员都会碰到的一些小问题,小技巧,常常出现的错误,为什么我就不能把他们贴出来供人分享呢?说不定就会帮到某位哥们解决大问题,更有可能你的几句话就能节省别人几分钟甚至几小时的时间。如果每个人都能在业余时间把自己的一些心得体会贴出来,相信更多的人将因此受益。当你遇到问题的时候,才能心安理得的去Google或Baidu。相信这也是技术论坛和技术Blog的初衷吧,毕竟这个世界并不是只有钱才是最重要的原动力。


1       改变Swing应用程序的默认字体/字号

经常使用Swing作为程序UI的人可能会注意到,Swing组件默认显示文字的字号为11。这对于英文显示毫无问题,但是如果用这个字号显示中文的话,这么小的字号就会使程序变得很难看。我当年在用IReport0.56的时候就发现他的菜单栏和弹出的Dialog里的字很难看,但是将字号调大之后就好多了。虽然在最近版本的JDK里似乎修正了这个字体问题,但是如果你的程序必须使用以前版本的JDK的话,这个问题就需要处理一下,下面就是一个不错的解决方案:

Font vFont = new Font("Dialog", Font.PLAIN, 13);

           UIManager.put("ToolTip.font", vFont);

           UIManager.put("Table.font", vFont);

           UIManager.put("TableHeader.font", vFont);

           UIManager.put("TextField.font", vFont);

           UIManager.put("ComboBox.font", vFont);

           UIManager.put("TextField.font", vFont);

           UIManager.put("PasswordField.font", vFont);

           UIManager.put("TextArea.font", vFont);

           UIManager.put("TextPane.font", vFont);

           UIManager.put("EditorPane.font", vFont);

           UIManager.put("FormattedTextField.font", vFont);

           UIManager.put("Button.font", vFont);

           UIManager.put("CheckBox.font", vFont);

           UIManager.put("RadioButton.font", vFont);

           UIManager.put("ToggleButton.font", vFont);

           UIManager.put("ProgressBar.font", vFont);

           UIManager.put("DesktopIcon.font", vFont);

           UIManager.put("TitledBorder.font", vFont);

           UIManager.put("Label.font", vFont);

           UIManager.put("List.font", vFont);

           UIManager.put("TabbedPane.font", vFont);

           UIManager.put("MenuBar.font", vFont);

           UIManager.put("Menu.font", vFont);

           UIManager.put("MenuItem.font", vFont);

           UIManager.put("PopupMenu.font", vFont);

           UIManager.put("CheckBoxMenuItem.font", vFont);

           UIManager.put("RadioButtonMenuItem.font", vFont);

           UIManager.put("Spinner.font", vFont);

           UIManager.put("Tree.font", vFont);

           UIManager.put("ToolBar.font", vFont);

           UIManager.put("OptionPane.messageFont", vFont);

           UIManager.put("OptionPane.buttonFont", vFont);

这段代码用在程序的开始部分,可以有效地将Swing组件的显示字体设置为我们在vFont所设定的内容。

1.1    让窗口更好地居中显示

无论是顶层组件JFrame还是对话框JDialog,让他们的窗口居中显示是一个很常见的问题,因为他们默认总是从左上角弹出来,这也太不爽了!对于这个问题,JBuilder应用程序生成向导给出了解决方案:

Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

Dimension frameSize = frame.getSize();

if (frameSize.height > screenSize.height)

    frameSize.height = screenSize.height;       

if (frameSize.width > screenSize.width)

 frameSize.width = screenSize.width;       

frame.setLocation((screenSize.width-frameSize.width)/2,screenSize.height-frameSize.height) / 2);

这个方法对于大多数窗口组件来说都足够了,但是还有其他问题存在,比如说分辨率和显示器的尺寸都会导致应用程序窗口“变形”,明明在17寸显示器1024*768分辨率下显示好好的窗口到了19寸的1280*800的宽屏下就会被“拉”得很“长”。于是,虽然有布局管理器帮我们管理拉伸后组件的放置,但仍然解决不了拉长后带来的美观问题。我的经验是,对于某些窗口,由于它被“拉长”之后由于其内部组件之间的间隙变大,会显得很难看。所以应该为他们设定一个最合适的显示大小。在居中显示的时候只调整位置而不改变大小,这样就不会影响窗口的美观。所以我们只需要对上面的代码小改一下即可,以JFrame为例:

Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

screenSize = Toolkit.getDefaultToolkit().getScreenSize();

frame.setPreferredSize(new Dimension(512,450));           

int frameWidth = this.getPreferredSize().width;

int frameHeight = this.getPreferredSize().height;

frame.setSize(frameWidth, frameHeight);

frame.setLocation((screenSize.width - frameWidth) / 2,(screenSize.height - frameHeight) / 2);

2       自定义JFrame的关闭事件

有的时候,当用户关闭应用程序窗口的时候,我们可能希望程序在结束之前保存一些必要的数据。对于这种需求,我们有两种备选方案:

2.1    获取程序关闭的“钩子”

Runtime.getRuntime().addShutdownHook(shutdownHook);

protected Thread shutdownHook = new PlatformShutdownHook();   

    protected class PlatformShutdownHook extends Thread {

        public void run()

        {

            //一些清理工作在这里进行……

        }

}

通过这种方法,我们就可以在程序结束时获得通知,以便进行一些保存或清理的工作。然而这种方法的缺点是,在程序收到结束通知的时候,所有的UI组件已经被销毁了,用户此时看到的是程序已经结束。而事实上如果程序保存需要花很长的时间的话,用户是不能获取任何信息的,这是一个很糟糕的用户体验。因为如果这时用户关机的话,程序就有可能丢失尚未保存的信息,而对于这一切,用户并不知情。

2.2    处理JFrame关闭事件

为了在UI被销毁之前收到程序结束的消息,我们需要自行处理窗口关闭的事件。注意在这里我们没有采用addActionListener(……)方法,因为这样做只能让我们在窗口关闭之后收到通知,这样就与上面的方法没什么区别了。

我们需要在JFrame的构造函数中设置:

//设定标志,让MainFrame自己接收窗口事件

enableEvents(AWTEvent.WINDOW_EVENT_MASK);

然后再实现下面的函数:

protected void processWindowEvent(final WindowEvent pEvent) {

        if (pEvent.getID() == WindowEvent.WINDOW_CLOSING) {

            /** 防止用户多次点击“关闭”按钮造成重复保存 **/

            if( !isClosing ) isClosing = true;

            else return;

 

            //处理JFrame关闭事件……

        }else{

            //忽略其他事件,交给JFrame处理

            super.processWindowEvent(pEvent);

        }

}

如此一来,我们就可以在窗口被关闭之前通知用户程序正在保存数据的信息,例如后面提到的InfiniteProgressPanel可以显示的内容。

3       日期选择组件与JDialog的冲突问题

由于很多应用程序都需要用户输入日期,却又怕用户输入的日期格式错误,所以日期选择组件便应运而生。虽然我们很需要它,但是网上绝大多数的组件都是需要给钱的。在找到SwingX之前,我找到的唯一能够免费使用的日历组件就是一个名为DateChooser的JDialog:

看样子很不错,它支持中文,对于今天高亮显示,可以调整年分和月份……一切都非常符合要求。但是这么好的组件却不能用在我的程序里,原因是在我的程序中,调用这个组件的组件也是一个JDialog,并且设置了setAlwaysOnTop(true)—即总在最前端显示。由于DateChooser也设定了在最前端显示,这就导致了它和其父组件的显示冲突,最终结果是DateChooser不能正常显示。对于这个问题,我最终使用SwingX的组件DatePicker来代替DateChooser完成选择日期的使命,惯于DatePicker的使用我将来会在“SwingX使用详解”中提到,这里就不再细说。但是这个问题仍然值得我们注意,即如果一个窗口组件是设置了总在最前端显示的JDialog,那么就不要以这个JDialog为父组件来弹出其他JDialog,以避免冲突的发生。

4       JTable的实用技巧

无论对于什么样的一个应用程序来说,用表格的形式来显示数据是再平常不过的事情了。于是JTable就成为我们在所有Swing组件中最不可或缺的朋友。对于JTable的操作,大多数情况下我们都可以不假外求,因为JDK自带的例子SwingSet2给我们展示了足够多的功能。

在这个例子里,我们可以改变单元格的间距,行高,选择类型(Selection Style),是否显示水平线,甚至可以将表格内容打印出来。其中,表格除了文字之外还可以包含其他组件和内容,如SwingSet2种就加入了可以选择颜色的JComboBox和喜爱的食物所代表的图片。

但有些时候,我们还会有一些其他的需求。例如说为了保护我们的眼睛,我们希望表格的内容是带有间隔色的,如奇数行显示蓝色,而偶数行显示白色。又或者我们希望表格中某些列的内容是可编辑的,而且他列的内容是不可编辑的。又或者让表格中的列带有排序的功能,能让我们点一下表头它就自己按照从低到高或从高到低的顺序自行排列。最后我们希望表格的表头和单元格力的内容能够居中显示。让我们一个一个来实现这些功能!

4.1    间隔色表格及单元格/表头居中显示

JTable的API并没有为我们提供更改表格行或列的颜色的能力。但是我们知道,表格的表头和内容的呈现形式都是由相应的Renderer来控制的,所以我们只需要继承单元格默认的Renderer并作相应的修改就可以达到目的:

由于实现了接口TableCellRenderer,我们只需要实现唯一的函数getTableCellRendererComponent(…)。在上例中我们看到,在函数中我们判断当前行是奇数还是偶数,如果是奇数,就设置其背景色为淡蓝色,否则就设其背景色为白色。在每次更新表格内容的时候,我们只需要调用下面的函数,就可以保证表格在内容被更改之后依然正确显示间隔色。

/** 为所有表格设置间隔色 **/

    private void setRenderColor(){

for( int i = 0; i < table.getColumnModel().getColumnCount(); i++ )                   table.getColumn( colname[i] ).setCellRenderer(colorRender );       

}

另外,如果我们想要让单元格中的内容居中显示的话,请注意到在设置间隔色部分下面的函数,通过setHorizontalAlignment(SwingConstants.CENTER)我们就可以让单元格内容居中显示。

虽然JTable表格的表头在默认情况下应该是居中显示的,但不知道为什么,在我的应用程序中表格的表头总是左对齐显示,这让我恼火不已。由于和单元格一样,表头的各项显示指标也是由其Renderer控制的,所以只需要设置一下表头的Renderer就能达到目的:

DefaultTableCellRenderer renderer = (DefaultTableCellRenderer) table.getTableHeader().getDefaultRenderer();

renderer.setHorizontalAlignment(renderer.CENTER);

利用这种方法,如果我们需要让他右对齐似乎也不是什么难事,对吗?

4.2    让某些单元格不可编辑

有些时候,我们希望有些行/列可以被编辑,而有些行/列不能被编辑。如下就是一例,我的程序希望第一列(编号列)的内容可以被用户通过双击进行编辑,而其他列则不能被用户编辑。单元格能否被编辑取决于JTable的isCellEditable(int row,int column)。如果该函数返回true则(row,column)所代表的单元格可以被编辑,否则该单元格不能被编辑。于是我建立了一个名为SingleUnitEditableTable的类,他继承自JTable,并Overwrite了isCellEditable(int row,int column)方法:

//设置单元格不可编辑,为缺省实现

    public boolean isCellEditable(int row, int column) {

        if( editableColumn != -1){

            if( column == editableColumn )

                return true;

            return false;

        }

        return false;

}

其中的editableColumn是一个内部属性,用来指定哪个列可以被编辑。通过这个例子,我相信,如果你想实现奇数行/列可编辑而偶数行/列不可被编辑或者满足特定条件的单元格不可被编辑这样的JTable易如反掌了吧?下面就是我的应用程序的结果:

第一列可编辑

其他列均不可编辑

4.3    JTable自排序

这个问题已经由JDK6.0帮我们解决了,在这个版本,JDK为我们提供了一个名为TableRowSorter的类,在程序中我们只需要写2行代码即可实现表格内容的排序:

TableRowSorter sorter = new TableRowSorter(tableModel);

       accAllTable.setRowSorter(sorter);

看到“编号”列旁边的箭头了吗?如果我们用鼠标点击表头,JTable就会自动为我们由小到大排序,再点一下,表格就会从大到小排序,真是十分方便。而对于JDK6.0之前的应用程序就没有这么好的运气了,我们需要自己实现一个TableRowSorter,并且自己生成一个表头的Renderer来实现排序小箭头,真是繁琐啊!我这里倒是有一个不错的实现,如果有人需要的话可以给我留言。不过自己实现Renderer采用的是JLabel,会改变表头的模样,不如默认的表头好看,所以可能的话还是升级吧J

5       用JEditorPane显示HTML描述的文本

从JDK1.4开始,Swing的很多组件(如JLabel)都可以显示HTML语言写的文本。这是一个巨大的进步,因为我们可以将所要显示的文字的配置信息如字体,字号,颜色,换行等信息直接以HTML写入到组件的setText()方法当中,不但免去了事后对这些信息进行繁杂配置的烦恼,而且还丰富和简化了所要显示文本的形式。而JEditorPane则有所不同,它天生就是用来分析并显示格式化文本的,由一些Java写的开源Web浏览器甚至都采用改进后的JEditorPane作为Web页的显示器。下图就是SwingSet2中的JEditorPane相关的例子。我们可以看到JEditorPane可以显示大多数的HTML元素,包括图片,格式化文字,URL链接等。

然而通过JEditorPane显示HTML描述的文本有两种方式:

第一种是直接使用JEditorPane.setPage(String htmlTxt);来显示用html语言写成的文本。但是这种方法的缺点是无法显示HTML文本中所描述的对外部资源(如图片,CSS等)的引用。所以如果要显示更为丰富的信息,仅仅用第一种方法是不够的。

       所以第二种方法就呼之欲出:将用HTML语言描述的动态文本信息写到文件中,使之成为真正的HTML文件,再用JEditorPane.setPage(URL)或JEditorPane.setPage(String htmlFilePath),JEditorPane方法读入这个动态生成的内容文件就可以让JEditorPane自动为我们显示丰富的信息了。

   String vNewReportFileName = "file:///c:/temp.html";

   JEditorPane reportPane = new JEditorPane();

   File f = new File(FileUtil.reportDir,vNewReportFileName);

 FileWriter fw = new FileWriter(f, false);

   fw.write("<html>");

 fw.write("<head>");

 …………

  fw.write("</body></html>");

//清理操作

  fw.flush();

  fw.close();

  f = null;

reportPane.setPage(vNewReportFileName);

下图就是我的程序所显示的结果,从图中我们可以清楚地看到由CSS文件定义的表格的Title,这个Title是由一个蓝色的图片作为背景的。

让人遗憾的是用JEditorPane显示的表格的边框都很粗,虽然我已经将了表格的border设置为1,可是JEditorPane依然我行我素。但是在IE下,表格的边框的表现就要好的多:

网上有人说这是一个Bug,但是没有人给过解决这个问题的方法,如果有人又解决方法的话请留言,我将不胜感激!

6       用InfiniteProgressPanel实现GlassPane

俗话说重要人物都最后出场,作为Swing篇的完结部分,我为大家隆重推荐一个GlassPane的实现—InfiniteProgressPanel,它的效果如图所示:

怎么样,很酷吧?这是在程序进行更新的时候能够给用户以提示,可以屏蔽用户操作而且十分美观的特殊进度条。它源于一个超级Java大牛的手笔,此君的《Swing Hacker》在去年如带给我的震撼到现在还挥之不去。从那以后,谁再敢说Java不能做出好看的用户界面之前都需要自己好好掂量一下自己是否有这么说的资格。这本书让我真正认识到,只有想不到没有做不到。都是一样用Swing,为啥人家就能玩出花样呢?差距!

其实现原理很简单,说白了就是用Java2D画圈!至于源码各位可以到网上自己搜。他的使用十分简单:

InfiniteProgressPanel glassPane = new InfiniteProgressPanel();

frame.setGlassPane(glassPane)

在需要它显示的时候,就这样做:

 Thread myThread = new Thread(new Runnable(){

         public void run() {

              InfiniteProgressPanel gl = thisRef.glassPane;

              gl.start();

              gl.setText("正在保存数据请稍候....");

              try {

                 //这里放要做的事情……

                 gl.setText("保存完毕,欢迎使用!");

                 Thread.sleep(1000);

               }catch(InterruptedException ex) {

               }finally{

                 gl.stop();

               }

            }

    });

myThread.start();       

这里有几个问题需要注意:

1.         必须要将InfiniteProgressPanel的显示放到一个线程里,相信大家都知道原因,我不用多说。

2.         在InfiniteProgressPanel结束之前的Thread.sleep(1000);是必要的,如果时间设得太短或不设将会导致InfiniteProgressPanel死掉。至于原因我没有时间深究,各位有兴趣可以自行察看其源码,如果你能找到原因高诉我,我会非常感激。

3.         在有些时候会出现圆圈“四处乱窜”的现象,不过不太常见。


posted @ 2007-07-30 09:19 路边的石头-java 阅读(1249) | 评论 (1) | 编辑 收藏
 
Web2.0下的十大AJAX安全漏洞以及成因

        JavaScript包含的Ajax是Web2.0应用的一个重要组成部分。该部分的进化发展使网络变成了超级平台。该转变同时也催生了新品种的病毒和蠕虫,比如Yamanner,Samy 以及Spaceflash等等。Google,Netflix,Yahoo 以及MySpace等门户网站在过去的几个月里都因为新的漏洞而蒙受一定损失。黑客们可以利用这些漏洞进行钓鱼,跨站点脚本(XSS)以及跨站点伪造(XSRF)请求等攻击。

  Ajax中没有固有的安全漏洞,但是对该技术向量的适配显著地改变了网络应用的开发途径以及方法论。以前,DCOM和CORBA组成核心中间件层的时候,将数据和对象序列化非常困难。Ajax使用简单的GET,POST或者SOAP调用,来转换XML,HTML,JS Array,JSON,JS Objects以及其他定制的对象;全部这些操作都不需要调用中间件层。Ajax的这种综合能力使应用服务器与浏览器之间的数据交换非常流畅。从服务器端传来的信息动态地被注入到当前的DOM相关环境,然后浏览器的DOM状态重置。在讲安全漏洞之前,我们先来看看促成Web2.0漏洞的关键因素。

  多重分散的终端点以及隐藏调用——Web2.0应用与Web1.0的主要区别就是信息访问机制的区别。比起它的前身Web1.0, Web2.0应用有数个Ajax终点。潜在的Ajax调用分散于整个浏览器页面,并且能够被各个事件分别调用。开发者恨难应付Ajax调用的这种分散性,并且由于这些调用是隐藏的,不那么明显,它还可能导致代码不规范。

  认证混乱——输入和输出内容认证是应用的重要因素之一。Web2.0应用使用桥,mashups,还有反馈等等。很多情况下,它假定“另一方”(读取服务器端或者客户端代码)已经实现了认证,这种混乱就导致了双方都没有实现适当的认证控制。

  不受信任的信息来源——Web2.0应用从很多不受信任的来源比如反馈,博客,搜索结果中获得信息。这些内容在提供给终端浏览器之前从来没有被认证,这就有可能引发跨站点攻击。黑客还有可能在浏览器中加载JavaScript,以便迫使浏览器发出跨域的调用并打开安全漏洞。那样的话,这些致命的漏洞就能被病毒和蠕虫利用。

  数据序列化——浏览器可以调用Ajax来实施数据序列化。它可以获取JS array,Objects,Feeds,XML文件,HTML 块以及JSON。如果这些序列块中的某一个被解析并修改了,黑客们就可以强迫浏览器执行恶意脚本。不受信任信息与数据序列化的结合,对终端用户的安全是致命的。

  动态脚本构成和执行——Ajax会建立一个后端通道,从服务器获取数据,然后将它传送给DOM。实现这一点的必要条件就是动态地执行JavaScripts,以便随时更新DOM或者浏览器页面缓存的状态。Ajax通过调用定制的功能或者eval()功能。未经认证的内容或者使用不安全的调用,轻则导致会话内容泄露,重则迫使浏览器执行恶意内容等各种后果。

  Web2.0应用可能因为上面提到的1个或多个失误而变得易受攻击。如果开发者不够审慎,没有花心思在安全管理上的话,那么服务器和浏览器端都会出现安全问题。以下是10个可能的安全漏洞的简要说明。

  (1)畸形的JS对象序列

  JavaScript支持面向对象编程(OOP)技术。它有很多不同的内置对象,也允许用户自己创建对象。使用者可以用new object() 或者自己编辑如下代码来创建新的对象。

  message = {

from : "john@example.com",

to : "jerry@victim.com",

subject : "I am fine",

body : "Long message here",

showsubject : function(){document.write(this.subject)}

};

  这是一个简单的消息对象,其中有2个字段需要电子邮件地址。我们可以使用Ajax来将该对象序列化并用JavaScript代码编译。程序员可以将它赋值到变量或者eval()。如果攻击者发送嵌入了脚本的恶意“主题”,那么读者就将成为跨站点脚本攻击的受害者。JS对象既包含数据也包含方法。对JS对象序列的不当使用将产生可以被诡计多端的注入代码利用的安全漏洞。

  (2)JSON对注入

  JavaScript对象符号(JSON)是一个简单而有效的少量数据交换格式,它包含对象,数组,Hash表,向量以及列表数据结构。JavaScript, Python, C, C++, C# 和Perl languages都支持JSON。JSON序列在Web2.0应用中是个非常有效的交换机制。开发者频繁使用Ajax和JSON,获取并传送必要的信息给DOM。下面是个简单的带有不同的name值对的JSON对象:“bookmarks”对象。

  {"bookmarks":[{"Link":"www.example.com","Desc":"Interesting link"}]}

  黑客们可以在Link或者Desc中注入恶意脚本。如果DOM和可执行程序被注入了,XSS目录也会被注入。这是使终端用户感染恶意内容的另一种方法。

  (3)JS数组中毒

  JS数组是另一个比较普遍的序列化对象。人们可以很容易地跨平台移植它,并且它在使用不同语言的结构中也很有效。感染一个JS数组可以扰乱整个DOM环境。黑客们可以在浏览器中使用简单的跨站点脚本攻击JS数组。下面是一个JS数组的例子:

  new Array(“Laptop”, “Thinkpad”, “T60”, “Used”, “900$”, “It is great and I have used it for 2 years”)

  该数组是从一个拍卖二手笔记本的网站传出来的。如果这个数组对象在服务器端没有被仔细处理,黑客就可以在最后字段中注入脚本。这种注入将危及浏览器安全并被攻击者利用。

  (4)被修改的XML数据流

  Ajax调用接受来自多个地址的XML。这些XML块来自运行在SOAP,REST或者XML-RPC的网络服务。这些网络服务是由从第三方的代理桥那里接收过来的。如果这些第三方XML数据流被攻击者修改过,那么攻击者就可能向其中注入恶意内容。

  浏览器从它自带的XML解析器接收该数据流。该解析器容易受不同的XML炸弹的攻击。人们也可以在该数据流中注入脚本,这样就可以导致跨站点脚本攻击(XSS)。浏览器接收未经认证的XML数据流的话,这就会危及终端客户端的安全。

  (5)DOM中脚本注入

  前四个漏洞都是由于序列化问题引起的。一旦浏览器收到序列化的对象数据流,开发者会发出某种调用来访问DOM。这种调用的目的是将新内容“重写”或者“重填”入DOM中,可以调用eval()这个定制功能,也可以使用document.write()。如果这些调用是在不受信任信息流上进行的,浏览器就有可能由于DOM的操作漏洞而受攻击。攻击者可以用很多document.*()调用来向DOM环境中注入XSS。

  例如,这段JavaScript代码:Document.write(product-review)。

  在这里,“Product-review”是从第三方blog上获得的变量。如果它含有JavaScript会怎样?答案很明显。这个JavaScript就会被浏览器运行。

  (6)跨域访问和回调

  Ajax不能从浏览器跨域访问。所有比较流行的浏览器都有个安全特性,那就是拦截跨域访问。一些网站服务为对象序列提供回调功能。开发者可以使用这个功能来把网站服务整合到浏览器本身。人们可以把该功能名传回,这样浏览器一找到回调对象数据流,它就会被浏览器中早已有的特殊功能名执行。

  这个回调对使用浏览器内认证的开发者来说是个额外负担。如果输入的对象数据流未经浏览器认证那么终端客户端就会成为跨域攻击的目标。不管是有意还是无意的,跨域服务可以向浏览器中注入恶意内容。该跨域调用在当前DOM环境中运行,于是导致当前对话也易受攻击。在实现应用之前,人们需要仔细检查整个跨域功能。

  (7)RSS和Atom注入

  联合的反馈,RSS以及Atom,是最普遍的一种将站点更新信息传到网络上的方法。许多新闻,博客,门户站点等等,都在网络上共享多个反馈。反馈是标准的XML文档,并且可以被任何程序接收。Web2.0应用使用窗口小部件或者浏览器内部元件整合了联合反馈。这些组件调用Ajax来访问反馈。

  这些反馈可以被终端用户方便地选择。一旦用户选择了它们,这些反馈就会被解析并注入到DOM中。那么如果这个反馈在注入之前没有被适当地认证过,就会出现一些安全问题。人们可以往浏览器中注入恶意链接或者JavaScript代码。注入之后,就大事不妙了,最终结果是XSS和对话被黑客拦截。

  (8)单击炸弹

  Web2.0应用可能不会很简单地就被黑客攻下,但他们可以对它进行基于事件的注入。人们可以将带有"onclick"字样的恶意链接用JavaScript注入。这样,浏览器就带着个随时等待终端用户右键点击来触发的炸弹。一旦用户点击了链接或按钮,能够启动炸弹的那个事件被启动了,那么攻击就成功了。此类攻击会导致对话被恶意代码拦截。

  这也是由于人们从那些没有经过正确验证的不受信任源处获得的信息,所导致的安全漏洞。为了利用该安全漏洞,它需要终端客户端触发一个事件。这个事件也许是诸如点击按钮或者链接的这种无害事件,但是点击后就使会用户损失惨重。它可能引起某个恶意事件,将当前对话信息发送给目标,又或者在当前浏览器环境中执行一系列脚本攻击。

  (9) 基于Flash的跨域访问

  黑客们可以使用Flash插件的Ajax接口,从而用浏览器中的JavaScritps发出GET和POST请求。这个接口使黑客们能进行跨域调用。为了避免安全问题,该Flash插件实现了根据策略访问其他域的功能。该策略可以通过在域的根部放置crossdomain.xml文件来配置。如果放置的文件配置不当——很普遍的现象——它就可能允许跨域访问。下面是一个配置不当的XML文档:

  现在可以从浏览器自身发出跨域调用了。这个结构还有一些其他安全问题。基于Flash的丰富网络应用(RIA)如果配置错误的话,很容易由于Ajax的跨域访问Bug而被攻击。

  (10) XSRF

  跨域伪造请求(XSRF)是个老牌的攻击向量了,它迫使浏览器向不同的域发出HTTP GET或者POST请求;这些请求可以跨域在运行的应用逻辑中启动某种事件。它可能请求修改密码或者电子邮件地址等。浏览器调用它后,它重放cookie并获得身份认证。这就是该请求的关键部分。如果某个应用只根据cookie来判识身份,那么该攻击就会成功。

  Web2.0中Ajax是就XML-RPC,SOAP或者REST与后端网络服务进行对话的,通过GET和POST可以进行这些调用。换句话说,人们可以对这些网络服务进行跨站点调用,从而危及受害者与网络服务接口的身份信息。XSRF这个攻击向量很有趣,它在这个新界定的端点情况中创造了新的层次。这些终点可能是为Ajax或者网络服务而准备的,但它们也有可能被跨域请求所激活。

  对安全漏洞的攻击以及相应对策

  Web2.0应用有多个终端点;每个点都是威胁的侵入点。为了保证安全,我们应当保护好所有这些点。在将第三方信息发送给客户端之前要对其进行彻底处理。

  为了处理Ajax序列,必须在它们到达DOM之前对输入数据流进行验证。XML解析以及跨域安全问题也需要额外重视,并实施更好的安全管理措施。我们应当遵循那个最简单最笨拙的原则:不让未经认证的跨域信息进入浏览器。有趣的是,到目前为止,安全专家们都不主张使用客户端脚本来进行输入验证,因为这很容易被规避掉。

  Web2.0促成了很多浏览器安全相关的新的漏洞。利用这些安全漏洞很难但不是不可能。安全问题以及促成因素结合起来将严重影响那些大的网络团体,比如能被攻击者蠕虫和病毒利用的那些组织。最终将导致身份信息的泄漏。

  结论

  本文简单地讲了一些可能出现的关于Ajax漏洞。还有很多其他潜在的漏洞,比如利用跨域代理来在浏览器中建立单项通道或者存储变量。

  Web2.0中很多逻辑都转到了客户端。这会将整个应用暴露给一些严重的威胁。对整合来自多方的、不受信源的数据的迫切要求也将全面增加风险向量:XSS,XSRF,跨域问题以及客户端上的序列,还有不安全的网站服务,服务器端的XML-RPC和REST访问。相反地,Ajax可被用来构造优美的无缝数据整合。但是,任一不安全的调用或者信息流都会使其产事与愿违的效果,从而促成可被利用的安全漏洞。

  这些新技术向量很有前景,令很多人兴奋不已,但是攻击者,病毒和蠕虫作者对它更感兴趣。为了保障安全,开发者应当在这些细节方面格外小心。


posted @ 2007-07-30 09:18 路边的石头-java 阅读(238) | 评论 (0) | 编辑 收藏
 
MySQL5.0学习

如何新增一个mysql用户

增加一个用户test1密码为abc,让他可以在任何主机上登录,并对所有数据库有查询、插入、修改、删除的权限。首先用以root用户连入MYSQL,然后键入以下命令:mysql>grant select,insert,update,delete on *.* to test1@"%" Identified by "abc";
增加的用户是十分危险的,你想如某个人知道test1的密码,那么他就可以在internet上的任何一台电脑上登录你的mysql数据库并对你的数据可以为所欲为了
增加一个用户test2密码为abc,让他只可以在localhost上登录,并可以对数据库mydb进行查询、插入、修改、删除的操作 (localhost指本地主机,即MYSQL数据库所在的那台主机),这样用户即使用知道test2的密码,他也无法从internet上直接访问数据 库,只能通过MYSQL主机上的web页来访问了。
mysql>grant select,insert,update,delete on mydb.* to test2@localhost identified by "abc";
或 mysql>grant all privileges on mydb.* to test2@localhost identified by "abc";
如果你不想test2有密码,可以再打一个命令将密码消掉。
mysql>grant select,insert,update,delete on mydb.* to test2@localhost identified by "";
另外,也可以通过直接往user表中插入新纪录的方式来实现。

如何修改mysql用户密码

如果要修改的用户不是root用户
方法1   先进入root用户的mysql数据库中
mysql>mysql -u root -p  mysql
mysql>update user set password=password('new_password') where user='user';
mysql>flush privileges;
如果修改的用户是root
首先停止MySQL的运行,然后重新启动 : PATH_TO_MYSQL/bin/mysqld --skip-grant-tables &
就可以不需要密码就进入 MySQL 了。
然后就是
mysql>use mysql
mysql>update user set password=password("new_pass") where user="root";
mysql>flush privileges;
重新杀 MySQL ,用正常方法启动 MySQL
一定注意:很多新手没有用password=password("..."),而是直接password="..."所以改掉密码不好使

另一种方法是使用mysqladmin命令
比如原来的用户user的密码是123现更新密码为123456
MySQL\bin\mysqladmin -u user -p password 123456
Enter password:123

如何删除用户和撤销权限

要完全删除一个用户,你必须用一条DELETE语句明确从user表中删除用户记录:
>mysql -u root -p mysql
>password:******
mysql->DELETE FROM user
           ->WHERE user=‘user_name’ and Host=‘host_name’;
mysql>FLUSH PRIVILEGES;

收回资料库使用权限的方法如下(以 MySQL root 进入):
mysql> revoke delete on addbook.* from maa@localhost;
Query OK, 0 rows affected (0.00 sec)

收回全部的权限
mysql> revoke all privileges on addbook.* from maa@localhost;
Query OK, 0 rows affected (0.00 sec)

增加和删除数据库

使用mysqladmin命令(增加)
MySQL\bin\mysqladmin -u root -p create dbtest
Enter password:********
Database "dbtest" created

使用mysqladmin命令(删除)
MySQL\bin\mysqladmin -u root -p drop dbtest
Enter password:********
Dropping the database is potentially a very bad thing to do.
Any data stored in the database will be destroyed.
Do you really want to drop the 'dbtest' database [y/N]
y
Database "dbtest" dropped
posted @ 2007-07-27 17:12 路边的石头-java 阅读(273) | 评论 (0) | 编辑 收藏
 
Linux下安装最新Apache2.0.52+PHP5+GD2+MySQL等

由于gd2才开始支持真彩图片的创建,所以,,升级服务器,因为原来的安装都是默认的系统安装,也更因为是个菜鸟,所以,安装很困难,起初根据网上一些文章在我的red hat A 3 上安装测试,不过,测试了安装php4.3.X 和php5.0.X都没有成功,最后,根据其他人的文章,自己搞了将近3天,终于安装上了,下面就我安装中出现的错误和过程写出来和大家分享,以免有像我一样的菜鸟们走弯路! (我可是两天多的时间,安装php的次数不下50次得来的经验,希望大家多多支持,有错误当然欢迎给我指正!转载请注明来自中国智慧在线(http: //article.21e.cn ,谢谢!),以下经过测试在red hat 9 和red hat A 3上通过!

如果你机器里原来装有了PHP或者APACHE的RPM或者低版本.你可以现删除,删除安装简洁一些,不过我的实际操作过程是煤油删除,而直接安装的.当然你也可以跟我一样!
首先,服务器GCC要有,不然什么都不能做.可以用gcc -v来查看是否安装了GCC,

#gcc -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2.3/specs
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --disable-checking --with-system-zlib --enable-__cxa_atexit --host=i386-redhat-linux
Thread model: posix
gcc version 3.2.3 20030502 (Red Hat Linux 3.2.3-34)

有以上类似信息说明已有GCC,没有就现安装吧,至于如何安装,我就不说了,可以用光盘安装或者是下载源文件都可以,当然你的版本不一定是3.2.3!

请下载以下所有的东西:

httpd-2.0.X.tar.gz 版本最好是下载最新的啦,下载地址:http://www.apache.org
MySQL-client-4.0.20-0.i386.rpm
MySQL-server-4.0.20-0.i386.rpm(如果你打算升级mysql的话请下载,下载地址:http: //www.mysql.org 当然也是下载最新的版本最好了,由于我不打算安装mysql,就省略了,安装的时候的步骤也是先安装mysql!)
php-5.0.X.tar.gz 下载地址:http://www.php.net
ZendOptimizer-2.5.3-linux-glibc21-i386.tar.gz
zend的最新版安装是好像有点问题,就下载这个版本就可以,zend用来加速php,你可以选择不安装!下载地址:http: //www.zend.com
gd-2.0.28.tar.gz 这个软件的下载地址,php推荐的是:http://www.boutell.com/gd/ 但是由于某些原因,开发者不支持gif图像的创建,这有点不太方便,所以,我下载了个支持gif图像的,也就是打了gif补丁的:http: //www.rime.com.au/gd/
libxml2-2.X.X.tar.gz 下载地址:
zlib-1.X.X.tar.gz 忘记了,自己找一下吧
jpegsrc.v6b.tar.gz 下载地址:ftp://ftp.uu.net/graphics/jpeg/
libpng-1.2.5.tar.gz 下载地址:http://www.libpng.org/pub/png/libpng.html
freetype2-X.X.tar.gz 下载地址:http://www.fretype.org

xpm-3.4k-2.i386.rpm 忘记了,自己找一下吧

以上都是我下载的官方地址,软件的版本中的X,你自己看看你想用哪个版,不过最好是用最新稳定版本的!如果你以前安装过上述软件的其他版本,你也可以选择不安装!

安装MYSQL 服务器:
#rpm -ivh MySQL-server-4.0.20-0.i386.rpm
#rpm -ivh MySQL-client-4.0.20-0.i386.rpm
安装好后试试能不能用
#mysql 如果设置了密码后测试方法是(mysql -u root -proot -h localhost 注意的是-p后煤没有空格,直接跟密码)
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 27651 to server version: 4.0.20-standard
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

说明可以了!
请自己改mysql的密码,方法不说了!

安装XPM

#rpm -ivhxpm-3.4k-2.i386.rpm

安装libxml
# tar -zxf libxml2-2.6.16.tar.gz
# cd libxml2-2.6.16
# ./configure (xml默认安装就可以,不要指定路径了,因为安装时php可能找不到它,PHP5只支持libxml2-2.5.10以上版本)
# make
# make install

安装zlib
# tar -zxf zlib-1.2.2.tar.gz
# cd zlib-1.2
# ./configure --prefix=/usr/local/zlib2 (注意,如果您以前没有安装zlib,可以不指定路径,我是没有删除以前的低版本才指定的!以下雷同!)
# make
# make install

安装jpeg
# tar -zxf jpegsrc.v6b.tar.gz
# cd jpeg-6b/
# ./configure --prefix=/usr/local/jpeg6
# make
# make install-lib
如果你选择默认安装,可能很顺利,指定路径后,请先创建以下文件夹
错误提示:...... /usr/bin/install -c -m 644 jconfig.h /usr/local/jpeg6/include/jconfig.h
/usr/bin/install: 无法创建一般文件‘/usr/local/jpeg6/include/jconfig.h’: 没有那个文件或目录
make: *** [install-headers] Error 1
# mkdir /usr/local/jpeg6
# mkdir /usr/local/jpeg6/include
# mkdir /usr/local/jpeg6/lib
# make install-lib
# make install

安装时如果错误提示:
/usr/bin/install: 无法创建一般文件‘/usr/local/jpeg6/lib/libjpeg.a’: 没有那个文件或目录
make: *** [install-lib] Error 1
创建如下文件夹:
# mkdir /usr/local/jpeg6/lib
# make install
/usr/bin/install -c cjpeg /usr/local/jpeg6/bin/cjpeg
/usr/bin/install: 无法创建一般文件‘/usr/local/jpeg6/bin/cjpeg’: 没有那个文件或目录
make: *** [install] Error 1
# mkdir /usr/local/jpeg6/bin
/usr/bin/install -c -m 644 ./cjpeg.1 /usr/local/jpeg6/man/man1/cjpeg.1
/usr/bin/install: 无法创建一般文件‘/usr/local/jpeg6/man/man1/cjpeg.1’: 没有那个文件或目录
make: *** [install] Error 1
# mkdir /usr/local/jpeg6/man
# mkdir /usr/local/jpeg6/man/man1
# make install
好了,直到安装成功!

安装libpng:
# tar -zxf libpng-1.2.7-config.tar.gz
# cd libpng-1.2.7-config
# ./configure --prefix=/usr/local/libpng2
# make
# make install

安装freetype:
# tar -zxf freetype-2.1.9.tar.gz
# cd freetype-2.1.9
# ./configure --prefix=/usr/local/freetype2
# make
# make install

安装gd库:
# tar -zxf gd-2.0.26gif.tar.gz
# cd gd-2.0.26gif
# ./configure --prefix=/usr/local/gd2 --with-zlib=/usr/local/zlib2/ --with-png=/usr/local/libpng2/ --with-jpeg=/usr/local/jpeg6/ --with-freetype=/usr/local/freetype2/ (请指定及格插件的安装路径,否则安装php的时候可能出错!)
# make
# make install

安装apache,(php5安装环境需要apache2.0.46以上版本,下载的时候注意!)
#tar zxvf httpd-2.0.50.tar.gz
#cd httpd-2.0.50
#./configure --prefix=/usr/local/apache2 --enable-module=so
#make
#make install

安装php5:
# tar -zxf php5-200411260130.tar.gz
# cd php5-200411260130
# ./configure --prefix=/usr/local/php --with-apxs2=/usr/local/apache2/bin/apxs --with-jpeg-dir=/usr/local/jpeg6/ --with-png-dir=/usr/local/libpng2/ --with-gd=/usr/local/gd2/ --with-freetype-dir=/usr/local/freetype2/ --enable-trace-vars --with-zlib-dir=/usr/local/zlib2/ -with-mysql=/var/lib/mysql
# make
# make install
# cp php.ini-dist /usr/local/php/lib/php.ini

安装libxml的时候如果指定了路径,make的时候可能出错,所以,我安装的时候没指定!
我MAKE PHP4.3.8 和5的时候都遇到:
ext/gd/gd.lo(.text+0x63a): In function `zm_deactivate_gd':

/root/software/php-4.3.8/ext/gd/gd.c:385: undefined reference to `gdFreeFontCache'

collect2: ld returned 1 exit status

make: *** [sapi/cli/php]Error 1
另外还有一个错误可能是什么: libphp.lo的错误(错误当时没有纪录,忘记了,大概是!)
是因为GD库的freetype没装,或者是安装gd库的时候煤油指定插件的路径!!

复制PHP.INI文件到正确位置
在PHP目录下运行
#cp php.ini-dist /usr/local/php/lib/php.ini

编辑apache配置文件httpd.conf
#vi /usr/local/apache2/conf/httpd.conf

要改的有如下几处:

一般都在
#AddType application/x-tar .tgz
下加一行
#LoadModule php5_module modules/libphp5.so
AddType application/x-httpd-php .php
如果你搜索其它地方没有以下这行

LoadModule php5_module modules/libphp5.so
请把上面的#号去掉

还有找到
DirectoryIndex index.html index.html.var
在后面加 index.php 让它把index.php做为默认页

找到
# don't use Group #-1 on these systems!
把下面的用户名和组改为
User apache
Group apache
(原来好像是nobody)

再找
#ServerName
把#去掉,后面的IP改成你的IP.

找到
DocumentRoot "/usr/local/apache2/htdocs"
把/usr/local/apache2/htdocs改为你存放网页文件的路径

为了让中文网页没乱码
找到
AddDefaultCharset iso8859-1
把后面的iso8859-1改为gb2312 或者是干脆off
其他的选项就自己修改吧!或者到http://article.21e.cn 有一个专门的httpd.conf的说明!

保存httpd.conf文件.

启动apache
# /usr/local/apache2/bin/apachectl start
如果没有出错,写一个测试页放到你网页目录下.访问就应该可以看到php的版本等信息了!

如无意外.PHP脚本已经可以连接mysql了.

用ZendOptimizer加速PHP

#tar zxvf ZendOptimizer-2.5.3-linux-glibc21-i386.tar.gz
#cd ZendOptimizer-2.5.3-linux-glibc21-i386
#./install.sh
安装的时候注意输入正确的安装apache的路径等内容!
安装完毕,再看看phpinfo页面的zend选项就发现了!!

如果你以前的apache没有删除,而你又想使用
# /etc/init.d/httpd restart
这样重起apache的话,那就修改/etc/init.d/httpd这个文件,修改其中的apahce的三个路径就可以了!!

好了,终于安装完毕,你的机器已经是最新的apache+php+mysql+gd.......了,恭喜!!

艾,累死我了,整整弄了好几天!!休息了!!~(安装的时候,可能安装的时候和写不不是很一样,不过大同小异!)

同时感谢以前那几位辛苦的安装者,参照你们的文章我才得以顺利安装成功,虽然还是用了好几天!~

posted @ 2007-07-27 15:13 路边的石头-java 阅读(205) | 评论 (0) | 编辑 收藏
 
rpm命令参数表

1.安装一个包
# rpm -ivh 
2.升级一个包
# rpm -Uvh 
3.移走一个包
# rpm -e 
4.安装参数
--force 即使覆盖属于其它包的文件也强迫安装
--nodeps 如果该RPM包的安装依赖其它包,即使其它包没装,也强迫安装。
5.查询一个包是否被安装
# rpm -q < rpm package name>
6.得到被安装的包的信息
# rpm -qi < rpm package name>
7.列出该包中有哪些文件
# rpm -ql < rpm package name>
8.列出服务器上的一个文件属于哪一个RPM包
#rpm -qf 
9.可综合好几个参数一起用
# rpm -qil < rpm package name>
10.列出所有被安装的rpm package
# rpm -qa 
11.列出一个未被安装进系统的RPM包文件中包含有哪些文件?
# rpm -qilp < rpm package name>
-------------------------------------------------------------------------------------

详细手册如下:
一、安装 

命令格式: 

rpm -i ( or --install) options file1.rpm ... fileN.rpm 

参数: 

file1.rpm ... fileN.rpm  将要安装的RPM包的文件名 

详细选项: 

-h (or --hash) 安装时输出hash记号 (``#'')   
--test         只对安装进行测试,并不实际安装。 
--percent      以百分比的形式输出安装的进度。 
--excludedocs  不安装软件包中的文档文件 
--includedocs  安装文档 
--replacepkgs  强制重新安装已经安装的软件包 
--replacefiles 替换属于其它软件包的文件 
--force        忽略软件包及文件的冲突 
--noscripts    不运行预安装和后安装脚本 
--prefix 将软件包安装到由 指定的路径下 
--ignorearch    不校验软件包的结构 
--ignoreos      不检查软件包运行的操作系统 
--nodeps        不检查依赖性关系 
--ftpproxy   用 作为 FTP代理   
--ftpport    指定FTP的端口号为 

通用选项 

-v  显示附加信息 
-vv 显示调试信息 
--root 让RPM将指定的路径做为"根目录",这样预安装程序和后安
装程序都会安装到这个目录下 
--rcfile 设置rpmrc文件为   
--dbpath   设置RPM 资料库存所在的路径为 

二、删除 

命令格式: 

rpm -e ( or --erase) options pkg1 ... pkgN 

参数 

pkg1 ... pkgN :要删除的软件包 

详细选项 

--test      只执行删除的测试 
--noscripts 不运行预安装和后安装脚本程序 
--nodeps    不检查依赖性 

通用选项 

-vv           显示调试信息 
--root 让RPM将指定的路径做为"根目录",这样预安装程序和后安装
程序都会安装到这个目录下 
--rcfile 设置rpmrc文件为 
--dbpath   设置RPM 资料库存所在的路径为 

三、升级 

命令格式 

rpm -U ( or --upgrade) options file1.rpm ... fileN.rpm 

参数 

file1.rpm ... fileN.rpm 软件包的名字 

详细选项 

-h (or --hash) 安装时输出hash记号 (``#'')   
--oldpackage   允许"升级"到一个老版本 
--test         只进行升级测试 
--excludedocs  不安装软件包中的文档文件 
--includedocs  安装文档 
--replacepkgs  强制重新安装已经安装的软件包 
--replacefiles 替换属于其它软件包的文件 
--force        忽略软件包及文件的冲突 
--percent      以百分比的形式输出安装的进度。 
--noscripts    不运行预安装和后安装脚本   
--prefix 将软件包安装到由 指定的路径下 
--ignorearch    不校验软件包的结构 
--ignoreos      不检查软件包运行的操作系统 
--nodeps        不检查依赖性关系 
--ftpproxy 用 作为 FTP代理   
--ftpport  指定FTP的端口号为 

通用选项 

-v  显示附加信息 
-vv 显示调试信息 
--root 让RPM将指定的路径做为"根目录",这样预安装程序和后安装程序都会安装到这个目录下 
--rcfile 设置rpmrc文件为   
--dbpath   设置RPM 资料库存所在的路径为 

四、查询 

命令格式: 

rpm -q ( or --query) options 

参数: 

pkg1 ... pkgN :查询已安装的软件包 

详细选项 

-p (or ``-'') 查询软件包的文件 
-f           查询属于哪个软件包 
-a                  查询所有安装的软件包 
--whatprovides  查询提供了 功能的软件包   
-g          查询属于 组的软件包 
--whatrequires 查询所有需要 功能的软件包 

信息选项 

显示软件包的全部标识 
-i 显示软件包的概要信息 
-l 显示软件包中的文件列表 
-c 显示配置文件列表 
-d 显示文档文件列表 
-s 显示软件包中文件列表并显示每个文件的状态 
--scripts 显示安装、卸载、校验脚本 
--queryformat (or --qf) 以用户指定的方式显示查询信息 
--dump 显示每个文件的所有已校验信息   
--provides 显示软件包提供的功能 
--requires (or -R) 显示软件包所需的功能 

通用选项 

-v 显示附加信息 
-vv 显示调试信息 
--root 让RPM将指定的路径做为"根目录",这样预安装程序和后安装程序都会安装到这个目录下 
--rcfile 设置rpmrc文件为   
--dbpath 设置RPM 资料库存所在的路径为 

五、校验已安装的软件包 

命令格式: 

rpm -V ( or --verify, or -y) options 

参数 

pkg1 ... pkgN 将要校验的软件包名 

软件包选项 

-p Verify against package file   
-f 校验所属的软件包 
-a Verify 校验所有的软件包 
-g 校验所有属于组  的软件包 

详细选项 

--noscripts 不运行校验脚本   
--nodeps    不校验依赖性 
--nofiles   不校验文件属性 

通用选项 

-v   显示附加信息 
-vv  显示调试信息 
--root 让RPM将指定的路径做为"根目录",这样预安装程序和后安装程序都会安装到这个目录下 
--rcfile 设置rpmrc文件为   
--dbpath   设置RPM 资料库存所在的路径为 

六、校验软件包中的文件 

语法: 

rpm -K ( or --checksig) options file1.rpm ... fileN.rpm 

参数: 

file1.rpm ... fileN.rpm 软件包的文件名 

Checksig--详细选项 

--nopgp 不校验PGP签名   

通用选项 

-v  显示附加信息 
-vv 显示调试信息 
--rcfile 设置rpmrc文件为   


七、其它RPM选项 

--rebuilddb 重建RPM资料库 
--initdb    创建一个新的RPM资料库 
--quiet     尽可能的减少输出 
--help      显示帮助文件 
--version   显示RPM的当前版本


posted @ 2007-07-27 14:45 路边的石头-java 阅读(233) | 评论 (0) | 编辑 收藏
 
使用Spring框架和AOP实现动态路由

本文的大体思路是展示了一次业务交易如何动态地为子系统处理过程触发业务事件。本文所示的例子使用Spring框架和Spring AOP有效地解耦业务服务和子系统处理功能。现在让我们仔细看看业务需求。

业务需求

客户注册系统(CRS——customer registration system)在其完成客户在线注册后需要发送通知给它的客户,并传送他们的地址数据到发票系统(invoice system),以便为付费产生发票。

技术设计

让我们把上面提到的业务需求分解成技术设计。本例中,我们将定义一个自定义业务事件以标示客户注册过程。

一个事件可以被认为是在特定时间间隔发生的一些事情。本例中,它是客户注册过程。典型地,当事件发生时,一个单一事件可能包含了一个或多个需要发生的动作。按照业务需求,我们已经确定了如下两个动作:

  1. 发送邮件通知客户。
  2. 传送客户地址数据到发票系统。

我们现在将设计事件数据结构以持有存储在事件数据库表中的信息。以下事件属性被确定下来。

  • 事件标识符:1
  • 事件描述:客户注册事件
  • 动作代码:MT

该事件标识符是映射到数据库中的主键。事件描述定义了关于事件的描述信息。最后一个是动作代码,当事件发生时,它代表了要发生的不同动作。该动作代码定义在动作代码对照表中。

上面提到的事件,其动作代码标识为M和T。M代表发送一个邮件通知到客户,T代表传送客户地址数据到发票系统。

Example: Event.java

 /**
*Event.java - The event domain object
*@author - Vigil Bose
*/
public class Event implements Serializable {

private Integer eventId;
private String eventDesc;
private String eventActionCodes;
private static final long serialVersionUID = 1L;

/** The cached hash code value for this instance. Settting to 0 triggers
re-calculation. */
private int hashValue = 0;

/**
*@return the eventActionCodes
*/
public String getEventActionCodes(){
return eventActionCodes;
}
/**
* @param eventActionCodes the eventActionCodes to set
*/
public void setEventActionCodes(String eventActionCodes) {
this.eventActionCodes = eventActionCodes;
}
/**
* @return the eventDesc
*/
public String getEventDesc() {
return eventDesc;
}
/**
* @param eventDesc the eventDesc to set
*/
public void setEventDesc(String eventDesc) {
this.eventDesc = eventDesc;
}
/**
* Return the simple primary key value that identifies this object.
* @return the eventId
*/
public Integer getEventId() {
return eventId;
}
/**
* Set the simple primary key value that identifies this object.
* @param eventId the eventId to set
*/
public void setEventId(Integer eventId) {
this.hashValue = 0;
this.eventId = eventId;
}
/**
*Implementation of the equals comparison on the basis of equality
*of the primary key values.
* @param rhs
* @return boolean
*/
public boolean equals(Object rhs){
if (rhs == null)
return false;
if (! (rhs instanceof Event))
return false;
Event that = (Event) rhs;
if (this.getEventId() == null || that.getEventId() == null)
return false;
return (this.getEventId().equals(that.getEventId()));
}

/**
* Implementation of the hashCode method conforming to the Bloch pattern with
* the exception of array properties (these are very unlikely primary key types).
* @return int
*/
public int hashCode(){
if (this.hashValue == 0){
int result = 17;
int eventIdValue = this.getEventId() == null ? 0 :
this.getEventId().hashCode();
result = result * 37 + eventIdValue;
this.hashValue = result;
}
return this.hashValue;
}
}

现在我们已经设计了事件领域对象以代表客户注册事件。现在我们转而设计Web层与业务服务层之间的API契约。设计的约束之一是改变领域模型不能破坏Web层与业务服务层之间的API契约。为了满足该设计约束,确定了两个数据包装类:AbstractData和UserData。AbstractData本质上定义了一些行为的抽象。UserData是AbstractData的子类,并提供了其他行为。比如,如果你有一个应用框架,抽象类可以提供诸如事件和消息处理的默认服务。

在本例中,AbstractData负责收集各种业务事件。AbstractData的子类是UserData,用来持有我们的主要领域对象(用户对象)。

用户领域对象由不同属性组成,这些属性是用来识别用户的,如userId、firstName、lastName和加过密的password。它还包含了地址领域对象,该对象有多个地址属性,如address line 1、address line 2、city、state等等。

Example: AbstractData.java

/**
*AbstractData.java - A template pattern like class that implments
*the event collection behavior. This class is used by all data
*transfer wrapper objects between UI Layer and Server side Layer
*@author - Vigil Bose
*/
public abstract class AbstractData{

/**
*Stores all the events identified during the transaction
*Processing.
*/
private Set eventSet = Collections.synchronizedSet(new HashSet());

/**
* @return Returns the eventSet.
*/
public Set getEventSet() {
return eventSet;
}

/**
*@param event - An instance of a business event resulted from a particular
*business transaction
*/
public void addToEventSet(Event event) {
this.eventSet.add(event);
}

}

在AbstractData中声明一个集合(Set)的原因,是为了避免在给定时间点集合中有同一重复事件。让我们看看UserData长什么样吧。UserData包含了实际User领域对象。因此针对User领域对象的任何改变都被限制在这个包装类中,不会破坏客户端和业务服务层之间的接口契约。

Example: UserData.java

/**
*UserData.java - A concrete POJO data wrapper whose responsibility is to
*holds the main domain object reference and used between client and business
*service layers.
*@author - Vigil Bose
*/
public class UserData extends AbstractData{

private User user;

/**
* @return The user domain object instance
*/
public Users getUsers(){
return this.users;
}
/**
*@param The user instance to set.
*/
public void setUser(User user){
this.user = user;
}

}

让我们看看业务服务接口。本例中,我们将在IRegistrationService接口中定义一个叫做doRegister()的API契约,以完成用户注册业务过程。该API本质上是事务型的,因为它要向多个数据库表插入记录。客户层和业务层通过该接口进行交互。

Example: IRegistrationService.java

/**
*IRegistrationService.java - A classic example of EJB's business
*methods interface pattern that exposes all the Registration
*related business methods that can be implemented by both the
*enterprise session bean as well as the Pojo service. Moreover
*this pattern allows us to switch to POJO implementation later
*if it makes sense to do so.
*@author - Vigil Bose
*/
public interface IRegistrationService{

/**
*API to complete the registration business process
*@param userData - The data wrapper object
*/
public void doRegister(AbstractData userData);

}

为了简单起见,本例中我们只用了POJO(Plain Old Java Object)服务。业务服务实现则实现了完成客户注册过程的业务逻辑。本例中,服务实现的唯一职责是将调用委派给数据访问层(Data Access Layer),以在适当的数据库表中记录客户注册交易的状态。

Example: RegistrationServiceImpl.java

/**
*The primary business method implementation of Customer Registration Service.
*This is a POJO. It does not depend on any Spring APIs. It's usable outside a
*Spring container, and can be instantiated using new in a JUnit test. However,
*we can still apply declarative transaction management to it using Spring AOP.
*@author - Vigil Bose
*/
public class RegistrationServiceImpl implements IRegistrationService{

private IRegistrationServiceDao registrationServiceDao;

/**
* A setter method of dependency injection
* @param registrationServiceDao - The registrationServiceDao to set.
*/
public setRegistrationServiceDao(IRegistrationServiceDao
registrationServiceDao){
this.registrationServiceDao = registrationServiceDao;
}
/**
* API to register the user
* @param user - The user domain object
*/
public void doRegister(AbstractData userData){
this.registrationServiceDao.completeRegistration(userData.getUser());
}
}

务实使用DAO模式

Data Access Object(DAO——数据访问对象)在核心J2EE设计模式一书中被编目为一个集成层设计模式。它把持久库存取和操作代码封装到了一个单独的层次。本文中所指的持久库就是RDBMS。

该模式在业务逻辑层和持久存储层之间引入了一个抽象层。业务对象通过数据访问对象访问RDBMS(数据源)。该抽象层简化了应用代码并引入了灵活性。理想地,对数据源所做的变动(比如变换数据库厂商或类型),仅仅需要改变数据访问对象,因而对业务对象影响最小。本例中,我们使用Hibernate实现数据访问策略。

DAO设计模式所提供的的灵活性主要被归因于对象设计的最佳实践:用接口编程。该原则规定了具体对象必须实现一个接口,在调用程序中使用该接口而非具体对象本身。因此,你可以容易地替换一个不同的实现,而对客户端代码冲击很小。

遵循上面所说的原则,我们将定义注册服务DAO接口——IRegistrationServiceDao.java,它有一个completeRegistraion()行为。业务组件将通过这个接口与DAO交互。

Example: IRegistrationServiceDao.java

/**
*A POJO data access object interface for the CRS services business layer.
*The API's defined in this interface are all transactional APIs within the
*business services layer
*@author - Vigil Bose
*/
public interface IRegistrationServiceDao{
/**
* Data Access API to create the new user in the system
* @param user - The composite user domain object
*/
public void completeRegistration(User user) throws DataAccessException;
}

在定义了数据访问接口之后,我们必须提供一个IRegistrationServiceDao的具体实现——RegistrationServiceDaoImpl。

本例中该实现中使用了Hibernate。这里所使用的模式是策略模式,可以用任何对象关系映射(Object Relation Mapping)产品或JDBC来替换。该类的职责是将客户注册交易的状态记录在数据库表中。

Example: RegistrationServiceDaoImpl.java

/**
*The Registration Services Data Access Strategy implementation
*using Hibernate persistence mechanism that support various
*registration related business transactions.
*@author - Vigil Bose
*/
public class RegistrationServiceDaoImpl extends HibernateDaoSupport
implements IRegistrationServiceDao{

/**
* Data Access API to create the new user in the system
* @param users - The composite users domain object
*/
public void completeRegistration(Users users) throws DataAccessException {

getHibernateTemplate().save(users);
}

}

任何应用程序中,访问只读数据都是重要的。让我们看一个普通java接口——ILookUpServiceDao的例子,它在CRS中被用到,其暴露了finder和getter方法以访问只读数据。

Example: ILookUpServiceDao.java

/**
*A POJO data access object interface that exposes the lookup API's in Customer
*Registration System.
*The API's defined in this interface can be used with or without any other
*transactional APIs within the business services layer
*@author - Vigil Bose
*/
public interface ILookUpServiceDao{
/**
* Data Access API to find the event instance based on its primary key
* @param eventId - The event tables primary key identifier
*/
public Event findEventById(Integer eventId) throws DataAccessException;

}

下例是Hibernate实现策略。ILookUpServiceDao接口中的API定义在具体类LookUpServiceDaoImpl中被实现。为了简单,这里只显示了一个API实现。

Example: LookUpServiceDaoImpl.java

/**
*A POJO data access implementation that implements the lookup API's in Customer
*Registration System.
*The API's defined in this interface can be used with any other
*transactional APIs within the business services layer
*@author - Vigil Bose
*/
public classe LookUpServiceDaoImpl extends HibernateDaoSupport
implements ILookUpServiceDao {
/**
* Data Access API to find the event instance based on its primary key
* @param eventId - The event tables primary key identifier
* @return an instance of Event domain object
* @throws DataAccessException
*/
public Event findEventById(Integer eventId) throws DataAccessException{
return (Event)getHibernateTemplate().get(Event.class, eventId);
}

}

Spring框架提供的HibernateDaoSupport类是一个模板模式实现,其抽象了Hibernate相关API和与Hibernate Session相关的资源管理。

有了数据访问实现,我们对业务需求的一个方面——客户注册过程——感到满意了。现在我们将考虑需求的第二个方面,它是在设计期间所确定的子系统功能。该需求是这样的:当注册完成时,系统将发送邮件通知给客户并传送客户地址数据给发票系统以产生发票。我们将通过著名的命令模式来实现子系统处理过程。

命令模式

命令模式用来提供一个公共接口以执行不同的命令。任何实现了该命令接口的类可以在其execute()方法中提供特定任务的实现。

在设计期间,我们已经确定了针对同一接口的两个不同命令实现。就业务服务层来说,子系统功能是一个单独的关注点。因此我使用AOP技术将这个单独的关注点从主要业务服务层实现(RegistrationServiceImpl)中解耦。

想更多了解AOP概念的人,请点击这里。你还可以从下面看到一些关于AOP的介绍。现在,让我们设计命令接口。

Example: ICommand.java

/**
*ICommand.java - The famous command interface that exposes the execute API.
*@author - Vigil Bose
*/
public interface ICommand{

/**
*The Command design pattern encapsulates the concept of the
*command into an object. The issuer holds a reference to the
*command object rather than to the recipient.The issuer sends
*the command to the command object by executing a specific
*method on it. The command object is then responsible for
*dispatching the command to a specific recipient to get the
*job done.
*@param data - The data transfer object
*/
public void execute(Object data);

}

典型的命令实现提供了打包一组计算的方法(一个接收者和一组动作)并把它作为第一级别对象向周围传递。该命令对象调用请求命令接收者(receiver)的方法以真正处理请求。通常也能发现一个命令实现自己处理特定任务,无须委派请求给receiver。在本例中,MailingCommandImpl通过调用EmailService实现了发送邮件通知的任务。为简单起见,EmailService实现没有在本例中展现。毕竟,我们的意图是展现业务事件是如何借助于AOP和Spring2.0被路由到子系统处理器的。

Example: MailingCommandImpl.java

/**
*MailingCommandImpl.java - A command implementation that implements
*the task of sending mail notification to the customer who completed
*the registration process.
*@author - Vigil Bose
*/
public class MailingCommandImpl implements ICommand{

private IEmailService emailService;

/**
*A setter method of dependency injection
*@param emailService - The emailService instance to set.
*/
public void setEmailService(IEmailService emailService){
this.emailService = emailService;
}
/**
*API execute is used to execute the mailing tasks implemented
*@param args - An instance of AbstractData used in business service layer
*/
public void execute(Object args){

//get the reference of user object
User user = (User)args;

//get the reference of address object via its parent object User.
Address address = user.getAddress()

//Invoke the EmailService API here to send out the notifications....

}
}

现在,我将设计第二个命令实现,它将帮助我们实现关于传送客户地址数据到发票应用的业务需求。在这个特定实现中,我们可以选择任何协议(如Web服务、消息传递,或 XML over Http等等)来发送客户信息到发票应用(假设发票应用可以使用上面提到的任何协议用作应用集成)。为了简单,下面给出的是使用JMS消息传递的例子。本例并没有展示JMS消息传递的内部结构。

Example: SendCustomerInfoCommandImpl.java

 /**
*SendCustomerInfoCommandImpl.java - A command implementation that implements
*the task of transmiting the customer's address data to the invoice system.
*@author - Vigil Bose
*/
public class SendCustomerInfoCommandImpl implements ICommand{

private IMessagingService messagingService;

/**
* A setter method of dependency injection
*/
public void setMessagingService(IMessagingService messagingService){
this.messagingService = messagingService;
}

/**
*API execute is used to execute the messaging task implemented.
*@param args - An instance of AbstractData used in the business service layer
*/
public void execute(Object args){

User user = (User)args;

//Invoke the appropriate messagingService API
//to send the customer information here....
}
}

AOP的基本概念

AOP也被称为Aspect Oriented Programming(面向方面编程)试图帮助程序员分离关注点,尤其是横向切面关注点(cross-cutting concerns)。过程、包、类及方法都是帮助程序员把关注点封装到单一实体内。但是有些关注点不适合这种形式的封装。我们称之为横向切面关注点,是因为它们横跨了程序中许多模块。它可能使代码分散或缠结(scattered or tangled),使人们更难理解和维护。当一个关注点(例如本利的事件路由)蔓延到许多模块(类和方法)时,代码被分散了。这意味着修改事件分发功能可能需要修改所有受影响的模块。

代码失去了典雅和简单,因为各种新的关注点已经与基本功能(有时称为业务逻辑关注点)缠结在一起。事务、消息传递、安全以及日志都是横向切面关注点的例子。

AOP试图通过让程序员在一个单独的称之为aspect的模块中表达横向切面关注点来解决这些问题。Aspect可以包含advice(加入到程序指定点的代码)和inter-type声明(增加到其他类的结构成员)。例如,一个安全模块可以包含在访问一个银行账户前执行安全检查的advice。pointcut定义了一个银行账户能被访问的时机(加入点,join points),而在advice体内的代码定义了安全检查是如何实现的。使用这种方式,检查代码和位置点可以在一个地方维护。更进一步,好的pointcut可以预见后期程序变动,因此如果另一个开发者创建了一个新的方法来访问银行账户,在其执行时advice将应用到该新方法上。占据领导地位的AOP实现是AspectJ、AspectWorkz、Spring AOP等等。

Spring AOP用纯Java实现。不需要特殊编译处理。AspectJ需要特殊编译处理。Spring AOP不需要控制各层级类装载器,因而适合用在J2EE web容器或应用服务器中。Spring 2.0还提供了与AspectJ的紧密集成。

事件路由

为了满足我们的业务需求,我以及确定了两个AOP组件。它们是RegistrationBeforeAdvice和RegistrationAfterAdvice。请参考Spring参考文档以获得更多关于各种AOP advice和其他概念。

识别出两个AOP组件背后的基本原理是支持事件路由和在代码中最小化交叉依赖。RegistrationBeforeAdvice的职责被限制在识别和收集适当的业务事件。该before advice的Spring AOP实现可以被配置在Spring应用上下文配置文件(Spring application context file)中,以截获业务服务接口API来注入定制行为——识别并增加正确的业务事件到事件集合中。

本例中,RegistrationBeforAdvice截获业务服务接口的doRegister(AbstractData data)API。该advice访问该服务接口API的入参(AbstractData)。早期在AbstractData层实现的事件集合在这里也变得垂手可得。RegistrationBeforeAdvice识别恰当的业务事件并把它增加到event集合中。

Spring应用上下文中的eventMap配置是一个全局事件映射(global event map)。eventKey将适当的业务服务接口API名称映射到事件标识符。这让我们可以在全局事件映射配置中无缝地将一个定义在业务服务接口的新的业务API映射到一个事件id,而无需修改RegistrationBeforeAdvice AOP组件的任何代码。然而,对这种方法有一个警告。当程序员犯了错误,在全局事件映射配置中配置了错误的方法名到eventId,这种错误在编译期间并不容易发现。但是一个简单的Junit测试即可发现这种用词不当的错误。

业务API名称:doRegister
Event Id: 1

映射另一个业务API名,如doUpdate(),到另一个值为2的事件id现在变得非常容易了。我们所要改变的只是在接口中定义新的API之后,在Spring应用上下文配置文件的事件映射中增加一个映射即可。下例给出了配置片断。

<!-- creates a java.util.Map instance with values loaded from
the supplied 'sourceMap'.Global Event Map. The values are mapped
in event table. The keys are matched with the business API name-->
<util:map id="eventMap">
<entry key="doRegister">
<value type="java.lang.Integer">1</value></entry>
<entry key="doUpdate">
<value type="java.lang.Integer">2</value></entry>
</util:map>

在某些情况下,单个业务处理可能导致多个事件。这仍只需轻微修改RegistrationAfterAdvice(译注:疑为RegistrationBeforeAdvice )的代码及事件映射配置即可。这种情况下,我们需要说明每个业务交易的事件列表。为简单起见,本文例中只限于展示一个业务交易仅有一个事件的情况。

请参考下面的代码样例,实际看看before advice。

Example: RegistrationBeforeAdvice.java

/**
*RegistrationBeforeAdvice.java - This advise acts before the doRegister() API in
*the business service interface executes and sets the appropriate event in the
*eventSet collection implemented in the AbstractData layer. The event is Customer
*Registered Event. This advice inserts custom behavior in IRegistrationService API
*doRegister() before it is executed and identifies the correct business event add
*it to the event collection
*@author - Vigil Bose
*/
public class RegistrationBeforeAdvice implements MethodBeforeAdvice{

private Map eventMap;
private ILookUpServiceDao lookUpServiceDao;

/**
* A setter method of dependency injection
* @param Map - The eventMap instance to set.
*/
public void setEventMap(Map eventMap){
this.eventMap = eventMap;
}
/**
* A setter method of dependency injection
* @param lookUpServiceDao - The lookUpServiceDao instance to set.
*/
public void setLookUpServiceDao(ILookUpServiceDao lookUpServiceDao){
this.lookUpServiceDao = lookUpServiceDao;
}
/**
*Before advice can insert custom behavior before the join point
*executes, but cannot change the return value.If a before advice
*throws an exception, this will abort further execution of the
*interceptor chain. The exception will propagate back up the
*interceptor chain. If it is unchecked, or on the signature of the
*invoked method, it will be passed directly to the client; otherwise
*it will be wrapped in an unchecked exception by the AOP proxy.
*@param method - method being invoked
*@param args - arguments to the method
*@param target - target of the method invocation. May be null
*@throws Throwable - if this object wishes to abort the call.
*Any exception thrown will be returned to the caller if it's allowed
*by the method signature. Otherwise the exception will be wrapped
*as a runtime exception
*@see org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method
*java.lang.Object[], java.lang.Object)
*/
public void before(Method method, Object[] args, Object target) throws Throwable {

AbstractData data = (AbstractData)args[0];

Set keySet = this.eventMap.keySet();
Integer eventId;

Event event = null;
String eventKey = null;

//Iterate through the key set and extract event identifier and
//retrieve the event from the database and add it to the event
//collection.

for (Iterator iter = keySet.iterator(); iter.hasNext();) {
eventKey = (String) iter.next();

//Check whether the eventKey is matched with the business
//service interface API name. If it does, extract the eventId
//and retrieve the event instance from the datastore and add it
//to the event collection.

if (eventKey.equalsIgnoreCase(method.getName()){
eventId = (Integer)eventMap.get(eventKey);

event = this.lookupService.findEventById(Integer eventId);
data.addToEventSet(event);
}
}
}
}

本例中,一个需考虑的设计限制是Before advice或After advice组件抛出的异常应该不影响在线业务交易。在线客户不应受到事件路由错误的惩罚。为简化起见,我没有在该例中展示如何处理异常。

RegistrationAfterAdvice负责迭代事件集合、动作代码以及初始化路由过程。本例中使用的动作代码是M和T。在Spring应用上下文配置文件中每一个动作代码都有命令与之映射。RegistrationAfterAdvice通过每个与事件(客户注册事件)相关联的动作代码及获得映射的命令对象实例对事件集合进行迭代。一旦命令对象引用被获得,路由自动地发生,通过传递客户数据给每个命令实现以执行适当的任务。

Example: RegistrationAfterAdvice.java

/**
*RegistrationAfterAdvice.java - This advise acts after when the doRegister()
*API of the business service implementation is executed. This advise will
*actually delegate the event actions associated with the Customer Registered
*Event to the appropriate command. This advice inserts custom behavior to
*IRegistrationService interface API's after the API is executed.
*@author - Vigil Bose
*/
public class RegistrationAfterAdvice implements AfterReturningAdvice {
/**
*After returning advice is invoked only on normal method return,
*not if an exception is thrown. Such advice can see the return
*value, but cannot change it.
*
*@param returnValue - the value returned by the method, if any
*@param method - method being invoked
*@param args - arguments to the method
*@param target - target of the method invocation. May be null
*@throws Throwable - if this object wishes to abort the call.
*Any exception thrown will be returned to the caller if it's allowed
*by the method signature. Otherwise the exception will be wrapped as a runtime
*exception
*@see org.springframework.aop.AfterReturningAdvice#afterReturning
*(java.lang.Object, java.lang.reflect.Method, java.lang.Object[],
*java.lang.Object)
*/
public void afterReturning(Object returnValue, Method method, Object[] args,
Object target) throws Throwable {

AbstractData data = (AbstractData)args[0];
User userInfo = (User)data.getUser();
Set eventSet = data.eventSet();

Set keySet = this.commandMap.keySet();
Event event = null;
String actionCodes = null;
String actionCode = null;

//Iterate through the event set
for (Iterator iter = eventSet.iterator(); iter.hasNext();) {

event = (Event) iter.next();
actionCodes = event.getEventActionCodes();

//Loop through the action codes and extract each command mapped to
//the action code in spring application context file.

for (int i=0; i < actionCodes.length();i++){
actionCode = new Character(eventActionCodes.charAt(i)).toString();
  command = (ICommand)commandMap.get(actionCode);
if (command != null){
command.execute(userInfo);
}
}
}
}
}

在上面例子中可以看到,我本可以在RegistrationAfterAdvice本身实现事件集合和事件路由机制。但为了保持事件集合和事件路由的责任分离,我决定使用两个AOP advice组件处理子系统路由功能。

完整配置

现在,在Spring应用上下文xml文件中将所有配置都串起来。在下面的例子中,Hibernate实现被用作数据访问层的数据关系映射(OR映射)。当应对多个资源提供者(如数据库和JMS)时,推荐使用JtaTransation策略。为简单起见,下例中只展示了本地事务策略。

Example: applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-2.0.xsd
       http://www.springframework.org/schema/jee
       http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">

     <bean id="registrationService"  class="RegistrationServiceImpl">
            <property name="registrationServiceDao" ref="registrationServiceDao"/>
    </beans>

     <bean id="registrationServiceDao"  class="RegistrationServiceDaoImpl">
            <property name="sessionFactory" ref="sessionFactory"/>
    </beans>

     <bean id="lookUpServiceDao"  class="LookUpServiceDaoImpl">
            <property name="sessionFactory" ref="sessionFactory"/>
    </beans>

     <bean id="sessionFactory"
  class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"/>
       <property> name="dataSource" ref="dataSource"/>
       <!-- Use the following property jtaTransactionManager when dealing
       with CMT transactions -->
  <!-- <property name="jtaTransactionManager" ref="jtaTransactionManager"/>-->
      <property name="hibernateProperties">
       <props>
           <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop>
           <prop key="hibernate.connection.pool_size">3</prop>
           <prop key="hibernate.show_sql">true</prop>
           <prop key="hibernate.generate_statistics">true</prop>
           <prop key="hibernate.cache.use_structured_entries">true</prop>
           <prop key="hibernate.max_fetch_depth">3</prop>
           <prop key="hibernate.cache.provider_class">
                     org.hibernate.cache.EhCacheProvider</prop>
           <prop key="hibernate.cache.region_prefix">node1</prop>
       </props>
       </property>
       <property name="mappingResources">
       <list>
             <value>Users.hbm.xml</value>
             <value>Address.hbm.xml</value>
       </list>
      </property>
    </bean>

      <bean>id="transactionManager"
  class="org.springframework.orm.hibernate3.HibernateTransactionManager">
       <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

      <!-- <bean id="jtaTransactionManager"
  class="org.springframework.jndi.JndiObjectFactoryBean"> -->
    <!--The JNDI name of TransactionManager published in OC4J Container in 10g-->
    <!-- <property name="jndiName"
          value="java:comp/pm/TransactionManager"/>
   </bean> -->

   <!-- Transaction manager that delegates to JTA for ultimate coordinate of  transactions -->
    <!-- <bean id="transactionManager"
     class="org.springframework.transaction.jta.JtaTransactionManager"/>-->

     <aop:config>
        <!--Format: execution(modifiers-pattern? ret-type-pattern
         declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)-->

         <!--The pointcut expression here is the execution of any public method
        defined by the IRegistrationService interface-->

         <aop:pointcut id="registrationServicePointcut"
             expression="execution(public * *..IRegistrationService.*(..))"/>
                     <!--
        Here: applying the advice named "registrationBeforeAdvice" to all methods        on classes named RegistrationServiceImpl.
        -->
        <aop:advisor pointcut-ref="registrationServicePointcut"
                  advice-ref="registrationBeforeAdvice" order="1"/> 

                 <!--
        This definition creates auto-proxy infrastructure based on the given
        pointcut, expressed in AspectJ pointcut language. Here: applying the
        advice named "registrationServiceTransactionAdvice" to all methods
        on classes named RegistrationServiceImpl.-->
        <aop:advisor pointcut-ref="registrationServicePointcut"
              advice-ref="registrationServiceTransactionAdvice" order="2"/>

                        <!--
       This definition creates auto-proxy infrastructure based on the given
       pointcut,expressed in AspectJ pointcut language. Here: applying the
       advice named "registrationAfterAdvice" to all methods on
       classes named RegistrationServiceImpl.
       -->
       <aop:advisor pointcut-ref="registrationServicePointcut"
              advice-ref="registrationAfterAdvice" order="3"/>

                 </aop:config>
      <!--
        Transaction advice definition, based on method name patterns.
        Defaults to PROPAGATION_REQUIRED for all methods whose name starts with
        "do". By default, the transaction is rolled back for runtime
        exceptions including DataAccessException.
     -->
     <tx:advice id="registrationServiceTransactionAdvice"
                  transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="do*"/>
            </tx:attributes>
     </tx:advice>

      <bean id="registrationBeforeAdvice" class="RegistraionBeforeAdvice">
            <property name="order" value="1"/>
            <property name="eventMap" ref="eventMap"/>
            <property name="lookUpServiceDao" ref="lookUpServiceDao"/>
                 </bean>

         <bean id="registrationAfterAdvice"
                      class="RegistrationAfterAdvice">
              <property name="order" value="3"/>
              <property name="commandMap">
                 <map>
                     <entry key="M"><ref bean="mailingCommand"/></entry>
                     <entry key="T"><ref bean="sendCustomerInfoCommand"/> </entry>
                 </map>
            </property>
       </beans>

      <bean id="mailingCommand" class="MailingCommandImpl">
             <property name="emailService" ref="emailService"/>
      </beans>

        <bean id="sendCustomerInfoCommand" class="SendCustomerInfoCommandImpl">
              <property name="messagingService" ref="messagingService"/>
       </beans>

     <bean id="messagingService"    class="MessagingService">
               <property name="jmsTemplate" ref="jmsTemplate"/>
        </beans>

     <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
             <property name="connectionFactory" ref="defaultQueueConnectionFactory" />
         <property name="defaultDestination" ref="defaultQueueDestination" />
    </bean>

     <!-- JNDI Lookup configuration for  event queue connection factory
     used in messaging  -->
    <jee:jndi-lookup id="defaultQueueConnectionFactory" jndi-name="EVENT_QUEUE"/>
    <!-- JNDI Lookup configuration for  event queue destination used in messaging --> 
    <jee:jndi-lookup id="defaultQueueDestination" jndi-name="EVENTQueue"/>

     <!-- JNDI Lookup configuration for DataSource in J2EE environments -->
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/userDS"/>

     <bean id="mailSender"
  class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="localhost"/>
     </bean>    
    
     <bean id="emailService" class="EmailServicesImpl">       
   <property name="mailSender" ref="mailSender"/>
     </bean>

     <!-- creates a java.util.Map instance with values loaded from
        the supplied 'sourceMap'.Global Event Map. The values are mapped
        in event table. The keys are matched with the business API name-->
    <util:map id="eventMap">
        <entry key="doRegister">
              <value type="java.lang.Integer">1</value></entry>
   </util:map>
</beans>

AOP——Aspect Oriented Programming是程序设计中相对比较新的概念。该技术是对面向对象技术的补充,并带来了更多强大能力和关注点的分离,而这些正是面向对象技术的弱项。

结论

关注点分离是开发面向服务架构的关键原则。它需要被分别应用到基础架构层和实现层。本文中,我们示范了如何使用Spring框架的依赖注入原则和AOP特性分离出横向切面关注点。正如你已经在例子代码中看到的,使用这一方法能让我们把处理服务每个关注点的代码的交叉依赖减到最小。

参考

Spring参考文档: http://www.springframework.org/docs/reference/


免责

请将所提供的代码更多的视为示范使用的例子,而非被证明准备就绪的产品。如果你使用了这些代码并发现有任何问题,请通知我以便更新。


查看英文原文:Dynamic Routing Using Spring framework and AOP

posted @ 2007-07-27 13:58 路边的石头-java 阅读(480) | 评论 (0) | 编辑 收藏
 
微软拟3年内推新操作系统Windows 7

7月21日消息,据国外媒体报道,微软计划在3年内推出继Windows Vista之后的新版操作系统,内部代号为“Windows 7”。

  尽管Windows Vista上市还没有几个月的时间,但据知情人士透露,微软已经开始在讨论下一代操作系统。据悉,下一版操作系统的内部代号为Windows 7,预计在3年内上市。

  与Vista一样,Windows 7也将分为个人版和企业版,而且也分为32位和64位两种版本。目前,关于Windows 7的其他信息还比较少,但微软计划将更多的信息提供给企业用户和合作伙伴。

  Windows 7此前的开发代码被称为“维也纳(Vienna)”,微软代表已经证实,Windows 7是下一代操作系统的内部代码。

  事实上,微软现在就讨论Windows 7并不足为奇。因为Windows Vista用了5年时间才上市,已经是备受业界指责。而包括CEO史蒂夫·鲍尔默在内的微软高层此前也曾表示,下一代操作系统绝对不会用5年时间。


posted @ 2007-07-27 13:55 路边的石头-java 阅读(159) | 评论 (0) | 编辑 收藏
 
福布斯:Google成黑客最得力助手

        7月11日消息,据国外媒体报道,网络安全研究人员仲尼·郎近日表示,随着搜索引擎功能的日益完善,以及越来越多的信息涌入互联网,Google已经成为黑客发送攻击的得力助手。

  当仲尼需要某些信息时,他和大家一样,都选择了Goolge。但是,与一般网民搜索新闻或电影等内容不同,仲尼所要查找的是信用卡信息、保护保证号,或企业服务器上的其他私人信息。

  当然,仲尼并不是要发动攻击,这只是他工作内容的一部分。他是IT服务公司“计算机科学”的安全研究员,同时也是一名黑客,并撰写过黑客技术书籍。

  仲尼近日在接受《福布斯》采访时表示,利用Google搜索引擎,他可以搜索到很多私人信息,如信用卡号码、社会保障号,以及其他很多发动攻击所需要的身份信息。

  仲尼称,在一些教育机构站点,他搜到了全部的Excel表格,上面有学生名称、社会保障号和学分。而且,这只是小儿科。仲尼利用Goolge还可以侵入到SQL服务器,直接获取数据库信息。

  随着搜索引擎使用频率的增高,再加上Web 2.0的流行使越来越的私人信息暴露在互联网上,仲尼认为,Google对黑客的帮助作用将越来越强大。


posted @ 2007-07-27 13:54 路边的石头-java 阅读(159) | 评论 (0) | 编辑 收藏
 
Java、.NET,为什么不合二为一?

十二年之前,Sun公司默默宣布了一种可以使网页更动感和更富有活力的编程语言及其环境。当然,目前Java语言已经成为了一种普遍使用的工具,不仅仅用于为网页添加更多的动态效果,还包括创建和生成这些网页(透过servlets和JSP技术),提供一个用于事务性过程和商业逻辑的平台(透过EJB技术,即Enterprise Java Beans),访问消息系统(透过JMS技术,即Java Message Service),访问关系型数据库(透过JDBC技术),甚至于访问不同的主机(透过Java Connector API技术)。但这个故事还远远没有终结,每天,以Java为中心的社区通过开源的努力和大量的项目变得越来越强大,甚至于官方的Java平台也不断地通过Java Community Process这样一个开放性的国际组织来进行构建、成长和对自身进行增强。

六年之前,微软大张旗鼓地宣布了一系列崭新的编程语言和应用于各种开发场景下的环境。在此之后,.NET已经出现了两个发行版本。每一个主要的发行版本都会对运行时和三款主流的语言(C#,C++和Visual Basic)产生巨大的改变,同时也会为客户层和服务层带来许多新特性,如事务的支持(System.Transactions),泛型的支持(同时支持C#和Visual Basic),目录服务支持,管理(WMI)等等。这个故事也远远没有终结,微软甚至计划将一系列新技术应用到其下一个发行版本中(NetFX 3.0, 随Vista发行)。一个急速增长的社区也依然在不断扩大,并用开源和商业的新项目以及新构想增强了.NET环境。

在这几年中,在Java和.NET环境之间产生了大量的讨论,大多数的讨论都强烈地倾向于两者中的一方,这几乎没有产生任何作用。毕竟,诸如“我的编程语言比你的编程语言要好”或者“我的平台比你的平台运行得要快”,乃至于“你们很逊”这类的话题或许在鸡尾酒会和小组会议上是一个你来我往的颇为有趣的话题,但是这些话题对于引导一个有意义的软件开发是没有任何成效可言的。在经历了立场和姿态上的对立以及互相攻击以后,当尝试使.NET和Java共同工作和对此进行有意义的讨论时,这些对话却又转向了一些诸如“Web服务”、“企业服务总线”或“面向服务的体系架构”等繁杂的词汇中,而没有任何实在的展示。当越过这些高层的讨论去关注底层的细节时,对话中经常出现的又是SOAP、WSDL和WS协议,或者通过消息交换数据,或者在CLR中实现JVM,或者在JVM中实现CLR等。

换句话说,来解释这些流行的用语即“你大步迈进并讨论这如何去解决这个问题,但是却从来没有真正得讨论为什么你要这样做” 从历史的角度看,关于Java/.NET互操作性的讨论降低到了体系结构的次要位置,仅次于“按需”主题——也就是说这种互操作的发生仅仅应该在一个企业同时使用.NET和Java系统,并且需要在它们之间进行对话时。尽管如此,在这个讨论中关于动机问题的讨论被忽视了——为什么开发人员想要同时使用Java和.NET?尽管可能不需要这样做。

从表面上看来,这是一个危险的主题。因为不是由于对某个平台“不能”做什么的暗示而招致完全的义愤,就是任何关于某个平台可能在某方面“优于”另一个平台的说法都会导致偏爱或无知的谴责。(这甚至忽略了一个基本的问题,即指出这里的“优于”的定义是什么)。与其无视或躲避这个话题,不如直接面对它。这样的谴责和批评是不应该被忽略的,事实上我们应该欢迎它们,并将其作为一个大讨论的一部分,这个大讨论将解答何时、何地以及如何做出这些决策。尽管这样,我们认为开放式的讨论,时刻检查思想,允许读者形成自己的、批判的观点依然非常重要。

本文作为关于Java/.NET互操作性主题系列文章中的一篇,也正是基于此观点进行的。

* * *

当在大脑中思索什么是Java和.NET都做的好的方面时,有好几个场景会浮现于我们的脑海并值得我们向前探索。

Office客户端,J2EE服务器

微软的Office产品,无论好坏,即使在有电脑的历史以来不是最流行(这里所说的流行是指安装在更多的电脑主机上)的软件平台,也必然是最流行的软件平台之一。目前,Office产品已经迎来了第二个十年,作为一个强大的平台,用户可以输入,维护和查看广泛的、不同来源的数据。对于那些目前依赖于J2EE后台服务器的用户来说,既然有相当数量的数据,那么将这些数据转入Office平台来实现更加简单的管理和考察将是一个很好的考量。更让人感兴趣的是来考察Office平台利用过程无关的通信工具、实现利用Spring或其他轻型Java容器中业已实现的商业逻辑的方式。

在2006年8月发行的MSDN杂志发表了数篇关于Office开发的文章(并为此强烈建议任何对于Office编程能力不熟悉的人将此作为背景材料),在以“使用Office作为一个开发平台的须知”为题的一篇文章中,用一个图表展示了Office平台的全部能力。这里我们没有卷帙浩繁地列出完全名单,而是用一块区域简单列出Office可以与Java平台进行良好互动的几点特性:

  • 外部自动化。由于COM自动化技术的强大,COM自动化的后继者,Visual Studio Tools for Office (VSTO),这个主要的Office,包括Word、Excel、 Outlook 和其他应用程序等,组件可以被外部的应用程序接口所驱动,因此,各种Office文档就可以通过一些通用语言从外部创建。拿Excel的强大的图表和计算功能或者Word的强大的编辑和拼写检查功能来说,考虑在Java应用程序如何结合这些功能来实现何种新功能将十分有趣,在服务器上(如一个Web应用程序可以驱动Word来创建一个顾客邮件或者打印由J2EE服务器传入的包含特定数据元素的报告文本,就像使用Velocity引擎填充模板生成HTML的方式一样),或者是在客户端,利用Eclipse富客户端平台,一个已经实现可以作为COM自动化组件的宿主(事实上,Eclipse可以在一个安装有Office的Windows操作系统里创建Word文档)。当用户仅仅需要查看Word文档而不是创作Word文档时,这就显得尤其重要。微软提供了一个免费的Word查看程序,如果Java的Web应用程序负责创建Word文档,然后通过HTTP协议在网络上传送,这样就可以提供一个比HTML更加丰富的格式。

  • 插件。Office也可以提供插件功能,一些软件组件作为插件在Office的内部运行,通常的情形是将它们自身作为一个主菜单项或者一个上下文菜单。一个.NET组件可以将其自身注册为一个Excel电子表格应用程序插件,使用一些形式的Java互联技术(Web服务,远程调用工具包或其他过程内宿主)来连接Java的商业组件用于验证、数据获取或存储。比如,很多公司使用Excel作为一个发票和/或会计解决方案,而且他们可能使用了一些Java组件作为一个简单的进出公司会计系统的方式。这个会计系统一般是庞大的、基于Java技术的一个应用程序包,运行在一个企业级的服务器上,通过EJB中的会话Bean提供的Java连接器进行访问。

  • Excel用户自定义函数。Excel在其计算引擎中已经提供调用用户自定义函数功能有非常久的时间了,从历史来看,这些函数必然是使用非托管的(原始C++)代码编写而成,这些代码为应用程序带来了危险的不稳定性。创建一个Excel中的用户自定义函数为应用程序服务器中业已存在的商业逻辑进行一个简单的包装——比如一个存货支票调用Excel表格模板来生成一个订购单——可以提供给对大多数Excel用户来说Excel不曾给过的强大的在线体验。

  • 智能标记。智能标记是微软为文档中的一些小方框所起的新名称,这写文档中的小方框包含一个箭头,一般位于感兴趣的内容旁边。在文档中,智能标记经常会被配置和自定义特殊的元素,比如说在Word的自动纠错中,如果Word认为出现了一个打印错误,那么在被纠正的单词上方悬停鼠标就会出现一个智能标记,在没有出现真正的错误的情况下,允许用户选择取消这个纠正。智能标记就是插件的一种形式,因此其他帮助用户弥补客户端和企业服务之间裂痕的可视化辅助组件也可以使用此种形式。

Office同样为那些使用了纲领性元素的组件和文档提供了一些部署的支持,因此在很多情况下,在这些组件内进行功能的升级就像到一个共享下载服务器发布一些东西一样简单。显然,一个主要的考虑是使用Office将出现许可费带来的成本,但幸运的是,大多数商业环境应该都已经部署了Office环境,减少了显著增加的费用。

Spring和J2EE容器中的Windwos工作流

Windows工作流是微软在“NetFX 3.0”发行版本中的发行的一个新框架,它将随着Windows Vista操作系统被同时安装。工作流的目的是提供一个方法,这个方法使得商业过程功能——或许是一个小规模的应用,比如网站中网页的交互,或许是一个大规模的应用,比如签署一个保险协议的主要过程——可以被非开发人员创建、查看、跟踪和编辑等。工作流的开发人员(或者是传统的.NET开发人员,或者是领域专家)使用一个类似流图表工具的环境设计工作流,这些工作流由一些活动组成,这些活动表示过程当中的一个个逻辑步骤。这个环境将会随Visual Studio一起被普遍提供,但是也可以在一些其他自定义的应用程序中存在,同样也允许公司将工作流的设计者完全剥离出传统的程序员工具之外。工作流设计的结果就是一个格式化的XML文档或代码,然后使用工作流编译器将其编译成一个.NET类,这个类将由工作流运行时处理,运行于各种环境之中,包括ASP.NET,控制台应用程序或者是一个拥有图形用户接口的应用程序等。工作流可以是串行的或是由外界状态改变驱动的,甚至可以跨越很长的时间间隔。

从事实上看,工作流运行时是被设计为易用于各种应用环境和上下文之中,一个最直接的想法就是使用一些连接技术将工作流应用于Spring(或其他J2EE容器)中,比如可能是工作流运行时支撑Spring容器创建自定义的活动,以用于调用Spring中的Bean类执行商业功能,也可能是在Spring的Bean中支撑工作流运行时,来执行对Spring接受的远程调用进行响应的功能。特别是在第二种情况下,终端用户可以设计业务过程并将其执行于传统的企业服务器中。同样,工作流的狂热爱好者已经描述了工作流可以如何被应用,以来结构化ASP.NET应用程序中网页的导航,这样一种方式不同于Structs的action映射文件。在servlet容器中支撑工作流来完成同样的目标是另一种可行的办法,同样也在servlet和JSP网页之间提供了一种可见的“流”,而非目前占据此位置的晦涩的XML语法。

WPF客户端到Java服务

本节将会描述最后一个,但肯定不是最不重要的场景,而且它有可能成为将.NET和Java在一起使用时最富有挑战的场景:在Java强大和可扩展的服务提供的数据模型之上(可能是Spring,EJB,或一些组合),使用新的WPF技术来提供一个丰富而强大的用户界面。WPF所宣称的基于xaml的编程模型,标志着相较于近一个时期以来典型的UI编程模型的重大改变,而且在许多方面都让人很容易地产生复杂的用户界面,这种技术超出了Swing或SWT目前所能够实现的。另外,由于xaml是一种基于文本的格式,因此可以动态生成XAML并将其下载到客户端执行,就像现在的HTML一样。

WPF前台与Java后台之间通过WCF进行对话将可能称为一个典型的场景。WCF是微软的新的通信管道,使所有的分布式通信编程模式成为一个单一的架构。除了支持许多最新的WS-*规范,WCF还通过多种途径提供了用于通信的丰富的可扩展性模型,包括通过REST格式(有时称作普通XML,或POX),甚至可能使用其他的通信媒介,比如UDP。Sun通过其Tango项目使得这个办法更加可行,作为一种特定的设计目标,Tango项目可以与WCF无缝集成。

* * *

不言而喻,以上这份列表是很难列出Java和.NET之间进行可能的互操作的所有场景的。事实上,为了让这篇文章处于一个可控的长度,在这儿我们忽略了下面几种可能性:

  • 采用Eclipse的富客户端平台作为客户端,要么部署一个通过由DCOM向.NET/COM+通信的服务组件,要么部署一个WCF服务。

  • 在一台部署了Excel计算引擎的Windows Server 2003机器中采用Swing客户端和/或Java Web Start创造一种便携式、可下载、零部署客户端应用解决方案。

  • 在一个SWT应用程序中利用DirectX提供本地的3D效果(包括音效)。

  • 使用微软的语音服务器,以提供交互式语音识别(IVR),而“前台”使用一个Swing或J2EE应用。

等等,等等,等等。

听起来好像这一切都是牵强和不合理的煽情,就像在脑海里浮现出那帮拥有大量时间但却没有常识的营销人员所作的事。当Java拥有公式引擎时何必使用Excel?当我们拥有JAX-WS时何必使用WCF?当我们拥有Java3D时何必使用WPF?让我们坦然的面对如下事实:.NET能做的任何事,Java都可以做到,反之亦然。免得我们因为偏爱某项技术被指责。但我们也尤其须要坦白承认的一个事实是:两种平台各有特殊的兴趣领域,并且它们在各自的领域做得都很好。开发人员愿意抛开立场偏见,进行开明的讨论,并发挥各自平台的优势以导致一些更大的利益。或是宽泛地引用卡尔?马克斯的一句名言,“对每一个项目而言,应该根据自己的需要充分发挥其所需平台的能力。”( From each platform, according to its abilities, to each project, according to its needs.)

查看英文原文:Java, .NET, But Why Together?
posted @ 2007-07-27 13:50 路边的石头-java 阅读(181) | 评论 (0) | 编辑 收藏
 
论Java和Ruby语言的变迁风险

我曾撰写的文章《从Java到Ruby:所有管理者需要知道的事情》并非是为程序开发者所写,而是写给技术选用的决策者看的。在理解Ruby难点以及使用Rails框架方面,Ruby拥护者们为刚起步的开发者做出了大量值得称赞的工作,但是对于管理者和执行人员来说,并没有足够的信息帮助他们在众多技术之间做出选择。在这个系列的上一篇文章中,我们探讨了在Java在线商店中建立向导项目的策略。在这篇文章中,我们将要探讨Java与Ruby语言迁移时风险预测方面的问题。

通常来说,“使用Ruby具有风险”是一种普遍的看法,这存在一定的原因。因为使用新的语言天生是有风险的。随着Ruby on Rails逐步进入到主流的开发领域中,这样的风险将会随时间逐渐降低,因为有逐步增长的开发者群、组件(或称作gems和plug-ins)相关的书籍、以及业务合作伙伴与你沟通交流。但同时你也可以听到主流的观点指出“使用Java是安全的”。对于这种的观点,我持有强烈的反对意见。随着语言的膨胀,这样的风险通常也会增长。为了便于理解在目前在这些观点上正发生什么变化,投入点精力去研究Java最初的应用情况是值得的。

新技术采用概况

许多分析家拥有技术应用所需的描述模型。其中最为流行的模型是定义在Ruby的Web开发框架Iowa中,用来描述农产品的应用,稍后在一本由Geoffrey A. Moore写作的名为《跨越鸿沟》(Crossing the Chasm)的书中,被用来描述技术内容。在书中,Moore分析了技术应用周期中存在着的五个截然不同的群体:

  • 技术专家。这个群体倾向于采用新的技术。任何一种有前途的技术都会引起这个群体的注意。
  • 先行采纳者。不管这项技术是否在主流技术中取得成功,这个群体都将会采用新的技术来提升竞争优势。
  • 实用主义者。一旦新的技术进入主流应用,或是有足够陡峭的增长曲线来保证技术将得到广泛采用,那么实用主义者就会积极采用新的技术。
  • 保守派。只有新技术成为必须的时候,他们才会考虑采用新的技术。
  • 怀疑论者。这个群体可能很晚才会采用新的技术,或者也可能永远只使用某一特定技术。

Moore指出,技术应用的关键之处在于团队中是否存在实用主义者。因为实用主义者需要新技术大规模的应用,这个中间群体希望看到其他务实派在团队做出承诺之前就使用新的技术。这是一个类似于《第二十二条军规》书中所描述的现象,因为务实派们都会相互依赖的存在。出于这样的原因,在先行采纳者排列在技术专家之后和务实派之前,你会经常在市场接受度曲线中看到一种下降的趋势。Moore将这种下降称之为鸿沟倾向,并且这种想法应出于围绕任何新技术的风险讨论的中心。

Moore解决方法是,把重点放在跨越鸿沟的过程中。通常来说,你很难通过一个巨大的飞跃跨过鸿沟。你需要有一个目标明确的细分市场。Java技术首先通过Applet程序进入网络客户端,之后转向服务端的计算、移动终端、以及其他类似于移动计算以及企业架构的应用,最终为网络带来强大冲击。

在《超越Java》一书中,我认为存在于程序设计语言之间的鸿沟特别严重。我们大多数人都认识到在Lisp语言上投入精力将大幅提高生产率,但是同时也会导致更难找到合适的程序开发人员、教学资源、类库以及组件等。同时我们还将不得不付出更多的花费来进行一些必要的整合工作。出于这个原因,大众市场只会以大约每十年的时间周期更换主流的编程语言。在服务端编程语言方面,可以清晰看到这种趋势的存在。COBOL和Fortran语言出现于1954年到1961年之间。C语言则诞生在上世纪70年代初期,C++是出现在上世纪80年代中期,Java语言则出现在1996年。我应当把C#语言算做整合高效的Java语言克隆版本,虽然这样的说法可能会引发一些争辩。许多其他的语言在此阶段中诞生,但是上述语言仍旧没有一个能够占据统治地位。伴随的风险是阻碍新编程语言被广泛采用的最重要原因。

Java的风险概况

使用Java语言曾经需要克服很大的风险。当时,大多数服务端的编程都在使用C++语言。C++是一门高效的操作系统语言,非常适用于应用程序开发。C语言家族在这方面的表现相当出色,因为客户机/服务器端编程以及用户界面开发需要程序性能与适应性良好地结合在一起,在当时其他的编程语言都无法符合这样的要求。为了克服伴随采用新编程语言而来的风险,Java需要以下的三个条件均成立:

  • C++开发者不得不经历一番辛苦的学习过程。指针的存在(由于缺少编译时的安全性)导致各种各样难以消除的缺陷。内存管理使得内存泄漏成为家常便饭。C++对于大多数程序开发者来说,显得过于复杂。这些问题增加了针对于C++语言的风险评估。
  • Java需要解决一些C++语言无法处理的工作。Java语言所具有简洁、灵活的特性以及众多C++所不包括的类库支持。这些要素减少了针对于Java语言的风险评估,并可以保持开发团队小型化最终从根本上提高生产力。
  • Java需要一个催化剂。随着网络爆炸,Applet应用普遍被嵌入在NetScape浏览器中,使得C语言开发者不得不转向去开始使用Java语言。C++因为和Java语法的类似,可以简单地进行过渡。Java得以迅速获得数量庞大的用户群,并且在同微软的竞争中逐步提升这样的过渡。

Java的膨胀要比我们以前所见的任何一次技术浪潮都要迅速,同时也可能比我一生所见的任何技术都要庞大,然而Java的发展蓝图却一直保持清晰。为了建立新的语言,原有的语言已不适应开发者的需求,新的语言必须要克服原有语言的缺陷,并最终以某些催化效应迅速聚集起数量庞大的用户群。

Java作为Internet应用语言在客户端迅速得到立足。借助于灵巧的Applet应用程序,由于Java提供了对于应用开发者极有帮助的特性,使得Java快速转移到服务器端开发,这些特性包含有:

  • 内存管理
  • 干净的继承模型
  • 更好的面向对象功能
  • 便携性
  • Internet类库
  • 安全

……以及其他许多特性。在我看来,Java一直以来都是最为成功的编程语言。随着Java不断的改进,使用Java语言变得越来越安全,并最终在Internet应用中统领着服务端开发的市场。商业投资,开发者社区,各种教育培训,开放源代码的框架,以及各种各样的信息发布都使得使用Java开发的风险降低。上述几点清晰地解释了Java取得成功的原因。

一旦新的程序开发语言跨越鸿沟,开发语言相关的风险则会随着市场占有率的提升显著减少。

Java则拥有一个令人赞叹的成功过程。但是程序设计语言没有仍旧停留在不确定的技术发展水平之上。所有成功语言都会产生技术膨胀,因为它们必须去适应使用者不断变化的需求。成功的编程语言无法像其他的语言一样快速的适应变化,他们必须保持一定程度上的向后兼容,来满足逐步增长的用户基本需求。随着技术滞后与语言膨胀的产生,另一种形式的风险预测逐步形成。为了新的风险预测,由于风险与程序开发者高效完成工作的能力相关,使得风险与市场占有率的降低有必然的联系。

到目前为止,我已经开始关注于新生技术的市场风险。在Java诞生十周年之际,另一种形式的风险评估成为必须。就像《人月神话》、《死亡之旅》和《人件》等许多有影响力的书籍中鼓吹的那些风险一样:

  • 低下的生产力将导致更庞大的团队规模和更长的时间周期
  • 风险随着项目的规模而增加
  • 风险随着团队规模的扩张而增加
  • 质量风险,以Bug的数量来衡量,随着代码行数的增加而增长
  • 成本的增长导致风险的增加
  • 综合成本随着复杂性的提高而增加

随着程序设计语言或者编程范例的使用有了积累,相对于技术发展水平,语言将会与生产力相关联。项目团队需要增加规模,开发者需要编写更多的代码来解决相同的问题。所有这些因素本身就会增加风险。所有的因素将会导致必然的结论。

由于市场主宰地位的终止,相对于技术发展水平来说,生产力风险与开发语言相关性将会增加。

在Java语言的范畴中,这些情况是否以及如何发生是一个将会引起激烈争论的话题。当然,Java仍然是解决整个一系列企业问题的最佳语言,比方说非常大型的项目,或是比如双相提交或核心对象关系映射等具备特定需求的问题。针对于Java的商业投资从来没有这么强过,并且Java社区一直是保持持续高涨。但是根基中的缺陷逐渐开始显现出来。

Java的企业级JavaBean框架,WS-*风格的网络服务,以及JavaEE的复杂性和宽松度已受到越来越多的批评。James Duncan Davidson,servlet的创始人之一,曾表示Java不再像从前那样方便易用。目前很难给一个普通的Java开发者,讲明白如何解决最一般的编程问题:比如有后台数据库支撑的网络应用。出现的相关证据是,已经出现了很多使用其他语言的开发框架,最为出名的就是Ruby on Rails,在处理小规模问题时具备极高的生产力。资深Java开发者James Duncan Davidson,Mike Clark,Justin Gehtland,Stuart Halloway以及其他许多开发者都证明,在关键的小型项目中使用了Rails之后,获得了非常高的生产效率:具备后台数据库支撑的绿色网络应用。当然,我的个人经验也是可以轻松地使用Ruby on Rails构造、部署并维护这样的应用。

这些报告将会引起广泛的争论,就像是早期关于Java生产力的那些报告一样。还记得,在Java开发广泛普及之前,Java首次出现在各式的小型应用中。开发人员的生产力是驱动Java早先增长期的重要标准。请谨记Moore关于新技术出现的理论。跨越鸿沟最好的方式不是通过一次大的跳跃,而是每次只前进一个小的阶段。

我坚信复杂性和松散的开发效率是使得Java目前正在经历风险的原因。

Ruby与生俱来的风险

比起其他新生的开发语言来,Ruby并没有什么特别之处。缺少商业投资,有限的开发资源,还缺少开发经验,这都为新生的程序设计语言带来了风险。下面是一些我遭遇到的较大的风险。

  • 人才的缺乏。很难找到熟练的Ruby开发人员。根据Java的发展情况来看,这样的现状将会很快有所改观,但是就目前来说,如果你计划在很短的时间内组织一个人数较多的Ruby开发团队,其困难程度远比组建相同的Java团队要大得多。
  • 缺少经验。许多LAMP相关的语言已经建立了记录跟踪机制。Google使用Python;许多主流的.COM公司使用Perl或C语言。目前仍没有使用Ruby打造的旗舰级应用,来展示Ruby语言强健的可拓展性,或是复杂的企业级集成。我们只是不知道Ruby是否可以解决某些特定类型的问题。
  • 部署和配置策略。Ruby on Rails已经出现将近一年的时间,所以在部署和配置方面的经验还不如竞争语言那样丰富。
  • 缺少类库支持。Ruby远不如Java语言拥有这么多丰富的类库支持。
  • 缺少商业投资。你需要花费很大的力气才能找到Ruby的咨询、培训或承包的机会,并且这些大多数还并不存在。

还有其他许多类似的风险。然而,你可以有效地降低使用Ruby语言的风险,比如采取绩效挂钩的风险预测。虽然开发和部署大型Ruby应用的相关知识积累仍然十分有限,但是你可以在适当的着眼点不断学习新的知识。对于PHP、Perl和Python等LAMP相关语言,业界有着非常丰富的知识积累。在应用部署机制、Web服务器以及非共享可拓展策略等方面都是一致的。

在考虑参与开发的人手时,不要低估你通过对员工进行内部培训来建立高效团队的能力。对于使用Spring、Eclipse、Hibernate和WebWork进行Java开发的新手,我的训练计划常常是为Ruby on Rails开发者指定培训计划的五倍。如果你开始使用具有类似于Ruby特性的开发语言,比方说Perl,Python或Smalltalk,它们可以帮助你很好地起步。如果你打算从零开始培养一个程序员的话,培养一个使用Ruby的开发者,远比培训Java开发者使用最新的一大堆各种框架要合算的多。

想一想那些众多的函数类库,有多少是你真正需要的?如果你需要分布式处理,双相提交,那么就使用Java编程。如果您需要与Microsoft Office的宏完美地整合,那么就使用.NET。但如果你想编写操作系统整合脚本,或编写基于数据库的绿色Web应用,那么Ruby则正是你所需要的。并且你可以经常编写要用到但手边没有的任何程序。我曾协助一家公司工作,他们在两个星期内编写了自己的数据库驱动程序,但仍然比完成项目其他工作所用的时间要多。我还认识一个使用Ruby在四小时内修补现有代码,为程序拓展Oracle支持的开发者。Thoughtworks在很短的开发周期内就发布了RBatis,即Ruby版本的实体关系映射工具iBATIS。

所以当你站在全局考虑时,会感觉到使用Ruby的风险往往被夸大了,尤其是在Java并没有带给你一切所需资源的时候。自己真正去尝试使用Ruby语言,是把这些风险纳入控制范围之内的最好方法。使用Rails开发一些实际的应用,并站在实践的角度上发言。而不要盲目迷信别人的说法。

神话 vs 事实

Rails是银弹。

人们曾经在Rails项目上失败过,并且还将会有更多失败的教训。如果你在没有具备必须技能的情况下使用它,你也将可能面临失败的命运。

与之类似的说明是,如果Java语言不是导致失败的问题根源,那么Ruby将同样不会是你的答案。大多数软件开发问题的出现是与特定技术无关的。如果你正在遭受打击,Ruby on Rails的采用只能加快你遭受打击的速度。

选择Ruby颇具风险,因为你无法预测到错误。

采用任何新的语言,最主要的风险是你将预测到错误,并且错误停滞在使用的类库之中。这的确是一项相当重大的风险,但是这个问题决不仅局限于Ruby语言之中。在Java语言里,你需要就主要类库的使用做出决定,其中任何一个都可能带给你复杂臃肿的代码。你是否会为声明事物选择Spring或EJB 3等技术?Java的持久层架构是不是一个正确的选择,或者Hibernate就是最终的解决方案?关于Web MVC分层的正确选择是什么,是逐步衰落的Struts框架,还是其他更易用的框架?

在Ruby语言之中,选择Web开发框架则相对简单许多。你将很可能与Rails一起工作。语言动态的特性同样各层之间的结构更为简化,通过特定的约定来使得开发配置比Java实现更为明晰。

为Java项目招募人手总是更为容易。

Java拥有数量庞大的开发者群体,但是开发社区之间有着巨大的分歧。如果你想使用一个综合的Java工具集,你的选择是十分有限的。即使你选择了像Spring这样的流行框架,你的团队必须还要学会使用针对给定项目所需的各种类库。在这种情况下,Java的核心力量,过多的函数类库,将会给项目带来副作用。相反,大部分的Ruby开发者都知道Rails框架。此外,你通常需要更多的Java开发者去处理类似的任务。有时,招募Java的开发人员要容易得多。但有时,情况也并不是这样。

Rails无法拓展。

Ruby on Rails其实有很好的延展性。它的缓存模型非常强大,并且非共享的架构在LAMP社区中多次被证明是非常有效的。实际上,我们知道Ruby on Rails完全可以适应较大型应用的要求。我们不知道Ruby on Rails是否可以承受大规模的应用部署。没有固有的架构使我相信这是一条死胡同。对于典型的应用,总之错误的潜伏期是存在于数据库端。

Rails的整合选项十分有限。

Rails对于基于ReST的Web服务有着良好的支持。Ruby同样通过JRuby项目提供对于JVM的支持,以及提供对于微软的CLR运行时的支持。同时Ruby也提供了良好的消息传输支持。最后,为项目选择最好的工具将会帮助你始终处于良好的状态。优秀的开发团队可以在Java和Ruby项目上同时获得成功。

总结:你可以承担什么样的角色?

如果你正在考虑使用Ruby,那么在你身边将会有很多有用的信息。与其他同时在有效使用Java和Ruby的开发者交流。阅读关于开发框架的资料。查找从Java到Ruby的迁移资料。如果你并不想放弃Java,只是想寻找轻量级的开发体验,那么去了解一下那些可以为你带来更多相关体验的项目,比如说RIFE、JMatter或Wicket项目。如果你认为Ruby可能是一个好的选择,那么要留心以下的建议:

  • 为项目选择合适的工具。Ruby on Rails并不是银弹,ROR是一个针对以数据库为后台的高度精简的Web应用开发环境。与新的数据库模式配合较好,或者你可以通过变更来适应Rails的各种固有优点。
  • 细心计划开发团队的热身阶段。你不需要在Monster.com站点投放广告并在三日之内为项目招募齐全开发人员。但你可能需要考虑培训你部分或全部的开发者,并且招募几个顶尖的Rails开发者,或是请求某些项目咨询来帮助你把项目启动。
  • 了解你使用传统方式的结合点。通常,项目中最头疼的部分是定义与外部系统的交互。你最初证明概念的工作需要与某些接触点交互,至少是要明确你在何处对项目感觉到满意。

如果你还是不确定,那么做一个先行者,或是遵从保守派的观点。缓解风险最佳的方法总是优秀的判断能力。

关于作者

Bruce Tate居住在德克萨斯州的奥斯丁,是一位山地自行车和橡皮艇爱好者,同时也是两个孩子的父亲。Bruce已经撰写了9本编程方面的书籍,其中包含两本Ruby的书籍以及五本Java相关的书籍。Bruce还是RapidRed公司的创始人,公司专注于包含Ruby和Rails在内的轻量级开发技术,并提供开发、资讯和培训等业务。Bruce是一位世界范围内广受称赞的优秀演说家、程序员、培训师以及技术顾问。

查看英文原文:From Java to Ruby: Risk

posted @ 2007-07-27 13:44 路边的石头-java 阅读(197) | 评论 (0) | 编辑 收藏
 
仅列出标题
共7页: 上一页 1 2 3 4 5 6 7 下一页 
 
<2025年6月>
日一二三四五六
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

 公告


 导航

  • BlogJava
  • 首页
  • 发新随笔
  • 发新文章
  • 联系
  • 聚合
  • 管理

 统计

  • 随笔: 63
  • 文章: 0
  • 评论: 27
  • 引用: 0

常用链接

  • 我的随笔
  • 我的评论
  • 我的参与
  • 最新评论

留言簿(2)

  • 给我留言
  • 查看公开留言
  • 查看私人留言

随笔分类

  • Java技术(2) (rss)
  • Web技术(1) (rss)
  • 专题技术(3) (rss)
  • 业内新闻(6) (rss)
  • 操作系统(3) (rss)
  • 数据库 (rss)
  • 生活娱乐(2) (rss)

随笔档案

  • 2007年8月 (31)
  • 2007年7月 (32)

搜索

  •  

最新评论

  • 1. re: 一张所有XP用户都感吃惊的图片[未登录]
  • 晕,这有什么好惊奇的,容易的很!
  • --无名
  • 2. re: 一张所有XP用户都感吃惊的图片
  • 看下什么目录
  • --xiaoyu211940
  • 3. re: 一张所有XP用户都感吃惊的图片
  • 000
  • --Super·shen BLOG 李禄燊
  • 4. re: 一张所有XP用户都感吃惊的图片
  • czvzxc
  • --ww
  • 5. re: Java实用经验总结--Swing篇
  • 真的不错

    谢谢啦
  • --windrain

阅读排行榜

  • 1. Java实用经验总结--Swing篇(1249)
  • 2. 使用xfire建立webservice (1053)
  • 3. 解决在linux下awt调用错误的问题(926)
  • 4. 机器猫全部剧场版的下载地址(918)
  • 5.  一张所有XP用户都感吃惊的图片(744)
  • 6. java 分布式RMI的简单使用(733)
  • 7. 做猪做狗都不做程序员(571)
  • 8. 当会话超时时重定向(541)
  • 9. 用开源组件jcaptcha做jsp彩色验证码 (511)
  • 10. 使用Spring框架和AOP实现动态路由(480)
  • 11. 北京图盟科技有限公司(Mapabc.com)基础研发部诚聘英才(415)
  • 12. Java实用经验总结--日期、数字篇(383)
  • 13. Web Service在电信行业数据业务中的应用 (376)
  • 14. 写得蛮好的linux学习笔记 (375)
  • 15. 如何编写代码才能使得效率高 (363)
  • 16. RMI使用手记(361)
  • 17. 在 10 分钟之内创建 Ruby Weblog(356)
  • 18. AJAX+jsp无刷新验证码实例(347)
  • 19. Linux下生成验证图片的问题说明(340)
  • 20. 80年代初出生的十大尴尬 (337)

评论排行榜

  • 1.  一张所有XP用户都感吃惊的图片(10)
  • 2. 做猪做狗都不做程序员(4)
  • 3. 机器猫全部剧场版的下载地址(3)
  • 4. AJAX+jsp无刷新验证码实例(1)
  • 5. 北京图盟科技有限公司(Mapabc.com)基础研发部诚聘英才(1)

Powered by: 博客园
模板提供:沪江博客
Copyright ©2025 路边的石头-java