摘要: 在这篇文章里,对于Spring XML的配置,我将向你展示12种比较好的实践。其中的一些实践不仅是好的实践,更是必要的实践。除此以外,还有其他因素,例如领域模型的设计,都能影响XML的配置,但是这篇文章重点研究XML配置的易读性和易管理性。
Spring是一个强大的JAVA应用框架,广泛地应用于JAVA的应用程序。为Plain Old Java Objects(POJOs)提供企业级服务。Spring利用依赖注入机制来简化工作,同时提高易测性。Spring beans及依赖,以及beans类所需的服务都在配置文件中进行了详细的说明,这个配置文件是典型的XML格式。但是它既冗长又不实用。对于需要定义大量Spring beans的大工程来说,我们难以阅读和管理它。
版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Jason;Li;evenbetter(作者的blog:http://blog.matrix.org.cn/page/evenbetter)
原文:http://www.onjava.com/pub/a/onjava/2006/01/25/spring-xml-configuration-best-practices.html
译文:http://www.matrix.org.cn/resource/article/44/44236_Spring+XML+Configurations.html
关键字:Spring;XML;Configurations
在这篇文章里,对于Spring XML的配置,我将向你展示12种比较好的实践。其中的一些实践不仅是好的实践,更是必要的实践。除此以外,还有其他因素,例如领域模型的设计,都能影响XML的配置,但是这篇文章重点研究XML配置的易读性和易管理性。
1。不要使用autowiring
Spring可以通过类的自省来自动绑定其依赖部分,使得你不必明确指明bean的属性和构造器。Bean的属性可以通过属性名称或类型匹配来实现自动绑定。构造器通过类型匹配来实现自动绑定。你甚至可以指定自动检测自动绑定模式,它可以引导Spring选择一种适当的运行机制。先来看看下面的一个例子:
<bean id="orderService"
class="com.lizjason.spring.OrderService"
autowire="byName"/>
OrderService类的属性名在容器中用于匹配bean实例。自动绑定可以潜在地节省一些打字和减少一些混乱。但是在现实世界的工程里你不应该使用这种方式,这是因为它牺牲了配置的清晰性和可维护性。许多指南和介绍中大量吹捧自动绑定是Spring的一种极好的特征而没有提到这一特性所带来的牺牲。依我的观点,这就像Spring中的object-pooling,它更像是一种为了占据更多市场的商业特征。它对于XML配置文件的小巧化是一个好办法,但实际上也增加了复杂程度,尤其当你运行有大量类声明的工程时。虽然Spring允许你混合自动绑定和手动绑定,但是这个矛盾会使XML配置更加晦涩难懂。
2.使用通俗的命名
这个方式对于Java编码也一样适用。在工程中使用清晰的、描述性的、协调的通俗名称对于开发者理解XML配置是十分有益的。例如对于bean ID,你可以根据通俗的Java类名来命名它。对于例子中OrderServiceDAO的bean ID命名为orderServiceDAO。对于大的工程,你可以在bean ID前面加上包名作为前缀。
3. 使用简洁的形式
简洁形式避免了冗长,是因为它从子元素中将属性值和参考写到属性中。例如下面的例子:
<bean id="orderService"
class="com.lizjason.spring.OrderService">
<property name="companyName">
<value>lizjason</value>
</property>
<constructor-arg>
<ref bean="orderDAO">
</constructor-arg>
</bean>
可以使用简洁形式将上述代码重写为:
<bean id="orderService"
class="com.lizjason.spring.OrderService">
<property name="companyName"
value="lizjason"/>
<constructor-arg ref="orderDAO"/>
</bean>
简洁形式功能在1.2版本中可以使用。对于<ref local="...">没有简洁形式。
简洁形式不但可以节约你的打字,而且可以使XML配置文件清晰。它最引人注目的是当在一个配置文件中有大量定义的类时可以提高易读性。
4. 对于构造器参数匹配,类型名比序号好。
当一个构造器含有一个以上的同种类型的参数,或者属性值的标签已经被占用时,Spring允许你使用从0计数的序号来解决这些会带来混淆的问题。例如:
<bean id="billingService"
class="com.lizjason.spring.BillingService">
<constructor-arg index="0" value="lizjason"/>
<constructor-arg index="1" value="100"/>
</bean>
像下面这样,利用类型属性来编写会更好一些:
<bean id="billingService"
class="com.lizjason.spring.BillingService">
<constructor-arg type="java.lang.String"
value="lizjason"/>
<constructor-arg type="int" value="100"/>
</bean>
使用索引可以稍稍减少一些冗长,但是和使用类型属性相比,它还是有容易发生错误的倾向和难于阅读的缺点。你应该只在构造器参数不明确的时候,才使用索引这一方法。
5. 尽可能重用已定义过的bean
Spring提供一种类似继承一样的机制来减少配置信息的复制并简化XML配置。定义一个子类可以从它父类那里继承配置信息,而父类实质上作为子类的一个模板。这就是大工程中所谓的重用。你所需要做的就是在父类bean中设置abstract=true,然后在子bean注明它自己的父类bean。例如:
<bean id="abstractService" abstract="true"
class="com.lizjason.spring.AbstractService">
<property name="companyName"
value="lizjason"/>
</bean>
<bean id="shippingService"
parent="abstractService"
class="com.lizjason.spring.ShippingService">
<property name="shippedBy" value="lizjason"/>
</bean>
ShippingService类从abstractService类那里继承companyName属性的值——lizjason。如果你没有为一个bean指明类或factory方法,那么这个bean便是抽象的。
6. 尽量使用ApplicationContext来装配定义的bean
像在Ant脚本中的引用一样,Spring的引用对于装配模块化的bean来说是很有用的。例如:
<beans>
<import resource="billingServices.xml"/>
<import resource="shippingServices.xml"/>
<bean id="orderService"
class="com.lizjason.spring.OrderService"/>
<beans>
相对于使用import在XML配置中来预装配,通过ApplicationContext来配置这些beans,显得更加灵活。利用ApplicationContext也使得XML配置易于管理。你可以像下面的例子那样在ApplictionContext构造器里布置bean:
String[] serviceResources =
{"orderServices.xml",
"billingServices.xml",
"shippingServices.xml"};
ApplicationContext orderServiceContext = new
ClassPathXmlApplicationContext(serviceResources);
7. 利用id作为bean的标识符
你可以指定一个id或名称来作为bean的标识符。虽然使用id不会提高易读性,但是它可以让XML parser对bean的引用有效方面进行更好的验证。如果由于XML IDREF的限制而不能使用某个id,你可以利用names来作为bean的标识符。XML IDREF的限制是id必须以字母开头(或者在XML规范中定义的标点符号),后面接着字母,数字,连字号,下划线,冒号等。实际上,遇到XML IDREF限制的问题是很少见的。
8. 在开发阶段使用依赖检验
你可以在bean中给依赖检验的属性设置值,而不采用原先默认的空值,属性设置例如simple,object或all,以便容器进行依赖检验。当bean的全部的属性(或某类属性)需要被明确设置或自动绑定时,依赖检验便显得很有用。
<bean id="orderService"
class="com.lizjason.spring.OrderService"
dependency-check="objects">
<property name="companyName"
value="lizjason"/>
<constructor-arg ref="orderDAO"/>
</bean>
在这个例子里,容器确保为orderService bean设置的属性不是primitives 或者 collections。为所有的bean设置默认依赖检测也是可以的,但是我们很少这样做,是因为有些bean的属性根本就不必设置。
9. 为每个配置文件加上一个header comment
最好使用descriptive id和名称来代替在XML配置文件中的注释。此外,加上一个配置文件header也很有用处,它可以概述文件中所定义的bean。你可以选择将描述内容加入description标签中。例如:
<beans>
<description>
This file defines billing service
related beans and it depends on
baseServices.xml,which provides
service bean templates...
</description>
...
</beans>
使用description标签的一个好处是可以容易地利用工具从标签中选取出description(的内容)。
10. 对于任何变化,要与队友积极交流
当你重构Java代码时,你需要随时更新配置文件并且通知队友。XML配置文件也是代码,它们是应用程序的至关重要的部分,但是它们难于阅读和维护。大部分时间你既要阅读XML配置文件又要阅读运行中的Java代码。
11. Setter injection优于constructor injection
Spring提供3种类型的依赖注入: constructor injection,setter injection, 和method injection。我们一般只用前两种类型。
<bean id="orderService"
class="com.lizjason.spring.OrderService">
<constructor-arg ref="orderDAO"/>
</bean>
<bean id="billingService"
class="com.lizjason.spring.BillingService">
<property name="billingDAO"
ref="billingDAO">
</bean>
这个例子中,orderService类使用的是constructor injection,而BillingService类使用的是setter injection。constructor injection可以确保bean不会在一个非法状态下被创建,但是setter injection更加灵活并且更易管理,尤其当类存在很多属性并且其中一些是可选的情况下。
12. 不要滥用依赖注入
作为最后一点,Spring ApplicationContext可以替你创建Java对象,但是并不是所有的Java对象都通过依赖注入来创建的。例如,全局的对象不应该通过ApplicationContext来创建。Spring是一个很棒的框架,但是,就易读性和易管理性而言,当定义大量bean的时候,基于XML的配置问题就会突出。过度的依赖注入会使XML配置变得复杂而且臃肿。记住!使用强大的IDE时,例如Eclipse和IntelliJ,与XML文件相比,Java代码更加易读,易维护,易管理。
总结
对于Spring的配置,XML是很优秀的方式。但当定义大量bean时,基于XML配置会变得冗长,笨拙。Spring提供了丰富的配置选项。适当地利用其中的选项可以使XML配置清晰,但是,有些选项,例如autowiring(自动绑定),往往会降低易读性和易维护性。文章中所列举的实例,可以帮助你创建出清晰易读的XML配置文件。
关于作者:
Jason Zhicheng Li是Object Computing, Inc. in St. Louis, MO(公司)一名资深的软件工程师。
资源:
Spring论坛:http://www.matrix.org.cn/topic.shtml?forumId=26
Class cls = Class.forName("bean.User");
Method mtd = cls.getMethod("setName",new Class []{String.class});
Object obj = (Object) cls.newInstance();
mtd.invoke(obj,new Object []{"JHF"});
return obj;
上述代码动态加载了User类,并通过Reflection调用了User.setName()方法设置name属性。
Spring 的BeanWrapper基于同样的原理
Object obj = Class.forName("bean.User").newInstance();
BeanWrapper bw = new BeanWrapperImple(obj);
bw.setPropertyValue("name","JHF");
...
bw.getPropertyValue("name");
JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework),供Java开发人员编写单元测试之用。
竹笋炒肉之Junit学习笔记
JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework),供Java开发人员编写单元测试之用。
1、概述
Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。
Junit本质上是一套框架,即开发者制定了一套条条框框,遵循这此条条框框要求编写测试代码,如继承某个类,实现某个接口,就可以用Junit进行自动测试了。
由于Junit相对独立于所编写的代码,可以测试代码的编写可以先于实现代码的编写,XP 中推崇的 test first design的实现有了现成的手段:用Junit写测试代码,写实现代码,运行测试,测试失败,修改实现代码,再运行测试,直到测试成功。以后对代码的修改和优化,运行测试成功,则修改成功。
Java 下的 team 开发,采用 cvs(版本控制) + ant(项目管理) + junit(集成测试) 的模式时,通过对ant的配置,可以很简单地实现测试自动化。
对不同性质的被测对象,如Class,Jsp,Servlet,Ejb等,Junit有不同的使用技巧,以后慢慢地分别讲叙。以下以Class测试为例讲解,除非特殊说明。
2、下载安装
去Junit主页下载最新版本3.8.1程序包junit-3.8.1.zip
用winzip或unzip将junit-3.8.1.zip解压缩到某一目录名为$JUNITHOME
将junit.jar和$JUNITHOME/junit加入到CLASSPATH中,加入后者只因为测试例程在那个目录下。
注意不要将junit.jar放在jdk的extension目录下
运行命令,结果如右图。
java junit.swingui.TestRunner junit.samples.AllTests
3、Junit架构
下面以Money这个类为例进行说明。
public class Money {
private int fAmount;//余额
private String fCurrency;//货币类型
public Money(int amount, String currency) {
fAmount= amount;
fCurrency= currency;
}
public int amount() {
return fAmount;
}
public String currency() {
return fCurrency;
}
public Money add(Money m) {//加钱
return new Money(amount()+m.amount(), currency());
}
public boolean equals(Object anObject) {//判断钱数是否相等
if (anObject instanceof Money) {
Money aMoney= (Money)anObject;
return aMoney.currency().equals(currency())
&& amount() == aMoney.amount();
}
return false;
}
}
Junit本身是围绕着两个设计模式来设计的:命令模式和集成模式.
命令模式
利用TestCase定义一个子类,在这个子类中生成一个被测试的对象,编写代码检测某个方法被调用后对象的状态与预期的状态是否一致,进而断言程序代码有没有bug。
当这个子类要测试不只一个方法的实现代码时,可以先建立测试基础,让这些测试在同一个基础上运行,一方面可以减少每个测试的初始化,而且可以测试这些不同方法之间的联系。
例如,我们要测试Money的Add方法,可以如下:
public class MoneyTest extends TestCase { //TestCase的子类
public void testAdd() { //把测试代码放在testAdd中
Money m12CHF= new Money(12, "CHF"); //本行和下一行进行一些初始化
Money m14CHF= new Money(14, "CHF");
Money expected= new Money(26, "CHF");//预期的结果
Money result= m12CHF.add(m14CHF); //运行被测试的方法
Assert.assertTrue(expected.equals(result)); //判断运行结果是否与预期的相同
}
}
如果测试一下equals方法,用类似的代码,如下:
public class MoneyTest extends TestCase { //TestCase的子类
public void testEquals() { //把测试代码放在testEquals中
Money m12CHF= new Money(12, "CHF"); //本行和下一行进行一些初始化
Money m14CHF= new Money(14, "CHF");
Assert.assertTrue(!m12CHF.equals(null));//进行不同情况的测试
Assert.assertEquals(m12CHF, m12CHF);
Assert.assertEquals(m12CHF, new Money(12, "CHF")); // (1)
Assert.assertTrue(!m12CHF.equals(m14CHF));
}
}
当要同时进行测试Add和equals方法时,可以将它们的各自的初始化工作,合并到一起进行,形成测试基础,用setUp初始化,用tearDown清除。如下:
public class MoneyTest extends TestCase {//TestCase的子类
private Money f12CHF;//提取公用的对象
private Money f14CHF;
protected void setUp() {//初始化公用对象
f12CHF= new Money(12, "CHF");
f14CHF= new Money(14, "CHF");
}
public void testEquals() {//测试equals方法的正确性
Assert.assertTrue(!f12CHF.equals(null));
Assert.assertEquals(f12CHF, f12CHF);
Assert.assertEquals(f12CHF, new Money(12, "CHF"));
Assert.assertTrue(!f12CHF.equals(f14CHF));
}
public void testSimpleAdd() {//测试add方法的正确性
Money expected= new Money(26, "CHF");
Money result= f12CHF.add(f14CHF);
Assert.assertTrue(expected.equals(result));
}
}
将以上三个中的任一个TestCase子类代码保存到名为MoneyTest.java的文件里,并在文件首行增加
import junit.framework.*;
,都是可以运行的。关于Junit运行的问题很有意思,下面单独说明。
上面为解释概念“测试基础(fixture)”,引入了两个对两个方法的测试。命令模式与集成模式的本质区别是,前者一次只运行一个测试。
集成模式
利用TestSuite可以将一个TestCase子类中所有test***()方法包含进来一起运行,还可将TestSuite子类也包含进来,从而行成了一种等级关系。可以把TestSuite视为一个容器,可以盛放TestCase中的test***()方法,它自己也可以嵌套。这种体系架构,非常类似于现实中程序一步步开发一步步集成的现况。
对上面的例子,有代码如下:
public class MoneyTest extends TestCase {//TestCase的子类
....
public static Test suite() {//静态Test
TestSuite suite= new TestSuite();//生成一个TestSuite
suite.addTest(new MoneyTest("testEquals")); //加入测试方法
suite.addTest(new MoneyTest("testSimpleAdd"));
return suite;
}
}
从Junit2.0开始,有列简捷的方法:
public class MoneyTest extends TestCase {//TestCase的子类
....
public static Test suite() {静态Test
return new TestSuite(MoneyTest.class); //以类为参数
}
}
TestSuite见嵌套的例子,在后面应用案例中有。
4、测试代码的运行
先说最常用的集成模式。
测试代码写好以后,可以相应的类中写main方法,用java命令直接运行;也可以不写main方法,用Junit提供的运行器运行。Junit提供了textui,awtui和swingui三种运行器。
以前面第2步中的AllTests运行为例,可有四种:
java junit.textui.TestRunner junit.samples.AllTests
java junit.awtui.TestRunner junit.samples.AllTests
java junit.swingui.TestRunner junit.samples.AllTests
java junit.samples.AllTests
main方法中一般也都是简单地用Runner调用suite(),当没有main时,TestRunner自己以运行的类为参数生成了一个TestSuite.
对于命令模式的运行,有两种方法。
静态方法
TestCase test= new MoneyTest("simple add") {
public void runTest() {
testSimpleAdd();
}
};
动态方法
TestCase test= new MoneyTest("testSimpleAdd");
我试了一下,好象有问题,哪位朋友成功了,请指点我一下。确实可以。
import junit.framework.*;
public class MoneyTest extends TestCase {//TestCase的子类
private Money f12CHF;//提取公用的对象
private Money f14CHF;
public MoneyTest(String name){
super(name);
}
protected void setUp() {//初始化公用对象
f12CHF= new Money(12, "CHF");
f14CHF= new Money(14, "CHF");
}
public void testEquals() {//测试equals方法的正确性
Assert.assertTrue(!f12CHF.equals(null));
Assert.assertEquals(f12CHF, f12CHF);
Assert.assertEquals(f12CHF, new Money(12, "CHF"));
Assert.assertTrue(!f12CHF.equals(f14CHF));
}
public void testAdd() {//测试add方法的正确性
Money expected= new Money(26, "CHF");
Money result= f12CHF.add(f14CHF);
Assert.assertTrue(expected.equals(result));
}
// public static void main(String[] args) {
// TestCase test=new MoneyTest("simple add") {
// public void runTest() {
// testAdd();
// }
// };
// junit.textui.TestRunner.run(test);
// }
public static void main(String[] args) {
TestCase test=new MoneyTest("testAdd");
junit.textui.TestRunner.run(test);
}
}
再给一个静态方法用集成测试的例子:
public static Test suite() {
TestSuite suite= new TestSuite();
suite.addTest(
new testCar("getWheels") {
protected void runTest() { testGetWheels(); }
}
);
suite.addTest(
new testCar("getSeats") {
protected void runTest() { testGetSeats(); }
}
);
return suite;
}
5、应用案例
Junit Primer例程,运行如下:
java com.hedong.JunitLearning.Primer.ShoppingCartTest
Ant+Junit+Mailto实现自动编译、调试并发送结果的build.xml
JUnit实施,写得很棒,理解也深刻。例程运行如下:
java com.hedong.JunitLearning.car.testCarNoJunit
java junit.swingui.TestRunner com.hedong.JunitLearning.car.testCar
Junit与log4j结合,阿菜的例程运行:
cd acai
ant junit
6、一些问题
有人在实践基础上总结出一些非常有价值的使用技巧,我没有经过一一“测试”,暂列在此。
不要用TestCase的构造函数初始化Fixture,而要用setUp()和tearDown()方法。
不要依赖或假定测试运行的顺序,因为JUnit利用Vector保存测试方法。所以不同的平台会按不同的顺序从Vector中取出测试方法。不知3.8中是不是还是如此,不过它提供的例子有一个是指定用VectorSuite的,如果不指定呢?
避免编写有副作用的TestCase。例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据。简单的回滚就可以了。
当继承一个测试类时,记得调用父类的setUp()和tearDown()方法。
将测试代码和工作代码放在一起,一边同步编译和更新。(使用Ant中有支持junit的task.)
测试类和测试方法应该有一致的命名方案。如在工作类名前加上test从而形成测试类名。
确保测试与时间无关,不要依赖使用过期的数据进行测试。导致在随后的维护过程中很难重现测试。
如果你编写的软件面向国际市场,编写测试时要考虑国际化的因素。不要仅用母语的Locale进行测试。
尽可能地利用JUnit提供地assert/fail方法以及异常处理的方法,可以使代码更为简洁。
测试要尽可能地小,执行速度快。
把测试程序建立在与被测对象相同的包中
在你的原始代码目录中避免测试码出现,可在一个源码镜像目录中放测试码
在自己的应用程序包中包含一个TestSuite测试类
7、相关资源下载
以下jar包,我只是做了打包、编译和调试的工作,供下载学习之用,相关的权利属于原作者。
可运行例程.jar
Build.xml
阿菜的例程
Junit API 汉译(pdf)
8、未完成的任务
httpunit
cactus
将Junit用链接池测试
主要参考文献:
JUnit入門
http://www.dotspace.twmail.org/Test/JUnit_Primer.htm
怎样使用Junit Framework进行单元测试的编写
http://www.chinaunix.net/bbsjh/14/546.html
Ant+Junit+Log4J+CVS进行XP模式开发的建立
http://ejb.cn/modules/tutorials/printpage.php?tid=4
用HttpUnit测试Web应用程序
http://www.zdnet.com.cn/developer/code/story/0,2000081534,39033726,00.htm
有没有用过Cactus的,Web层的测试是Cactus还是JUnit?
http://www.jdon.com/jive/thread.jsp?forum=16&thread=9156
Ant+junit的测试自动化 biggie(原作)
http://www.csdn.net/Develop/article/19%5C19748.shtm
JUnit实施
http://www.neweasier.com/article/2002-08-07/1028723459.html
JUnitTest Infected: Programmers Love Writing Tests
http://junit.sourceforge.net/doc/testinfected/testing.htm
JUnit Cookbook
http://junit.sourceforge.net/doc/cookbook/cookbook.htm
JUnit Primer
http://www.itu.dk/~lthorup/JUnitPrimer.html
IBM DevelopWorks
http://www-106.ibm.com/search/searchResults.jsp?query=junit&searchScope=dW&searchType=1&searchSite=dWChina&pageLang=zh&langEncoding=gb2312&Search.x=0&Search.y=0&Search=Search
文章出自: www.fanqiang.com
在Java应用程序运行时,特别是需要在跨平台工作环境下运行时,需要确定操作系统类型、用户JDK版本和用户工作目录等随工作平台变化的信息,来保证程序正确运行。一般情况下,可以利用JDK提供的系统属性类(Properties)中的方法,快速地获取工作环境信息。另外,程序开发人员还可以定义与应用程序相关的系统属性文件,在用户程序执行过程中动态地加载程序员定义的属性文件来控制程序运行。
本文通过对系统属性类的分析,结合实际例子介绍如何定义系统属性文件,并讨论安全策略文件的定义。
属性类 Java Properties类的继承关系如下:
java.lang.Object
+--java.util.Dictionary
+--java.util.Hashtable
+--java.util.Properties
在应用程序开始执行时,程序首先读取系统的默认属性。如果定义了用户属性文件,则应用程序加载该属性文件。程序运行过程中可以根据执行情况动态地修改属性定义,并在程序结束运行前,保存属性文件。
获取属性的方法:
●contains(Object value)、containsKey(Object key):如果给定的参数或属性关键字在属性表中有定义,该方法返回True,否则返回False;
●getProperty(String key)、getProperty(String key, String default):根据给定的属性关键字获取关键字值;
●list(PrintStream s)、list(PrintWriter w): 在输出流中输出属性表内容;
●size():返回当前属性表中定义的属性关键字个数。
设置属性的方法:
●put(Object key, Object value) :向属性表中追加属性关键字和关键字的值;
●remove(Object key) :从属性表中删除关键字。
获取系统属性
系统属性是指与用户程序相关的操作系统配置信息以及软件信息。通常与用户程序相关的属性关键字包括:
●file.separator : 文件分隔符, Windows环境下为“\",Unix环境下为“/”;
●user.home :用户主目录;
●java.home :Java实时运行环境的安装目录;
●java.ext.dirs :JDK的安装目录;
●os.name :操作系统名称;
●user.name :用户登录名称;
●os.version :操作系统版本;
●path.separator :当前操作系统的路径分隔符;
●user.dir :当前用户程序所在目录。
下面结合例子说明获取系统属性的方法。
/*getSystemProperties.java*/
import java.util.Properties;
public class getSystemProperties
{
public static void main(String args[])
{
//通过获得系统属性构造属性类 prop
Properties prop = new Properties(
System.getProperties() );
//在标准输出中输出系统属性的内容
prop.list(System.out);
}
//根据获取的系统属性确定程序执行流程
……
}
上述程序执行后,在Windows环境中产生类似如下的输出:
user.home=C:\WIN95
java.home=D:\JDK1.2\JRE
java.ext.dirs=D:\JDK1.2
os.name=Windows 95
user.name=Office
java.vm.name=Classic VM
os.version=4.10
path.separator=;
file.separator=\
user.dir=D:\JavaTest
属性文件的操作方法
Java程序开发人员可以通过定义属性文件来设置程序运行的参数。属性文件是程序外部文件,在应用程序初始运行时,可以通过读取属性文件来获取有关程序运行的参数。例如,在程序运行时需要在程序外部控制程序的执行流程,这时可以通过定义属性文件的方法达到目的。下面结合例子说明属性文件的操作方法:
/*PropertyFile.java*/
//引入相关类
import java.io.*;
import java.util.Properties;
public class PropertyFile
{
//定义文件输入和输出流
static FileInputStream fis;
static FileOutputStream fos;
public static void main(String args[])
{
//生成新的属性对象
Properties prop = new Properties();
try
{
//生成文件输入和输出流,输入流指向用户已经定义的属性文件,输出流指向由应用程序新定义的属性文件
fis = new FileInputStream
(“firstProp.txt”);
fos = new FileOutputStream
(“secondProp.txt”);
}
catch(FileNotFoundException e)
{
System.out.println(“Cannot create the file stream”);
}
try
{
//从输入文件加载系统属性
prop.load(fis);
//根据程序执行情况更改属性关键字的值
prop.put(“Switch”,“1”);
//输出新属性文件secondProp.txt
prop .save ( fos ,“-- a new properties file-”);
}
catch(IOException e)
{
System.out.println(“Exception in repleace the keyword”);
}
}
}
在程序执行之前,用户必须首先定义属性文件firstProp.txt,该文件内容如下:
Switch= 0
Version= 1.0
Directory= JavaTest
程序运行后,输出新的属性文件secondProp.txt,该文件内容如下(注意观察两个文件关键字Switch内容的区别):
#--a new properties file--
#Sun Mar 11 21:22:40 CST 2001
Switch= 1
Version= 1.0
Directory= JavaTest
从上例可以看出,通过JDK提供的属性类Properties可以方便地在程序外部对应用程序的执行流程进行控制,从而简化了编程难度,使程序流程可控性更好。
安全策略文件
Java提供的应用程序环境的安全策略,使得不同的代码对系统资源拥有不同的访问许可。Java应用程序安全策略由Policy对象来表达,通过定义安全策略文件来实现。Java 1.2的安全策略文件分为三级:系统安全策略文件、用户安全策略文件和缺省安全策略文件。当Java应用程序启动后,按照先后顺序加载安全策略内容。下面结合典型的安全策略文件内容对安全策略文件的定义进行说明:
grant
{
//对系统和用户目录设置“读”的权限
permission java.util.PropertyPermission “user.dir”,
“read”;
permission java.util.PropertyPermission “user.home”,“read”;
permission java.util.PropertyPermission “java.home”,“read”;
permission java.util.PropertyPermission “java.class.
path”,“read”;
permission java.util.PropertyPermission “user.name”,“read”;
//其他安全策略内容
};
上述安全策略文件定义了应用程序对用户目录、用户登录目录、JDK安装目录、用户名称等系统内容具有读的权限。
Tomcat 5.0 对于 Get 请求的默认编码是 ISO-8859-1
对请求参数转码:new String(XXX.getBytes("ISO8859-1"),"编码")
Motivition
曾经有一个网友问过我这样一个问题:
<%@page contentType="text/html; charset=UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
中国
</body>
</html>
这个页面在为什么在运行的时候“中国”会变成乱码?
Analysis
Key Step
对于上面问题的分析需要从整个JSP页面请求的生命周期来看,一般的都需要经历下面几个阶段:
1。应用服务器根据JSP页面生成一个Java文件
2。应用服务器调用java.exe将Java文件编译成一个Servlet对应的class文件
3。用户的浏览器请求JSP对应的Servlet,Web容器起一个线程执行Servlet,将数据返回给客户端浏览器
4。用户的IE根据返回的数据,将结果显示给用户。
Key Step Analysis
为了更好的了解编码问题,我们暂时先从上面的四个环节一步步来分析,根据分析的结果,来得到最终的解决办法。
1. 在应用服务器根据JSP页面生成Java文件阶段。
应用服务器会将整个JSP页面的代码读取出来,然后写到一个新的JAVA文件中,在读文件和写文件的时候都牵涉到一个编码问题,这个编码问题应用服务器是如何解决的呢?我研究Tomcat应用服务器的源代码,发现Tomcat中有一个pageEncoding参数非常重要,在ParserController会从JSP文件中读出这个参数(如果没有读到,就从第一行的contentType中读取charset),然后保存起来,如果没有读取到这个参数,会从JspConfig中读出一个默认的PageEncoding参数,如果这两个参数都没有的设置,系统会默认成ISO8859-1的编码来读取原来的JSP文件。
从上面的分析出,我们已经基本了解了应用服务器读取JSP文件的编码方式,由于Java底层都是基于Unicode编码来存储字符的,所以在写文件的时候,都输出成Unicode编码的形式。
2。在JDK将Java文件编译成Class文件的时候
可以利用-encoding参数指定源文件的编码,这在手动编译的时候非常重要,因为这决定了Java虚拟机读取Java文件时采用的编码方式,但是在Web应用中这个环节我们可以忽略,因为应用服务器可以很好的解决这个编码。以Tomcat为例,由于生成的java文件是固定的UTF-8编码,所以Tomcat也固定的采用UTF-8编码来读取,通过浏览AbstractCatalinaTask可以看到reader = new InputStreamReader(hconn.getInputStream(), CHARSET);其中的CHARSET=utf-8。所以在这个环节中应用服务器都可以很好的把握,不会带来编码问题。
3. 用户的浏览器请求JSP对应的Servlet阶段。
如果前面的环节中不会带来编码问题,也就是说在Java虚拟机中运行的时候,能正常的获取到“中国”,那么在执行servlet的环节中不会“中国”始终是以Unicode存储的中国,那么在第三个环节中需要关注的是JspWriter如何将数据返回给客户端浏览器。大家可以试验一下,在java中如果用new String(str.getBytes("encoding"),"encoding")执行的时候,始终不会出现乱码问题,也就是说,一个字符串可以用不同的代码来getBytes()生成字节数组(底层I18N.jar所作的工作,提供Byte2Char和Char2Byte的转换)。
如果大家可以理解这一点,那么下面大家就需要了解JspWriter输出字符串时采用的编码方式是什么?通过浏览Response.java类可以了解到Tomcat应用服务器是根据contentType来获取的writer的编码方式,也就是说,最后返回客户端的字节流是contentType对应的charset中获取出来的字节数组。
4. IE根据返回的数据处理显示阶段
通过前面的分析可以了解到,应用服务器返回的“中国”是根据ContentType中的charset来显示的,只要IE知道该用这个编码来接收字节流并转成字符串,并将用户的浏览器推荐合适的编码来查看结果,用户就可以浏览到正确的“中国”两个字。可以高兴得是,目前的IE等浏览器正式这样处理的。
Conclusion
通过上面的分析,我们可以看到,在整个JSP页面的编码过程中,我们真正要解决的是JSP文件到Java文件这个过程中的编码问题,也就是PageEncoding参数的设置问题。由于pageEncoding参数是servlet2.3规范中规定的参数,所以下面的方法在很多应用服务器下面都通用,这方面的设置本人在工作中基本上得到了下面的一些方法:
1。在JSP页面的中加上pageEncoding参数,比如:<%@ page contentType="text/html; charset=UTF-8" pageEncoding="GBK"%>,这样就可以将页面可以用ANSI来存储。也就是说当页面存储的编码方式和chtentType中的charset不一样的时候,可以考虑加上pageEncoding参数。
2。有些应用服务器(如weblogic),在没有获取到pageEncoding参数的时候,不是先从charset中获取编码类型,而是从另外的一些配置文件,如weblogic.xml文件中加上下面的代码:
<jsp-descriptor>
<jsp-param>
<param-name>compilerSupports</param-name>
<param-value>true</param-value>
</jsp-param>
<jsp-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</jsp-param>
</jsp-descriptor>
(在Tomcat5X种也有类似的处理,在应用的web.xml文件中加上类似下面的配置项)
</jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
以上是对JSP页面编码的一些分析和处理方法,希望能对大家今后的学习和工作中有帮助!
ApplicationContext是Spring的核心,Context我们通常解释为上下文环境,我想用“容器”来表述它更容易理解一些,ApplicationContext则是“应用的容器”了:P,Spring把Bean放在这个容器中,在需要的时候,用getBean方法取出,虽然我没有看过这一部分的源代码,但我想它应该是一个类似Map的结构。
在Web应用中,我们会用到WebApplicationContext,WebApplicationContext继承自ApplicationContext,先让我们看看在Web应用中,怎么初始化WebApplicationContext,在web.xml中定义:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- OR USE THE CONTEXTLOADERSERVLET INSTEAD OF THE LISTENER
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
-->
可以看出,有两种方法,一个是用ContextLoaderListener这个Listerner,另一个是ContextLoaderServlet这个Servlet,这两个方法都是在web应用启动的时候来初始化WebApplicationContext,我个人认为Listerner要比Servlet更好一些,因为Listerner监听应用的启动和结束,而Servlet得启动要稍微延迟一些,如果在这时要做一些业务的操作,启动的前后顺序是有影响的。
那么在ContextLoaderListener和ContextLoaderServlet中到底做了什么呢?
以ContextLoaderListener为例,我们可以看到
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
protected ContextLoader createContextLoader() {
return new ContextLoader();
}
ContextLoader是一个工具类,用来初始化WebApplicationContext,其主要方法就是initWebApplicationContext,我们继续追踪initWebApplicationContext这个方法(具体代码我不贴出,大家可以看Spring中的源码),我们发现,原来ContextLoader是把WebApplicationContext(XmlWebApplicationContext是默认实现类)放在了ServletContext中,ServletContext也是一个“容器”,也是一个类似Map的结构,而WebApplicationContext在ServletContext中的KEY就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我们如果要使用WebApplicationContext则需要从ServletContext取出,Spring提供了一个WebApplicationContextUtils类,可以方便的取出WebApplicationContext,只要把ServletContext传入就可以了。
上面我们介绍了WebApplicationContext在Servlet容器中初始化的原理,一般的Web应用就可以轻松的使用了,但是,随着Struts的广泛应用,把Struts和Spring整个起来,是一个需要面对的问题,Spring本身也提供了Struts的相关类,主要使用的有org.springframework.web.struts.ActionSupport,我们只要把自己的Action继承自ActionSupport,就是可以调用ActionSupport中getWebApplicationContext()的方法取出WebApplicationContext,但这样一来在Action中,需要取得业务逻辑的地方都要getBean,看上去不够简洁,所以Spring又提供了另一个方法,用org.springframework.web.struts.ContextLoaderPlugIn,这是一个Struts的Plug,在Struts启动时加载,对于Action,可以像管理Bean一样来管理,在struts-config.xml中Action的配置变成类似下面的样子
<action attribute="aForm" name="aForm" path="/aAction" scope="request" type="org.springframework.web.struts.DelegatingActionProxy">
<forward name="forward" path="forward.jsp" />
</action>
注意type变成了org.springframework.web.struts.DelegatingActionProxy,之后我们需要建立action-servlet.xml这样的文件,action-servlet.xml符合Spring的spring-beans.dtd标准,在里面定义类似下面的
<bean name="/aAction" class="com.web.action.Aaction" singleton="false">
<property name="businessService">
<ref bean="businessService"/>
</property>
</bean>
com.web.action.Aaction是Action的实现类,businessService是需要的业务逻辑,Spring会把businessService注入到Action中,在Action中只要写businessService的get和set方法就可以了,还有一点,action的bean是singleton="false",即每次新建一个实例,这也解决了Struts中Action的线程同步问题,具体过程是当用户做“/aAction”的HTTP请求(当然应该是“/aAction.do”),Struts会找到这个Action的对应类org.springframework.web.struts.DelegatingActionProxy,DelegatingActionProxy是个代理类,它会去找action-servlet.xml文件中“/aAction”对应的真正实现类,然后把它实例化,同时把需要的业务对象注入,然后执行Action的execute方法。
使用了ContextLoaderPlugIn,在struts-config.xml中变成类似这样配置
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml" />
</plug-in>
而在web.xml中不再需要ContextLoaderListener或是ContextLoaderServlet。
说到这里不知道大家会不会有这样的问题,如果使用ContextLoaderPlugIn,如果我们有些程序是脱离Struts的Action环境,我们怎么处理,比如我们要自定义标记库,在标记库中,我们需要调用Spring管理的业务层逻辑对象,这时候我们就很麻烦,因为只有在action中动态注入业务逻辑,其他我们似乎不能取得Spring的WebApplicationContext。
别急,我们还是来看一下ContextLoaderPlugIn的源码(源码不再贴出),我们可以发现,原来ContextLoaderPlugIn仍然是把WebApplicationContext放在ServletContext中,只是这个KEY不太一样了,这个KEY值为ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX+ModuleConfig.getPrefix()(具体请查看源代码),这下好了,我们知道了WebApplicationContext放在哪里,只要我们在Web应用中能够取到ServletContext也就能取到WebApplicationContext了:)
假设表单为formPost,上传图像域为photo_file,允许上传的图像类型为jpg, bmp, gif
if (document.formPost.photo_file.value.substr(document.formPost.photo_file.value.length - 4, 4).toLowerCase().match("^.jpg|.bmp|.gif$") == null)
{
alert("图像类型不是JPG,BMP,GIF类型");
}
雨一直下,春节临近,回家的车票也没买到,身心处在一种难言的状态~
某个大虾的代码:
这个Upload比smartUpload好用多了.完全是我一个个byte调试出来的,不象smartUpload的bug具多.
调用方法:
Upload up = new Upload();
up.init(request);
/**
此处可以调用setSaveDir(String saveDir);设置保存路径
调用setMaxFileSize(long size)设置上传文件的最大字节.
调用setTagFileName(String)设置上传后文件的名字(只对第一个文件有效)
*/
up. uploadFile();
然后String[] names = up.getFileName();得到上传的文件名,文件绝对路径应该是
保存的目录saveDir+"/"+names[i];
可以通过up.getParameter("field");得到上传的文本或up.getParameterValues("filed")
得到同名字段如多个checkBox的值.
其它的自己试试.
源码:____________________________________________________________
package com.inmsg.beans;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class Upload {
private String saveDir = "."; //要保存文件的路径
private String contentType = ""; //文档类型
private String charset = ""; //字符集
private ArrayList tmpFileName = new ArrayList(); //临时存放文件名的数据结构
private Hashtable parameter = new Hashtable(); //存放参数名和值的数据结构
private ServletContext context; //程序上下文,用于初始化
private HttpServletRequest request; //用于传入请求对象的实例
private String boundary = ""; //内存数据的分隔符
private int len = 0; //每次从内在中实际读到的字节长度
private String queryString;
private int count; //上载的文件总数
private String[] fileName; //上载的文件名数组
private long maxFileSize = 1024 * 1024 * 10; //最大文件上载字节;
private String tagFileName = "";
public final void init(HttpServletRequest request) throws ServletException {
this.request = request;
boundary = request.getContentType().substring(30); //得到内存中数据分界符
queryString = request.getQueryString();
}
public String getParameter(String s) { //用于得到指定字段的参数值,重写request.getParameter(String s)
if (parameter.isEmpty()) {
return null;
}
return (String) parameter.get(s);
}
public String[] getParameterValues(String s) { //用于得到指定同名字段的参数数组,重写request.getParameterValues(String s)
ArrayList al = new ArrayList();
if (parameter.isEmpty()) {
return null;
}
Enumeration e = parameter.keys();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
if ( -1 != key.indexOf(s + "||||||||||") || key.equals(s)) {
al.add(parameter.get(key));
}
}
if (al.size() == 0) {
return null;
}
String[] value = new String[al.size()];
for (int i = 0; i < value.length; i++) {
value[i] = (String) al.get(i);
}
return value;
}
public String getQueryString() {
return queryString;
}
public int getCount() {
return count;
}
public String[] getFileName() {
return fileName;
}
public void setMaxFileSize(long size) {
maxFileSize = size;
}
public void setTagFileName(String filename) {
tagFileName = filename;
}
public void setSaveDir(String saveDir) { //设置上载文件要保存的路径
this.saveDir = saveDir;
File testdir = new File(saveDir); //为了保证目录存在,如果没有则新建该目录
if (!testdir.exists()) {
testdir.mkdirs();
}
}
public void setCharset(String charset) { //设置字符集
this.charset = charset;
}
public boolean uploadFile() throws ServletException, IOException { //用户调用的上载方法
setCharset(request.getCharacterEncoding());
return uploadFile(request.getInputStream());
}
private boolean uploadFile(ServletInputStream servletinputstream) throws //取得央存数据的主方法
ServletException, IOException {
String line = null;
byte[] buffer = new byte[256];
while ( (line = readLine(buffer, servletinputstream, charset)) != null) {
if (line.startsWith("Content-Disposition: form-data;")) {
int i = line.indexOf("filename=");
if (i >= 0) { //如果一段分界符内的描述中有filename=,说明是文件的编码内容
String fName = getFileName(line);
if (fName.equals("")) {
continue;
}
if (count == 0 && tagFileName.length() != 0) {
String ext = fName.substring( (fName.lastIndexOf(".") + 1));
fName = tagFileName + "." + ext;
}
tmpFileName.add(fName);
count++;
while ( (line = readLine(buffer, servletinputstream, charset)) != null) {
if (line.length() <= 2) {
break;
}
}
File f = new File(saveDir, fName);
FileOutputStream dos = new FileOutputStream(f);
long size = 0l;
while ( (line = readLine(buffer, servletinputstream, null)) != null) {
if (line.indexOf(boundary) != -1) {
break;
}
size += len;
if (size > maxFileSize) {
throw new IOException("文件超过" + maxFileSize + "字节!");
}
dos.write(buffer, 0, len);
}
dos.close();
}
else { //否则是字段编码的内容
String key = getKey(line);
String value = "";
while ( (line = readLine(buffer, servletinputstream, charset)) != null) {
if (line.length() <= 2) {
break;
}
}
while ( (line = readLine(buffer, servletinputstream, charset)) != null) {
if (line.indexOf(boundary) != -1) {
break;
}
value += line;
}
put(key, value.trim(), parameter);
}
}
}
if (queryString != null) {
String[] each = split(queryString, "&");
for (int k = 0; k < each.length; k++) {
String[] nv = split(each[k], "=");
if (nv.length == 2) {
put(nv[0], nv[1], parameter);
}
}
}
fileName = new String[tmpFileName.size()];
for (int k = 0; k < fileName.length; k++) {
fileName[k] = (String) tmpFileName.get(k); //把ArrayList中临时文件名倒入数据中供用户调用
}
if (fileName.length == 0) {
return false; //如果fileName数据为空说明没有上载任何文件
}
return true;
}
private void put(String key, String value, Hashtable ht) {
if (!ht.containsKey(key)) {
ht.put(key, value);
}
else { //如果已经有了同名的KEY,就要把当前的key更名,同时要注意不能构成和KEY同名
try {
Thread.currentThread().sleep(1); //为了不在同一ms中产生两个相同的key
}
catch (Exception e) {}
key += "||||||||||" + System.currentTimeMillis();
ht.put(key, value);
}
}
/*
调用ServletInputstream.readLine(byte[] b,int offset,length)方法,该方法是从ServletInputstream流中读一行
到指定的byte数组,为了保证能够容纳一行,该byte[]b不应该小于256,重写的readLine中,调用了一个成员变量len为
实际读到的字节数(有的行不满256),则在文件内容写入时应该从byte数组中写入这个len长度的字节而不是整个byte[]
的长度,但重写的这个方法返回的是String以便分析实际内容,不能返回len,所以把len设为成员变量,在每次读操作时
把实际长度赋给它.
也就是说在处理到文件的内容时数据既要以String形式返回以便分析开始和结束标记,又要同时以byte[]的形式写到文件
输出流中.
*/
private String readLine(byte[] Linebyte,
ServletInputStream servletinputstream, String charset) {
try {
len = servletinputstream.readLine(Linebyte, 0, Linebyte.length);
if (len == -1) {
return null;
}
if (charset == null) {
return new String(Linebyte, 0, len);
}
else {
return new String(Linebyte, 0, len, charset);
}
}
catch (Exception _ex) {
return null;
}
}
private String getFileName(String line) { //从描述字符串中分离出文件名
if (line == null) {
return "";
}
int i = line.indexOf("filename=");
line = line.substring(i + 9).trim();
i = line.lastIndexOf("\\");
if (i < 0 || i >= line.length() - 1) {
i = line.lastIndexOf("/");
if (line.equals("\"\"")) {
return "";
}
if (i < 0 || i >= line.length() - 1) {
return line;
}
}
return line.substring(i + 1, line.length() - 1);
}
private String getKey(String line) { //从描述字符串中分离出字段名
if (line == null) {
return "";
}
int i = line.indexOf("name=");
line = line.substring(i + 5).trim();
return line.substring(1, line.length() - 1);
}
public static String[] split(String strOb, String mark) {
if (strOb == null) {
return null;
}
StringTokenizer st = new StringTokenizer(strOb, mark);
ArrayList tmp = new ArrayList();
while (st.hasMoreTokens()) {
tmp.add(st.nextToken());
}
String[] strArr = new String[tmp.size()];
for (int i = 0; i < tmp.size(); i++) {
strArr[i] = (String) tmp.get(i);
}
return strArr;
}
}
下载其实非常简单,只要如下处理,就不会发生问题。
public void downLoad(String filePath,HttpServletResponse response,boolean isOnLine)
throws Exception{
File f = new File(filePath);
if(!f.exists()){
response.sendError(404,"File not found!");
return;
}
BufferedInputStream br = new BufferedInputStream(new FileInputStream(f));
byte[] buf = new byte[1024];
int len = 0;
response.reset(); //非常重要
if(isOnLine){ //在线打开方式
URL u = new URL("file:///"+filePath);
response.setContentType(u.openConnection().getContentType());
response.setHeader("Content-Disposition", "inline; filename="+f.getName());
//文件名应该编码成UTF-8
}
else{ //纯下载方式
response.setContentType("application/x-msdownload");
response.setHeader("Content-Disposition", "attachment; filename=" + f.getName());
}
OutputStream out = response.getOutputStream();
while((len = br.read(buf)) >0)
out.write(buf,0,len);
br.close();
out.close();
}