|
2008年4月14日
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">
|