2009年2月12日
“又是一年毕业时 ”,看到一批批学子离开人生的象牙塔,走上各自的工作岗位;想想自己也曾经意气风发、踌躇满志,不觉感叹万千……本文是自己工作6年的经历沉淀或者经验提炼,希望对所有的软件工程师们有所帮助,早日实现自己的人生目标。本文主要是关于软件开发人员如何提高自己的软件专业技术方面的具体建议,前面几点旨在确定大的方向,算是废话吧。
'\ i'E*o5?5g6]#g(y)n8L1、分享第一条经验:“学历代表过去、能力代表现在、学习力代表未来。”其实这是一个来自国外教育领域的一个研究结果。相信工作过几年、十几年的朋友对这个道理有些体会吧。但我相信这一点也很重要:“重要的道理明白太晚将抱憾终生!”所以放在每一条,让刚刚毕业的朋友们早点看到哈!
TechWeb-技术社区7N9{'t%a+M
2、一定要确定自己的发展方向,并为此目的制定可行的计划。不要说什么,“我刚毕业,还不知道将来可能做什么?”,“跟着感觉走,先做做看”。因为,这样的观点会通过你的潜意识去暗示你的行为无所事事、碌碌无为。一直做技术,将来成为专家级人物?向管理方向走,成为职业经理人?先熟悉行业和领域,将来自立门户?还是先在行业里面混混,过几年转行做点别的?这很重要,它将决定你近几年、十年内“做什么事情才是在做正确的事情!”。
8R(X1}1g R/D$Z*K$y5U
3、软件开发团队中,技术不是万能的,但没有技术是万万不能的!在技术型团队中,技术与人品同等重要,当然长相也比较重要哈,尤其在MM比较多的团队中。在软件项目团队中,技术水平是受人重视和尊重的重要砝码。无论你是做管理、系统分析、设计、编码,还是产品管理、测试、文档、实施、维护,多少你都要有技术基础。算我孤陋寡闻,我还真没有亲眼看到过一个外行带领一个软件开发团队成功地完成过软件开发项目,哪怕就一个,也没有看到。倒是曾经看到过一个“高学历的牛人”(非技术型)带一堆人做完过一个项目,项目交付的第二天,项目组成员扔下一句“再也受不了啦!”四分五裂、各奔东西。那个项目的“成功度”大家可想而知了。
tech.techweb.com.cn&F,q9R;K/u.@
4、详细制定自己软件开发专业知识学习计划,并注意及时修正和调整(软件开发技术变化实在太快)。请牢记:“如果一个软件开发人员在1、2年内都没有更新过自己的知识,那么,其实他已经不再属于这个行业了。”不要告诉自己没有时间。来自时间管理领域的著名的“三八原则”告诫我们:另外的那8小时如何使用将决定你的人生成败!本人自毕业以来,平均每天实际学习时间超过2小时。
+j&G1B p7U/? g
5、书籍是人类进步的阶梯,对软件开发人员尤其如此。书籍是学习知识的最有效途径,不要过多地指望在工作中能遇到“世外高人”,并不厌其烦地教你。对于花钱买书,我个人经验是:千万别买国内那帮人出的书!我买的那些家伙出的书,100%全部后悔了,无一本例外。更气愤的是,这些书在二手市场的地摊上都很难卖掉。“拥有书籍并不表示拥有知识;拥有知识并不表示拥有技能;拥有技能并不表示拥有文化;拥有文化并不表示拥有智慧。”只有将书本变成的自己智慧,才算是真正拥有了它。
,U5p1B&q9s4u程序开发,操作系统,服务器,源码下载,Linux,Unix,BSD,PHP,Apach,asp,下载,源码,黑客,安全,技术社区,技术论坛6、不要仅局限于对某项技术的表面使用上,哪怕你只是偶尔用一、二次。“对任何事物不究就里”是任何行业的工程师所不应该具备的素质。开发Windows应用程序,看看Windows程序的设计、加载、执行原理,分析一下PE文件格式,试试用SDK开发从头开发一个Windows应用程序;用VC++、 Delphi、Java、.Net开发应用程序,花时间去研究一下MFC、VCL、J2EE、.Net它们框架设计或者源码;除了会用J2EE、 JBoss、Spring、Hibernate,
http://www.bt285.cn 等等优秀的开源产品或者框架,抽空看看大师们是如何抽象、分析、设计和实现那些类似问题的通用解决方案的。试着这样做做,你以后的工作将会少遇到一些让你不明就里、一头雾水的问题,因为,很多东西你“知其然且知其所以然”!
$Z.F7B.I2u1X7、在一种语言上编程,但别为其束缚了思想。“代码大全”中说:“深入一门语言编程,不要浮于表面”。深入一门语言开发还远远不足,任何编程语言的存在都有其自身的理由,所以也没有哪门语言是“包治百病”的“灵丹妙药”。编程语言对开发人员解决具体问题的思路和方式的影响与束缚的例子俯拾皆是。我的经验是:用面对对象工具开发某些关键模块时,为什么不可以借鉴C、C51、汇编的模块化封装方式?用传统的桌面开发工具(目前主要有VC++、Delphi)进行系统体统结构设计时,为什么不可以参考来自Java社区的IoC、AOP设计思想,甚至借鉴像Spring、Hibernate、JBoss等等优秀的开源框架?在进行类似于实时通信、数据采集等功能的设计、实现时,为什么不可以引用来自实时系统、嵌入式系统的优秀的体系框架与模式?为什么一切都必须以个人、团队在当然开发语言上的传统或者经验来解决问题???“他山之石、可以攻玉”。
程序开发,操作系统,服务器,源码下载,Linux,Unix,BSD,PHP,Apach,asp,下载,源码,黑客,安全,技术社区,技术论坛:q8~7X.v'p&B/S
8、养成总结与反思的习惯,并有意识地提炼日常工作成果,形成自己的个人源码库、解决某类问题的通用系统体系结构、甚至进化为框架。众所周知,对软件开发人员而言,有、无经验的一个显著区别是:无经验者完成任何任务时都从头开始,而有经验者往往通过重组自己的可复用模块、类库来解决问题(其实这个结论不应该被局限在软件开发领域、可以延伸到很多方面)。这并不是说,所有可复用的东西都必须自己实现,别人成熟的通过测试的成果也可以收集、整理、集成到自己的知识库中。但是,最好还是自己实现,这样没有知识产权、版权等问题,关键是自己实现后能真正掌握这个知识点,拥有这个技能。
9M2s*m+V.W"?*[*W3v)l+O9、理论与实践并重,内外双修。工程师的内涵是:以工程师的眼光观察、分析事物和世界。一个合格的软件工程师,是真正理解了软件产品的本质及软件产品研发的思想精髓的人(个人观点、欢迎探讨)。掌握软件开发语言、应用语言工具解决工作中的具体问题、完成目标任务是软件工程师的主要工作,但从软件工程师这个角度来看,这只是外在的东西,并非重要的、本质的工作。学习、掌握软件产品开发理论知识、软件开发方法论,并在实践中理解、应用软件产品的分析、设计、实现思想来解决具体的软件产品研发问题,才是真正的软件工程师的工作。站在成熟理论与可靠方法论的高度思考、分析、解决问题,并在具体实践中验证和修正这些思想与方式,最终形成自己的理论体系和实用方法论。
6l)d+H,S7~(B+`10、心态有多开放,视野就有多开阔。不要抱着自己的技术和成果,等到它们都已经过时变成垃圾了,才拿出来丢人现眼。请及时发布自己的研究成果:开发的产品、有创意的设计或代码,公布出来让大家交流或者使用,你的成果才有进化和升华的机会。想想自己2000年间开发的那些Windows系统工具,5、6年之后的今天,还是那个样子,今天流行的好多Windows系统工具都比自己的晚,但进化得很好,且有那么多用户在使用。并且,不要保守自己的技术和思想,尽可能地与人交流与分享,或者传授给开发团队的成员。“与人交换苹果之后,每个人还是只有一个苹果;但交换思想之后,每个人都拥有两种思想”,道理大家都懂,但有多少人真正能做到呢?
:n4K*z,@0l#t$?&]"r
11、尽量参加开源项目的开发、或者与朋友共同研制一些自己的产品,千万不要因为没有钱赚而不做。网络早已不再只是“虚拟世界”,网上有很多的开源项目、合作开发项目、外包项目,这都是涉猎工作以外的知识的绝好机会,并且能够结识更广的人缘。不要因为工作是做ERP,就不去学习和了解嵌入式、实时、通信、网络等方面的技术,反过来也是一样。如果当别人拿着合同找你合作,你却这也不会,那也不熟时,你将后悔莫及。
&_8[,U&x'N4{12、书到用时方恨少,不要将自己的知识面仅仅局限于技术方面。诺贝尔经济学奖得主西蒙教授的研究结果表明: “对于一个有一定基础的人来说,他只要真正肯下功夫,在6个月内就可以掌握任何一门学问。”教育心理学界为感谢西蒙教授的研究成果,故命名为西蒙学习法。可见,掌握一门陌生的学问远远没有想象的那么高难、深奥。多方吸取、广泛涉猎。极力夯实自己的影响圈、尽量扩大自己的关注圈。财务、经济、税务、管理等等知识,有空花时间看看,韬光养晦、未雨绸缪。
3z&W3]3c1s3h13、本文的总结与反思:
8w0b/e(y:E4y3n3Q3|"|+{-k
A:不要去做技术上的高手,除非你的目标如此。虽然本文是关于提高软件开发知识的建议,做技术的高手是我一向都不赞同的。你可以提高自己的专业知识,但能胜任工作即止。
4w#v8I5?(f,q-A程序开发,操作系统,服务器,源码下载,Linux,Unix,BSD,PHP,Apach,asp,下载,源码,黑客,安全,技术社区,技术论坛B:提高软件知识和技术只是问题的表面,本质是要提高自己认识问题、分析问题、解决问题的思想高度。软件专业知识的很多方法和原理,可以很容易地延伸、应用到生活的其它方面。
TechWeb-技术社区:H:e7P"C/V,v-g
C:在能胜任工作的基础上,立即去涉猎其它领域的专业知识,丰富自己的知识体系、提高自己的综合素质,尤其是那些目标不在技术方面的朋友。
我们通常在开发web应用过程中,展现层Action的单元测试经常被我们忽视了,主要原因是:
1、Action层的业务逻辑比较简单。大家潜意识认为这一部分的代码不重要。
2、Action层难以模拟http请求传递参数,需要依赖web容器,因此给单元测试编写带来一定的难度。
我写了一个简单的Action单元测试用例,供大家参考。基于struts的mock和webwork的ActionProxyFactory都可以进行Action的单元测试。我个人比较倾向与ActionProxyFactory做单元测试。其实写action单元测试非常简单,大致分为三步就可以完成单元测试:
一、设置ActionContext上下文参数
将表单传递的请求参数添加到map中
二、创建Action动态代理对象
通过public abstract ActionProxy createActionProxy(String namespace, String actionName, Map extraContext) throws Exception 创建action代理对象。
三、junit断言执行结果
assertEquals(testAction.login(),”success”)
详细用例参考:
public class TestActionTest extends BaseCaseTest{
private ActionProxy proxy = null;
private IVoucherService voucherService;
@Before
public void setUp() throws Exception {
IMocksControl control = EasyMock.createControl();
voucherService = control.createMock(IVoucherService.class);
Map<String, Object> params = new HashMap<String, Object>();
params.put(”loginId”,”test”);
params.put(”password”,”111111″);
params.put(”website”,” http://www.bt285.cn ″);
params.put(”name”,”小说″);
params.put(”voucherService”, voucherService);
Map extraContext = new HashMap();
extraContext.put(ActionContext.PARAMETERS,params);
try {
proxy = ActionProxyFactory.getFactory().createActionProxy(”/ http://www.5a520.cn user”, “testAction”, extraContext);
proxy.setExecuteResult(false);
assertEquals(proxy.execute(),”success”);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testLogin() {
TestAction testAction = (TestAction) proxy.getAction();
assertEquals(testAction.login(),”success”);
}
}
注:创建代理action一定要执行proxy.execute()方法,否则参数不能够增加到actionContext上下文中。因为proxy.execute()中会执行 invocation.invoke()核心方法,遍历执行action中所有的拦截器,包括其中的参数拦截器
某天在服务器上的网页打不开了,频繁报以下错误。
2007-3-18 1:08:26 org.apache.tomcat.util.threads.ThreadPool logFull
严重: All threads (150) are currently busy, waiting. Increase maxThreads (150) or check the servlet status
在网上找了些回答,以下是我觉得正确的回答:
1.我想你的部分资源没有释放,积压卡死的
2.连接池问题
3.应该是服务器端响应request的线程的处理时间过长导致的
分析:
当时使用网站的人数只有2个人,不可能答到到了并发线程150的上线。所以应该不是数据库的问题。
通过对出错的提示判断,应该是连接池使用不合理造成的,或者根本没设置连接池。和数据库连接的部分是使用Spring的数据源JDBC连的,如下:
<beans>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- driver for MySQL-->
<property name="driverClassName"><value>org.gjt.mm.mysql.Driver</value></property>
<property name="url"><value>jdbc:mysql:// www.bt285.cn :3306/test?useUnicode=true&characterEncoding=UTF8</value></property>
<property name="username"><value>test</value></property>
<property name="password"><value>test</value></property>
</beans>
问题应该出现在Spring的DriverManagerDataSource上,它负责管理这些连接的。
下边是对DriverManagerDataSource 的解释
DriverManagerDataSource in Spring Framework
javax.sql Interface DataSource
Implementation of SmartDataSource that configures a plain old JDBC Driver via
bean properties, and returns a new Connection every time.
Useful for test or standalone environments outside of a J2EE container, either
as a DataSource bean in a respective ApplicationContext, or in conjunction with
a simple JNDI environment. Pool-assuming Connection.close() calls will simply
close the connection, so any DataSource-aware persistence code should work.
In a J2EE container, it is recommended to use a JNDI DataSource provided by the
container. Such a DataSource can be exported as a DataSource bean in an
ApplicationContext via JndiObjectFactoryBean, for seamless switching to and from
a local DataSource bean like this class.
If you need a "real" connection pool outside of a J2EE container, consider
Apache's Jakarta Commons DBCP. Its BasicDataSource is a full connection pool
bean, supporting the same basic properties as this class plus specific settings.
It can be used as a replacement for an instance of this class just by changing
the class name of the bean definition to
"org.apache.commons.dbcp.BasicDataSource".
-----------------------------------------------
Many Jakarta projects support interaction with a relational database. Creating a
new connection for each user can be time consuming (often requiring multiple
seconds of clock time), in order to perform a database transaction that might
take milliseconds. Opening a connection per user can be unfeasible in a
publicly-hosted Internet application where the number of simultaneous users can
be very large. Accordingly, developers often wish to share a "pool" of open
connections between all of the application's current users. The number of users
actually performing a request at any given time is usually a very small
percentage of the total number of active users, and during request processing is
the only time that a database connection is required. The application itself
logs into the DBMS, and handles any user account issues internally.
There are several Database Connection Pools already available, both within
Jakarta products and elsewhere. This Commons package provides an opportunity to
coordinate the efforts required to create and maintain an efficient,
feature-rich package under the ASF license.
The commons-dbcp package relies on code in the commons-pool package to provide
the underlying object pool mechanisms that it utilizes.
Applications can use the commons-dbcp component directly or through the existing
interface of their container / supporting framework. For example the Tomcat
servlet container presents a DBCP DataSource as a JNDI Datasource. James (Java
Apache Mail Enterprise Server) has integrated DBCP into the Avalon framework. A
Avalon-style datasource is created by wrapping the DBCP implementation. The
pooling logic of DBCP and the configuration found in Avalon's excalibur code is
what was needed to create an integrated reliable DataSource.
看完了解释,事实上是因为DriverManagerDataSource建立连接是只要有连接就新建一个connection,根本没有连接池的作用。改为以下开源的连接池会好点。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<property name="url">
<value>jdbc:hsqldb:hsql:// www.5a520.cn :9001</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value></value>
</property>
</bean>
测试通过,问题消除,如果没有搜索引擎找答案不会这么快解决问题。
Nexus介绍
Nexus 是Maven仓库管理器,如果你使用Maven,你可以从Maven中央仓库 下载所需要的构件(artifact),但这通常不是一个好的做法,你应该在本地架设一个Maven仓库服务器,在代理远程仓库的同时维护本地仓库,以节省带宽和时间,Nexus就可以满足这样的需要。此外,他还提供了强大的仓库管理功能,构件搜索功能,它基于REST,友好的UI是一个extjs的REST客户端,它占用较少的内存,基于简单文件系统而非数据库。这些优点使其日趋成为最流行的Maven仓库管理器。
下载和安装
你可以从http://nexus.sonatype.org/downloads/ 或是http://www.5a520.cn 下载最新版本的Nexus,笔者使用的是1.3.0版本。
Nexus提供了两种安装方式,一种是内嵌Jetty的bundle,只要你有JRE就能直接运行。第二种方式是WAR,你只须简单的将其发布到web容器中即可使用。
Bundle方式安装
解压nexus-webapp-1.3.0-bundle.zip 至任意目录,如D:\dev_tools ,然后打开CMD,cd至目录D:\dev_tools\nexus-webapp-1.3.0\bin\jsw\windows-x86-32 ,运行Nexus.bat 。你会看到Nexus的启动日志,当你看到“Started SelectChannelConnector@0.0.0.0:8081”之后,说明Nexus启动成功了,然后打开浏览器,访问http://127.0.0.1:8081/nexus,你会看到如下的页面:
要停止Nexus,Ctrl+C即可,此外InstallNexus.bat可以用来将Nexus安装成一个windows服务,其余的脚本则对应了启动,停止,暂停,恢复,卸载Nexus服务。
WAR方式安装
你需要有一个能运行的web容器,这里以Tomcat为例,加入Tomcat的安装目录位于D:\dev_tools\apache-tomcat-6.0.18 ,首先我们将下载的nexus-webapp-1.3.0.war 重命名为nexus.war ,然后复制到D:\dev_tools\apache-tomcat-6.0.18\webapps\nexus.war ,然后启动CMD,cd到D:\dev_tools\apache-tomcat-6.0.18\bin\ 目录,运行startup.bat 。一切OK,现在可以打开浏览器访问http://www.bt285.cn :8080/nexus,你会得到和上图一样的界面。
代理外部Maven仓库
登陆
要管理Nexus,你首先需要以管理员身份登陆,点击界面右上角的login,输入默认的登录名和密码:admin/admin123,登陆成功后,你会看到左边的导航栏增加了很多内容:
这里,可以管理仓库,配置Nexus系统,管理任务,管理用户,角色,权限,查看系统的RSS源,管理及查看系统日志,等等。你会看到Nexus的功能十分丰富和强大,本文,笔者只介绍一些最基本的管理和操作。
代理Maven中央仓库
点击左边导航栏的Repositories,界面的主面板会显示所有一个所有仓库及仓库组的列表,你会看到它们的Type字段的值有group,hosted,proxy,virtual。这里我们不关心virtual,只介绍下另外三种类型:
- hosted,本地仓库,通常我们会部署自己的构件到这一类型的仓库。
- proxy,代理仓库,它们被用来代理远程的公共仓库,如maven中央仓库。
- group,仓库组,用来合并多个hosted/proxy仓库,通常我们配置maven依赖仓库组。
由此我们知道,我们需要配置一个Maven中央仓库的proxy,其实Nexus已经内置了Maven Central,但我们需要做一些配置。点击仓库列表中的Maven Central,你会注意到它的Policy是release,这说明它不会代理远程仓库的snapshot构件,这是有原因的,远程仓库的snapshot版本构件不稳定且不受你控制,使用这样的构件含有潜在的风险。然后我们发现主面板下方有三个Tab,分别为Browse,Configuration和Mirrors,我们点击Configuration进行配置,你现在需要关心的是两个配置项:“Remote Storage Location”为远程仓库的地址,对于Maven Central来说是http://repo1.maven.org/maven2/;“Download Remote Indexes”顾名思义是指是否下载远程索引文件,Maven Central的该字段默认为False,这是为了防止大量Nexus无意识的去消耗中央仓库的带宽(中央仓库有大量的构件,其索引文件也很大)。这里我们需要将其设置为True,然后点击Save。在Nexus下载的中央仓库索引文件之后,我们就可以在本地搜索中央仓库的所有构件。下图展示了我们刚才所涉及的配置:
添加一个代理仓库
这里我们再举一个例子,我们想要代理Sonatype的公共仓库,其地址为:http://repository.sonatype.org/content/groups/public/。步骤如下,在Repositories面板的上方,点击Add,然后选择Proxy Repository,在下方的配置部分,我们填写如下的信息:Repository ID - sonatype;Repository Name - Sonatype Repository;Remote Storage Location - http://repository.sonatype.org/content/groups/public/。其余的保持默认值,需要注意的是Repository Policy,我们不想代理snapshot构件,原因前面已经描述。然后点击Save。配置页面如下:
管理本地Maven仓库
Nexus预定义了3个本地仓库,分别为Releases,Snapshots,和3rd Party。这三个仓库都有各自明确的目的。Releases用于部署我们自己的release构件,Snapshots用于部署我们自己的snapshot构件,而3rd Party用于部署第三方构件,有些构件如Oracle的JDBC驱动,我们不能从公共仓库下载到,我们就需要将其部署到自己的仓库中。
当然你也可以创建自己的本地仓库,步骤和创建代理仓库类似,点击Repository面板上方的Add按钮,然后选择Hosted Repository,然后在下方的配置面板中输入id和name,注意这里我们不再需要填写远程仓库地址,Repository Type则为不可修改的hosted,而关于Repository Policy,你可以根据自己的需要选择Release或者Snapshot,如图:
管理Maven仓库组
Nexus中仓库组的概念是Maven没有的,在Maven看来,不管你是hosted也好,proxy也好,或者group也好,对我都是一样的,我只管根据groupId,artifactId,version等信息向你要构件。为了方便Maven的配置,Nexus能够将多个仓库,hosted或者proxy合并成一个group,这样,Maven只需要依赖于一个group,便能使用所有该group包含的仓库的内容。
Nexus预定义了“Public Repositories”和“Public Snapshot Repositories”两个仓库组,前者默认合并所有预定义的Release仓库,后者默认合并所有预定义的Snapshot仓库。我们在本文前面的部分创建了一个名为“Sonatype Repository”的仓库,现在将其合并到“Public Repositories”中。
点击仓库列表中的“Public Repositories”,然后选择下方的"Configuration" Tab,在配置面板中,将右边“Avaiable Repositories”中的“Sonatype Repository”拖拽到左边的“Ordered Group Repository”中,如图:
创建仓库组和创建proxy及hosted仓库类似,这里不再赘述。需要注意的是format字段需要填写“maven2”,添加你感兴趣的仓库即可。
搜索构件
在浩大的Maven仓库中一下下点击链接,浏览路径以寻找感兴趣的构件是一件很郁闷的事情。Nexus基于nexus-indexer提供构件搜索功能,要想对仓库进行搜索,无论是hosted,proxy,或者group,你都必须确认索引文件存在。这一点对于代理仓库尤其重要,有些远程仓库可能根本就没有索引,所以你无法搜索这些代理仓库。有些远程仓库的远程索引非常大,如中央仓库达到了70M左右,那么第一次下载索引需要花很多时间,所以要期望得到搜索结果,确保看到如下的文件:
一旦你的Nexus拥有了本地或者远程仓库的索引文件,你就可以享受Nexus的构件搜索功能了。不论登陆与否,你都可以使用关键字进行模糊搜索,比如我在左边导航栏上部的搜索框内输入junit,然后点击搜索按钮,右边立刻会分页显示500多条的junit相关构件信息。如果你了解更多的信息,你也可以通过限定groupId,artifactId,version进行搜索,点击导航栏中的“Advanced Search”,点击右边所有页面左上角的下拉框,选择“GAV Search”。笔者这里输入junit:junit:4.4,然后回车:
选择一项搜索结果,在页面下方会显示“Artifact Information”的面板,你可以点击"artifact"或者"pom"下载对应文件,而该面板右边更显示了一个Maven依赖配置,你可以直接复制该配置到Maven POM中,这是个十分方便的特性。
此外,值得一提的是,Nexus还支持基于classname的搜索,你只需点击搜索页面右上角的下拉框,选择“Classname Search”,然后输入类名即可,这里我不再赘述。
配置Maven使用Nexus
默认情况下,Maven依赖于中央仓库,这是为了能让Maven开箱即用,但仅仅这么做明显是错误的,这会造成大量的时间及带宽的浪费。既然文章的前面已经介绍了如何安装和配置Nexus,现在我们就要配置Maven来使用本地的Nexus,以节省时间和带宽资源。
我们可以将Repository配置到POM中,但一般来说这不是很好的做法,原因很简单,你需要为所有的Maven项目重复该配置。因此,这里我将Repository的配置放到$user_home/.m2/settings.xml中:
- <settings>
- ...
- <profiles>
- <profile>
- <id>dev</id>
- <repositories>
- <repository>
- <id>local-nexus</id>
- <url>http://127.0.0.1:8080/nexus/content/groups/public/</url>
- <releases>
- <enabled>true</enabled>
- </releases>
- <snapshots>
- <enabled>true</enabled>
- </snapshots>
- </repository>
- </repositories>
- </profile>
- </profiles>
- <activeProfiles>
- <activeProfile>dev</activeProfile>
- </activeProfiles>
- ...
- </settings>
<settings>
...
<profiles>
<profile>
<id>dev</id>
<repositories>
<repository>
<id>local-nexus</id>
<url>http://127.0.0.1:8080/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>dev</activeProfile>
</activeProfiles>
...
</settings>
由于我们不能直接在settings.xml中插入<repositories>元素,这里我们编写了一个profile,并添加了一个profile并使用<activeProfile>元素自动将这个profile激活。这里的local-nexus仓库指向了刚才我们配置的Nexus中“Public Repositories”仓库组,也就是说,所有该仓库组包含的仓库都能供我们使用。此外,我们通过<releases>和<snapshots>元素激活了Maven对于仓库所有类型构件下载的支持,当然你也可以调节该配置,比如说禁止Maven从Nexus下载snapshot构件。
使用该配置,Maven就会从你的Nexus服务器下载构件了,速度和从Central下载可不是一个数量级的。
部署构件至Nexus
Nexus提供了两种方式来部署构件,你可以从UI直接上传,也可以配置Maven部署构件。
通过Nexus UI部署
有时候有个jar文件你无法从公共Maven仓库找到,但是你能从其它得到这个jar文件(甚至是POM),那么你完全可以将这个文件部署到Nexus中,使其成为标准流程的一部分。步骤如下:
点击左边导航栏的"Repository",在右边的仓库列表中选择一个仓库,如“3rd Party”,然后会看到页面下方有四个tab,选择最后一个“Upload”,你会看到构件上传界面。选择你要上传的构件,并指定POM,(或者手工编写GAV等信息),最后点击Upload,该构件就直接被部署到了Nexus的"3rd Party"仓库中。如图:
通过Maven部署
更常见的用例是:团队在开发一个项目的各个模块,为了让自己开发的模块能够快速让其他人使用,你会想要将snapshot版本的构件部署到Maven仓库中,其他人只需要在POM添加一个对于你开发模块的依赖,就能随时拿到最新的snapshot。
以下的pom.xml配置和settings.xml能让你通过Maven自动化部署构件:
pom.xml
- <project>
- ...
- <distributionManagement>
- <repository>
- <id>nexus-releases</id>
- <name>Nexus Release Repository</name>
- <url>http://127.0.0.1:8080/nexus/content/repositories/releases/</url>
- </repository>
- <snapshotRepository>
- <id>nexus-snapshots</id>
- <name>Nexus Snapshot Repository</name>
- <url>http://127.0.0.1:8080/nexus/content/repositories/snapshots/</url>
- </snapshotRepository>
- </distributionManagement>
- ...
- </project>
<project>
...
<distributionManagement>
<repository>
<id>nexus-releases</id>
<name>Nexus Release Repository</name>
<url>http://127.0.0.1:8080/nexus/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<name>Nexus Snapshot Repository</name>
<url>http://127.0.0.1:8080/nexus/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
...
</project>
settings.xml
- <settings>
- ...
- <servers>
- <server>
- <id>nexus-releases</id>
- <username>admin</username>
- <password>admin123</password>
- </server>
- <server>
- <id>nexus-snapshots</id>
- <username>admin</username>
- <password>admin123</password>
- </server>
- </servers>
- ...
- </settings>
<settings>
...
<servers>
<server>
<id>nexus-releases</id>
<username>admin</username>
<password>admin123</password>
</server>
<server>
<id>nexus-snapshots</id>
<username>admin</username>
<password>admin123</password>
</server>
</servers>
...
</settings>
这里我们配置所有的snapshot版本构件部署到Nexus的Snapshots仓库中, 所有的release构件部署到Nexus的Releases仓库中。由于部署需要登陆,因为我们在settings.xml中配置对应Repository id的用户名和密码。
然后,在项目目录中执行mvn deploy ,你会看到maven将项目构件部署到Nexus中,浏览Nexus对应的仓库,就可以看到刚才部署的构件。当其他人构建其项目时,Maven就会从Nexus寻找依赖并下载。
总结
本文介绍强大的仓库管理器——Nexus,包括如何下载安装Nexus,配置Nexus代理中央仓库,管理Nexus的代理仓库,本地仓库,以及仓库组。并帮助你了解如何通过Nexus搜索构件。最后,如何在Maven中配置Nexus仓库,以及如何部署构件到Nexus仓库中。这些都是Nexus中最基本也是最常用的功能。随着使用的深入,你会发现Nexus还有很多其它的特性,如用户管理,角色权限管理等等。
Nexus的OSS版本是完全开源的,如果你有兴趣,你可以学习其源码,甚至自己实现一个REST客户端。
马上拥抱Nexus吧,它是免费的!
对于一个能够访问源代码的经验丰富的Java开发人员来说,任何程序都可以被看作是博物馆里透明的模型。类似线程转储(dump)、方法调用跟踪、断点、切面(profiling)统计表等工具可以让我们了解程序目前正在执行什么操作、刚才做了什么操作、未来将做什么操作。但是在产品环境中情况就没有那么明显了,这些工具一般是不能够使用的,或最多只能由受过训练的开发者使用。支持团队和最终用户也需要知道在某个时刻应用程序正在执行什么操作。
为了填补这个空缺,我们已经发明了一些简单的替代品,例如日志文件(典型情况下用于服务器处理)和状态条(用于GUI应用程序)。但是,由于这些工具只能捕捉和报告可用信息的一个很小的子集,并且通常必须把这些信息用容易理解的方式表现出来,所以程序员趋向于把它们明确地编写到应用程序中。而这些代码会缠绕着应用程序的业务逻辑,当开发者试图调试或了解核心功能的时候,他们必须"围绕这些代码工作",而且还要记得功能发生改变后更新这些代码。我们希望实现的真正功能是把状态报告集中在某个位置,把单个状态消息作为元数据(metadata)来管理。
在本文中我将考虑使用嵌入GUI应用程序中的状态条组件的情形。我将介绍多种实现这种状态报告的不同方法,从传统的硬编码习惯开始。随后我会介绍Java 1.5的大量新特性,包括注解(annotation)和运行时字节码重构(instrumentation)。
状态管理器(StatusManager)
我的主要目标是建立一个可以嵌入GUI应用程序的JStatusBar Swing组件。图1显示了一个简单的Jframe中状态条的样式。
图1.我们动态生成的状态条
|
由于我不希望直接在业务逻辑中引用任何GUI组件,我将建立一个StatusManager(状态管理器)来充当状态更新的入口点。实际的通知会被委托给StatusState对象,因此以后可以扩展它以支持多个并发的线程。图2显示了这种安排。
图2. StatusManager和JstatusBar
|
现在我必须编写代码调用StatusManager的方法来报告应用程序的进程。典型情况下,这些方法调用都分散地贯穿于try-finally代码块中,通常每个方法一个调用。
/*http://www.bt285.cn */
public void connectToDB (String url) {
StatusManager.push("Connecting to database");
try {
...
} finally {
StatusManager.pop();
}
}
|
这些代码实现了我们所需要功能,但是在代码库中数十次、甚至于数百次地复制这些代码之后,它看起来就有些混乱了。此外,如果我们希望用一些其它的方式访问这些消息该怎么办呢?在本文的后面部分中,我将定义一个用户友好的异常处理程序,它共享了相同的消息。问题是我把状态消息隐藏在方法的实现之中了,而没有把消息放在消息所属的接口中。
面向属性编程
我真正想实现的操作是把对StatusManager的引用都放到代码外面的某个地方,并简单地用我们的消息标记这个方法。接着我可以使用代码生成(code-generation)或运行时反省(introspection)来执行真正的工作。XDoclet项目把这种方法归纳为面向属性编程(Attribute-Oriented Programming),它还提供了一个框架组件,可以把自定义的类似Javadoc的标记转换到源代码之中。
但是,JSR-175包含了这样的内容,Java 1.5为了包含真实代码中的这些属性提供了一种结构化程度更高的格式。这些属性被称为"注解(annotations)",我们可以使用它们为类、方法、字段或变量定义提供元数据。它们必须被显式声明,并提供一组可以包含任意常量值(包括原语、字符串、枚举和类)的名称-值对(name-value pair)。
注解(Annotations)
为了处理状态消息,我希望定义一个包含字符串值的新注解。注解的定义非常类似接口的定义,但是它用@interface关键字代替了interface,并且只支持方法(尽管它们的功能更像字段):
public @interface Status {
String value();
} |
与接口类似,我把@interface放入一个叫做Status.java的文件中,并把它导入到任何需要引用它的文件中。
对我们的字段来说,value可能是个奇怪的名称。类似message的名称可能更适合;但是,value对于Java来说具有特殊的意义。它允许我们使用@Status("...")代替@Status(value="...")来定义注解,这明显更加简捷。
我现在可以使用下面的代码定义自己的方法:
@Status("Connecting to database")
public void connectToDB (String url) {
...
} |
请注意,我们在编译这段代码的时候必须使用-source 1.5选项。如果你使用Ant而不是直接使用javac命令行建立应用程序,那么你需要使用Ant 1.6.1以上版本。
作为类、方法、字段和变量的补充,注解也可以用于为其它的注解提供元数据。特别地,Java引入了少量注解,你可以使用这些注解来定制你自己的注解的工作方式。我们用下面的代码重新定义自己的注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Status {
String value();
} |
@Target注解定义了@Status注解可以引用什么内容。理想情况下,我希望标记大块的代码,但是它的选项只有方法、字段、类、本地变量、参数和其它注解。我只对代码感兴趣,因此我选择了METHOD(方法)。
@Retention注解允许我们指定Java什么时候可以自主地抛弃消息。它可能是SOURCE(在编译时抛弃)、CLASS(在类载入时抛弃)或RUNTIME(不抛弃)。我们先选择SOURCE,但是在本文后部我们会更新它。
重构源代码
现在我的消息都被编码放入元数据中了,我必须编写一些代码来通知状态监听程序。假设在某个时候,我继续把connectToDB方法保存源代码控件中,但是却没有对StatusManager的任何引用。但是,在编译这个类之前,我希望加入一些必要的调用。也就是说,我希望自动地插入try-finally语句和push/pop调用。
XDoclet框架组件是一种Java源代码生成引擎,它使用了类似上述的注解,但是把它们存储在Java源代码的注释(comment)中。XDoclet生成整个Java类、配置文件或其它建立的部分的时候非常完美,但是它不支持对已有Java类的修改,而这限制了重构的有效性。作为代替,我可以使用分析工具(例如JavaCC或ANTLR,它提供了分析Java源代码的语法基础),但是这需要花费大量精力。
看起来没有什么可以用于Java代码的源代码重构的很好的工具。这类工具可能有市场,但是你在本文的后面部分可以看到,字节码重构可能是一种更强大的技术。 重构字节码
不是重构源代码然后编译它,而是编译原始的源代码,然后重构它所产生的字节码。这样的操作可能比源代码重构更容易,也可能更加复杂,而这依赖于需要的准确转换。字节码重构的主要优点是代码可以在运行时被修改,不需要使用编译器。
尽管Java的字节码格式相对简单,我还是希望使用一个Java类库来执行字节码的分析和生成(这可以把我们与未来Java类文件格式的改变隔离开来)。我选择了使用Jakarta的Byte Code Engineering Library(字节码引擎类库,BCEL),但是我还可以选用CGLIB、ASM或SERP。
由于我将使用多种不同的方式重构字节码,我将从声明重构的通用接口开始。它类似于执行基于注解重构的简单框架组件。这个框架组件基于注解,将支持类和方法的转换,因此该接口有类似下面的定义:
public interface Instrumentor
{
public void instrumentClass (ClassGen classGen,Annotation a);
public void instrumentMethod (ClassGen classGen,MethodGen methodGen,Annotation a);
} |
ClassGen和MethodGen都是BCEL类,它们使用了Builder模式(pattern)。也就是说,它们为改变其它不可变的(immutable)对象、以及可变的和不可变的表现(representation)之间的转换提供了方法。
现在我需要为接口编写实现,它必须用恰当的StatusManager调用更换@Status注解。前面提到,我希望把这些调用包含在try-finally代码块中。请注意,要达到这个目标,我们所使用的注解必须用@Retention(RetentionPolicy.CLASS)进行标记,它指示Java编译器在编译过程中不要抛弃注解。由于在前面我把@Status声明为@Retention(RetentionPolicy.SOURCE)的,我必须更新它。
在这种情况下,重构字节码明显比重构源代码更复杂。其原因在于try-finally是一种仅仅存在于源代码中的概念。Java编译器把try-finally代码块转换为一系列的try-catch代码块,并在每一个返回之前插入对finally代码块的调用。因此,为了把try-finally代码块添加到已有的字节码中,我也必须执行类似的事务。
下面是表现一个普通方法调用的字节码,它被StatusManager更新环绕着:
0: ldc #2; //字符串消息
2: invokestatic #3; //方法StatusManager.push:(LString;)V
5: invokestatic #4; //方法 doSomething:()V
8: invokestatic #5; //方法 StatusManager.pop:()V
11: return |
下面是相同的方法调用,但是位于try-finally代码块中,因此,如果它产生了异常会调用StatusManager.pop():
0: ldc #2; //字符串消息
2: invokestatic #3; //方法 StatusManager.push:(LString;)V
5: invokestatic #4; //方法 doSomething:()V
8: invokestatic #5; //方法 StatusManager.pop:()V
11: goto 20
14: astore_0
15: invokestatic #5; //方法 StatusManager.pop:()V
18: aload_0
19: athrow
20: return
Exception table:
from to target type
5 8 14 any
14 15 14 any |
你可以发现,为了实现一个try-finally,我必须复制一些指令,并添加了几个跳转和异常表记录。幸运的是,BCEL的InstructionList类使这种工作相当简单。
在运行时重构字节码
现在我拥有了一个基于注解修改类的接口和该接口的具体实现了,下一步是编写调用它的实际框架组件。实际上我将编写少量的框架组件,先从运行时重构所有类的框架组件开始。由于这种操作会在build过程中发生,我决定为它定义一个Ant事务。build.xml文件中的重构目标的声明应该如下:
<instrument class="com.pkg.OurInstrumentor">
<fileset dir="$(classes.dir)">
<include name="**/*.class"/>
</fileset>
</instrument> |
为了实现这种事务,我必须定义一个实现org.apache.tools.ant.Task接口的类。我们的事务的属性和子元素(sub-elements)都是通过set和add方法调用传递进来的。我们调用执行(execute)方法来实现事务所要执行的工作--在示例中,就是重构<fileset>中指定的类文件。
public class InstrumentTask extends Task {
...
public void setClass (String className) { ... }
public void addFileSet (FileSet fileSet) { ... }
public void execute () throws BuildException {
Instrumentor inst = getInstrumentor();
try {
DirectoryScanner ds =fileSet.getDirectoryScanner(project);
// Java 1.5 的"for" 语法
for (String file : ds.getIncludedFiles()) {
instrumentFile(inst, file);
}
} catch (Exception ex) {
throw new BuildException(ex);
}
}
...
} |
用于该项操作的BCEL 5.1版本有一个问题--它不支持分析注解。我可以载入正在重构的类并使用反射(reflection)查看注解。但是,如果这样,我就不得不使用RetentionPolicy.RUNTIME来代替RetentionPolicy.CLASS。我还必须在这些类中执行一些静态的初始化,而这些操作可能载入本地类库或引入其它的依赖关系。幸运的是,BCEL提供了一种插件(plugin)机制,它允许客户端分析字节码属性。我编写了自己的AttributeReader的实现(implementation),在出现注解的时候,它知道如何分析插入字节码中的RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性。BCEL未来的版本应该会包含这种功能而不是作为插件提供。
编译时刻的字节码重构方法显示在示例代码的code/02_compiletime目录中。
但是这种方法有很多缺陷。首先,我必须给建立过程增加额外的步骤。我不能基于命令行设置或其它编译时没有提供的信息来决定打开或关闭重构操作。如果重构的或没有重构的代码需要同时在产品环境中运行,那么就必须建立两个单独的.jars文件,而且还必须决定使用哪一个。
在类载入时重构字节码
更好的方法可能是延迟字节码重构操作,直到字节码被载入的时候才进行重构。使用这种方法的时候,重构的字节码不用保存起来。我们的应用程序启动时刻的性能可能会受到影响,但是你却可以基于自己的系统属性或运行时配置数据来控制进行什么操作。
Java 1.5之前,我们使用定制的类载入程序可能实现这种类文件维护操作。但是Java 1.5中新增加的java.lang.instrument程序包提供了少数附加的工具。特别地,它定义了ClassFileTransformer的概念,在标准的载入过程中我们可以使用它来重构一个类。
为了在适当的时候(在载入任何类之前)注册ClassFileTransformer,我需要定义一个premain方法。Java在载入主类(main class)之前将调用这个方法,并且它传递进来对Instrumentation对象的引用。我还必须给命令行增加-javaagent参数选项,告诉Java我们的premain方法的信息。这个参数选项把我们的agent class(代理类,它包含了premain方法)的全名和任意字符串作为参数。在例子中我们把Instrumentor类的全名作为参数(它必须在同一行之中):
-javaagent:boxpeeking.instrument.InstrumentorAdaptor=
boxpeeking.status.instrument.StatusInstrumentor |
现在我已经安排了一个回调(callback),它在载入任何含有注解的类之前都会发生,并且我拥有Instrumentation对象的引用,可以注册我们的ClassFileTransformer了:
public static void premain (String className,
Instrumentation i)
throws ClassNotFoundException,
InstantiationException,
IllegalAccessException
{
Class instClass = Class.forName(className);
Instrumentor inst = (Instrumentor)instClass.newInstance();
i.addTransformer(new InstrumentorAdaptor(inst));
} |
我们在此处注册的适配器将充当上面给出的Instrumentor接口和Java的ClassFileTransformer接口之间的桥梁。
public class InstrumentorAdaptor
implements ClassFileTransformer
{
public byte[] transform (ClassLoader cl,String className,Class classBeingRedefined,
ProtectionDomain protectionDomain,byte[] classfileBuffer)
{
try {
ClassParser cp =new ClassParser(new ByteArrayInputStream(classfileBuffer),className + ".java");
JavaClass jc = cp.parse();
ClassGen cg = new ClassGen(jc);
for (Annotation an : getAnnotations(jc.getAttributes())) {
instrumentor.instrumentClass(cg, an);
}
for (org.apache.bcel.classfile.Method m : cg.getMethods()) {
for (Annotation an : getAnnotations(m.getAttributes())) {
ConstantPoolGen cpg =cg.getConstantPool();
MethodGen mg =new MethodGen(m, className, cpg);
instrumentor.instrumentMethod(cg, mg, an);
mg.setMaxStack();
mg.setMaxLocals();
cg.replaceMethod(m, mg.getMethod());
}
}
JavaClass jcNew = cg.getJavaClass();
return jcNew.getBytes();
} catch (Exception ex) {
throw new RuntimeException("instrumenting " + className, ex);
}
}
...
} |
这种在启动时重构字节码的方法位于在示例的/code/03_startup目录中。
异常的处理
文章前面提到,我希望编写附加的代码使用不同目的的@Status注解。我们来考虑一下一些额外的需求:我们的应用程序必须捕捉所有的未处理异常并把它们显示给用户。但是,我们不是提供Java堆栈跟踪,而是显示拥有@Status注解的方法,而且还不应该显示任何代码(类或方法的名称或行号等等)。
例如,考虑下面的堆栈跟踪信息:
java.lang.RuntimeException: Could not load data for symbol IBM
at boxpeeking.code.YourCode.loadData(Unknown Source)
at boxpeeking.code.YourCode.go(Unknown Source)
at boxpeeking.yourcode.ui.Main+2.run(Unknown Source)
at java.lang.Thread.run(Thread.java:566)
Caused by: java.lang.RuntimeException: Timed out
at boxpeeking.code.YourCode.connectToDB(Unknown Source)
... 更多信息 |
这将导致图1中所示的GUI弹出框,上面的例子假设你的YourCode.loadData()、YourCode.go()和YourCode.connectToDB()都含有@Status注解。请注意,异常的次序是相反的,因此用户最先得到的是最详细的信息。
图3.显示在错误对话框中的堆栈跟踪信息
为了实现这些功能,我必须对已有的代码进行稍微的修改。首先,为了确保在运行时@Status注解是可以看到的,我就必须再次更新@Retention,把它设置为@Retention(RetentionPolicy.RUNTIME)。请记住,@Retention控制着JVM什么时候抛弃注解信息。这样的设置意味着注解不仅可以被编译器插入字节码中,还能够使用新的Method.getAnnotation(Class)方法通过反射来进行访问。
现在我需要安排接收代码中没有明确处理的任何异常的通知了。在Java 1.4中,处理任何特定线程上未处理异常的最好方法是使用ThreadGroup子类并给该类型的ThreadGroup添加自己的新线程。但是Java 1.5提供了额外的功能。我可以定义UncaughtExceptionHandler接口的一个实例,并为任何特定的线程(或所有线程)注册它。
请注意,在例子中为特定异常注册可能更好,但是在Java 1.5.0beta1(#4986764)中有一个bug,它使这样操作无法进行。但是为所有线程设置一个处理程序是可以工作的,因此我就这样操作了。
现在我们拥有了一种截取未处理异常的方法了,并且这些异常必须被报告给用户。在GUI应用程序中,典型情况下这样的操作是通过弹出一个包含整个堆栈跟踪信息或简单消息的模式对话框来实现的。在例子中,我希望在产生异常的时候显示一个消息,但是我希望提供堆栈的@Status描述而不是类和方法的名称。为了实现这个目的,我简单地在Thread的StackTraceElement数组中查询,找到与每个框架相关的java.lang.reflect.Method对象,并查询它的堆栈注解列表。不幸的是,它只提供了方法的名称,没有提供方法的特征量(signature),因此这种技术不支持名称相同的(但@Status注解不同的)重载方法。
实现这种方法的示例代码可以在peekinginside-pt2.tar.gz文件的/code/04_exceptions目录中找到。
取样(Sampling)
我现在有办法把StackTraceElement数组转换为@Status注解堆栈。这种操作比表明看到的更加有用。Java 1.5中的另一个新特性--线程反省(introspection)--使我们能够从当前正在运行的线程中得到准确的StackTraceElement数组。有了这两部分信息之后,我们就可以构造JstatusBar的另一种实现。StatusManager将不会在发生方法调用的时候接收通知,而是简单地启动一个附加的线程,让它负责在正常的间隔期间抓取堆栈跟踪信息和每个步骤的状态。只要这个间隔期间足够短,用户就不会感觉到更新的延迟。
下面使"sampler"线程背后的代码,它跟踪另一个线程的经过:
class StatusSampler implements Runnable
{
private Thread watchThread;
public StatusSampler (Thread watchThread)
{
this.watchThread = watchThread;
}
public void run ()
{
while (watchThread.isAlive()) {
// 从线程中得到堆栈跟踪信息
StackTraceElement[] stackTrace =watchThread.getStackTrace();
// 从堆栈跟踪信息中提取状态消息
List<Status> statusList =StatusFinder.getStatus(stackTrace);
Collections.reverse(statusList);
// 用状态消息建立某种状态
StatusState state = new StatusState();
for (Status s : statusList) {
String message = s.value();
state.push(message);
}
// 更新当前的状态
StatusManager.setState(watchThread,state);
//休眠到下一个周期
try {
Thread .sleep(SAMPLING_DELAY);
} catch (InterruptedException ex) {}
}
//状态复位
StatusManager.setState(watchThread,new StatusState());
}
} |
与增加方法调用、手动或通过重构相比,取样对程序的侵害性(invasive)更小。我根本不需要改变建立过程或命令行参数,或修改启动过程。它也允许我通过调整SAMPLING_DELAY来控制占用的开销。不幸的是,当方法调用开始或结束的时候,这种方法没有明确的回调。除了状态更新的延迟之外,没有原因要求这段代码在那个时候接收回调。但是,未来我能够增加一些额外的代码来跟踪每个方法的准确的运行时。通过检查StackTraceElement是可以精确地实现这样的操作的。
通过线程取样实现JStatusBar的代码可以在peekinginside-pt2.tar.gz文件的/code/05_sampling目录中找到。
在执行过程中重构字节码
通过把取样的方法与重构组合在一起,我能够形成一种最终的实现,它提供了各种方法的最佳特性。默认情况下可以使用取样,但是应用程序的花费时间最多的方法可以被个别地进行重构。这种实现根本不会安装ClassTransformer,但是作为代替,它会一次一个地重构方法以响应取样过程中收集到的数据。
为了实现这种功能,我将建立一个新类InstrumentationManager,它可以用于重构和不重构独立的方法。它可以使用新的Instrumentation.redefineClasses方法来修改空闲的类,同时代码则可以不间断执行。前面部分中增加的StatusSampler线程现在有了额外的职责,它把任何自己"发现"的@Status方法添加到集合中。它将周期性地找出最坏的冒犯者并把它们提供给InstrumentationManager以供重构。这允许应用程序更加精确地跟踪每个方法的启动和终止时刻。
前面提到的取样方法的一个问题是它不能区分长时间运行的方法与在循环中多次调用的方法。由于重构会给每次方法调用增加一定的开销,我们有必要忽略频繁调用的方法。幸运的是,我们可以使用重构解决这个问题。除了简单地更新StatusManager之外,我们将维护每个重构的方法被调用的次数。如果这个数值超过了某个极限(意味着维护这个方法的信息的开销太大了),取样线程将会永远地取消对该方法的重构。
理想情况下,我将把每个方法的调用数量存储在重构过程中添加到类的新字段中。不幸的是,Java 1.5中增加的类转换机制不允许这样操作;它不能增加或删除任何字段。作为代替,我将把这些信息存储在新的CallCounter类的Method对象的静态映射中。
这种混合的方法可以在示例代码的/code/06_dynamic目录中找到。
概括
图4提供了一个矩形,它显示了我给出的例子相关的特性和代价。
图4.重构方法的分析
|
你可以发现,动态的(Dynamic)方法是各种方案的良好组合。与使用重构的所有示例类似,它提供了方法开始或终止时刻的明确的回调,因此你的应用程序可以准确地跟踪运行时并立即为用户提供反馈信息。但是,它还能够取消某种方法的重构(它被过于频繁地调用),因此它不会受到其它的重构方案遇到的性能问题的影响。它没有包含编译时步骤,并且它没有增加类载入过程中的额外的工作。
未来的趋势
我们可以给这个项目增加大量的附件特性,使它更加适用。其中最有用的特性可能是动态的状态信息。我们可以使用新的java.util.Formatter类把类似printf的模式替换(pattern substitution)用于@Status消息中。例如,我们的connectToDB(String url)方法中的@Status("Connecting to %s")注解可以把URL作为消息的一部分报告给用户。
在源代码重构的帮助下,这可能显得微不足道,因为我将使用的Formatter.format方法使用了可变参数(Java 1.5中增加的"魔术"功能)。重构过的版本类似下面的情形:
/* http://www.5a520.cn */
public void connectToDB (String url) {
Formatter f = new Formatter();
String message = f.format("Connecting to %s", url);
StatusManager.push(message);
try {
...
} finally {
StatusManager.pop();
}
}
|
不幸的是,这种"魔术"功能是完全在编译器中实现的。在字节码中,Formatter.format把Object[]作为参数,编译器明确地添加代码来包装每个原始的类型并装配该数组。如果BCEL没有加紧弥补,而我又需要使用字节码重构,我将不得不重新实现这种逻辑。
由于它只能用于重构(这种情况下方法参数是可用的)而不能用于取样,你可能希望在启动的时候重构这些方法,或最少使动态实现偏向于任何方法的重构,还可以在消息中使用替代模式。
你还可以跟踪每个重构的方法调用的启动次数,因此你还可以更加精确地报告每个方法的运行次数。你甚至于可以保存这些次数的历史统计数据,并使用它们形成一个真正的进度条(代替我使用的不确定的版本)。这种能力将赋予你在运行时重构某种方法的一个很好的理由,因为跟踪任何独立的方法的开销都是很能很明显的。
你可以给进度条增加"调试"模式,它不管方法调用是否包含@Status注解,报告取样过程中出现的所有方法调用。这对于任何希望调试死锁或性能问题的开发者来说都是无价之宝。实际上,Java 1.5还为死锁(deadlock)检测提供了一个可编程的API,在应用程序锁住的时候,我们可以使用该API把进程条变成红色。
本文中建立的基于注解的重构框架组件可能很有市场。一个允许字节码在编译时(通过Ant事务)、启动时(使用ClassTransformer)和执行过程中(使用Instrumentation)进行重构的工具对于少量其它新项目来说毫无疑问地非常有价值。
总结
在这几个例子中你可以看到,元数据编程(meta-programming)可能是一种非常强大的技术。报告长时间运行的操作的进程仅仅是这种技术的应用之一,而我们的JStatusBar仅仅是沟通这些信息的一种媒介。我们可以看到,Java 1.5中提供的很多新特性为元数据编程提供了增强的支持。特别地,把注解和运行时重构组合在一起为面向属性的编程提供了真正动态的形式。我们可以进一步使用这些技术,使它的功能超越已有的框架组件(例如XDoclet提供的框架组件的功能)。
最近搞了一个java发送传真程序,在网上搜了半天,没找到具体的例子,最后找到了国外的开源技术jacob,才解决了这个问题。
环境要求:windows xp系统,jdk 1.4(不要太高或太低,不然会发生与dll不匹配),运行java程序的机器需要有猫及驱动(一般的猫都会支持fax功能),jacob版本1.9(最好使用这个版本,其他版本会报错),xp本身能够通过猫发送传真(确认一下环境可以发送传真即可)
实现功能:java jni调用本地jacob.dll,jacob.dll中封装好的接口调用本地服务(如excel、outlook、vbscript等)faxserver.faxserver.1,实现服务器端发送传真。
步骤:
1 将jacob.dll文件拷贝到windows/system32下
2 创建java项目,将jacob.jar,jacob.dll放到项目lib路径下,同时将他们也放到jdk的lib路径下
3 编写程序(可参考jacob官方的api文档 http://www.5a520.cn ),如下:
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.com.DispatchEvents;
import com.jacob.com.Variant;
public class faxtest {
public void sendFax(String filename,Sring faxnumber) {
ActiveXComponent objFax = new ActiveXComponent("FaxServer.FaxServer.1");//这个名字一般要与注册表里fax服务名匹配对了
Dispatch faxObject = objFax.getObject();
Dispatch.call(faxObject, "Connect", "");
Dispatch doc = Dispatch.call(faxObject, "CreateDocument", filename)
.toDispatch();
Dispatch.put(doc, "RecipientName", "someone");
Dispatch.put(doc, "FaxNumber", faxnumber); //注意电话号码的格式
Dispatch.put(doc, "DisplayName", "zhupan");
Dispatch.call(doc, "Send");
Dispatch.call(faxObject, "DisConnect");
}
public static void main(String[] args) {
try {
faxtest faxDocumentProperties = new faxtest();
faxDocumentProperties.sendFax(" http://www.bt285.cn /WW.doc","028886666");
System.out.print("ok fax transfer successfully !");
} catch (Exception e) {
System.out.println(e);
}
}
}
4 调试,如果报错"no progid"异常,一般问题都是jdk与dll不匹配,或者传真服务名称(FaxServer.FaxServer.1)不匹配。
方法一:
最简单的方式就是在JSP页面的开始部分使用如下的头部信息
<%response.setContentType("application/msexcel");
response.setHeader("Content-disposition","attachment; filename=excelname.xls");%>
<html xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns="http://www.w3.org/TR/REC-html40">
<head>
<meta http-equiv=Content-Type content="text/html; charset=GBK">
<meta name=ProgId content=Excel.Sheet>
<meta name=Generator content="Microsoft Excel 11">
</head>
在导出按钮中,直接转到要导出的页面。设置为如上的头部信息就可以。
方法二
使用script
button onclick事件调用下面js 方法
然后你要导出的table定义个id=viewtable.这样就只导出这个table的内容。操作按钮在table外,不会导出来的。
<script language=javascript>
function printToExcel() {
window.clipboardData.setData( "Text ",document.all( 'viewtable ').outerHTML);
try
{
var ExApp = new ActiveXObject( "Excel.Application ")
var ExWBk = ExApp.workbooks.add()
var ExWSh = ExWBk.worksheets(1)
ExApp.DisplayAlerts = false
ExApp.visible = true
}
catch(e)
{
alert( "您的电脑没有安装Microsoft Excel软件!需要从
www.bt285.cn下载 ")
return false
}
ExWBk.worksheets(1).Paste;
}
</script>
方法三、方法四:
使用POI-JXL等插件
摘要: 不用复杂的代码,就可以让您的JavaBeans自己控制到XML文件的相互转化。本文展示了怎样通过JOX来实现从JavaBeans到XML文件的相互转换。
为了灵活的满足Web应用和Web services需求的变化,Java和XML的轻便性和可扩展性使它们成为解决这一问题的理想选择。SAX (Simple API ...
阅读全文
虽然在很多方面,JavaScript可用于改进您的网页并提高您的访问网站的效率,但是也有几件事的JavaScript不能做到的。其中的一些限制是由于该脚本浏览器窗口运行,因此无法访问服务器,而另一些则是出于安全性的考虑,以阻止网页篡改您的计算机。对于这些局限性还没有可以解决的办法,而任何抱怨其电脑不能通过JavaScript执行下列任务的人,是因为没有对所要做的事情考虑周全。
没有服务器端脚本的帮助,JavaScript就不能在服务器上写文件
使用Ajax,JavaScript可以向服务器发送请求。这个请求可以用XML或纯文本的方式读取文件,但是它不能写文件,除非被服务器调用的文件以脚本方式运行才能写文件。比如 http://www.bt285.cn/content.php?id=1196863 这张甜性涩爱下载页面是用json获取的,但是在此页面里不能直接写入数据。
JavaScript不能访问数据库
除非你使用Ajax,并且服务器端脚本为你执行数据库访问
JavaScript不能从用户处读取或写文件
尽管JavaScript在用户端计算机上运行,而该用户端也正在浏览网页,但仍不允许对任何网页本身以外的数据进行访问。这样做是出于安全的考虑,因为其他网页有可能更新您的计算机并且非法安装上我们都不清楚的东西。唯一例外的是所谓的cookies文件,它是小文本文件,可以由JavaScript写入和读取。该浏览器限制对Cookie的访问,所以一个给定的网页只能访问该网页所创造的cookie。
如果没有打开窗口,JavaScript不能选择窗口
该项限制同样出于安全性的考虑
JavaScript不能访问网页
尽管不同的网页可以在同一时间打开,可以在单独的浏览器中或者同一个浏览器的不同窗体中打开。在网页上运行的JavaScript从属于一个网页,因此不能访问来自不同域名中不同网页的信息。这一限制有助于确保你的隐私信息不会被其他同时打开网页的人共享。而唯一能访问来自另一域名的文件的方法是对你的服务器进行Ajax调用,并却具备一个可以访问其他域名的服务器端脚本。
JavaScript不能保护你的页面资源和图像
页面上的任何图像都是分开下载到电脑上的,所以我们在看网页的时候,就已经拥有了所有图像的备份。而对于网页上真正的HTML资源,也同样如此。网页需要解密所有加密的网页,以显示该网页。而一个加密的我那个也可能要求按顺序依次激活JavaScript,以达到依次解密再显示出来的目的。一旦网页被解密,任何知道该方法的人都能轻易保存解密的网页资源备份,比如 http://www.5a520.cn/s_c1vvs30vvf5a6Y6Lev6aOO5rWB 这张官路风流最新章节520页面,一但下载到客户端,那些这张页面所相当的js,css,jpg等http连接也下载到你本地了。
- private static Credit BaseCredit = new Credit();
- public static Credit getNewCredit() {
- return (Credit) BaseCredit.clone();
- }
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit() {
return (Credit) BaseCredit.clone();
}
Java语言习惯用语
1.循环
■ 在重要的循环里,消除循环终止判断时的方法调用。。。
例如:将...bt www.bt285.cn
for(int i=0; i<collection.size();i++){
- ...
- }
for(int i=0; i<collection.size();i++){
...
}
替换为…
- for(int i=0; n=collection.size();i<n;i++){
- ...
- }
for(int i=0; n=collection.size();i<n;i++){
...
}
■ 通常,把与循环index不相关的移到循环的外面
- for(int i=0; terminal=x.length;i<terminal;i++){
- x[i] = x[i]/scaleA *scaleB;
- }
for(int i=0; terminal=x.length;i<terminal;i++){
x[i] = x[i]/scaleA *scaleB;
}
应该该成:
- Double scale = scaleB*scaleA;
- for(int i=0; terminal=x.length;i<terminal;i++){
- x[i] = x[i]/scale ;
- }
Double scale = scaleB*scaleA;
for(int i=0; terminal=x.length;i<terminal;i++){
x[i] = x[i]/scale ;
}
2.字符串
■ 消除字符串连接
■ 创建长字符串时,总是使用StringBuffter代替String
■ 预先分配StringBuffer空间 StringBuffer sb = new StringBuffer(5000);
3.基本数据类型
■ 在重要的循环里使用基本数据类型(int型数据通常比long/double型数据更快)
■ 基本数据类型(Boolean,Integer,etc)的包装类主要用在当传递的方法参数必须是一个对象的引用时(而不是一个基本数据类型)
■ 对所有的常量代数表达式使用static final修饰符
■ 使常量更容易引用(编译器预先计算常量表达式)
4.异常
■ 异常只用于单个真正的错误条件 如小说520网 www.5a520.cn 抛出异常时
抛出一个异常和执行一个catch代码块花费是很高的(主要由于当创建一个异常时要获得线程栈的一个快照)
只当条件真的异常时才抛出一个异常
■ 抛出异常首先要创建一个新的对象。
Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。
只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。
异常只能用于错误处理,不应该用来控制程序流程。
■ 使编译器和运行时最优化,将几个方法调用放在一个try/catch块中,而不是为每个方法调用实现几个try/catch块
- try{
- Some.method1();
- }catch(method1Exception e){
- handle exception 1
- }
-
- try{
- Some.method2();
- }catch(method2Exception e){
- handle exception 2
- }
-
- try{
- Some.method3();
- }catch(method3Exception e){
- handle exception 3
- }
try{
Some.method1(); //Difficut for java1.4
}catch(method1Exception e){
handle exception 1 // to optimize this code
}
try{
Some.method2(); //Difficut for java1.4
}catch(method2Exception e){
handle exception 2 // to optimize this code
}
try{
Some.method3(); //Difficut for java1.4
}catch(method3Exception e){
handle exception 3 // to optimize this code
}
应该写为:
- try{
- Some.method1();
- Some.method2();
- Some.method3();
- }catch(method1Exception e){
- handle exception 1
- }catch(method2Exception e){
- handle exception 2
- }catch(method3Exception e){
- handle exception 3
- }
try{
Some.method1();
Some.method2();
Some.method3(); //Difficut for java1.4
}catch(method1Exception e){
handle exception 1
}catch(method2Exception e){
handle exception 2
}catch(method3Exception e){
handle exception 3
}
5.基准
■ 注意,所有这些技巧会因不同的平台和虚拟机而不同
一 例如:在有些servlet容器内,通过一个OutputStream作为字节输出会更快
一 在其它的容器内,通过一个PrintWriter输出字符会更快
■ 这些技巧描述的是最可移植的建议
■ 你可能需要运行一些基准来判断在你的平台上怎么样是最快的
6.不用new关键词创建类的实例
■用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。
但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。
在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。
例如,下面是Factory模式的一个典型实现:
- public static Credit getNewCredit() {
- return new Credit();
- }
public static Credit getNewCredit() {
return new Credit();
}
优化后:
- private static Credit BaseCredit = new Credit();
- public static Credit getNewCredit() {
- return (Credit) BaseCredit.clone();
- }
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit() {
return (Credit) BaseCredit.clone();
}
上面的思路对于数组处理同样很有用。
7.使用非阻塞I/O
■Java版本较低的JDK不支持非阻塞I/O API。为避免I/O阻塞,一些应用采用了创建大量线程的办法(在较好的情况下,会使用一个缓冲池)。这种技术可以在许多必须支持并发I/O流的应用中见到,如Web服务器、报价和拍卖应用等。然而,创建Java线程需要相当可观的开销。
JDK 1.4引入了非阻塞的I/O库(java.nio)。如果应用要求使用版本较早的JDK,在这里有一个支持非阻塞I/O的软件包。
8.不要重复初始化变量
■默认情况下,调用类的构造函数时, Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和 double变量设置成0.0,逻辑值设置成false。
当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键词创建一个对象时,构造函数链中的所有构造函数都会被自动调用。
9.尽量指定类的final修饰符
■带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String。为String类指定final防止了人们覆盖length()方法。
另外,如果指定一个类为final,则该类所有的方法都是final。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。
10.尽量使用局部变量
■调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化。请参见《尽可能使用堆栈变量》。
11.乘法和除法
■考虑下面的代码:
- for (val = 0; val < 100000; val +=5) { alterX = val * 8; myResult = val * 2; }
for (val = 0; val < 100000; val +=5) { alterX = val * 8; myResult = val * 2; }
优化后:
- for (val = 0; val < 100000; val += 5) { alterX = val << 3; myResult = val << 1; }
for (val = 0; val < 100000; val += 5) { alterX = val << 3; myResult = val << 1; }
修改后的代码不再做乘以8的操作,而是改用等价的左移3位操作,每左移1位相当于乘以2。相应地,右移1位操作相当于除以2。值得一提的是,虽然移位操作速度快,但可能使代码比较难于理解,所以最好加上一些注释。
摘要: 主要使用的是java.util.Robot类来捕获屏幕,可以实现对屏幕一个矩形区域的捕获,通过这个类,我们也可以实现一个远程桌面控制的程序。
package com.qiu.util;
import java.io.*;
import java.net.*;
import javax.swing.*;
import java.a...
阅读全文