|
2006年3月31日
1. java zip 多个文件时,如果先添加了一个excel文件,然后再想添加其他的文件时会出现 steam is closed的错误。这是因为work.write(outputSteam)后,出调用outputSteam.close(),关闭输出流。 解决方法: 将原来的程序: ZipEntry entry = new ZipEntry( "file3.txt" ); zos.putNextEntry( entry ); workbook.write( zos ); zos.closeEntry(); 改为: ZipEntry entry = new ZipEntry( "file3.txt" ); zos.putNextEntry( entry ); workbook.write( new NonCloseableOutputStream( zos ) ); zos.closeEntry(); 其中 NonCloseableOutputStream 定义如下: public class NonCloseableOutputStream extends java.io.FilterOutputStream { public NonCloseableOutputStream(OutputStream out) { super(out); } @Override public void close() throws IOException { flush(); } } 2. 使用binary使得mysql区分大小写 select * from table1 where binary field1 = 'abc';
https://notepad-plus-plus.org/community/topic/13661/plugin-manager-x64-available-submit-your-plugins
move Git Server to a new IP/URL:
you can just edit .git/config and change the URLs there
也可以在git视图中,右键点击项目,选择属性,然后修改url中的地址
autohotkey listary cmder可以split screen,在一个窗口中同时运行数个cmd
官网地址:autohotkey.com ; fill password ^Numpad2:: Send, root{tab}root{enter} Return ^Numpad3:: IfWinExist, ahk_exe OUTLOOK.EXE { WinActivate ahk_exe OUTLOOK.EXE ; Automatically uses the window found above. ; WinMaximize ; same ;Send, Some text.{Enter} msgbox Outlook is running. } Return
<html> <head> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script> window.onload = function () { var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } }); } </script> </head>
<body> <div id="app"> {{ message }} </div> </body> </html>
String[] splits=someString.split("a,b,c,d", ","); logger.debug( "array: {}", (Object) splits ); 这里要注意的就是要把数组的数据类型强制转换为Object
在windows环境中,可以用如下方法重置root密码 1、先停止mysql数据库 2、保存密码重置sql文件 5.7.6(包括)以后的版本:ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass'; 5.7.5(包括)以前的版本:SET PASSWORD FOR 'root'@'localhost' = PASSWORD('MyNewPass'); 假设保存到文件: c:\reset.txt 3、以管理员身份打开命令行窗口,运行 C:\> cd "C:\Program Files\MySQL\MySQL Server 5.5\bin" C:\> mysqld --init-file=C:\reset.txt
4、启动后,还不能马上用新密码连接数据库,需要重启mysql数据库
This is a general step that happens when m2e/m2eclipse (Maven integration for Eclipse) is installed, whether projects are actively using it or not. 这是因为m2eclipse(maven插件)要在启动时需要进行的一个步骤。 This step can be disabled through the Eclipse preferences: Window / Preferences / Maven / "Download repository index updates on startup". This option is on the main "Maven" preference page (not a child page). Just uncheck the box to prevent this from happening. 我们可以停止这个动作。方法:Windows -> Preferences -> Maven 取消勾选 Download repository index updates on startup
有好几个java library都可以实现这个功能,但是从pdf提取文本的一个问题是,提取出来的文本没有固定的顺序,不容易比较好的还原其格式。
我的做法是使用pdfclown来进行这项工作。官方网站是:https://pdfclown.org/ 先下载其最新版本。 参考其示例代码:https://pdfclown.org/2010/01/02/upcoming-0-0-8-whats-going-to-be-new/#more-30
使用这段代码,我们不仅可以得到文本的字符串,还能得到文本的页数和相对坐标。 我的思路是先把所有文本的字符串和坐标提取出来。然后排序,排序的顺序是纵坐标,然后横坐标。 这样排序完毕后,就能比较好的解决文本格式问题。
1, 先定义一个input, 做为datepicker的容器。 <input type='text' class="form-control" id="dateTo" name="dateTo" required/>
2, 在后面加上glyphicon, 注意关键是label 中的for的id需要是前面定义的容器的id, 这样点击glyphicon的时候就会触发弹出日期选择框。
<label for="dateTo" class="input-group-addon"><span class="glyphicon glyphicon-time"></span></label>
在日志文件中看到这个错误信息 Cause: java.sql.SQLException: #HY000
后来才知道这是因为数据库中有个别字段要求不能为空, 但是insert语句中没有提供数据,造成了这个错误。 关键是错误信息不明确直观,不容易知道是这个原因
public void afterJFinalStart(){ Configuration config = FreeMarkerRender.getConfiguration(); config.setTemplateUpdateDelayMilliseconds( 2 ); config.setAPIBuiltinEnabled( true ); }
中文版地址 https://angular.cn/
1, call ##002# to cancel "call diversion"
2, call 121600, choose option "2" to cancel "Active call catcher"
1. 格式化XML的插件 可以安装“XML Tools", 安装完毕后,选择 插件->XML Tools->Pretty Print(XML Only - with line breaks)
2. 格式化JSON的插件 可以安装”JSON Viewer", 安装完毕后,选择 插件->JSON Viewer->Format JSON
3. 格式化SQL的插件 可以安装“Poor man's T-Sql Formatter", 选择 插件->Poor man's T-Sql Formatter->Format T-Sql Code
使用的工具
1. Apache HttpClient 2. Firefox + FireBug 3. Burp Suite ( https://portswigger.net/burp ) + Firefox FoxyProxy
Firefox + FireBug 主要用于查看渲染出的页面中的信息(比如:表单项的名称,节点ID等等) Burp Suite 主要用于动态拦截页面的交互,查看Ajax的调用。 HttpClient 用于最后程序的编制。搞清楚了网页交互的过程,就可以自主决定程序需要包含的内容。 在实际网页中,可能需要点开数级菜单,才能最后看到需要的内容。 但是在程序中,可以直接跳到最后一步。
1. 表格文字右对齐
<table>
<tr>
<td><p style="text-align:right;margin:0;padding:0">文字右对齐</p></td>
<td>文字左对齐</td>
</tr>
</table>
2. 表格边缘的margin
需要在表格外再套一个div
<div style="margin:10px"> <table>
...... </table>
</div> 3. btn-toolbar class can put a margin between 2 "pull-right" buttons <div class="row"> <div class="col-md-2"></div> <div class="col-md-8 btn-toolbar"> <input type="submit" class="btn btn-warning pull-right" value="Submit"> <input type="button" id="profilePassBackBtn" class="btn btn-info pull-right" value="Back"> </div> <div class="col-md-2"> </div> </div>
AngularJS 2.0 已经发布了Beta版本,相信正式版不久以后就会发布了。
下面是官网上的新功能介绍:
1. 更快更高效。AngularJS 2 将会比 AnuglarJS 1 快很多。因为它会支持:从远程胳快速加载、离线编译以便于更快启动、以及超快的变动检测和为使滚动更平滑的视图缓存等等。
2. 更加简单清晰。语法将会显得更加自然,易于编写
3. 跨越平台。无论是台式机、手机浏览器、安卓、IOS平台,AngularJS都能提供相应的支持。
4. 无缝从 AngularJS 1 升级到 2
5. 简便的开发。支持各种开发语言,ES5, TypeScript, Dart
6. 全面完备的路由。 方便地映射URL到应用组件,并提供多种高级功能,比如:嵌套和邻接路由,支持卡片栈导航、动画过渡、手机用户延迟加载等等
7. 依赖注入。
8. 旧浏览器的良好支持
9. 动画效果 (仍在开发中)
10. 国际化支持(仍在开发中)
- Go to web project properties.
- Deployment Assembly (Left).
- Add > Select project > Select your lib project > Check "Assemble projects into the WEB-INF/lib folder of the web application" if not checked > Finish.
使用酷狗就可以转换。 右键点击歌曲 ,工具,格式转换。 唯一要注意的是要先登录。
今天把commons dbcp 和 pool都升级到2.x, 结果发现不能正常的工作,卡在new BasicDataSource()上了. 后来才发现原因是因为没有加入commons-logging的jar文件 几个注意点: 1. commons dbcp2.x 和 commons pool需要同时升到2.x 2. dbcp 2.x要运行在java 7以上 3. mysql connector要5.1.11以上 4. 需要有commons-logging的包,我使用的是slf4j, 就需要加一个jcl-over-slf4j
Error com.jcraft.jsch.JSchException: The cipher 'aes256-cbc' is required, but it is not available. or
Caused by: java.security.InvalidKeyException: Illegal key size
我在网上搜索了一下如何使用Selenium下载文件,其中确实有几篇文件介绍了实现的方法。 但是其主要思想都是使用httpClient或者URL获得InputStream, 然后保存到文件中。 但是,其中的问题是用户登录的Session不能维持。
我发现了一个简单的方法。 直接使用WebDriver.get, 示例如下:
webDriver.get("https://website.com/login"); WebElement element = driver.findElement( By.id( "userID" ) ); element.sendKeys( "user01" );
element = driver.findElement( By.id( "passwd" ) ); element.sendKeys( "password" );
element = driver.findElement( By.name( "Login" ) ); element.submit();
webDriver.get("https://website.cm/download.do?start=xx&end=yy"); String source = webDriver.getPageSource();
这个source就是我们想保存的要下载的内容。 只要把这个String写到一个文件中,就实现了文件下载的目的
摘要: 在我的上一篇文章中介绍了如何进行GPG加密解密。
加密解密的基本操作流程是,用户使用公钥对明文进行加密,解密方使用私钥对密文进行解密。
在实际应用中,除了加密保证文本内容不泄露外,同时还要考虑能够验证密文发送方的身份,比较普遍使用的方法就是签名。
本文主要对具体的方法进行介绍并附上源代码。 阅读全文
Java程序中访问拥有全部读写权限的目录相对比较简单,和普通的目录没有什么差别。 但是要访问一个需要用户和密码验证的目录就需要一点点小技巧了。 这里介绍一个开源的库能够比较容易的实现这一需求。 1。 下载库文件: https://jcifs.samba.org/ 下载的zip文件中, 不仅包含了jar文件,还有文档和示例。 2。拷贝jcif-1.3.18.jar到类路径中。 3。代码示例: 1 String user = "your_user_name"; 2 String pass ="your_pass_word"; 3 4 String sharedFolder="shared"; 5 String path="smb://ip_address/"+sharedFolder+"/test.txt"; 6 NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication("",user, pass); 7 SmbFile smbFile = new SmbFile(path,auth); 8 SmbFileOutputStream smbfos = new SmbFileOutputStream(smbFile); 9 smbfos.write("testing.and writing to a file".getBytes()); 10 System.out.println("completed nice !"); 说明: 如果有一个共享目录,比如: \\192.168.1.2\testdir\ 那么smb的路径就是:smb://192.168.1.2/testdir/ NtlmPasswordAuthentication需要三个参数, 第一个是域名,没有的话,填null, 第二个是用户名,第三个是密码
得到SmbFile之后,操作就和java.io.File基本一样了。 另外还有一些功能比如: SmbFile.copyTo SmbFile.renameTo 等等
先将my.default.ini改名为my.ini放到bin目录
命令行执行: mysqld --initialize --user=mysql --console
先执行以上命令, 生成库. 注意有个临时密码, 要记下来. 安装服务:mysqld.exe --install MySql5.7 --defaults-file=c:\mysql\mysql5.7\my.ini
然后启动服务.
然后再命令行: mysql -uroot -p 输入密码, 再输入: set password = password('root') 改密码成功, 然后就可以操作了.
如果只是在beforeSubmit()中 调用$('#fieldname').val(2)是不能成功修改表单的值的。 因为此时ajaxForm已经把表单中所有的内容存储在arr之中了。 $('#form1').ajaxForm({ beforeSubmit: function(arr){ for ( var i = 0; i < arr.length; i ++ ) { if ( arr[i].name == "fieldName1" ) { arr[i].value = '新的值'; } } } }); 需要使用这种方式进行修改。
今天在运行myeclipse的时候,突然报nullPointerException. 具体的错误信息如下: Message: Errors running builder ‘DeploymentBuilder’ on project XXX’. Exception Stack Trace java.lang.NullPointerException 解决方法: 1. Shut down the workspace. 2. Delete the file com.genuitec.eclipse.ast.deploy.core.prefs which is located at <workspace dir>/.metadata/.plugins/org.eclipse.core.runtime/.settings/com.genuitec.eclipse.ast.deploy.core.prefs 3. Start the IDE.
ipconfig /flushdns ipconfig /registerdns netsh winsock reset
重新启动电脑。
今天下载了Apache James 3.0 Beta 5, 文件名:james-server-app-3.0.0-beta5-20150627.102412-1076-app.zip 解压,运行run.bat 然后,注册domain james-cli --host localhost adddomain example.com 添加用户 james-cli.bat --host localhost adduser test@example.com password 然后测试发送邮件,客户端显示发送成功,但是james服务器报错,找不到MimeConfig的无参数构造函数。 解决方法: 使用旧的mime4j的jar包替换james 3.0 beta5中自带的最新包。 beta5中自带的是0.8.0版,apache网站中可以下载到0.7.2 下载apache-mime4j-0.7.2-bin.zip, 将其中的apache-mime4j-core-0.7.2.jar, apache-mime4j-dom-0.7.2.jar复制到james\lib目录, 并将其更名覆盖原有的 apache-mime4j-core-0.8.0-20150617.024907-738.jar apache-mime4j-dom-0.8.0-20150617.024927-735.jar 重新启动james, 发送邮件, 成功。
摘要: 解压/生成有密码保护的压缩文件, 研发过程中,作者研究了压缩文件格式文档: http://www.pkware.com/documents/casestudies/APPNOTE.TXT,并且参考了7-zip的实现。
阅读全文
摘要: 花了两天时间终于把windows10安装好了,以下是我的一些个人的体会
阅读全文
在JfinalConfig的继承类中, configConstant() 需要设置me.setDevMode(true); 1. 只有在DevMode下,才能禁止freeMarker的缓存。 Configuration config = FreeMarkerRender.getConfiguration(); config.setTemplateUpdateDelayMilliseconds(0); 才会生效
2. 这时才会有JFinal Action Report日志输出
本文将简单介绍如何使用PowerMock和Mockito来mock
1. 构造函数
2. 静态函数
3. 枚举实现的单例
4. 选择参数值做为函数的返回值
5. 在调用mock出来的方法中,改变方法参数的值
一点简要说明:Mockito其实已经可以满足大部分的需求,但是它的实现机制是使用cglib来动态创建接口的类的实例。但是这种实现方式不能用于构造函数和静态函数,因为那需要使用类的字节码(比如使用javassist). 所以我们才需要结合使用PowerMock.
1. mock构造函数, 如果有代码没有使用DI注入依赖实例,在单元测试中可以使用PowerMock来模拟创建对象。
注意的开始两行的2个注解 @RunWith 和 @PrepareForTest
@RunWith比较简单,后面始终是PowerMockRunner.class
@PrepareForText后面需要加的是调用构造函数的类名,而不是有构造函数的类本身。
在下面的例子中,我们要测试的类是:Helper, 在Helper类中调用了Somthing类的构造函数来创建实例。
@RunWith(PowerMockRunner.class)
@PrepareForTest(Helper.class)
public class HelperTest {
@Mock
private Something mockSomething;
@InjectMocks
private Helper helper;
@Test
public void doSomething() throws Exception {
String argument = "arg";
PowerMockito.whenNew(Something.class).withArguments(argument).thenReturn(mockSomething);
// 调用需要测试方法
helper.doSomething(argument);
// 进行验证
verify(mockSomething).doIt();
}
}
public class Helper {
public void doSomething(String arg) {
Something something = new Something(arg);
something.doit();
}
}
2,mock 静态函数, 单例模式就是一个典型的会调用静态函数的例子。 注意要点与mock构造函数相同。
class ClassWithStatics {
public static String getString() {
return "String";
}
public static int getInt() {
return 1;
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassWithStatics.class)
public class StubJustOneStatic {
@Test
public void test() {
PowerMockito.mockStatic(ClassWithStatics.class);
when(ClassWithStatics.getString()).thenReturn("Hello!");
System.out.println("String: " + ClassWithStatics.getString());
System.out.println("Int: " + ClassWithStatics.getInt());
}
}
3。mock枚举实现的单例
SingletonObject.java
public enum SingletonObject { INSTANCE; private int num; protected void setNum(int num) { this.num = num; } public int getNum() { return num; } }
SingletonConsumer.java
public class SingletonConsumer {
public String consumeSingletonObject() {
return String.valueOf(SingletonObject.INSTANCE.getNum());
} }
SingletonConsumerTest.java
@RunWith(PowerMockRunner.class) @PrepareForTest({SingletonObject.class}) public class SingletonConsumerTest { @Test public void testConsumeSingletonObject() throws Exception { SingletonObject mockInstance = mock(SingletonObject.class); Whitebox.setInternalState(SingletonObject.class, "INSTANCE", mockInstance); when(mockInstance.getNum()).thenReturn(42); assertEquals("42", new SingletonConsumer().consumeSingletonObject()); } }
4。返回参数值做为函数返回值。
mockito 1.9.5之后,提供一个方便的方法来实现这个需要,在这之前可以使用一个匿名函数来返回一个answer来实现。
when(myMock.myFunction(anyString())).then(returnsFirstArg());
其中returnsFirstArg()是org.mockito.AdditionalAnswers中的一个静态方法。
在这个类中还有其他的一些类似方法
returnsSecondArg()
returnsLastArg()
ReturnsArgumentAt(int position)
5. 在调用mock出来的方法中,改变方法参数的值
when( myMock.someMethod( any( List.class ) ) ).thenAnswer( ( new Answer<Void>() {
@Override
public Void answer( InvocationOnMock invocation )
throws Throwable {
Object[] args = invocation.getArguments();
List arg1 = (List)args[0];
arg1.add("12345");
return null;
}
} ) );
Verifying with generic parameters
verify(someService).process(Matchers.<Collection<Person>>any());
verify(adunoMasterBaseProcessor).processBinFiles( anyListOf(File.class) );
Oracle提供的JDK其实已经自带一定程度的热加载功能,但是如果你修改了类名,方法名,或者添加了新类,新方法的话。 Tomcat都需要重新启动来使得刚才的更改生效。 而JRebel和springloaded都能有效地解决这个问题。其中springloaded是开源软件,可以免费使用,尤其难得。 其主页:https://github.com/spring-projects/spring-loaded 在官方页面的简单介绍中,作者只讲述了如何在java程序中应用springloaded,而没有说明如何在tomcat中进行配置。 本文将简要进行介绍。 1,下载springloaded到本地目录,比如:c:\temp\springloaded-1.2.3.RELEASE.jar 2. 修改tomcat的应用,禁止tomcat自己的热加载,方法是在META-INF目录下创建context.xml文件,里面包含如下语句,关键便是其中设置reloadable为false <?xml version="1.0" encoding="UTF-8"?> <Context antiResourceLocking="false" privileged="true" useHttpOnly="true" reloadable="false" /> 3.在运行环境中添加springloaded的jar文件,在eclipse中右键点击项目,run as->run configuration 在弹出的窗口中,选择Arguments标签,在vm arguments的末尾添加: -javaagent:C:\temp\springloaded-1.2.3.RELEASE.jar -noverify 点击应用按钮。 以上便完成了所有的配置,步骤并不复杂。
java wrapper是一个可以用于将java应用程序包装成windows服务的工具。 并且可以通过简单的配置来允许使用visualVM进行监控。 配置方法: 在wrapper.conf中添加如下3行 wrapper.java.additional.1=-Dcom.sun.management.jmxremote.port=9898 #这里的端口号可以自行选择。 wrapper.java.additional.2=-Dcom.sun.management.jmxremote.ssl=false wrapper.java.additional.3=-Dcom.sun.management.jmxremote.authenticate=false 修改完毕保存后重新启动服务。 打开visualVM, 在菜单中选择 file->Add JMX Connection。 在弹出窗口中,connection一项中输入: localhost:9898 即可。 此配置对于jconsole也同样有效。
在一些历史遗留代码中,会用到java.util.logging. 如果在新的项目中引用了这些代码,而又不希望去一个一个的修改原来的代码。 可以使用slf4j提供的类来转接这部分的日志输出。 方法: 1、类路径中添加 slf4j-api-1.7.10.jar jul-to-slf4j.1.7.10.jar ( 用于将java.util.logging的日志桥接到slf4j中) logback-core.1.1.2.jar logback-classic-1.1.2.jar 2、在代码中添加: // Optionally remove existing handlers attached to j.u.l root logger SLF4JBridgeHandler.removeHandlersForRootLogger(); // (since SLF4J 1.6.5)
// add SLF4JBridgeHandler to j.u.l's root logger, should be done once during // the initialization phase of your application SLF4JBridgeHandler.install(); 注意事项: 1、这个桥接可以会造成性能问题。 和其他的桥接实现(比如:log4j, commons logging)不同,这个模块并不真正的完全替代java.util.logging类,因为这个java.util.logging是java自带的。 所以只是把原来的日志对象进行了转换,简单的说,这个转换过程是有开销的。 关键在于,不管日志语句有没有根据日志级别被关闭,这个转换无法避免。 2、不能在类路径中放入 slf4j-jkd14.jar jul-toslf4j.jar
1. Text Editor: Notepad++/Syncplify.me Notepad! 2. Browser: Chrome/Firefox 3. 文件管理: XYplorer Lite/Explorer++/Q-Dir 4. Mind map: XMind Free 5. Video player: PotPlayer 6. Music player: Kugou 7. Mysql client: HeidiSql 8. PDF reader: Foxit Reader 9. File/Folder synchronize : FreeFileSync 10. MP3 tools: Audacity/MP3 Gain 11. Zip: 7-zip 12. Partition Management: EaseUS Partition Master Free / MiniTool Free Partition Manager 13. Data Recovery: EaseUS Data Recovery Wizard Free / MiniTool Free Data Recovery 14. PDF Printer: PDF reDirect v2 15. 个人信息管理: EssentialPIM Free Edition 16. 远程登录: Terminals 17. 文本比较合并: winmerge 18. (s)FTP client: WinSCP 19. 图像处理: GIMP
Ember 是一个旨在创建大型 web应用的JavaScript框架,它消除了样板(boilerplate)并提供了标准的应用程序架构。
Manning: Ember.js in action 第一章
Manning: Ember.js in action 第五章
先给一个例子: $http. get('/remote/item' ). then(function(response) { console.log('成功。'); }, function(errResponse) { console. error('出错.' ); });
一。介绍Promise 在这个例子中,$http.get()函数返回了一个Promise对象, 有了这个对象,我们才能很方便地直接在后面添加then函数的定义。 Promise对象在AngularJS中是一个非常重要的存在。它提供了强大的功能和便利性。
1。异步性 从定义的语法上看,操作似乎是同步的,但是Promise的工作其实是异步的,只有在服务端返回数据后,后续的函数才会被调用。这是一个事件驱动,非阻塞式的框架。
2。它避免了其它框架的嵌套回调函数的缺点。 -所有异步任务都会返回一个Promise对象 -每个Promise对象都有一个then函数,then函数有两个参数,分别是成功处理函数和失败处理函数 -失败处理函数和成功处理函数都只会在异步处理完成后被调用一次 -then函数也会返回Promise对象,这样,我们可以把多个函数串连起来成为一个函数链 -成功处理函数和失败处理函数的返回值可以被传递到函数链下一个的函数中 -如果在成功(或者失败)处理函数中,又开始了一个异步调用,那么函数链中的函数将会在这个异步调用结束后才开始
二。异步链式调用的后续处理 假如我们定义了如下的函数链: $http.get('/item').then(s1, e1).then(s2, e2).then(s3, e3); 我们如何自主的根据函数链中每个函数的运行结果,决定触发后续函数的成功处理函数或者失败处理函数呢? 比如说,在s1处理过程中,发生问题,于是我们触发了e2, 但是在e2处理完后,我们又想触发s3. AnguarJS提供了$q来满足这样的需求。 如果我们想触发函数链中下一个函数的成功处理,我们只需要最后给出一个返回值,有了返回值,AngularJS会认为函数执行正确,自动调用下一个函数中的成功处理 如果想触发失败处理,那么可以简单地返回$q.reject(data),这样就会触发下一个函数的失败处理
在前文(http://www.blogjava.net/usherlight/archive/2015/02/01/422633.html)中我们曾经介绍过,定义controller时,需要2个参数,第一个参数是controller的名称,第二个参数是一个数组,数组的最后一个元素将是controller的函数,前面的参数是controller的依赖项。我们现在就来仔细分析一下其中的具体过程。 先给一个例子: angular. module('notesApp' , []) . controller('MainCtrl' , ['$log' , function($log) { var self = this; self. logStuff = function() { $log. log('The button was pressed' ); }; }]) 在这个例子中可以看到,我们在第一个参数中用字符串(服务名称)添加了一个依赖项。当我们通过字符串声明了这一个服务之后,我们就可以把它当作一个变量注入到函数中。AngularJS会自动查找字符串名称对应的服务名,按照顺序将其注入到函数中。 myModule.controller("MainCtrl", ["$log", "$window", function($l, $w) {}]); 在这个例子中,$log, $windows是AngularJS自带的两个服务,在数组中通过名称声明后,会被注入到函数的两个参数中。 比较常用的AngularJS自带的服务有:$window, $location, $http等
从上面的例子中可以看出,AngularJS的设计思想就是不要在函数中自己去实例化或者通过其它途径来获取服务的实例,而是声明需要的对象,由AngularJS来注入具体的实例。
创建自己的服务 什么时候应该创建服务,而不是controller呢? 1。 需要重用的时候 2。需要保留应用级的状态。这是非常重要的一点,controller是会不断地被创建和销毁的,如果需要保存应用级的状态,就需要使用service 3。和页面显示无关 4。需要和第三方服务整合 5。缓存
服务是会被延迟加载的,也就是说只有在第一次被引用的时候,才会被创建。 服务将会被定义一次,也只会被实例化一次。
摘要: 默认情况下,每隔一秒种,SpringLoaded就会扫描类路径,自动加载改变过的类, 而不需要重新启动应用 阅读全文
07. ng-repeart a. 在循环map的时候,会自动根据键值进行排序。 b. 一些自带的变量,$first(是否是第一个), $last(是否是最后一个), $middle(是否是中间的), $index(下标,根据键值排序后的下标), $even, $odd 08. 自己定义新变量时不要使用$$开头。 09. 可以使用track-by表达式来优化对DOM的操作,对DOM对象使用从数据库取得的ID来进行标记,这样的话,当我们重复多次从数据库中取出相同的数据的时候,DOM对象就能够被重用。 10. 数据双向绑定的好处 a. 如果我们想改变页面Form中的数值,我们不需要在Javascript中,根据ID或者名称来查找相应的Form控件,只需要改变Controller变量的值,不需要JQuery的Selector,也不需要findElementByID b. 如果我们想在javascript中获取Form控件的值,在控件的变量中就能直接获得。 11. 使用ng-submit比在button上使用ng-click要好一些。HTML的表单的提交有多种方式,比如在输入域中按回车键就会触发ng-submit,而不会触发button的ng-click事件。 12. 在ng-model中,可以直接引用一个对象,比如:<input type="text" ng-model="ctrl.user.name">,而不需要事先在model中以self.user={}定义。在AngularJS中,使用了ng-model的话,AngularJS在初始化数据绑定的时候,自动创建其中的对象和键值。在刚才的例子中,一旦用户开始在输入域中键入第一个字母,用户user就会被自动创建。 13. 推荐使用将相关数据集中到一个对象的方式来进行数据绑定,比如,用户名和密码,推荐使用: <input type="text" ng-model="ctrl.user.name"> <input type="text" ng-model="ctrl.user.password"> 而不是: <input type="text" ng-model="ctrl.name"> <input type="text" ng-model="ctrl.password">
1. AngularJS的module函数有两种用法,
a. 定义一个module, 需要传入2个参数,module('moduleName', []), 第一个参数是新的module名称,第二个参数是新module所依赖的module数组。
b. 载入一个module, 只需要1个参数,module('moduleName'), 唯一的一个参数指定要载入的module名称。
2. 使用controller函数来定义一个控制器(controller), 用ng-controller将控制器绑定到具体的HTML组件上。定义控制器的controller函数也需要2个参数,第一个是控制器名称,第二个参数同样也是一个数组,数组的最后一个元素就是controller本身的函数,前面的元素用字符串的形式指定其需要的依赖项。如果没有依赖项,那就只需要定义函数。比如:
angular.module('app1', [])
.controller('mainControl', [function() {
console.log('controller created.');
}]);
3. 在controller函数中用var定义的局部变量,在HTML中是不可见的。
4. 推荐在controller函数中尽量避免直接引用this, 比较好的做法是使用代理。原因是一个函数中的this关键词在被外部调用的时候,是会被覆盖掉的。这样的话,在函数内部和外部的this会是完全不同两个对象。
代理用法示例:
angular.module('app1', [])
.controller('mainControl', [function() {
var self = this;
self.message = 'Hello world';
self.changeMessage = function() {
self.message = 'Goodbye.';
};
}]);
5. ng-bind与双大括号的区别, ng-bind和{{}}可以说基本上是可以互相替换的,但是也有区别。区别在于:AngularJS在启动的时候就会执行ng-bind, 而{{}}的替换时间会稍晚一些。有可能发现页面在加载的时候,双括号被一闪而过地替换掉(只在页面初次加载的时候发生)。但是ng-bind就没有这个问题。
6. ng-cloak可以用于解决双括号闪现的问题。
1. HTML页面的加载,这会触发加载页面包含的所有JS (包括 AngularJS) 2. AngularJS启动,搜寻所有的指令(directive) 3. 找到ng-app,搜寻其指定的模块(Module),并将其附加到ng-app所在的组件上。 4. AnguarJS遍历所有的子组件,查找指令和bind命令 5. 每次发现ng-controller或者ng-repeart的时候,它会创建一个作用域(scope),这个作用域就是组件的上下文。作用域指明了每个DOM组件对函数、变量的访问权。 6. AngularJS然后会添加对变量的监听器,并监控每个变量的当前值。一旦值发生变化,AngularJS会更新其在页面上的显示。 7. AngularJS优化了检查变量的算法,它只会在某些特殊的事件触发时,才会去检查数据的更新,而不是简单地在后台不停地轮询。
Java虚拟机规范规定JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等,而Hotspot jvm的实现中,将堆内存分为了三部分,新生代,老年代,持久带,其中持久带实现了规范中规定的方法区,而内存模型中不同的部分都会出现相应的OOM错误,接下来我们就分开来讨论一下。 栈溢出(StackOverflowError) 栈溢出抛出java.lang.StackOverflowError错误,出现此种情况是因为方法运行的时候栈的深度超过了虚拟机容许的最大深度所致。 出现这种情况,一般情况下是程序错误所致的,比如写了一个死递归,就有可能造成此种情况。 下面我们通过一段代码来模拟一下此种情况的内存溢出。 - import java.util.*;
- import java.lang.*;
- public class OOMTest{
-
- public void stackOverFlowMethod(){
- stackOverFlowMethod();
- }
-
- public static void main(String... args){
- OOMTest oom = new OOMTest();
- oom.stackOverFlowMethod();
- }
-
- }
运行上面的代码,会抛出如下的异常: 引用 Exception in thread "main" java.lang.StackOverflowError at OOMTest.stackOverFlowMethod(OOMTest.java:6) 堆溢出(OutOfMemoryError:java heap space) 堆内存溢出的时候,虚拟机会抛出java.lang.OutOfMemoryError:java heap space,出现此种情况的时候,我们需要根据内存溢出的时候产生的dump文件来具体分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm启动参数)。出现此种问题的时候有可能是内存泄露,也有可能是内存溢出了。 如果内存泄露,我们要找出泄露的对象是怎么被GC ROOT引用起来,然后通过引用链来具体分析泄露的原因。 如果出现了内存溢出问题,这往往是程序本生需要的内存大于了我们给虚拟机配置的内存,这种情况下,我们可以采用调大-Xmx来解决这种问题。 下面我们通过如下的代码来演示一下此种情况的溢出: - import java.util.*;
- import java.lang.*;
- public class OOMTest{
-
- public static void main(String... args){
- List<byte[]> buffer = new ArrayList<byte[]>();
- buffer.add(new byte[10*1024*1024]);
- }
-
- }
我们通过如下的命令运行上面的代码: - java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest
程序输入如下的信息: 引用 [GC 1180K->366K(19456K), 0.0037311 secs] [Full GC 366K->330K(19456K), 0.0098740 secs] [Full GC 330K->292K(19456K), 0.0090244 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at OOMTest.main(OOMTest.java:7) 从运行结果可以看出,JVM进行了一次Minor gc和两次的Major gc,从Major gc的输出可以看出,gc以后old区使用率为134K,而字节数组为10M,加起来大于了old generation的空间,所以抛出了异常,如果调整-Xms21M,-Xmx21M,那么就不会触发gc操作也不会出现异常了。 通过上面的实验其实也从侧面验证了一个结论:当对象大于新生代剩余内存的时候,将直接放入老年代,当老年代剩余内存还是无法放下的时候,出发垃圾收集,收集后还是不能放下就会抛出内存溢出异常了 持久带溢出(OutOfMemoryError: PermGen space) 我们知道Hotspot jvm通过持久带实现了Java虚拟机规范中的方法区,而运行时的常量池就是保存在方法区中的,因此持久带溢出有可能是运行时常量池溢出,也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。当持久带溢出的时候抛出java.lang.OutOfMemoryError: PermGen space。 我在工作可能在如下几种场景下出现此问题。 1.使用一些应用服务器的热部署的时候,我们就会遇到热部署几次以后发现内存溢出了,这种情况就是因为每次热部署的后,原来的class没有被卸载掉。 2.如果应用程序本身比较大,涉及的类库比较多,但是我们分配给持久带的内存(通过-XX:PermSize和-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。 3.一些第三方框架,比如spring,hibernate都通过字节码生成技术(比如CGLib)来实现一些增强的功能,这种情况可能需要更大的方法区来存储动态生成的Class文件。 我们知道Java中字符串常量是放在常量池中的,String.intern()这个方法运行的时候,会检查常量池中是否存和本字符串相等的对象,如果存在直接返回对常量池中对象的引用,不存在的话,先把此字符串加入常量池,然后再返回字符串的引用。那么我们就可以通过String.intern方法来模拟一下运行时常量区的溢出.下面我们通过如下的代码来模拟此种情况: - import java.util.*;
- import java.lang.*;
- public class OOMTest{
-
- public static void main(String... args){
- List<String> list = new ArrayList<String>();
- while(true){
- list.add(UUID.randomUUID().toString().intern());
- }
- }
-
- }
我们通过如下的命令运行上面代码: java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest 运行后的输入如下图所示: 引用 Exception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) at OOMTest.main(OOMTest.java:8) 通过上面的代码,我们成功模拟了运行时常量池溢出的情况,从输出中的PermGen space可以看出确实是持久带发生了溢出,这也验证了,我们前面说的Hotspot jvm通过持久带来实现方法区的说法。 OutOfMemoryError:unable to create native thread 最后我们在来看看java.lang.OutOfMemoryError:unable to create natvie thread这种错误。 出现这种情况的时候,一般是下面两种情况导致的: 1.程序创建的线程数超过了操作系统的限制。对于Linux系统,我们可以通过ulimit -u来查看此限制。 给虚拟机分配的内存过大,导致创建线程的时候需要的native内存太少。我们都知道操作系统对每个进程的内存是有限制的,我们启动Jvm,相当于启动了一个进程,假如我们一个进程占用了4G的内存,那么通过下面的公式计算出来的剩余内存就是建立线程栈的时候可以用的内存。 线程栈总可用内存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序计数器占用的内存 通过上面的公式我们可以看出,-Xmx 和 MaxPermSize的值越大,那么留给线程栈可用的空间就越小,在-Xss参数配置的栈容量不变的情况下,可以创建的线程数也就越小。因此如果是因为这种情况导致的unable to create native thread,那么要么我们增大进程所占用的总内存,或者减少-Xmx或者-Xss来达到创建更多线程的目的。
现在的显示屏都在飚像素,商家的宣传一个比一个噱头大,什么 Retina、4K、8K 这种名词一个接一个的出来, 这些到底都是啥意思? 首先,Retina 和 4K 以及 8K 并不是同一层面的定义。屏幕一般是以像素点做单位的,4K 和 8K 就是直接限定了像素点的多少,而 Retina 则是没有硬性的规范。Retina 屏幕的概念最早由苹果公司执行长史蒂夫·乔布斯(Steve Jobs)于 WWDC2010 发布 iPhone 4 时提出的。 定义是:要求在正常观看距离下,足以使人肉眼无法分辨其中的单独像素。因此它并没有限定像素值多少。
4K 就是水平方向每一行的像素值达到或是接近 1024 的 4 倍,8K 就是达到或接近 8 倍。 以此为标准,4K 一般图像就是指 4096*2160 的分辨率。当然,这也不是硬性要求,像市场上很多 4K 屏幕其实是 3840*2160 或是 3656*2664,这些都是 4K 图像分辨率的范畴。 8K 就是分辨率在 7680*4320 左右。
顺便说一下,720p 则是指竖直方向的像素点达到 720 个,1080p 则是 1080 个,“P”是逐行扫描的意思
问:那到底 Retina 和 4K 或是 Retina 和 8K 哪个更清楚呢? 答:不一定,二者不能平行比较。 因为 4K 和 8K 是限定了像素点的多少,而 Retina 是要求正常距离看不到像素点。 举个例子:如果放到正常的 42 寸屏幕上,4K 和 8K 在正常距离观看下都看不到像素点,那么两者都可以被称作“Retina 屏幕”。
可是如果给你一台 500 寸的巨大屏幕,那么即便是 8K 也会到处是马赛克,这时 Retina 观感依然是高清无像素点,必然比 8K 和 4K 清楚的多。
Last_SQL_Error: Error 'Lock wait timeout exceeded; try restarting transaction' on query. Default database: 'test'. Query: 'DELETE FROM table1 WHERE id = 361' 1 row in set (0.00 sec)
solution: restart slave;
stop slave; start slave;
1. server.xml 在<engine>中添加 <Realm className="org.apache.catalina.realm.MemoryRealm" /> 2. tomcat-user.xml <role rolename="manager"/> <role rolename="manager-gui"/>
<user username="admin" password="tomcat" roles="manager"/>
1. 自动扫描配置文件改动
<configuration scan="true" scanPeriod="30 seconds">
....
</configuration
2. 日志每天归档,同时目录名包含相应的年份和月份
<fileNamePattern>F:\Programs\GlobalPos\GatewayCiti\logs\%d{yyyy/MM,aux}\G%d{dd}-%i.log</fileNamePattern>
注意其中aux的使用,在fileNamePatter中如果出现多个%d的情况下,只能有一个为主配置,其他都需要使用aux标记为附属配置
其中的%i请参看下节的介绍
3. 文件同时根据日期和大小滚动创建
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily -->
<!--
<fileNamePattern>F:\Programs\GlobalPos\NetReport\logs\Portal-%d{yyyyMMdd}.log</fileNamePattern>
-->
<!-- Size and time based archiving -->
<fileNamePattern>D:\logs\%d{yyyy/MM,aux}\L%d{dd}-%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
fileNamePattern在上一节已经介绍,这里主要介绍timeBasedFileNamingAndTriggeringPolicy,此处配置对文件大小的限定,由fileNamePattern的%i在确定下标在文件名中的位置
此示例产生的日志文件将会是:
D:\logs\2015\01\L05-0.log 如果该文件大于100M,就会生成D:\logs\2015\01\L05-1.log
OpenPGP 号称是世界上使用最广泛的邮件加密标准. OpenPGP is the most widely used email encryption standard in the world. ( http://www.openpgp.org/ ) 这篇例子介绍如何使用这个标准进行文件的加密解密 (https://www.bouncycastle.org/latest_releases.html, 需要下载: bcprov-jdk15on-151.jar, bcpg-jdk15on-151.jar). 主要是使用bouncycastle提供的OpenPGP的库来完成这个功能,参照了其提供的示例程序,进行了部分改动 ( Bouncy Castle 是一种用于 Java 平台的开放源码的轻量级密码术包。它支持大量的密码术算法,并提供 JCE 1.2.1 的实现。因为 Bouncy Castle 被设计成轻量级的,所以从 J2SE 1.4 到 J2ME(包括 MIDP)平台,它都可以运行。它是在 MIDP 上运行的唯一完整的密码术包。)1. 添加循环遍历来查找第一个可用的message 2. 需要注意的是在main函数中的, 如果不添加这一句的话 Security.addProvider(new BouncyCastleProvider()); 程序运行中会报错:No such Provider "BC" 3. 错误Exception in thread "main" java.security.InvalidKeyException: Illegal key size or default parameters , 这是因为java缺省的库支持的key长度比较短,需要到oracle的网站上去下载一个支持更长key的库覆盖原有的库文件 <JAVA_HOME>/lib/securty/ 目录下的两个jar文件 local_policy.jar and US_export_policy.jar 搜索这个文件: Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files
package org.bouncycastle.openpgp.examples;
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.Security; import java.util.Iterator;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataList; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPOnePassSignatureList; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; import org.bouncycastle.util.io.Streams;
/** * A simple utility class that encrypts/decrypts public key based * encryption files. * <p> * To encrypt a file: KeyBasedFileProcessor -e [-a|-ai] fileName publicKeyFile.<br> * If -a is specified the output file will be "ascii-armored". * If -i is specified the output file will be have integrity checking added. * <p> * To decrypt: KeyBasedFileProcessor -d fileName secretKeyFile passPhrase. * <p> * Note 1: this example will silently overwrite files, nor does it pay any attention to * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase * will have been used. * <p> * Note 2: if an empty file name has been specified in the literal data object contained in the * encrypted packet a file with the name filename.out will be generated in the current working directory. */ public class KeyBasedFileProcessor { private static void decryptFile( String inputFileName, String keyFileName, char[] passwd, String defaultFileName) throws IOException, NoSuchProviderException { InputStream in = new BufferedInputStream(new FileInputStream(inputFileName)); InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName)); decryptFile(in, keyIn, passwd, defaultFileName); keyIn.close(); in.close(); }
/** * decrypt the passed in message stream */ private static void decryptFile( InputStream in, InputStream keyIn, char[] passwd, String defaultFileName) throws IOException, NoSuchProviderException { in = PGPUtil.getDecoderStream(in); try { JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); PGPEncryptedDataList enc;
Object o = pgpF.nextObject(); // // the first object might be a PGP marker packet. // if (o instanceof PGPEncryptedDataList) { enc = (PGPEncryptedDataList)o; } else { enc = (PGPEncryptedDataList)pgpF.nextObject(); } // // find the secret key // Iterator it = enc.getEncryptedDataObjects(); PGPPrivateKey sKey = null; PGPPublicKeyEncryptedData pbe = null; PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection( PGPUtil.getDecoderStream(keyIn), new JcaKeyFingerprintCalculator());
while (sKey == null && it.hasNext()) { pbe = (PGPPublicKeyEncryptedData)it.next(); sKey = PGPExampleUtil.findSecretKey(pgpSec, pbe.getKeyID(), passwd); } if (sKey == null) { throw new IllegalArgumentException("secret key for message not found."); } InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(sKey)); JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear); Object message = plainFact.nextObject(); while ( true ) { if (message instanceof PGPCompressedData) { PGPCompressedData cData = (PGPCompressedData)message; JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream()); message = pgpFact.nextObject(); } if (message instanceof PGPLiteralData) { PGPLiteralData ld = (PGPLiteralData)message;
String outFileName = ld.getFileName(); if (outFileName.length() == 0) { outFileName = defaultFileName; }
InputStream unc = ld.getInputStream(); OutputStream fOut = new BufferedOutputStream(new FileOutputStream(outFileName));
Streams.pipeAll(unc, fOut);
fOut.close(); break; } else if (message instanceof PGPOnePassSignatureList) { System.out.println("encrypted message contains a signed message - not literal data."); } else if (message instanceof PGPSignatureList) { System.out.println("encrypted message contains a signed message - not literal data."); } else { throw new PGPException("message is not a simple encrypted file - type unknown."); } message = plainFact.nextObject(); } if (pbe.isIntegrityProtected()) { if (!pbe.verify()) { System.err.println("message failed integrity check"); } else { System.err.println("message integrity check passed"); } } else { System.err.println("no message integrity check"); } } catch (PGPException e) { System.err.println(e); if (e.getUnderlyingException() != null) { e.getUnderlyingException().printStackTrace(); } } }
private static void encryptFile( String outputFileName, String inputFileName, String encKeyFileName, boolean armor, boolean withIntegrityCheck) throws IOException, NoSuchProviderException, PGPException { OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName)); PGPPublicKey encKey = PGPExampleUtil.readPublicKey(encKeyFileName); encryptFile(out, inputFileName, encKey, armor, withIntegrityCheck); out.close(); }
private static void encryptFile( OutputStream out, String fileName, PGPPublicKey encKey, boolean armor, boolean withIntegrityCheck) throws IOException, NoSuchProviderException { if (armor) { out = new ArmoredOutputStream(out); }
try { byte[] bytes = PGPExampleUtil.compressFile(fileName, CompressionAlgorithmTags.ZIP);
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator( new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("BC"));
encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider("BC"));
OutputStream cOut = encGen.open(out, bytes.length);
cOut.write(bytes); cOut.close();
if (armor) { out.close(); } } catch (PGPException e) { System.err.println(e); if (e.getUnderlyingException() != null) { e.getUnderlyingException().printStackTrace(); } } }
public static void main( String[] args) throws Exception { Security.addProvider(new BouncyCastleProvider());
if (args.length == 0) { System.err.println("usage: KeyBasedFileProcessor -e|-d [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]"); return; }
if (args[0].equals("-e")) { if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia")) { encryptFile(args[2] + ".asc", args[2], args[3], true, (args[1].indexOf('i') > 0)); } else if (args[1].equals("-i")) { encryptFile(args[2] + ".bpg", args[2], args[3], false, true); } else { encryptFile(args[1] + ".bpg", args[1], args[2], false, false); } } else if (args[0].equals("-d")) { decryptFile(args[1], args[2], args[3].toCharArray(), new File(args[1]).getName() + ".out"); } else { System.err.println("usage: KeyBasedFileProcessor -d|-e [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]"); } } } asdf
Netty作为一个异步非阻塞式的框架,是不允许在ChannelHandler中长时间处理事务(比如数据库的操作),阻塞I/O的读写处理的。 在Netty in Action中是这样描述的:
While the I/O thread must not be blocked at all, thus prohibiting any direct blocking operations within your ChannelHandler, there is a way to implement this requirement. You can specify an EventExecutorGroup when adding ChannelHandlers to the ChannelPipeline. This EventExecutorGroup will then be used to obtain an EventExecutor, which will execute all the methods of the ChannelHandler. This EventExecutor will use a different thread from the I/O thread, thus freeing up the EventLoop. I/O线程是不允许被阻塞的,也就是不能在ChannelHandler中进行任何阻塞式的处理,但是对此我们也有相应的解决方法. 就是在把ChannelHanders添加到ChannelPipeline的时候,指定一个EventExecutorGroup,ChannelHandler中所有的方法都将会在这个指定的EventExecutorGroup中运行。 而这个EVentExecutorGroup运行的线程与I/O线程不同,达到不阻塞I/O的目的。 程序示例如下: Channel ch = ...; ChannelPipeline p = ch.pipeline(); EventExecutor e1 = new DefaultEventExecutorGroup(16); EventExecutor e2 = new DefaultEventExecutorGroup(8); p.addLast(new MyProtocolCodec()); p.addLast(e1, new MyDatabaseAccessingHandler()); p.addLast(e2, new MyHardDiskAccessingHandler()); 需要补充说明一下,上面的示例程序似乎有点问题。使用上述方法添加ChannelHandler到pipeline中以后,channelHandler的所有方法确实什么在一个单独的线程中被处理。 但是,每次DefaultEventExcutorGroup线程池中的线程不能被重用,每次都会生成一个新的线程,然后在新的线程中调用ChannelHandler, 在visualvm可以看到线程数量直线增长。 解决的方法是:不能使用局部变量形式的DefaultEventExecutorGroup。而使用类静态成员变量: static final EventExecutor e1 = new DefaultEventExecutorGroup(16); 我分析原因可能是:在新的连接到来,创建ChannelPipeline给新Channel的时候,如果不使用静态的共享变量,而使用局部变量的话,就造成DefaultEventExecutorGroup被多次重复创建。因此,虽然一个DefaultEventExecutorGroup中的Thread数量是固定的,但是却产生了多余的DefaultEventExecutorGroup。从VisualVM中也可以看到,DefaultEventExecutorGroup线程的名字会是: xxx-2-1 xxx-3-1 xxx-4-1 xxx-n-1 说明是Group的数量(第一个数字)在增多,而不是Group中的线程数量(第二个数字)在增多 改成静态变量后,线程名会是: xxx-2-1 xxx-2-2 xxx-2-3 xxx-2-n 最后一个n就是在创建DefaultEventExecutorGroup时候,传入的线程个数参数的大小。
Netty 1. there're 2 EventLoopGroup in netty, bossGroup and workerGroup, (1 implementation NioEventLoopGroup is a kind of thread pool) 2. bossGroup is Acceptor,is responsible for creating Channels for incoming connection requests 3. workerGroup is the Reactor/Selector?, handling I/O requests. 4. a thread in bossGroup will be listening in the port, Once a connection has been accepted workerGroup assigns an EventLoop to its Channel 5. multiple channels can be registered into 1 EventLoop, multiple EventLoops will exist in workerGroup 6. workerGroup will iterate all the EventLoop, and iterate all the channels in EventLoop, if any of the channel is ready to execute/process 7. it will invoke all the channelHandlers in the channelPipeline 8. ChannelPipelines are containers for chains of ChannelHandlers which executed in order 9. There are, in fact, two ways of sending messages in Netty. You can write directly to the Channel or write to the ChannelHandlerContext object. The main difference is that the former approach causes the message to start from the tail of the ChannelPipeline, while the latter causes the message to start from the next handler in the ChannelPipeline. 10. While the I/O thread must not be blocked at all, thus prohibiting any direct blocking operations within your ChannelHandler, there is a way to implement this requirement. You can specify an EventExecutorGroup when adding ChannelHandlers to the ChannelPipeline. This EventExecutorGroup will then be used to obtain an EventExecutor, which will execute all the methods of the ChannelHandler. This EventExecutor will use a different thread from the I/O thread, thus freeing up the EventLoop. Channel ch = ...; ChannelPipeline p = ch.pipeline(); EventExecutor e1 = new DefaultEventExecutor(16); EventExecutor e2 = new DefaultEventExecutor(8); p.addLast(new MyProtocolCodec()); p.addLast(e1, new MyDatabaseAccessingHandler()); p.addLast(e2, new MyHardDiskAccessingHandler()); http://stackoverflow.com/questions/12928723/netty-4-eventloopgroup-eventloop-eventexecutor-thread-affinity
http://denis.doublebuffer.net/lablog/2012/11/19/fixing-the-screen-flickering-on-a-dell-inspiron-n5720-and-maybe-many-others/ A Dell Inspiron N5720 has a nVidia GT 630M and an Intel HD Graphics 4000. Let’s see what the Intel Graphics Control panel looks like. To open it, follow these instructions. Select Advanced mode. Go to the Power menu and change the power source to “On battery”. Now uncheck the little check box that says “Display Refresh Rate Switch”. Apply. OK. And you’re done.
Where are the Database Files Stored?When using database URLs like jdbc:h2:~/test , the database is stored in the user directory. For Windows, this is usually C:\Documents and Settings\<userName> or C:\Users\<userName> . If the base directory is not set (as in jdbc:h2:test ), the database files are stored in the directory where the application is started (the current working directory). When using the H2 Console application from the start menu, this is<Installation Directory>/bin . The base directory can be set in the database URL. A fixed or relative path can be used. When using the URL jdbc:h2:file:data/sample , the database is stored in the directory data (relative to the current working directory). The directory is created automatically if it does not yet exist. It is also possible to use the fully qualified directory name (and for Windows, drive name). Example:jdbc:h2:file:C:/data/tes
在主动模式下,FTP客户端随机开启一个大于1024的端口N向服务器的21号端口发起连接,然后开放N+1号端口进行监听,并向服务器发出PORT N+1命令。服务器接收到命令后,会用其本地的FTP数据端口(通常是20)来连接客户端指定的端口N+1,进行数据传输。 在被动模式下,FTP库户端随机开启一个大于1024的端口N向服务器的21号端口发起连接,同时会开启N+1号端口。然后向服务器发送PASV命令,通知服务器自己处于被动模式。服务器收到命令后,会开放一个大于1024的端口P进行监听,然后用PORT P命令通知客户端,自己的数据端口是P。客户端收到命令后,会通过N+1号端口连接服务器的端口P,然后在两个端口之间进行数据传输。 总的来说,主动模式的FTP是指服务器主动连接客户端的数据端口,被动模式的FTP是指服务器被动地等待客户端连接自己的数据端口。
被动模式的FTP通常用在处于防火墙之后的FTP客户访问外界FTp服务器的情况,因为在这种情况下,防火墙通常配置为不允许外界访问防火墙之后主机,而只允许由防火墙之后的主机发起的连接请求通过。 因此,在这种情况下不能使用主动模式的FTP传输,而被动模式的FTP可以良好的工作。
Standard模式FTP 客户端首先和FTP Server的TCP 21端口建立连接,通过这个通道发送命令,客户端需要接收数据的时候在这个通道上发送PORT命令。 PORT命令包含了客户端用什么端口接收数据。 在传送数据的时候,服务器端通过自己的TCP 20端口发送数据。 FTP server必须和客户端建立一个新的连接用来传送数据。 Passive模式在建立控制通道的时候和Standard模式类似,当客户端通过这个通道发送PASV 命令的时候,FTP server打开一个位于1024和5000之间的随机端口并且通知客户端在这个端口上传送数据的请求, 然后FTP server 将通过这个端口进行数据的传送,这个时候FTP server不再需要建立一个新的和客户端之间的连接。
java.awt.Desktop is the class you're looking for.
import java.awt.Desktop; import java.net.URI; // ... if(Desktop.isDesktopSupported()) { Desktop.getDesktop().browse(new URI("http://www.example.com")); }
If you're using Java 6 or above, see the Desktop API, in particular browse. Use it like this (not tested):
// using this in real life, you'd probably want to check that the desktop
// methods are supported using isDesktopSupported()...
String htmlFilePath = "path/to/html/file.html"; // path to your new file
File htmlFile = new File(htmlFilePath);
// open the default web browser for the HTML page
Desktop.getDesktop().browse(htmlFile.toURI());
// if a web browser is the default HTML handler, this might work too
Desktop.getDesktop().open(htmlFile);
例子: java -cp bin;lib/* org.kevin.task.RunJobA f:\temp\jobs 1.路径中需要使用/,而不是\\, 2.只能用一个*,不需要添加后缀,
P66 1. components: a. Message a.1 Header a.1.1 Destination a.1.2 DeliveryMode: persistent, non-persistent a.2 Body b. Queue c. Client c.1 Producer c.2 Consumer - Message Selector d. Domain, 2 styles of messaging d.1 point-to-point d.1.1 destination known as QUEUE d.1.2 similar to person to person email sent through a mail server d.1.3 multiple consumers can be registered on a single queue but only one consumer will receive a a given messge and it's upt to that consumer to acknowledge the message d.2 publish-subscribe d.2.1 destination known as TOPICS d.2.2 publishers send messages to the topic and subscribers register to receive messages from the topic d.2.3 durable subscriptions allow for subscriber disconnection without missing any messages
最近在开发中遇到一个问题,就是如何判断远端服务器是否已经断开连接,如果断开那么需要重新连接。 首先想到socket类的方法isClosed()、isConnected()、isInputStreamShutdown()、isOutputStreamShutdown()等,但经过试验并查看相关文档,这些方法都是本地端的状态,无法判断远端是否已经断开连接。 然后想到是否可以通过OutputStream发送一段测试数据,如果发送失败就表示远端已经断开连接,类似ping,但是这样会影响到正常的输出数据,远端无法把正常数据和测试数据分开。 最后又回到socket类,发现有一个方法sendUrgentData,查看文档后得知它会往输出流发送一个字节的数据,只要对方Socket的SO_OOBINLINE属性没有打开,就会自动舍弃这个字节,而SO_OOBINLINE属性默认情况下就是关闭的,太好了,正是我需要的! 于是,下面一段代码就可以判断远端是否断开了连接: Java代码 - try{
- socket.sendUrgentData(0xFF);
- }catch(Exception ex){
- reconnect();
- }
有一个WEB应用,使用了AJAX框架,所有页面的展示都是从AJAX的函数中进行的。 在这种情况,有一个很简单的方法来下载一个文件: window.location="download.action?para1=value1...."
run in the mysql command line client next sequence of queries:
USE mysq; SHOW TABLE STATUS; SHOW INDEX FROM `columns_priv`; DESCRIBE `columns_priv`; SHOW INDEX FROM `db`; DESCRIBE `db`;
describe `column_priv`; ERROR `(HY000): Can't create/write to file 'c:\windows\Temp\#sql_7a4_0.MYD (Errcode: 2)
Delete the file #sql_7a4_0.MYD if exists
在apache dbutil中,给数据列起别名有时候不起作用。这时候,需要在连接字符串中添加一个参数:
useOldAliasMetadataBehavior=true";
Mysql replication error 1201 On a fine happy morning I am greeted with an alert that slave is not running. Running start slave yields this: ERROR 1201 (HY000): Could not initialize master info structure; more error messages can be found in the MySQL error log Enabling the log yielded nothing. Googling yielded This Page that helped me a lot, but I didn't have to do quite as much work all over again. Here is what I did. First, I saved my "SHOW SLAVE STATUS\G" output: ************************** 1. row ************************** Slave_IO_State: Master_Host: 127.0.0.1 Master_User: replication Master_Port: 3307 Connect_Retry: 60 Master_Log_File: mysqld-bin.000401 Read_Master_Log_Pos: 98 Relay_Log_File: mysqld-relay-bin.006135 Relay_Log_Pos: 242 Relay_Master_Log_File: mysqld-bin.000401 Slave_IO_Running: No Slave_SQL_Running: No Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 98 Relay_Log_Space: 0 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: NULL 1 row in set (0.00 sec) STOP SLAVE; RESET SLAVE; mysql> CHANGE MASTER TO MASTER_LOG_POS=98 , MASTER_LOG_FILE = 'mysqld-bin.000401'; Query OK, 0 rows affected (0.04 sec) mysql> start slave; Query OK, 0 rows affected (0.00 sec) The position comes from the output I saved before It's working fine again. Still don't know what caused this - possibly random remote server power cycling...
picky挑剔的; quick-tempered容易发脾气的; rude粗鲁无礼的; scatter-brained 记性不好的; slapdash粗心大意的; sly狡猾的; spiteful怀恨在心的; thoughtless草率的; fussy挑剔的; manic狂躁的; manipulative喜欢指使别人的; moody情绪化的
package org.springside.examples.miniweb.dao.account; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.stereotype.Component; import org.springside.examples.miniweb.entity.account.Group; import org.springside.examples.miniweb.entity.account.User; /** * GroupDao的扩展行为实现类. */ @Component public class GroupDaoImpl implements GroupDaoCustom { private static final String QUERY_USER_BY_GROUPID = "select u from User u left join u.groupList g where g.id=?"; @PersistenceContext private EntityManager em; @Override public void deleteWithReference(Long id) { //因為Group中沒有与User的关联,只能用笨办法,查询出拥有该权限组的用户, 并删除该用户的权限组. Group group = em.find(Group.class, id); List<User> users = em.createQuery(QUERY_USER_BY_GROUPID).setParameter(1, id).getResultList(); for (User u : users) { u.getGroupList().remove(group); } em.remove(group); } }
造成这个错误的原因主要是一些系统文件夹system权限丢失。 所以只要把权限加回去就行了。 我主要查看了 c:\windows\installer C:\Documents and Settings\<user name>\Local Settings\Temp 右键点击目录,点安全选项页,在“组和用户名称”列表中把system用户添加进去,并在下面的权限列表中勾选“完全控制” 即可
Spring Security中指定session超时后跳转的页面
在xml配置文件中加入:<session-management invalid-session-url="/session-timeout.htm" />
在applicationContext.xml中定义了一个DataSource:<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" /> 但是在代码中,使用anotation进行注入的时候,却总是找不到这个dataSource. @Autowired public void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); jdbcInsert = new SimpleJdbcInsert(dataSource); } 最后终于想明白了,原因大概是这样的,使用autowired的时候,默认是根据类型来匹配的,在xml中定义的类型是:BasicDataSource,而不是接口DataSource,所以默认情况下这样是无法自动装配的。解决办法是指令使用名字来进行bean的匹配,也就是用Qualifier指定bean的id. @Autowired public void setDataSource(@Qualifier("dataSource") DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); jdbcInsert = new SimpleJdbcInsert(dataSource); } 另外一点,在网上搜索的过程中发现有不少人都有类似的问题,但是他们的原因是没有正确使用spring的注入,而是自己在代码中new了一个Dao的实例,这样的话,spring是无法将dataSource注入到dao的实例中的
HeidiSql是一家德国公司研发的轻量级的,开源mysql客户端工具。体积十分小巧,可是十分实用。
我之所以喜欢的原因: 1,有导入/导出的功能,可以将数据直接从文本文件中导入到数据库的数据表中。 2,可以将选中的数据导出成为sql语句 3,界面布置十分合理,操作简便
在FreeMarker中使用:${rc.getMessage("key.in.properties")}
mysql数据复制的一个非常有用的选项。
大部分情况下,我使用两个mysql数据库双向复制。 结构大致如下: A <==> B 但是,现在有了新需要,要求在另一个机器上复制出生产环境的所有数据,但是这些数据的复制是单向,也就是说数据只会从生产环境复制到新的服务器上,而所有新数据库的数据变化不会复制到生产环境中。
新的结构大致如下: A <==> B --> C (新的服务器) 设置数据单向复制的步骤其实非常简单,只要在新的服务器(也就是slave端)指定master的 1,在服务器端B,先stop slave; 2, show master status, 记录文件名和position 3,backup the whole database 4,start slave 5, 在slave端,restore database 6, change master .... 到目前为止,看上去一切都进展顺利,但是马上我就发现slave端只有服务器B的数据。因为服务器B没有把从服务器A中复制过来的数据记录到日志中。 所以,slave C就无法得到此部分数据。 后来,经过网上搜索发现有一个选项 -log_slave_update,可以让服务器B记录此部分数据。 实践中,在服务器B中加入此选项后,机器C中就能够获得所有的数据。
主要是使用: Biff8EncryptionKey.setCurrentUserPassword(password); 在打开workbook之前 HSSFWorkbook workbook = new HSSFWorkbook(inp); 注意事项:这个应该是只适用于xls,而不是xlsx
Tapestry5 in action现在还只是提供MEAP形式的订购,MEAP-Manning Early Access Program, 也就是提前试阅的意思,如果读者订购了此版本,那么manning会在作者每完成一章后,将该章节发给订购者,订购者可以提交自己的反馈,manning会根据情况发布相应的错误修改信息。
目前此书的第一章可以免费下载:
http://www.blogjava.net/Files/usherlight/Tapestry5%20in%20action%20MEAP%20ch01.zip
我使用的是sun提供的javax.comm包,不确定其他的comm支持包(比如:rxtx)行为与之相同
1、事件驱动的数据读取
需要注意的是:一个SerialPort只能注册一个事件监听程序,因此,有时候有会发现,你的事件监听处理程序会变得非常庞大
另外,要注意的就是事件处理如果比较耗时的话,最好是在单独的线程中运行,否则会阻塞数据的接收。
串口接收到数据后,默认在线程: Win32SerialPort Notification thread中运行。如果不及时释放的话,会造成数据无法读取。
我的做法是,接收到数据后,先判断数据的合法和完整性,如果没有接收到完整的数据,则在缓存数据后,马上返回
如果数据完整而且合法,则另开一个线程,进行数据处理
2、数据的发送
建议单独使用一个线程来发送数据,目的还是为了防止阻塞,有一个技巧就是使用OUTPUT_BUFFER_EMPTY事件来发送。
uTorrent
7-zip
CDBurnerXP
Notepad++
OpenOffice(LibreOffice)
FireFox
VirtualBox(vmware player)
WinCDEmu
搜狗五笔
打开Sun virtualBox界面,点击设置,点“数据空间”,再点右边带有加号的图标,这时选择你想共享的文件夹,有只读模式与固定模式,选择一种,只读模式是指在虚拟系统中你不能操作这个文件夹,只能读文件,固定模式是指完全操作。
然后打开你的虚拟系统,点开界面上的菜单“设备”选择最后一项“安装增强功能”虚拟系统开始安装,结束后重启虚拟系统。在虚拟系统中打开资源管理器,右击最下面的“网络”,点开“映射网络驱动器”浏览,打击“virtualbox..."(要等会)找到前面共享的了文件夹,确定,
然后你应该在你的资源管理器中就可以看到你刚刚映射的驱动器,你可以设置多个共享文件夹,步骤如前。你想用主机上哪个文件,你就把它复制到共享的文件夹就可以了。
1、用@RequestParam获取HttpServletRequest里的参数值相当方便,spring可以进行自动的类型转换
2、对于Restful的支持,结合@RequestMapping的UriTemplate和method以及@PathVariable,非常方便地实现了restful的url
3、对于Ajax的支持,@RespsonseBody提供了方便的机制
4、灵活丰富的@Controller的函数返回类型
5、可以方便地给@Controller函数注入各种资源
其他的一些别的框架都有特性,诸如国际化,文件上传什么的就不说了
1、Tapestry5.2.4的发布,Tapestry5.2.3版本在内部投票中被否决(主要是因为使用maven快速创建的原型有问题),所以在被否决的3天后便发布了5.2.4,相对5.2.2来说,变动并不大,只有8个错误修复和5个功能改进。但是至少说明Tapestry的项目还在顺利的进行中。
2、Tapestry主页的全新改版。主要是完善了文档。这一点是非常重要的,Tapestry项目组的成员也承认Tapestry在推销自己或者是在市场推广方面做得非常失败(very bad in marketing),所以最近也采取了一些措施来进行改变,比如,预计明年会发行Tapestry5 in action一书等等。
3、还有一件事需要提及的是:appfuse的作者最近对web框架进行了一番对比http://raibledesigns.com/rd/entry/my_comparing_jvm_web_frameworks,Tapestry在13个框架中名列第7,刚好是中间的位置。Tapestry项目的成员颇有不满,认为作者Matt Raible对Tapestry不够了解,有误导观众之嫌。Tapestry项目成员Igor E. Poteryaev认为Matt在 认识度,开发效率,项目健康度,测试友好性,scalability等方面严重低估了Tapestry的能力。
1、发布了MyBatis3.0.3, 具体内容不详,没有能够发现release notes.
2、发布了mybatis-generator 1.3.0, 这是一个能够根据数据库的表自动生成mybatis的
sqlmap xml文件
与数据表对应的java class
使用上两个文件的java类
我下载后,尝试着使用了一下,发现需要有一个配置文件,我觉得应该是用于提供数据库连接的信息。但是配置文件的格式不详。
现在发现mybatis的文档比较成问题,很多东西都没有说明,使用起来很困难啊。
3、新增了两个中方翻译文档
# MyBatis 3 User Guide Simplified Chinese.pdf
# MyBatis-Spring Reference Simplied Chinese.pdf
4、发布了mybatis-spring-1.0.0-RC2
5、发布了mybatis-guice-1.0.0-RC3
主要是一些bug修复和改进,最重要的一点是能够从link对象生成绝对路径了。
这主要是便于ajax的使用。
Bug
* Element.forceAttribute uses the element's namespace to match the attribute.
* Element.attribute(String name, String value) adds elements that already exist
* Element#addClassName can create an additional new 'class' attribute
* Properties defined in an Interface are not exposed by PropertyAccess for abstract classes that do not directly implement the methods
* Some services require a notification that they have been reloaded, so they can clean up external dependencies
* Whitespaces in SymbolConstants.SUPPORTED_LOCALES cause that locales are not persised
* Validation macros do not work when used in @Validate annotation
* Client-side validation of @Pattern is broken
* Linking a Form to a Zone will no longer work unless the Form contains validated fields
* When using PropertyShadowBuilder to build a service, if the property is null, an immediate exception is needed (rather than a NullPointerException)
* When using a MultiZoneUpdate, Tapestry will clear the referenced zone
Improvement
* Add the facility to optionally disable on-focus-change triggered validation while retaining the on-form-submit validation
* Form component should be able to render a secure URL even on an insecure page
* New annotations @Decorate and @Advise to identify methods that decorate or annotate services
* Extend Link with new methods for producing absolute URLs (that include scheme, hostname, etc.)
* Simplify connecting a link or form to an (enclosing) Zone
* BeanBlockContribution should be split into two sub-classes: EditBlockContribution and DisplayBlockContribution
* Define a special CSS class to prevent a client-side form from submitting normally (for Ajax use cases)
* Additional method for Link: addParameterValue(String,Object) that uses ContextPathEncoder to encode object value to a string
* SeleniumTestCase should expose the underlying CommandProcessor, to allow execution of commands not defined by the Selenium interface
* Allow individual SeleniumTestCases to run w/o configuring SeleniumLauncher
今天在网上转了转,发现Tapestry又有了新动作。
1、从5.2.0alpha版8月11日发布到现在,终于发布5.2.1Beta版了。这个版本主要是修改alpha版本中的bug.
5.2相对于5.1进行了许多重大的改进,详细内容可见:Tapestry5.2的新变化
2、据称在5.3版中将引入大量的新功能,具体内容不详。
3、以往最令人诟病的文档问题,目前开发小组也在努力解决。
首先,建立了新的文档网页:http://people.apache.org/~uli/tapestry-site/,不过目前这个网站还没有完全完成,只能算是预览版,但是这毕竟是在正确的道路上前进。
其次,Tapestry 5 in action一书正在写作中,已经完成20%,预计明年一季度能够出版。
我相信,随着功能的不断完善和补充,再加上文档的逐渐充实,Tapestry完全有理由能吸引更多的开发者来关注和使用。
这是mybatis从ibatis更名过来后,发布的第2个版本(第1个版本是3.0.1)。
在这个版本中只修复了4个bug(感觉数目有点少,难道是因为上一个版本的bug真的如此之少?),增加了一个小功能.
作者的话:尽管只有5个修改,但我还是觉得值得为此发布一个新版本。
这5个修改是:
1、在org.apache.ibatis.Session中增加了一个新方法:void select(String statement, ResultHandler handler);
原来只有:
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
2、修复了ManagedConnection中关闭连接的问题
3、修复了schema migration中,语句提交(statement commit)不正确的问题
4、修复了延迟加载已经预读取属性时的问题
5、修复了schema migration中,FileInputStream没有正确关闭的问题
介绍一个使用GIMP来批量处理图像的方法。 GIMP是一个免费的图像编辑工具,功能非常强大。并且提供了类似于firefox的插件机制,可以通过添加插件来不断地获得新功能。
批量处理图像就是通过一个批处理插件来完成的。这个插件的下载地址:http://members.ozemail.com.au/~hodsond/dbp.html
插件下载后,解压缩,然后把dbp.exe放在plugin目录下,比如:C:\Program Files\GIMP-2.0\lib\gimp\2.0\plug-ins
启动GIMP,在菜单 Filter 里会发现一个新的选项:Batch Process...
点击这个选项,会弹出一个新窗口,这个就是批处理的操作界面了。
通过这个插件,我们可以完成 批量改变大小/重命名/旋转/锐化/模糊/调整亮度/裁剪 这些功能。
Tapestry开发小组中的一个成员的blog中记录了5.2版本带来的新变化
1、增强了class reload的功能(我个人也认为这一点是5.2版本最令人激动的改进),以前Tapestry和其他的web框架类似,修改页面不需要重新启动application server, 但是修改了类之后,必须重新启动应用服务器才行。但是在Tapestry5.2中,对这一点进行了修改,如果只改变了接口的实现类,而不改变接口的方法签名,就不需要重新启动应用服务器
2、新增了若干个组件,比如:Error和Trigger。
Error和原有的Errors类似,但是Error用于给指定的组件显示验证错误信息。
Trigger提供了在泻染页面过程中触发任务事件的功能,常常用于通过RenderSupport来给页面添加JavaScript代码
3、新的插件(Mixin),包括RenderClientId, RenderNotification
4、集成了JSR-303 Bean的验证,现在可以在页面中使用JSR-303标准的注解来给字段指定需要的验证
5、新的注解,包括@Contribute,@RequestParameter, @ActivationRequestParameter, 使用后两个注解能很容易地获取request中的参数
6、新的页面生命周期事件:pageReset
7、链接修饰过程中的新事件: decoratePageRenderLink, decoreateComponentEventLink
8、页面解析器的更换,原来使用StAX,造成了对Google App Engine和对OSGI的不兼容,5.2版本中使用了标准的SAX解析器
9、页面缓冲池的废除(我认为这是5.2版本的一个相当大而且也是非常重要的一个变化,我认为页面缓冲池技术是tapestry学习曲线陡峭的一个重要原因),5.2版本中所有页面将只有一个实例(也就是lewis howard说的单例化),页面属性的值将会在每个线程中使用一个Map来保存。这样一个页面实例可以在多个线程中使用,而不会有同步问题。
但是,由于这是一个新的尝试,所以lewis也不确定这样做的效果是否很好(详见:http://tapestryjava.blogspot.com/2010/07/everyone-out-of-pool-tapestry-goes.html)所以,在5.2中可以通过配置恢复页面缓冲池的使用。
jquery有一个很方便的插件UI Table Filter可以根据输入的内容隐藏显示表格中相应的数据行。
因为目前使用的tapestry捆绑的是prototype,所以就自己写了一个类似的插件。
<html>
<head>
<script src="prototype-1.6.0.2.js" type="text/javascript"></script>
<script language="javaScript">
Event.observe(window, 'load', function() {
Event.observe('filter', 'keyup', filterTable);
});
function filterTable() {
var filterCaseElement = document.getElementById('filterCase');
var caseSensitive = filterCaseElement.checked;
$$('tr').each(function(trElement, ind) {
var val = $('filter').value;
if ( ! caseSensitive ) {
val = val.toLowerCase();
}
trElement.childElements().each(function(tdElememt) {
var tdText = tdElement.innerText;
if ( ! caseSensitive ) {
tdText = tdText.toLowerCase();
}
if (tdText.include(val)) {
trElement.show();
} else if ( ind > 0 ) {
trElement.hide();
}
});
});
}
</script>
</head>
<body>
filter: <input type="text" id="filter" name="filter"/> <input type="checkbox" id="filterCase" onchange="javaScript:filterTable();"/> Case-Sensitive
<table border="1">
<thead>
<tr>
<th>name</th>
<th>column1</th>
<th>column2</th>
<th>column3</th>
<th>column4</th>
<th>column5</th>
<th>column6</th>
</tr>
</thead>
<tr>
<td>TEST</td>
<td>00150002331</td>
<td>238156</td>
<td>075</td>
<td>001</td>
<td>172.16.14.20</td>
<td>1-1-05</td>
</tr>
<tr>
<td>TEST-2</td>
<td>00150002332</td>
<td>238157</td>
<td>075</td>
<td>002</td>
<td>172.16.14.21</td>
<td>1-1-05</td>
</tr>
<tr>
<td>TEST</td>
<td>00150002333</td>
<td>238158</td>
<td>075</td>
<td>003</td>
<td>172.16.14.23</td>
<td>1-1-05</td>
</tr>
<tr>
<td>TEST</td>
<td>00150002341</td>
<td>238159</td>
<td>075</td>
<td>004</td>
<td>172.16.14.24</td>
<td>1-1-05</td>
</tr>
<tr>
<td>TEST</td>
<td>00150002339</td>
<td>238186</td>
<td>075</td>
<td>006</td>
<td>172.16.14.26</td>
<td>1-1-06</td>
</tr>
</table>
</body>
</html>
如果页面中多个表格,而只需要对其中的一个表格的数据进行过滤的话,简单地把其中:$$('tr').each(function(ele, index) 改成 $$('#tableId, tr').each(function(ele, index) 就行了,其中的tableId就是表格的id
今天打开tapestry的网站,发现tapestry5.2的alpha版本已经发布出来了。
在5.2中还是新增了不少令人激动的功能
1、QueryParameterMapped注解,使用这个注解可以很方便地把request中的参数映射到bean里的属性
2、(这是最让我期待的功能)服务终于能够动态加载了!
3、在事件处理方法中可以使用QueryParameter来注解参数
4、submit组件现在可以cancel表单里的数据了
5、不再使用StAX作为tml的解析器了,而是采用标准的SAX解析器,这样减少外界引用,而且兼容性更好
6、组件中也可以使用SessionAttribute来获得session中的数据了,以前好像只有在page中才能用
7、引入了JSR-303 Bean Validation Integration Library.
GIMP(http://www.gimp.org/)是一个开源的图形处理工具,功能与PHOTOSHOP相比也并不相差太多。
1、选择要保留的图形,
有2种办法,第一种就是直接用魔术棒工具来选择,但一般来说这部分图形的色彩比较复杂,选择起来相对困难
第2种办法是先选择背景,然后使用“反向选择”。
友情提示:在使用魔术棒的时候,有4种模式,可以利用其中的添加,减少来不断地修正选择的范围
2、打开图层面板
3、菜单:选择->浮动,在图层面板中可以看到多一个浮动的图层,但是现在图层里还没有东西
4、菜单:图层->创建,这时候就可以看到,在刚才多出来的浮动图层中出现了选择好的要保留的图像
5、去除背景图层,点击背景图层前面的眼睛,使得背景图层不可见
保存,生成透明背景的新图像
CardLayout布局管理器能够帮助用户处理两个以至更多的成员共享同一显示空间,它把容器分成许多层,每层的显示空间占据整个容器的大小,但是每层只允许放置一个组件,当然每层都可以利用Panel来实现复杂的用户界面.布局管理器(CardLayout)就象一副叠得整整齐齐的扑克牌一样,有54 张牌,但是你只能看见最上面的一张牌,每一张牌就相当于布局管理器中的每一层.
流式布局管理器把容器看成一个行集,好象平时在一张纸上写字一样,一行写满就换下一行。行高是用一行中的控件高度决定的。FlowLayout是所有 JApplet/JApplet的默认布局。在生成流式布局时能够指定显示的对齐方式,默认情况下是居中(FlowLayout.CENTER)
GridLayout 将成员按网格型排列,每个成员尽可能地占据网格的空间,每个网格也同样尽可能地占据空间,从而各个成员按一定的大小比例放置。如果你改变大小, GridLayout将相应地改变每个网格的大小,以使各个网格尽可能地大,占据Container容器全部的空间。
基本布局策略是把容器的空间划分成若干行乘若干列的网格区域,组件就位于这些划分出来的小区域中,所有的区域大小一样。组件按从左到右,从上到下的方法加入。
BoxLayout布局能够允许将控件按照X轴(从左到右)或者Y轴(从上到下)方向来摆放,而且沿着主轴能够设置不同尺寸。
HTTP Status Codes Explained
All valid HTTP 1.1 Status Codes simply explained.
HTTP, Hypertext Transfer Protocol, is the method by which clients (i.e. you) and servers communicate. When someone clicks a link, types in a URL or submits out a form, their browser sends a request to a server for information. It might be asking for a page, or sending data, but either way, that is called an HTTP Request. When a server receives that request, it sends back an HTTP Response, with information for the client. Usually, this is invisible, though I'm sure you've seen one of the very common Response codes - 404, indicating a page was not found. There are a fair few more status codes sent by servers, and the following is a list of the current ones in HTTP 1.1, along with an explanation of their meanings.
A more technical breakdown of HTTP 1.1 status codes and their meanings is available at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html. There are several versions of HTTP, but currently HTTP 1.1 is the most widely used.
Informational
- 100 - Continue
A status code of 100 indicates that (usually the first) part of a request has been received without any problems, and that the rest of the request should now be sent.
- 101 - Switching Protocols
HTTP 1.1 is just one type of protocol for transferring data on the web, and a status code of 101 indicates that the server is changing to the protocol it defines in the "Upgrade" header it returns to the client. For example, when requesting a page, a browser might receive a statis code of 101, followed by an "Upgrade" header showing that the server is changing to a different version of HTTP.
Successful
- 200 - OK
The 200 status code is by far the most common returned. It means, simply, that the request was received and understood and is being processed.
- 201 - Created
A 201 status code indicates that a request was successful and as a result, a resource has been created (for example a new page).
- 202 - Accepted
The status code 202 indicates that server has received and understood the request, and that it has been accepted for processing, although it may not be processed immediately.
- 203 - Non-Authoritative Information
A 203 status code means that the request was received and understood, and that information sent back about the response is from a third party, rather than the original server. This is virtually identical in meaning to a 200 status code.
- 204 - No Content
The 204 status code means that the request was received and understood, but that there is no need to send any data back.
- 205 - Reset Content
The 205 status code is a request from the server to the client to reset the document from which the original request was sent. For example, if a user fills out a form, and submits it, a status code of 205 means the server is asking the browser to clear the form.
- 206 - Partial Content
A status code of 206 is a response to a request for part of a document. This is used by advanced caching tools, when a user agent requests only a small part of a page, and just that section is returned.
Redirection
- 300 - Multiple Choices
The 300 status code indicates that a resource has moved. The response will also include a list of locations from which the user agent can select the most appropriate.
- 301 - Moved Permanently
A status code of 301 tells a client that the resource they asked for has permanently moved to a new location. The response should also include this location. It tells the client to use the new URL the next time it wants to fetch the same resource.
- 302 - Found
A status code of 302 tells a client that the resource they asked for has temporarily moved to a new location. The response should also include this location. It tells the client that it should carry on using the same URL to access this resource.
- 303 - See Other
A 303 status code indicates that the response to the request can be found at the specified URL, and should be retrieved from there. It does not mean that something has moved - it is simply specifying the address at which the response to the request can be found.
- 304 - Not Modified
The 304 status code is sent in response to a request (for a document) that asked for the document only if it was newer than the one the client already had. Normally, when a document is cached, the date it was cached is stored. The next time the document is viewed, the client asks the server if the document has changed. If not, the client just reloads the document from the cache.
- 305 - Use Proxy
A 305 status code tells the client that the requested resource has to be reached through a proxy, which will be specified in the response.
- 307 - Temporary Redirect
307 is the status code that is sent when a document is temporarily available at a different URL, which is also returned. There is very little difference between a 302 status code and a 307 status code. 307 was created as another, less ambiguous, version of the 302 status code.
Client Error
- 400 - Bad Request
A status code of 400 indicates that the server did not understand the request due to bad syntax.
- 401 - Unauthorized
A 401 status code indicates that before a resource can be accessed, the client must be authorised by the server.
- 402 - Payment Required
The 402 status code is not currently in use, being listed as "reserved for future use".
- 403 - Forbidden
A 403 status code indicates that the client cannot access the requested resource. That might mean that the wrong username and password were sent in the request, or that the permissions on the server do not allow what was being asked.
- 404 - Not Found
The best known of them all, the 404 status code indicates that the requested resource was not found at the URL given, and the server has no idea how long for.
- 405 - Method Not Allowed
A 405 status code is returned when the client has tried to use a request method that the server does not allow. Request methods that are allowed should be sent with the response (common request methods are POST and GET).
- 406 - Not Acceptable
The 406 status code means that, although the server understood and processed the request, the response is of a form the client cannot understand. A client sends, as part of a request, headers indicating what types of data it can use, and a 406 error is returned when the response is of a type not i that list.
- 407 - Proxy Authentication Required
The 407 status code is very similar to the 401 status code, and means that the client must be authorised by the proxy before the request can proceed.
- 408 - Request Timeout
A 408 status code means that the client did not produce a request quickly enough. A server is set to only wait a certain amount of time for responses from clients, and a 408 status code indicates that time has passed.
- 409 - Conflict
A 409 status code indicates that the server was unable to complete the request, often because a file would need to be editted, created or deleted, and that file cannot be editted, created or deleted.
- 410 - Gone
A 410 status code is the 404's lesser known cousin. It indicates that a resource has permanently gone (a 404 status code gives no indication if a resource has gine permanently or temporarily), and no new address is known for it.
- 411 - Length Required
The 411 status code occurs when a server refuses to process a request because a content length was not specified.
- 412 - Precondition Failed
A 412 status code indicates that one of the conditions the request was made under has failed.
- 413 - Request Entity Too Large
The 413 status code indicates that the request was larger than the server is able to handle, either due to physical constraints or to settings. Usually, this occurs when a file is sent using the POST method from a form, and the file is larger than the maximum size allowed in the server settings.
- 414 - Request-URI Too Long
The 414 status code indicates the the URL requested by the client was longer than it can process.
- 415 - Unsupported Media Type
A 415 status code is returned by a server to indicate that part of the request was in an unsupported format.
- 416 - Requested Range Not Satisfiable
A 416 status code indicates that the server was unable to fulfill the request. This may be, for example, because the client asked for the 800th-900th bytes of a document, but the document was only 200 bytes long.
- 417 - Expectation Failed
The 417 status code means that the server was unable to properly complete the request. One of the headers sent to the server, the "Expect" header, indicated an expectation the server could not meet.
Server Error
- 500 - Internal Server Error
A 500 status code (all too often seen by Perl programmers) indicates that the server encountered something it didn't expect and was unable to complete the request.
- 501 - Not Implemented
The 501 status code indicates that the server does not support all that is needed for the request to be completed.
- 502 - Bad Gateway
A 502 status code indicates that a server, while acting as a proxy, received a response from a server further upstream that it judged invalid.
- 503 - Service Unavailable
A 503 status code is most often seen on extremely busy servers, and it indicates that the server was unable to complete the request due to a server overload.
- 504 - Gateway Timeout
A 504 status code is returned when a server acting as a proxy has waited too long for a response from a server further upstream.
- 505 - HTTP Version Not Supported
A 505 status code is returned when the HTTP version indicated in the request is no supported. The response should indicate which HTTP versions are supported.
摘要: 一、往串口写数据
import java.io.*;
import javax.comm.*;
import java.util.*;
public class PortWriter
{
static Enumeration ports;
... 阅读全文
"java decompiler"是一个非常出色的java反编译工具,详见主页:http://java.decompiler.free.fr/
在myeclipse8.5中的安装也比较简单,下载:http://java.decompiler.free.fr/jd-eclipse/update/jdeclipse_update_site.zip
然后把这个压缩包解开放在myeclipse8.5的dropins目录下,比如:C:\Programs\Genuitec\MyEclipse-8.5\dropins\jdeclipse_update_site
重起myeclipse就行了,第一次打开class文件,可能会稍微有一点慢,要等待一小会才会反编译出来。
好像还有点问题
操作系统升级到windows7之后,原来很多软件就不能用了。对于虚拟光驱软件,我推荐一款免费的:WinCDEmu,这是一个在SourceForge上的免费软件。优点是使用方便,他可以虚拟出任意多个虚拟光驱,而且可以选择手工指定盘符。只要双击文件,就会弹出一个对话框,询问光驱的盘符。当然你也可以选择让WinCDEmu自动选择盘符。他支持大部分主流的文件格式: ISO, CUE, NRG, MDS/MDF, CCD, IMG
Recently I got a chance working with Spring security, formerly known as Acegi Security for spring. While working with the framework, I heard comments from friends and colleagues saying that spring security lacks proper documentation. So thought of sharing a little knowledge. By the way, this is first ever blog posting and kindly excuse me and let me know any errors and improvements.
Spring security offers a simple configuration based security for your web applications helping you secure your web application with out littering your business logic with any security code. It provides securing URL's based on the Role (Authorities), securing your business methods based on the ACL's.
The first step in hooking up the spring security to your web application is by specifying the DelegatingFilterProxy in your web.xml.
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
REQUEST
INCLUDE
FORWARD
If you want to externalize all of your security related configuration into a separate file, you can do so and add that to your context location param.
contextConfigLocation
/WEB-INF/beans.xml , /WEB-INF/springSecurity.xml
Now comes the part of security configuration for your application, Adding the URL security patterns is pretty simple and straight forward. Add all the URL patterns which you want to secure and add the wild card pattern at the end. You need to have some default principal and role even for non logged in users as you need to give access to pages like log in, register and forgot password kind of functionality even to non logged in users.
I tried to add comments to pretty much every element which I am using here.
As an example I added just a wild card intercept url which make every page of my application secure. You need to exclude different urls based on the roles.
Following is my custom implementation of AuthenticationEntryPoint, which currently is not doing any thing except leveraging the commence to its super class which is the spring implementation of AuthenticationProcessingFilterEntryPoint. I hooked it to add any custom logic.
public class CustomAuthenticationEntryPoint extends AuthenticationProcessingFilterEntryPoint {
private static final Log logger = LogFactory.getLog(CustomAuthenticationEntryPoint.class);
@Override
public void commence(ServletRequest request, ServletResponse response, AuthenticationException authException) throws IOException, ServletException {
super.commence(request, response, authException);
}
}
This is my custom authentication manager which actually does the custom login of the user. It will throw an BadCredentialsException in case of invalid credentials or thorws a AuthenticationServiceException in case of a service error (Database error, SQL error or any other error).
public class CustomAuthunticationManager implements AuthenticationManager {
@Autowired
UserManagerService userManagerService;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(StringUtils.isBlank((String) authentication.getPrincipal()) || StringUtils.isBlank((String) authentication.getCredentials())){
throw new BadCredentialsException("Invalid username/password");
}
User user = null;
GrantedAuthority[] grantedAuthorities = null;
try{
user = userManagerService.getUser((String) authentication.getPrincipal(), (String) authentication.getCredentials());
}
catch(InvalidCredentialsException ex){
throw new BadCredentialsException(ex.getMessage());
}
catch(Exception e){
throw new AuthenticationServiceException("Currently we are unable to process your request. Kindly try again later.");
}
if (user != null) {
List roles = user.getAssociatedRoles();
grantedAuthorities = new GrantedAuthority[roles.size()];
for (int i = 0; i < roles.size(); i++) {
Role role = roles.get(i);
GrantedAuthority authority = new GrantedAuthorityImpl(role.getRoleCode());
grantedAuthorities[i] = authority;
}
}
else{
throw new BadCredentialsException("Invalid username/password");
}
return new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(), grantedAuthorities);
}
}
At the client side (jsp), the simple configuration you need to do is post the request to"/j_spring_security_check" with parameters "j_username" and "j_password".
That's pretty much all you need to do for enabling spring security to your existing web application. I will try to explain about doing the method security using ACL's and configuring the view using spring security tags in another post.
作者相信ga版本将会在不久的未来发布。
所作者透露,在新版本中会增加一个新的组件:dynamic.
这个组件的用途是更换皮肤。在旧版本的tapestry中,由于tapestry的模板是一致的,所以要实现换肤相当困难。
但在新版本中的这个组件比较完美地解决了这个问题。
作者自我评价这个组件是令人激动的一个新事物。
现在打开iBatis的主页:ibatis.apache.org会发现作者发布了一个新闻或者说是声明。
大意是因为各种原因,已经把iBatis更名为MyBatis, 并且搬迁到Google Code中了。
新的主页地址是: http://www.mybatis.org
另外,MyBatis3.0.1的正式版已经在http://code.google.com/p/mybatis/可以下载了。
4月17日,iBatis3.0的GA版本的候选版本(candidate)已经在主页上公布出来了(ibatis.apache.org)
根据其描述,只有PMC的投票才会被真正的计算。但是同时又补充说明,其实很多PMC的投票是参考社区用户的投票结果的。
所以也鼓励大家发表自己的看法,并期待大家的反馈。
原文:
After well over a year of development and testing effort, iBATIS 3.0 is now ready for prime time. I've uploaded the new bundles for iBATIS 3 Core and Migrations. It is this release that we'll vote on for GA status. I'll leave the vote open for a good while (maybe a couple of weeks), just to give everyone a chance to try it out. As usual, only PMC votes actually count, but many PMC members will vote based on the community votes. So please offer your feedback. Otherwise, enjoy iBATIS 3.0!
在最上面添加了标签页,目前分了6大块:Java,SOA,.Net,TEST,AJAX,TSSJs
主框架也进行了调整,从原来的2列变成了3列,将原来在右边的要点移到了左边。文章占据了中间最大片的面积。最右边是新闻。
感觉整个界面比原来的色调要清新明亮,版块分割的更清楚。
出自伏尔泰的名句。在许多优秀的程序员的编程习惯中,寻找解决方案就如同是寻找女友一般,总是力求完美,如果一个解决方案不够完美的话,那么宁可舍弃也不会降低标准而采用。但是这样的结果很有可能会是一叶障目,不见森林。
举个实际的例子来说,tapestry5的服务动态加载就是一个典型。
大家知道,在Tapestry中,页面模板的动态加载一直是其的一个宣传点。也就是说,页面模板如果修改了,不需要重新启动应用服务器,修改即时生效。但是,Tapestry却一直没有实现服务的动态加载,也就是说服务的类修改了,就必须重新启动服务器。为什么呢,因为追求完美。
其实很久以前在Tapestry的作者的头脑中,包括许多的用户都想在Tapestry中加入服务动态加载的功能,但是最后作者却放弃了。原因是因为,在Tapestry的架构中,服务比页面要复杂的多,服务不是单独存在的,他会被Inject到许多其他的服务,而这些服务又会被inject到其他的服务中,最后导致你很难界定哪些类需要被重新加载。
但是最近有一个人提出一个建议,为什么要实现完整的动态加载呢。其实只实现服务的实现类的动态加载就已经很好了。
确实,如果保持接口不变,只在接口具体实现修改的时候加载,确实大大简化了问题,因为这样变化范围就被限定在单个类之内了。虽然这样并不完美,因为如果改变了对外接口,比如,添加了一个新的方法,或者改变了方法的签名,还是需要重新启动服务器。可是这样也已经在很大程度上提升了用户的使用满意度。
正如有句古语所说:退一步海阔天空。也在此提醒自己在以后的开发过程中,尽量拓展自己的思维,不要一下子钻到牛角尖里。
2月25日,IBatis3发布了Beta10,在主页上宣称,一个月来Beta9的公测都没有收到真正的Bug提交。
所有收到的问题都是新的功能要求。
所以,如果没有意外的话,Beta10将会用于投票表决是否作为GA(General Available)版本。
在教程的第四部分(http://www.blogjava.net/usherlight/archive/2009/06/20/283396.html)我们已经粗略地介绍了Tapestry的前缀,其中提及最常用的两种literal和prop。这里顺便再介绍一下其他的几个前缀.
1、context,这个经常在引用图片的时候用到。比如:<img src="${context:images/icon.png}"/> 这样就能够在页面上显示在web-inf/images/icon.png
2、message,这个是需要I18N的应用必用的前缀。${message:some-key}
3、var,用于定义生成一个临时变量。比如:<li t:type="loop" source="1..10" value="var:index">${var:index}</li>。如果没有加这个前缀var:的话,就需要在页面类中添加一个property: index以及相应的getter和setter。
我们这次介绍自定义一个cycle前缀,这个可以用于在表格中显示不同背景的行(也就是斑马条)。
比如:
<t:grid .... rowClass="cycle:line1,line2">
...
</t:grid>
或者
<t:loop ...>
<div class="${cycle:line1,line2}">aaaa</div>
</tloop>
自定义prefix一般来说是3个步骤,
1、定义一个BindingFactory,这个需要实现BindingFactory接口
2、定义一个Binding继承AbstractBinding
3、注册这个Binding
看一下具体的prefix的类:
1import java.util.ArrayList;
2import java.util.List;
3
4import org.apache.tapestry5.Binding;
5import org.apache.tapestry5.BindingConstants;
6import org.apache.tapestry5.ComponentResources;
7import org.apache.tapestry5.ioc.Location;
8import org.apache.tapestry5.services.BindingFactory;
9import org.apache.tapestry5.services.BindingSource;
10
11/** *//**
12 * Implementation of the cycle: binding prefix -- we parse list of bindings
13 * and generate delegate bindings for each element<br>
14 * default binding is literal, other bindings can be used by specifying prefix.<br>
15 * example: "cycle:prop:name,prop:lastName,sth,sth else"
16 */
17public class CycleBindingFactory implements BindingFactory {
18 private final BindingSource _bindingSource;
19
20 public CycleBindingFactory(BindingSource source){
21 this._bindingSource = source;
22 }
23
24 public Binding newBinding(String description, ComponentResources container, ComponentResources component,
25 String expression, Location location)
26 {
27 List<Binding> delegates = new ArrayList<Binding>();
28 String[] bindingNames = expression.split(",");
29
30 for (String bindingName : bindingNames){
31 String defaultBinding = BindingConstants.LITERAL;
32
33 Binding binding = _bindingSource.newBinding(description, container, component, defaultBinding, bindingName, location);
34 delegates.add(binding);
35 }
36
37 CycleBinding cycleBinding = new CycleBinding(delegates);
38 container.addPageLifecycleListener(cycleBinding);
39
40 return cycleBinding;
41 }
42}
1import java.util.List;
2
3import org.apache.tapestry5.Binding;
4import org.apache.tapestry5.internal.bindings.AbstractBinding;
5import org.apache.tapestry5.runtime.PageLifecycleListener;
6
7
8public class CycleBinding extends AbstractBinding implements PageLifecycleListener{
9 private final List<Binding> delegates;
10 private int index = 0;
11
12 public CycleBinding(List<Binding> delegates) {
13 this.delegates = delegates;
14 }
15
16 public Object get() {
17 Object ret = delegates.get(index).get();
18 index ++;
19 if(index>=delegates.size()) index = 0;
20 return ret;
21 }
22
23 @Override
24 public boolean isInvariant() {
25 return false;
26 }
27
28 @Override
29 public Class<Object> getBindingType() {
30 return Object.class;
31 }
32
33
34 public void containingPageDidDetach() {
35 index=0;
36 }
37
38 public void containingPageDidAttach() {/**//*not interested*/}
39
40 public void containingPageDidLoad() {/**//*not interested*/}
41}
Binding和BindingFactory写好了,注册后就可以使用了,注册的过程是在AppModel中添加以下一段代码:
1 public static void contributeBindingSource(
2 MappedConfiguration<String, BindingFactory> configuration,
3 BindingSource bindingSource
4 )
5 {
6 configuration.add("cycle",new CycleBindingFactory(bindingSource));
7 }
1. 先开始看SpringSide吧。
主要看寒冬日志版3.2.1开始有的JMS演示和WebService演示
一直觉得EJB好像已经日薄西山了,但是实际上生命力还挺顽强的。
另外,还有Web Service, SOA这个概念还是有人相信的。
所以,延伸项目有:JMS, MQ
我记得好像是Appfuse的作者曾经这样评价过Tapestry:只要你真正掌握了Tapestry,你的开发效率将会得到极大的提高。为什么呢?我认为他这样说的一个重要原因就是Tapestry的组件机制。Tapestry提供了非常便利的组件定义机制,随着Tapestry的组件不断积累,Tapestry的开发将会变得越来越简单。
本文就用一个实例来看一下Tapestry中是如何添加一个自定义组件的。
Tapestry的内置组件只提供了checkbox,而且只能返回一个boolean,用于表明是否被选中。
比如,要进行一个群众喜爱的水果调查,选项有: 苹果,葡萄,桃子,香蕉...,就需要对应每个选项设置一个布尔型变量,显得比较繁琐。
这里我们将添加一个组件用于将一组checkbox集中起来返回一个逗号分隔的字符串值。
通过查看Tapestry中的checkbox的源码(已经附在文章的后面)可以知道,Tapestry可以很容易地通过Request来获取Form中的变量的值。
遇到的问题:
Tapestry的checkbox组件不允许设置相同的name,如果name相同,Tapestry会自动在name后面添加后缀来使之不同。
If a component renders multiple times, a suffix will be appended to the to id to ensure uniqueness(http://tapestry.apache.org/tapestry5.1/tapestry-core/ref/org/apache/tapestry5/corelib/components/Checkbox.html)。如果各checkbox的name不同,我们无法通过request来获得一组checkbox的值。
思路:
在页面模板中不使用tapestry的checkbox组件,而使用Html的checkbox,这样可以避免tapestry自动修改checkbox的name。
添加一个新的tapestry组件,来映射接受所有同名的checkbox的值,并把值返回给tapestry页面中对应的变量。这个组件需要有一个属性,这个属性的值就是所有同组checkbox的name,这样,这个组件就可以通过Request来获取所有相同name的checkbox的值。
代码:
1 public class CheckBoxGroup extends AbstractField {
2
3 @SuppressWarnings("unused")
4 @Parameter(required = true, autoconnect = true)
5 private String value;
6
7 @Parameter(required = true, autoconnect = true)
8 private String groupName;
9
10 @Inject
11 private Request request;
12
13 @SuppressWarnings("unused")
14 @Mixin
15 private RenderDisabled renderDisabled;
16
17 @Inject
18 private ComponentResources resources;
19
20 @BeginRender
21 void begin(MarkupWriter writer)
22 {
23 writer.element("input", "type", "checkbox",
24 "name", groupName,
25 "id", getClientId(),
26 "style", "display:none");
27
28 resources.renderInformalParameters(writer);
29
30 decorateInsideField();
31 }
32
33 @AfterRender
34 void after(MarkupWriter writer)
35 {
36 writer.end(); // input
37 }
38
39 @Override
40 protected void processSubmission(String elementName)
41 {
42 String elementValue = "";
43 String[] valueArray = request.getParameters(groupName);
44 if ( valueArray != null && valueArray.length > 0 ) {
45 elementValue = valueArray[0];
46 for ( int i = 1; i < valueArray.length; i ++ ) {
47 elementValue += "," + valueArray[i];
48 }
49 }
50 value = elementValue;
51 }
52 }
组件的使用:
-----tml------
<t:CheckBoxGroup t:groupName="literal:bookId" t:value="selectedBooks"/>
<t:loop source="bookList" value="book" encoder="encoder">
<div><input type="checkbox" name="bookId" value="${book.id}"/> ${book.name}</div>
</t:loop>
注意checkBoxGroup的groupName和其他checkbox的name必须一致,checkBoxGroup的value的值就是页面中的变量名
-----java-----
@SuppressWarnings("unused")
@Property
private final ValueEncoder<Book> encoder = new ValueEncoder<Book>() {
public String toClient(Book value) {
return String.valueOf(value.getId());
}
public Book toValue(String clientValue) {
return bookDao.getBook(Integer.parseInt(clientValue));
}
};
public List<Book> getBookList() {
return bookDao.getBooks();
}
@SuppressWarnings("unused")
@Property
private Book book;
@SuppressWarnings("unused")
@Property
private String selectedBooks;
在tapestry5中,在页面之间传递基本有3种方法
1、存放在Session中
2、使用@Persist进行持久化
3、使用页面context来传递参数。
其中1和2都需要将数据存放在Session中,相对来说系统的开销比较大。尤其是多用户高并发情况下,对于性能可能会有一定的影响。
使用页面Context来传递则需要在开发时写一些代码,增加了一些开发量,显得没有前两种方法方便。
第3种方法的实现是需要在页面中添加onActivate和onPassivate方法来完成页面参数的传递。
我们先来看一下其背后的故事。
举个例子,比如说我们有两个页面,第一个是查询条件输入页面input,另一个是查询结果输出页面output。input页面中有两个查询条件,起始时间dateFrom和终止时间dateTo
在Input.java中,我们可以很直观地这样写:
@InjectPage
private Output output;
@Property
private String dateFrom;
@Property
private String dateTo;
Object onFormSubmit() {
output.setDateFrom(dateFrom);
output.setDateTo(dateTo);
return output;
}
首先使用注解注入output页面,然后在表单的提交事件中,返回output,这样就在程序中定义了返回页面,而不是使用配置文件的方式。
但是这样的实现却不能正确运行,原因是因为Tapestry5的使用了页面池技术,页面在每次渲染前都是从页面池中随机获取一个页面,而从页面池中取得的页面,所有的属性都是被清空了的。
也就是说在上例中,虽然我们注入了output页面,但是此页面马上就被放入了页面池,而且其中的属性值马上就被清空了。这就是引入onActivate和onPassivate这丙个方法的原因。tapestry5在清空属性前会首先查看是否包含onPassivate方法,如果有,就把其返回值保存起来,然后从页面池中取得页面后,再把刚才保存的值作为参数传递给onActivate方法。
这就是方法3的基本原理,但是无论是在官方的文档或是示例或者网上其他的应用中,可以发现大部分都是使用单个参数的,比如说id。这也很容易理解,因为onPassivate的方法的返回值只能有一个。
在Tapestry5的官方文档中,只有一句非常简要的话介绍了如果传递多个文档的方法: The activation context may consist of a series of values, in which case the return value of the method should be an array or a List. (参见:http://tapestry.apache.org/tapestry5.1/guide/pagenav.html)。
但是这并不是说只要在onPassivate中把参数的值加入到List中,返回一个List,而在onActivate中接受一个List参数,然后就可以得到其中的参数了,因为Tapestry5把参数传给onActivate的方法其实是通过将参数作为HttpRequest中的参数的。如果试图使用上述方法就是得到一个“无法将List转换成String的错误”
所以方法应该是这样的,在Output中:
private List<String> paramList;
public void setParamList(List<String> paramList) {
this.paramList = paramList;
}
public List<String> getParamList() {
return paramList;
}
List<String> onPassivate() {
return paramList;
}
void onActivate(String dateFrom, String dateTo) {
this.dateFrom = dateFrom;
this.dateTo = dateTo;
}
private String dateFrom;
private String dateTo;
在Input页面中,需要把onFormSubmit改一下:
Object onFormSubmit() {
List<String> list = new ArrayList<String>();
output.setParamList(list);
return output;
}
其中,需要注意的是output中的onActivate方法,基参数的顺序必须和List中放入的参数顺序一致。
在上一篇中我们研究了如何实现SpringSecurity中Jsp Tag的<security:authorize ifAllGranted="ROLE_SUPERVISOR">的功能。这一次我们一起研究一下如何实现在Tapestry5.1中添加一个Filter来对所有的操作进行权限的过滤控制。
在SpringSecurity中,我们一般是在application-context.xml中,添加一个SpringSecurity的Filter,然后在另外一个xml中详细配置如何根据Url的规则进行权限的控制。而Tapestry的哲学是尽量减少Xml中的配置(其IOC容器也基本上是借鉴Guice而不Spring的),所以我们也是在代码中实现权限规则的控制。
总体上来看,可以用两种方式来实现url规则,一种是Request级别的Filter,一种是页面组件级别的Filter,如果是Request级别的话,可以从Request对象中获取Url路径,这样就与SpringSecurity基本一样了。本文主要介绍页面组件级别的Filter,从中我们也可以体会到Tapestry5.1中的IOC容器的强大和便利。
这就是Filter的代码,这个Filter必须实现ComponentRequestFilter接口。值得注意的是其构造函数所需要用到的4个参数,这4个参数都是Tapestry5本身自有的服务,所以我们什么也不用做,Tapestry5自动会将服务的实例注入进来,这就是Tapestry-IOC的威力。
ComponentRequestFilter接口一共有4个方法需要实现,具体代码如下:
1 public class RequiresLoginFilter implements ComponentRequestFilter {
2
3 private final PageRenderLinkSource renderLinkSource;
4
5 private final ComponentSource componentSource;
6
7 private final Response response;
8
9 private final ApplicationStateManager appStateManager;
10
11 public RequiresLoginFilter(PageRenderLinkSource renderLinkSource,
12 ComponentSource componentSource, Response response,
13 ApplicationStateManager appStateManager
14 ) {
15 this.renderLinkSource = renderLinkSource;
16 this.componentSource = componentSource;
17 this.response = response;
18 this.appStateManager = appStateManager;
19 }
20
21 public void handleComponentEvent(
22 ComponentEventRequestParameters parameters,
23 ComponentRequestHandler handler) throws IOException {
24
25 if (dispatchedToLoginPage(parameters.getActivePageName())) {
26 return;
27 }
28
29 handler.handleComponentEvent(parameters);
30
31 }
32
33 public void handlePageRender(PageRenderRequestParameters parameters,
34 ComponentRequestHandler handler) throws IOException {
35 if (dispatchedToLoginPage(parameters.getLogicalPageName())) {
36 return;
37 }
38 handler.handlePageRender(parameters);
39
40 }
41
42 private boolean dispatchedToLoginPage(String pageName) {
43 Component page = componentSource.getPage(pageName);
44
45 if (page.getClass().isAnnotationPresent(RequiresLogin.class)) {
46 if ( ! appStateManager.exists(Authentication.class)) {
47 redirect();
48 return true;
49 }
50 Authentication auth = appStateManager.get(Authentication.class);
51 if ( auth == null ) {
52 redirect();
53 return true;
54 }
55
56 if ( ! auth.isLoggedIn()) {
57 redirect();
58 return true;
59 }
60
61 RequiresLogin requireLogin = page.getClass().getAnnotation(
62 RequiresLogin.class);
63 String ifNotGranted = requireLogin.ifNotGranted();
64 String ifAllGranted = requireLogin.ifAllGranted();
65 String ifAnyGranted = requireLogin.ifAnyGranted();
66 boolean permitted = auth.checkPermission(ifNotGranted, ifAllGranted, ifAnyGranted);
67 if ( ! permitted ) {
68 return true;
69 }
70 }
71
72 return false;
73 }
74
75 private void redirect() {
76 Link link = renderLinkSource.createPageRenderLink("Logout");
77
78 try {
79 response.sendRedirect(link);
80 } catch (Exception e) {
81 }
82 }
83
84 }
在ComponentRequestFilter中,我们无法使用@SessionState注解来直接注入Session中的变量,但是我们可以通过ApplicationStateManager来取得。
现在我们需要把刚定义的Filter注册到系统中,很简单,只要在AppModule中添加以下函数就行了:
1 public static void contributeComponentRequestHandler(
2 OrderedConfiguration<ComponentRequestFilter> configuration) {
3 configuration.addInstance("RequiresLogin", RequiresLoginFilter.class);
4 }
5
从本例子中我们可以看到Tapesty Ioc容器使用的便利性,也认识到了Ioc容器在Tapestry体系中的重要性
IBatis2中提供了3种DataSource的配置:JNDI, Apache DBCP, IBatis自带的SimpleDataSource。但在IBatis3中只提供了两种DataSource: UNPOOLED, POOLED。
如果要实现自定义的DataSource,就需要通过扩展DataSourceFactory。本文就演示一下这个过程。
准备工作:Connection Pool的选择,通过搜索发现目前比较流行的免费数据库连接池主要有3种:Apache DBCP, C3P0, Proxool。
看了一下,Proxool的最新版本是0.9.1(2008-08-23), C3P0的最新版本是0.9.1.2(2007-05-21), DBCP最新版本是1.2.2(2007-04-04)
好像这3个项目都已经挺长时间没有更新了。但是总体评价上C3P0无论从稳定上还是效率上都要好一点。
(具体这3个项目谁更优秀,并不是本文的重点,本文主要是介绍一下如何在IBatis3中自定义数据源)
大致步骤:
1、实现org.apache.ibatis.datasource.DataSourceFactory接口,主要是2个方法
a、public DataSource getDataSource() 如何具体地得到一个数据源
b、public void setProperties(Properties properties) 如何设置数据源的参数属性
2、实现javax.sql.DataSource,这个就是提供给DataSourceFactory的实例
3、在IBatis3中引用新加入的数据源
1. 从代码中可以看出,IBatis3与IBatis2不同,不再通过一个Configuration类来进行数据源属性的设置,而是使用反射机制直接调用数据源的方法来设置参数。
这就要求配置文件中的参数名称必须与数据源类中的方法名匹配.
1 public class C3p0DataSourceFactory implements DataSourceFactory {
2
3 private DataSource dataSource;
4
5 public C3p0DataSourceFactory() {
6 dataSource = new C3p0DataSource();
7 }
8
9 public DataSource getDataSource() {
10 return dataSource;
11 }
12
13 public void setProperties(Properties properties) {
14 Properties driverProperties = new Properties();
15 MetaObject metaDataSource = MetaObject.forObject(dataSource);
16 for (Object key : properties.keySet()) {
17 String propertyName = (String) key;
18 if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
19 String value = properties.getProperty(propertyName);
20 driverProperties.setProperty(propertyName
21 .substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
22 } else if (metaDataSource.hasSetter(propertyName)) {
23 String value = (String) properties.get(propertyName);
24 Object convertedValue = convertValue(metaDataSource,
25 propertyName, value);
26 metaDataSource.setValue(propertyName, convertedValue);
27 } else {
28 throw new DataSourceException("Unkown DataSource property: "
29 + propertyName);
30 }
31 }
32 if (driverProperties.size() > 0) {
33 metaDataSource.setValue("driverProperties", driverProperties);
34 }
35 }
36
37 @SuppressWarnings("unchecked")
38 private Object convertValue(MetaObject metaDataSource, String propertyName,
39 String value) {
40 Object convertedValue = value;
41 Class targetType = metaDataSource.getSetterType(propertyName);
42 if (targetType == Integer.class || targetType == int.class) {
43 convertedValue = Integer.valueOf(value);
44 } else if (targetType == Long.class || targetType == long.class) {
45 convertedValue = Long.valueOf(value);
46 } else if (targetType == Boolean.class || targetType == boolean.class) {
47 convertedValue = Boolean.valueOf(value);
48 }
49 return convertedValue;
50 }
51
52 private static final String DRIVER_PROPERTY_PREFIX = "driver.";
53 private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX
54 .length();
55
56 }
57
2. 数据源类,其中的一堆setter就是用于设置属性的。
1 public class C3p0DataSource implements DataSource {
2
3 private ComboPooledDataSource dataSource;
4 public C3p0DataSource() {
5 this.dataSource = new ComboPooledDataSource();
6 }
7
8 public Connection getConnection() throws SQLException {
9 return dataSource.getConnection();
10 }
11
12 public Connection getConnection(String username, String password)
13 throws SQLException {
14 return dataSource.getConnection(username, password);
15 }
16
17 public PrintWriter getLogWriter() throws SQLException {
18 return dataSource.getLogWriter();
19 }
20
21 public int getLoginTimeout() throws SQLException {
22 return dataSource.getLoginTimeout();
23 }
24
25 public void setLogWriter(PrintWriter out) throws SQLException {
26 dataSource.setLogWriter(out);
27 }
28
29 public void setLoginTimeout(int seconds) throws SQLException {
30 dataSource.setLoginTimeout(seconds);
31 }
32
33
34 public synchronized void setDriver(String driver) {
35 try {
36 dataSource.setDriverClass(driver);
37 } catch (Exception e) {
38 }
39 }
40
41 public void setUrl(String url) {
42 dataSource.setJdbcUrl(url);
43 }
44
45 public void setUsername(String username) {
46 dataSource.setUser(username);
47 }
48
49 public void setPassword(String password) {
50 dataSource.setPassword(password);
51 }
52
53 public void setInitialPoolSize(int initialPoolSize) {
54 dataSource.setInitialPoolSize(initialPoolSize);
55 }
56
57 public void setMaxPoolSize(int maxPoolSize) {
58 dataSource.setMaxPoolSize(maxPoolSize);
59 }
60
61 public void setMinPoolSize(int minPoolSize) {
62 dataSource.setMinPoolSize(minPoolSize);
63 }
64
65 public void setPreferredTestQuery(String preferredTestQuery) {
66 dataSource.setPreferredTestQuery(preferredTestQuery);
67 }
68
69 public void setPoolPingQuery(String poolPingQuery) {
70 dataSource.setPreferredTestQuery(poolPingQuery);
71 }
72 }
3. 在配置文件Configuration.xml中,可以先定义数据源的别称,然后就象POOLED和UNPOOLED一样使用别称来引用数据源。
<Configuration>
...
<typeAlias>
<typeAlias type="com.test.datasource.C3p0DataSourceFactory" alias="C3P0"/>
</typeAlias>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="C3P0">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="poolPingQuery" value="${pingquery}"/>
</dataSource>
</environment>
</environments>
...
<Configuration>
最近在使用tapestry5.1.0.5开发项目的时候,突然报错:
Exception in thread "main" java.lang.ClassFormatError: Invalid length 561 in LocalVariableTable in class file
在网上搜索后,发现有人也有同样的错误,解决方法有两种:
http://mail-archives.apache.org/mod_mbox/tapestry-users/200909.mbox/%3Cecd0e3310909040909id5275beld935fc60d54d490a@mail.gmail.com%3E
其中一个人的错误原因是在其类路径中有不同版本的javassists的jar文件。
另一个的解决方法是使用eclipse自带的jdk来编译java类。
而我自己仔细检查了类路径中的文件,并没有重复的javassists,不过我觉得问题应该就在javassists上,
因为这显然是javassists在操作class文件时报的错误,
我去网上搜索这方面的信息,发现有好几个人都和我一样在使用javassists3.11.0GA版本的时候,会出现这个错误。
后来,我改用Tapestry5中自带的javassists3.9.0GA后,问题消失了。
这次经验教训是并不是所有最新的东西都是最好的。合适的才是最好的。
IBatis3的Beta8版本已经发布了,在官方网站上声称目前的版本已经非常稳定,只有4个已知的问题,其中2个是非功能性的。作者宣称,这样的状况使它对于近期发布GA版本充满信心。
那么IBatis3与IBatis2相比,究竟变化在哪里呢?
最重要的变化是IBatis3中引入了接口绑定(Interface Binding)的概念。在IBatis2中,没有应用Java5的泛型,所以需要大量使用强制类型转换,比如:
Employee employee = (Employee)sqlMapper.queryForList("getEmployee", 5);
//...and...
List employees = sqlMapper.queryForList("listAllEmployees");
但是在IBatis3中,方法改变成:
MapperFactory factory = someConfiguration.buildMapperFactory();
EmployeeMapper employeeMapper = factory.getMapper (EmployeeMapper.class);
Employee emp = empMapper.getEmployee(5);
//...and...
List<Employee> employees = empMapper.listAllEmployees();
所以IBatis3至少需要使用Java5以上的版本。上面代码中,EmployeeMapper是一个自定义的接口(注意,开发人员只需要定义一个接口,不需要提供具体的实现)
public interface EmployeeMapper {
Employee getEmployee (int employeeId);
List<Employee> listAllEmployees();
}
这样就行了,IBatis会自动为你生成接口的具体实现。是不是感觉有点酷?
Tapestry中并没有类似于Spring Security这样的专门的权限框架。对此Tapestry的作者Lewis认为主要是用户对于权限的要求实在太多变化了。他认为很难抽象出一个通用的权限框架来满足所有的用户,所以他干脆就不费事去做这件事了。但其实我们很容易就能利用Tapestry已有的工具来完成类似于SpringSecurity的功能。
本文主要介绍如何实现类似于SpringSecurity的jsp tag的功能。在Tapestry中,利用Components实现这一点非常容易。
其基本原理是Tapestry5中一个页面或者组件的渲染生成过程是基于一个状态机和队列完成的。这样,渲染生成过程就被细分成了很多个小模块,我们可以非常容易地覆写这些小模块。具体内容详见官方文档:http://tapestry.apache.org/tapestry5.1/guide/rendering.html。如果权限校验不通过,我们就可以控制不显示组件的内容。
我们这里就是主要依赖这个过程来实现在页面这一层面对权限进行校验和控制。
代码主要包含两大部分,一个组件和一个用于权限控制的服务。
参考了Tapestry-Spring-Security的实现,我也将组件命名为IfRole(当然,我们也可以和Tapestry-Spring-Security一样,也再生成一个IfLoggedIn组件)。权限控制的服务我命名为:AuthenticationService。
主要的实现思路:
将AuthenticationService申明为SessionState变量。这样这个变量就可以在所有的页面和组件之间很方便地共享了。一般情况下,是在登录页面对AuthenticationService进行赋值,而在退出页面清空AuthenticationService这个变量。
代码(这部分代码完全根据应用的需求进自行更改):
AuthenticationService的代码:
public class AuthenticationService {
private List<String> privilegeList;
// privilegeList 的getter and setter
public boolean checkPermission(String ifNotGranted, String ifAllGranted,
String ifAnyGranted) {
if (((null == ifAllGranted) || "".equals(ifAllGranted))
&& ((null == ifAnyGranted) || "".equals(ifAnyGranted))
&& ((null == ifNotGranted) || "".equals(ifNotGranted))) {
return false;
}
if ((null != ifNotGranted) && !"".equals(ifNotGranted)) {
StringTokenizer st = new StringTokenizer(ifNotGranted, ",");
while (st.hasMoreTokens()) {
String value = st.nextToken();
if (privilegeList.contains(value)) {
return false;
}
}
}
if ((null != ifAllGranted) && !"".equals(ifAllGranted)) {
StringTokenizer st = new StringTokenizer(ifAllGranted, ",");
while (st.hasMoreTokens()) {
String value = st.nextToken();
if (!privilegeList.contains(value)) {
return false;
}
}
}
if ((null != ifAnyGranted) && !"".equals(ifAnyGranted)) {
StringTokenizer st = new StringTokenizer(ifAnyGranted, ",");
while (st.hasMoreTokens()) {
String value = st.nextToken();
if (privilegeList.contains(value)) {
return true;
}
}
return false;
}
return true;
}
}
IfRole的代码(这个类需要放在Components目录下):
public class IfRole {
/** *//**
* A comma-separated list of roles is supplied to one or more of the
* following parameters. If none are supplied, the default behavior is to
* forbid access. Behavior should be self-explanatory.
*/
@Parameter(required = false, defaultPrefix = "literal")
private String ifAllGranted;
@Parameter(required = false, defaultPrefix = "literal")
private String ifAnyGranted;
@Parameter(required = false, defaultPrefix = "literal")
private String ifNotGranted;
/** *//**
* An alternate {@link Block} to render if the test parameter is false. The default, null, means
* render nothing in that situation.
*/
@Parameter(name = "else")
private Block elseBlock;
private boolean test;
@SessionState
private AuthenticationService auth;
private boolean checkPermission() {
return auth.checkPermission(ifNotGranted, ifAllGranted, ifAnyGranted);
}
void setupRender() {
test = checkPermission();
}
/** *//**
* Returns null if the test method returns true, which allows normal
* rendering (of the body). If the test parameter is false, returns the else
* parameter (this may also be null).
*/
Object beginRender() {
return test ? null : elseBlock;
}
/** *//**
* If the test method returns true, then the body is rendered, otherwise not. The component does
* not have a template or do any other rendering besides its body.
*/
boolean beforeRenderBody() {
return test;
}
}
示例:
1. 在登录页面:
@SessionState
private Authentication auth;
......
// if user name and password is valid:
auth.setPrivliegeList(.....);
2. 在需要权限控制的页面模板中:
<t:ifRole ifAllGranted="admin">
administrator can see this block
</t:ifRole>
与现在最流行的SSH相比较,Tapestry能够完全替代其中Struts2和Spring,但是他还是需要一个ORM的框架。IBatis由于比较低的学习曲线,也受到很多人的喜爱。尤其是在IBatis3中引入了许多新的概念和想法,使用更加安全和便利。
本文主要介绍如何将Tapestry5.1和IBatis3进行整合。
简要步骤:
1. 准备工作
2. 数据库的建立
3. POJO的建立
4. IBatis相关配置文件的创建
5. Tapestry相关代码的完成
概要说明:
1、准备工作。这一部分是比较简单的,Eclipse之类的开发环境是必需的。Tapestry5.1、IBatis3(目前还是Beta7)、数据库(我使用的是MySql)的下载安装。
2、数据库的建立,由于是示例,所以数据库的建立也非常简单,只有一张User表,3个字段,Id,Name,Password
3、com.sample.User类,对应数据库表的3个字段,生成User类
4、IBatis配置文件:Configuration.xml,UserMapper.xml,jdbc.properties的生成, 前两个必需,最后一个可选.
5、在AppModule里,使用build方法, 添加服务生成IBatis3的SqlSessionFactory, 在需要使用SqlSessionFactory的地方,使用@InjectService注入即可
详细说明:
1、大家到各自的网站上下载相应的包好了。我只罗列一下我所用到的Lib:
antlr-runtime-3.1.1.jar
commons-codec-1.3.jar
commons-lang-2.4.jar
ibatis-3-core-3.0.0.216.jar
javassist.jar
log4j-1.2.14.jar
mysql-connector-java-5.0.5.jar
slf4j-api-1.5.10.jar
slf4j-log4j12-1.5.10.jar
stax2-api-3.0.1.jar
tapestry-core-5.1.0.5.jar
tapestry-ioc-5.1.0.5.jar
tapestry5-annotations-5.1.0.5.jar
woodstox-core-lgpl-4.0.7.jar
2、Create Table
DROP TABLE IF EXISTS `test`.`user`;
CREATE TABLE `test`.`user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
`password` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
3、
package com.sample.model;
public class User {
private long id;
private String name;
private String password;
// getter and setter ....
}
4、我把Configuration.xml和UserMapper.xml都放在src目录下,这样在部署的时候,就是生成在classes,也就是类路径的根目录下。
Configuration.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//ibatis.apache.org//DTD Config 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties">
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="poolPingEnabled" value="${pingenable}"/>
<property name="poolPingQuery" value="${pingquery}"/>
<property name="poolPingConnectionsNotUsedFor" value="${pingnotusetime}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
UserMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="com.sample.model.UserMapper">
<select id="selectUser" parameterType="int" resultType="com.sample.model.User">
select * from user where id = #{id}
</select>
</mapper>
jdbc.properties:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test?autoReconnect=true
jdbc.username=root
jdbc.password=root
pingenable=true
pingquery=SELECT 1
pingoldertime=0
pingnotusetime=3600000
5、
package com.sample.web.services;
public class AppModule {
public static SqlSessionFactory buildSqlSessionFactory() {
try {
String resource = "Configuration.xml";
Reader reader = Resources.getResourceAsReader(resource);
return new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
logger.warn("failed to build SqlSessionFactory: ", e);
return null;
}
}
private static Logger logger = LoggerFactory.getLogger(AppModule.class);
}
package com.sample.model;
public interface UserMapper {
public User selectUser(int id);
}
package com.pc.sample.web.pages;
public class Layout {
@InjectService("SqlSessionFactory")
private SqlSessionFactory sqlMapper;
public String getUserName() {
if ( sqlMapper == null ) {
return "null-mapper";
}
SqlSession session = sqlMapper.openSession();
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
if ( userMapper == null ) {
return "null-userMapper";
}
User user = userMapper.selectUser(1);
if ( user == null ) {
return "null-user";
}
return user.getName();
} catch (Exception e) {
return "exception-" + e.getMessage();
} finally {
session.close();
}
}
}
几个注意事项:
1,
因为我的IBatis的配置文件Configuration.xml是放在类路径的根目录下,所以在初始化SqlSessionFactory的时候,直
接用String resource =
"Configuration.xml";就行了,否则需要添加相应的路径,比如:把Configuration.xml与User类放在一起,也就是在
com.sample.model这个package中,那么就要写成:String resource =
"com/sample/model/Configuration.xml";
同样,在Configuration.xml中,指定UserMapper.xml的规则也是这样的。
2,UserMapper的使用。Mapper的使用是IBatis3中才有的新功能,也是IBatis用户指南中推荐使用的方式。因为这样使用的话,就完全避免了类型的强制转换,实现了类型安全。
需要注意的是UserMapper只是一个接口。我们不需要提供这个接口的具体实现。IBatis3会自动生成一个具体的实例。
其中的方法名必须与UserMapper.xml中的select语句的id一样。在我的例子中是selectUser.
另外,此方法的返回值的类型必须与UserMapper.xml中配置的returnType一致。
最后要提醒的是UserMapper.xml中的namespace必须是UserMapper的全类名,在本例中就是com.sample.model.UserMapper
here is a summary of key features in Spring 3.0 overall:
* Spring expression language (SpEL): a core
expression parser for use in bean definitions, allowing for references
to nested bean structures (e.g. properties of other beans) as well as
to environmental data structures (e.g. system property values) through
a common #{…} syntax in property values.
* Extended support for annotation-based components:
now with the notion of configuration classes and annotated factory
methods (as known from Spring JavaConfig). Spring also allows for
injecting configuration values through @Value expressions now,
referring to configuration settings via dynamic #{…} expressions or
static ${…} placeholders.
* Powerful stereotype model: allows for creating
'shortcut' annotations through the use of meta-annotations, e.g. for
default scopes and default transactional characteristics on custom
stereotypes. Imagine a custom @MyService annotation indicating
@Service, @Scope("request") and @Transactional(readOnly=true) through a
single annotation.
* Standardized dependency injection annotations:
Spring 3.0 comes with full support for the JSR-330 specification for
Dependency Injection in Java – annotation-driven injection via @Inject
and its associated qualifier and provider model, as an alternative to
Spring's own @Autowired and co.
* Declarative model validation based on constraint annotations:
Spring-style setup of a JSR-303 Bean Validation provider (such as
Hibernate Validator 4.0). Comes with an annotation-driven validation
option in Spring MVC, exposing a unified view on constraint violations
through Spring’s binding result facility.
* Enhanced binding and annotation-driven formatting:
Converter and Formatter SPIs as an alternative to standard
PropertyEditors. Formatting may be driven by annotations in a style
similar to JSR-303 constraints, e.g. using @DateTimeFormat. Also, check
out the new mvc namespace for convenient setup of formatting and
validation in Spring MVC.
* Comprehensive REST support: native REST
capabilities in Spring MVC, such as REST-style request mappings, URI
variable extraction through @PathVariable parameters, and view
resolution driven by content negotiation. Client-side REST support is
available in the form of a RestTemplate class.
* Rich native Portlet 2.0 support: Spring MVC fully
supports Portlet 2.0 environments and Portlet 2.0’s new event and
resource request model. Includes specialized mapping facilities for
typical portlet request characteristics: @ActionMapping,
@RenderMapping, @ResourceMapping, @EventMapping.
* Object/XML Mapping (OXM): as known from Spring
Web Services, now in Spring Framework core. Marshalling and
Unmarshaller abstractions with out-of-the-box support for JAXB 2,
Castor, etc. Comes with integration options for XML payloads in Spring
MVC and Spring JMS.
* Next-generation scheduling capabilities: new
TaskScheduler and Trigger mechanisms with first-class cron support.
Spring 3.0 comes with a convenient task namespace and also supports
@Async and @Scheduled annotations now. This can be executed on top of
native thread pools or server-managed thread pools.
Beyond those big themes, there are hundreds of refinements in the
details which you will particularly appreciate when upgrading from
Spring 2.5. Check the changelog and the javadocs…
In terms of system requirements, Spring 3.0 covers a broad range of
environments. For two key characteristics, Spring 3.0 supports Java SE 5 and above and Servlet 2.4 and above, e.g. Tomcat 5.x and 6.x, also retaining compatibility with common enterprise servers such as WebSphere 6.1 and WebLogic 9.2
(which are formally still based on J2EE 1.4). At the same time, we
support GlassFish v3 already – adapting to Java EE 6 API level in
Spring as well.
As a consequence, Spring 3 brings brand-new component model features, and also standards like JSR-330 injection and JSR-303 validation, to established production environments – without having to upgrade your server installation! All you have to do is to upgrade the application libraries of your Spring-powered application to Spring 3.0…
Enjoy – and watch out for follow-up posts about specific Spring 3 features, as well as for samples running on Spring 3.0!
struts2的文件上传对文件大小的限制,缺省值是2m,也就是说缺省情况下,最大只能上传2m的文件。根据文档所说需要对fileUpload这个拦截器的一个参数maximunSize进行设置
<interceptor-ref name="fileUpload">
<param name="maximumSize">1000000</param>
<param name="allowedTypes">image/gif,image/jpeg,image/jpg,image/png</param>
</interceptor-ref>
但是我设置了之后并没有作用。
后来,仔细查看日志后才发现错误是commons-fileupload里面的文件大小限制引起了错误。
在struts.xml中,添加
<constant name="struts.multipart.maxSize" value="16777216"/>
解决问题!
摘要: JavaRebel是一个工具,主要是用于热加载,比如说在Tomcat之类的应用服务器中,更新了class或者某些资源文件,使用了JRebel之后,就不需要重新启动应用服务器。这对于开发的人来说,是特别方便的。当然Java也提供了HotSpot的JVM,但是如果你修改的类中有方法名称变动的话,HotSpot就无能为力了,必须要重要启动应用服务器。
这里有一点先声明一下,本文只是破解仅限于学习和研究... 阅读全文
目前从实际应用来看,ORM的老大自然是Hibernate,可是iBatis因为相对比较直观、学习曲线相对较低,因而也赢得了不少用户的青睐。
本文主要介绍作为iBatis辅助工具的iBator的使用方法。
iBator是一个iBatis相关代码的自动生成工具。
1、安装iBator的插件
在Eclipse中,使用添加站点的方法,输入网址http://ibatis.apache.org/tools/ibator,进行iBator的安装。
2、建议不要直接在使用iBatis的项目里直接使用iBator,推荐另外单独建立一个项目来生成。比如,建立一个项目叫:IbatorPrj
3、右键点击IbatorPrj这个项目,如果刚才的插件安装正确的话,就会看到一个“Add iBATOR to the build path”的选项,点击一下。
4、创建iBator的配置文件。下面是我的例子,大家在实际使用的过程中,需要根据自己的情况进行相应的修改。
主要就是数据库JDBC库的路径、数据库驱动的类名、项目的名称、包名等。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ibatorConfiguration
PUBLIC "-//Apache Software Foundation//DTD Apache iBATIS Ibator Configuration 1.0//EN"
"http://ibatis.apache.org/dtd/ibator-config_1_0.dtd">
<ibatorConfiguration>
<classPathEntry location="c:\javaLibs\MySql\mysql-connector-java-5.0.6-bin.jar" />
<ibatorContext id="SampleiBator" targetRuntime="Ibatis2Java5">
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost/sample" userId="root" password="admin">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<javaModelGenerator targetPackage="com.sample"
targetProject="IbatorPrj\src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="com.sample.xml"
targetProject="IbatorPrj\src">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<daoGenerator type="GENERIC-CI" targetPackage="com.sample.dao"
targetProject="IbatorPrj\src">
<property name="enableSubPackages" value="true" />
</daoGenerator>
<table schema="sample" tableName="tab1" domainObjectName="JavaBean1">
<property name="useActualColumnNames" value="false" />
<generatedKey column="ID" sqlStatement="MySql" identity="true" />
</table>
</ibatorContext>
</ibatorConfiguration>
5、配置文件生成完毕后,右键点击这个文件,选择“Generate iBatis Artifact”,然后你就在配置的文件夹下找到自动生成的文件了。
摘要: Tapestry IoC容器从历史上来说,是从从HiveMind继承发展而来,但是HiveMind和目前大红大紫的Spring都不能满足Tapestry的一些特定的需求,所以全新开发了一套IoC的容器。
其核心思想就是使用Java代码自身来解决依赖注入而不是由Xml之类的配置文件来完成,这和Guice的思想是非常相似的,Lewis也承认从Guice那里借鉴了不少。
另外需要说明一下的是,Tapesty还从中国的一个非常古老但又充满哲理的游戏--围棋中借鉴了一些术语和思想。大意是围棋中经常要把棋子走的轻盈(Lightness),让每个棋子都能尽量地高效。编程也一样要轻量(Lightness)。 阅读全文
在应用中一般普通的JavaPojo都是由Spring来管理的,所以使用autowire注解来进行注入不会产生问题,但是有两个东西是例外的,一个是Filter,一个是Servlet,这两样东西都是由Servlet容器来维护管理的,所以如果想和其他的Bean一样使用Autowire来注入的话,是需要做一些额外的功夫的。
对于Filter,Spring提供了DelegatingFilterProxy,所以本文主要讲述Servlet的解决。
1、比较直观但是不大优雅的做法是重写init()方法,在里面使用AutowireCapableBeanFactory来手工告诉Spring:我这个Servlet是需要这样的一个Bean的。具体写法:
public void init(ServletConfig servletConfig) throws ServletException {
ServletContext servletContext = servletConfig.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
AutowireCapableBeanFactory autowireCapableBeanFactory = webApplicationContext.getAutowireCapableBeanFactory();
autowireCapableBeanFactory.configureBean(this, BEAN_NAME);
}
其中,BEAN_NAME就是需要注入的Bean在spring中注册的名字.
这样写的主要问题是就是那个BEAN_NAME,这样写有点主动查找,而不是依赖注入的感觉。
2、创建一个类似于DelegatingFilterProxy那样的代理,通过代理根据配置来找到实际的Servlet,完成业务逻辑功能。
假定我们有一个Servlet名字叫UserServlet,需要注入一个UserManager,伪代码如下:
public class UserServlet extends HttpServlet {
@Autowired(required = true)
private UserManager userManager;
}
第一步:
public class DelegatingServletProxy extends GenericServlet {
private String targetBean;
private Servlet proxy;
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
proxy.service(req, res);
}
@Override
public void init() throws ServletException {
this.targetBean = getServletName();
getServletBean();
proxy.init(getServletConfig());
}
private void getServletBean() {
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
this.proxy = (Servlet) wac.getBean(targetBean);
}
}
第二步:
配置web.xml文件,原来UserServlet的配置大致是这样的:
<servlet>
<servlet-name>userServlet</servlet-name>
<servlet-class>com.sample.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>userServlet</servlet-name>
<url-pattern>/userServlet</url-pattern>
</servlet-mapping>
现在修改为
<servlet>
<servlet-name>userServlet</servlet-name>
<servlet-class>com.sample.DelegatingServletProxy</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>userServlet</servlet-name>
<url-pattern>/userServlet</url-pattern>
</servlet-mapping>
注意,spring是根据Servlet的名字来查找被代理的Servlet的,所以,首先我们要在UserServlet类前面加上@Component,来告诉Srping:我也是一个Bean。如果名称和Web.xml里面定义的不一样的话,可以在这里指定Bean的名字,比如: @Component("userServlet")
在我的随笔 Extjs Tree + JSON + Struts2中我介绍了如何异步加载一个Extjs的树,但是很多网友留言说不能成功操作。现在我自己做了一个所有源代码的包,供大家下载。
有几点事项请大家注意
1、blogjava的文件上载要求单个文件不能超过4M,所以,我把web-inf目录下的所有jar文件删除了。
所有jar文件的列表是:
commons-beanutils-1.7.0.jar
commons-collections-3.2.jar
commons-digester-1.6.jar
commons-lang-2.3.jar
commons-logging-1.1.jar
dom4j-1.6.1.jar
ezmorph-1.0.4.jar
freemarker-2.3.8.jar
javassist-3.8.1.jar
json-lib-2.2.1-jdk15.jar
log4j-1.2.13.jar
ognl-2.6.11.jar
struts2-core-2.0.11.jar
xml-apis-1.0.b2.jar
xwork-2.0.4.jar
注意红色标记的那个jar文件是上次随笔中遗漏了的。这个文件是需要的。
2、blogjava要求上传文件不能是war文件,所以我把war文件改成了rar后缀。
文件的URL: war文件下载
struts2中conventions plugin的url取名规则:
假设有一个类:com.example.actions.HelloWorld,
Struts2会自动搜索所有实现了com.opensymphony.xwork2.Action接口或者在struts.xml中<constant name="struts.convention.package.locators" value="actions"/> 指定的包下的类。
现存HelloWorld只是一个POJO,但是他在actions包下,这样Struts2就认可这是一个Action.
那么URL会是什么呢?是hello-world,类似于:http://localhost:8080/<contextPath>/hello-world.action.
如果你不喜欢这样的自动分配的URL,那么可以在里面的方法使用@Action来改变
@Action("/helloWorld")
public void execute() throws Exception {
return "success";
}
目前新版本的Eclipse在启动应用服务器的时候有一个新的选项:Start the server in profiling mode。
我个人使用的是tomcat6.0
但是我在一开始点击这个按钮的时候,出现了错误提示信息: Could not launch in profiling mode because no profilers are configured.
经过一番搜索,发现要求安装TPTP(Test and Performance Tools Platform),下面我把我的安装步骤简单地列举如下:
1、下载,TPTP的最新版本是4.6.0,下载地址:http://www.eclipse.org/tptp/home/downloads/?ver=4.6.0#tptp-plugins. 这里包括两个部分:runtime和sdk,如果你只是进行profile的运行分析,不对TPTP进行任何扩展,那就只需要下载runtime。下载的时候可以下载相应平台的(比如:windows的)或者下载全部平台的压缩包。我选择的是下载runtime和SDK两个的所有平台的压缩包,
2、安装。安装TPTP是非常简单的,把下载的压缩包解压到Eclipse的安装目录下就行了
3、除了上面的runtime/SDK,还需要安装Agent Controller,第一步当然还是下载了,地址:http://www.eclipse.org/tptp/home/downloads/?ver=4.6.0#rac
同样的,这也有runtime和SDK两部分,选择和步骤1一样。
4、把下载的两个压缩也解压到Eclipse的安装目录下。
5、其他还有一些可选的步骤,比如:Native Logging/Generic Log Adapter之类的东西,我一概都没有下载安装。
6、重新启动Eclipse,点击start server in profiling mode, OK, 成功,出现了一个对话框,让你选择监控的类型,是要监控线程,还是内存,选择一个,然后浏览一下你的网站,就能得到一张列表了。
总的来说,整个过程并不复杂,非常顺利。
1. 数据库的表结构
CREATE TABLE `software` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `version` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`publish_time` datetime NOT NULL,
`software_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
);
2. java的class
---------------------------------------
Software.java
import java.util.LinkedHashSet;
import java.util.Set;
import javax.persistence.Entity;
@Entity
public class Software {
private Long id;
private String name;
private Set<Version> versions = new LinkedHashSet<Version>();
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToMany(cascade = { CascadeType.ALL }, mappedBy="software")
@JoinColumn(name = "software_id")
@Fetch(FetchMode.SUBSELECT)
@OrderBy("id")
public Set<Version> getVersions() {
return version;
}
public void setVersions(Set<Version> Versions) {
this.versions = versions;
}
}
-----------------------------------------------------
Version.java
import java.util.Date;
import javax.persistence.Entity;
@Entity
public class Version{
private Long id;
private Date publishTime;
private Software software;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getPublishTime() {
return publishTime;
}
public void setPublishTime(Date publishTime) {
this.publishTime = publishTime;
}
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
@JoinColumn(name = "software_id")
public Software getSoftware() {
return software;
}
public void setSoftware(Software software) {
this.software = software;
}
}
3. 测试代码
Software software = new Software();
software.setName("Windows");
Version version = new Version;
version.setPublishTime(new Date());
version.setSoftware(software);
software.getVersions().add(version);
software.save();
hibernate会自动生成两条insert语句,一条是software的insert语句,一条是version的insert语句。
同样,如果删除software的话,也会生成两条delete语句
前一段时间,使用了NetBeans的6.5版本,发现诸多不如意的地方,详见:http://www.blogjava.net/usherlight/archive/2009/08/07/247005.html
最近,看到NetBeans发布了新版本6.7,后来我又升级到了6.7.1. 在使用过程中,还是感到了不少不满意的地方。
1. 可选择的或者说内建支持的应用服务器种类偏少。没有Jetty
2. 应用的发布的运行还是一如上次的不顺利。
a. 启动、停止还是有问题,我使用的是jdk1.6+Tomcat6.0,点击服务器停止按钮,有时候并没有真正停止Tomcat,我只有运行Tomcat目录下的bat文件来停止Tomcat
b. 自动的部署有问题,我最后就是因为这个问题而放弃NetBeans的,我的应用修改了之后,总是无法正确部署。Tomcat启动后,进入首页,内容还是没有变化,经常需要先Clean,再Deploy。
不过,NetBeans也有优点,至少内建支持maven,就是一个比较方便的地方。
根据前面的4部分内容,我们已经了解了Tapestry的基本概念,掌握了配置、组件等内容。现在我们通过剖析Tapestry的入门示例来对Tapestry进行一个总体上认识。
1、web.xml
<web-app>
<display-name>app Tapestry 5 Application</display-name>
<context-param>
<!-- The only significant configuration for Tapestry 5, this informs Tapestry
of where to look for pages, components and mixins. -->
<param-name>tapestry.app-package</param-name>
<param-value>t5demo</param-value>
</context-param>
<filter>
<filter-name>app</filter-name>
<filter-class>org.apache.tapestry.TapestryFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>app</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
这就是一个最简单的Tapestry应用所需要配置的内容了。
a.context-param中的tapestry.app-package配置,这在第一部分说过:这是Tapestry要求配置的java package的名称,Tapestry相关内容都需要在这个package下面的pages, services, componets子package下。这里的配置是t5demo
b.TapestryFileter的配置。这个非常容易理解,几乎所有现在流行的web框架都需要一个类似的定义。
2、start.tml以及相应的java class,例子中就是t5demo.pages.Start.java
Start.java非常简单,只定义了一个get方法:
public class Start
{
public Date getCurrentTime()
{
return new Date();
}
}
相应的页面start.tml
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd">
<head>
<title>app Start Page</title>
</head>
<body>
<h1>app Start Page</h1>
<p> This is the start page for this application, a good place to start your modifications.
Just to prove this is live: </p>
<p> The current time is: ${currentTime}. </p>
<p>
[<t:pagelink t:page="Start">refresh</t:pagelink>]
</p>
</body>
</html>
首先要注意在html的tag中加入了Tapestry的命名空间。
第二、${currentTime}就是Tapestry的Tag了,这里就会调用对应class的getCurrentTime方法在页面上显示对应的值。
第三、<t:pagelink>定义一个到自己本身页面的链接,来完成刷新的任务。t:pagelink在本系列的第4部分介绍过。
3、需要的library:
commons-codec.jar
javassist.jar
log4j.jar
slf4j-api.jar
slf4j-log4j.jar
tapestry5-annotations-5.1.0.5.jar
tapestry-core-5.1.0.5.jar
tapestry-ioc-5.1.0.5.jar
4、再加上一个log4j.properties,这就是一个最简单的tapestry应用所需要的全部东西了。
怎么样,感觉还是挺简单的吧。
个人认为flex项目不没有能够迅速普及的原因是:
缺少IDE的支持。adobe做为一个商业公司对flex
builder进行收费当然无可厚非。但是我认为这确实在一定程度上阻碍了flex的发展。做一个对比,jdk和flex
sdk一样都免费了。但是我们有eclipse,
netbeans这样优秀的免费IDE来进行开发,而eclipse是ibm捐献出来的,netbeans是sun提供的。为什么adobe不能这样搞
呢,毕竟赢利途径不止是卖ide一条嘛。
好了,闲话说了一堆,现存转入重点,谈一下我们今天要介绍的内容:
JSF-FLEX项目的目的是为了让用户能够象创建JSF组件一样创建Flex组件。JSF-FLEX项目能够自动生成mxml,swc,swf等文件,
并能把这些组件的值通过JSON+JAVASCRIPT和传递给Managed
Beans。另外还提供渲染工具,能够把JSF-FLEX的组件与普通组件结合起来显示在同一个视图中。
http://code.google.com/p/jsf-flex/
发布谷歌 Chorme 浏览器之后的 9 个月是令人兴奋的。今天,全球超过 3
千万的用户经常性地在使用它。我们为那些活在网络中的人们设计了谷歌 Chrome
浏览器,帮助他们搜索信息、查收邮件、获知新闻、购物,或者与朋友保持联络。然而,浏览器工作在的各种操作系统却诞生于没有互联网的时代——这正是为什么
今天我们要宣布这个新的项目:谷歌 Chrome 操作系统,它是谷歌 Chrome 浏览器的自然延伸,也是我们重新思考操作系统之道的尝试。
谷歌 Chrome 操作系统是一个开放源代码的、精巧的操作系统,它最初会针对上网本。在今年晚些时候,我们将开放它的源代码。在 2010
年下半年,运行谷歌 Chrome
操作系统的上网本就将被带给我们的消费者。我们已经与合作伙伴讨论过这个项目,并将很快与开放源代码社区合作,所以选择现在与大家分享我们的愿景,以让大
家了解我们正在努力成就的目标。
快速、简便和安全是谷歌 Chrome
操作系统的核心特质。我们正在将该操作系统设计得更快速更精巧,数秒间即可启动并将您带入互联网世界。用户界面最小化以避免掩盖您的个人风格,而大多数的
用户体验则将发生于网络之上。就像我们为谷歌 Chrome 浏览器所做的一样,我们要回归本来,彻底地重新设计谷歌 Chrome
操作系统的底层安全架构。如此一来,用户就不必再应对病毒、恶意软件和安全更新。一切皆由系统搞定。
谷歌 chrome 操作系统可运行于 x86 和 ARM
芯片,并且,我们正与各类原始设备商合作,以便于明年为市场带来许多的上网本选择。谷歌 Chrome 运行于一个新的基于 Linux
核心的窗口系统,其软件架构十分简单。对应用开发者们来说,网络即是平台。所有的互联网应用都将自动运行,而新的各种应用可由您最喜欢的网络语言编写。当
然,这些应用不仅可以运行于谷歌 Chrome 操作系统,也可以运行于 Windows、Mac 和Linux
上的任何标准浏览器,从而令开发者拥有任何平台上的最大用户基数。
谷歌 Chrome 操作系统是一个全新的项目,与 Android 无关。Android
从设计之初就跨手机、机顶盒和上网本等多样终端而工作。谷歌 Chrome 操作系统则为那些花费绝大部分时间用于上网的用户而设计,与此同时,谷歌
Chrome 操作系统被设计用于装备从小型上网本到大型台式系统在内的各种计算机。谷歌 Chrome 操作系统和 Android
有一些领域重合,因为我们坚信选择驱动创新,并让包括谷歌在内的每一个人受益。
我们从用户那里听到了很多反馈,他们所传达的信息毋庸置疑——计算机应该更好。人们希望可以即刻获得电子邮件,而不是浪费时间等待计算机启动和浏
览器开启;人们希望他们的计算机总是像第一次买到时那样快速运行;人们希望他们的数据可以唾手可得,无论他们在哪里,也无需担心电脑丢失或者忘记备份文
件。更重要的是,人们不希望花几个小时在每一片新的硬件上以配置他们的计算机,或是不得不为不断的软件更新而烦心。当我们的用户拥有了更佳计算机体验的时
候,这些快乐的用户们就更愿意呆在互联网上,而谷歌即可从中受益。
我们仍有许多工作需要完成,并且为了成就这一愿景,我们显然需要来自开放源代码社区的很多帮助。如果您对这个项目感兴趣或者有其他问题,请查看常
见问题及回答。我们为即将到来的一切而兴奋不已,我们希望您的心情和我们一样。敬请在这个秋季期待更多的更新,并祝愿大家度过一个愉快的夏天。
另外也有人持有不同意见:
LinuxWorld发表分析文章称,有5大原因显示Chrome OS无法成功:
1,上网本所占份额很小
Google计划推出Chrome OS是基于对上网本需求强劲的预期,不过,虽然上网本很重要,但在PC销售中所占比重仍很小。
另外,Google可能还有个想法:对微软来说,他们需要靠出售操作系统获利,而对Google来说,可以免费贡献Chrome。然而,诸如Linux这样的免费操作系统已经存在多年,但Linux在操作系统市场仅占很小份额,约为1%。
Chrome OS主要还是依靠那些上网本用户,他们不需要特别的应用,因此能接受低价但又能提供相当功能的操作系统。
2,微软或许会拼命回击
想想,如果微软宣布2010年Windows 7上网本版将免费提供,那还有多少人会用Chrome OS。至少从理论上来说,任何目前Google能做的事情,微软都能做得更好。
如果微软变得疯狂,任何事情都可能发生。多年来,微软的敌人一直是它自己。像Chrome OS这样的外部刺激或许会帮助微软获得活力,就如免疫系统一样消灭入侵的外来威胁。
3,Google在云计算应用上缺少建树
目前,Google Docs算是Google推出的最好的云计算应用,其它的乏善可陈。而且,就Google Docs来说,仍有很多事情无法做到。如果偶尔使用,倒也不错,但作为完全替代的解决方案,无法满足要求。
4,Chrome并不是一个真正的操作系统
Google希望推出的Chrome OS对用户来说隐藏操作系统的影子,而直接提供方便易用的应用。但究竟有多少操作系统的功能会被牺牲掉?
而另外,如果Chrome越接近真正的操作系统,那它就更像Linux。而这又不是Google希望看到的。
5,兼容性问题
对硬件和软件兼容性问题的解决是微软能一直统治操作系统市场的原因之一。微软在通过推行标准解决兼容性问题的同时也成为垄断者。不过,消费者还是投了微软的票,因为他们不必再担心兼容性问题。
因此,Chrome OS对于那些可以说是一次性使用的上网本来说,或许有点用处,但对整个操作系统市场来说将是微不足道的一份子。
一段时间没有用笔记本上的Eclipse,今天给安装了一个新的3.5版(Galileo)的Eclipse,结果发现启动的时候,跳出一个对话框说是
JVM terminated. Exit Code=-1.
在网上找了一下,最后有效而且最简单的方法是:
删除eclipse目录下的eclipse.ini,然后Eclipse又能启动工作了。
原因应该是Eclipse.ini文件里的启动参数和我的笔记本不配合(个人猜测),删除后,使用默认配置,启动成功。
摘要: Tapestry最基本的组成部分:页面组件 阅读全文
摘要: Tapestry重要特性之一:页面的缓存以及页面间值的传递 阅读全文
摘要: Tapestry的基本配置和目录结构 阅读全文
Ben Gidley进行了一个关于Tapestry5.1.0.5的性能测试。原文见:http://blog.gidley.co.uk/2009/05/tapestry-load-testing-round-up.html
最后,他得出的结论是:
1、Tapestry的速度是比较快的。即使在一定的压力下Tapestry的反应时间也相当短。Tapestry并不总是最快的解决方案,但它对于我(译注:Gidley)已经足够快了。
2、Tapestry没有内存泄漏。我以前曾经听说过Tapestry会占用大量的内存,实际上,正好相反。它使用的内存比struts/jsp还要少。内存使用曲线相当的平坦。
3、Tapestry在表单应用中比struts要快。Tapestry在应用变得非常复杂的时候有一定的优势。这可能利益于其模块池技术。
4、Tapestry不轻易崩溃,即使崩溃,也会恢复。Tapestry在极大压力的情况下确实会相应变慢,但是它会暂停或者遇到瓶颈(译注:我怀疑是作者这里有笔误,从语气和上下文来看,感觉应该不是暂停和没有瓶颈),这的确是一个好事情。另外在压力减轻之后,Tapestry能够自动恢复。
5、更多的CPU并一定会提升性能。在一系列的测试中,性能与CPU的数量并不是线性增长。2个CPU确实比一个CPU的性能翻倍了,但是4个CPU并不比2个CPU的性能翻倍。因此,建议在多个双核CPU的虚拟机上运行,而不是少数的4核CPU上运行。
6、64位比32位要快。这一点很让我惊奇。不管在Solaris还是Linux上,运行在64位JVM中要比在32位JVM要快。
7、Linux要比Open Solaris X86要快。这一点同样让我惊奇。我本来以为性能应该是相似的。
最终的结论是:Tapestry即使是对于一个大并发量的Web应用来说也已经足够快了。如果你的应用有性能问题的话,那么问题应该出在你自己本身的代码上。
上述是原文的翻译。下面是一些评论:
Howard(应该是Tapestry的作者):Taptestry5和Struts相比,我认为差别应该是在反射的使用上(包括在java.bean.Introspector中大量的synchronization)。因此在Struts将查询参数的名称映射成JavaBean属性的时候,会比较耗时。而Tapestry5是不使用反射的,Tapestry在查询参数和JavaBean的属性之间使用一种“预编程”向量组件,也许这就是两者(Tapestry和Struts)的差别。当然,这只是猜想,如果要证实的话,是需要花费很多时间的。我认为OGNL的教训不是说反射很慢,而是在于一个关键代码上的序列存取对于性能的影响是相当大的。
最后一个小提示:我觉得在Tapestry5应用中如果把BeanModel从BeanModelSource中只提取一次,然后给Grid,BeanEditForm等等提供一个可以存取的方法,将会获得相当的性能提升。这样就不是需要每次都重建BeanModel,将减少操作的消耗。
jeverest:我也进行了Tapestry的性能测试,并且同意Tapestry5的性能比较以前版本的要快。
Tapestry5.1经过数个alpha,Beta版的非正式发布,今天终于在主页看到最终正式版5.1.0.5的发布。
这次的版本算得上是比较迅速了,从官方主页中可以看到,第一版5.1.0.1是2月24日发布了,短短3个月不到的时间,发了4个版本,动作不可谓不迅速。
5.1中具有以下几个新特性
1、Tapestry现在开始采用BlackBird作为JavaScript的调试工具
2、一个Ajax的事件请求现在可以返回一个MultiZoneUpdate实例来更新浏览器中的多个区
3、客户端数字的检验实现了国际化
4、相对于5.0.18有显著的性能提升,主要是页面的加载时间和页面的渲染时间大大缩短
5、Tapestry的IoC服务现在既可以是Advised也可以是Decorated
6、Tapestry的服务现在可以注入到Spring的Bean中
7、对于支持Gzip的客户端,Tapestry现在可以压缩返回包
8、有序的和Mapped的配置信息现在可以被重新赋值
9、属性表达式得到加强,现在可以调用有参数的方法,或者创建一个列表
10、IoC的贡献既可以是类(自动生成实例)也可以是具体的实例
11、现在提供了一个简单的可以重写内建服务的方法
这里面最让我感兴趣的还是性能的提升,不知道在展示大数据量的时候性能的提升到底有多少,有机会一定要测试一下。
MySql中设置了Replication后,平常的使用都一直没有问题。
今天,我在Sql Brower中用Sql命令插入了几条数据却没有被复制。
原因是这样的:我在Sql Browser中没有选择我需要数据更新的数据库,而且使用Mysql这个数据库作为当前数据库。
而在Sql中指定了我的数据库名称,这样,我的数据如我所愿地进行了更新。
但是,通过这种方式的操作好像无法被复制。
我思考了一下,觉得应该是Log记录的问题,MySql设置了数据库复制后,有一个Log会记录所有数据库的变更,另一个数据库会根据这个Log来进行同样的数据操作。这样就实行了数据的复制。
我感觉如果你没有使用use <数据库名>这个命令,而是使用其他的数据库作为当前数据库,那么Log的记录就缺失了,因此复制也将不会进行。
1. 安装路径中的目录尽量不要有带空格的, 比如:最好不要安装在programs file下,好像会导致找不到conf下的配置文件
2. 不知道为什么,我无法使用development.conf来启动Resin, 只有resin.conf是可用的。
1. TagLib的运用(Spring Security)
在web.xml中添加:
<servlet>
<servlet-name>JSPSupportServlet</servlet-name>
<servlet-class>org.apache.struts2.views.JspSupportServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
在页面的最上面添加<#assign security=JspTaglibs["http://www.springframework.org/security/tags"] />
使用的时候:
<@security.authorize ifAnyGranted="ROLE_USER,ROLE_ADMIN">
Hello
</@security.authorize>
注意中间用的是句号,而不再是冒号,我一开始在这里没注意,花了不少时间解决这个问题
2. Context Path的取得
在Google中搜了一下,有人提问题,但是没有得到解决,后来查资料才知道应该是这样写的:
${request.contextPath}
3. 字符串的比较
字符串不能直接比较大小,我原来两个日期字符串的比较就需要先转换成日期型
<#if dateString1?date("yyyy-MM-dd HH:mm:ss") < dateString2?date("yyyy-MM-dd HH:mm:ss")>
日期小
</#if>
4. <#if><#else>
在if比较时小于号可以直接使用,但是大于号不行,要写成
<#if a > b>
</#if>
5. 在jfinal中使用map 在jfinal中如果像通常情况下使用map, <#list map?keys as key> ${key} </#list> 会发现不仅是所有键值,所有的java方法名也被打印出来,比如:hashcode, getClass, put, get, clone, equals, containsKey, values等等。 正确的方法是: <#list map.keySet() as key> ${key} </#list>
if the key of the map is not String, such as Integer or other types, we can access the value of the map by map.get( 1 ) instead of map[1]
最近把Tapestry和JSF都研究了一下,最后还是决定选择Tapestry。
最主要的原因还是从性能上的考虑。
Tapestry的5.1版的最主要改动就是想提升性能,而JSF似乎还没有这方面的行动。
而且从Tapestry5.1的版本发布情况来看,动作相当的迅速。预计Final版马上就要出来了。
据Lewis的说法,Tapestry5.0在页面内容比较少的时候,速度几乎和纯JSP页面一样快。只是在展示大量数据的会有一定的性能瓶颈。
所以,他推出5.1来解决这个问题。
而JSF的实现和组件库呈现一种百花齐放的状态,难免会有一些良莠不齐。
当然大家需要选择其中比较好的,但是选型本身就是一件非常头疼的事情。
我选择的是MyFaces+RichFaces,但是我查看了RichFaces的在线Demo后,对其展示速度不是很满意。
不知道是演示网站的问题还是RichFaces本身的问题。
也欢迎大家进行讨论。
不过Tapestry相对JSF而言的一个缺点就是文档不够丰富。毕竟JSF是标准啊。
1. 启动速度比我的MyEclipse7.0要快不少。
2. 部署还是有问题
a. 速度慢,
b. 有一个spring的配置文件没有自动更新
c. 启动和停止glassfish都非常慢
3. 没有内建支持Jetty
最近一段时间研究了一下Tapestry, 确实是一个非常优秀的web框架。到目前为止,我觉得与别的框架比较下来,Tapestry最独到的地方在于Taglib的设计。
根据我的观察,好像只有Tapestry实现了将Taglib嵌入到html控件这样的功能。或许这个说法不准确,不过最是想表达这样一个意思。使用了Tapestry标签的jsp页面是可以在DreamWeaver之类的页面编辑工具中完整地显示出来的。
不你struts或者jsf那样,使用s:或者h: 即:<s:text>...
而Tapestry是:<input t:type="">
当然,其事件驱动的思想也是与大多数的Web框架不同的,不过JSF在这一点上和它是非常类似的。关键是jsf是JEE的标准,而且得到了不少开源组织的
拥护,产生了myfaces之类的实现,而且有了大量诸如:fichfaces,icefaces,restfaces等等的components
package. 在这一点上是Tapesty无法比拟的。
Struts2的官方文档看似琳琅满目,但实际上并不完备,许多细节问题并没有深入涉及,部分内容甚至还有错误(可参见:http://www.blogjava.net/usherlight/archive/2008/12/30/249143.html)。这次主要是补充一下,使用了ZeroConfig插件情况下,如何返回Stream类型的配置。
在ZeroConfig+CodeBehind结合使用的情况下,Struts.xml中基本上已经空无一物了。配置基本上使用Annotation在Java的类中注释完成。
@Parent("default")
@Result(name="rawFile", value="inputStream", type=StreamResult.class, params={"contentType", "text/html", "inputName", "inputStream", "", "", "bufferSize", "1024"})
public class FileDownloadAction {
public String execute() throws Exception {
inputStream = new FileInputStream("c:\\temp\\file.txt");
}
private InputStream inputStream;
//... getter and setter
}
这里最重要的是Result里面params的写法,params后面的大括号中,奇数个字符串是key值,偶数个字符串是value值。
其中,inputName的值-inputStream要和Class中的InputStream的属性名一致,而Result中的value的值也要与之一致。
今天试了一下引用url作为<s:a>的href,结果页面根本不能正确显示,后台log里面出现了一大片的错误,大意是<s:param>的用法错误。但是我的写法是完全copy在线文档http://struts.apache.org/2.x/docs/a.html
具体内容就是下面这一部分:
<s:url id="testUrlId" namespace="/subscriber" action="customField" method="delete">
<s:param name="customFieldDefinition.id" value="${id}"/>
</s:url>
经过尝试发现实际上应该这样写: <s:param name="customFieldDefinition.id" value="id"/>
struts2的文档一直被大家所指责,看来这种指责不无道理啊
我使用的是annotation方式的hibernate配置。结果在启动Tomcat的时候报错:
Invocation of init method failed; nested exception is org.hibernate.HibernateException: cannot simultaneously fetch multiple bags
解决方法:
去除Annotation中的所有FetchType="EAGAR"
NetBeans从功能来上,是相当不错的。而Eclipse则有一些成也插件,败也插件的感觉,至少存在插件的版本依赖造成混乱的抱怨。
但是NetBeans的在使用上的体验与eclipse相比还是有较大的差距(个人感觉).
首先令我感到不爽的是,netbeans不能正常停止tomcat.需要我手动在任务管理器里终止进程.
其次,netbeans的部署好像有些问题,部署速度比较慢,deploying的进度条总在那里闪,需要较长的时间才部署完毕。而且重新部署的策略也让人疑惑,感觉不是有了更新才部署,而是定时部署,因为我过了一段时间我就发现deploying的进度条出现了。最让人不爽的是部署有问题。我更新了 applicationContext-security.xml,点击clean and build,结果系统运行不正确,我查了半天才发现build目录下的这个文件根本没有改变。
在视图的查看上,netbeans也没有eclipse方便灵活,在eclipse中我喜欢将源文件(package presentation)设置为hierarchical, 这样在package explorer里面,视图显得比较简洁。另外,eclipse可以设置代码窗口与文件窗口里的文件连动。这两个功能我在netbeans里还没有找到。
mysql安装调试完毕,正式投入运行后,马上进行了mysql备份任务的生成。结果第二天一看,并没有能够如愿地生成备份文件。
马上开始查找原因。首先在mysql administrator里立即运行备份任务,没有问题,备份文件很快就生成了。但备份任务就是不能正确执行。
在事件查看器里发现了mysql的错误日志,root@localhost(password: no)access denied, error number: 1045。
奇怪,root@localhost无法登录数据库?可是登录mysql administrator,并且在里面单独运行backup都是正常的啊。
冷静一下,在windows控制面板的计划任务里,找到数据库备份的计划任务(Mysql5.X的备份计划实际上是生成了一个windows的计划任务,执行其设置好的脚本),查看了一下脚本,并没有什么问题。
再仔细研究了一下,发现问题应该是在password:no上,相当于试图不提供密码而使用root@localhost进行备份,所以出错了。那么如何改正呢。
最后发现问题是在;mysql_user_connection.xml里。这个文件里包含了mysql登录用的别名信息。结果不知道是什么原因这个文件里相同的别名出现了两次,第一次的配置里密码为空,而第二次的配置是正确的。
<last_connection>2</last_connection>
<password_storage_type>3</password_storage_type>
<user_connection>
<connection_name></connection_name>
<username>root</username>
<hostname>localhost</hostname>
<port>3306</port>
<schema></schema>
<advanced_options/>
<storage_path></storage_path>
<notes></notes>
<connection_type>0</connection_type>
<storage_type>2</storage_type>
<password_storage_type>3</password_storage_type>
<password/>
</user_connection>
<user_connection>
<connection_name>proddb</connection_name>
<username>root</username>
<hostname>localhost</hostname>
<port>3306</port>
<schema>message</schema>
<advanced_options/>
<storage_path></storage_path>
<notes></notes>
<connection_type>0</connection_type>
<storage_type>2</storage_type>
<password_storage_type>3</password_storage_type>
<password/>
</user_connection>
<user_connection>
<connection_name>proddb</connection_name>
<username>root</username>
<hostname>localhost</hostname>
<port>3306</port>
<schema>message</schema>
<advanced_options/>
<storage_path></storage_path>
<notes></notes>
<connection_type>0</connection_type>
<storage_type>1</storage_type>
<password_storage_type>3</password_storage_type>
<password>9D203859E</password>
</user_connection>
而登录mysql administrator时,根据last_connection的值,使用的是proddb的第二个配置。所以可以正常登录,而且在里面执行脚本,备 份都没有问题,而windows执行计划任务时,通过别名proddb在mysql_user_connection.xml中查找,找到的是第一个,其 中没有密码信息,所以报错。
问题找到了,解决就容易了,删除mysql_user_connection.xml中的proddb的第一个配置。备份计划任务果然能够正确地执行了。
今天尝试使用了一下两款MSN的插件应用:Msn Shell和Msn Plus! Live。两者都能解决多账号登录的要求。
其中Msn Shell是国人产品而Msn Plus! Live是法国人开发的产品,但是其I18N做的还是不错,内置了许多语言。
我首先尝试了Msn Plus! Live,第一感还是不错的,安装过程很顺利。安装好了之后,马上就是相关的设置。
设置完毕后,就可以直接使用了。其中我比较感兴趣的是快捷文字输入。
但是我马上发现一个问题:不知道为什么Msn Plus! Live总是占用15%左右的CPU,这一点令我十分不爽。直接从电脑从喀嚓出去。
接下来安装Msn Shell。安装过程也是一样的顺利,功能明显比Msn Plus! Live要多一点。在联系人上面多了一条“最近联系人”。
还多了一个“天气预报”可以自由选择国内的城市。另外还可以设置屏蔽MSN里面的广告。
但也有一点不爽的是在下面多了一条MSN Shell的工具条,我并不需要,但无法取消。
最后结论当然是选择Msn Shell。
right click project -> property -> source -> source/binary format -> choose JDK 5 instead of JDK 1.4
最好的办法是在新建项目时,在下拉框选择J2EE 1.4后,不要根据NetBeans提示的勾选set source same as JDK,这样会自动将Source设置为JDK 5而不是JDK 1.4.
01. download SpringSide 3.0.4 and extract to a folder and extract to a folder-SpringSide304
02. cd SpringSide304, mvn compile
03. mvn clean install
04. cd examples\mini-web\bin
05. start nexus, \tools\nexus\nexus-webapp-1.1.0\bin\jsw\windows-x86-32\Nexus.bat
06. run copy-jar.bat, all the jar files will be generated in exmples\mini-web\webapp
07. slf4j: jul-to-slf4j-1.5.6.jar
08. dozer in sourceforge.net
09. copy all the java source code in directory: modules\core\src\main\java\
10. copy all the java source code in directory: examples\mini-web\src\main\java
11. copy all the resource file in directory:examples\mini-web\src\main\resources
12. copy all the file in examples\mini-web\webapp
13. create database and run script in directory:
examples\mini-web\src\main\sql\derby, if the database you use isn't
derby, should modify the script
14. modify the the datasource in ApplicationContext.xml, the database name, user name, user password
15. copy the database jdbc driver package(for example: mysql-connector-java-X.jar) to lib
16. mini-web.log is in tomcat\logs
17. click run button in Netbeans.
UltraEdit是一个功能非常强大的文本编辑工具。
但是,它不是免费的。
在网上搜索了阵,对比了不少工具,诸如EMacs、Crimson Editor等等。最后选定了PSPad
主要是因为:
1、界面操作与UltraEdit比较类似,其他很多工具与日常的相差比较大。
2、功能与UltraEdit相比,并没有什么差别,我目前发觉得比较大的差别就在于列模式的选择上。
3、最大的优点就是免费。
在网上搜索对比了一番,最后觉得还是EssentialPIM这个软件最适合我的需要。
我主要就是想找一个能够每天记录一下当天的工作日志,最好能有提醒功能(Schedule)
EssentialPIM完全能够胜任这个工作。
其主要功能有:
Schedule
To-do List
Notes
界面简单清晰。另外还有一个密码保护的功能。
非常适合我。
推荐一把。
地址:www.essentialpim.com
1.Tools to satisfy your calendar, contact management, to do list and notes needs.
2.Synchronization with Outlook, Windows Mobile devices, Palm, iPOD, Google Calendar.
3.Simple printout of any or all modules and quick export of your data into the most useful formats (iCal, vCard, HTML).
4.Strong data protection using Advanced Encryption Standard (AES) algorithm.
5.Intuitive interface in many languages including German, Italian, French and Spanish.
EssentialPIM有Pro版和Free版,功能自然是有差别了,但对于我来说,Free就完全够用了。
[WARNING] POM for 'org.hibernate:jtidy:pom:r8-20060801:runtime' is invalid. It will be ignored for artifact resolution. Reason: Parse error reading PO
M. Reason: TEXT must be immediately followed by END_TAG and not START_TAG (position: START_TAG seen ...<licenses>\n\t\t\t<license>... @12:13) for pro
ject org.hibernate:jtidy at F:\Document\Datafile\repository\org\hibernate\jtidy\r8-20060801\jtidy-r8-20060801.pom
把原来的maven的local repository里面org\hibernate\jtidy\r8-20060801\jtidy-r8-20060801.pom目录下的pom.xml文件中
<licenses>
<licenses>
<license>
<name>Java HTML Tidy License</name>
<url>http://svn.sourceforge.net/viewvc/*checkout*/jtidy/trunk/jtidy/LICENSE.txt?revision=95</url>
<distribution>repo</distribution>
</license>
</licenses>
</licenses>
改为:
<licenses>
<license>
<name>Java HTML Tidy License</name>
<url>http://svn.sourceforge.net/viewvc/*checkout*/jtidy/trunk/jtidy/LICENSE.txt?revision=95</url>
<distribution>repo</distribution>
</license>
</licenses>
也就是删除多余的<licenese>起止标签.
应该是central的repository里面的这个文件有问题。
在FreeMarker中可以使用Strust2的标签set调用Java类的方法来获取返回值
<@s.set name="val" value="@com.test.utils.Property@getInstance().get('Value')"/>
然后就可以使用以下的语句在FreeMarker中取得val的值
<#if (Request.appType)?default("") == "OK">
</#if>
使用方法:
var now = new Date();
now.format("m/dd/yy");
// Returns, e.g., 6/09/07
// Can also be used as a standalone function
dateFormat(now, "dddd, mmmm dS, yyyy, h:MM:ss TT");
// Saturday, June 9th, 2007, 5:46:21 PM
// You can use one of several named masks
now.format("isoDateTime");
// 2007-06-09T17:46:21
// ...Or add your own
dateFormat.masks.hammerTime = 'HH:MM! "Can\'t touch this!"';
now.format("hammerTime");
// 17:46! Can't touch this!
// When using the standalone dateFormat function,
// you can also provide the date as a string
dateFormat("Jun 9 2007", "fullDate");
// Saturday, June 9, 2007
// Note that if you don't include the mask argument,
// dateFormat.masks.default is used
now.format();
// Sat Jun 09 2007 17:46:21
// And if you don't include the date argument,
// the current date and time is used
dateFormat();
// Sat Jun 09 2007 17:46:22
// You can also skip the date argument (as long as your mask doesn't
// contain any numbers), in which case the current date/time is used
dateFormat("longTime");
// 5:46:22 PM EST
// And finally, you can convert local time to UTC time. Either pass in
// true as an additional argument (no argument skipping allowed in this case):
dateFormat(now, "longTime", true);
now.format("longTime", true);
// Both lines return, e.g., 10:46:21 PM UTC
// ...Or add the prefix "UTC:" to your mask.
now.format("UTC:h:MM:ss TT Z");
// 10:46:21 PM UTC
/*
* Date Format 1.2.2
* (c) 2007-2008 Steven Levithan <stevenlevithan.com>
* MIT license
* Includes enhancements by Scott Trenda <scott.trenda.net> and Kris Kowal <cixar.com/~kris.kowal/>
*
* Accepts a date, a mask, or a date and a mask.
* Returns a formatted version of the given date.
* The date defaults to the current date/time.
* The mask defaults to dateFormat.masks.default.
*/
var dateFormat = function () {
var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
timezoneClip = /[^-+\dA-Z]/g,
pad = function (val, len) {
val = String(val);
len = len || 2;
while (val.length < len) val = "0" + val;
return val;
};
// Regexes and supporting functions are cached through closure
return function (date, mask, utc) {
var dF = dateFormat;
// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
if (arguments.length == 1 && (typeof date == "string" || date instanceof String) && !/\d/.test(date)) {
mask = date;
date = undefined;
}
// Passing date through Date applies Date.parse, if necessary
date = date ? new Date(date) : new Date();
if (isNaN(date)) throw new SyntaxError("invalid date");
mask = String(dF.masks[mask] || mask || dF.masks["default"]);
// Allow setting the utc argument via the mask
if (mask.slice(0, 4) == "UTC:") {
mask = mask.slice(4);
utc = true;
}
var _ = utc ? "getUTC" : "get",
d = date[_ + "Date"](),
D = date[_ + "Day"](),
m = date[_ + "Month"](),
y = date[_ + "FullYear"](),
H = date[_ + "Hours"](),
M = date[_ + "Minutes"](),
s = date[_ + "Seconds"](),
L = date[_ + "Milliseconds"](),
o = utc ? 0 : date.getTimezoneOffset(),
flags = {
d: d,
dd: pad(d),
ddd: dF.i18n.dayNames[D],
dddd: dF.i18n.dayNames[D + 7],
m: m + 1,
mm: pad(m + 1),
mmm: dF.i18n.monthNames[m],
mmmm: dF.i18n.monthNames[m + 12],
yy: String(y).slice(2),
yyyy: y,
h: H % 12 || 12,
hh: pad(H % 12 || 12),
H: H,
HH: pad(H),
M: M,
MM: pad(M),
s: s,
ss: pad(s),
l: pad(L, 3),
L: pad(L > 99 ? Math.round(L / 10) : L),
t: H < 12 ? "a" : "p",
tt: H < 12 ? "am" : "pm",
T: H < 12 ? "A" : "P",
TT: H < 12 ? "AM" : "PM",
Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
};
return mask.replace(token, function ($0) {
return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
});
};
}();
// Some common format strings
dateFormat.masks = {
"default": "ddd mmm dd yyyy HH:MM:ss",
shortDate: "m/d/yy",
mediumDate: "mmm d, yyyy",
longDate: "mmmm d, yyyy",
fullDate: "dddd, mmmm d, yyyy",
shortTime: "h:MM TT",
mediumTime: "h:MM:ss TT",
longTime: "h:MM:ss TT Z",
isoDate: "yyyy-mm-dd",
isoTime: "HH:MM:ss",
isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};
// Internationalization strings
dateFormat.i18n = {
dayNames: [
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
],
monthNames: [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
]
};
// For convenience
Date.prototype.format = function (mask, utc) {
return dateFormat(this, mask, utc);
};
补充说明:
Mask |
Description |
d |
Day of the month as digits; no leading zero for single-digit days. |
dd |
Day of the month as digits; leading zero for single-digit days. |
ddd |
Day of the week as a three-letter abbreviation. |
dddd |
Day of the week as its full name. |
m |
Month as digits; no leading zero for single-digit months. |
mm |
Month as digits; leading zero for single-digit months. |
mmm |
Month as a three-letter abbreviation. |
mmmm |
Month as its full name. |
yy |
Year as last two digits; leading zero for years less than 10. |
yyyy |
Year represented by four digits. |
h |
Hours; no leading zero for single-digit hours (12-hour clock). |
hh |
Hours; leading zero for single-digit hours (12-hour clock). |
H |
Hours; no leading zero for single-digit hours (24-hour clock). |
HH |
Hours; leading zero for single-digit hours (24-hour clock). |
M |
Minutes; no leading zero for single-digit minutes.
Uppercase M unlike CF timeFormat 's m to avoid conflict with months. |
MM |
Minutes; leading zero for single-digit minutes.
Uppercase MM unlike CF timeFormat 's mm to avoid conflict with months. |
s |
Seconds; no leading zero for single-digit seconds. |
ss |
Seconds; leading zero for single-digit seconds. |
l or L |
Milliseconds. l gives 3 digits. L gives 2 digits. |
t |
Lowercase, single-character time marker string: a or p.
No equivalent in CF. |
tt |
Lowercase, two-character time marker string: am or pm.
No equivalent in CF. |
T |
Uppercase, single-character time marker string: A or P.
Uppercase T unlike CF's t to allow for user-specified casing. |
TT |
Uppercase, two-character time marker string: AM or PM.
Uppercase TT unlike CF's tt to allow for user-specified casing. |
Z |
US timezone abbreviation, e.g. EST or MDT. With non-US timezones or in the Opera browser, the GMT/UTC offset is returned, e.g. GMT-0500
No equivalent in CF. |
o |
GMT/UTC timezone offset, e.g. -0500 or +0230.
No equivalent in CF. |
S |
The date's ordinal suffix (st, nd, rd, or th). Works well with d .
No equivalent in CF. |
'…' or "…" |
Literal character sequence. Surrounding quotes are removed.
No equivalent in CF. |
UTC: |
Must
be the first four characters of the mask. Converts the date from local
time to UTC/GMT/Zulu time before applying the mask. The "UTC:" prefix
is removed.
No equivalent in CF. |
And here are the named masks provided by default (you can easily change these or add your own):
Name |
Mask |
Example |
default |
ddd mmm dd yyyy HH:MM:ss |
Sat Jun 09 2007 17:46:21 |
shortDate |
m/d/yy |
6/9/07 |
mediumDate |
mmm d, yyyy |
Jun 9, 2007 |
longDate |
mmmm d, yyyy |
June 9, 2007 |
fullDate |
dddd, mmmm d, yyyy |
Saturday, June 9, 2007 |
shortTime |
h:MM TT |
5:46 PM |
mediumTime |
h:MM:ss TT |
5:46:21 PM |
longTime |
h:MM:ss TT Z |
5:46:21 PM EST |
isoDate |
yyyy-mm-dd |
2007-06-09 |
isoTime |
HH:MM:ss |
17:46:21 |
isoDateTime |
yyyy-mm-dd'T'HH:MM:ss |
2007-06-09T17:46:21 |
isoUtcDateTime |
UTC:yyyy-mm-dd'T'HH:MM:ss'Z' |
2007-06-09T22:46:21Z |
A couple issues:
- In the unlikely event that there is ambiguity in the meaning of your mask (e.g.,
m followed by mm ,
with no separating characters), put a pair of empty quotes between your
metasequences. The quotes will be removed automatically.
- If you need to include literal quotes in your mask, the following rules apply:
- Unpaired quotes do not need special handling.
- To
include literal quotes inside masks which contain any other quote marks
of the same type, you need to enclose them with the alternative quote
type (i.e., double quotes for single quotes, and vice versa). E.g.,
date.format('h "o\'clock, y\'all!"')
returns "6 o'clock, y'all". This can get a little hairy, perhaps, but I
doubt people will really run into it that often. The previous example
can also be written as date.format("h") + "o'clock, y'all!" .
通过上两篇文章的研究,
详见:
我的struts2项目性能调优三步曲:http://www.blogjava.net/usherlight/archive/2008/07/01/211869.html
我的struts2项目性能调优三步曲(续):http://www.blogjava.net/usherlight/archive/2008/07/12/214462.html
得出的结论是:影响Struts2性能的原因在于Ognl的Value Stack的性能不佳。那么如果解决呢:
* 我首先尝试使用JSF。
一开始选择JSF的原因主要是:
1、Stuts2自己提供了JSF的Plugin
2、JSF是Sun作为标准提出,而且已经通过的。从Google的趋势搜索上也可以看出,搜索JSF的人在增多。
3、JSF作为一种以组件为基础的Web Framework有其独到之处,其内建的和其他许多开源的组件使用起来相当方便、强大。当然,对于不同的应用来说也有不利之处(后面会提到),但是如果能够坚持长期使用,逐渐积累组件库的话,JSF是一个很好的选择。
4、JSF的文档(或者说是书籍)还是比较多的。
经过测试使用后,发现其性能与Struts2相比确实提升不少。但是后来遇到了一个问题,所以最后还是放弃了JSF。这个问题是关于JSF的DataTable的,JSF提供的DataTable其实使用起来很方便,可定制化程度也不错,只是刚好缺少了我所希望的功能(也可能是我不知道如何实现)。我的应用中的DataTable是一个动态的结果集,也就是说输出的列是不能预先确定的,而DataTable却要求先声明好所有的DataColumn,我不知道如何解决这个问题。所以最后放弃了JSF。
* 我的第二个选择是FreeMarker
选择FreeMarker的原因是:
1、FreeMarker是Struts2缺少的模板引擎,Struts2的标签大部分是使用FreeMarker的,使用FreeMarker的话,连Plugin都省去了。
2、FreeMarker相对比较轻量级、因为他本身只是一个模板引擎,与JSF这样一个大而全的WebFramework相对,轻巧多了。
3、FreeMarker的学习起来非常容易,只要把他网站上的Document过一遍,基本上就OK了。
4、FreeMarker虽然体积小,功能还是相当强的,I18N,Converter之类的东西基本都全了,至少我所需要的功能全有。
5、FreeMarker相当灵活,他不象JSF把底层的东西封装了以后,暴露出一些属性可以设置,如果你需要的属性不能设置,你就没有办法了。在FreeMarker你直接操作最底层的东西,拥有很大的灵活性。当然,牺牲了一些方便性,比如,要用FreeMarker生成一个下拉框,就需要较多的工作量了。
测试之后,使用FreeMarker的性能很不错,在大数据量操作的情况下,至少一个数量级的性能提升。
主要原因是freeMarker的值直接从action中取得的,所以避开了ognl的stack value.
* 我的最终结论,如果要在Struts2中,展示或者操作大量数据,强烈推荐使用FreeMarker。
原文链接:http://www.theserverside.com/news/thread.tss?thread_id=50360
Jt2.7已经发布了,Jt是一个针对Java应用快速实现的面向模式的框架。Jt已经在数个重要的大型系统被采用。Jt实现了很多个被熟知的模式,包括:Dao,GoF的设计模式以及一些J2EE的模式。
Jt2.7的部分部件的功能得到了增强,而且增加了一个Jt自动化向导。Jt向导一个在Jt框架上建起来的应用,能够自动生成应用的框架结构。Jt向导能够在设计模式(包括Jt消息、DAO,MVC,GoF)的基础上自动生成应用模块。目前Jt向导实现了与MVC Struts和DAO Hibernate的集成。DAO的映射文件,Struts的配置文件,视图(JSPs),Java类都能够使用Jt向导来自动生成。
具体的功能包括:
* 实现了J2EE设计模式,包括J2EE business delegate,J2EE Session Facade,J2EE Service Locator和J2EE Value Object。
* 通过实现Web Service适配器和代理集成Web Service。Jt messaging API极大地简化了web service的开发和部署。
* 与business process modeling(BPM)的集成。Jt框架提供了一个jBPM的适配器。jBPM是一个BPM技术的开源实现。Jt应用现在可以使用流程图来模块化,这是一个非常好的模块化业务流程的方法。
* 与MVC设计模式和Ajax的集成。统一化的Jt模块和适配器提供了Jt框架API和上述两种技术间的透明接口。业务逻辑可以由Jt框架模块或BPM业务流程来实现。
* 与Hibernate的集成。Jt适配器提供了与Hibernate的透明接口。
* 与JDBC的集成
* 通过实现命令模式,支持Log,排队机制和操作的回退。
* 与JavaMail的集成
* 与EJB的集成。EJB客户端可以透明地存取远程框架对象。比EJB开发要简单的多。另外还实现了J2EE Service Locator模式。
* 方便的定制应用。主要通过配置文件完成:对象的属性可以从资源文件中加载。
* 与JSP的集成
* 通过XML适配器、助手、和内建的bean/XML映射与XML API的集成
Jt的在线文档: http://jt.dev.java.net/servlets/ProjectDocumentList
其他的一些信息可以在这里找到:http://jt.dev.java.net
前一阵子使用了DHtmlx的Tree,视觉效果不错,功能也不弱。具体参见: http://dhtmlx.com
现在把Struts2结合DHtmlxTree的经验心得整理一下,发表出来:
一、Struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="demo" extends="struts-default">
<action name="menu" method="execute" class="demo.TreeMenuAction">
<result>/WEB-INF/menu.jsp</result>
</action>
</package>
</struts>
二、tree.jsp
<%@ taglib prefix="s" uri="/struts-tags"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path;
%>
<html>
<head>
<title>Main Page</title>
<!-- 注意此处的路径需要根据各自具体情况修改 -->
<link rel="STYLESHEET" type="text/css" href="styles/dhtmlxtree.css">
<script src="scripts/dhtmlxcommon.js"></script>
<script src="scripts/dhtmlxtree.js"></script>
</head>
<body onload="loadTree(); " style="padding: 0; margin: 0; overflow: hidden; height: 100%;">
<script>
String.prototype._dhx_trim = function(){
return this.replace(/ /g," ").replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g,"");
}
/* init tree */
var tree;
function loadTree(){
tree=new dhtmlXTreeObject("doctree_box","100%","100%",0);
tree.setImagePath("images/"); <!-- 注意此处的路径需要根据各自具体情况修改 -->
tree.setOnClickHandler(
function(id){ openPathDocs(id); }
);
tree.loadXML("<%=basePath%>/menu.do");
}
/* open path funtion */
function openPathDocs(id){
if (tree.getUserData(id, "thisurl") != null ){
window.frames.sampleframe.location.href = "<%=path%>/" + tree.getUserData(id, "thisurl") + ".do";
return;
}
}
function autoselectNode(){
tree.selectItem(node,true);
tree.openItem(node);
}
</script>
<table width="100%" height="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td valign="top" width="276">
<div id="doctree_box" style="width: 274px; height: 100%;"></div>
</td>
<td width="10" background="images/grid.gif">
</td>
<td align="right">
<iframe id="sampleframe" name="sampleframe" width="100%" height="99%" frameborder="0" src="blank.html" style="border: 0px solid #cecece;"></iframe>
</td>
</tr>
</table>
</body>
</html>
上面的JavaScript基本上是从dhtmlx的例子中修改而来,理解起来并不复杂,只有
String.prototype._dhx_trim = function(){
return this.replace(/ /g," ").replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g,"");
}
这一段代码含义不明。
三、Action
package demo;
public class TreeMenuAction {
private String menuString;
public String execute() {
StringBuffer buf = new StringBuffer();
buf.append("<tree id=\"0\">");
buf.append(" <item text=\"Java\">");
buf.append(" <item text=\"Thinking in java\">");
buf.append(" <userdata name=\"thisurl\">java_tij.do</userdata>");
buf.append(" </item>");
buf.append(" <item text=\"Head first design pattern\">");
buf.append(" <userdata name=\"thisurl\">java_hfdp.do</userdata>");
buf.append(" </item>");
buf.append(" </item>");
buf.append(" <item text=\"Fiction\">");
buf.append(" <item text=\"Harry Porter\">");
buf.append(" <userdata name=\"thisurl\">fiction_hp.do</userdata>");
buf.append(" </item>");
buf.append(" <item text=\"Oliver Twist\">");
buf.append(" <userdata name=\"thisurl\">fiction_ot.do</userdata>");
buf.append(" </item>");
buf.append(" </item>");
buf.append("</tree>");
menuString = buf.toString();
return "success";
}
public String getMenuString() {
return menuString;
}
public void setMenuString(String menuString) {
this.menuString = menuString;
}
}
四、menu.jsp
<%@ page contentType="text/xml;charset=UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<s:property value="menuXmlString" escape="false"/>
过程是这样的:首先在浏览器地址栏中输入:http://......./tree.jsp
展示tree.jsp,在load函数中调用menu.do
menu.do对应TreeMenuAction,返回menu.jsp,而menu.jsp只包含menuString的值,注意在menu.jsp中的escape="false"
最近,继续研究了Struts2性能的调优方法,总结了一下,得出新三步曲:
4. 使用FreeMarker的最新版本2.3.13,因为在版本2.3.11中,FreeMarker针对性能进行了改进,以下是FreeMarker2.3.11的release notes:
2.3.11
Date of release: 2007-12-04
This release contains several performance and usability improvements.
5. ognl2.7
所称ognl2.7相对于2.6在性能上有了“显著”的提升,于是下载了2.7以及2.7所需要的javassist-3.8.0.GA.jar
其实,经过上面2个步骤,我并没有发现应用的性能有显著的改善,可能我的页面中从ValueStack中的存取操作并不是特别多,也不是特别的复杂,所以,Ognl对我的影响并不明显。
6. 最后使用了JProfiler对Tomcat进行了监控,最后发现问题在自定义模板上,我将页面的自定义模板全部删除,果然页面的响应速度有了较大的提升。
前一段时间有反映说是一个使用了struts2的生产系统的页面显示速度太慢。登录后发现确实如此,于是进行了一番性能调优的研究和测试。
一,根据struts2官方的性能调优说明进行了一些参数的修改。
http://struts.apache.org/2.x/docs/performance-tuning.html
http://cwiki.apache.org/WW/performance-tuning.html
Turn off logging and devMode.(关闭logging和Devmode)
这个当然没问题,但是全部关闭logging不现实,我只是关闭了struts2相关package的logging
Do not use interceptors you do not need.
把struts.xml中不需要的interceptor统统删除
Use the correct HTTP headers (Cache-Control & Expires).
不确定应该如何修改
Copy the static content from the Struts 2 jar when using the Ajax theme (Dojo) or the Calendar tag.
关于这点,后面会提到
Create a freemarker.properties file in your WEB-INF/classes directory.
照做
Create the freemarker.properties file and add the following setting (or whatever value you deem fitting):
template_update_delay=60000
照做
Enable Freemarker template caching
As of Struts 2.0.10, setting the property struts.freemarker.templatesCache to true will enable the Struts internal caching of Freemarker templates. This property is set to false by default.
照做
进行上述修改后,发现页面打开的速度并没有明显的提高.
二,此时我已经基本锁定网页打开速度慢的原因与ajax(或者说是dojo)有关。因为dojo的js库大概有450K左右,先尝试使用gzip压缩javascript,减小传输量,看能否加快页面的加载速度
在Tomcat的server.xml的connector中添加如下配置,激活gzip功能
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml,text/javascript,application/x-javascript,application/javascript"
进行上述修改后,发现页面打开的速度还是没有明显的提高.
三,经过上述两个实验,觉得应该是struts2所封闭的dojo的性能问题了。于是引入JQuery.
JQuery的js文件最小是55K, gzip后应该更小,页面的响应速度明显改善(一个数量级以上的提高),主要原因在于与服务器交互的处理上极大地提升了效率。而且页面处理代码更加简洁明了。
最后,我删除了所有的<s:head theme="ajax"/>和 <s:head/>(如果页面中加入<s:head />,那么在Struts2生成的html中后包含dojo.js),使用JQuery来完成所有的Ajax和javascript功能。
在利用网页展示查询结果,经常会遇到要求导出成Excel的需求。采用这种方法可以定制输出的格式和内容(还不支持合并单元格和公式),生成真正的Excel格式(不是csv)的Excel。
一、struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.i18n.encoding" value="UTF-8"/>
<package name="demo" extends="struts-default">
<action name="excel" method="execute" class="demo.ExcelAction">
<result name="excel" type="stream">
<param name="contentType">application/vnd.ms-excel</param> <!-- 注意这里的ContentType -->
<param name="inputName">excelStream</param> <!-- 这里需要和Action里的变量名一致 -->
<param name="contentDisposition">filename="standard.xls"</param>
<param name="bufferSize">1024</param>
</result>
</action>
</package>
</struts>
二、Struts2的 Action
package demo;
public class ExcelAction {
private InputStream excelStream; // 需要生成getter和setter
public String execute() throws Exception {
StringBuffer excelBuf = new StringBuffer();
excelBuf.append("BookName").append("\t").append("Year").append("\t").append("author").append("\n");
excelBuf.append("Thinking in Java").append("\t").append("2001").append("\t").append("Eckel").append("\n");
excelBuf.append("Spring in action").append("\t").append("2005").append("\t").append("Rod").append("\n");
String excelString = excelBuf.toString();
logger.debug("result excel String: " + excelString);
excelStream = new ByteArrayInputStream(excelString.getBytes(), 0, excelString.length());
return "excel";
}
// getter and setter
...
}
三、Jsp页面
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<s:head />
</head>
<body>
<s:form action="" method="post">
<s:submit key="button.submit"/>
</s:form>
</body>
</html>
iBatis自己带了一个simple的数据库连接池,基本的功能都有。但是在处理部分数据库(比如mysql)的连接空闲时间太长(mysql是8小时)自动超时的时候,就比不上象c3p0这样的连接池软件了(c3p0能自动处理数据库连接被关闭的情况)。
我目前采用的方法是iBatis本身提供的一种算得上是取巧的办法,基本思想就是每隔一段时间往数据库发一条查询语句,这样使得数据库空闲时间不会太长,而使得其自动关闭。
方法是在SqlMapConfig.xml的dataSource进行如下配置:
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="${jdbc.driverClassName}"/>
<property name="JDBC.ConnectionURL" value="${jdbc.url}"/>
<property name="JDBC.Username" value="${jdbc.username}"/>
<property name="JDBC.Password" value="${jdbc.password}"/>
<property name="Pool.PingEnabled" value="true"/>
<property name="Pool.PingQuery" value="select 1"/>
<property name="Pool.PingConnectionsOlderThan" value="0"/>
<property name="Pool.PingConnectionsNotUsedFor" value="3600000"/>
</dataSource>
开始的3行是关于数据库连接信息的,不需要说明了。
Pool.PingEnabled:是用于设置开启是否允许检测连接状态
Pool.PingQuery:是用于检测连接的查询语名,当然是越简单越好
Pool.PingConnectionOlderThan:对持续连接时间超过设定值(毫秒)的连接进行检测,我将其设置为0(不进行此项检测),否则,iBatis在超过这个时间后,执行每个sql以前检测连接,对于性能可能会有一定的影响。
Pool.PingConnectionsNotUsedFor:对空闲超过设定值(毫秒)的连接进行检测,我设置为1小时(mysql缺省的关闭时间是8小时)
当然,还有一个办法是使用c3p0这样的连接池
但是需要自己写一部分代码,实现以下接口:
public interface DataSourceFactory {
public void initialize(Map map);
public DataSource getDataSource();
}
在table中,如果表格内容为空,那么显示的时候,表格的边框会少掉一块。在HTML的规范中,应该使用empty-cells:
show这个style来解决,但是IE要到IE8之后才支持这个属性。而FireFox2是已经支持了。
那么在IE7之前的版本中要解决这个问题,好像是需要使用border-collapse:collapse;这个style.
<table style="border-collapse:collapse">
首先,在web项目的页面根目录下建立目录template
然后创建目录simple和xhtml,以上的目录名是struts2缺省使用的,不同的主题使用相应的目录。然后再创建一个components目录,在这个目录下,创建一个property.ftl。 最后的目录结构如下:
template/simple/components/property.ftl
template/xhtml/components/property.ftl
然后在property.ftl中可以使用FreeMarker来定义新的模板(FreeMarker的具体语法可以查看FreeMarker的官方网站,相当的详细易懂):
<#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" />
<@s.if test="${parameters.value} == null || ${parameters.value} == '' "> </@s.if>
<@s.else><@s.property value="${parameters.value}" /></@s.else>
<#include "/${parameters.templateDir}/xhtml/controlfooter.ftl" />
以上是一个我自定义的模板,检测结果是否为空字符串,如果是空的话,就输出一个 这样在输出结果时表格的边框线就是完整的了。
定义好之后,在jsp页面中就可以这样使用了:
<s:component template="/components/property.ftl" theme="simple">
<s:param name="value" value="%{'bookName'}"/>
</s:component>
第一行中的目录名从自components开始,struts2会自动在template目录下去寻找,如是主题是simple, 就在simple目录下找。
另外,param的语法要注意一下,%{}里面需要加一对引号
对于jsp页面,为了防止页面被服务器缓存、始终返回同样的结果。
通常的做法是在客户端的url后面加上一个变化的参数,比如加一个当前时间。
我现在使用的方法是在jsp头部添加以下代码:
<%
request.setAttribute("decorator", "none");
response.setHeader("Cache-Control","no-cache"); //HTTP 1.1
response.setHeader("Pragma","no-cache"); //HTTP 1.0
response.setDateHeader ("Expires", 0); //prevents caching at the proxy server
%>
这样如果有多个调用此页面的链接就不需要一个一个全部添加参数了。
在数据库的设计中,字典项是经常使用的技巧。
比如在一个图书馆系统中,书籍表(Book)会有一个分类字段,这时候我们一般会单独建立一张分类表(Category),在书籍表只保存分类表的ID。
在用户界面上显示书籍明细的时候,会要求显示CategoryID在Category表中对应的名称。
这样通常的做法是把Book和Category两张表进行关联。
但在实际应用中,Category一般都是Cache在应用服务器端,再使用数据表的连接就不够高效。
我的做法是这样的:在iBatis中使用SqlMap从表中将数据取出,此时不使用数据表的连接。
package com.demo;
public class Book {
/* 省略了getter和setter方法 */
private int id;
private String name;
private int categoryId;
private String author;
}
package com.demo;
public class Category {
private static Map<Integer, Category> cacheMap;
/* 省略了这两个属性的getter和setter方法 */
private int id;
private String name;
public static Category getCategory(int id) {
init();
return cacheMap.get(id);
}
public static Map<Integer, Category> getCategoryMap() {
init();
return cacheMap();
}
private init() {
if ( cacheMap != null ) return;
// the code to load category from datebase
// 在这里为了演示的需要,使用以下代码
cacheMap = new HashMap<Integer, Category>();
Category category = new Category();
category.setId(1);
category.setName("Fiction");
cacheMap.put(1, category);
category = new Category();
category.setId(2);
category.setName("Cartoon");
}
}
package com.demo;
public class BookAction {
/* 省略了属性的getter和setter方法 */
Book book;
public String execute() {
book = new Book();
book.setId(1);
book.setName("Thinking in java");
book.setCategoryId(1);
bookList.add(book);
return SUCCESS;
}
}
JSP:
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<s:head />
</head>
<body>
<table border="1">
<tr>
<td>
<s:text name="page.label.userName" />
</td>
<td>
<s:property value="book.name" />
</td>
</tr>
<tr>
<td>
<s:text name="page.label.category" />
</td>
<td>
<s:property value="@com.demo.Category@getCategory(book.categoryId).getName()"/></td>
</tr>
</body>
</html>
2008年2月24日,Flex3正式发布了。分为标准版和Professional版。
两者差别不大,标准版没有advanced datagrid和一些性能分析工具。(advanced datagrid)可是我期盼好久的功能了。
另外,价钱也是相当的不菲。Standard is $249 US, Professional costs $699.
此外,还可以选择从flex2升级上去:升级到Standard的话,99美元,升级到professional要299美元。
Tomcat在后台重起后,所有的session失效。如果客户端继续点击了一个菜单项,发出一个请求。会得到一个exception。
这时候,可以定义一个名为:sessionTimeout的global results
<result name="sessionTimeout">/WEB-INF/pages/session_timeout.jsp</result>
这样,所有Action的session timeout都会被定向到指定的页面
最近尝试用extjs来展示树状菜单。着实花了一番功夫。树状菜单的菜单项需要动态加载,而目前版本的extjs中只支持JSON格式的数据。查了一些资
料,决定使用struts2的json-plugin。首先按照例子做了一个,但是结果就是不成功,界面上只出来了一个js中生成的root节点,不能加
载从后台生成的数据。研究后发现是数据格式有问题。使用json-plugin生成的数据格式如下:
{"cls":"folder","id":10,"leaf":false,"children":[{"cls":"file","id":11,"leaf":true,"children":null,"text":"S600"},{"cls":"file","id":12,"leaf":true,"children":null,"text":"SLK200"}],"text":"Benz"}
而extjs需要的数据格式如下:
[{"cls":"folder","id":10,"leaf":false,"children":[{"cls":"file","id":11,"leaf":true,"children":null,"text":"S600"},{"cls":"file","id":12,"leaf":true,"children":null,"text":"SLK200"}],"text":"Benz"}]
区别很小,就只相差最外面的两个方括号。但是少了这两个方括号,在json中,含义迥然不同,前者表示一个对象,而后者表示一个数组。而extjs中
tree的dataloader需要的数据必须是一个数组。而这样的数据格式是json-plugin自动生成的,无法改变。所以,我最后放弃了json
-plugin,转而使用json-lib来解决这个问题。
1. 下载json-lib, http://json-lib.sourceforge.net/
2. lib目录下的jar文件清单:
commons-beanutils-1.7.0.jar
commons-collections-3.2.jar
commons-digester-1.6.jar
commons-lang-2.3.jar
commons-logging-1.1.jar
dom4j-1.6.1.jar
ezmorph-1.0.4.jar
freemarker-2.3.8.jar
javassist-3.8.1.jar
json-lib-2.2.1-jdk15.jar
log4j-1.2.13.jar
ognl-2.6.11.jar
struts2-core-2.0.11.jar
xml-apis-1.0.b2.jar
xwork-2.0.4.jar
首先配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
然后是struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true"/>
<constant name="struts.i18n.encoding" value="UTF-8"/>
<package name="person" extends="struts-default">
<action name="menus" method="execute" class="com.lab.MenuAction">
<result>/menu.jsp</result>
</action>
</package>
</struts>
3. 树的节点模型(省略了getter,setter)
public class Menu {
private int id;
private String text;
private boolean leaf;
private String cls;
private List<Menu> children;
}
4. action
package com.lab;
import java.util.ArrayList;
import java.util.List;
import net.sf.json.JSONArray;
public class MenuAction {
private String menuString;
private List<Menu> menus;
public String execute() {
menus = new ArrayList<Menu>();
Menu benz = new Menu();
benz.setText("Benz");
benz.setCls("folder");
benz.setLeaf(false);
benz.setId(10);
menus.add(benz);
List<Menu> benzList = new ArrayList<Menu>();
benz.setChildren(benzList);
Menu menu;
menu = new Menu();
menu.setText("S600");
menu.setCls("file");
menu.setLeaf(true);
menu.setId(11);
benzList.add(menu);
menu = new Menu();
menu.setText("SLK200");
menu.setCls("file");
menu.setLeaf(true);
menu.setId(12);
benzList.add(menu);
Menu bmw = new Menu();
bmw.setText("BMW");
bmw.setCls("folder");
bmw.setLeaf(false);
bmw.setId(20);
menus.add(bmw);
List<Menu> bmwList = new ArrayList<Menu>();
bmw.setChildren(bmwList);
menu = new Menu();
menu.setText("325i");
menu.setCls("file");
menu.setLeaf(true);
menu.setId(21);
bmwList.add(menu);
menu = new Menu();
menu.setText("X5");
menu.setCls("file");
menu.setLeaf(true);
menu.setId(22);
bmwList.add(menu);
JSONArray jsonObject = JSONArray.fromObject(menus);
try {
menuString = jsonObject.toString();
} catch (Exception e) {
menuString = "ss";
}
return "success";
}
public String getMenuString() {
return menuString;
}
public void setMenuString(String menuString) {
this.menuString = menuString;
}
}
5. menu.jsp
<%@ taglib prefix="s" uri="/struts-tags" %>
<s:property value="menuString" escape="false"/>
6. html页面和js
我使用的就是extjs的example中的reorder.html和reorder.js,更改了reorder.js中treeloader的dataurl: menus.action
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Reorder TreePanel</title>
<link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" />
<!-- GC -->
<!-- LIBS -->
<script type="text/javascript" src="extjs/adapter/ext/ext-base.js"></script>
<!-- ENDLIBS -->
<script type="text/javascript" src="extjs/ext-all.js"></script>
<script type="text/javascript" src="reorder.js"></script>
<!-- Common Styles for the examples -->
<link rel="stylesheet" type="text/css" href="extjs/resources/css/example.css" />
</head>
<body>
<script type="text/javascript" src="../examples.js"></script><!-- EXAMPLES -->
<h1>Drag and Drop ordering in a TreePanel</h1>
<p>This example shows basic drag and drop node moving in a tree. In this implementation there are no restrictions and
anything can be dropped anywhere except appending to nodes marked "leaf" (the files). <br></p>
<p>Drag along the edge of the tree to trigger auto scrolling while performing a drag and drop.</p>
<p>In order to demonstrate drag and drop insertion points, sorting was <b>not</b> enabled.</p>
<p>The data for this tree is asynchronously loaded with a JSON TreeLoader.</p>
<p>The js is not minified so it is readable. See <a href="reorder.js">reorder.js</a>.</p>
<div id="tree-div" style="overflow:auto; height:300px;width:250px;border:1px solid #c3daf9;"></div>
</body>
</html>
js:
/*
* Ext JS Library 2.0.1
* Copyright(c) 2006-2008, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
Ext.onReady(function(){
// shorthand
var Tree = Ext.tree;
var tree = new Tree.TreePanel({
el:'tree-div',
autoScroll:true,
animate:true,
enableDD:true,
containerScroll: true,
loader: new Tree.TreeLoader({
dataUrl:'http://localhost:8080/lab/menus.action'
})
});
// set the root node
var root = new Tree.AsyncTreeNode({
text: 'Ext JS',
draggable:false,
id:'source'
});
tree.setRootNode(root);
// render the tree
tree.render();
root.expand();
});
我已经上传了完整的War文件(包含所有源代码),见: Extjs Tree + JSON + Struts2 的所有示例源代码和war文件下载
摘要: Struts2和Struts相比,一个重大改进就是支持Ajax。 本文主要看一下Struts2中的Div是如何用来输出Ajax结果,其中主要使用了Dojo。
首先,我们先创建一个简单的用例,在这个用例中,将在屏幕上显示一个用户列表,点击列表中的userid时,列表的下方将显示用户的详细信息,显示用户详细信息的这个步骤我们将使用Ajax。
一、创建web.xml
<?xml v... 阅读全文
File[] filesWanted = dir.listFiles(
new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".html") || name.endsWith(".htm");
}
});
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<s:head />
</head>
<body>
<table border="1">
<s:iterator value="dataMap.keySet()" id="class">
<s:iterator value="dataMap.get(#class).keySet()" id="group">
<tr>
<td><s:property value="group"/></td>
<s:iterator value="dataMap.get(#class).get(#group).values()" id="name">
<td><s:property value="name"/></td>
</s:iterator>
</tr>
</s:iterator>
</s:iterator>
</table>
</body>
</html>
在Struts2中,radio标签可以使用一个list来输出一组radio按钮,
<s:radio name="sex" list="#{'male','female'}" label="%{getText('app.label.sex')}" />
但是如何设置其中一个被默认选中。
查阅了struts2的文档,发现radio标签有一个value属性是用于对radio的进行预选的: http://struts.apache.org/2.x/docs/radio.html
value: Preset the value of input element.
于是,进行了试验,<s:radio name="sex" list="#{'male','female'}" label="%{getText('app.label.sex')}" value="male" />
结果失败了。male的值并没有被选中,经过反复研究,终于得到了正确的结果:
<s:radio name="sex" list="#{'male','female'}" label="%{getText('app.label.sex')}" value="'male'" />
看到其中的区别了吗,就是多了两个单引号。
我认为这是因为value属性的特性引起的。如果male没有加引号,那么struts2会去值的堆栈中寻找变量名为male的值,结果找不到。
加上单引号后,struts2(应该是ognl)把'male'认为是一个简单的字符串。
这样,radio就能够正确地匹配到值,使指定的值默认被选中
在iBatis中,对于in子句的标准做法是采用动态sql来解决的。具体方法大致是:Java代码传入一个List或者数组,然后在sqlMapConfig映射中使用iterate循环取这个变量,动态地生成sql语句。
这个标准解法的缺点是,使用起来比较麻烦
1. 需要在sqlMapConfig中使用动态语句
2. 需要传入一个Iterable的变量
对于这个问题,我使用了一个偷懒的办法,就是使用$标记。
在iBatis中,普通的变量,比如:v,是使用#号,在这个例子中,就是:#v#。
这样,iBatis会使用prepareStatement,并对变量进行变量绑定。
而$符号是简单替代的用法,在数据库的执行效率上要比前一种差。但优点就是简单方便。
比如:
SELECT * FROM emp WHERE emp_no in ($empString$);
而empString的值就是1, 2, 3. 在Log中,可以看到,Sql语句就是:SELECT * FROM emp WHERE emp_no in (1,2,3)
dhtmlXTree的节点定义可以从服务器生成的Xml中动态加载,我目前使用的是Struts2. 因此,我的做法是,在Javascript生成dhtmlxTree的时候,请求一个加载Struts2的action,Struts2的Action进行用户权限认证,动态生成菜单的Xml字符串。而在jsp中只输出这一个字符串。
开始进行的都比较顺利,但是客户端和服务端一连起来就出问题,页面在加载Xml的时候总是弹出一个对话框,说是加载的xml格式不正确。但是我在IE中直接输入Action,页面显示的结果十分正确,没有问题。后来,无意中我查看了返回xml页面的源文件,这才发现了问题。
原来,xml的<和>全都被转换成<和>了。
我一开始是想在后台考虑如何对字符串进行转换,后来在查struts2文档的时候发现,<s:property有一个escape属性,可以完美地解决这个问题,<s:property value="menuXmlString" escape="false"/>
例子已经整理出来了:http://www.blogjava.net/usherlight/archive/2008/08/07/220756.html
最近在网上搜索网页上的树形菜单的开源项目,发现有一个dhtmlx公司做的挺好的。
dhtmlx.com
其组件包括:
dhtmlxTree dhtmlxTabbar
dhtmlxGrid dhtmlxCombo
dhtmlxTreeGrid dhtmlxVault
dhtmlxMenu dhtmlxToolbar
界面做得相当漂亮,在Live Demo中有3种Theme可以选择。
功能也相当出色:可以拖放树中的节点、动态改变节点的Icon,定义节点事件。
可以动态地从Xml中加载菜单的结构定义
节点可编辑
可使用键盘浏览树
树节点可带checkbox
- Multibrowser/Multiplatform support
- XHTML compatible
- Loading from XML or Javascript
- Async mode loading support
- Editable Items
- Keyboard navigation
- Multiselect
- Drag-&-drop (within one tree, between trees)
- Right-to-left languages support (RTL)
- Full controll with JavaScript API
- Dynamic Loading for big trees
- Distributed Loading for big levels
- Smart XML Parsing for big trees
- Serialization to XML
Customizable drag-&-drop to/from dhtmlxGrid
Copy with drag-n-drop
Drop-between/drop-inside
Checkboxes (two/three states, disabled/hidden, radio)
Customizable View
Unlimited User-data for nodes
ASP.NET custom server control
JSP custom tag
Macromedia Cold Fusion support
Detailed documentation
网上介绍使用zipInStream和zipOutStream对文件或者文件夹进行压缩和解压缩的文章比较多。
但是这次项目中需要对byte[]进行压缩,然后decode,通过http发送到服务端。
最简单的方法,当然是把byte[]写到文件里,然后根据网上已有的文章,生成fileInputStream,构造zipInStream。
但是这个做法有着明显的问题,需要操作IO,在效率上不可取。
下面是利用ByteArrayOutStream来完成压缩和解压缩的代码。
/**
* Answer a byte array compressed in the Zip format from bytes.
*
* @param bytes
* a byte array
* @param aName
* a String the represents a file name
* @return byte[] compressed bytes
* @throws IOException
*/
public static byte[] zipBytes(byte[] bytes) throws IOException {
ByteArrayOutputStream tempOStream = null;
BufferedOutputStream tempBOStream = null;
ZipOutputStream tempZStream = null;
ZipEntry tempEntry = null;
byte[] tempBytes = null;
tempOStream = new ByteArrayOutputStream(bytes.length);
tempBOStream = new BufferedOutputStream(tempOStream);
tempZStream = new ZipOutputStream(tempBOStream);
tempEntry = new ZipEntry(String.valueOf(bytes.length));
tempEntry.setMethod(ZipEntry.DEFLATED);
tempEntry.setSize((long) bytes.length);
tempZStream.putNextEntry(tempEntry);
tempZStream.write(bytes, 0, bytes.length);
tempZStream.flush();
tempBOStream.flush();
tempOStream.flush();
tempZStream.close();
tempBytes = tempOStream.toByteArray();
tempOStream.close();
tempBOStream.close();
return tempBytes;
}
/**
* Answer a byte array that has been decompressed from the Zip format.
*
* @param bytes
* a byte array of compressed bytes
* @return byte[] uncompressed bytes
* @throws IOException
*/
public static void unzipBytes(byte[] bytes, OutputStream os) throws IOException {
ByteArrayInputStream tempIStream = null;
BufferedInputStream tempBIStream = null;
ZipInputStream tempZIStream = null;
ZipEntry tempEntry = null;
long tempDecompressedSize = -1;
byte[] tempUncompressedBuf = null;
tempIStream = new ByteArrayInputStream(bytes, 0, bytes.length);
tempBIStream = new BufferedInputStream(tempIStream);
tempZIStream = new ZipInputStream(tempBIStream);
tempEntry = tempZIStream.getNextEntry();
if (tempEntry != null) {
tempDecompressedSize = tempEntry.getCompressedSize();
if (tempDecompressedSize < 0) {
tempDecompressedSize = Long.parseLong(tempEntry.getName());
}
int size = (int)tempDecompressedSize;
tempUncompressedBuf = new byte[size];
int num = 0, count = 0;
while ( true ) {
count = tempZIStream.read(tempUncompressedBuf, 0, size - num );
num += count;
os.write( tempUncompressedBuf, 0, count );
os.flush();
if ( num >= size ) break;
}
}
tempZIStream.close();
}
1. 先写Controller
2. Controller将业务逻辑委派给Service完成
3. Service返回一个Domain Object Model
4. 将Domail Object Model封装成ModelAndView作为Controller的返回结果,并赋予View的名称。
5. InternalResourceViewResolver根据View名称取出对应的Jsp文件,创建一个包含前缀和后缀的真正的路径
6. 这些定义在spring-servlet.xml文件中
7. 配置文件:首先要在web.xml中配置ContextLoaderListener,介绍这个的文章非常多
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
8. 在web.xml中加入DispatherServlet的配置
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
9. spring会根据这个servlet的名字(在这里是spring)自动寻找 <名字>-servlet.xml(这里将会是:spring-servlet.xml)
10. 在spring-servlet.xml中,将service注射给controller
On Sep 4, Matt Raible annouced the release of AppFuse 2.0RC1.
On Sep 7, Matt Raible uploaded a PDF to appfuse.dev.java.net contains the relevant pages from the wiki that help you develop with AppFuse 2.0.
And yesterday, Matt Raible uploaded a big package to appfuse.dev.java.net. It included all the dependencies of the appfuse2.0 RC1 needed.
good job, Matt.
http://www.w3schools.com/schema/schema_howto.asp
What is an XML Schema?
The purpose of an XML Schema is to define the legal building blocks of an XML
document, just like a DTD.
An XML Schema:
- defines elements that can appear in a document
- defines attributes that can appear in a document
- defines which elements are child elements
- defines the order of child elements
- defines the number of child elements
- defines whether an element is empty or can include text
- defines data types for elements and attributes
- defines default and fixed values for elements and attributes
Well-Formed is not Enough
A well-formed XML document is a document that conforms to the XML syntax
rules, like:
- it must begin with the XML declaration
- it must have one unique root element
- start-tags must have matching end-tags
- elements are case sensitive
- all elements must be closed
- all elements must be properly nested
- all attribute values must be quoted
- entities must be used for special characters
The <schema> element may contain some attributes. A schema declaration
often looks something like this:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.w3schools.com"
xmlns="http://www.w3schools.com"
elementFormDefault="qualified">
...
...
</xs:schema>
|
The following fragment:
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
indicates that the elements and data types used in the schema come from the
"http://www.w3.org/2001/XMLSchema" namespace. It also specifies that the
elements and data types that come from the "http://www.w3.org/2001/XMLSchema"
namespace should be prefixed with xs:
This fragment:
targetNamespace="http://www.w3schools.com"
|
indicates that the elements defined by this schema (note, to, from, heading,
body.) come from the "http://www.w3schools.com" namespace.
This fragment:
xmlns="http://www.w3schools.com"
|
indicates that the default namespace is "http://www.w3schools.com".
This fragment:
elementFormDefault="qualified"
|
indicates that any elements used by the XML instance document which were
declared in this schema must be namespace qualified.
What is a Simple Element?
A simple element is an XML element that can contain only text. It cannot
contain any other elements or attributes.
Defining a Simple Element
The syntax for defining a simple element is:
<xs:element name="xxx" type="yyy"/>
|
XML Schema has a lot of built-in data types. The most common types are:
- xs:string
- xs:decimal
- xs:integer
- xs:boolean
- xs:date
- xs:time
Default and Fixed Values for Simple Elements
Simple elements may have a default value OR a fixed value specified.
A default value is automatically assigned to the element when no other value
is specified.
In the following example the default value is "red":
<xs:element name="color" type="xs:string" default="red"/>
|
A fixed value
is also automatically assigned to the element, and you cannot specify another
value.
In the following example the fixed value is "red":
<xs:element name="color" type="xs:string" fixed="red"/>
|
How to Define an Attribute?
The syntax for defining an attribute is:
<xs:attribute name="xxx" type="yyy"/>
|
Restrictions are used to define acceptable values for XML
elements or attributes. Restrictions on XML elements are called facets.
Restrictions on Values
The following example defines an element called "age" with a restriction. The
value of age cannot be lower than 0 or greater than 120:
<xs:element name="age">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="120"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
Restrictions on a Set of Values
To limit the content of an XML element to a set of acceptable values, we
would use the enumeration constraint.
The example below defines an element called "car" with a restriction. The
only acceptable values are: Audi, Golf, BMW:
<xs:element name="car">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Audi"/>
<xs:enumeration value="Golf"/>
<xs:enumeration value="BMW"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
The example above could also have been written like this:
<xs:element name="car" type="carType"/>
<xs:simpleType name="carType">
<xs:restriction base="xs:string">
<xs:enumeration value="Audi"/>
<xs:enumeration value="Golf"/>
<xs:enumeration value="BMW"/>
</xs:restriction>
</xs:simpleType>
|
Note: In this case the type "carType" can be used by other elements
because it is not a part of the "car" element.
Restrictions on a Series of Values
To limit the content of an XML element to define a series of numbers or
letters that can be used, we would use the pattern constraint.
The example below defines an element called "letter" with a restriction. The
only acceptable value is ONE of the LOWERCASE letters from a to z:
<xs:element name="letter">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[a-z]"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
The next example defines an element called "initials" with a restriction. The
only acceptable value is THREE of the UPPERCASE letters from a to z:
<xs:element name="initials">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[A-Z][A-Z][A-Z]"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
The next example also defines an element called "initials" with a
restriction. The only acceptable value is THREE of the LOWERCASE OR UPPERCASE
letters from a to z:
<xs:element name="initials">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-Z][a-zA-Z][a-zA-Z]"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
The next example defines an element called "choice" with a restriction. The
only acceptable value is ONE of the following letters: x, y, OR z:
<xs:element name="choice">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[xyz]"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
The next example defines an element called "prodid" with a restriction. The
only acceptable value is FIVE digits in a sequence, and each digit must be in a
range from 0 to 9:
<xs:element name="prodid">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:pattern value="[0-9][0-9][0-9][0-9][0-9]"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
Other Restrictions on a Series of Values
The example below defines an element called "letter" with a restriction. The
acceptable value is zero or more occurrences of lowercase letters from a to
z:
<xs:element name="letter">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="([a-z])*"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
The next example also defines an element called "letter" with a restriction.
The acceptable value is one or more pairs of letters, each pair consisting of a
lower case letter followed by an upper case letter. For example, "sToP" will be
validated by this pattern, but not "Stop" or "STOP" or "stop":
<xs:element name="letter">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="([a-z][A-Z])+"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
The next example defines an element called "gender" with a restriction. The
only acceptable value is male OR female:
<xs:element name="gender">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="male|female"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
The next example defines an element called "password" with a restriction.
There must be exactly eight characters in a row and those characters must be
lowercase or uppercase letters from a to z, or a number from 0 to 9:
<xs:element name="password">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-Z0-9]{8}"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
Restrictions on Whitespace Characters
To specify how whitespace characters should be handled, we would use the
whiteSpace constraint.
This example defines an element called "address" with a restriction. The
whiteSpace constraint is set to "preserve", which means that the XML processor
WILL NOT remove any white space characters:
<xs:element name="address">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:whiteSpace value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
This example also defines an element called "address" with a restriction. The
whiteSpace constraint is set to "replace", which means that the XML processor
WILL REPLACE all white space characters (line feeds, tabs, spaces, and carriage
returns) with spaces:
<xs:element name="address">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:whiteSpace value="replace"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
This example also defines an element called "address" with a restriction. The
whiteSpace constraint is set to "collapse", which means that the XML processor
WILL REMOVE all white space characters (line feeds, tabs, spaces, carriage
returns are replaced with spaces, leading and trailing spaces are removed, and
multiple spaces are reduced to a single space):
<xs:element name="address">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:whiteSpace value="collapse"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
Restrictions on Length
To limit the length of a value in an element, we would use the length,
maxLength, and minLength constraints.
This example defines an element called "password" with a restriction. The
value must be exactly eight characters:
<xs:element name="password">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:length value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
This example defines another element called "password" with a restriction.
The value must be minimum five characters and maximum eight characters:
<xs:element name="password">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="5"/>
<xs:maxLength value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
|
Restrictions for Datatypes
Constraint |
Description |
enumeration |
Defines a list of acceptable values |
fractionDigits |
Specifies the maximum number of decimal places allowed. Must be
equal to or greater than zero |
length |
Specifies the exact number of characters or list items allowed.
Must be equal to or greater than zero |
maxExclusive |
Specifies the upper bounds for numeric values (the value must be
less than this value) |
maxInclusive |
Specifies the upper bounds for numeric values (the value must be
less than or equal to this value) |
maxLength |
Specifies the maximum number of characters or list items allowed.
Must be equal to or greater than zero |
minExclusive |
Specifies the lower bounds for numeric values (the value must be
greater than this value) |
minInclusive |
Specifies the lower bounds for numeric values (the value must be
greater than or equal to this value) |
minLength |
Specifies the minimum number of characters or list items allowed.
Must be equal to or greater than zero |
pattern |
Defines the exact sequence of characters that are acceptable
|
totalDigits |
Specifies the exact number of digits allowed. Must be greater
than zero |
whiteSpace |
Specifies how white space (line feeds, tabs, spaces, and carriage
returns) is handled |
What is a Complex Element?
A complex element is an XML element that contains other elements and/or
attributes.
There are four kinds of complex elements:
- empty elements
- elements that contain only other elements
- elements that contain only text
- elements that contain both other elements and text
How to Define a Complex Element
Look at this complex XML element, "employee", which contains only other
elements:
<employee>
<firstname>John</firstname>
<lastname>Smith</lastname>
</employee>
|
We can define a complex element in an XML Schema two different ways:
1. The "employee" element can be declared directly by naming the element,
like this:
<xs:element name="employee">
<xs:complexType>
<xs:sequence>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
|
If you use the method described above, only the "employee" element can use
the specified complex type. Note that the child elements, "firstname" and
"lastname", are surrounded by the <sequence> indicator. This means that
the child elements must appear in the same order as they are declared. You will
learn more about indicators in the XSD Indicators chapter.
2. The "employee" element can have a type attribute that refers to the name
of the complex type to use:
<xs:element name="employee" type="personinfo"/>
<xs:complexType name="personinfo">
<xs:sequence>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
</xs:sequence>
</xs:complexType>
|
You can also base a complex element on an existing complex element and add
some elements, like this:
<xs:element name="employee" type="fullpersoninfo"/>
<xs:complexType name="personinfo">
<xs:sequence>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="fullpersoninfo">
<xs:complexContent>
<xs:extension base="personinfo">
<xs:sequence>
<xs:element name="address" type="xs:string"/>
<xs:element name="city" type="xs:string"/>
<xs:element name="country" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
|
Complex Types with Mixed Content
An XML element, "letter", that contains both text and other elements:
<letter>
Dear Mr.<name>John Smith</name>.
Your order <orderid>1032</orderid>
will be shipped on <shipdate>2001-07-13</shipdate>.
</letter>
|
The following schema declares the "letter" element:
<xs:element name="letter">
<xs:complexType mixed="true">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="orderid" type="xs:positiveInteger"/>
<xs:element name="shipdate" type="xs:date"/>
</xs:sequence>
</xs:complexType>
</xs:element>
|
Note: To enable character data to appear between the child-elements of
"letter", the mixed attribute must be set to "true". The <xs:sequence> tag
means that the elements defined (name, orderid and shipdate) must appear in that
order inside a "letter" element.
We can control HOW elements are to be used in documents with
indicators.
Indicators
There are seven indicators:
Order indicators:
Occurrence indicators:
Group indicators:
- Group name
- attributeGroup name
All Indicator
The <all> indicator specifies that the child elements can appear in any
order, and that each child element must occur only once:
<xs:element name="person">
<xs:complexType>
<xs:all>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
</xs:all>
</xs:complexType>
</xs:element>
|
Note: When using the <all> indicator you can set the
<minOccurs> indicator to 0 or 1 and the <maxOccurs> indicator can
only be set to 1
Choice Indicator
The <choice> indicator specifies that either one child element or
another can occur:
<xs:element name="person">
<xs:complexType>
<xs:choice>
<xs:element name="employee" type="employee"/>
<xs:element name="member" type="member"/>
</xs:choice>
</xs:complexType>
</xs:element>
|
Sequence Indicator
The <sequence> indicator specifies that the child elements must appear
in a specific order:
<xs:element name="person">
<xs:complexType>
<xs:sequence>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
|
Occurrence Indicators
Occurrence indicators are used to define how often an element can occur.
Note: For all "Order" and "Group" indicators (any, all, choice,
sequence, group name, and group reference) the default value for maxOccurs and
minOccurs is 1.
maxOccurs Indicator
The <maxOccurs> indicator specifies the maximum number of times an
element can occur:
<xs:element name="person">
<xs:complexType>
<xs:sequence>
<xs:element name="full_name" type="xs:string"/>
<xs:element name="child_name" type="xs:string" maxOccurs="10"/>
</xs:sequence>
</xs:complexType>
</xs:element>
|
The example above indicates that the "child_name" element can occur a minimum
of one time (the default value for minOccurs is 1) and a maximum of ten times in
the "person" element.
minOccurs Indicator
The <minOccurs> indicator specifies the minimum number of times an
element can occur:
<xs:element name="person">
<xs:complexType>
<xs:sequence>
<xs:element name="full_name" type="xs:string"/>
<xs:element name="child_name" type="xs:string"
maxOccurs="10" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
|
The example above indicates that the "child_name" element can occur a minimum
of zero times and a maximum of ten times in the "person" element.
Tip: To allow an element to appear an unlimited number of times, use
the maxOccurs="unbounded" statement
Group Indicators
Group indicators are used to define related sets of elements.
Element Groups
Element groups are defined with the group declaration, like this:
<xs:group name="groupname">
...
</xs:group>
|
You must define an all, choice, or sequence element inside the group
declaration. The following example defines a group named "persongroup", that
defines a group of elements that must occur in an exact sequence:
<xs:group name="persongroup">
<xs:sequence>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
<xs:element name="birthday" type="xs:date"/>
</xs:sequence>
</xs:group>
|
After you have defined a group, you can reference it in another definition,
like this:
<xs:group name="persongroup">
<xs:sequence>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
<xs:element name="birthday" type="xs:date"/>
</xs:sequence>
</xs:group>
<xs:element name="person" type="personinfo"/>
<xs:complexType name="personinfo">
<xs:sequence>
<xs:group ref="persongroup"/>
<xs:element name="country" type="xs:string"/>
</xs:sequence>
</xs:complexType>
|
Attribute Groups
Attribute groups are defined with the attributeGroup declaration, like
this:
<xs:attributeGroup name="groupname">
...
</xs:attributeGroup>
|
The following example defines an attribute group named "personattrgroup":
<xs:attributeGroup name="personattrgroup">
<xs:attribute name="firstname" type="xs:string"/>
<xs:attribute name="lastname" type="xs:string"/>
<xs:attribute name="birthday" type="xs:date"/>
</xs:attributeGroup>
|
After you have defined an attribute group, you can reference it in another
definition, like this:
<xs:attributeGroup name="personattrgroup">
<xs:attribute name="firstname" type="xs:string"/>
<xs:attribute name="lastname" type="xs:string"/>
<xs:attribute name="birthday" type="xs:date"/>
</xs:attributeGroup>
<xs:element name="person">
<xs:complexType>
<xs:attributeGroup ref="personattrgroup"/>
</xs:complexType>
</xs:element>
|
The <any> element enables us to extend the XML document
with elements not specified by the schema!
The <any> Element
The <any> element enables us to extend the XML document with elements
not specified by the schema.
The following example is a fragment from an XML schema called "family.xsd".
It shows a declaration for the "person" element. By using the <any>
element we can extend (after <lastname>) the content of "person" with any
element:
<xs:element name="person">
<xs:complexType>
<xs:sequence>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
<xs:any minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
|
Now we want to extend the "person" element with a "children" element. In this
case we can do so, even if the author of the schema above never declared any
"children" element.
Look at this schema file, called "children.xsd":
<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.w3schools.com"
xmlns="http://www.w3schools.com"
elementFormDefault="qualified">
<xs:element name="children">
<xs:complexType>
<xs:sequence>
<xs:element name="childname" type="xs:string"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
|
The XML file below (called "Myfamily.xml"), uses components from two
different schemas; "family.xsd" and "children.xsd":
<?xml version="1.0" encoding="ISO-8859-1"?>
<persons xmlns="http://www.microsoft.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:SchemaLocation="http://www.microsoft.com family.xsd
http://www.w3schools.com children.xsd">
<person>
<firstname>Hege</firstname>
<lastname>Refsnes</lastname>
<children>
<childname>Cecilie</childname>
</children>
</person>
<person>
<firstname>Stale</firstname>
<lastname>Refsnes</lastname>
</person>
</persons>
|
The XML file above is valid because the schema "family.xsd" allows us to
extend the "person" element with an optional element after the "lastname"
element.
The <any> and <anyAttribute> elements are used to make EXTENSIBLE
documents! They allow documents to contain additional elements that are not
declared in the main XML schema.
The <anyAttribute> element enables us to extend the XML
document with attributes not specified by the schema!
The <anyAttribute> Element
The <anyAttribute> element enables us to extend the XML document with
attributes not specified by the schema.
The following example is a fragment from an XML schema called "family.xsd".
It shows a declaration for the "person" element. By using the
<anyAttribute> element we can add any number of attributes to the "person"
element:
<xs:element name="person">
<xs:complexType>
<xs:sequence>
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
</xs:sequence>
<xs:anyAttribute/>
</xs:complexType>
</xs:element>
|
Now we want to extend the "person" element with a "gender" attribute. In this
case we can do so, even if the author of the schema above never declared any
"gender" attribute.
Look at this schema file, called "attribute.xsd":
<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.w3schools.com"
xmlns="http://www.w3schools.com"
elementFormDefault="qualified">
<xs:attribute name="gender">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="male|female"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:schema>
|
The XML file below (called "Myfamily.xml"), uses components from two
different schemas; "family.xsd" and "attribute.xsd":
<?xml version="1.0" encoding="ISO-8859-1"?>
<persons xmlns="http://www.microsoft.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:SchemaLocation="http://www.microsoft.com family.xsd
http://www.w3schools.com attribute.xsd">
<person gender="female">
<firstname>Hege</firstname>
<lastname>Refsnes</lastname>
</person>
<person gender="male">
<firstname>Stale</firstname>
<lastname>Refsnes</lastname>
</person>
</persons>
|
The XML file above is valid because the schema "family.xsd" allows us to add
an attribute to the "person" element.
The <any> and <anyAttribute> elements are used to make EXTENSIBLE
documents! They allow documents to contain additional elements that are not
declared in the main XML schema.
: ViewStack的大小是由其子组件的大小决定的,而ViewStack并不会在改变活动子组件的时候自动resize。 只有使用以下方法来控制ViewStack的大小。 1. 使用相同的固定值明确指定所有子组件的大小 2. 使用相同的比例值指定所有子组件的大小 3. 将ViewStack的width和height的值设置为一个固定或者值。
上述3种方法是Adobe官方文档提供的。但是这3种方法不能解决ViewStack子组件大小不一致,ViewStack不能自动调整的问题。 我最后是使用ActionScript动态解决的。 在更换ViewStack的active child之间,首先设置viewStack的大小。
<mx:ViewStack id="appStack"> <mx:VBox id="v1"/> <mx:VBox id="v2"/> </mx:ViewStack>
public function changeChild() : void { appStack.width = 200; appStack.height = 200; appStack.selectChild = v1; }
我错了,使用ViewStack的resizeToContent是最好的解决办法。
Spring的关键之一就是容器,在Spring中主要是两种容器:一个是BeanFactory,一个是ApplicationContext。 容器的作用是,管理所有的bean的生命周期,从创建bean的实例开始,到最后bean的消亡。 这两种容器的作用基本相同,但是Application Context是BeanFactory的子类,增加了一些功能,所以更为强大一些,主要体现在3个方面: 1. 能解析文本消息,提供文本的国际化(I18N)。 2. ApplicationContext提供一种通用的方法来加载文件资源,比如:图像文件。 3. 能够发布事件到注册的监听器。 所以,在大多数应用中,都使用Application Context。
ApplicationContext接口的实现类有很多,但常用的有3个: 1. ClassPathXmlApplicationContext 2. FileSystemXmlApplicationContext 3. XmlWebApplicationContext
ApplicationContext和BeanFactory的另一个区别在于对singleton bean的加载上。Bean Factory延迟加载所有的bean直到getBean()的调用,而ApplicationContext稍微智能一些,预先加载所有的singleton bean。
1. 安装Mysql,Maven等等。这些在网上都有详细的说明。 2. 我更改了Maven的Repository的路径,缺省是放在C:\document and settings\<user name>\.m2\repository目录下,我觉得放在C:下不好,所以更换了路径。 打开~maven/conf/setting.xml,修改<localRepository>的值。
3. 使用Maven下载appfuse 我使用的是struts所以,使用的命令是: mvn archetype:create -DarchetypeGroupId=org.appfuse -DarchetypeArtifactId=appfuse-modular-struts -DremoteRepositories=http://static.appfuse.org/repository -DarchetypeVersion=2.0-m5 -DgroupId=com.mycompany.app -DartifactId=appfuse 这里,我把下载的目录名改为了appfuse,在appfuse.org的quick start中是使用myproject的。
4. 运行的过程中,会出错,我在两台机器上都遇到了错误。 关系不大。可以继续进行。
5. 下载源代码。 我是在Eclipse中使用Subversion下载的,可以使用mvn appfuse:full-source,但是只能下载到web下的代码,service, data等部分的代码就没有了。 Svn的Repository的地址是:https://appfuse.dev.java.net/svn/appfuse
6. Java Source Code已经尽在掌握了,只是还分布在不同的目录里。 分别是在:data,service,webapp,都在main\java目录下。
7. 开始获取jsp,配置文件等。 首先cd ~maven\repo\org\hibernate\jtidy\r8-20060801 edit jtidy-r8-20060801.pom 去掉一个重复的 <licenses> 标签.
8. 去掉mysql的root用户的密码, update user set password=password('') where user='root'; flush privileges;
9. cd appfuse mvn integration-test 在appfuse-snapshot1.0目录下,把jsp、image,js,css等全部复制过来 另外,还有很多配置文件,象applicationContext-dao.xml等等。 还有一个,就是library了。其中有一个要注意的是ehcache需要使用1.3.0, 如果使用1.2.X,会报 javax.servlet.ServletException: Failure when attempting to set Content-Encoding: gzip 这个错误。
10. 我是使用Eclipse的Tomcat插件的,因此,建立了一个Tomcat project 把Java源文件复制到web-inf\src下, org.appfuse.dao org.appfuse.model org.appfuse.service org.appfuse.util org.appfuse.webapp(Webapp目录下) 另外, common decorators images scripts styles template 403.jsp 404.jsp index.jsp....... 还有web-inf目录下的: 数10个配置文件和lib目录下数10个jar文件
11. 启动Tomcat插件,在浏览器中浏览:http://localhost:8080/appfuse/index.jsp 用户:admin 密码:admin OK.
之所以,这么麻烦的折腾,主要是想在appfuse应用中,打断点,进行逐步跟踪。充分了解认知演习appfuse的细节。
最近做的一个程序是用Swing的,要求能够根据不同的分辨率自动调整界面上所有组件的大小。也就是说不是写死是1024×768,并且字体也需要根据大小自动变化。 我使用的工具是Netbeans,为实现动态变化,我使用了GridBagLayout。首先,新建一个类,继承JPanel。然后设置JPanel的Layout为GridBagLayout。当然,根据情况,可以和Html中的表格一样,Panel里面嵌套Panel,要点是每个Panel的Layout都设置为GridBagLayout(使用其他的Layout也可以实现这样的功能,但是个人感觉GridBagLayout最容易控制和使用)。 Layout的设置只是第一步,缺省情况下,GridBagLayout会把Panel中所有的组件排成一行,从左到右逐个排放。这时候,就要使用Customize Layout(定制布局)的功能,点击后,会再弹出一个窗口 在新的窗口中,可以拖动Panel里面的组件,象表格一样,组织安放所有的组件,相当方便。 这些步骤完成后,重要的两个属性是,填充(Fill),建议把所有组件的Fill属性,都选成Both,也就是水平和垂直方向都延伸填充。这样,Panel里面的所有组件会平铺开来,占满Panel的所有空间。那么,如何调整这些组件的大小呢?需要使用weightx和weighty这两个属性。这两属性的值使用0.0~1.0之间的小数,数越大,组件所占据的空间越大。 通过以上的设置,就可以实现组件大小随着Panel大小的变化而变化了。 那么,又如何实现字体的变化呢?这个只能通过编程实现了。但是initComponents函数里的代码都是自动生成的,如何添加自定义的代码呢。点击属性面板里的字体属性后面的小方框,在弹出的对话框里,点击高级按钮,勾选“生成初始化后的代码”,然后在文本框里,输入代码,这段自定义代码,会在每次自动生成代码的时候,添加到initComponents函数中。
SubVersion的官方网站中有两个版本可供下载,一个是for apaache2.0.X的,一个是for apache2.2.X的,第一个是可执行文件,在已经安装了Apache2.0.X的机器上运行后,会自动在httpd.conf文件中添加相应的内容,并自动复制模块和动态链接库到相应目录。 而for Apache2.2.X的那个是一个压缩包,需要手工在apache的httpd.conf中添加相应内容,主要是启用DAV,并增加一个location。这些步骤在网上都可以搜索得到,但是我发现,网上的很多文章都忽略了将动态链接库复制到apache的bin目录下这一个步骤,这样会导致apache http server无法启动。 需要复制的文件是: libdb44.dll libeay32.dll ssleay32.dll
这些文件可以复制到D:\Program Files\Apache Software Foundation\Apache2.2\bin目录(也就是apache安装目录的bin目录)下。
原文地址:http://www.ibm.com/developerworks/java/library/j-cq08296/?ca=dgr-lnxw07JUnit4vsTestNG
JUnit到了4.0以后,增加了许多新特性,变得更加灵活易用。但是另一个也是基于Annotations的TestNG在灵活易用方面已经走在了JUnit的前面。实际上,TestNG是基于Annotation的测试框架的先驱,那么这两者之间的差别是什么呢,在这里,将对两者进行一些简单的比较。
首先两者在外观上看起来是非常相似的。使用起来都非常的简便。但是从核心设计的出发点来说,两者是不一样的。JUnit一直将自己定位于单元测试框架,也就是说用于测试单个对象。而TestNG定位于更高层次的测试,因此具备了一些JUnit所没有的功能。
A simple test case
At first glance, tests implemented in JUnit 4 and TestNG look remarkably similar. To see what I mean, take a look at the code in Listing 1, a JUnit 4 test that has a macro-fixture (a fixture that is called just once before any tests are run), which is denoted by the @BeforeClass attribute:
先来看一个简单的测试例子: 第一眼看上去,JUnit和TestNG几乎一模一样。
package test.com.acme.dona.dep;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.junit.BeforeClass; import org.junit.Test;
public class DependencyFinderTest { private static DependencyFinder finder;
@BeforeClass public static void init() throws Exception { finder = new DependencyFinder(); }
@Test public void verifyDependencies() throws Exception { String targetClss = "test.com.acme.dona.dep.DependencyFind";
Filter[] filtr = new Filter[] { new RegexPackageFilter("java|junit|org")};
Dependency[] deps = finder.findDependencies(targetClss, filtr);
assertNotNull("deps was null", deps); assertEquals("should be 5 large", 5, deps.length); } }
package test.com.acme.dona.dep;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import org.testng.annotations.BeforeClass; import org.testng.annotations.Configuration; import org.testng.annotations.Test;
public class DependencyFinderTest { private DependencyFinder finder;
@BeforeClass private void init(){ this.finder = new DependencyFinder(); }
@Test public void verifyDependencies() throws Exception { String targetClss = "test.com.acme.dona.dep.DependencyFind";
Filter[] filtr = new Filter[] { new RegexPackageFilter("java|junit|org")};
Dependency[] deps = finder.findDependencies(targetClss, filtr); assertNotNull(deps, "deps was null" ); assertEquals(5, deps.length, "should be 5 large"); } }
仔细观察,会发现一些不一样的地方。首先,JUnit要求BeforClass的方法为static,因此finder也随之需要声明为static。另外init方法必须声明为public。而TestNG却都不需要这样做。
看一下典型的Log4j.properties
log4j.rootLogger=DEBUG, A1
log4j.appender.A1=org.apache.log4j.FileAppender log4j.appender.A1.File=${LOG_PATH}/pi.log log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] %-5p %c - %m%n
第一行就是定义了Log4j中的层次结构的最顶端root的一些属性 log4j.rootLogger=DEBUG, A1, 声明log4j的根节点允许Debug以上级别的日志输出。其Appender的名称为:A1
从第二行开始就是对名为A1的Appender进行定义了 log4j.appender.A1=org.apache.log4j.FileAppender 这一行定义了这个appender使用的类型,这里是把日志输出到文件,目前log4j支持的appender种类不少,最常见的就是FileAppender和ConsoleAppender了。 其他还有数据库、邮件等等
log4j.appender.A1.File=${LOG_PATH}/pi.log 这一行定义了日志输出文件所在的目录和文件名称
log4j.appender.A1.layout=org.apache.log4j.PatternLayout 这一行定义日志文件所输出使用的模式
log4j.appender.A1.layout.ConversionPattern=[%t] %-5p %c - %m%n 这一行定义了日志每一行的格式
Log4j
Log4j的核心是3大组件:Logger、Appender、Layout。
首先是Logger,Logger的意义在于它不象System.out,它可以根据需要屏蔽部分的Log输出,同时其他的Log输出不受影响。 一、Logger有层次结构。有了层次结构就意味着有了继承关系,也就意味着可以重用。这似乎和面向对象语言很想象。 Logger都是有名称的,而Logger的名称和Java一样,也是XXX.XXX.XXX,和Java一样的规则,相当简单。类似于Java中的Object,Logger的存在一个默认的根节点:root
Logger的名称一般这样获得: Logger logger = Logger.getLogger("XXX.XXX.XXX"); 或者 Logger logger = Logger.getLogger(this.getClass()); Log4j并没有强制要求用类名作为Logger的名称,但是这是推荐的做法。
二、有了层次结构后,就要说一下级别了,文章开始的时候就提到,Log4j的优势就在于能够根据需要过滤Log的输出,主要(不是全部)就是通过级别实现的。 Log4j把级别分为:Fatal,Error,Warn,Info,Debug。这样的区分也是经过慎重考虑的,如果引入太多的层次,会使得程序开发者在记录日志的时候,难以选择,会挑花了眼。 级别之间存在优先级的高低。 通过如下语句,输出不同级别的Log logger.debug("..."); logger.info("..."); logger.warn("..."); logger.error("..."); logger.fatal("...");
如果Logger的级别设为Warn,那么只有级别比Warn高的语句的Log才会输出。 比如:logger.debug语句这时不起作用。
三、层次结构的继承关系现在便发挥作用了。子节点如果没有显式定义级别,那么自动继承最近的父节点的级别。这样,就不需要为每一个Logger都去定义级别了,因为至少根节点是存在的,可以从根节点中获得级别定义。
四、全局级别,可以通过设置日志的“门槛”,来实现全局强制性的级别控制。 LoggerResposity reposity = x.getLoggerResposity(); resposity.setThreshold(Level.WARN); 这完全可以在配置文件中配置。 这样,logger.info语句将不再起作用。
其次是Appender,Appender决定了Log究竟输出到什么地方,Log4j提供了多重输出的功能,也就是说可以为Log定义多个输出地点。 同样,层次结构在这里也发挥的威力,子节点的Logger将会继承父节点的Appender,免去了一个一个定义Appender的工作,根节点默认的Appender的Console。 当然,也可以设置不继承父节点的Appender
最后是Layout,Layout决定了Log的格式。
Log4j的配置完全可以通过编程实现,对于特别简单的应用来说,绝对是够用了。但是,对于稍微大一点的应用,把配置硬编码在程序中是不灵活的。所以,使用配置配置文件是比较好的选择。
首先是应用的代码, 在应用中使用 <mx:ModuleLoader >来加载模块
<?xml version="1.0"?> <!-- modules/URLModuleLoaderApp.mxml --> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" viewSourceURL="srcview/index.html">
<mx:Panel title="Module Example" height="90%" width="90%" paddingTop="10" paddingLeft="10" paddingRight="10" paddingBottom="10" > <mx:Label width="100%" color="blue" text="Select the tabs to change the panel."/> <mx:TabNavigator id="tn" width="100%" height="100%" creationPolicy="auto" > <mx:VBox id="vb1" label="Column Chart Module"> <mx:Label id="l1" text="ColumnChartModule.swf"/> <mx:ModuleLoader url="ColumnChartModule.swf"/> </mx:VBox> <mx:VBox id="vb2" label="Pie Chart Module"> <mx:Label id="l2" text="piehchartmodule.swf"/> <mx:ModuleLoader url="piechartmodule.swf"/> </mx:VBox> <mx:VBox id="vb3" label="Line Chart Module"> <mx:Label id="l3" text="linehchartmodule.swf"/> <mx:ModuleLoader url="linechartmodule.swf"/> </mx:VBox> </mx:TabNavigator> </mx:Panel>
</mx:Application>
在这个应用中主要是一个TagNavigator, 里面有三个标签页. 每个标签页加载一个模块. 下面是其中一个模块的代码:
<?xml version="1.0"?> <!--ColumnChartModule.mxml --> <mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" >
<mx:Script><![CDATA[ import mx.collections.ArrayCollection; [Bindable] public var expenses:ArrayCollection = new ArrayCollection([ {Month:"Jan", Profit:2000, Expenses:1500}, {Month:"Feb", Profit:1000, Expenses:200}, {Month:"Mar", Profit:1500, Expenses:500} ]); ]]></mx:Script> <mx:ColumnChart id="myChart" dataProvider="{expenses}"> <mx:horizontalAxis> <mx:CategoryAxis dataProvider="{expenses}" categoryField="Month" /> </mx:horizontalAxis> <mx:series> <mx:ColumnSeries xField="Month" yField="Profit" displayName="Profit" /> <mx:ColumnSeries xField="Month" yField="Expenses" displayName="Expenses" /> </mx:series> </mx:ColumnChart> <mx:Legend dataProvider="{myChart}"/> </mx:Module>
最后, 应用和三个模块一共会生成4个SWF. 一般来说, 应用使用延迟加载策略. 也就是说, 如果你打开应用后, 从来都不使用其中的某个模块, 那个这个模块永远不会被加载. 这次做的好处是, 加快了第一次打开应用的速度, 但随之而来的缺点就是, 第一次打开使用某个功能, 需要加载模块时, 会需要一点等待的时间.
今天在一台新的机器上, 装开发环境, 下载了新的JDK1.6和Tomcat6. 安装完毕后, Tomcat无法正常启动, 在Tomcat的Logs中发现有以下错误:
javajni.c] [error] The specified module could not be found.
到底是怎么回事呢? 因为是模块加载的问题, 所以拿出从systeminternals下载的FileMon来监测到底是哪个东西加载失败了. 启动FileMon, 然后, 尝试启动Tomcat, 结果当然是失败了. 然后在FileMon中过滤一下, 很快就找到了一个错误: MSVCR71.dll, 这个动态链接库加载失败. 经过搜索后发现在JDK的bin目录下有这个文件. 将其复制到system32目录下, 然后再启动Tomcat, 果然成功了!
这个问题可能和jakarta_servic加载动态链接库的LOAD_WITH_ALTERED_PATH选项有关.
最近judahfrangipane提出一种新的模式: DUDE. 大多数人都知道MVC, 问题是如果不管具体情况, 生搬硬套MVC模式就会有一些问题. 一个应用中不是所有的东西都会有一个View, 也不是所有的东西都有一个独立的Controller. 有时候, 可能会有多个Controller. 所以judahfrangipane推荐了一种新模式:
Data Models 如果必要的话 User Interface 如果必要的话 Design patterns 如果必要的话 Event handling 如果必要的话
注意, 上述的四个部分不一定是全部必需的. 这样, 你在进行应用设计的时候, 就少了一些条条框框. 而可以根据知识, 经验, 设计模式来找到一个适合应用的途径, 就象Chuck Hoffman所说的那样, 不要"过度设计". 但是有两个东西必须完全分离, 那就是data和UI.
新特性 1. 支持AMF3, 这一点无疑是激动人心的. 2. 支持JSON, 除了gateway.php外, 增加了json.php, 让你的服务能够使用JSON. 有两个例子: http://5etdemi.com/amfphp2/samples/ajaxtables/ http://5etdemi.com/amfphp2/samples/spry/ 3. 一个新的service browser 4. 在php端, 不再需要写 $this->methodTable 这个东东了. 在amfphp 1.9 中 所有的方法默认为可以远程访问,除非方法名是以下划线开始 或者 方法是 private 的(只有 php5 支持) 。
1. 先确定phpinfo()可以运行. 2. 在通过phpinfo()查看两个参数, 一个是php.ini的路径, 一个extension_dir的配置 我看到我的php.ini文件使用是的php安装路径下的, 而不是网上文章所说需要拷贝到c:\windows, 首先要确定你不要改错了文件, 免得浪费时间. 然后在php.ini文件中, 再修改extension_dir参数. 3. 几个与extension有关的dll需要去掉注释. 4. 需要设置一个PHPRC的环境变量, 指明php的安装路径.
首先介绍一下SystemManager. SystemManager是Flex应用的主控者, 它控制着应用窗口, Application实例, 弹出窗口, cursors, 并管理着ApplicationDomain中的类. SystemManager是FlashPlayer实例化的第一个类, 它存储了主应用窗口的大小和位置信息, 保存其子组件比如:浮动弹出窗口和模态窗口的痕迹. 通过SystemManager可以获得内嵌字体,样式和document对象. 自定义的可视化组件(UIComponent的子类)只有在调用过addChild()后, 才会有一个SystemManager赋给他们, 之前是Null. 所以在自定义可视化组件的构造函数中不要使用SystemManager.
通常, Application对象创建时, 发生如下事件: 1. 实例化Application对象 2. 初始化Application.systemManager 3. Application在初始化过程之前, 派发预初始化事件. 4. 调用createChild(). 此时, 所有应用组件被创建, 所有组件的createChild()被调用. 5. Application派发初始化事件, 表明所有的组件初始化完毕. 6. 派发creationComplete事件 7. Application对象添加到显示列表中 8. 派发applicationComplete事件
大多数情况下, 我们使用<mx:Application>来创建application对象, 但如果使用ActionScript来创建的话, 那么建议不要在application的构造函数中创建组件, 推荐在crateChildren函数中, 主要是从性能方面考虑.
Flash包含的是一个时间线上的多个帧, 而Flex的SWF只包含2个帧. SystemManager, Preloader, DownloadProgressBar和少量工具类都在第一帧, 剩下的包括应用代码/ 内嵌资源全都在第二帧中. 当Flash Player下载下载SWF时, 只要接收到第一帧内足够的数据, 就会实例化SystemManager, 由它来创建Preloader, 然后创建DownloadProgressBar, 这两个对象会察看剩余字节的传输过程. 当第一帧的所有字节传输完毕后, SystemManager发送enterFrame到第二帧, 然后是其他事件. 最后Application对象派发applicationComplete事件.
一个动态修改Tree节点标签的例子
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" horizontalAlign="left" creationComplete="initApp()"> <mx:Script><![CDATA[ [Bindable]public var _xmlData:XML; [Bindable]private var _xmlCur:XML; private function initApp():void { //set the test data _xmlData = <node label="Mail Box"> <node label="Inbox"/> <node label="Deleted mail"/> <node label="Personal"/> <node label="Professional"/> <node label="Spam"/> <node label="Sent"/> </node>
myTree.selectedItem = myTree.dataProvider[0]; //select the first node callLater(expandTreeNode, [myTree.selectedItem]); //use callLater to expand that node }//initApp private function expandTreeNode(myXMLNode:XML):void{ myTree.expandChildrenOf(myXMLNode,true); //expand the node _xmlCur = XML(myTree.selectedItem); //set the bindable variable } private function oChangeTree(oEvent:Event):void { _xmlCur = XML(oEvent.target.selectedItem); //set the bindable variable }// private function updateNode(oEvent:Event):void { var xmlSelected:XML = XML(myTree.selectedItem) //get a reference to the selected node xmlSelected.@label = tiLabel.text; //set the label attribute }//updateNode ]]></mx:Script> <mx:Label text="Update selected Node label" /> <mx:TextInput id="tiLabel" text="{_xmlCur.@label}" change="updateNode(event)" /> <mx:HBox> <mx:Tree id="myTree" width="200" height="200" labelField="@label" showRoot="true" dataProvider="{_xmlData}" change="oChangeTree(event)" /> <mx:DataGrid id="dg" dataProvider="{_xmlData.node}" > <mx:columns> <mx:Array> <mx:DataGridColumn headerText="Name" dataField="@label" /> </mx:Array> </mx:columns> </mx:DataGrid> <mx:Label text="{_xmlCur.@label}" /> </mx:HBox> </mx:Application>
应用中主要包含4个组件, 一个TextInput, 一个Tree, 一个DataGrid, 一个Label 1. 先看Tree, Tree使用_xmlData作为数据源, 定义一个change事件处理函数, 将当前节点存储到_xmlCur变量中. 2. TextInput的数据源就是_xmlCur的label属性, 也就是Tree当前节点的标签. 他也定义了一个change事件处理函数, 在TextInput中的文本改变时, 将新的文本赋值给Tree当前节点的标签, 也就是改变当前Tree节点的标签值. 值得注意的是赋值并不是直接给Tree中或者节点中的某个属性, 而是通过修改数据源Xml的值来改变的. 3. DataGrid的使用显示了如何在DataGrid中展示Xml的技巧. 4. 最后一个Label只是简单的显示当前节点的标签值, 与TextInput的文本保持同步
今天准备用一下, 已经很久没有使用的NetBeans来测试一个小程序.
在开始菜单中, 点了一下, 结果没有出现我期待的IDE, 而是弹出一个对话框, 说无法在C:盘的JDK目录中找到Java.exe. 奇怪啊, 我的JDK是安装在D:的啊.
于是, 在NetBeans的目录下, 查看了一下, 看到有个etc目录, 进去后, 打到了netbeans.conf. 打开一看, 果然NetBeans的JDK路径配置在此. 将此文件中的路径改为正确路径. OK, 问题解决, NetBeans又正常启动了.
1. Caringorm2.1的包中增加了完整的ASDoc文档
2. 增加Locale,错误信息的国际化的处理。 添加了一个Properties文件, 添加了com.adobe.cairngorm.CairngormMessageCodes和com.adobe.cairngorm.CairngormError两个类,其中CairngormMessageCodes用于定义Properties文件中的键值,而CairngormError封装了Error,在应用执行的过程不再直接抛出Error,而抛出一个CairngormError,其中带一个参数就是MessageCode,根据MessageCode到Properties文件中取出相应的消息。
3. 在business中,添加了一个IServiceLocator的接口 ServiceLocator实现IServiceLocator接口,相比以前增加了以下几个方法: a、public function getRemoteObject( serviceId : String ) : RemoteObject // Return the RemoteObject for the given service id. b、public function getHTTPService( serviceId : String ) : HTTPService // Return the HTTPService for the given service id. c、public function getWebService( serviceId : String ) : WebService // Return the WebService for the given service id. d、public function getConsumer( serviceId : String ) : Consumer // Return the message Consumer for the given service id. e、public function getProducer( serviceId : String ) : Producer // Return the message Produce for the given service id. f、public function getDataService( serviceId : String ) : DataService // Return the DataService for the given service id. g、public function setCredentials( username : String, password : String ) : void // Set the credentials for all registered services. Note that services that use a proxy or a third-party adapter to a remote endpoint will
原来的getService和getInvokerService方法已经废弃,改为了getRemoteObject
4. 在Command中,增加了ICommand接口,原来的Command接口继承ICommand
5. 在VO中,增加了IValueObject接口,原来的ValueObject继承IValueObject接口
上面添加的几个接口,除了IServiceLocator相较2.0版本有了较大的变化,增加了一些方法,其他的几个接口,依我所见,纯粹是换了名字而已。
这个名字的改变反应出FDS的一个重要的扩展:LiveCycle Data Service与Adobe其他LiveCycle服务产品的整合将更紧密。 LiveCycel Data Service将有一些重要的新的变化: 1. 提升数据服务消息的性能 2. 与PDF、J2EE门户等的集成 3. 另外还有一些针对未来Apollo应用的很重要的功能,比如:本地数据缓存和脱机消息等。 下载地址: http://labs.adobe.com/technologies/livecycle_dataservices2_5/
新的Flex.org把内容分成5大部分 左边4个,分别是:Flex实例,Flex下载、Flex社区、帮助 右边一个大块是:Flex介绍和Flex盛会。
整体感觉比以前要清新爽洁。
最上面的一排菜单项好像比以前要多。 有博客、社区、文档、下载、工作、演示、支持。
Flexlib 1.5 下载地址: http://flexlib.googlecode.com/files/flexlib-.1.5.zip里面有4个目录: bin src docs examples 包含: AdvancedForm ConvertibleTreeList DragCanvas DraggableSlider HAccordion ImageMap PromptingTextinput ScrollableMenus SuperTabNavigator TreeGrid VerticalMenuBar 这些内容
怎样才能开发出一个好的软件系统呢?分成3个步骤: 1、首先确定用户需求,确保最后出口的软件确实是用户需要的东西。这是最重要的一步。 2、应用基本的OO原则来给系统添加可扩展性。 3、尽量使用系统易维护、可重用。
对象专注于自己要做的事情,而且只是自己份内的事情。 1. 对象应该做对象名所表明的事情 2. 每个对象应该表现出单一的一致性的概念。比如:鸭子这个抽象对象就不能表现得象一个真实的会叫的鸭子,或者象一只黄色的塑料鸭子。 3. 如果你的对象中的一个属性经常没有具体的值或者经常是Null,那么你的对象就可能承担了多余的职责。就需要考虑一下,这个属性确实是这个对象的一部分吗?
灵活性:这是用户满意所必需的。 封装:帮助你将代码组装成逻辑模块。任何时候看到了重复的代码,请想办法把它们封装起来。 功能:功能改变最好将影响局限在功能本身,不要影响其他的代码。
Caringen是一个Eric Feminella自己开发的一个小工具. 现在他把这个工具共享出来. Cairngen并不能让所有的人解决所有的问题, 它只是简单地加快开发的过程.
Cairngen是一个Caringorm的代码生成器, 它使得开发者可以很方便地生成Cairngorm项目的结构框架, 包括: Cairngorm项目的目录结构, 一个缺省的ModelLocator, Controller, ServiceLocator. Cairngen也可以生成Event, Command, Delegate类.
Cairngen使用Flex2和PHP5.2.0开发, 利用了AMFPHP. 安装过程十分方便. 你可以在Flex Builder的浏览器中直接运行Cairngen来快速方便地生成Cairngorm的类.
Cairngen使用'步骤(Sequence)'来描述Event, Command, Delegate类之间的关系. 在Cairngen中只要命名一个'步骤',点击一下生成按钮就可以生成一个'步骤'. 然后刷新一下Flex项目, 生成的类就OK了. 在一些特殊的情况下, 你不希望添加Delegate类, 那么你可以选择排除Delegate类的生成, 这样的话, Cairngen只生成Event和Command. 生成的Command类实现Cairngorm的Command接口, 但不实现IResponder接口. Cairngen还能够正确地将事件强制性转化(casting)为Coand中的类型, 并import所有相关的类.
系统需求: 1. 了解AMFPHP, Apache2.0.59和PHP5.2.0, 在使用Cairngen之前要求先安装AMFPHP, 而且gateway.php的访问虚拟目录应该如下设置: http://localhost/amfphp/gateway.php. 在1.0版正式发布后, 这个URL可以自由配置. 2. 还需要安装Flex2, PHP, 你可以用WAMP5来集成安装Apache5.5和PHP. 安装好PHP和AMFPHP后, 就可以解压缩Cairngen Alpha 1. 把services目录复制到amfphp的根目录下. 然后复制cairgen-ui目录到apache的www根目录下.
使用: 安装好PHP和AMFPHP后, 就可以开始使用Cairngen了. 打开浏览器输入http://localhost/cairngen-ui/Cairngen.html打开生成器的图形界面. 创建Cairgen项目: 点击 begin 给你的Cairngen项目命名. 这个名称与Flex项目的名称相同, 选择你的Cairngorm的版本, 选择Flex项目的路径. 设置项目中包路径(比如: com.domain.projectname). 最后, 检验一下输入内容, 点击"Create Project". 刷新Flex项目查看缺省的Cairngorm项目结构, ModelLocator, Service和Controller类.
创建"步骤":
首先是给步骤命名, 然后选择是否要生成Delegate类, 点击生成, 并刷新Flex项目. 可以发现需要的类已经生成完毕了. 现在需要把它们添加到Controller中. 接下来编辑模板文件: 你可以修改模板文件(.tpl)来实现你的特定的Cairngorm需求; 模板文件在 amfphp\services\com\ericfeminella\cairngen\templates目录下.
生成器下载地址: http://code.ericfeminella.com/cairngen/Cairngen%20Alpha%201%20PreRelease.zip
AppGen 1、在AppGen中没有能够生成Struts的Form 在gen的target的中,添加生成Struts Form的子任务 <!-- generate struts forms --> <xdoclet destdir="${build.dir}/${gen.dir}/web" excludedtags="@version,@author" addedtags="@xdoclet-generated at ${TODAY}" force="${xdoclet.force}" mergedir="metadata/web"> <fileset dir="F:\\Java\\Eclipse\\Space\\appfuse\\WEB-INF\\src"/>
<configParam name="basePackageName" value="org.appfuse"/>
<!-- generate struts forms --> <actionform templateFile="${template.dir}/generic/struts_form.xdt"> <packageSubstitution packages="model" substituteWith="webapp.form"/> </actionform> </xdoclet>
2、在AppGen中没有能够生成struts-config.xml 在gen的target中添加生成Struts-config.xml的子任务。 <taskdef name="webdoclet" classname="xdoclet.modules.web.WebDocletTask"> <classpath> <path refid="xdoclet.classpath"/> <path refid="web.libs.classpath"/> </classpath> </taskdef> <webdoclet destdir="build/gen/web/WEB-INF" force="true" mergedir="metadata/web" excludedtags="@version,@author" verbose="true"> <fileset dir="${model.src.dir}"/> <strutsconfigxml validateXML="true" version="1.2"/> <strutsvalidationxml version="1.1.3"/> </webdoclet> 其中需要注意的是web.libs.classpath,一开始我没有添加这些库,结果无法正确生成配置文件。 在Appfuse中,大多数的Action是继承BaseAction的,只有UploadAction是直接继承Struts的Action;在没有添加库文件时,生成的struts-config.xml只包含一个UploadAction的映射。 添加完整的库文件后,才能生成正确的配置文件。
最后整理一下,使用AppGen生成的文件:
1、ApplicationContext-hibernate.xml中添加personDao的配置,ApplicationContext-service.xml中添加personManager的配置 2、PersonDaoHibernate.java,PersonManager.java,PersonManagerImpl.java,PersonAction.java,PersonForm.java,PersonAction.java 3、PersonForm.jsp,PersonList.jsp 4、struts-config.xml覆盖 5、menu-config-Person.xml合并到menu-config.xml中,menu-Person.jsp添加到common\menu.jsp中 6、sample-data-Person.xml,用于测试的数据
Appfuse中与数据库有关的有用的脚本只有少数的几个,其他还有一些新建、编译、打包、部署、安装等等,由于我是在Eclipse环境下,使用Tomcat插件进行开发,对于这些其实不需要特别的关心。 列举一下我认为有用的: 1、hibernatedoclet 使用xdoclet生成Hibernate的映射文件hbm.xml。 2、db-prepare 使用hbm2ddl使用并执行create-table.sql,根据hbm在数据库中建立数据表。要注意的是,生成过程还需要POJO的class。 3、db-load 使用dbunit将Xml格式的数据insert到数据库中。
目前我的build.xml中只包含这几个target。
另外,AppGen的build.xml中,我认为有用的也主要是init和gen这两个target。 其中init用于交互输入参数,而gen是使用xdoclet根据模板生成Actions/Controllers, Action/Controller Tests, test data, i18n keys and JSPs。
ASAP是一个开源的RIA应用库,主要面向可维护的、多视频的、基于事件互动的应用开发。提供实用的、可重用的、基于模式的解决方案。 目前版本是0.9.4,从他们的网站上看,好像已经不少公司已经采用了他们的框架。
主页:http://asapframework.org/wiki/bin/view/ASAP/
最近Adobe公司的一批人在Google的Code Project上发布了FlexLib,一个开源的Flex2组件库。 现有的组件包括: ConvertibleTreeList, Draggable Slider, PromptingTextInput, Scrollable Menu Controls, SuperTabNavigator, Alternative Scrolling Canvases, Horizontal Accordion
大家可以去看看。
另外,还有个人也发布了一个Flex2 组件目录: FlexBox。这个网站
现在有一个方便的做法来根据选择Enable/Disable控件。比如:有一个DataGrid和数个操作数据的按钮,当选中某行是激活其中一个按钮,选中另一行时,激活其中另一个按钮,等等。
通常的做法是写一大堆的if else语句,或者是设置一堆变量绑定到按钮上,然后在代码中设置这些变量的值,比如:
[Bindable] private var somethingSelected:Boolean = false; <mx:Button label="Publish" click="publishItem()" enabled="{somethingSelected}" />
这是一个有益的尝试。所有的依赖于DataGrid状态的控件都绑定一个变量。改变变量的值就改变控件的enabled状态。但是你还需要确定改变变量值的时机。比如:
private function publishItem() : void { // get the selected item // publish it grid.selectedItem = -1; // clear the selection somethingSelected = false; }
在复杂的情况下,假设有另一个按钮需要在选中某个特定值时被激活。那么你不仅需要考虑选中对应的那个变量,还需要考虑其他按钮的情况。也就是说,在publishItem函数中你还需要设置其他变量的值。
一个更方便的做法是这样的。现在我们Publish按钮和somethingSelected变量进行绑定。我们还需要做的是把somethingSelected变量与DataGrid的选中状态进行关联。对此,我们可以使用<mx:Binding>标签。
<mx:Binding source="grid.selectedIndex >= 0" destination="somethingSelected" />
这样somethingSelected的值与DataGrid的选中状态就紧密地联系起来了。当选中了DataGrid中的某条数据时,所有进行enabled="{somethingSelected}"绑定的按钮就被激活。<mx:Binding>标签的source属性不需要一定是一个变量,完全可以是一个表达式。
比如稍微复杂一点的一个例子:
<mx:Binding source="grid.selectedItem.code == 1" destination="codeOnePicked" />
这样,当DataGrid选中行的code字段的值为1时,变量codeOnePicked的值被设置为true。 mxml中可以如下定义:
<mx:CheckBox label="Code One?" selected="{codeOnePicked}" /> <mx:Button label="Publish" enabled="{somethingSelected && !codeOnePicked}" />
这样,当选中行的code字段值为1时,上面的复选框被激活,而下面的按钮无效。
这次详细地说明一下Appfuse的认证过程: 1. 在Web.xml中定义了FilterChainProxy, 2. 在Security.xml中对FilterChain进行了详细定义。 3. 其中AuthenticationManager的Providers包含了DaoAuthenticationProvider,而此Provider的UserDetailsService引用了userDao。 4. 在applicationContext-hibernate.xml中定义了userDao,是org.appfuse.dao.hibernate.UserDaoHibernate。 5. 而UserDaoHibernate实现了UserDetailsService接口,主要是实现了loadUserByUsername(String)方法,其中参数是String类型的用户名,而返回一个UserDetails类型的对象。 6. 在LoadUserByUsername方法中,使用getHibernateTemplate().find来获得了一个org.appfuse.model.User类型的List,其中User类型实现了UserDetails。
Acegi提供了一个优秀的基于J2EE企业级应用的安全认证机制。尤其是对于Spring框架的支持,在J2EE的企业软件开发解决方案中是领先的。 来看一下Acegi的重要的共享组件。如果组件是框架的核心而且一旦缺少这些组件框架将无法运转,那么这些组件可以称为是“共享”的。这些Java类型是系统其他部分的基础,所以理解他们是十分重要的,虽然你可以并不会直接与之互动。
其中最基础的对象是SecurityContextHolder,用于存储应用安全上下文的细节信息。缺省情况下,SecurityContextHolder使用ThreadLocal来存储信息,这意味着安全上下文对于同一个线程的所有方法都是有效的。有一些应用不适合使用ThreadLocal,比如:一个Swing的客户端可能希望所有JVM所有的线程都使用相同的安全上下文。对于这种情况,你可以用SecurityContextHolder.MODE_GLOBAL。你可以把SecurityContextHolder从缺省模式MODE_THREADLOCAL改变为MODE_GLOBAL。
在SecurityContextHolder中存储了与应用互动的规则。Acegi使用Authentication对象来表示这些信息。这并不是需要你自己创建Authentication对象,更通常的做法是查询到一个Authentication对象。举例如下,在应用的任何一个地方都可以这样使用:
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if( obj instanceof UserDetails){ String username=((UserDetails)obj).getUsername(); }else{ String username=obj.toString(); }
上面这段代码介绍了不少有意思的对象和关系。首先,大家会发现在SecurityContextHolder和Authentication之间存在着一个即时对象:SecurityContext,SecurityContextHolder.GetContext()返回的类型就是SecurityContext。Acegi有数个SecurtiyContext的实现。
另一个值得注意的是我们从Authentication中获得了一个规则。这个规则的类型是:Object。大多数情况下,我们可以把它强制性转换成UserDetails对象。UserDetails是Acegi的核心接口。它代表了一种规则,但是经过了应用相关的扩展。可以把UserDetails想象成为应用数据库与Acegi的SecurityContextHolder需要的两者之间的适配器(Adapter)。如果作为应用自己的数据库的代表,那么可以把UserDetails强制性转换为其原始类,这样,你就可以调用其中的业务方法(比如:getEmail()等等)。
那么,为什么要提供一个UserDetails对象呢?是这样的:有一个特殊的接口:UserDetailsService,这个接口只有一个方法,这个方法接收一个String类型的表示用户名的参数,返回UserDetails对象。大多数认证提供provider装配一个代理到UserDetailsService上。UserDetailsService被用于创建SecurityContextHolder中存储的Authentication对象。Acegi中提供了若干个UserDetailsService的实现,一个使用内存Map,一个用JDBC。大多数用户倾向于写一个自己的实现,通常是使用DAO。不论UserDetailsService返回的是什么,都可以通过SecurityContextHolder获得。
Authentication提供另一个重要的方法是getAuthorites()。这个方法返回一个GrantedAuthority对象的数组。GrantedAuthority是授权给的认证。这个认证通常指的是“角色”,比如:ROLE_ADMINISTRATOR或者ROLE_HR_SUPERVISOR。这些角色需配置用于web认证,方法认证和域对象认证。如果Acegi的其他部分希望看到这些认证,那么UserDetailsService返回GrantedAuthority对象即可。
最后,有时你需要在HTTP requests之间传递SecurityContext,有时每次请求都需要重新认证。那么可以使用HttpSessionContextIntergrationFilter,这是用于在HTTP Request之间传递SecurityContext的东东。就象名称所表示的那样:HttpSession用于存储这些信息。但是你不需要直接操作HttpSession。
1、编译部署成功后,在地址栏中输入:http://localhost:8080/appfuse,即可看到首页-登录页
2、第一个页面是login.jsp,此页面使用include包含了taglibs.jsp和loginForm.jsp 其中taglibs.jsp是一个公共页面,定义了页面中使用到的taglibs 而loginForm.jsp则是用户登录Form定义的页面
3、现在会发现一点:此时浏览器中显示的内容比login.jsp的内容要多,包括最上方的语言切换行,下面的标题行,最下文的版本信息行等。 这是因为Appfuse使用了Sitemesh的Decorator。 在WEB-INF目录下,有两个Xml文件:sitemesh.xml和decorators.xml 在Web.xml中定义了sitemesh的Filter 在decorators目录下有default.jsp这个布局文件。 Sitemesh的作用就是对定义的文件进行装饰。 在Appfuse中,会对所有的文件作为Body安放到default.jsp这个布局文件中。
4、查看login.jsp,发现他的Form的Action是比较奇怪的:j_security_check 这并不是一个真实存在的URL 在security.xml中可以发现,这个是authenticationProcessingFilter的filterProcessesUrl属性 这就是Spring中使用Acegi安全认证服务,在Appfuse1.9.4中使用的是Acegi的基于表单的身份认证 filterProcessUrll告诉AuthenticationProcessingFilter应该拦截哪个URL 属性authenticationFailureUrl指定当身份验证失败时用户应该被送往哪里 defaultTargetUrl定义登陆成功时转向的页面
5、用户登录成功后,转到defaultTargeUrl所指定的路径:/ 根路径 而此路径的Welcome file是index.jsp, 而index.jsp又redirect到了mainMenum.html 因此会显示mainMenu.html页面。
http://www.graniteds.org/confluence/display/INTRO/Granite+Data+Services+Overview
支持AMF3
支持AMF3
EJB3服务的透明externalization机制,参见Flex2的开发文档
http://www.adobe.com/livedocs/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=ent_services_config_097_11.html
在Flex的序列化和反序列化过程中,只能传递public的,非静态的属性,如果要序列化此部分内容在Flex与Java间传递,必须使用externalization机制
ActionScript3的Beans的Lazy Initialize
EJB3实体Bean到ActionScript3的类的代码生成(计划中)
POJO服务(远程调用简单Java类的公共方法)
一系列Flex组件用于复杂的数据结构(计划中)、
(续昨日) 页面乱码的问题,今天看了一下,是资源文件的问题。 原来appfuse提供的资源文件没有用native2ascii处理过。
学习心得: 1、Appfuse的页面基本上都包含了commom\taglibs.jsp,在此文件中定义了taglib 在Tomcat5以上的版本中,不再需要在Web.xml中定义Taglib了,只需要在WEB-INF\lib目录下放置standard.jar就可以了。
目前网上有一些介绍如何在Eclipse中应用Appfuse的文章,但因为Appfuse本身附带了强大的Ant任务,Eclipse在其中如Raible据说更多充当的是文本编辑器的作用。编译/部署都使用Ant完成,也比较方便。但是,无法跟踪调试源码,有些不爽,因此,想把Appfuse进行提炼完全整合到Eclipse中。
一、运行Ant New 1. 不知道为什么,此项任务会到jre的lib目录下寻找tools.jar,没有仔细地查找原因,简单地把jdk\lib\tools.jar拷贝过来 2. 需要定义CATALINA_HOME这个环境变量 OK, 完成上述两个步骤后,Ant命令执行成功,命令过程中有数个选项可以选择,比如:应用名称,数据库名称,Package名称,这些关系都不大。 有一个选项比较重要一些,是询问使用何种Web框架,有webwork, tapestry, spring, jsf等,我也是使用的默认选项:struts.
二、启动MySql数据库,运行Ant setup-db 在数据库中生成mydb数据库,以及数据表
三、在Eclipse中建立Tomcat Project名字与Ant New中输入的应用一致。
四、将common, decorators, images, MATA-INF, scripts, styles这几个目录从ant new生成的目录复制到Eclipse项目的目录中. 将web-inf\pages目录下的jsp文件拷贝到Eclipse相同目录下。
五、运行ant webdoclet, 生成Form目录下的3个Form的java源文件,以及model目录下的2个hibernate的xml映射文件
六、将org.appfuse.webapp,org.appfuse.sercies, org.appfuse.dao以及model和util目录所包含的所有Java源文件复制到Eclipse项目所在的文件夹web-inf\src中。
七、将lib复制到web-inf\lib目录下 acegi-security-1.0.2.jar activation.jar antlr-2.7.6.jar antlr.jar asm.jar aspectjweaver-1.5.2.jar cglib-2.1.3.jar clickstream-1.0.2.jar commons-beanutils.jar commons-codec-1.3.jar commons-collections.jar commons-dbcp.jar commons-digester.jar commons-fileupload.jar commons-io.jar commons-lang.jar commons-logging-1.1.jar commons-pool.jar commons-validator.jar displaytag-1.1.jar dom4j-1.6.1.jar dwr.jar ehcache-1.2.3.jar hibernate3.jar itext-1.4.jar jakarta-oro.jar jstl.jar jta.jar log4j-1.2.11.jar mail.jar mysql-connector-java-5.0.3-bin.jar oscache-2.3.2.jar sitemesh-2.2.1.jar spring.jar standard.jar struts-el.jar struts-menu-2.4.2.jar struts.jar urlrewrite-3.0-beta.jar velocity-1.4.jar velocity-tools-view-1.1.jar
八、启动Sysdeo Tomcat, 在浏览器中输入http://locallhost:8080/myapp, 出现页面,输入mraible/tomcat,登录成功。
九、还有一个问题就是目前所有的页面都是乱码,明天再解决了,今天要睡觉了。
最近在Oracle网站上下载一个Oracle 10g安装了一下,只有一张盘,而且安装时还有一个便捷的选项,非常方便。 可以安装完毕后,使用的时候却遇到了麻烦:如果只是本机的访问 sqlplus system/manager这样是没有问题的。 但是如果使用 sqlplus system/manager@orcl的时候却会报ora-12514的错误。 解决方法: 1. 打开<OracleHome>/network/admin/listener.ora文件,找到: SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (SID_NAME = PLSExtProc) (ORACLE_HOME = D:\oracle\product\10.2.0\db_1) (PROGRAM = extproc) ) ) 2. 添加: (SID_DESC = (GLOBAL_DBNAME = ORACLE) (ORACLE_HOME = D:\oracle\product\10.2.0\db_1) (SID_NAME = ORACLE) ) 3. 最后变成: SID_LIST_LISTENER = (SID_LIST = (SID_DESC = (SID_NAME = PLSExtProc) (ORACLE_HOME = D:\oracle\product\10.2.0\db_1) (PROGRAM = extproc) ) (SID_DESC = (GLOBAL_DBNAME = ORACLE) (ORACLE_HOME = D:\oracle\product\10.2.0\db_1) (SID_NAME = ORACLE) ) ) 4. 保存文件,重启服务中的TNSListener,OK! PS: Oracle10g有一个好处:不再与Tomcat的端口冲突了。原来的Oracle9i安装完成后,8080端口就会被占用,一般都需要改tomcat的端口。现在终于轻松了。
Flex是一个事件驱动的编程模型, 任何事情的发生, 其背后必然存在一个事件. 而开发者第一次看到MXML时, 很难体会到一个Xml标记的应用的事件流和实例化的生命周期. 这个对于HTML和Flash的开发者尤其会感到困惑, 因为其熟悉的方式与Flex的一点也不相似. HTML的实例化是从上到下的, Flash的执行是从Frame0开始一帧帧运行的. 而Flex则又有不同.
从我们开始学习Flex时, 我们就需要了解事件流和MXML的实例化. 我非常困惑因为我实在难以理解什么样的事件会被触发或者事件什么时候会被触发. 关键是要理解事件的基础并亲自观察事件流的初始化.
我们来看一个简单的MXML的应用.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" backgroundGradientColors="[#67cbff, #fcffff]" color="#000000" fontSize="12" preinitialize="report( event , 'preinitialize' )" initialize="report( event , 'initialize' )" creationComplete="report( event , 'creationComplete' )" applicationComplete="report( event , 'applicationComplete' )" > <mx:Script> <![CDATA[ [Bindable] public var outTextData:String=""; public function report( event:Event , value:String ):void { outTextData += String( flash.utils.getTimer() ) + 'ms >> ' + event.currentTarget + '.' + value + '\n'; } ]]> </mx:Script> <mx:TextArea id="outTextArea" text="{ outTextData }" right="10" left="10" top="50" bottom="10" alpha="0.5" wordWrap="false" initialize="report( event , 'initialize' )" creationComplete="report( event , 'creationComplete' )" /> <mx:Button y="10" height="30" left="168" width="150" id="HelloButton" label="Say Hello" initialize="report( event , 'initialize' )" creationComplete="report( event , 'creationComplete' )" rollOver="report( event , 'rollOver' )" rollOut="report( event , 'rollOut' )" click="report( event , 'click > Hello!' )" /> <mx:Button id="GoodByeButton" label="Say Goodbye" y="10" left="10" height="30" width="150" color="#000000" initialize="report( event , 'initialize' )" creationComplete="report( event , 'creationComplete' )" click="report( event , 'click > Goodbye!' )" /> <mx:Button id="ClearButton" label="Clear" y="10" left="326" height="30" color="#000000" right="10" initialize="report( event , 'initialize' )" creationComplete="report( event , 'creationComplete' )" click="outTextData='';report( event , 'click' )" /> </mx:Application>
这个应用运行时, 输出了实例流程和事件流. 这校我们就能够看到所有事件的触发顺序. 可以发现应用启动后, 事件的顺序是一定的. 下面是输出的内容:
167ms >> EventFlow0.preinitialize 183ms >> EventFlow0.outTextArea.initialize 187ms >> EventFlow0.HelloButton.initialize 188ms >> EventFlow0.GoodByeButton.initialize 189ms >> EventFlow0.ClearButton.initialize 189ms >> EventFlow0.initialize 243ms >> EventFlow0.outTextArea.creationComplete 243ms >> EventFlow0.HelloButton.creationComplete 243ms >> EventFlow0.GoodByeButton.creationComplete 244ms >> EventFlow0.ClearButton.creationComplete 244ms >> EventFlow0.creationComplete 246ms >> EventFlow0.applicationComplete
一旦applicationComplete事件触发后, 组件就会在鼠标事件派发后触发自己的事件.
1807ms >> EventFlow0.HelloButton.rollOver 2596ms >> EventFlow0.HelloButton.rollOut 2954ms >> EventFlow0.HelloButton.rollOver 3170ms >> EventFlow0.HelloButton.rollOut 3543ms >> EventFlow0.HelloButton.rollOver 4052ms >> EventFlow0.HelloButton.click > Hello! 4267ms >> EventFlow0.HelloButton.click > Hello! 4474ms >> EventFlow0.HelloButton.click > Hello! 4569ms >> EventFlow0.HelloButton.rollOut 4907ms >> EventFlow0.GoodByeButton.click > Goodbye! 5130ms >> EventFlow0.GoodByeButton.click > Goodbye!
在ActionScript 3中,你会发现在flash.utils包中有一系列函数提供了反射的功能。主要包含以下功能:
* 确定对象的类 * 获取类的成员、方法、构造函数、父类的信息 * 确定接口声明的常数和方法 * 在运行时根据类名创建类的实例 * 在运行时根据成员名称获取或者设置对象成员的值 * 在运行时根据方法名称,调用对象的方法
你可以使用类似于"describeType"之类的功能,它返回一个Xml对象。举一个例子:
package { import flash.display.Sprite; import flash.utils.describeType; public class DescribeTypeExample extends Sprite { public function DescribeTypeExample() { var child:Sprite = new Sprite(); var description:XML = describeType(child); trace(description..accessor.@name.toXMLString()); } } }
如果你想进一步,根据类名创建对象的实例,我们可以使用"getDefinitionByName()"
package { import flash.display.DisplayObject; import flash.display.Sprite; import flash.utils.getDefinitionByName;
public class GetDefinitionByNameExample extends Sprite { private var bgColor:uint = 0xFFCC00; private var size:uint = 80;
public function GetDefinitionByNameExample() { var ClassReference:Class = getDefinitionByName(“flash.display.Sprite”) as Class; var instance:Object = new ClassReference(); instance.graphics.beginFill(bgColor); instance.graphics.drawRect(0, 0, size, size); instance.graphics.endFill(); addChild(DisplayObject(instance)); } } }
尽管这是一些非常方便的方法,但是在FlashPlayer中使用反射还是会有许多的限制,因为缺乏运行时的动态源码编译。上面的功能对于那些在内建的类,比如:Sprite类来说无疑是有用的,但是对于自定义类来说,我们会遇到很多麻烦。比如:
package { import com.customtypes.string; // Custom String Implementation Class import flash.utils.getDefinitionByName;
public class GetDefinitionByNameExample { public function GetDefinitionByNameExample() { var ClassReference:Class = getDefinitionByName(“com.customtypes.string”) as Class; var instance:Object = new ClassReference(); instance.customParameter = “my parameter”; } } }
尽管我们使用了import语句,但是"getDefinitionByName()"还是会失败。原因上面已经说过了,在运行时编译源代码是不允许的。也许以后可以。在目前情况下,要实现上述功能,至少要在代码中初始化一个类的实例。也就是声明一个类的实例:
var customType : com.customtypes.string;
其实非常简单, 只需要在application类中定义一个public类型的变量就行了:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
public var foo:String = "bar";
]]>
</mx:Script>
</mx:Application>
在其它的地方就可以使用
Application.application.foo
来访问了. 如果需要, 也可以添加 Bindable 属性. 但是要注意到一个问题就是, 在使用的时候编译器不会进行类型的检查, 因此最好添加类型强制转换.
当然, 全局变量实现的另一种方法是使用单例模式.
有许多方法可以把一个应用拆分成基于个独立下载的部分。甚至于将每个类都分成单独的文件,由ClassLoader在需要的时候加载。但是如果这样效率是比较低下的,因为类的引用有着明显的“引用地域”;如果你引用了一个类,往往会马上牵涉到需要引用一大堆其他的类,如果把这些类全都打包在一起,效率会高得多。
由编译器自动选择一个非常好的打包方法是比较困难的。很可能需要在应用开发时进行一些设定,并不时地监控类的引用。能够统计出最优的分拆方法:应用应该分成几个SWF,哪些类应该放在哪个SWF中。但是这种方法听起来更象是一种研究范畴,实际操作起来非常困难。
让我们来看一些更具有操作性的方法。
很多应用分解后,包含两种类型的功能:“启动后立即填充”和“启动后稍后填充”。
有许许多多的应用是这种模式的。比如:游戏;你有一个游戏引擎和一些游戏场景。或者Portals和Porlets; 一些基础的共享功能和数据驱动的小应用。或者是一个大型的有着1500个页面的保险应用,运行特定功能是只会访问一小部分的页面。或者是充斥大量内容的应用,它可以独立的更新部分内容而不是强制用户每次浏览时都必须下载全部内容。
我称这些相对独立的可以延迟加载的功能为“模块”(Modules),称加载模块的应用为“Shell”。
在这里,我们先不看如何做,先来看一些
shell需要能够与模块交互,同样模块也需要和Shell交互。如果shell引用了modules的一个类,那么它会把它链接进来。同样,如果模块类引用了shell类,它也会把它链接进来。应用能正常运行只有两个方法:或者引用是相同而且共享的(这样就不需要下载两次),或者两者是不同的,而且没有任何关系(尽管两个类名字相同,但是它们被认为是无关的,而且不能交互)。
最好的解决办法是让模块和shell通过接口交互。这样,shell不需要引用模块,而是引用模块会实现的一些接口。同样,模块不实现shell的类,而是允许调用的API接口。
这样在shell变化的时候减少了重新编译模块的次数。具体实现的变化频率往往会比接口本身的变化高得多,而只要接口稳定,就不需要重新编译所有的东西。
注意:需要使用extern(或者extern-library-path)选项来创建模块,这样可以自动剔除shell的类,因为模块是被加载到shell的子应用域中的,将shell的类剔除是安全的。这样模块可以真正直接引用shell中的类。
这个例子包括了应用中使用RSL的完整流程。使用命令行进行编译,但是你可以使用FlexBuilder用相同的过程来创建使用RSL。
记住SWC文件是一个包含SWF文件的二进制文件,而SWF文件包含运行时的定义和附属元数据。你可以用压缩工具比如WinZip来打开SWC文件。 在使用RSL之前,首先需要了解如何静态链接一个SWC文件。
在这个例子中,应用有一个app.mxml文件,使用ProductConfigurator.as和ProductView.as。文件目录如下:
project/src/app.mxml project/libsrc/ProductConfigurator.as project/libsrc/ProductView.as project/lib/ project/bin/
编译这个应用时,可以使用source-path选项将/libsrc目录下的类链接进来,方法如下:
cd project/src mxmlc -o=../bin/app.swf -source-path+=../libsrc app.mxml
这个命令添加ProductConfigurator和ProductView类到SWF文件中。
如果要创建库,可以用compc来创建SWC文件,用下面的命令:
cd project compc -source-path+=libsrc -debug=false -o=lib/mylib.swc ProductConfigurator ProductView
注意要将debug选项设置为false. 生成结果是project/lib/mylib.swc文件,包含ProductConfigurator和ProductView两个类。
现在可以使用新创建的库来重新编译应用,用library-path选项来指定库,方法如下:
cd project/src mxmlc -o=../bin/app.swf -library-path+=../lib/mylib.swc app.mxml
创建库以后,你可以用RSL来重新编译生成应用。完整的步骤如下:
1. 指示编译器不要将库链接到应用中。 2. 准备RSL,以便于在运行时使用。 3. 指示编译器生成附加元数据用于加载RSL。
第一步是指定编译生成应用时库中的哪些类需要排除在外。主要是使用external-library-path选项,如下面的例子所示:
cd project/src mxmlc -o=../bin/app.swf -external-library-path+=../lib/mylib.swc app.mxml
如果你尝试运行app.swf,Flash Player会抛出一个运行时异常。因为ProductConfigurator和ProductView类还未定义。external-library-path配置选项告诉编译器编译这些库,但是忽略了定义。你也可以使用externs选项,但是一般来说,使用external-library-path更方便。
下一步是准备RSL以便于能在运行时找到它。首先从SWC文件中将library.swf解压出来。
下面是如何解压的例子:
cd project/lib unzip mylib.swc library.swf mv library.swf ../bin/myrsl.swf
此例子中将library.swf更名为myrsl.swf,并把它移动到应用SWF文件所在的目录。
最后一步是使用RSL重新编译应用。主要是使用runtime-shared-libraries选项,方法如下:
cd project/src mxmlc -o=../bin/app.swf -external-library-path+=../lib/mylib.swc -runtime-shared-libraries=myrsl.swf app.mxml
现在新的SWF文件会在运行应用前动态加载RSL了。
在编译应用时要使用RSL, 需要使用下列编译选项:
* runtime-shared-libraries 提供运行运行时共享库的位置. * external-library-path|externs|load-externs 提供编译时库的位置. 编译器需要这个信息动态链接.
使用runtime-shared-libraries选项来指定SWF文件的位置, 这样应用能够在运行时加载RSL. 需要指定SWF与部署位置的相对路径. 比如: 如果把library.swf文件放在web_root/libraries目录下, 而应用在web_root目录下, 那么文件的指定方法是: libraries/library.swf 可以用这个选项指定多个库. 如果指定了多个库, 需要用逗号分隔.
使用external-library-path选项来指定library在编译时的SWC文件或者目录的位置. 编译器会在编译时根据这个选项进行链接的检查. 你还可以使用externs或者load-externs选项来指定其他单独的classes或者xml文件来定义库的内容.
下面是一个编译MyApp应用的命令行示例, 其中使用了2个库:
mxmlc -runtime-shared-libraries= ../libraries/CustomCellRenderer/library.swf, ../libraries/CustomDataGrid/library.swf -external-library-path=../libraries/CustomCellRenderer, ../libraries/CustomDataGrid MyApp.mxml
库的顺序非常重要, 因为基础类必须先加载.
你先可以使用配置文件, 示例如下:
<compiler> <external-library-path> <path-element>../libraries/CustomCellRenderer</path-element> <path-element>../libraries/CustomDataGrid</path-element> <path-element>../libs/playerglobal.swc</path-element> </external-library-path> </compiler> <runtime-shared-libraries> <url>../libraries/CustomCellRenderer/library.swf</url> <url>../libraries/CustomDataGrid/library.swf</url> </runtime-shared-libraries>
runtime-shared-libraries选项值是library.swf文件是相对部署目录的路径. external-library-path选项是编译时SWC文件的路径. 因此, 必须先知道库的部署路径.
示例中, 编译时文件结构如下:
c:/appfiles/MyApp.mxml c:/libraries/CustomCellRenderer/CustomCellRenderer.swc c:/libraries/CustomDataGrid/CustomDataGrid.swc
library.swf在编译进不是必需的. Flex编译器不验证SWF文件的存在与否, 但会把路径信息编译进行最后的应用代码中.
文件的部署结构如下:
web_root/MyApp.swf web_root/libraries/CustomCellRenderer/library.swf web_root/libraries/CustomDataGrid/library.swf
创建库
可以使用Flex Builder或者Compc命令行来创建库. 库可以是一个SWC文件, 或者是包含了library.swf和catalog.xml文件的目录. 一个库通常包含自定义组件和类. 然后就可以在RSL中使用这些库了.
在Flex Bulder中, 通过使用Flex Library Build Path对话框来添加资源到库中.
在命令行中, 使用include-classes和include-namespaces选项来添加文件到库中.
下面的命令行示例说明了如何创建一个名字叫CustomCellRenderer的库:
compc -source-path ../mycomponents/components/local -include-classes CustomCellRendererComponent -directory=true -debug=false -output ../libraries/CustomCellRenderer
所有包含的组件必须是静态链接的文件. 使用compc编译器创建库时, 不能使用include-file选项, 因为这个选项不是将library.swf文件静态链接到库中的.
可以使用directory选项指定输出到一个目录而不是到一个SWC文件中:
<?xml version="1.0"> <flex-config> <compiler> <source-path> <path-element>mycomponents/components/local</path-element> </source-path> </compiler> <output>libraries/CustomCellRenderer</output> <directory>true</directory> <debug>false</false> <include-classes> <class>CustomCellRendererComponent</class> </include-classes> </flex-config>
输出会是一个目录,目录里包含两个文件 * catalog.xml * library.swf
创建library.swf文件后, 你可以编译应用并且指定文件的位置.
UI的问题如何解决呢: AWT, Swing, SWT, 或者是一些其他的比如: Tkinter, WxPython之类的东西? Bruce认为这些对于创建一个真正跨平台的应用是不够的. 他认为最好的解决方案就是Flex!
Flex and Flash provide a complete, unlimited, flexible tool to build user experiences. From the standpoint of a programmer’s time investment, you can learn a single language for building UIs without worrying about running into problems or limitations later—issues like:
- Installation problems
- Constraints on what you can create
- Sudden steep climbs in the learning curve
原文链接:
http://www.artima.com/weblogs/viewpost.jsp?thread=193593
Computing Thoughts
Hybridizing Java
by Bruce Eckel
January 30, 2007
RSL也需要谨慎使用
RSL也不是对于所有的应用都是有益的. 需要对应用RSL前后的下载时间和启动时间都测试过, 才能得到正确的结论.
RSL不能跨域共享. 如果客户在一个域中使用了RSL, 然后运行了另一个域的应用, 虽然这两个RSL是相同的, 但是需要下载两次.
RSL通常会增加应用的启动时间. 这是应用不管整个库实际如何使用, 只是简单地全部加载整个库. 就这一点来说, RSL越小越好. 这与静态链接库的使用是不同的. 当你编译一个Felx应用时, 编译器只解开需要的组件. 一般来说, 库的大小可以是任意的, 它只影响编译时间而不会影响下载的时间.
如果在好几个应用中使用相同的组件库, 那么可以考虑合并这些库, 形成一个RSL. 但是如果库合并后, 每个应用只会用到其中的一小部分, 那么还不如多加载几个小RSL更高效.
如果一些类重复打包在多个RSL中, 那么一定要注意同步更新的问题.
RSL不能应用在基类是Sprite或者MovieClip的纯ActionScript项目中. 因为RSL需要基类知道如何加载RSL, 比如: Application或者SimpleApplication.
关于 framework.swc文件
framework.swc是一个标准的SWC文件. 缺省地它不能用作RSL. 整个framwork.swc文件不被链接到任何一个应用中. Flex编译器只将那些应用用到的部分链接到生成最后的SWF文件. 比如: 如果一个应用只使用了Button, Panel和TextArea控件, 那么只有这几个控件和它们的依赖项被编译器链接.
几乎所有的应用都需要framework.swc文件的一部分, 但是它并不适合作为RSL. 原因如上据说, RSL是整个链接, 不管实际使用多少的. 如果RSL包含了很多类, 而应用只使用了其中的一小部分, 那么这样的加载方式并不是最合理的. 这样使用会造成应用的启动时间大大增加.
RSL的优点
下面的一个例子说明了将几个的共享组件做成RSL的优点. 在这个例子中, 组件库的大小是150K, 编译后的应用的大小是100K.
使用了RSL, RSL只被下载一次. 那么合计下载量是350K, 节约了30%. 如果再添加第3个, 第4个应用的话, 每次都能150K的下载量.
一般来说, 在一个域中使用同一个RSL的应用越多, 那么好处就越大.
理解链接可以帮助你理解RSL是怎样工作的. Flex编译器支持静态链接和动态链接. 静态链接是最通常的方法. 但是动态链接使你借助于RSL来实现SWF文件的缩水以及应用的下载次数.
当你使用静态链接时, 编译器将应用中所有引用的类和依赖生成到最终的SWF文件中, 这个文件会比较大, 下载也会比较慢, 但是下载完毕后, 运行会比较快, 因为SWF文件中已经包含了所有的代码.
如果你的应用中使用了类库, 那么你需要使用类路径或者是添加SWC文件. 如果是使用类路径, 编译器会将类路径中用到的那部分类打包生成到SWF文件中. 如果是使用SWC文件, 编译器会将整个SWC文件打包到SWF文件中.
动态链接是这样的: 一个应用要使用的一部分类存在于一个外部的文件中, 运行时动态加载. 这样的话, 主SWF文件可以小一些, 但是应用依赖于运行时加载的外部文件. RSL就是使用动态链接的.
如果想使用动态链接类, 需要把它们编译成库. 然后编译器将库中的内容从SWF文件剔除出去. 而且必须在编译时提供链接检查.
为指定哪些文件是动态链接的,需要使用外部库路径选项,外部选项或者外部加载编译选项. 这些选项告诉编译器从应用中去除此部分内容, 而预备在运行时调用. 外部选项为动态链接指定了单独的类. 外部加载选项指定了一个XML文件, 描述了动态链接的类.
指定SL的外部资源的顺序是非常关键的, 因为被其他类调用的基础类必须被首先加载.
你还要用runtime-shared-libraries选项来指定RSL的位置.
你可以使用link-report这个编译选项来查看应用的链接信息.
减小应用SWF文件大小的一个方法就是将一些共享的外部资源拆分出去, 成为一个独立的文件, 这样可以单独地加载缓存到客户端. 这些共享资源可以由多个应用在运行时进行加载, 但是传递到客户端的动作只会发生一次. 这些共享文件被称为运行时共享库(Runtime Shared Libraries)或者简写为RSL.
如果你有多个应用而且这些应用共享一些核心组件或者类, 那么作为RSL,用户只会唯一的一次加载这些资源. 只要应用在同一个域中, 这些应用共享同一个缓存的RSL, 这样应用文件的大小就减小了. 使用RSL的应用越多, 效果越好, 如果只有一个应用, 总的文件大小不但不减小,反而会增大.
你既可以使用Flex Builder通过项目选项来创建Flex的Library项目,也可以使用compc这样的命令行. 编译好RSL以后, 可以在编译应用时把Library的位置传递给编译器.
下面是适合使用RSL的典型用例: * 大型应用, 需要使用通用组件库加载多个小型应用. 最顶级的应用和下级应用可以共享存储在RSL中的组件.
* 在一个服务器上的使用通用组件库的系列应用. 当用户操作第一个应用时, 用户下载应用的SWF文件和RSL. 当操作第二个应用时, 用户就只需要下载应用本身的SWF文件就行了. * 一个独立的应用使用RSL的意义在于: 如果这个应用本身会频繁的修改, 而有一部分组件是极少改动的. 在这种情况下, 使用RSL的好处是: 组件下载一次, 而应用可以多次下载.
总结起来需要注意的原则就是:尽量在编译的阶段发现错误,因为这时发现并解决错误远远要比在运行时出错解决起来容易的多。可是实际情况中很多人经常会违反这一原则,下面是我列举的10个技巧:
1) 永远不要使用'Object' 来存储数据. 因为这是一种绕开编译器检查的做法,完全不符合面向对象的思想。如果要用Hashmap或者相应的数组,那么可以用Dictionary。否则,请自已定义一个类,而不要使用Object来作为数据的存储器。
2) 不要用Object来作为对象的类型。这是欺骗编译器的方法。如果必须这样用,最好在存取属性或者调用函数时,先进行强制性的类型转换,至少这是给编译器的一个提示,也帮助其他人理解你的代码。
3) 也不要用*类型。
4) 不要将ActionScript的类声明为动态。
5) 注意Application.application (和其他的无类型的框架属性). 这个属性有一些古怪. 他应该是Applicaton类型的,因为他指向的实例必须是Application的子类. 但实际上他的类型是'Object', 无法应用编译时的检查。如果你一定要用,最好也进行类型的强制性转换。:
MyApplication( Application.application ).functionCall();
6) 封装你的Xml。使用Xml与Server进行数据的交互非常方便,但是尽量避免使用Xml作为核心数据模型。从服务器收到数据后,尽量把Xml转换为强类型的对象模型。在Flex应用内部使用Xml是绕开编译器的作法。
7) 不要使用DynamicEvent.
8) 不要使用mx:Model。如上所述,请尽量使用自定义的强类型的类.
9) 不要使用Cairngorm中的data属性,而应该继承扩展CairngormEvent来传递数据。
10) 不要把编译器的严格模式关掉
The major change to this release is how Cairngorm is being packaged. Cairngorm 2.1 introduced a dependency on fds.swc, which isn't part of the standard Flex SDK - it is part of FDS. So, we've repackaged Cairngorm into the core Cairngorm and Cairngorm Enterprise - this also starts aligning Cairngorm with Steven Webster's presentation at Max when he spoke about the Cairngorm Roadmap. We're also now going to to distribute Cairngorm in Binary form (SWC), as a source zip and a documentation zip.
* Removed dependency on Flex Data Services (fds.swc) - externalised to Cairngorm Enterprise * Flex SDK SWCs are no longer linked into Cairngorm.swc (produces a smaller cairngorm.swc) * Added support for setting remote credentials * Fixed bug with Web services not loading the WSDL (no need to call loadWSDL() explicitly) * ModelLocator interface has been deprecated. Added com.adobe.cairngorm.model.IModelLocator * Added deprecation metadata for compiler support
对于我来说,关注的只有一点:ModelLocator被改成IModelLocator了。 其他的只是Cairngorm的打包方式而已。 Cairngorm2.1中依赖于fds.swc,而fds.swc不是Flex SDK中的东西,是FDS的一部分。 在2.2中会将Cairngorm拆成两部分:Core Cairngorm和Cairngorm Enterprise 和FDS.swc相关的部分放入Cairngorm Enterprise中。
首先在这个版本中,修正了数百个Bug
1. 添加对Mac的支持 2. 支持Eclipse 3.2,而不是仅仅支持3.1 3. 运行时的CSS支持 4. 增加了一个Mercury的插件,支持自动测试 5. 其他一些很棒的功能:包括FlashType的字体和ASDoc的支持。
1. 要把database.properties和mail.properties拷贝到build/classes 2. 要在数据库中建立test用户 3. ant db-create建立数据库 4. ant db-prepare建立数据表 5. ant db-load 将数据插入到数据表中 6. 修改myapp\build\classes\META-INF目录中的applicationContext-hibernate.xml 添加 <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location"><value>database.properties</value></property> </bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"> <value>${hibernate.connection.driver_class}</value> </property> <property name="url"> <value>${hibernate.connection.url}</value> </property> <property name="username"> <value>${hibernate.connection.username}</value> </property> <property name="password"> <value>${hibernate.connection.password}</value> </property> </bean> 7. 修改myapp\build\class\META-INF目录下的applicationContext-service.xml 将其中的mail.host, mail.username, mail.password, mail.default.from 三个变量修改成合适的值 8. ant deploy 9. 启动tomcat, http://localhost:8080/myapp 10. 输入mraible,密码tomcat,进入系统
这个函数只是一个示例函数,演示如何遍历一个Tree。 此函数严格说起来其实是两个函数:上半部分用于回溯父节点,下半部分递归遍历子节点
/** * This method will traverse a Tree's model independent of it's * type. * * <p>Note :: This method may look long and arduous but, rest assured * it has all the checks to perform like a champ. Also, you 'could' * refactor part of this method but, for the sake of explanation, I * kept it all in one place.</p> * * <p>Remember, I had coupled the model to this method by tracing * @label, obviously you do not need to do this. The intention of * this example is to show you that the dataDescriptor seperates * the models type and is awesome. It enables you to create a tight * method like this without type checks on the model.</p> * * @param tree The Tree instance that will be examined by the method. * @param item An item found in the dataProvider of the Tree passed in. * @param startAtParent A boolean that determines if the method upon * initialization will back up one leve3l to the item passed in and * start it's recursion at the item's parent node. */
public
function walkTree (tree:Tree, item: Object, startAtParent: Boolean = false): void{ // get the Tree's data descriptor var descriptor:ITreeDataDescriptor = tree. dataDescriptor; var cursor:IViewCursor; var parentItem: Object; var childItem: Object; var childItems: Object; // if the item is null, stop if(item == null) return; // do we back up one level to the item's parent if(startAtParent ) { // get the parent parentItem = tree. getParentItem(item ); // is the parent real if(parentItem ) { trace("|-- Parent Node ", parentItem [tree. labelField]); // if the parent is a branch if(descriptor. isBranch(parentItem )) { // if the branch has children to run through if(descriptor. hasChildren(parentItem )) { // get the children of the branch // this part of the algorithm contains the item // passed childItems = descriptor. getChildren(parentItem ); } } // if the branch has valid child items if(childItems ) { // create our back step cursor cursor = childItems. createCursor(); // loop through the items parent's children (item) while(!cursor. afterLast) { // get the current child item childItem = cursor. current;
var label:String = childItem[tree.labelField]; var branch:Boolean = descriptor.isBranch(childItem); // good place for a custom method() trace("Sibling Nodes :: ", label, "Is Branch :: ", branch); // if the child item is a branch if(descriptor.isBranch(childItem)) // traverse the childs branch all the way down // before returning walkTree(tree, childItem); // do it again! cursor.moveNext(); } } } } else// we don't want the parent OR this is the second iteration { // if we are a branch if(descriptor.isBranch(item)) { // if the branch has children to run through if(descriptor.hasChildren(item)) { // get the children of the branch childItems = descriptor.getChildren(item); } // if the child items exist if(childItems) { // create our cursor pointer cursor = childItems.createCursor(); // loop through all of the children // if one of these children are a branch we will recurse while(!cursor.afterLast) { // get the current child item childItem = cursor.current; var label:String = childItem[tree.labelField]; var branch:Boolean = descriptor.isBranch(childItem); // good place for a custom method() trace("-- Sub Node :: ", label, "Is Branch :: ", branch); // if the child item is a branch if(descriptor.isBranch(childItem)) // traverse the childs branch all the way down // before returning walkTree(tree, childItem); // check the next child cursor.moveNext(); } } } } }
在Flex的开发过程中,尤其是在使用Cairngorm的时候,总会遇到需要在Model的属性值改变后,需要调用一个指定的函数。传统的解决方法是使用一个Setter方法,在这个方法中首先对属性赋值,然后调用指定的函数,比如:
--------------------------- [ChangeEvent("deleteEnabledChange")] public function get deleteEnabled() : Boolean { return _deleteButtonVisible; }
public function set deleteEnabled(value : Boolean) : Void { _deleteButtonVisible = value; SimpleButton(deleteButton)._visible = false; dispatchEvent(new Event("deleteEnabledChange", this)); } --------------
但是现在我们有一个更简洁的解决之道 首先添加这样一个类 package com.adobe.ac.util { public class Observe { public var handler : Function; public function set source( source : * ) : void { handler.call(); } } } 然后就可以使用这个作为标签了。 <util:Observe source="{ model.myProperty }" handler="{ this.myFunction }"/> 使用了这样的一个标签后,今后嘦是对model.myProperty进行赋值,就会调用myFunction 这个号称世上最小的标签是不是很好用啊?
1、www.FlashDevelop.com网站最近发布了FlashDevelop2.0.2,其中增加了对Flex2的支持。 2、下面介绍一下使用的方法。我用的是一个笔记本,只有一个C盘,所有安装路径都在C盘。 3、首先是下载FlashDevelop2.0.2(以下简称FD),我安装在c:\program files\flashdevelop目录下。 还要注意下载安装JDK5,和.net framework1.1(这个程序是用C#开发的) 4、再下载Flex2 SDK,从www.flex.org的左上角可以找到,好象要在Adobe公司里注册一下。 下载完毕后,解压缩,我是放在c:\flex_2_sdk目录下。 运行FD,按F9进入程序设置,找到 ASCompletion.Flex2SDK.Path这个选项,把它的值设为SDK的路径。 我这里就是C:\Flex_2_SDK 5、下载AS3 Intrinsic Classes URL Address我把它解压缩在C:\Program Files\FlashDevelop\Library目录下 6、下载AS3 top-level declaration解压缩在C:\Program Files\FlashDevelop 在FD中的程序设置(Program Setting)中设置ASCompletion.Macromedia.Classpath的值为此路径, 设置ASCompletion.MTASC.UseStdClasses的值为False 7、下载the MXML definition file这解压缩在C:\Program Files\FlashDevelop\Data目录下
到此步骤,FD的安装基本告一段落。
下面就可以新建项目了,为方便起见 8、下载项目模板 http://www.bit-101.com/flashdevelop/ProjectTemplates.zip,解压缩到C:\Program Files\FlashDevelop\Data\ProjectTemplates目录下。 9、重新启动FD,新建项目,选择Flex模板。在右边会看到deploy、html_template, src三个目录和build.properties, build.xml两个文件,src目录下有一个app.mxml的空文件。 至此,项目的目录框架搭建完成。下面就是要编译生成SWF文件了。
10、对于编译生成SWF文件有好几种方式,在此介绍两种,一种是FD开发者提供了,使用MSASC,一种是使用Ant。第一种速度奇块,但是设置比较复杂,第二种速度较慢,比较方便。 11、如果使用第一种,就需要在每个文件的头部添加东西 AS文件头添加
/** * @mxmlc -default-size 400 300 -incremental=true */ mxml文件头添加 <!-- @mxmlc -default-size 400 300 -incremental=true --> 12、如果是用第二种方法,首先要装Ant,这个大家可能都有。 13、然后修改Build.properties文件,把里面的一些路径改一下。比如:flex2.dir 14、然后到C:\Program Files\FlashDevelop\Settings目录下找到Toolbar.xml 添加一行 <button label="ANT Build" click="PluginCommand" image="54" tag="Run;SaveAll;ant" shortcut="F7" /> 15、重新启动FD,在工具条上看见多了一个图标,直接点这个按钮,或者按F7,就可以调用Ant生成SWF文件了。而且Swf文件的显示就是在FD集成环境里面的。第一种方式是在外部启动一个IE显示Swf的。
现在最新的版本是2.02, 主页: http://www.ariaware.com 结构框架图:
看得出: 这个框架与Cairngorm非常的相似, 但是进行了相当的简化. Controller + Command + ServiceLocator 这三驾马车还在. Cairngrom中的Model, EventDispatcher, Delegate都不存在了. ViewHelper和ViewLocator被ArpForm所取代. 对于初学者来说,这个简化版的可能更容易被接受吧.
在NetBeans中启动Duke Bank应用。 点击 Transfer Funds、ATM等页面都能正常显示。 只有accountList页面,会报错,提示说: Servlet.service() for servlet jsp threw exception
org.apache.jasper.JasperException: <h3>Validation error
messages from TagLibraryValidator for c in /accountList.jsp</h3>null: org.xml.sax.SAXParseException: An invalid XML character (Unicode: 0x1b) was found in the CDATA section. <h3>Validation error messages from TagLibraryValidator for fmt in /accountList.jsp</h3>null: org.xml.sax.SAXParseException: An invalid XML character (Unicode: 0x1b) was found in the CDATA section. 解决方法也很简单,去掉accountList.jsp页面中的 \$ 这两个字符就可以了。
在这里记录一下我的经历: 1. 安装Netbeans5.5 2. 下载JBoss4.0.4, 解压后放在C:\jboss目录下 3. 下载J2EE1.4的教程 下载4. 我只是把其中的Bank目录解压出来 5. 下载JBoss的Start Guide 地址: http://labs.jboss.com/portal/jbossas/docs 6. 将其中的Bank目录中的内容提制到刚才的Bank目录中. 7. 将Bank倒入到NetBeans中, 其中步骤与 http://gceclub.sun.com.cn/NetBeans/tutorials/javaee/jboss-getting-started.html 一文中一样. 8. 修改JBoss-build.xml, 此文件第2行引用了一个Jboss.properties文件, 里面定义了JBoss的三个路径设置,由于我只解压了Bank目录, 因此未包含此文件. 修改办法是: 删除<property file="jboss-build.properties"/>这一行, 添加下面三行: <property name="jboss.home" value="c:/jboss"/> <property name="jboss.server" value="${jboss.home}/server/default"/> <property name="jboss.deploy" value="${jboss.server}/deply"/> 9. 修改hsqldb-ds.xml Jboss4.0.4中这部分的配置与前版本又有所不同. 首先修改Connection-url 改为: <connection-url>jdbc:hsqldb:hsql://localhost:1701</connection-url> 另外需要把 <mbean code="org.jboss.jdbc.HypersonicDatabase" name="jboss:service=Hypersonic"> <attribute name="Port">1701</attribute> <attribute name="BindAddress">${jboss.bind.address}</attribute> <attribute name="Silent">true</attribute> <attribute name="Database">default</attribute> <attribute name="Trace">false</attribute> <attribute name="No_system_exit">true</attribute> </mbean> 这个mbean给放出来(原来是注释掉的) 因为我们要通过外部工具来连接数据库,所以需要这样修改数据库的配置. 否则因为安全原因, 数据库是不允许外部连接的. 10. 修改完毕后, 启动JBoss 11. 在NetBeans中, 通过Ant运行数据库脚本, 编译,打包,部署,启动例程应用. PS: 好象NetBeans对JBoss4.0.5的支持不是很好.在NetBeans下启动Jboss4.0.5会导致NetBeans程序的退出. 我后来换用了NetBeans5.5与Jboss4.0.4的捆绑安装包才能正常使用的.
RichtechMedia.com最近提供了RiaWave:一个轻量级的快速开发框架 尽管我是一个Cairngorm的拥护者,但我也不得不承认:Cairngorm有些太复杂了. 尤其是对于中小型的项目来说. RiaWave并不是要取代Cairngrom, 但是对于新学者来说是非常适合的. 链接地址: http://ria.richtechmedia.com/flexsample/sample.zip
http://flexsearch.org
搜索内容包括:Flex的论坛、博客、网站等等,非常棒!
FlashDevelop是一个可自由定制的轻量级的脚本编辑器. 程序主要是用于ActionScript2.0的开发, 但同时也可以很方便地用于其他的语言. FlashDevelop需要.Net1.1 Framework的支持.
主要功能
项目管理, 与SWFMILL和MTASC无缝集成(使用单独的命令和工具) 智能的ActionScript自动完成和浏览, 能够自动搜索类路径 MTASC代码检查, 生成可点击的出错结果 集成SWF播放器和Socket日志
其他功能
自动清理修改的类的ASO文件 源文件资源管理 自动生成JavaDoc 使用F1查找智能的ActionScript的帮助 使用F4跳转到类/成员的声明位置 Flash8的测试 XML, HTML/PHP, JS, CSS代码的高亮显示 支持多种字符编码方式 XML文件编程菜单的可定制 有图形化界面进行AS2Api的文档生成 Snippets 书签 代码折叠 缩放 有类似于C#的Region代码折叠功能
必须停止在具体技术方面的思考,而开始在模式和方法方面的思考,这才是处理我们正在解决问题的最好办法。
第一步. 使用正统的Localizer方法 在项目中添加一个类ChnReportLocalizer,继承ReportLocalizer,将ReportLocalizer类的所有代码拷贝过来。原来的代码返回的都是英文,我们需要全部翻译成中文 比如原来是 case ReportStringId.Msg_FileNotFound: return "File not found."; 我们修改成: case ReportStringId.Msg_FileNotFound: return "文件没有找到"; 全部翻译完毕后,赋值 DevExpress.XtraReports.Localization.ReportLocalizer.Active = new ChnReportLocalizer(); 再次运行XtraReports的Designer,发现只有第一个Pointer被正确的翻译了,下面的CheckBox、RichText等等都还是英文。查找原因发现工具箱的控件是动态生成的,在XtraReports.UserDesigner.Native.AsmHelper中的函数GetToolboxItems负责往工具箱内添加控件。控件显示的名称就是ToolboxItem的DisplayName。
OK,搞清楚之后开始动手修改, 1. 在XtraReports.Localization.ReportStringId这个枚举中添加控件对应的枚举,比如:Subreport对应添加枚举值为UD_SubreportCaption,CheckBox添加的枚举值为UD_CheckBoxCaption,控件名与枚举值的名称需要有对应关系,因为需要使用Enum.Parse根据控件的名称映射枚举值。 2. 在ChnReportLocalizer中添加对应的翻译,case ReportStringID.UD_SubreportCaption : return "子报表"; 等等。 3. 在AsmHelper中修改原代码,新生成的ToolboxItem不再直接添加到ArrayList中,先进行DisplayName的本地化。先根据DisplayName使用Enum.Parse获得枚举值,然后调用ReportLocalizer.GetString获得本地化的字符串赋值给ToolboxItem.DisplayName。
1. 有一条清楚的消息表明已经发生了一个错误,不能简单地Try...Catch一个异常,而不加以处理。 2. 有一个唯一的错误号,他可以据此访问可方便获得的客户支持系统 3. 问题快速得到解决,并且可以确信他的请求已经得到处理,或者将在设定的时间段内得到处理
几条建议: ☆ 如果无法处理某个异常,那就不要捕获它。 ☆ 如果捕获了一个异常,请不要胡乱处理它。 ☆ 尽量在靠近异常被抛出的地方捕获异常。 ☆ 在捕获异常的地方将它记录到日志中,除非您打算将它重新抛出。 ☆ 按照您的异常处理必须多精细来构造您的方法。 ☆ 需要用几种类型的异常就用几种,尤其是对于应用程序异常。 ☆ 把低层次的异常封装成层次较高程序员较容易理解的异常。 ☆ 尽量输出造成异常的完整数据 ☆ 尽量捕获具有特定含义的异常:比如SqlException,而不是简单地捕获一个Exception。
1. 使用添加新功能更容易 2. 提升原有代码的设计 3. 使用代码容易理解 4. 使得原来的代码不再那么令人“生厌”。 一句经典的话: Keeping code clean is a lot like keeping a room clean. Once
your room becomes a mess, it becomes harder to clean. The worse the mess
becomes, the less you want to clean it. If I can keep my room clean for several weeks, continuous hygiene starts to
become a habit. Unfortunately, new habits often run the risk of being compromised by old habits. To keep code clean, we must continuously remove duplication and
simplify and clarify code. We must not tolerate messes in code, and we must not
backslide into bad habits. Clean code leads to better design, which leads to
faster development, which leads to happy customers and programmers. Keep your
code clean. 最后一句Martin Fowler的话: Any fool can write code that a computer can understand. Good programmers write
code that humans can understand。
官方网址:http://www.badboy.com.au/ Badboy is a powerful tool designed to aid in testing
and development of complex dynamic applications. Badboy makes web testing
and development easier with dozens of features including a simple yet
comprehensive capture/replay interface, powerful load testing support,
detailed reports, graphs and much more!
Badboy是一个强大的工具,被设计用于测试和开发复杂的动态应用。Badboy功能丰富(包括一个捕获/重播接口,强大的压力测试支持,详细的报告、图形)使得测试和开发更加容易。
Badboy对于个人使用者是免费的。
Fiddler是一个微软开发的Http代理,可以用于跟踪调试客户端与服务器之间的Http信息,Fiddler监控所有的Http往来,可以设置断点,窜改出入数据。
在ActionScript3.0中是不支持函数重载的. 最近, 想了个办法来模拟. public function add(obj1 : Object = null, obj2: Object = null) : void { if (obj1 == null && obj2 == null ) { // 调用无参数Add函数 AddNoArgument(); } else if ( typeof(obj1).toString().toLowerCase() == "string") && obj2 == null ) { // 调用String参数的Add函数 AddString(obj1.toString()); } } 当然这个方法也会有一些问题. 第二种方法 public function add( args) : void { if (args.length == 0) { // 调用无参数Add函数 AddNoArgument(); } else if ( args.length == 1 && typeof(args[0]) == "int") ) { // 调用String参数的Add函数 AddString(args[0].toString()); } } 其中addString是add函数的String类型的重载函数. 如果大家有什么更好的解决方案, 请不吝赐教.
XtraReports的设计器可以作为给终端用户使用的独立的报表设计器, 也就是说用户可以自己编辑,预览,甚至创建报表. 这个设计器和Visual Studio的集成开发环境很象, 可以定制外观, 满足用户的需要. 甚至, 你可以自己创建报表设计器, 因为在XtraReport中设计器是一个分开独立的控件. 先介绍一下设计器的组成元件. 报表设计器主要是用于使用户可以编辑报表. 标准的设计器是使用XRDesignFormEx类来展示的, 这个类提供了编辑和创建报表的主要功能. 下图是一个标准的报表设计器: 这个Form里包含了不同的元件: 报表在XRDesignPanel对象中被编辑, 其他工具栏和停靠面板由XRDesignBarManager和XRDesignDockManager控制. 所有的这些控件都可以通过设计Form的XRDesignFormExBase.DesignPanel, XRDesignFormEx.DesignBarManager和XRDesignFormEx.DesignDockManager属性来操作. 下面列举了设计器中使用的元件接口. Element | Description |
---|
Design Panel | 这是报表设计器的主要元件. 它展示了一个面板用于创建一个新的报表或者编辑原有的报表. 它显示报表带并控制报表带中控件的归属, 并可以移动, 缩放, 复制, 粘贴这些控件, 另外还可以在控件上绑定数据, 或者进行其他的操作. 而且可以预览报表效果.
| Main Menu | 展示报表设计器的主菜单. 包含了一系列的项目和子项目, 用于为用户提供相关操作. 通常, 它复制所有的ToolBar按钮, 格式化按钮以及布局按钮.
| Main Toolbar | 展示报表设计器的ToolBar. 包含创建报表, 保存装载报表布局, 剪裁,复制,粘贴等按钮, 以及撤销, 重做等操作.
| Formatting Toolbar | 展示报表设计器的格式化Toolbar. 包含一些缺省的操作文本的按钮.
| Layout Toolbar | 展示报表设计器的布局Toolbar. 包含缩放, 移动一组控件位置的按钮.
| Status Bar
| 在报表设计器底部展示StatusBar. 通常用于显示报表编辑操作过程中的特殊信息.
| Toolbox | 展示报表设计器的控件工具箱. 包含所有报表控件, 可以拖放新的控件到报表区中. 可以添加自定义控件到工具箱中.
| Report Explorer | 展示报表设计器的Explorer. 它显示了报表树状结构, 使用它可以很方便地浏览整个报表.
| Field List | 展示了报表设计器的树状字段列表. 显示了数据源的树状结构. 用于将数据绑定到报表.
| Property Grid | 展示报表设计器的属性容器. 可以修改报表元件的属性.
|
激活报表设计器: 尽管报表设计器是XRDesignFromEx的实例, 但一般不需要手工创建此对象. 通常使用以下代码: // Create a new report. XtraReport1 report = new XtraReport1();
// Invoke the End-User Designer and load the report into it. report.ShowDesigner();
// Invoke the End-User Designer and load the report into it, modally. report.ShowDesignerDialog();
在此介绍创建报表过程中使用到的XtraReports的控件. 所有的控件都可以在设计时,运行时,或者通过设计器添加到报表中, 本文介绍的是一些最基本的信息.
报表控件概览
XtraReports中的所有报表对象都通过在Bands中的控件来表现. 在设计时, 报表控件可以从工具箱添加到报表中, 只有点击工具箱中需要的控件, 然后放到报表带中即可. 另一种方式是使用字段列举窗口添加控件, 这种情况下, 控件将和数据字段绑定.
如果要在运行时添加报表控件, 你只需要简单地创建一个相应类型的变量, 并通过XRControlCollection.Add方法添加到报表带中即可
所有的报表控件包括bands都是继承自XRControl. 其中部分控件可以还作为容器, 这种情况下控件集合可以通过XRControl.Controls来获得. 如果要获取控件的容器, 可使用XRControl.Parent属性.
下面列举了XtraReports中的标准报表控件.
Class
|
Description
|
Subreport
|
用于在报表中添加子报表. 被添加的报表需要被包含在项目中, 并设置SubReport的ReportSource属性. 子报表可用于合并报表和创建主从表.
|
WinControlContainer
|
用于添加Windows Forms或者第三方的控件. 此对实际上是添加的控件的封装, 并提供对Drawing方法的支持
|
XRBarCode
|
用于在报表中显示条形码. 使用XRBarCode.Symbology和XRBarCode.SymbologyId来设置条形码的类型
|
XRChart
|
在报表中显示图形, 使用此控件来根据一个或多个SeriesViewTypes来展示不同的数据.
|
XRCheckBox
|
用于在报表中展示复选框.
|
XRLabel
|
标签. 可以是单行的, 也可以是多行的.
|
XRLine
|
线条. 使用这个控件来画线.
|
XRPageBreak
|
换页. 如果是Band中需要换页, 那么使用控件的Band.PageBreak属性比较好.
|
XRPageInfo
|
显示报表页面的附加信息. 可以显示页码, 日期或者其他信息.
|
XRPanel
|
用于包含其他控件的控件. 可以使用此控件来进行控件的分组.
|
XRPictureBox
|
在报表中显示一个图片
|
XRRichText
|
显示一个控件, 用于显示,输入,修改格式化的文本. 可以用XRRichText.LoadFile从文件中加载文本.
|
XRTable
|
表格 |
XRTableCell
|
表格单元. 与XRControl.Text绑定
|
XRTableRow
|
表格中的行. 通过不需要用到此控件, 直接操作表格单元更普遍.
|
XRZipCode
|
邮政编码 |
一个普通的XtraReport报表一般包含数个包含报表控件的带. 在这里介绍一个XtraReports中的几种不同类型的带, 他们都有一些什么用途, 又是如何添加,编辑,删除.
Report Bands
在把报表添加到应用之后, 缺省的样子如下图所示. 可以看到, 报表被初始化分为3个基本的带(页头, 明细, 和页脚), 这些带中可以添加不同的报表控件.
每一种带指明了控件在报表中是怎样定位和被打印的, 即确定了打印的顺序和次数. 注意, 有些<band strips>包含了带是如何被打印的信息, 比如: PageHeader和PageFooter就说明了是每一页都有这一带的.
在创建报表时, 可以添加或者删除任意的带. 在XtraReports中有很多不同类型的带, 每一个都是<Band>的子类. 列举如下:
下图显示了不同类型的带的相对位置:
TopMarginBand and BottomMarginBand
ReportHeaderBand and ReportFooterBand
PageHeaderBand and PageFooterBand
GroupHeaderBand and GroupFooterBand
DetailBand
报表的预览显示的不是带本身, 而是<PrintingSystem>的输出. PageHeaderBand, PageFooterBand, TopMarginBand和BottomMarginBand在报表预览的每一页都有输出. ReportHeaderBand和ReportFooterBand类只显示一次. GroupHeaderBand和GroupFooterBand出现在每个记录组中.
在XtraReport中, 每一个报表都是XtraReport或者其子类. 打个比方说, XtraReport就好象Windows Forms. 同样的道理, 所有的form都Form类的子类. XtraReport中的报表类可以与数据绑定也可以不绑定. 如果要创建一个绑定数据的报表, 需要查看<数据绑定>和<绑定数据控件>这两个主题的帮助. 在创建一个报表时, 可以从已有的报表中加载样式和布局, 样式中包含了报表控件外观的属性值, 而布局包含了报表的结构信息. 另外, 还可以从其他报表系统中导入报表, 比如: Access, 水晶报表等等, 如果要详细了解XtraReport的导入功能, 请参阅<Importing Overview>主题. 报表类(XtraReport的子类)创建后, 就可以生成其实例. 需要注意的是, XtraReport对象可以在Windows Forms中使用也可以在Asp.net中使用. 在Windows应用中使用报表, 通常需要维护报表的<Printing System>, 这个对象提供了报表的输出功能. 创建报表有两种方式, 一种是简单地添加一个"模板"报表, 一种是通过报表向导来创建报表. 在报表添加到项目后, 报表设计器提供了大量的设计时元素来加快简化报表的创建. XtraReport工具箱包含了所有的控件, Report Navigator可以浏览整个报表, Feild List可以拖放数据字段来创建与数据绑定的报表控件. XtraReport的所有报表都是由<Report Band>和<Report Control>组成的. public class XtraReport1 : DevExpress.XtraReports.UI.XtraReport { private DevExpress.XtraReports.UI.DetailBand Detail; private DevExpress.XtraReports.UI.PageHeaderBand PageHeader; private DevExpress.XtraReports.UI.PageFooterBand PageFooter; private DevExpress.XtraReports.UI.XRLabel xrLabel1; private DevExpress.XtraReports.UI.XRLabel xrLabel2;
private System.ComponentModel.Container components = null;
public XtraReport1() { InitializeComponent(); }
protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } // . 然后开始创建报表的结构, 首先在XtraReportBase.Bands属性中添加Bands, 然后在相应的Bands的XRControl.Controls属性中添加控件. 报表带和控件的添加方法一般是这样的 // Add Detail, PageHeader and PageFooter bands to the report's collection of bands. this.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] {this.Detail, this.PageHeader, this.PageFooter});
// Add two XRLabel controls to the Detail band. this.Detail.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] {this.xrLabel1, this.xrLabel2}); 最后创建好的报表可以输出给用户看了 // Create a report. XtraReport1 report = new XtraReport1();
// Create the report's document so it can then be previewed, printed or exported. // NOTE: Usually you don't need to call this method as it's automatically called by all of the following methods. // See the corresponding member topic to find out when it needs to be called. report.CreateDocument();
// Show the form with the report's print preview. report.ShowPreview();
// Print the report in a dialog and "silent" mode. report.PrintDialog(); report.Print();
// Open the report in the End-User designer report.RunDesigner();
// Export the report. report.CreateHtmlDocument("report.html"); report.CreatePdfDocument("report.pdf"); report.CreateImage("report.jpg", System.Drawing.Imaging.ImageFormat.Gif); 附: XtraReport的类结构层次图:
1. 我从未创建过一个不使用Cairngorm的RIA应用, 甚至是连一个Mini项目我也使用了Cairngorm. 使用Cairngorm放大或者说是加剧了学习曲线, 大多数第一次使用Flex的开发者是从创建小的应用开始逐步了解Flex的思想和概念. 所以最好先从使用容器和组件,创建动态界面,使用WebService/HTTP Service,绑定其返回结果这样的步骤开始. 然后尝试更新后台的持久数据,使用ColdFunsion/Java/C#/PHP等等.这是Flex开发者学习道路上的一个里程碑. 接下来,可以去理解风格/状态/效果这样的Flex功能, 在Mxml中写一些ActionScript. 以上的这些步骤都不涉及到Cairngorm,直到你能熟练地使用Flex后, 可以去阅读Cairngorm的系列文章(http://www.adobe.com/devnet/flex/articles/cairngorm_pt1.html) 2. 你的应用与用户的交互非常少,只有一两个 这主要是用于衡量应用的复杂性,如果你的应用中的用户用例(Use-Case)比较多,那么Cairngorm对你将会非常有价值. 相反,如果用例比较少,那么你完全可以不使用Cairngorm来完成应用. 最好的解决方案不是无物可加,而是无物可舍! the best solution is not when there is nothing left to add, but when there is nothing left to take away! 3. 你是应用的唯一开发者 Cairngorm的优点是可控性/可维护性/伸缩性, 在别人的工作基础添加功能变得非常非常容易. 而如果只有你一个开发者,那么不使用Cairngorm将会一种简洁的做法. 4. 你发现了Cairngorm的Bug 如果你发现了Bug,那么就不适合在重要的项目中使用Cairngorm. Cairngorm并不是完全没有Bug,在早期的Beta版中,和其他软件产品一样,我们忽略了一些偶尔出现的Bug.
Cairngorm的组成: 1. Model Locator:保存应用的ValueObject(数据)和共享变量,与HttpSession类似,只不过是保存在客户端而不是在服务器端而已 2. View:一个或者多个Flex组件(按钮、Panel、下拉框等等)组合在成一个被命名的单元。绑定Model Locator中的数据,根据用户动作(点击、滚动、拖放)产生自定义的Cairngorm事件。 3. Front Controller:接收Cairngorm事件,并映射到对应的Cairngorm命令。 4. Command:处理业务逻辑,调用Cairngorm的代理或者其他命令,更新Model Locator中的Value Object和变量值。 5. Delegate:在命令中创建,初始化一个远程调用(Http, WebService等等),并将返回结果传递给Command。 6. Service:定义远程调用连接远程的数据存储。 Cairngorm的工作方式: 1. 客户端界面由各个View组成,View通过绑定Model Locator的成员来显示数据。View根据用户操作生成事件。这些事件由Front Controller广播并接收,然后映射到相应的命令。命令包含业务逻辑、创建代理来完成操作,处理代理返回的结果,并更新Model Locator的数据。因为View是与Model Locator中的数据绑定的,因此Model Locator中数据更新后,View自动反应出数据的变化。由代理调用服务并返回处理结果不是必须的,但是这是推荐做法。
在Flex2中VideoDisplay取代了1.5中的MediaPlay,提供播放Flv视频的功能. 而且对于此组件的控制都需要自己编写. 最近遇到一个问题,就是想通过设置PlayheadTime来实现视频播放时间的跳转时,发现有时会出现失灵的情况. 经研究后发现主要是因为组件加载视频不完整的原因. 经过测试,如果是在本地的开发环境中,视频的加载非常快,100M的Flv几乎是瞬间加载完毕. 但是如果将编译生成的SWF文件上传到服务器,然后加载Flv文件,速度非常慢,主要是需要通过互联网传送. 所以会出现在本地开发时,视频的跳转一切正常.但是在服务器上却屡屡出错.
解决方法: 可以在视频加载过程Progress事件中添加代码,强制在全部加载完成后启动播放.
if ( videoPlayer.bytesLoaded / videoPlayer.bytesTotal > 0.99 ) videoPlayer.play();
开源项目fluorine(Flex与.net的AMF0网关、WebORB的替代者)使用注意事项 网站链接:http://fluorine.thesilentgroup.com/fluorine/index.html
Fluorine是与AmfPHP、OpenAmf类似的但是用于.net平台的开源AMF网关。可以应用在Flex2与.net(1.1与2.0)中。 通过使用Fluorine可以在Flex中直接调用.net程序中类的相应方法,并且完成了Class Mapping。也就是说可以传递一个复杂对象作为参数。 通过我的试验发现,.net返回的DataTable可以被正确地解析为AS3中的Array
下面是我在使用的发现的一些注意事项: Flex端: 1. 在VO的构造函数中需要调用flash.net.registerClassAlias("com.ariaware.pizza.vo.OrderVO", OrderVO); 其中第一个参数是VO在.net端的Assemble全称,即NameSpace+类名,第二个参数是ActionScript中的VO类
2. GateWayURL需要书写正确,一般的形式是:var gatewayURL:String = "http://localhost/FluorinePizzaService/Gateway.aspx";
3. 在指定.net端类名和方法名的时候,注意大小写一致 var pizzaService:AMF0Service = new AMF0Service ( gatewayURL, "pizzaService", null );
.net端 1. 注意VO中的字段名称与Flex端VO类中的名称一定要完全对应,否则就取不到对应的值。
2. 需要在Web.config中添加下面几行,紧跟在<system.web>后面就行了 <httpModules> <add name="FluorineGateway" type="com.TheSilentGroup.Fluorine.FluorineGateway, com.TheSilentGroup.Fluorine" /> </httpModules> 3. 项目中需要引用com.TheSilentGroup.Fluorine这个Dll
4. 需要在项目中添加Gateway.aspx这样一个空的Web窗体
使用时: 1. 需要注意清除一下本地的Cookie,如果有一个名为.ASPXAUTH的Cookie如果不为空,会造成FormsAuthentication.Decrypt的错误。
一、在JavaScript中调用Flex方法 在Flex中可以用ExternalInterface来调用Flex的方法,途径是通过在Flex应用可调用方法列表中添加指定的公用方法。在Flex应用中通过调用addCallback()可以把一个方法添加到此列表中。addCallback将一个ActionScript的方法注册为一个JavaScript和VBScript可以调用的方法。 addCallback()函数的定义如下: addCallback(function_name:String, closure:Function):void function_name参数就是在Html页面中脚本调用的方法名。closure参数是要调用的本地方法,这个参数可以是一个方法也可以是对象实例。
举个例子: <mx:Script> import flash.external.*; public function myFunc():Number { return 42; } public function initApp():void { ExternalInterface.addCallback("myFlexFunction",myFunc); } </mx:Script> 那么在Html页面中,先获得SWF对象的引用,也就是用<object .../>声明的Swf的Id属性,比如说是MyFlexApp。然后就可以用以下方式调用Flex中的方法。 <SCRIPT language='JavaScript' charset='utf-8'> function callApp() { var x = MyFlexApp.myFlexFunction(); alert(x); } </SCRIPT> <button onclick="callApp()">Call App</button>
二、在Flex中调用 JavaScript 你可以调用Html页面中的JavaScript,通过与JavaScript的交互,可以改变Style,调用远程方法。还可以将数据传递给Html页面,处理后再返回给Flex,完成这样的功能主要有两种方法:ExternalInterface()和navigateToUrl()。 在Flex中调用JavaScript最简单的方法是使用ExternalInterface(),可以使用此API调用任意JavaScript,传递参数,获得返回值,如果调用失败,Flex抛出一个异常。 ExternalInterface封装了对浏览器支持的检查,可以用available属性来查看。 ExternalInterface的使用非常简单,语法如下: flash.external.ExternalInterface.call(function_name: String[, arg1, ...]):Object; 参数function_name是要调用的JavaScript的函数名,后面的参数是JavaScript需要的参数。 举个例子说明如何调用JavaScript函数 Flex应用中,添加如下方法: <mx:Script> <?xml version="1.0" encoding="iso-8859-1"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> import flash.external.*; public function callWrapper():void { var f:String = "changeDocumentTitle"; var m:String = ExternalInterface.call(f,"New Title"); trace(m); } </mx:Script> <mx:Button label="Change Document Title" click="callWrapper()"/> </mx:Application> Html页面中有如下函数定义: <SCRIPT LANGUAGE="JavaScript"> function changeDocumentTitle(a) { window.document.title=a; return "successful"; } </SCRIPT>
一。安装amfphp, 1. 下载ampfphp1.2版本 2. 建立一个目录amfphp, 将包中的文件解压到此目录中。 3. 目录结构举例如下: c:\amfphp\amf-core c:\amfphp\browser c:\amfphp\services c:\amfphp\gateway.php 在Http Server中(可以是IIS,Apache Http Server)中建立一个虚拟目录,映射c:\amfphp 4. 验证 在浏览器中输入 http://localhost/amfphp/gateway.php 会看到一个成功页面。
二。编写PHP端代码 举个例子:定义一个sample类,这个类编写在sample.php中 其中定义一个getUsers方法 这个php文件放在amfphp\services\中。 <?php // Create new service for PHP Remoting as Class class sample { function sample () { // Define the methodTable for this class in the constructor $this->methodTable = array( "getUsers" => array( "description" => "Return a list of users", "access" => "remote" ) ); }
function getUsers ($pUserName) { $mysql = mysql_connect(localhost, "username", "password"); mysql_select_db( "sample" ); //return a list of all the users $Query = "SELECT * from users"; $Result = mysql_query( $Query ); while ($row = mysql_fetch_object($Result)) { $ArrayOfUsers[] = $row; } return( $ArrayOfUsers ); } } ?> 验证: 在浏览器中输入http://localhost/amfphp/browser/index.html 会发现在左边Frame中有一个sampe的链接,点击后,在右边Frame中可以测试此方法。
三。编写Flex端代码 首先写一个RemotingConnection类,继承自NetConnection,主要是用于统一指定编码格式 package { import flash.net.NetConnection; import flash.net.ObjectEncoding; public class RemotingConnection extends NetConnection { public function RemotingConnection( sURL:String ) { objectEncoding = ObjectEncoding.AMF0; if (sURL) connect( sURL ); } public function AppendToGatewayUrl( s : String ) : void { } } } 然后在应用中可以进行如下调用: <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" creationComplete="initApplication()"> <mx:DataGrid dataProvider="{dataProvider}"> <mx:columns> <mx:DataGridColumn headerText="Userid" dataField="userid"/> <mx:DataGridColumn headerText="User Name" dataField="username"/> <mx:DataGridColumn headerText="User Name" dataField="emailaddress"/> </mx:columns> </mx:DataGrid> <mx:Script> <![CDATA[ import mx.controls.Alert; [Bindable] public var dataProvider:Array; import flash.net.Responder; public var gateway : RemotingConnection; public function initApplication() : void { gateway = new RemotingConnection( "http://localhost/amfphp/gateway.php" ); gateway.call( "sample.getUsers", new Responder(onResult, onFault)); } public function onResult( result : Array ) : void { dataProvider = result; // mx.controls.Alert.show("result: " + result.toString()); } public function onFault( fault : String ) : void { // trace( fault ); mx.controls.Alert.show(fault); } ]]> </mx:Script> </mx:Application>
补充说明:如果调用的PHP函数需要参数,比如:getUsers($user_name) 那么可以在Flex调用端,需要相应的添加此参数: gateway.call( "sample.getUsers", new Responder(onResult, onFault), "<username>");
MacroMedia的Flex2.0 Beta3发布了。 今天想把项目从Beta2迁移到Beta3下,有一个事情需要注意一下:
<mx:Application />不再支持原有的xmlns="*"
也就是说原来的在同一目录下的使用缺省*作为命名空间的Component在Beta3中会出错,错误提示: Cann't resolve ... as a component implementation
举个例子说明一下,比如你有一个MXML Application文件名是:main.mxml, 引用了一个名为UserComponent的组件。 <mx:Application xmlns="*"> <UserComponent id="userComp"/> </mx:Application>
UserComponent.mxml文件与main.mxml放在一起。这样的做法在Beta2中是OK的。 在Beta3中需要修改: <mx:Application xmlns:MyComp="*"> <MyComp:UserComponent id="userComp"/> </mx:Application> 也就是说必须有一个缺省的NameSpace。
另外Tree中change事件 event.target.selectedNode 属性修改为 event.target.selectedItem
不断补充中。。。。
<mx:tree/>中,folderOpenIcon="UIComponent" 需要修改为: folderOpenIcon="mx.core.UIComponent" folderClosedIcon也一样。(2006.5.12)
最近在搞一个把DocBook的Xml文档转换成Html的工作。 遇到了一些问题,把解决的心得总结一下写在这里。
首先介绍一下DocBook的由来,DocBook是一个开源项目,其实就是一些DTD,规定了书写文档的一些标准。 在此标准下,就可以抛弃Word,WPS之类的工具用写字板来写文档了。 文档此Xml形式保存,需要的时候就使用Xslt通过已有的大量的各种各样的Xsl转换成所需要的格式,比如:RTF,PDF,HTML等等。 这种方式带来了比较大的自由和灵活性。很多人对用这种方式写文档非常推崇。 DocBook网站:http://www.docbook.org, http://docbook.sf.net 讲解DocBook Xsl的一个很好网站:http://www.sagehill.net/docbookxsl O'reilly出版一本DocBook的书(免费) http://www.oreilly.com/catalog/docbook/chapter/book/docbook.html DocBook百科全书: http://wiki.docbook.org/topic
遇到的一个主要问题是原来的DocBook就是想把一个Docbook文档中的章节拆分转换生成若干个Html. 查阅文档后得知使用html\chunk.xsl可以实现此功能。 OK,下载Saxon,用Exe4Java生成Windwos平台下的EXE文件后,进行转换。
Good,果然生成了一堆Html,不过为什么Html的文件都是些ch0X.html这样的格式? 再查阅文档,原来这是DocBook的生成规则,第一章生成的Html就取名为ch01.html, Sect1生成s01,加上所在章节的前缀,如果在第2章,则生成文件名为ch02s01.html,以此类推。 如果想要生成自定义的文件名,就需要在DocBook中指定相应的id属性。如: <chapter id="workflow">...</chapter>, 那么这一章就会生成workflow.html.
ok, 给DocBook文档中所有章节加上了id属性,指定了文件名。再生成一下。
Faint,失败了,生成的文件名还是老样子?为什么? 再仔细查阅文档,原来在转换的时候少指定了一个参数 use.id.as.filename, 只有此参数值不为0时,上述特性才生效。而缺省值是0.
OK,添加此参数, 执行如下命令:
saxon docbook.xml docbook-xsl-1.69.1\html\chunk.xsl use.id.as.file=1
如果使用xsltproc, 命令格式如下: xsltproc --stringparam use.id.as.filename 1 ..\docbook-xsl-1.69.1\html\chunk.xsl docbook.xml
Good, 成功了。
最后,推荐一本关于Xslt的好书: Manning - Xslt Quickly, 使用了大量的实例来引导读者一步步,由浅入深逐步学会Xslt
在Flex2.0中, Validator组件的使用方式和1.5中相比, 进行了一些改变, 不再需要定义Model, 可以在Validator属性中直接引用Form成员了. <mx:Form id="loginForm"> <mx:Text text=" {AtsModelLocator.getInstance().loginFailMessage }" width="80%" color="red"/> <mx:FormItem label="Username: " required="true"> <mx:TextInput id="username" /> </mx:FormItem>
<mx:FormItem label="Password: " required="true"> <mx:TextInput id="password" /> </mx:FormItem> </mx:Form>
<mx:ControlBar> <mx:Button id="loginSubmit" label="Login" mouseUp="loginUser()"/> </mx:ControlBar> <mx:StringValidator id="userNameValidator" source="{username}" property="text" tooShortError="This string is shorter than the minimum allowed length of 3. " tooLongError="This string is longer than the maximum allowed length of 20." minLength="4" maxLength="20"/>
<mx:StringValidator id="userPassValidator" source="{password}" property="text" tooShortError="This string is shorter than the minimum allowed length of 6. " tooLongError="This string is longer than the maximum allowed length of 10." minLength="4" maxLength="20"/>
这样就定义好了两个Validator, 可以对用户名和用户密码进行校验. 但是怎么使用这两个Validator呢?
我是这样用的:
<mx:Script> <![CDATA[ import mx.controls.Alert; import mx.events.ValidationResultEvent; import mx.validators.ValidationResult; import com.ats.vo.LoginVO; import com.ats.control.LoginEvent; import mx.validators; public function loginUser() : void { if ( ! modelCheckValid ) { modelCheckValid = true; return; } var loginVO : LoginVO = new LoginVO(); loginVO.username = username.text; loginVO.password = password.text; var event : LoginEvent = new LoginEvent( loginVO ); dispatchEvent( event ); } private var modelCheckValid : Boolean = true; ]]> </mx:Script>
<mx:Form id="loginForm"> <mx:Text text=" {AtsModelLocator.getInstance().loginFailMessage }" width="80%" color="red"/> <mx:FormItem label="Username: " required="true"> <mx:TextInput id="username" /> </mx:FormItem>
<mx:FormItem label="Password: " required="true"> <mx:TextInput id="password" /> </mx:FormItem> </mx:Form>
<mx:ControlBar> <mx:Button id="loginSubmit" label="Login" mouseUp="loginUser()"/> </mx:ControlBar> <mx:StringValidator id="userNameValidator" source="{username}" property="text" tooShortError="This string is shorter than the minimum allowed length of 3. " tooLongError="This string is longer than the maximum allowed length of 20." minLength="4" maxLength="20" invalid="modelCheckValid=false" trigger="{loginSubmit}" triggerEvent="mouseDown"/>
<mx:StringValidator id="userPassValidator" source="{password}" property="text" tooShortError="This string is shorter than the minimum allowed length of 6. " tooLongError="This string is longer than the maximum allowed length of 10." minLength="4" maxLength="20" invalid="modelCheckValid=false" trigger="{loginSubmit}" triggerEvent="mouseDown"/>
为什么这么复杂地在Validator中定义trigger, triggerEvent呢? 原因是这样的: 如果不是在Validator的invalid事件中去设置modelCheckValid这个标志量. 就需要在loginUser()函数中对所有Validator进行判断, 代码会显得比较臃肿复杂. 而且如果需要考虑是否需要一次性显示出所有校验失败的错误. 代码示例: <mx:Script> <![CDATA[ import mx.controls.Alert; import mx.events.ValidationResultEvent; import mx.validators.ValidationResult; import com.ats.vo.LoginVO; import com.ats.control.LoginEvent; import mx.validators; public function loginUser() : void { var vrEvent : ValidateResultEvent; var checkFailed : Boolean = false; vrEvent = userNameValidator.validate(); if ( vrEvent.results != null && vrEvent.results.length > 0 ) { // 验证失败 checkFailed = true; } vrEvent = userPassValidator.validate(); if ( vrEvent.results != null && vrEvent.results.length > 0 ) { // 验证失败 checkFailed = true; } if ( checkFailed ) return; var loginVO : LoginVO = new LoginVO(); loginVO.username = username.text; loginVO.password = password.text; var event : LoginEvent = new LoginEvent( loginVO ); dispatchEvent( event ); } ]]> </mx:Script>
<mx:Form id="loginForm"> <mx:Text text=" {AtsModelLocator.getInstance().loginFailMessage }" width="80%" color="red"/> <mx:FormItem label="Username: " required="true"> <mx:TextInput id="username" /> </mx:FormItem>
<mx:FormItem label="Password: " required="true"> <mx:TextInput id="password" /> </mx:FormItem> </mx:Form>
<mx:ControlBar> <mx:Button id="loginSubmit" label="Login" mouseUp="loginUser()"/> </mx:ControlBar> <mx:StringValidator id="userNameValidator" source="{username}" property="text" tooShortError="This string is shorter than the minimum allowed length of 3. " tooLongError="This string is longer than the maximum allowed length of 20." minLength="4" maxLength="20"/>
<mx:StringValidator id="userPassValidator" source="{password}" property="text" tooShortError="This string is shorter than the minimum allowed length of 6. " tooLongError="This string is longer than the maximum allowed length of 10." minLength="4" maxLength="20"/>
这种方法也是可行的. 至于具体使用哪一个, 凭自己的喜好了.
这一段时间在 Cairngorm上搭建了一个小项目, 顺便小结一下开发过程: 1. 首先规划构建View, 将一个应用的界面, 分成适当的Mxml Component 2. view中必然涉及的需要数据的绑定, 将组件需要的数据都集中到ModelLocator中. 3. 设计事件(CairngormEvent), 也就是与用户交互的过程中以及系统运转的过程中会需要派发哪些事件, 需要注意的一点是, Cairngorm中Flex事件也需要转化成CairngormEvent 4. 设计事件的处理函数, 也就是命令. 在FrontControl中对事件和命令进行注册. 5. 命令中通过代理去调用服务. 5. 设计代理类, 在代理类中调用服务 5. 设计命令中涉及的服务(最可能的是与数据库的交互), 并添加相应的配置 6. 设计服务中需要使用的ValueObject 7. 命令中如果需要对视图组件数据进行存取, 需要通过ViewHelper来完成, 设计相应的ViewHelper. 同时在mxml中对viewHelper进行注册.
补充说明: 事件的产生不一定全部是与用户交互的结果, 也就是说不全是由View产生的. 当然大部分的事件(比如用户点击了保存按钮)是这样产生的. 在命令中也可以产生命令, 典型的就是SequenceCommand, 应用中可以把一个事件的处理分成几个步骤来完成, 完成第1个步骤后怎么通知第2个步骤开始呢, 当然还是继续派发事件啦. 在SequenceCommand在类中, 把派发事件封装了一下, 给出了一个executeNextCommand()可以直接调用. 不过在这里我也遇到了一个问题, 直接使用SequenceCommand的executeNextCommand()并不管用. 好象dispatchEvent并没有效果, 我后来是自己修改了代码, 使用Application.application.dispatchEvent才解决问题的.
Cairngorm的优点: 一. 实现了比较彻底的解耦 1. 事件机制, 对用户的响应(比如点击保存按钮), 并不是直接从View中抓取数据, 然后New一个类, 调用这个类的某个方法, 将数据保存到数据库中, 而只是简单地派发一个事件, 具体事件由谁来响应, 如何处理对他来说是透明不可见的. 2. Locator模式, Cairngorm中service, view, model的获取都是通过Locator的, 也就是说系统其他部分对于service, view, model只需要知道其ID就够了, 其内部实现等待细节都是不需要知道的. 举个例子, 传统的方法: 你要找一个叫张三的人帮你干一件事, 你需要知道张三长什么样, 然后在一个坐着10个人的大办公室里找到他, 告诉他你的要求. 而现在在这个大办公室的门口多了一个前台小姐, 你只需要告诉这个小姐,我要找张三, 然后她会帮你去找, 你根本不需要知道张三的模样.
RIA的优点: 由于Flex的原因, 系统处理是异步的. 比如, 你请求了一个比较耗时的数据库读取操作, 请求发出后, 你就可以进行其他操作了, 服务结束会产生相应事件, 然后由Command进行后续处理, 最后引发页面数据更新.
进行了一系列的改动。最明显的是Xml命名空间的变化. 从www.macromedia.com/2005/xml 修改为 www.adobe.com/2006/xml 其次变量、和成员的绑定变得更简单,对于[Bindable]属性的变量或者类成员。 可以不需要手工指定并派发事件,系统会自动进行。 Beta1代码示意: private var _fieldTest:String; [Bindable("textChanged")] public function get fieldTest() : String{ return _fieldTest;} public function set fieldTest(s : String) { _fieldTest = s; dispatchEvent(new Event("textChanged")); } Beta2代码示意: private var _fieldTest : String; [Bindable] public function get fieldTest() { return _fieldTest; } public function set fieldTest(s : String) { _fieldTest = s; } 有所简化。 另外一些多余的属性和方法被取消。 一些类、属性、方法改变名字。
摘要: 最近做一个项目的时候,需要将数据库从原先的SqlServer迁移到Oracle中。需要迁移的不仅是数据还需要将表结构、存储过程、视图、触发器.... 所有东西都迁过去。于是在网上搜索了一下,很快找到了www.swissql.com中提供了这样的工具。但是能下载的是30天有效。只能转换2000行Sql文本的试用版。自己动手、丰衣足食。开始破解:1. 安装SwisSql2. 把SwisSql的Lib目... 阅读全文
|