2006年8月2日
http://www.blogjava.net/aoxj/archive/2010/04/10/317953.html
摘要: 返回值 意义SKIP_BODY 表示不用处理标签体,直接调用doEndTag()方法。SKIP_PAGE 忽略标签后面的JSP内容。EVAL_PAGE 处理标签后,继续处理JSP后面的内容。 ... 阅读全文
说明一下,这一篇文章的用到的lucene,是用2.0版本的,主要在查询的时候2.0版本的lucene与以前的版本有了一些区别. 其实这一些代码都是早几个月写的,自己很懒,所以到今天才写到自己的博客上,高深的文章自己写不了,只能记录下一些简单的记录与点滴,其中的代码算是自娱自乐的,希望高手不要把重构之类的砸下来...
1、在windows系统下的的C盘,建一个名叫s的文件夹,在该文件夹里面随便建三个txt文件,随便起名啦,就叫"1.txt","2.txt"和"3.txt"啦 其中1.txt的内容如下:
代码
而"2.txt"和"3.txt"的内容也可以随便写几写,这里懒写,就复制一个和1.txt文件的内容一样吧
2、下载lucene包,放在classpath路径中 建立索引:
代码 - package lighter.javaeye.com;
-
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.util.Date;
-
- import org.apache.lucene.analysis.Analyzer;
- import org.apache.lucene.analysis.standard.StandardAnalyzer;
- import org.apache.lucene.document.Document;
- import org.apache.lucene.document.Field;
- import org.apache.lucene.index.IndexWriter;
-
-
-
-
- public class TextFileIndexer {
- public static void main(String[] args) throws Exception {
-
- File fileDir = new File("c:\\s");
-
-
- File indexDir = new File("c:\\index");
- Analyzer luceneAnalyzer = new StandardAnalyzer();
- IndexWriter indexWriter = new IndexWriter(indexDir, luceneAnalyzer,
- true);
- File[] textFiles = fileDir.listFiles();
- long startTime = new Date().getTime();
-
-
- for (int i = 0; i < textFiles.length; i++) {
- if (textFiles[i].isFile()
- && textFiles[i].getName().endsWith(".txt")) {
- System.out.println("File " + textFiles[i].getCanonicalPath()
- + "正在被索引....");
- String temp = FileReaderAll(textFiles[i].getCanonicalPath(),
- "GBK");
- System.out.println(temp);
- Document document = new Document();
- Field FieldPath = new Field("path", textFiles[i].getPath(),
- Field.Store.YES, Field.Index.NO);
- Field FieldBody = new Field("body", temp, Field.Store.YES,
- Field.Index.TOKENIZED,
- Field.TermVector.WITH_POSITIONS_OFFSETS);
- document.add(FieldPath);
- document.add(FieldBody);
- indexWriter.addDocument(document);
- }
- }
-
- indexWriter.optimize();
- indexWriter.close();
-
-
- long endTime = new Date().getTime();
- System.out
- .println("这花费了"
- + (endTime - startTime)
- + " 毫秒来把文档增加到索引里面去!"
- + fileDir.getPath());
- }
-
- public static String FileReaderAll(String FileName, String charset)
- throws IOException {
- BufferedReader reader = new BufferedReader(new InputStreamReader(
- new FileInputStream(FileName), charset));
- String line = new String();
- String temp = new String();
-
- while ((line = reader.readLine()) != null) {
- temp += line;
- }
- reader.close();
- return temp;
- }
- }
索引的结果:
代码 - File C:\s\1.txt正在被索引....
- 中华人民共和国全国人民2006年
- File C:\s\2.txt正在被索引....
- 中华人民共和国全国人民2006年
- File C:\s\3.txt正在被索引....
- 中华人民共和国全国人民2006年
- 这花费了297 毫秒来把文档增加到索引里面去!c:\s
3、建立了索引之后,查询啦....
代码 - package lighter.javaeye.com;
-
- import java.io.IOException;
-
- import org.apache.lucene.analysis.Analyzer;
- import org.apache.lucene.analysis.standard.StandardAnalyzer;
- import org.apache.lucene.queryParser.ParseException;
- import org.apache.lucene.queryParser.QueryParser;
- import org.apache.lucene.search.Hits;
- import org.apache.lucene.search.IndexSearcher;
- import org.apache.lucene.search.Query;
-
- public class TestQuery {
- public static void main(String[] args) throws IOException, ParseException {
- Hits hits = null;
- String queryString = "中华";
- Query query = null;
- IndexSearcher searcher = new IndexSearcher("c:\\index");
-
- Analyzer analyzer = new StandardAnalyzer();
- try {
- QueryParser qp = new QueryParser("body", analyzer);
- query = qp.parse(queryString);
- } catch (ParseException e) {
- }
- if (searcher != null) {
- hits = searcher.search(query);
- if (hits.length() > 0) {
- System.out.println("找到:" + hits.length() + " 个结果!");
- }
- }
- }
-
- }
其运行结果:
引用 找到:3 个结果! 具体的API的用法,这里就不说了,具体的做法参考lucene的官方文档吧... 下一篇文章: 搜索篇:lucene的简单实例<二> http://www.javaeye.com/post/190576打一个例子吧, 这是lucene2.0的API 代码 - QueryParser qp = new QueryParser("body", analyzer);
- query = qp.parse(queryString);
这是lucene1.4.3版的API
代码 - query = QueryParser.parse(key,queryString,new new StandardAnalyzer());
详细的改动看一些官方的文档就清楚啦 文章的时候,感觉比较难写的就是标题,有时候不知道起什么名字好,反正这里写的都是关于lucene的一些简单的实例,就随便起啦.
Lucene 其实很简单的,它最主要就是做两件事:建立索引和进行搜索 来看一些在lucene中使用的术语,这里并不打算作详细的介绍,只是点一下而已----因为这一个世界有一种好东西,叫搜索。 IndexWriter:lucene中最重要的的类之一,它主要是用来将文档加入索引,同时控制索引过程中的一些参数使用。 Analyzer:分析器,主要用于分析搜索引擎遇到的各种文本。常用的有StandardAnalyzer分析器,StopAnalyzer分析器,WhitespaceAnalyzer分析器等。 Directory:索引存放的位置;lucene提供了两种索引存放的位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene提供了FSDirectory和RAMDirectory两个类。 Document:文档;Document相当于一个要进行索引的单元,任何可以想要被索引的文件都必须转化为Document对象才能进行索引。 Field:字段。 IndexSearcher:是lucene中最基本的检索工具,所有的检索都会用到IndexSearcher工具; Query:查询,lucene中支持模糊查询,语义查询,短语查询,组合查询等等,如有TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些类。 QueryParser: 是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成Query对象。 Hits:在搜索完成之后,需要把搜索结果返回并显示给用户,只有这样才算是完成搜索的目的。在lucene中,搜索的结果的集合是用Hits类的实例来表示的。 上面作了一大堆名词解释,下面就看几个简单的实例吧: 1、简单的的StandardAnalyzer测试例子
代码 - package lighter.javaeye.com;
-
- import java.io.IOException;
- import java.io.StringReader;
-
- import org.apache.lucene.analysis.Analyzer;
- import org.apache.lucene.analysis.Token;
- import org.apache.lucene.analysis.TokenStream;
- import org.apache.lucene.analysis.standard.StandardAnalyzer;
-
- public class StandardAnalyzerTest
- {
-
- public StandardAnalyzerTest()
- {
- }
- public static void main(String[] args)
- {
-
- Analyzer aAnalyzer = new StandardAnalyzer();
-
- StringReader sr = new StringReader("lighter javaeye com is the are on");
-
- TokenStream ts = aAnalyzer.tokenStream("name", sr);
- try {
- int i=0;
- Token t = ts.next();
- while(t!=null)
- {
-
- i++;
-
- System.out.println("第"+i+"行:"+t.termText());
-
- t=ts.next();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
显示结果: 引用 第1行:lighter 第2行:javaeye 第3行:com 提示一下: StandardAnalyzer是lucene中内置的"标准分析器",可以做如下功能: 1、对原有句子按照空格进行了分词 2、所有的大写字母都可以能转换为小写的字母 3、可以去掉一些没有用处的单词,例如"is","the","are"等单词,也删除了所有的标点 查看一下结果与"new StringReader("lighter javaeye com is the are on")"作一个比较就清楚明了。 这里不对其API进行解释了,具体见lucene的官方文档。需要注意一点,这里的代码使用的是lucene2的API,与1.43版有一些明显的差别。
2、看另一个实例,简单地建立索引,进行搜索
代码 - package lighter.javaeye.com;
- import org.apache.lucene.analysis.standard.StandardAnalyzer;
- import org.apache.lucene.document.Document;
- import org.apache.lucene.document.Field;
- import org.apache.lucene.index.IndexWriter;
- import org.apache.lucene.queryParser.QueryParser;
- import org.apache.lucene.search.Hits;
- import org.apache.lucene.search.IndexSearcher;
- import org.apache.lucene.search.Query;
- import org.apache.lucene.store.FSDirectory;
-
- public class FSDirectoryTest {
-
-
- public static final String path = "c:\\index2";
-
- public static void main(String[] args) throws Exception {
- Document doc1 = new Document();
- doc1.add( new Field("name", "lighter javaeye com",Field.Store.YES,Field.Index.TOKENIZED));
-
- Document doc2 = new Document();
- doc2.add(new Field("name", "lighter blog",Field.Store.YES,Field.Index.TOKENIZED));
-
- IndexWriter writer = new IndexWriter(FSDirectory.getDirectory(path, true), new StandardAnalyzer(), true);
- writer.setMaxFieldLength(3);
- writer.addDocument(doc1);
- writer.setMaxFieldLength(3);
- writer.addDocument(doc2);
- writer.close();
-
- IndexSearcher searcher = new IndexSearcher(path);
- Hits hits = null;
- Query query = null;
- QueryParser qp = new QueryParser("name",new StandardAnalyzer());
-
- query = qp.parse("lighter");
- hits = searcher.search(query);
- System.out.println("查找\"lighter\" 共" + hits.length() + "个结果");
-
- query = qp.parse("javaeye");
- hits = searcher.search(query);
- System.out.println("查找\"javaeye\" 共" + hits.length() + "个结果");
-
- }
-
- }
运行结果: 代码 - 查找"lighter" 共2个结果
- 查找"javaeye" 共1个结果
很久没有看lucene了,这两三天又复习了一下,上一些代码都是前几个月写的,只是改动了一些字符串和包名显示。转载时请说明,文章来自:http://lighter.javaeye.com。 如有什么错误的地方,恳请指出,谢谢。
wen19851025 写道 //测试字符串 StringReader sr = new StringReader("lighter javaeye com"); //生成TokenStream对象 TokenStream ts = aAnalyzer.tokenStream("name", sr); 请问:以上的解析是按什么来解析,为什么他会自动的按空格或者","进行字符分割,再一个当SR里输入是中文字符时,他将会对每个字进行分割,请问这是为什么,同时这功能的实现又意为着什么呢.....???? StandardAnalyzer是lucene中内置的"标准分析器",可以做如下功能: 1、对原有句子按照空格进行了分词 2、所有的大写字母都可以能转换为小写的字母 3、可以去掉一些没有用处的单词,例如"is","the","are"等单词,也删除了所有的标点 同时也可以对中文进行分词(效果不好),现在有很多的中文分词包可以采用
摘要: Lucene是apache组织的一个用java实现全文搜索引擎的开源项目。其功能非常的强大,api也很简单。总得来说用Lucene来进行建立和搜索和操作数据库是差不多的(有点像),Document可以看作是数据库的一行记录,Field可以看作是数据库的字段。用lucene实现搜索引擎就像用JDBC实现连接数据库一样简单。
... 阅读全文
昨天,由于程序中需要用到一个WEB TABS样式的页面,GOOGLE之,才发现这东西真不好找。 displaytag 这个东西计划要支持TABS,为啥只是计划,虽然它的表格样式看起来不错。 webtabs 这个嘛,样子太丑了,拿不出手。 prizetags 这个可控制的属性太少了,不爽。 Ditchnet JSP Tabs Taglib 就是它,为什么没有地方能下载到,哪位好心人能告诉我?
下面的这个站收录的比较全,收藏之。 东西还真不少,慢慢学啦,可惜没有我想要的 【Java开源 Jsp标签库】 Posted by E_wsq 2006-3-29 9:32:00 displytag
与Struts结合使用最出名的一个tag主要是显示表格数据很漂亮、完善。 500)this.style.width=500;” border=0> http://displaytag.sourceforge.net/ cewolf tag
用来在web上显示复杂图形报表的一个jsp tag。 500)this.style.width=500;” border=0> http://cewolf.sourceforge.net/ Loading Tab
当一个复杂的操作可以加载比较长的时间时,用这个tag。 500)this.style.width=500;” border=0> http://www.mycgiserver.com/~eboudrant/#taglibs DbForms
DbForms!它是一个基于 Java (Servlet,JSP/Taglib)的快速应用程序开发环境,可以帮助开发人员快速建造基于Web的数据库应用程序。 500)this.style.width=500;” border=0> http://jdbforms.sourceforge.net/ Jakarta Taglibs
Jakarta Taglibs 是为JSP定制标签库和相关的项目提供的一个开源仓库,如 TagLibraryValidator类,和对页面生成工具的扩展来支持标签库。Jakarta Taglibs 也包括了对JSP Standard Tag Library (JSTL)的参考实现。这个实现基于项目标准。目前,在Jakarta Taglibs 中没有其它标签库代表了Java Community Process (JCP) 标准。 500)this.style.width=500;” border=0> http://jakarta.apache.org/taglibs/index.html EasyLDAP
WebJMX标签库项目可以控制你的JMX 接口。WebJMX 这个标签库项目的目的是生成一个JSP标签库,可以让有技巧的JSP开发人员为JMX生成一个可定制的、规范的、基于Web的界面。 500)this.style.width=500;” border=0> http://webjmx.sourceforge.net/JPivot
JPivot - 是一个JSP 自定制的标签库,可以绘制一个OLAP表格和图表。用户可以执行典型的OLAP导航,如下钻,切片和方块。它使用Mondrian 作为其OLAP服务器。 500)this.style.width=500;” border=0> http://jpivot.sourceforge.net/JSP Tree Tag
JCE taglib把JCE(Java Cryptographic Extensions)包装成TagLib并且包含了EL函数。使用这个标签能够为jsp应用程序加强安全性。 500)this.style.width=500;” border=0> http://jcetaglib.sourceforge.net/Prize Tags
Prize Tags是一个集许多功能于一身的Jsp标签库。其中最受欢迎的Tree Tag,这个Tag可以为不同节点指定不同的图标,而且可以服务端可以监控客户端节点的展开,关闭,选中与未选中等事件。除了Tree Tag还有日历Tag,Icon Tag,Alternate Tag ,Template Tag 等其它的功能。 500)this.style.width=500;” border=0> http://www.jenkov.com/prizetags/introduction.tmplStruts-Layout
Struts-Layout是一个用在Struts的标签库.这个强大的标签库可以用来显示面板(panels),输入框,表格,treeviews,sortable lists,datagrids,popups,日历等.使用这些标签可以不用写HTML代码,甚至可以不用懂得HTML.这个项目还提供一个Eclipse下的插件Kiwi帮助使用Struts和Struts-Layout来开发Jsp页面.以下是一张例图: 500)this.style.width=500;” border=0>
这个AjaxTags是在现有的Struts HTML标签库上添加对AJAX (Asynchronous Javascript+XML)技术的支持。这样就可以为现有的基于Struts HTML标签库的应用程序添加AJAX功能而不用破坏现存的代码并且开发者不需要了解AJAX是怎样工作的。 500)this.style.width=500;” border=0> http://struts.sourceforge.net/ajaxtags/AWTaglib
AWTaglib是一个Jsp标签可用于创建网格(grid)控件.它还提供一些额外的功能可以把网格中的数据导出为XLS,PDF和CSV(利用JasperReports来实现)并能与Struts框架相结合. 500)this.style.width=500;” border=0> http://awtaglib.sourceforge.net/eXtremeTable
eXtremeTable是一个可扩展的用于以表格的形式来显示数据的一组JSP标签库.
关键字 J2EE Spring Acegi
[简介]
对于一个典型的Web应用,完善的认证和授权机制是必不可少的,在SpringFramework中,Juergen Hoeller提供的范例JPetStore给了一些这方面的介绍,但还远远不够,Acegi是一个专门为SpringFramework提供安全机制的 项目,全称为Acegi Security System for Spring,当前版本为0.5.1,就其目前提供的功能,应该可以满足绝大多数应用的需求。
本文的主要目的是希望能够说明如何在基于Spring构架的Web应用中使用Acegi,而不是详细介绍其中的每个接口、每个类。注意,即使对已经存在的Spring应用,通过下面介绍的步骤,也可以马上享受到Acegi提供的认证和授权。
[基础工作] 在你的Web应用的lib中添加Acegi下载包中的acegi-security.jar
[web.xml] 实现认证和授权的最常用的方法是通过filter,Acegi亦是如此,通常Acegi需要在web.xml添加以下5个filter:
<filter> <filter-name>Acegi Channel Processing Filter</filter-name> <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetClass</param-name> <param-value>net.sf.acegisecurity.securechannel.ChannelProcessingFilter</param-value> </init-param> </filter> <filter> <filter-name>Acegi Authentication Processing Filter</filter-name> <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetClass</param-name> <param-value>net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter</param-value> </init-param> </filter> <filter> <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name> <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetClass</param-name> <param-value>net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter</param-value> </init-param> </filter> <filter> <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name> <filter-class>net.sf.acegisecurity.ui.AutoIntegrationFilter</filter-class> </filter> <filter> <filter-name>Acegi HTTP Request Security Filter</filter-name> <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class> <init-param> <param-name>targetClass</param-name> <param-value>net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter</param-value> </init-param> </filter>
最先引起迷惑的是net.sf.acegisecurity.util.FilterToBeanProxy,Acegi自己的文档上解释是: “What FilterToBeanProxy does is delegate the Filter’s methods through to a bean which is obtained from the Spring application context. This enables the bean to benefit from the Spring application context lifecycle support and configuration flexibility.”,如希望深究的话,去看看源代码应该不难理解。
再下来就是添加filter-mapping了: <filter-mapping> <filter-name>Acegi Channel Processing Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>Acegi Authentication Processing Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>Acegi HTTP BASIC Authorization Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>Acegi Security System for Spring Auto Integration Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>Acegi HTTP Request Security Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
这里,需要注意以下两点: 1) 这几个filter的顺序是不能更改的,顺序不对将无法正常工作; 2) 如果你的应用不需要安全传输,如https,则将”Acegi Channel Processing Filter”相关内容注释掉即可; 3) 如果你的应用不需要Spring提供的远程访问机制,如Hessian and Burlap,将”Acegi HTTP BASIC Authorization Filter”相关内容注释掉即可。
[applicationContext.xml] 接下来就是要添加applicationContext.xml中的内容了,从刚才FilterToBeanFactory的解释可以看出,真正的filter都 在Spring的applicationContext中管理:
1) 首先,你的数据库中必须具有保存用户名和密码的table,Acegi要求table的schema必须如下:
CREATE TABLE users ( username VARCHAR(50) NOT NULL PRIMARY KEY, password VARCHAR(50) NOT NULL, enabled BIT NOT NULL ); CREATE TABLE authorities ( username VARCHAR(50) NOT NULL, authority VARCHAR(50) NOT NULL ); CREATE UNIQUE INDEX ix_auth_username ON authorities ( username, authority ); ALTER TABLE authorities ADD CONSTRAINT fk_authorities_users foreign key (username) REFERENCES users (username);
2) 添加访问你的数据库的datasource和Acegi的jdbcDao,如下:
<bean id=”dataSource” class=”org.springframework.jdbc.datasource.DriverManagerDataSource”> <property name=”driverClassName”><value>${jdbc.driverClassName}</value></property> <property name=”url”><value>${jdbc.url}</value></property> <property name=”username”><value>${jdbc.username}</value></property> <property name=”password”><value>${jdbc.password}</value></property> </bean> <bean id=”jdbcDaoImpl” class=”net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl”> <property name=”dataSource”><ref bean=”dataSource”/></property> </bean>
3) 添加DaoAuthenticationProvider:
<bean id=”daoAuthenticationProvider” class=”net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider”> <property name=”authenticationDao”><ref bean=”authenticationDao”/></property> <property name=”userCache”><ref bean=”userCache”/></property> </bean>
<bean id=”userCache” class=”net.sf.acegisecurity.providers.dao.cache.EhCacheBasedUserCache”> <property name=”minutesToIdle”><value>5</value></property> </bean>
如果你需要对密码加密,则在daoAuthenticationProvider中加入:<property name=”passwordEncoder”><ref bean=”passwordEncoder”/></property>,Acegi提供了几种加密方法,详细情况可看包 net.sf.acegisecurity.providers.encoding
4) 添加authenticationManager:
<bean id=”authenticationManager” class=”net.sf.acegisecurity.providers.ProviderManager”> <property name=”providers”> <list> <ref bean=”daoAuthenticationProvider”/> </list> </property> </bean>
5) 添加accessDecisionManager:
<bean id=”accessDecisionManager” class=”net.sf.acegisecurity.vote.AffirmativeBased”> <property name=”allowIfAllAbstainDecisions”> <value>false</value> </property> <property name=”decisionVoters”> <list><ref bean=”roleVoter”/></list> </property> </bean> <bean id=”roleVoter” class=”net.sf.acegisecurity.vote.RoleVoter”/>
6) 添加authenticationProcessingFilterEntryPoint:
<bean id=”authenticationProcessingFilterEntryPoint” class=”net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint”> <property name=”loginFormUrl”><value>/acegilogin.jsp</value></property> <property name=”forceHttps”><value>false</value></property> </bean>
其中acegilogin.jsp是登陆页面,一个最简单的登录页面如下:
<%@ taglib prefix=’c’ uri=’http://java.sun.com/jstl/core’ %> <%@ page import=”net.sf.acegisecurity.ui.AbstractProcessingFilter” %> <%@ page import=”net.sf.acegisecurity.AuthenticationException” %> <html> <head> <title>Login</title> </head>
<body> <h1>Login</h1> <form action=”<c:url value=’j_acegi_security_check’/>” method=”POST”> <table> <tr><td>User:</td><td><input type=’text’ name=’j_username’></td></tr> <tr><td>Password:</td><td><input type=’password’ name=’j_password’></td></tr> <tr><td colspan=’2′><input name=”submit” type=”submit”></td></tr> <tr><td colspan=’2′><input name=”reset” type=”reset”></td></tr> </table> </form> </body> </html>
7) 添加filterInvocationInterceptor:
<bean id=”filterInvocationInterceptor” class=”net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor”> <property name=”authenticationManager”> <ref bean=”authenticationManager”/> </property> <property name=”accessDecisionManager”> <ref bean=”accessDecisionManager”/> </property> <property name=”objectDefinitionSource”> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON \A/sec/administrator.*\Z=ROLE_SUPERVISOR \A/sec/user.*\Z=ROLE_TELLER </value> </property> </bean>
这里请注意,要objectDefinitionSource中定义哪些页面需要权限访问,需要根据自己的应用需求进行修改,我上面给出 的定义的意思是这样的: a. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON意思是在比较请求路径时全部转换为小写 b. \A/sec/administrator.*\Z=ROLE_SUPERVISOR意思是只有权限为ROLE_SUPERVISOR才能访问/sec/administrator*的页面 c. \A/sec/user.*\Z=ROLE_TELLER意思是只有权限为ROLE_TELLER的用户才能访问/sec/user*的页面
8) 添加securityEnforcementFilter:
<bean id=”securityEnforcementFilter” class=”net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter”> <property name=”filterSecurityInterceptor”> <ref bean=”filterInvocationInterceptor”/> </property> <property name=”authenticationEntryPoint”> <ref bean=”authenticationProcessingFilterEntryPoint”/> </property> </bean>
9) 添加authenticationProcessingFilter:
<bean id=”authenticationProcessingFilter” class=”net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter”> <property name=”authenticationManager”> <ref bean=”authenticationManager”/> </property> <property name=”authenticationFailureUrl”> <value>/loginerror.jsp</value> </property> <property name=”defaultTargetUrl”> <value>/</value> </property> <property name=”filterProcessesUrl”> <value>/j_acegi_security_check</value> </property> </bean> 其中authenticationFailureUrl是认证失败的页面。
10) 如果需要一些页面通过安全通道的话,添加下面的配置:
<bean id=”channelProcessingFilter” class=”net.sf.acegisecurity.securechannel.ChannelProcessingFilter”> <property name=”channelDecisionManager”> <ref bean=”channelDecisionManager”/> </property> <property name=”filterInvocationDefinitionSource”> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON \A/sec/administrator.*\Z=REQUIRES_SECURE_CHANNEL \A/acegilogin.jsp.*\Z=REQUIRES_SECURE_CHANNEL \A/j_acegi_security_check.*\Z=REQUIRES_SECURE_CHANNEL \A.*\Z=REQUIRES_INSECURE_CHANNEL </value> </property> </bean>
<bean id=”channelDecisionManager” class=”net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl”> <property name=”channelProcessors”> <list> <ref bean=”secureChannelProcessor”/> <ref bean=”insecureChannelProcessor”/> </list> </property> </bean> <bean id=”secureChannelProcessor” class=”net.sf.acegisecurity.securechannel.SecureChannelProcessor”/> <bean id=”insecureChannelProcessor” class=”net.sf.acegisecurity.securechannel.InsecureChannelProcessor”/>
[缺少了什么?] Acegi目前提供了两种”secure object”,分别对页面和方法进行安全认证管理,我这里介绍的只是利用 FilterSecurityInterceptor对访问页面的权限控制,除此之外,Acegi还提供了另外一个Interceptor– MethodSecurityInterceptor,它结合runAsManager可实现对对象中的方法的权限控制,使用方法可参看Acegi自带的文档 和contact范例。
[最后要说的] 本来以为只是说明如何使用Acegi而已,应该非常简单,但真正写起来才发现想要条理清楚的理顺所有需要的bean还是很 困难的,但愿我没有遗漏太多东西,如果我的文章有什么遗漏或错误的话,还请参看Acegi自带的quick-start范例,但请 注意,这个范例是不能直接拿来用的。
原文在这里:
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=54881
<hibernate-configuration> <session-factory>
<property name="dialect">org.hibernate.dialect.Oracle9Dialect</property> <property name="show_sql">true</property> <property name="current_session_context_class">thread</property> <!--Fetch Size 是设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数--> <property name="jdbc.fetch_size">20</property> <!--Batch Size是设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,有点相当于设置Buffer缓冲区大小的意思--> <property name="jdbc.batch_size ">20</property> <property name="connection.username">HNMC</property> <property name="connection.password">sundy</property> <property name="connection.url">jdbc:oracle:thin:@192.168.0.20:1521:ora9</property> <property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property> <!-- 在这种情况下,没问题啊 --> <property name="c3p0.timeout">60</property> <property name="c3p0.idle_test_period">600</property>
<!--
<!--<property name="c3p0.idle_test_period">120</property>--> 如改以下俩值,或他们差距很大: <property name="c3p0.timeout">600</property> <!--<property name="c3p0.idle_test_period">60</property>--> 当我启动tomcat,运行程序后,空闲600后出现下列异常,但程序还照常运行
-->
<!-- Hibernate里可以设置的属性不多: #c3p0-native property name hibernate configuration key #c3p0.acquireIncrement hibernate.c3p0.acquire_increment #c3p0.idleConnectionTestPeriod hibernate.c3p0.idle_test_period #c3p0.initialPoolSize not available -- uses minimum size #c3p0.maxIdleTime hibernate.c3p0.timeout #c3p0.maxPoolSize hibernate.c3p0.max_size #c3p0.maxStatements hibernate.c3p0.max_statements #c3p0.minPoolSize hibernate.c3p0.min_size #c3p0.testConnectionsOnCheckout hibernate.c3p0.validate hibernate 2.x only! 另外的属性你需要配置c3p0.properties 比如: c3p0.acquireRetryDelay=111 c3p0.acquireRetryAttempts=22 c3p0.breakAfterAcquireFailure=true
-->
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> <property name="hibernate.c3p0.max_size">2</property> <property name="hibernate.c3p0.min_size">2</property> <property name="hibernate.c3p0.timeout">50000</property> <property name="hibernate.c3p0.max_statements">100</property> <property name="hibernate.c3p0.idle_test_period">3000</property> <property name="hibernate.c3p0.acquire_increment">2</property>
<!-- 另<property name="hibernate.c3p0.validate">false</property>确实在hibernate中不可设置 --> <property name="hibernate.c3p0.validate">false</property>
<!--C3P0 setting--> <property name="c3p0.max_size">20</property> <property name="c3p0.min_size">5</property> <!--获取连接的等待时间--> <property name="c3p0.timeout">3600</property> <property name="c3p0.max_statements">100</property> <!--每隔3600毫秒测试连接是否可以正常使用--> <property name="c3p0.idle_test_period">3600</property> <property name="c3p0.acquire_increment">2</property> <mapping resource="com/sundy/hnmc/beans/AccountBean.hbm.xml"/> </session-factory> </hibernate-configuration>
另一个设置 <!-- 数据源配置 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass"> <value>org.gjt.mm.mysql.Driver</value> </property> <property name="jdbcUrl"> <value>jdbc:mysql://localhost/tycho?useUnicode=true&characterEncoding=utf-8</value> </property> <property name="properties"> <props> <prop key="hibernate.hbm2ddl.auto">update</prop> <prop key="c3p0.minPoolSize">1</prop> <prop key="hc3p0.maxPoolSize">10</prop> <prop key="hc3p0.timeout">60</prop> <prop key="c3p0.max_statement">50</prop> <prop key="c3p0.testConnectionOnCheckout">true</prop> <prop key="hibernate.c3p0.testConnectionOnCheckout">false</prop> <prop key="user">root</prop> <prop key="password">root</prop> </props> </property> </bean>
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> <property name="hibernate.c3p0.max_size">20</property> <property name="hibernate.c3p0.min_size">5</property> <property name="hibernate.c3p0.timeout">120</property> <property name="hibernate.c3p0.max_statements">100</property> <property name="hibernate.c3p0.idle_test_period">120</property> <property name="hibernate.c3p0.acquire_increment">2</property>
-
LoadModule proxy_module modules/mod_proxy.so
-
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
-
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
-
-
ProxyPass /admin balancer://tomcatcluster/admin
lbmethod
=
byrequests
stickysession
=
JSESSIONID
nofailover
=
Off
timeout
=
5
maxattempts
=
3
-
ProxyPassReverse /admin balancer://tomcatcluster/admin
-
-
<
Proxy
balancer://tomcatcluster
>
-
BalancerMember ajp://localhost:8009
route
=
web1
-
BalancerMember ajp://localhost:10009
smax
=
10
route
=
web2
-
BalancerMember ajp://localhost:11009
route
=
web3
-
BalancerMember ajp://localhost:12009
smax
=
10
route
=
web4
-
</
Proxy
>
Taglib 原理和实现之支持El表达式 |
|
作者:佚名 文章来源:未知 点击数:
107 更新时间:2006-2-17 |
1.先看这么一个例子
<%@ page contentType="text/html; charset=gb2312" language="java"%> <%@ taglib uri="/WEB-INF/tlds/c.tld" prefix="c"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <body> <% String tut = "tutorial"; request.setAttribute("tut",tut); %> The String in request is : <c:out value="${tut}"/> </body> </html> |
2.如何支持el表达式
在路径org.apache.taglibs.standard.lang.support下,有个叫 ExpressionEvaluatorManager.evaluate 的方法,当el表达式作为入参时,调用这个方法,在tag内即可自动把el表达式转化。例如,你想tag的value字段支持el表达式,那么只需在set方法里如下调用:
public void setValue(Object value)throws JspException { this.value = ExpressionEvaluatorManager.evaluate( "value", value.toString(), Object.class, this, pageContext); } |
ExpressionEvaluatorManager.evaluate有四个参数。第一个表示tag的名字,在取el表达式出错时使用。一般和属性名字相同。第二个要求字符串,通常简单调用输入对象的toString方法。第三个是类,通常用Object.class。第四个用this即可,第五个是pageContext变量。
通常不用对这个方法思考太多。只需改改属性名字,其他照搬即可。
注意:当你的tag属性支持el表达式时,你必须把它声明为Object对象。如上述的value,应该声明为:
private Object value = null; |
3.实例:让OutputTag支持El表达式
package diegoyun;
import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.TagSupport;
import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
public class NewOutputTag extends TagSupport { private Object name = null;
public void setName(Object name) throws JspException { this.name = ExpressionEvaluatorManager.evaluate( "name", name.toString(), Object.class, this, pageContext); } public int doStartTag() throws JspException{ try { JspWriter out = pageContext.getOut(); out.print("Hello! " + name); } catch (Exception e) { throw new JspException(e); } return EVAL_PAGE;
} } |
在diego.tld里添加声明
<!--NewOutputTag--> <tag> <name>newout</name> <tag-class>diegoyun.NewOutputTag</tag-class> <body-content>empty</body-content> <attribute> <name>name</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> |
编写jsp测试
<%@ page language="java" %> <%@ taglib uri="/WEB-INF/tlds/diego.tld" prefix="diego"%> <html> <body bgcolor="#FFFFFF"> <% String s = "diego"; request.setAttribute("name",s); %> Test El supported tag: <br> <diego:newout name="${name}"/>
</body> </html> |
可以看到页面输出为:
Test El supported tag: Hello! diego |
类的初始化和对象初始化是 JVM 管理的类型生命周期中非常重要的两个环节,Google 了一遍网络,有关类装载机制的文章倒是不少,然而类初始化和对象初始化的文章并不多,特别是从字节码和 JVM 层次来分析的文章更是鲜有所见。
本文主要对类和对象初始化全过程进行分析,通过一个实际问题引入,将源代码转换成 JVM 字节码后,对 JVM 执行过程的关键点进行全面解析,并在文中穿插入了相关 JVM 规范和 JVM 的部分内部理论知识,以理论与实际结合的方式介绍对象初始化和类初始化之间的协作以及可能存在的冲突问题。
问题引入
近日我在调试一个枚举类型的解析器程序,该解析器是将数据库内一万多条枚举代码装载到缓存中,为了实现快速定位枚举代码和具体枚举类别的所有枚举元素,该类在装载枚举代码的同时对其采取两种策略建立内存索引。由于该类是一个公共服务类,在程序各个层面都会使用到它,因此我将它实现为一个单例类。这个类在我调整类实例化语句位置之前运行正常,但当我把该类实例化语句调整到静态初始化语句之前时,我的程序不再为我工作了。
下面是经过我简化后的示例代码:
[清单一]
package com.ccb.framework.enums;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class CachingEnumResolver {
//单态实例 一切问题皆由此行引起
private static final CachingEnumResolver SINGLE_ENUM_RESOLVER = new
CachingEnumResolver();
/*MSGCODE->Category内存索引*/
private static Map CODE_MAP_CACHE;
static {
CODE_MAP_CACHE = new HashMap();
//为了说明问题,我在这里初始化一条数据
CODE_MAP_CACHE.put("0","北京市");
}
//private, for single instance
private CachingEnumResolver() {
//初始化加载数据 引起问题,该方法也要负点责任
initEnums();
}
/**
* 初始化所有的枚举类型
*/
public static void initEnums() {
// ~~~~~~~~~问题从这里开始暴露 ~~~~~~~~~~~//
if (null == CODE_MAP_CACHE) {
System.out.println("CODE_MAP_CACHE为空,问题在这里开始暴露.");
CODE_MAP_CACHE = new HashMap();
}
CODE_MAP_CACHE.put("1", "北京市");
CODE_MAP_CACHE.put("2", "云南省");
//..... other code...
}
public Map getCache() {
return Collections.unmodifiableMap(CODE_MAP_CACHE);
}
/**
* 获取单态实例
*
* @return
*/
public static CachingEnumResolver getInstance() {
return SINGLE_ENUM_RESOLVER;
}
public static void main(String[] args) {
System.out.println(CachingEnumResolver.getInstance().getCache());
}
}
|
想必大家看了上面的代码后会感觉有些茫然,这个类看起来没有问题啊,这的确属于典型的饿汉式单态模式啊,怎么会有问题呢?
是的,他看起来的确没有问题,可是如果将他 run 起来时,其结果是他不会为你正确 work。运行该类,它的执行结果是:
[清单二]
CODE_MAP_CACHE为空,问题在这里开始暴露.
{0=北京市}
|
我的程序怎么会这样?为什么在 initEnum() 方法里 CODE_MAP_CACHE 为空?为什么我输出的 CODE_MAP_CACHE 内容只有一个元素,其它两个元素呢????!!
看到这里,如果是你在调试该程序,你此刻一定觉得很奇怪,难道是我的 Jvm 有问题吗?非也!如果不是,那我的程序是怎么了?这绝对不是我想要的结果。可事实上无论怎么修改 initEnum() 方法都无济于事,起码我最初是一定不会怀疑到问题可能出在创建 CachingEnumResolver 实例这一环节上。正是因为我太相信我创建 CachingEnumResolver 实例的方法,加之对 Java 类初始化与对象实例化底层原理理解有所偏差,使我为此付出了三、四个小时--约半个工作日的大好青春。
那么问题究竟出在哪里呢?为什么会出现这样的怪事呢?在解决这个问题之前,先让我们来了解一下JVM的类和对象初始化的底层机制。
类的生命周期
上图展示的是类生命周期流向;在本文里,我只打算谈谈类的"初始化"以及"对象实例化"两个阶段。
类初始化
类"初始化"阶段,它是一个类或接口被首次使用的前阶段中的最后一项工作,本阶段负责为类变量赋予正确的初始值。
Java 编译器把所有的类变量初始化语句和类型的静态初始化器通通收集到 <clinit> 方法内,该方法只能被 Jvm 调用,专门承担初始化工作。
除接口以外,初始化一个类之前必须保证其直接超类已被初始化,并且该初始化过程是由 Jvm 保证线程安全的。另外,并非所有的类都会拥有一个 <clinit>() 方法,在以下条件中该类不会拥有 <clinit>() 方法:
- 该类既没有声明任何类变量,也没有静态初始化语句;
- 该类声明了类变量,但没有明确使用类变量初始化语句或静态初始化语句初始化;
- 该类仅包含静态 final 变量的类变量初始化语句,并且类变量初始化语句是编译时常量表达式。
对象初始化
在类被装载、连接和初始化,这个类就随时都可能使用了。对象实例化和初始化是就是对象生命的起始阶段的活动,在这里我们主要讨论对象的初始化工作的相关特点。
Java 编译器在编译每个类时都会为该类至少生成一个实例初始化方法--即 "<init>()" 方法。此方法与源代码中的每个构造方法相对应,如果类没有明确地声明任何构造方法,编译器则为该类生成一个默认的无参构造方法,这个默认的构造器仅仅调用父类的无参构造器,与此同时也会生成一个与默认构造方法对应的 "<init>()" 方法.
通常来说,<init>() 方法内包括的代码内容大概为:调用另一个 <init>() 方法;对实例变量初始化;与其对应的构造方法内的代码。
如果构造方法是明确地从调用同一个类中的另一个构造方法开始,那它对应的 <init>() 方法体内包括的内容为:一个对本类的 <init>() 方法的调用;对应用构造方法内的所有字节码。
如果构造方法不是通过调用自身类的其它构造方法开始,并且该对象不是 Object 对象,那 <init>() 法内则包括的内容为:一个对父类 <init>() 方法的调用;对实例变量初始化方法的字节码;最后是对应构造子的方法体字节码。
如果这个类是 Object,那么它的 <init>() 方法则不包括对父类 <init>() 方法的调用。
类的初始化时机
本文到目前为止,我们已经大概有了解到了类生命周期中都经历了哪些阶段,但这个类的生命周期的开始阶段--类装载又是在什么时候被触发呢?类又是何时被初始化的呢?让我们带着这三个疑问继续去寻找答案。
Java 虚拟机规范为类的初始化时机做了严格定义:"initialize on first active use"--" 在首次主动使用时初始化"。这个规则直接影响着类装载、连接和初始化类的机制--因为在类型被初始化之前它必须已经被连接,然而在连接之前又必须保证它已经被装载了。
在与初始化时机相关的类装载时机问题上,Java 虚拟机规范并没有对其做严格的定义,这就使得 JVM 在实现上可以根据自己的特点提供采用不同的装载策略。我们可以思考一下 Jboss AOP 框架的实现原理,它就是在对你的 class 文件装载环节做了手脚--插入了 AOP 的相关拦截字节码,这使得它可以对程序员做到完全透明化,哪怕你用 new 操作符创建出的对象实例也一样能被 AOP 框架拦截--与之相对应的 Spring AOP,你必须通过他的 BeanFactory 获得被 AOP 代理过的受管对象,当然 Jboss AOP 的缺点也很明显--他是和 JBOSS 服务器绑定很紧密的,你不能很轻松的移植到其它服务器上。嗯~……,说到这里有些跑题了,要知道 AOP 实现策略足可以写一本厚厚的书了,嘿嘿,就此打住。
说了这么多,类的初始化时机就是在"在首次主动使用时",那么,哪些情形下才符合首次主动使用的要求呢?
首次主动使用的情形:
- 创建某个类的新实例时--new、反射、克隆或反序列化;
- 调用某个类的静态方法时;
- 使用某个类或接口的静态字段或对该字段赋值时(final字段除外);
- 调用Java的某些反射方法时
- 初始化某个类的子类时
- 在虚拟机启动时某个含有main()方法的那个启动类。
除了以上几种情形以外,所有其它使用JAVA类型的方式都是被动使用的,他们不会导致类的初始化。
我的问题究竟出在哪里
好了,了解了JVM的类初始化与对象初始化机制后,我们就有了理论基础,也就可以理性的去分析问题了。
下面让我们来看看前面[清单一]的JAVA源代码反组译出的字节码:
[清单三]
public class com.ccb.framework.enums.CachingEnumResolver extends
java.lang.Object{
static {};
Code:
0: new #2; //class CachingEnumResolver
3: dup
4: invokespecial #14; //Method "<init>":()V ①
7: putstatic #16; //Field
SINGLE_ENUM_RESOLVER:Lcom/ccb/framework/enums/CachingEnumResolver;
10: new #18; //class HashMap ②
13: dup
14: invokespecial #19; //Method java/util/HashMap."<init>":()V
17: putstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
20: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
23: ldc #23; //String 0
25: ldc #25; //String 北京市
27: invokeinterface #31, 3; //InterfaceMethod
java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; ③
32: pop
33: return
private com.ccb.framework.enums.CachingEnumResolver();
Code:
0: aload_0
1: invokespecial #34; //Method java/lang/Object."<init>":()V
4: invokestatic #37; //Method initEnums:()V ④
7: return
public static void initEnums();
Code:
0: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map; ⑤
3: ifnonnull 24
6: getstatic #44; //Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #46; //String CODE_MAP_CACHE为空,问题在这里开始暴露.
11: invokevirtual #52; //Method
java/io/PrintStream.println:(Ljava/lang/String;)V
14: new #18; //class HashMap
17: dup
18: invokespecial #19; //Method java/util/HashMap."<init>":()V ⑥
21: putstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
24: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
27: ldc #54; //String 1
29: ldc #25; //String 北京市
31: invokeinterface #31, 3; //InterfaceMethod
java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; ⑦
36: pop
37: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
40: ldc #56; //String 2
42: ldc #58; //String 云南省
44: invokeinterface #31, 3; //InterfaceMethod
java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; ⑧
49: pop
50: return
public java.util.Map getCache();
Code:
0: getstatic #21; //Field CODE_MAP_CACHE:Ljava/util/Map;
3: invokestatic #66; //Method
java/util/Collections.unmodifiableMap:(Ljava/util/Map;)Ljava/util/Map;
6: areturn
public static com.ccb.framework.enums.CachingEnumResolver getInstance();
Code:
0: getstatic #16;
//Field SINGLE_ENUM_RESOLVER:Lcom/ccb/framework/enums/CachingEnumResolver; ⑨
3: areturn
}
|
如果上面[清单一]显示,清单内容是在 JDK1.4 环境下的字节码内容,可能这份清单对于很大部分兄弟来说确实没有多少吸引力,因为这些 JVM 指令确实不像源代码那样漂亮易懂。但它的的确确是查找和定位问题最直接的办法,我们想要的答案就在这份 JVM 指令清单里。
现在,让我们对该类从类初始化到对象实例初始化全过程分析[清单一]中的代码执行轨迹。
如前面所述,类初始化是在类真正可用时的最后一项前阶工作,该阶段负责对所有类正确的初始化值,此项工作是线程安全的,JVM会保证多线程同步。
第1步:调用类初始化方法 CachingEnumResolver.<clinit>(),该方法对外界是不可见的,换句话说是 JVM 内部专用方法,<clinit>() 内包括了 CachingEnumResolver 内所有的具有指定初始值的类变量的初始化语句。要注意的是并非每个类都具有该方法,具体的内容在前面已有叙述。
第2步:进入 <clinit>() 方法内,让我们看字节码中的 "①" 行,该行与其上面两行组合起来代表 new 一个 CachingEnumResolver 对象实例,而该代码行本身是指调用 CachingEnumResolver 类的 <init>()方法。每一个 Java 类都具有一个 <init>() 方法,该方法是 Java 编译器在编译时生成的,对外界不可见,<init>() 方法内包括了所有具有指定初始化值的实例变量初始化语句和java类的构造方法内的所有语句。对象在实例化时,均通过该方法进行初始化。然而到此步,一个潜在的问题已经在此埋伏好,就等着你来犯了。
第3步:让我们顺着执行顺序向下看,"④" 行,该行所在方法就是该类的构造器,该方法先调用父类的构造器 <init>() 对父对象进行初始化,然后调用 CachingEnumResolver.initEnum() 方法加载数据。
第4步:"⑤" 行,该行获取 "CODE_MAP_CACHE" 字段值,其运行时该字段值为 null。注意,问题已经开始显现了。(作为程序员的你一定是希望该字段已经被初始化过了,而事实上它还没有被初始化)。通过判断,由于该字段为 NULL,因此程序将继续执行到 "⑥" 行,将该字段实例化为 HashMap()。
第5步:在 "⑦"、"⑧" 行,其功能就是为 "CODE_MAP_CACHE" 字段填入两条数据。
第6步:退出对象初始化方法 <init>(),将生成的对象实例初始化给类字段 "SINGLE_ENUM_RESOLVER"。(注意,此刻该对象实例内的类变量还未初始化完全,刚才由 <init>() 调用 initEnum() 方法赋值的类变量 "CODE_MAP_CACHE" 是 <clinit>() 方法还未初始化字段,它还将在后面的类初始化过程再次被覆盖)。
第7步:继续执行 <clinit>()方法内的后继代码,"②" 行,该行对 "CODE_MAP_CACHE" 字段实例化为 HashMap 实例(注意:在对象实例化时已经对该字段赋值过了,现在又重新赋值为另一个实例,此刻,"CODE_MAP_CACHE"变量所引用的实例的类变量值被覆盖,到此我们的疑问已经有了答案)。
第8步:类初始化完毕,同时该单态类的实例化工作也完成。
通过对上面的字节码执行过程分析,或许你已经清楚了解到导致错误的深层原因了,也或许你可能早已被上面的分析过程给弄得晕头转向了,不过也没折,虽然我也可以从源代码的角度来阐述问题,但这样不够深度,同时也会有仅为个人观点、不足可信之嫌。
如何解决
要解决上面代码所存在的问题很简单,那就是将 "SINGLE_ENUM_RESOLVER" 变量的初始化赋值语句转移到 getInstance() 方法中去即可。换句话说就是要避免在类还未初始化完成时从内部实例化该类或在初始化过程中引用还未初始化的字段。
写在最后
静下浮燥之心,仔细思量自己是否真的掌握了本文主题所引出的知识,如果您觉得您已经完全或基本掌握了,那么很好,在最后,我将前面的代码稍做下修改,请思考下面两组程序是否同样会存在问题呢?
程序一
public class CachingEnumResolver {
public static Map CODE_MAP_CACHE;
static {
CODE_MAP_CACHE = new HashMap();
//为了说明问题,我在这里初始化一条数据
CODE_MAP_CACHE.put("0","北京市");
initEnums();
}
|
程序二
public class CachingEnumResolver {
private static final CachingEnumResolver SINGLE_ENUM_RESOLVER;
public static Map CODE_MAP_CACHE;
static {
CODE_MAP_CACHE = new HashMap();
//为了说明问题,我在这里初始化一条数据
CODE_MAP_CACHE.put("0","北京市");
SINGLE_ENUM_RESOLVER = new CachingEnumResolver();
initEnums();
}
|
最后,一点关于 JAVA 群体的感言:时下正是各种开源框架盛行时期,Spring 更是大行其道,吸引着一大批 JEE 开发者的眼球(我也是 fans 中的一员)。然而,让我们仔细观察一下--以 Spring 群体为例,在那么多的 Spring fans 当中,有多少人去研究过 Spring 源代码?又有多少人对 Spring 设计思想有真正深入了解呢?当然,我是没有资格以这样的口吻来说事的,我只是想表明一个观点--学东西一定要"正本清源"。
献上此文,谨以共勉。
日志论
在应用程序中输出日志有有三个目的:
(1)监视代码中变量的变化情况,把数据周期性地记录到文件中供其他应用进行统计分析工作。
(2)跟踪代码运行进轨迹,作为日后审计的依据。
(3)担当集成开发环境中的调试器,向文件或控制台打印代码的调试信息。
Apache能用日志包(Commons Logging Package)是Apache的一个开放源代码项目,它提供了一组通用的日志接口, 用户可以自由地选择实现日志接口的第三方软件。通用日志包目前支持以下日志实现:
|
- log4J日志器(http://jakarta.apache.org/log4j)
-
JDK1.4 Logging日志器(JDK1.4自带)
-
SimpleLog日志器(把日志消息输出到标准系统错误流System.err)
-
NoOpLog(不输出任何日志信息)
通用日志包中的两个常用接口:LogFactory和Log,分别介绍如下:
-
Log接口
通用日志包把消息分为6个级别:FATAL、ERROR、WARN、INFO、DEBUG和TRACE。其中FATAL级别最高, TRACE级别最低。 Log接口提供输出不同级别消息的方法:
off---------------------------------最高等级的,用于关闭所有日志记录
fatal(Object message)-------输出FATAL级别的消息。
error(Object message)-------输出ERROR级别的消息。
warn(Object message)-------输出WARN级别的消息。
info(Object message)-------输出INFO级别的消息。
debug(Object message)-------输出DEBUG级别的消息。
trace(Object message)-------输出TRACE级别的消息。
all----------------------------------最低等级的,用于打开所有日志记录
注:只有当输出日志的级别大于或等于为日志配置器配置的日志级别时,这个方法才会执行。
如何指定日志器的日志级别,不同的日志器实现会有不同的实现方案。
LogFactory接口提供了获得日志器实例的两个静态方法:
public static Log getLog(String name) throws LogConfigurationException;
public static Log getLog(Class class) throws LogConfigurationException;
注:name参数作为日志器的名字;class参数指定类名作为日志器名字。
log4j简介 几乎每个大的应用程序都有它自己的日志和跟踪程序的API。顺应这一规则,E.U. SEMPER项目组决定 编写它自己的程序跟踪API(tracing API)。这开始于1996年早期。经过无数的工作,更改和性能加强,这 个API终于成为一个十分受欢迎的Java日志软件包,那就是log4j。这个软件包的发行遵守open source动议认 证的Apache Software License。最新的log4j版本包括全部的源代码,类文件和文档资料, 可以在 http://logging.apache.org/log4j/找到它们。另外,log4j已经被转换成 C, C++, C#, Perl, Python, Ruby, 和 Eiffel 语言。 Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、 文件、GUI组件、甚至是套接口服务 器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条 日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣 的就 是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
log4j三个组件
通常,我们都提供一个名为 log4j.properties的文件,在第一次调用到Log4J时,Log4J会在类路径 (../web-inf/class/当然也可以放到其它任何目录,只要该目录被包含到类路径中即可)中定位这个文件,并读入 这个文件完成的配置。这个配置文件告 诉Log4J以什么样的格式、把什么样的信息、输出到什么地方。 Log4j有三个主要的组件:Loggers(记录器),Appenders(输出源)和Layouts(布局),这里可简单理解为日志 类别,日志要输出的地方和日志以何种形式输出。综合使用这三个组件可以轻松的记录信息的类型和级别,并可 以在运行时控制日志输出的样式和位置。下面对三个组件分别进行说明: 1、 Loggers Loggers组件在此系统中被分为五个级别:DEBUG、INFO、WARN、ERROR和FATAL。这五个级别是有顺序 的,DEBUG < INFO < WARN < ERROR < FATAL,分别用来指定这条日志信息的重要程度,明白这一点很重要, 这里Log4j有一个规则:假设Loggers级别为P,如果在Loggers中发生了一个级别Q比P高,则可以启动,否则屏蔽掉。 假设你定义的级别是info,那么error和warn的日志可以显示而比他低的debug信息就不显示了。 Java程序举例来说: //建立Logger的一个实例,命名为“com.foo” Logger logger = Logger.getLogger("com.foo"); //"com.foo"是实例进行命名,也可以任意 //设置logger的级别。通常不在程序中设置logger的级别。一般在配置文件中设置。 logger.setLevel(Level.INFO); Logger barlogger = Logger.getLogger("com.foo.Bar"); //下面这个请求可用,因为WARN >= INFO logger.warn("Low fuel level."); //下面这个请求不可用,因为DEBUG < INFO logger.debug("Starting search for nearest gas station."); //命名为“com.foo.bar”的实例barlogger会继承实例“com.foo”的级别。因此,下面这个请求可用,因为INFO >= INFO barlogger.info("Located nearest gas station."); //下面这个请求不可用,因为DEBUG < INFO barlogger.debug("Exiting gas station search"); 这里“是否可用”的意思是能否输出Logger信息。 在对Logger实例进行命名时,没有限制,可以取任意自己感兴趣的名字。一般情况下建议以类的所在位置来 命名Logger实例,这是目前来讲比较有效的Logger命名方式。这样可以使得每个类建立自己的日志信息,便于管理。 比如: static Logger logger = Logger.getLogger(ClientWithLog4j.class.getName()); 2、Appenders 禁用与使用日志请求只是Log4j其中的一个小小的地方,Log4j日志系统允许把日志输出到不同的地方, 如控制台(Console)、文件(Files)、根据天数或者文件大小产生新的文件、以流的形式发送到其它地方等等。 其语法表示为: org.apache.log4j.ConsoleAppender(控制台) org.apache.log4j.FileAppender(文件) org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件) org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件) org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) 配置时使用方式为: log4j.appender.appenderName = fully.qualified.name.of.appender.class log4j.appender.appenderName.option1 = value1 … log4j.appender.appenderName.option = valueN 这样就为日志的输出提供了相当大的便利。 3、Layouts 有时用户希望根据自己的喜好格式化自己的日志输出。Log4j可以在Appenders的后面附加Layouts来完成 这个功能。Layouts提供了 四种日志输出样式,如根据HTML样式、自由指定样式、包含日志级别与信息的样式 和包含日志时间、线程、类别等信息的样式等等。 其语法表示为: org.apache.log4j.HTMLLayout(以HTML表格形式布局), org.apache.log4j.PatternLayout(可以灵活地指定布局模式), org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串), org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息) 配置时使用方式为: log4j.appender.appenderName.layout =fully.qualified.name.of.layout.class log4j.appender.appenderName.layout.option1 = value1 … log4j.appender.appenderName.layout.option = valueN
log4j基本编程方法
以上是从原理方面说明Log4j的使用方法,在具体Java编程使用Log4j可以参照以下示例: 1、 建立Logger实例: 语法表示:public static Logger getLogger( String name) 实际使用:static Logger logger = Logger.getLogger(ServerWithLog4j.class.getName ()) ; 2、 读取配置文件: 获得了Logger的实例之后,接下来将配置Log4j使用环境: 语法表示: BasicConfigurator.configure():自动快速地使用缺省Log4j环境。 PropertyConfigurator.configure(String configFilename):读取使用Java的特性文件编写的配置文件。 DOMConfigurator.configure(String filename):读取XML形式的配置文件。 实际使用: PropertyConfigurator.configure("ServerWithLog4j.properties"); 3、 插入日志信息 完成了以上连个步骤以后,下面就可以按日志的不同级别插入到你要记录日志的任何地方了。 语法表示: Logger.debug(Object message);//调试信息 Logger.info(Object message);//一般信息 Logger.warn(Object message);//警告信息 Logger.error(Object message);//错误信息 Logger.fatal(Object message);//致命错误信息
实际使用:logger.info("ServerSocket before accept: " + server); log4j配置文件
在实际编程时,要使Log4j真正在系统中运行事先还要对配置文件进行定义。定义步骤就是对Logger、 Appender及Layout的分别使用。 Log4j支持两种配置文件格式,一种是XML格式的文件,一种是java properties(key=value) 【Java特性文件(键=值)】。下面我们介绍使用Java特性文件做为配置文件的方法 具体如下: 1、配置根Logger,其语法为: log4j.rootLogger = [ level ] , appenderName1, appenderName2, … level : 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定义的级别。 Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。通过在这里定义的级别, 您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定 义了INFO级别,则应用程序中所有DEBUG级 别的日志信息将不被打印出来。 appenderName:就是指定日志信息输出到哪个地方。您可以同时指定多个输出目的地。 例如:log4j.rootLogger=info,A1,B2,C3 2、配置日志信息输出目的地,其语法为: log4j.appender.appenderName = fully.qualified.name.of.appender.class // "fully.qualified.name.of.appender.class" 可以指定下面五个目的地中的一个: 1.org.apache.log4j.ConsoleAppender(控制台) 2.org.apache.log4j.FileAppender(文件) 3.org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件) 4.org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件) 5.org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) 1.ConsoleAppender选项 Threshold=WARN:指定日志消息的输出最低层次。 ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。 Target=System.err:默认情况下是:System.out,指定输出控制台 2.FileAppender 选项 Threshold=WARN:指定日志消息的输出最低层次。 ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。 File=mylog.txt:指定消息输出到mylog.txt文件。 Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。 3.DailyRollingFileAppender 选项 Threshold=WARN:指定日志消息的输出最低层次。 ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。 File=mylog.txt:指定消息输出到mylog.txt文件。 Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。 DatePattern='.'yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、 天、时和分。即对应的格式如下: 1)'.'yyyy-MM: 每月 2)'.'yyyy-ww: 每周 3)'.'yyyy-MM-dd: 每天 4)'.'yyyy-MM-dd-a: 每天两次 5)'.'yyyy-MM-dd-HH: 每小时 6)'.'yyyy-MM-dd-HH-mm: 每分钟 4.RollingFileAppender 选项 Threshold=WARN:指定日志消息的输出最低层次。 ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。 File=mylog.txt:指定消息输出到mylog.txt文件。 Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。 MaxFileSize=100KB: 后缀可以是KB, MB 或者是 GB. 在日志文件到达该大小时,将会自动滚动,即将原来 的内容移到mylog.log.1文件。 MaxBackupIndex=2:指定可以产生的滚动文件的最大数。
实际应用: log4j.appender.A1=org.apache.log4j.ConsoleAppender //这里指定了日志输出的第一个位置A1是控制台ConsoleAppender
╭=========================================╮ 青山不改 绿水长流http://blog.csdn.net/wangyihust 欢迎各位转贴,但需声明版权,尊重技术原创性 :) E-mail:wangyihust@163.com OICQ:76406573 ╰=========================================╯ 3、配置日志信息的格式,其语法为: 1).log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class "fully.qualified.name.of.layout.class" 可以指定下面4个格式中的一个: 1.org.apache.log4j.HTMLLayout(以HTML表格形式布局), 2.org.apache.log4j.PatternLayout(可以灵活地指定布局模式), 3.org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串), 4.org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息) 1.HTMLLayout 选项 LocationInfo=true:默认值是false,输出java文件名称和行号 Title=my app file: 默认值是 Log4J Log Messages. 2.PatternLayout 选项 ConversionPattern=%m%n :指定怎样格式化指定的消息。 3.XMLLayout 选项 LocationInfo=true:默认值是false,输出java文件和行号 实际应用: log4j.appender.A1.layout=org.apache.log4j.PatternLayout
2). log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n 这里需要说明的就是日志信息格式中几个符号所代表的含义: -X号: X信息输出时左对齐; %p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL, %d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式, 比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921 %r: 输出自应用启动到输出该log信息耗费的毫秒数 %c: 输出日志信息所属的类目,通常就是所在类的全名 %t: 输出产生该日志事件的线程名 %l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码 中的行数。举例:Testlog4.main(TestLog4.java:10) %x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。 %%: 输出一个"%"字符 %F: 输出日志消息产生时所在的文件名称 %L: 输出代码中的行号 %m: 输出代码中指定的消息,产生的日志具体信息 %n: 输出一个回车换行符,Windows平台为"\r\n",Unix平台为"\n"输出日志信息换行 可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如: 1)%20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。 2)%-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,"-"号指定左对齐。 3)%.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边 多出的字符截掉,但小于30的话也不会有空格。 4)%20.30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符, 就从左边交远销出的字符截掉。
这里上面三个步骤是对前面Log4j组件说明的一个简化;下面给出一个具体配置例子,在程序中可以参照执行: log4j.rootLogger=INFO,A1,B2 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n 根据上面的日志格式,某一个程序的输出结果如下: 0 INFO 2003-06-13 13:23:46968 ClientWithLog4j Client socket: Socket[addr=localhost/127.0.0.1,port=8002,localport=2014] 16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server says: 'Java server with log4j, Fri Jun 13 13:23:46 CST 2003' 16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j GOOD 16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Command 'HELLO' not understood.' 16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j HELP 16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Vocabulary: HELP QUIT' 16 DEBUG 2003-06-13 13:23:46984 ClientWithLog4j QUIT 4. 当输出信息于回滚文件时
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender //指定以文件的方式输出日志 log4j.appender.ROLLING_FILE.Threshold=ERROR log4j.appender.ROLLING_FILE.File=rolling.log //文件位置,也可以用变量、rolling.log log4j.appender.ROLLING_FILE.Append=true log4j.appender.ROLLING_FILE.MaxFileSize=10KB //文件最大尺寸 log4j.appender.ROLLING_FILE.MaxBackupIndex=1 //备份数 log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
5.Log4J对应用性能的影响
如果在程序运行中输出大量日志,显然会对应用的性能造成一定的影响。Log4J对性能的影响取决于以下因素:
-
日志输出目的地:输出到控制台的速度和输出到文件系统的速度是不一样的。
-
日志输出格式:格式简单,速度也更快。
-
日志级别:日志级别设置的越低,输出的日志内容越多,对性能的影响也越大。
log4j全能配置文件(转自gmmgood)
下面给出得Log4J配置文件实现了输出到控制台,文件,回滚文件,发送日志邮件, 输出到数据库日志表,自定义标签等全套功能。
log4j.rootLogger=DEBUG,CONSOLE,A1,im #DEBUG,CONSOLE,FILE,ROLLING_FILE,MAIL,DATABASE
log4j.addivity.org.apache=true
################### # Console Appender ################### log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.Threshold=DEBUG log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n #log4j.appender.CONSOLE.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD] n%c[CATEGORY]%n%m[MESSAGE]%n%n
##################### # File Appender ##################### log4j.appender.FILE=org.apache.log4j.FileAppender log4j.appender.FILE.File=file.log log4j.appender.FILE.Append=false log4j.appender.FILE.layout=org.apache.log4j.PatternLayout log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n # Use this layout for LogFactor 5 analysis
######################## # Rolling File ######################## log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender log4j.appender.ROLLING_FILE.Threshold=ERROR log4j.appender.ROLLING_FILE.File=rolling.log log4j.appender.ROLLING_FILE.Append=true log4j.appender.ROLLING_FILE.MaxFileSize=10KB log4j.appender.ROLLING_FILE.MaxBackupIndex=1 log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
#################### # Socket Appender #################### log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender log4j.appender.SOCKET.RemoteHost=localhost log4j.appender.SOCKET.Port=5001 log4j.appender.SOCKET.LocationInfo=true # Set up for Log Facter 5 log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout log4j.appender.SOCET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n% t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n
######################## # Log Factor 5 Appender ######################## log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000
######################## # SMTP Appender ####################### log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender log4j.appender.MAIL.Threshold=FATAL log4j.appender.MAIL.BufferSize=10 log4j.appender.MAIL.From=chenyl@hollycrm.com log4j.appender.MAIL.SMTPHost=mail.hollycrm.com log4j.appender.MAIL.Subject=Log4J Message log4j.appender.MAIL.To=chenyl@hollycrm.com log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout log4j.appender.MAIL.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
######################## # JDBC Appender ####################### log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/test log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver log4j.appender.DATABASE.user=root log4j.appender.DATABASE.password= log4j.appender.DATABASE.sql=INSERT INTO LOG4J (Message) VALUES ('[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n') log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout log4j.appender.DATABASE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender log4j.appender.A1.File=SampleMessages.log4j log4j.appender.A1.DatePattern=yyyyMMdd-HH'.log4j' log4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout
################### #自定义Appender ################### log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net log4j.appender.im.username = username log4j.appender.im.password = password log4j.appender.im.recipient = corlin@cybercorlin.net
log4j.appender.im.layout=org.apache.log4j.PatternLayout log4j.appender.im.layout.ConversionPattern =[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
|
使用XML配置文件
首先,看看下面的XML配置文件示例:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="ConsoleAppender" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.SimpleLayout"/> </appender> <root> <priority value ="debug" /> <appender-ref ref="ConsoleAppender"/> </root> </log4j:configuration>
文件以标准的XML声明作为开始,后面跟着指出DTD(文档类型定义)的DOCTYPE声明,它定义了XML文件的结构, 例如,什么元素可以嵌入在其他元素中等等。上面文件在log4j发行版的src/java/org/apache/log4j/xml目录中。 接着看看封装所有元素的 log4j:configuration 元素,它在DOCTYPE声明中被指定为根元素。嵌入在根元素中有两个结构:
<appender name="ConsoleAppender" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.SimpleLayout"/> </appender>
这里创建一个名叫"ConsoleAppender"的 Appender,注意,你可以选择任何名字,该示例之所以选择"ConsoleAppender", 完全是为了示例的设计。接着这个appender类以全名形式给出,经常用规范(fully qualified)类名。 Appender必须具有 一个指定的 name和class。嵌入在 Appender之内的是 layout元素,这里它被指定为SimpleLayout。 Layout 必须具 有一个 class属性。
<root> <priority value ="debug" /> <appender-ref ref="ConsoleAppender"/> </root>
root元素必须存在且不能被子类化。示例中的优先级被设置为"debug",设置appender饱含一个appender-ref元素。 还有更多的属性或元素可以指定。查看log4j发行版中的src/java/org/apache/log4j/xml/log4j.dtd以了解关于XML配置 文件结构的更多信息。可以用下面这种方法把配置信息文件读入到Java程序中:
DOMConfigurator.configure("configurationfile.xml");
DOMConfigurator 用一棵DOM树来初始化log4j环境。这里是示例中的XML配置文件:configurationfile.xml。这里是 执行该配置文件的java程序:
import org.apache.log4j.Logger; import org.apache.log4j.xml.DOMConfigurator; public class externalxmltest { static Logger logger = Logger.getLogger(filetest.class); public static void main(String args[]) { DOMConfigurator.configure("xmllog4jconfig.xml"); logger.debug("Here is some DEBUG"); logger.info("Here is some INFO"); logger.warn("Here is some WARN"); logger.error("Here is some ERROR"); logger.fatal("Here is some FATAL"); } }
对于带有PatternLayout的FileAppender的日志记录器Logger的XML配置文件:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="appender" class="org.apache.log4j.FileAppender"> <param name="File" value="Indentify-Log.txt"/> <param name="Append" value="true"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %p - %m%n"/> </layout> </appender> <root> <priority value ="info"/> <appender-ref ref="appender"/> </root> </log4j:configuration>
log4j日志写入数据库
首先创建一数据库表:
字段
|
描述
|
GUID
|
流水号 IDENTITY (1, 1)
|
DATE
|
时间
|
THREAD
|
当前线程
|
LEVEL
|
当前级别
|
CLASS
|
当前类的java程序/方法
|
MESSAGES
|
当前输出信息
|
EXCEPTION
|
异常信息
|
log4j.properties如下配置:
log4j.rootLogger=DEBUG,CONSOLE,DATABASE
log4j.addivity.org.apache=true
########################
# JDBC Appender
#######################
log4j.appender.DATABASE.Threshold=INFO
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
#log4j.appender.DATABASE.BufferSize=10
log4j.appender.DATABASE.URL=you jdbcurl
log4j.appender.DATABASE.driver=you jdbc driver
log4j.appender.DATABASE.user=
log4j.appender.DATABASE.password=
log4j.appender.DATABASE.sql=INSERT INTO YOU_LOG_TABLE VALUES ('%d{yyyy-MM-dd HH:mm:ss}', '%t', '%p', '%l', '%m', '')
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
下面我们对“%d %t %p %l %m %n”一一讲解:
l %d输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式, 比如:%d{yyyy-MM-dd HH:mm:ss},输出类似:2006-01-18 17:50:22',刚好适合插入SQLServer;
l %t 产生该日志事件的线程名;
l %p 日志的log_level,如DEBUG、WARN或者INFO;
l %c 输出所属的类目,通常就是所在类的全名,如“com.eking.TestLog”;
l %m 日志的内容;
l %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。 如Wite2Database.main(Wite2Database.java:18);
l %n 输出一个回车换行符,Windows平台为“ ”,Unix平台为“ ”
程序代码:
import sun.jdbc.odbc.*; import java.sql.*;
import org.apache.log4j.Category; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.jdbc.*;
public class Write2Database{ public static void main(String[] args){ static Logger logger = Logger.getLogger ( write2database.class.getName () ) ;
PropertyConfigurator.configure ( "log4j2database.properties" ) ;
logger.info ( "test" ) ; } }
运行起来,你就会看到有这样的sql语句被执行了: INSERT INTO jws_log VALUES ('2006-01-18 17:50:22', 'main', 'INFO', 'Wite2Database.main(Write2Database.java:18)', 'test', '')
注意
:在赛迪论坛上有一个笔者按照上述类似的方法没有运行成功,现将他所出现的问题和解决方法转载。
上述方法是利用传统的数据库连接方法,对于数据库的管理和效率严重不足,在现在这个连接池横行的时代, 为什么我们不能给给Log4j配上连接池,让Log4j利用数据连接池的连接和数据库进行通讯。 现查看Log4j的Api,发现JDBCAppender这个类有以下几段话:WARNING: This version of JDBCAppender is very likely to be completely replaced in the future. Moreoever, it does not log exceptions. The JDBCAppender provides for sending log events to a database.
For use as a base class:
-
Override
getConnection() to pass any connection you want. Typically this is used to enable application wide connection pooling.
-
Override
closeConnection(Connection con) -- if you override getConnection make sure to implement closeConnection to handle the connection you generated. Typically this would return the connection to the pool it came from.
-
Override
getLogStatement(LoggingEvent event) to produce specialized or dynamic statements. The default uses the sql option value.
原来log4j建议我们把其提供的JDBCAppender作为基类来使用,然后Override三个父类的方法:getConnection(), closeConnection(Connection con)和getLogStatement(LoggingEvent event)。
原来如此,那就写一个子类JDBCPoolAppender来替代这个JDBCAppender JDBCPoolAppender代码和其相关代码如下: JDBCPoolAppender.java: package common.log; import java.sql.Connection; import org.apache.log4j.spi.LoggingEvent; import java.sql.SQLException; import java.sql.Statement; import java.util.Iterator; import org.apache.log4j.spi.ErrorCode; import org.apache.log4j.PatternLayout; import common.sql.MyDB; import common.sql.GeneralDb; public class JDBCPoolAppender extends org.apache.log4j.jdbc.JDBCAppender { private MyDB mydb = null; protected String sqlname=""; //增加一个数据库jndiName的属性 protected Connection connection = null; protected String sqlStatement = ""; /** * size of LoggingEvent buffer before writting to the database. * Default is 1. */ protected int bufferSize = 1; public JDBCPoolAppender() { super(); } /** * ArrayList holding the buffer of Logging Events. */ public void append(LoggingEvent event) { buffer.add(event); if (buffer.size() >= bufferSize) flushBuffer(); } /** * By default getLogStatement sends the event to the required Layout object. * The layout will format the given pattern into a workable SQL string. * * Overriding this provides direct access to the LoggingEvent * when constructing the logging statement. * */ protected String getLogStatement(LoggingEvent event) { return getLayout().format(event); } /** * * Override this to provide an alertnate method of getting * connections (such as caching). One method to fix this is to open * connections at the start of flushBuffer() and close them at the * end. I use a connection pool outside of JDBCAppender which is * accessed in an override of this method. * */ protected void execute(String sql) throws SQLException { Connection con = null; Statement stmt = null; try { con = getConnection(); stmt = con.createStatement(); stmt.executeUpdate(sql); } catch (SQLException e) { if (stmt != null) stmt.close(); throw e; } stmt.close(); closeConnection(con); //System.out.println("Execute: " + sql); } /** * Override this to return the connection to a pool, or to clean up the * resource. * * The default behavior holds a single connection open until the appender * is closed (typically when garbage collected). */ protected void closeConnection(Connection con) { mydb=null; try { if (connection != null && !connection.isClosed()) connection.close(); } catch (SQLException e) { errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE); } } /** * Override 此函数来利用连接池返回一个Connetion对象 * */ protected Connection getConnection() throws SQLException { try { mydb = GeneralDb.getInstance(sqlname); connection = mydb.getConnection(); } catch (Exception e) { errorHandler.error("Error opening connection", e, ErrorCode.GENERIC_FAILURE); } return connection; } /** * Closes the appender, flushing the buffer first then closing the default * connection if it is open. */ public void close() { flushBuffer(); try { if (connection != null && !connection.isClosed()) connection.close(); } catch (SQLException e) { errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE); } this.closed = true; } /** * loops through the buffer of LoggingEvents, gets a * sql string from getLogStatement() and sends it to execute(). * Errors are sent to the errorHandler. * * If a statement fails the LoggingEvent stays in the buffer! */ public void flushBuffer() { //Do the actual logging removes.ensureCapacity(buffer.size()); for (Iterator i = buffer.iterator(); i.hasNext(); ) { try { LoggingEvent logEvent = (LoggingEvent) i.next(); String sql = getLogStatement(logEvent); execute(sql); removes.add(logEvent); } catch (SQLException e) { errorHandler.error("Failed to excute sql", e, ErrorCode.FLUSH_FAILURE); } } // remove from the buffer any events that were reported buffer.removeAll(removes); // clear the buffer of reported events removes.clear(); } /** closes the appender before disposal */ public void finalize() { close(); } /** * JDBCAppender requires a layout. * */ public boolean requiresLayout() { return true; } /** * */ public void setSql(String s) { sqlStatement = s; if (getLayout() == null) { this.setLayout(new PatternLayout(s)); } else { ((PatternLayout) getLayout()).setConversionPattern(s); } } /** * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m") */ public String getSql() { return sqlStatement; } public void setSqlname(String sqlname){ sqlname=sqlname; } public String getSqlname(){ return sqlname; } public void setBufferSize(int newBufferSize) { bufferSize = newBufferSize; buffer.ensureCapacity(bufferSize); removes.ensureCapacity(bufferSize); } public int getBufferSize() { return bufferSize; } } MyDB.java: package common.sql; import java.sql.*; import com.codestudio.sql.*; //引入开源项目Poolman数据库连接池的包 public class MyDB { public static final String module = MyDB.class.getName(); private String dbName = ""; private PoolMan plmn = null; public MyDB(String dbName) { try { if (plmn == null) { plmn = (PoolMan) Class.forName("com.codestudio.sql.PoolMan"). newInstance(); } } catch (Exception ec) { System.out.println(ec.toString()+module); } this.dbName = dbName; } private Connection getNewConnection() { Connection conn = null; try { conn = plmn.connect("jdbc:poolman://" + dbName); conn.setAutoCommit(true); } catch (Exception ec) { System.out.println(ec.toString()+"First:Connect sqlsever failed"+module); try { Thread.sleep(1000); conn = plmn.connect("jdbc:poolman://" + dbName); conn.setAutoCommit(true); } catch (Exception ecs) { System.out.println(ecs.toString()+"Again:Connect sqlsever faile"+module); } } return conn; } public Connection getConnection() { return getNewConnection(); } } GeneralDb.java: package common.sql; package common.sql; import java.util.*; public class GeneralDb { private static Hashtable dbPool; public static MyDB getInstance(String dbname) { if (dbPool == null) { dbPool = new Hashtable(); } MyDB db = (MyDB) dbPool.get(dbname); if (db == null) { db = new MyDB(dbname); dbPool.put(dbname, db); } return db; } } Log4j数据库连接池的配置如下: log4j.appender.JDBC=common.log.JDBCPoolAppender log4j.appender.JDBC.sqlname=log log4j.appender.JDBC.layout=org.apache.log4j.PatternLayout log4j.appender.JDBC.sql=INSERT INTO LOGGING (log_date, log_level, location, message) VALUES ('%d{ISO8601}', '%-5p', '%C,%L', '%m') poolman.xml配置如下: 〈?xml version="1.0" encoding="UTF-8"?> 〈poolman> 〈management-mode>local〈/management-mode> 〈datasource> 〈dbname>log〈/dbname> 〈jndiName>log〈/jndiName> 〈driver>com.mysql.jdbc.Driver〈/driver> 〈url>jdbc:mysql://localhost:3306/test〈/url> 〈username>use〈/username> 〈password>password〈/password> 〈minimumSize>0〈/minimumSize> 〈maximumSize>10〈/maximumSize> 〈logFile>logs/mysql.log〈/logFile> 〈/datasource> 〈/poolman>
运行成功!对于JDBCPoolAppender的属性(比如sqlname属性)我们可以利用Log4j的反射机 制随便添加,只要在配置文件给其附上值即可应用,而原来的父类里面的一些属性(username 什么的)和其get,set方法由于在连接池中不需要,所以删除。而在JDBCPoolAppender类中,我也 只是将getConnection 方法Override ,在这个方法中我们可以根据需要生成我们的Connection 对象,另外两个方法大家可以根据需求来决定怎样Override。
Log4Net for .net framework
等待研究
用户可以从http://logging.apache.org/log4net/下载log4net的源代码。解压软件包后,在解压的src目录 下将log4net.sln载入Visual Studio .NET,编译后可以得到log4net.dll。用户要在自己的程序里加入日志功能, 只需将log4net.dll引入工程即可。
CSDN 推荐tag: api com hibernate ice java os sql 函数 软件
选自《精通Hibernate:Java对象持久化技术详解》 作者:孙卫琴 来源: www.javathinker.org 如果转载,请标明出处,谢谢 1.1 Hibernate API 变化 1.1.1 包名 1.1.2 org.hibernate.classic包 1.1.3 Hibernate所依赖的第三方软件包 1.1.4 异常模型 1.1.5 Session接口 1.1.6 createSQLQuery() 1.1.7 Lifecycle 和 Validatable 接口 1.1.8 Interceptor接口 1.1.9 UserType和CompositeUserType接口 1.1.10 FetchMode类 1.1.11 PersistentEnum类 1.1.12 对Blob 和Clob的支持 1.1.13 Hibernate中供扩展的API的变化 1.2 元数据的变化 1.2.1 检索策略 1.2.2 对象标识符的映射 1.2.3 集合映射 1.2.4 DTD 1.3 查询语句的变化 1.3.1 indices()和elements()函数 尽管Hibernate 3.0 与Hibernate2.1的源代码是不兼容的,但是当Hibernate开发小组在设计Hibernate3.0时,为简化升级Hibernate版本作了周到的考虑。对于现有的基于Hibernate2.1的Java项目,可以很方便的把它升级到Hibernate3.0。 本文描述了Hibernate3.0版本的新变化,Hibernate3.0版本的变化包括三个方面: (1)API的变化,它将影响到Java程序代码。 (2)元数据,它将影响到对象-关系映射文件。 (3)HQL查询语句。 值得注意的是, Hibernate3.0并不会完全取代Hibernate2.1。在同一个应用程序中,允许Hibernate3.0和Hibernate2.1并存。 1.1 Hibernate API 变化 1.1.1 包名 Hibernate3.0的包的根路径为: “org.hibernate” ,而在Hibernate2.1中为“net.sf.hibernate”。这一命名变化使得Hibernate2.1和Hibernate3.0能够同时在同一个应用程序中运行。 如果希望把已有的应用升级到Hibernate3.0,那么升级的第一步是把Java源程序中的所有“net.sf.hibernate”替换为“org.hibernate”。 Hibernate2.1中的“net.sf.hibernate.expression”包被改名为“org.hibernate.criterion”。假如应用程序使用了Criteria API,那么在升级的过程中,必须把Java源程序中的所有“net.sf.hibernate.expression”替换为“org.hibernate.criterion”。 如果应用使用了除Hibernate以外的其他外部软件,而这个外部软件又引用了Hibernate的接口,那么在升级时必须十分小心。例如EHCache拥有自己的CacheProvider: net.sf.ehcache.hibernate.Provider,在这个类中引用了Hibernate2.1中的接口,在升级应用时,可以采用以下办法之一来升级EHCache: (1)手工修改net.sf.ehcache.hibernate.Provider类,使它引用Hibernate3.0中的接口。 (2)等到EHCache软件本身升级为使用Hibernate3.0后,使用新的EHCache软件。 (3)使用Hibernate3.0中内置的CacheProvider:org.hibernate.cache.EhCacheProvider。 1.1.2 org.hibernate.classic包 Hibernate3.0把一些被废弃的接口都转移到org.hibernate.classic中。 1.1.3 Hibernate所依赖的第三方软件包 在Hibernate3.0的软件包的lib目录下的README.txt文件中,描述了Hibernate3.0所依赖的第三方软件包的变化。 1.1.4 异常模型 在Hibernate3.0中,HibernateException异常以及它的所有子类都继承了java.lang.RuntimeException。因此在编译时,编译器不会再检查HibernateException。 1.1.5 Session接口 在Hibernate3.0中,原来Hibernate2.1的Session接口中的有些基本方法也被废弃,但为了简化升级,这些方法依然是可用的,可以通过org.hibernate.classic.Session子接口来访问它们,例如: org.hibernate.classic.Session session=sessionFactory.openSession(); session.delete("delete from Customer "); 在Hibernate3.0中,org.hibernate.classic.Session接口继承了org.hibernate.Session接口,在org.hibernate.classic.Session接口中包含了一系列被废弃的方法,如find()、interate()等。SessionFactory接口的openSession()方法返回org.hibernate.classic.Session类型的实例。如果希望在程序中完全使用Hibernate3.0,可以采用以下方式创建Session实例: org.hibernate.Session session=sessionFactory.openSession(); 如果是对已有的程序进行简单的升级,并且希望仍然调用Hibernate2.1中Session的一些接口,可以采用以下方式创建Session实例: org.hibernate.classic.Session session=sessionFactory.openSession(); 在Hibernate3.0中,Session接口中被废弃的方法包括: * 执行查询的方法:find()、iterate()、filter()和delete(String hqlSelectQuery) * saveOrUpdateCopy() Hibernate3.0一律采用createQuery()方法来执行所有的查询语句,采用DELETE 查询语句来执行批量删除,采用merge()方法来替代 saveOrUpdateCopy()方法。 提示:在Hibernate2.1中,Session的delete()方法有几种重载形式,其中参数为HQL查询语句的delete()方法在Hibernate3.0中被废弃,而参数为Ojbect类型的的delete()方法依然被支持。delete(Object o)方法用于删除参数指定的对象,该方法支持级联删除。 Hibernate2.1没有对批量更新和批量删除提供很好的支持,参见<<精通Hibernate>>一书的第13章的13.1.1节(批量更新和批量删除),而Hibernate3.0对批量更新和批量删除提供了支持,能够直接执行批量更新或批量删除语句,无需把被更新或删除的对象先加载到内存中。以下是通过Hibernate3.0执行批量更新的程序代码: Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlUpdate = "update Customer set name = :newName where name = :oldName"; int updatedEntities = s.createQuery( hqlUpdate ) .setString( "newName", newName ) .setString( "oldName", oldName ) .executeUpdate(); tx.commit(); session.close(); 以下是通过Hibernate3.0执行批量删除的程序代码: Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlDelete = "delete Customer where name = :oldName"; int deletedEntities = s.createQuery( hqlDelete ) .setString( "oldName", oldName ) .executeUpdate(); tx.commit(); session.close(); 1.1.6 createSQLQuery() 在Hibernate3.0中,Session接口的createSQLQuery()方法被废弃,被移到org.hibernate.classic.Session接口中。Hibernate3.0采用新的SQLQuery接口来完成相同的功能。 1.1.7 Lifecycle 和 Validatable 接口 Lifecycle和Validatable 接口被废弃,并且被移到org.hibernate.classic包中。 1.1.8 Interceptor接口 在Interceptor 接口中加入了两个新的方法。 用户创建的Interceptor实现类在升级的过程中,需要为这两个新方法提供方法体为空的实现。此外,instantiate()方法的参数作了修改,isUnsaved()方法被改名为isTransient()。 1.1.9 UserType和CompositeUserType接口 在UserType和CompositeUserType接口中都加入了一些新的方法,这两个接口被移到org.hibernate.usertype包中,用户定义的UserType和CompositeUserType实现类必须实现这些新方法。 Hibernate3.0提供了ParameterizedType接口,用于更好的重用用户自定义的类型。 1.1.10 FetchMode类 FetchMode.LAZY 和 FetchMode.EAGER被废弃。取而代之的分别为FetchMode.SELECT 和FetchMode.JOIN。 1.1.11 PersistentEnum类 PersistentEnum被废弃并删除。已经存在的应用应该采用UserType来处理枚举类型。 1.1.12 对Blob 和Clob的支持 Hibernate对Blob和Clob实例进行了包装,使得那些拥有Blob或Clob类型的属性的类的实例可以被游离、序列化或反序列化,以及传递到merge()方法中。 1.1.13 Hibernate中供扩展的API的变化 org.hibernate.criterion、 org.hibernate.mapping、 org.hibernate.persister和org.hibernate.collection 包的结构和实现发生了重大的变化。多数基于Hibernate 2.1 的应用不依赖于这些包,因此不会被影响。如果你的应用扩展了这些包中的类,那么必须非常小心的对受影响的程序代码进行升级。 1.2 元数据的变化 1.2.1 检索策略 在Hibernate2.1中,lazy属性的默认值为“false”,而在Hibernate3.0中,lazy属性的默认值为“true”。在升级映射文件时,如果原来的映射文件中的有关元素,如、等没有显式设置lazy属性,那么必须把它们都显式的设置为lazy=“true”。如果觉得这种升级方式很麻烦,可以采取另一简单的升级方式:在元素中设置: default-lazy=“false”。 1.2.2 对象标识符的映射 unsaved-value属性是可选的,在多数情况下,Hibernate3.0将把unsaved-value="0" 作为默认值。 在Hibernate3.0中,当使用自然主键和游离对象时,不再强迫实现Interceptor.isUnsaved()方法。 如果没有设置这个方法,当Hibernate3.0无法区分对象的状态时,会查询数据库,来判断这个对象到底是临时对象,还是游离对象。不过,显式的使用Interceptor.isUnsaved()方法会获得更好的性能,因为这可以减少Hibernate直接访问数据库的次数。 1.2.3 集合映射 元素在某些情况下被和元素替代。此外,Hibernate3.0用 元素来替代原来的.元素,用元素来替代原来的元素。 1.2.4 DTD 对象-关系映射文件中的DTD文档,由原来的: http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd 改为: http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd 1.3 查询语句的变化 Hibernate3.0 采用新的基于ANTLR的HQL/SQL查询翻译器,不过,Hibernate2.1的查询翻译器也依然存在。在Hibernate的配置文件中,hibernate.query.factory_class属性用来选择查询翻译器。例如: (1)选择Hibernate3.0的查询翻译器: hibernate.query.factory_class= org.hibernate.hql.ast.ASTQueryTranslatorFactory (2)选择Hibernate2.1的查询翻译器 hibernate.query.factory_class= org.hibernate.hql.classic.ClassicQueryTranslatorFactory 提示:ANTLR是用纯Java语言编写出来的一个编译工具,它可生成Java语言或者是C++的词法和语法分析器,并可产生语法分析树并对该树进行遍历。ANTLR由于是纯Java的,因此可以安装在任意平台上,但是需要JDK的支持。 Hibernate开发小组尽力保证Hibernate3.0的查询翻译器能够支持Hibernate2.1的所有查询语句。不过,对于许多已经存在的应用,在升级过程中,也不妨仍然使用Hibernate2.1的查询翻译器。 值得注意的是, Hibernate3.0的查询翻译器存在一个Bug:不支持某些theta-style连结查询方言:如Oracle8i的OracleDialect方言、Sybase11Dialect。解决这一问题的办法有两种:(1)改为使用支持ANSI-style连结查询的方言,如 Oracle9Dialect,(2)如果升级的时候遇到这一问题,那么还是改为使用Hibernate2.1的查询翻译器。 1.3.1 indices()和elements()函数 在HQL的select子句中废弃了indices()和elements()函数,因为这两个函数的语法很让用户费解,可以用显式的连接查询语句来替代 select elements(...) 。而在HQL的where子句中,仍然可以使用elements()函数。 Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=623674
2006/5/16
hibernate中session.delete(sql),3.1与2.0的区别,导致的错误
今天使用session.delete(sql);报下面的错误:
org.hibernate.MappingException: Unknown entity: java.lang.String at org.hibernate.impl.SessionFactoryImpl.getEntityPersister(SessionFactoryImpl.java:569) at org.hibernate.impl.SessionImpl.getEntityPersister(SessionImpl.java:1086) at org.hibernate.event.def.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:63) at org.hibernate.impl.SessionImpl.delete(SessionImpl.java:579)
查查终于知道原因:
session.delete(sql)是2.0的接口,3.1中已经废弃,同时废弃的有: find()、iterate()、filter()和delete(String hqlSelectQuery),saveOrUpdateCopy()
如果要使用的话,可以采用以下方式创建Session实例:
org.hibernate.classic.Session session=sessionFactory.openSession();
org.hibernate.classic.Session保留了2.0的接口
下面是3.1的reference里面这样写的,我使用了,但是还是报错,郁闷:
1) Hibernate3.0执行批量删除的程序代码: Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlDelete = "delete Customer where name = :oldName"; int deletedEntities = s.createQuery( hqlDelete ) .setString( "oldName", oldName ) .executeUpdate(); tx.commit(); session.close();
2)Hibernate3.0执行批量更新的程序代码: Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlUpdate = "update Customer set name = :newName where name = :oldName"; int updatedEntities = s.createQuery( hqlUpdate ) .setString( "newName", newName ) .setString( "oldName", oldName ) .executeUpdate(); tx.commit(); session.close();
我按照上面的写法,运行后报错如下:
org.hibernate.QueryException: query must begin with SELECT or FROM: delete [delete Resource r where r.nodeId=:nodeId and r.devId is not null ] at org.hibernate.hql.classic.ClauseParser.token(ClauseParser.java:83) at org.hibernate.hql.classic.PreprocessingParser.token(PreprocessingParser.java:108) at org.hibernate.hql.classic.ParserHelper.parse(ParserHelper.java:28) at org.hibernate.hql.classic.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:176) at org.hibernate.hql.classic.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:152) at org.hibernate.impl.SessionFactoryImpl.getQuery(SessionFactoryImpl.java:427) at org.hibernate.impl.SessionImpl.getQueries(SessionImpl.java:884) at org.hibernate.impl.SessionImpl.executeUpdate(SessionImpl.java:865) at org.hibernate.impl.QueryImpl.executeUpdate(QueryImpl.java:89)
昨天的问题解决了
又把hibernate3的手册仔细的看了一遍,确认自己的写法是没错的,然后又上hibernate的论坛,几乎没有人有我的这个问题,这才想到是不是同事在hibernate.hbm.xml中设置了什么参数,使得我不能使用3.0的特性呢?
看了,果真如此,郁闷哦~
里面有一句:
<property name="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</property>
这样就使用了2.0的接口.郁闷啊.
把这句去掉就OK了.
附:
因为使用了中文拼装的hql后,中文会变成问号,大致如下:
String hql="from user u where u.name like %"+userName+"%";
3.0版本是:
Query query = session.createQuery("from user u where u.name like :username").setString("username",userName); 现在要通过一个主键id去数据库中删除对应的一条记录,我该怎样做?如果是将参数delId传递进来的话,那该怎么处理?
我这样做的,但是报了错
public void delfun(String delId) throws DatastoreException {
Session session = HibernateUtil.getSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
Info info = (Info)session.load(Info.class,id); //出错行
session.delete(delId);
tx.commit();
}
catch(Exception ex) {
HibernateUtil.rollbackTransaction(tx);
throw DatastoreException.datastoreError(ex);
}
finally {
HibernateUtil.closeSession(session);
}
}
错误信息为:
com.framework.exceptions.DatastoreException.datastoreError(DatastoreException.java:28)
com.service.NpisServiceImpl.delGyinfo(NpisServiceImpl.java:131)
|
| |
| 回复人:pdvv(我爱花猫) | 2006-3-1 17:32:22 | 得分:0 | | | ? |
你给出的异常应该属于你自己框架的信息吧,没有多少参考价值。
使用Session.load()要确保数据一定存在,还是用get()吧;
或者直接session.delete("from INFO where ID = 1");
|
| Top | |
| 回复人:yuanjian0211(元剑) | 2006-3-1 17:46:04 | 得分:0 | | | ? |
错误提示信息为:
Hibernate: select gyinfo0_.id as id0_, info0_.userid as userid0_, info0_.tit
le as title0_,info0_.descript as descript0_ from Info info0_ where info0_.id=?
请指点
|
| Top | |
| 回复人:yuanjian0211(元剑) | 2006-3-1 17:56:29 | 得分:0 | | | ? |
id在数据库表中为bigint型
在对象中为long型
|
| Top | |
| 回复人:iori_powermax() | 2006-03-02 01:06:00 | 得分:0 | | | ? | 我也在学习hibernate,不知道说的对不对.
你删除的应该是整个对象,而不是一个id,所以应该为:
Info info = (Info)session.load(Info.class,id);
session.delete(info);
或者还有一种删除记录的方法:
info=(Info)sn.creatQuery("from Info as a where a.ID= '"+delId+"'").uniqueResult();
sn.delete(info);
另外想问一下大家:
hibernate的creatSQLQuery()是不是只支持查询?好象增删改都不可以
|
| Top | |
| 回复人:ymfhcn(这痞子真帅) | 2006-03-02 08:06:00 | 得分:0 | | | ? | creatSQLQuery()是执行sql语句的,不过稍微有些改动,里面什么SQL语句都可以写
|
| = | 一般分类 | 仙侠修真 | VBScript | .NET | 面试题 | 存储过程 | Hibernate | JavaScript | J2EE | Struts自我介绍 我选择,所以我自由!
切换风格 订阅我的Blog 博客日历 文章归档... 最新发表... 博客统计... 总文章数: 33 总评论数: 16 总引用数: 0 总浏览数: 2744
网站链接... 资源 =========================================================== Hibernate 的 session.delete(obj =========================================================== 作者: iceling2008(http://iceling2008.itpub.net) 发表于:2006.03.30 16:33 分类: Hibernate 出处:http://iceling2008.itpub.net/post/13270/64328 ---------------------------------------------------------------
session.delete(obj)将obj的状态变为transient。两种情况 1)obj是session的cache里边的cache没有的,比如: session.delete(new Employee(4)); 2)obj存在于session的cache中,比如: Employee employee = (Employee)session.load(Employee.class, new Integer(4)); session.delete(employee); 这两种情况都是允许的,hibernate都会发送一条delete语句给数据库。 delete执行之后,如果调用了session.load(), 又可以分为两种情况:1)在session.flush()之前,如: tx.beginTransaction(); session.delete(new Employee(4)); session.load(Employee.class, new Integer(4));//发生在session.flush()之前 tx.commit(); 那么hibernate会抛出ObjectDeletedException:The object with that id was deleted: 2)在session.flush()之后,如: tx.beginTransaction(); session.delete(new Employee(4)); session.load(Employee.class, new Integer(4)); tx.commit(); tx.beginTransaction(); session.load(Employee.class, new Integer(4));//同一个session中,上面的tx.commit()将session flush了一次。 tx.commit(); 那么这个时候hibernate仅仅会抛出ObjectNotFoundException:No row with the give... 表示找不到该object。如果第二个tx里边采用session.get()也就不会抛出exception了。 delete执行之后,如果调用了session.save(obj): tx.beginTransaction(); Employee employee = (Employee)session.load(Employee.class, new Integer(4)); session.delete(employee); System.out.println(employee); session.save(employee); System.out.println(employee); tx.commit(); 这种情况是完全合理的,合法的。 delete将employee从persistent的状态变为transient的状态。 save将employee从transient状态变为persistent的状态。save一个被delete的obj的时候,在save处hibernate强制执行session.flush(),发送delete语句,然后按照常规的save流程来进行。为什么要这么做,还没有完全想明白。
delete执行之后,如果对obj对象属性的修改,tx.commit()时不会进行dirtyChecking。 这个道理比较显然。 |
hibernate连接sqlserver2000问题的解决
这几天在进行数据库的移植,将oracle数据库的东西移植到mssqlserver 2000上
其中运行一个查询的时候,报如下的错误:
[Microsoft][SQLServer 2000 Driver for JDBC]Can't start a cloned connection while in manual transaction mode.
解决办法:
<property name="connection.url">
jdbc:microsoft:sqlserver://192.168.1.110:2433;SelectMethod=cursor
</property>
(红色为增加的)
原因:
1. 处于手动事务状态,并且使用direct模式,因为hibernate默认为direct模式,
即
SelectMethod=direct
2. 在一个SQL SERVER的JDBC连接上执行多个STATEMENTS的操作
超强的贴,每句都是经典
遇到 "Automation服务器不能创建对象"
运行 Regsvr32 scrrun.dll。
高级 DAO 编程
学习编译更好的 DAO 的技巧
|
|
级别: 初级
Sean C. Sullivan
, 软件工程师
2003 年 10 月 15 日
J2EE 开发人员使用数据访问对象(Data Access Object DAO)设计模式,以便将低级别的数据访问逻辑与高级别的业务逻辑分离。实现 DAO 模式涉及比编写数据访问代码更多的内容。在本文中,Java 开发人员 Sean C. Sullivan 讨论了 DAO 编程中三个常常被忽略的方面:事务界定、异常处理和日志记录。
在过去 18 个月中,我参加了一个由有才华的软件工程师组成的小组,构建定制的、基于 Web 的供应链管理应用程序。我们的应用程序访问范围广泛的持久性数据,包括配送状态、供应链衡量(metrics)、库存、货运发票、项目管理数据和用户信息。我们用 JDBC API 连接到我们公司的不同数据库平台上,并在整个应用程序中使用 DAO 设计模式。
图 1 显示了应用程序和数据源之间的关系:
图 1. 应用程序和数据源
在整个应用程序中使用数据访问对象(DAO)使我们可以将底层数据访问逻辑与业务逻辑分离开来。我们构建了为每一个数据源提供 GRUD (创建、读取、更新、删除)操作的 DAO 类。
在本文中,我将为您介绍构建更好的 DAO 类的 DAO 实现策略和技术。更确切地说,我将讨论日志、异常处理和事务界定。您将学到如何将这三者结合到自己的 DAO 类中。本文假定您熟悉 JDBC API、SQL 和关系数据库编程。
我们将以对 DAO 设计模式和数据访问对象的概述开始。
DAO基础
DAO 模式是标准 J2EE 设计模式之一。开发人员用这种模式将底层数据访问操作与高层业务逻辑分离开。一个典型的 DAO 实现有以下组件:
- 一个 DAO 工厂类
- 一个 DAO 接口
- 一个实现了 DAO 接口的具体类
- 数据传输对象(有时称为值对象)
具体的 DAO 类包含访问特定数据源的数据的逻辑。在下面一节中您将学习设计和实现数据访问对象的技术。有关 DAO 设计模式的更多内容请参阅 参考资料。
事务界定
关于 DAO 要记住的重要一点是它们是事务性对象。由 DAO 所执行的每一个操作 -- 如创建、更新或者删除数据 -- 都与一个事务相关联。因此, 事务界定的概念就变得特别重要了。
事务界定是定义事务边界的方式。J2EE 规范描述了两种事务界定的模型:编程式(programmatic)和声明式(declarative)。表 1 分析了这两种模型:
表 1. 两种事务界定的模型
声明式事务界定
|
编程式事务界定
|
程序员用 EJB 部署描述符声明事务属性。 |
程序员负责编写事务逻辑。 |
运行时环境(EJB 容器)用这些属性自动管理事务。 |
应用程序通过一个 API 控制事务。 |
我们将侧重于编程式事务界定。
设计考虑
如前所述,DAO 是事务性对象。一个典型的 DAO 执行像创建、更新和删除这样的事务性操作。在设计 DAO 时,首先要问自己以下问题:
- 事务要如何开始?
- 事务应如何结束?
- 哪一个对象将负责开始一个事务?
- 哪一个对象将负责结束一个事务?
- DAO 是否要负责事务的开始和结束?
- 应用程序是否需要通过多个 DAO 访问数据?
- 事务涉及到一个 DAO 还是多个 DAO?
- 一个 DAO 是否调用另一个 DAO 的方法?
了解上述问题的答案将有助于您选择最适合的 DAO 的事务界定策略。在 DAO 中有两种主要的界定事务的策略。一种方式是让 DAO 负责界定事务,另一种将事务界定交给调用这个 DAO 方法的对象处理。如果选择了前一种方式,那么就将事务代码嵌入到 DAO 中。如果选择后一种方式,那么事务界定代码就是在 DAO 类外面。我们将使用简单的代码示例帮助您更好理解每一种方式是如何工作的。
清单 1 显示了一个有两种数据操作的 DAO:创建和更新:
清单 1. DAO 方法
public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);
|
清单 2 显示了一个简单的事务。事务界定在 DAO 类外面。注意在这个例子中调用者是如何在一个事务中结合多个 DAO 操作的。
清单 2. 调用者管理的事务
tx.begin(); // start the transaction
dao.createWarehouseProfile(profile);
dao.updateWarehouseStatus(id1, status1);
dao.updateWarehouseStatus(id2, status2);
tx.commit(); // end the transaction
|
这种事务界定策略对于需要在一个事务中访问多个 DAO 的应用程序特别有用。
可以用 JDBC API 或者 Java 事务 API(Java Transaction API JTA)实现事务界定。 JDBC 事务界定比 JTA 事务界定要简单,但是 JTA 提供了更多的灵活性。在下面一节中我将更深入地分析事务界定的机制。
用 JDBC 进行事务界定
JDBC 事务是用 Connection 对象控制的。JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交和手工提交。 java.sql.Connection 提供了以下控制事务的方法:
-
public void setAutoCommit(boolean)
-
public boolean getAutoCommit()
-
public void commit()
-
public void rollback()
清单 3 显示了如何用 JDBC API 界定一个事务:
清单 3. 用 JDBC API 进行事务界定
import java.sql.*;
import javax.sql.*;
// ...
DataSource ds = obtainDataSource();
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
// ...
pstmt = conn.prepareStatement("UPDATE MOVIES ...");
pstmt.setString(1, "The Great Escape");
pstmt.executeUpdate();
// ...
conn.commit();
// ...
|
使用 JDBC 事务界定时,您可以将多个 SQL 语句结合到一个事务中。JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。在下面,我们将看一下如何用 JTA 进行事务界定。因为 JTA 不像 JDBC 那样有名,所以我们首先做一个简介。
JTA简介
Java 事务 API(JTA) 及其同门兄弟 Java 事务服务(Java Transaction Service JTS)为 J2EE 平台提供了分布式事务服务。一个 分布式的事务涉及一个事务管理器和一个或者多个资源管理器。一个 资源管理器是任何类型的持久性的数据存储。事务管理器负责协调所有事务参与者之间的通信。事务管理器与资源管理器之间的关系如图 2 所示:
图 2. 一个事务管理器和资源管理器
JTA 事务比 JDBC 事务功能更强。JDBC 事务局限为一个数据库连接,而 JTA 事务可以有多个参与者。所有下列 Java 平台组件都可以参与 JTA 事务:
- JDBC 连接
- JDO
PersistenceManager 对象
- JMS 队列
- JMS 主题
- 企业 JavaBeans
- 符合 J2EE 连接体系结构(J2EE Connector Architecture)规范的资源适配器
使用 JTA 的事务界定
要用 JTA 进行事务界定,应用程序要调用 javax.transaction.UserTransaction 接口中的方法。清单 4 显示了对 UserTransaction 对象的典型 JNDI 查询:
清单 4. 一个对 UserTransaction 对象的 JDNI 查询
import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup("java:comp/UserTransaction");
UserTransaction utx = (UserTransaction) txObj;
|
当应用程序找到了 UserTransaction 对象后,就可以开始事务了,如清单 5 所示:
清单 5. 用 JTA 开始一个事务
utx.begin();
// ...
DataSource ds = obtainXADataSource();
Connection conn = ds.getConnection();
pstmt = conn.prepareStatement("UPDATE MOVIES ...");
pstmt.setString(1, "Spinal Tap");
pstmt.executeUpdate();
// ...
utx.commit();
// ...
|
当应用程序调用 commit() 时,事务管理器用一个两阶段的提交协议结束事务。
控制事务的 JTA 方法
javax.transaction.UserTransaction 接口提供了以下事务控制方法:
-
public void begin()
-
public void commit()
-
public void rollback()
-
public int getStatus()
-
public void setRollbackOnly()
-
public void setTransactionTimeout(int)
应用程序调用 begin() 开始事务。应用程序调用 commit() 或者 rollback() 结束事务。参阅 参考资料以了解更多关于用 JTA 进行事务管理的内容。
使用 JTA 和 JDBC
开发人员通常在 DAO 类中用 JDBC 进行底层数据操作。如果计划用 JTA 界定事务,那么就需要有一个实现 javax.sql.XADataSource 、 javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个 XADataSource 对象就是一个 XAConnection 对象的工厂。 XAConnection s 是参与 JTA 事务的 JDBC 连接。
您将需要用应用服务器的管理工具设置 XADataSource 。从应用服务器和 JDBC 驱动程序的文档中可以了解到相关的指导。
J2EE 应用程序用 JNDI 查询数据源。一旦应用程序找到了数据源对象,它就调用 javax.sql.DataSource.getConnection() 以获得到数据库的连接。
XA 连接与非 XA 连接不同。一定要记住 XA 连接参与了 JTA 事务。这意味着 XA 连接不支持 JDBC 的自动提交功能。同时,应用程序一定不要对 XA 连接调用 java.sql.Connection.commit() 或者 java.sql.Connection.rollback() 。相反,应用程序应该使用 UserTransaction.begin()、 UserTransaction.commit() 和 serTransaction.rollback() 。
选择最好的方式
我们讨论了如何用 JDBC 和 JTA 界定事务。每一种方式都有其优点,您需要决定哪一种最适合于您的应用程序。
在最近的许多项目中,我们小组是用 JDBC API 进事务界定来构建 DAO 类的。这些 DAO 类可以总结如下:
- 事务界定代码嵌入在 DAO 类中。
- DAO 类使用 JDBC API 进行事务界定。
- 调用者不能界定事务。
- 事务范围局限于单个 JDBC 连接。
JDBC 事务并不总是适合复杂的企业应用程序。如果您的事务要跨越多个 DAO 或者多个数据库,那么下列实现策略也许更合适:
- 事务用 JTA 界定。
- 事务界定代码从 DAO 中分离出来。
- 调用者负责界定事务。
- DAO 加入一个全局事务。
JDBC 方式由于其简单性而具有吸引力,JTA 方式提供了更大的灵活性。您所选择的实现将取决于应用程序的特定需求。
日志记录和 DAO
一个良好实现的 DAO 类将使用日志记录来捕捉有关其运行时行为的细节。您可以选择记录异常、配置信息、连接状态、JDBC 驱动程序元数据、或者查询参数。日志对于开发的所有阶段都很有用。我经常在开发时、测试时和生产中分析应用程序日志。
在本节,我将展示一个显示如何将 Jakarta Commons Logging 加入到 DAO 中的代码示例。在这之前,让我们回顾一下一些基本知识。
选择日志库
许多开发人员使用一种原始格式进行日志记录: System.out.println 和 System.err.println 。 Println 语句速度快且使用方便,但是它们没有提供全功能的日志记录系统所具有的功能。表 2 列出了 Java 平台的日志库:
表 2. Java 平台的日志库
日志库
|
开放源代码?
|
URL
|
java.util.logging |
不是 |
http://java.sun.com/j2se/ |
Jakarta Log4j |
是 |
http://jakarta.apache.org/log4j/ |
Jakarta Commons Logging |
是 |
http://jakarta.apache.org/commons/logging.html |
Jakarta Commons Logging 可以与 java.util.logging 或者 Jakarta Log4j 一同使用。Commons Logging 是一个日志抽象层,它隔离了应用程序与底层日志实现。使用 Commons Logging,您可以通过改变配置文件更换底层日志实现。Commons Logging 在 Jakarta Struts 1.1 和 Jakarta HttpClient 2.0 中使用。
一个日志记录示例
清单 7 显示了如何在 DAO 类中使用 Jakarta Commons Logging:
清单 7. DAO 类中的 Jakarta Commons Logging
import org.apache.commons.logging.*;
class DocumentDAOImpl implements DocumentDAO
{
static private final Log log = LogFactory.getLog(DocumentDAOImpl.class);
public void deleteDocument(String id)
{
// ...
log.debug("deleting document: " + id);
// ...
try
{
// ... data operations ...
}
catch (SomeException ex)
{
log.error("Unable to delete document", ex);
// ... handle the exception ...
}
}
}
|
日志记录是所有任务关键型应用程序的重要部分。如果在 DAO 中遇到故障,那么日志通常可以提供判断出错位置的最好信息。将日志加入到 DAO 可以保证您有机会进行调试和故障排除。
DAO 中的异常处理
我们讨论过了事务界定和日志,现在对于如何在数据访问对象上应用它们有了更深入的理解。我们的第三个和最后一个讨论议题是异常处理。遵从几个简单的异常处理指导可以使您的 DAO 更容易使用、更健壮及更易于维护。
在实现 DAO 模式时,考虑以下问题:
- DAO 的公共接口中的方法是否抛出检查过的异常?
- 如果是的话,抛出何种检查过的异常?
- 在 DAO 实现类中如何处理异常?
在使用 DAO 模式的过程中,我们的小组开发了一些处理异常的原则。遵从这些原则可以极大地改进您的 DAO:
- DAO 方法应该抛出有意义的异常。
- DAO 方法不应该抛出
java.lang.Exception 。 java.lang.Exception 太一般化了。它不传递关于底层问题的任何信息。
- DAO 方法不应该抛出
java.sql.SQLException 。SQLException 是一个低级别的 JDBC 异常。一个 DAO 应该力争封装 JDBC 而不是将 JDBC 公开给应用程序的其余部分。
- 只有在可以合理地预期调用者可以处理异常时,DAO 接口中的方法才应该抛出检查过的异常。如果调用者不能以有意义的方式处理这个异常,那么考虑抛出一个未检查的(运行时)异常。
- 如果数据访问代码捕获了一个异常,不要忽略它。忽略捕获的异常的 DAO 是很难进行故障诊断的。
- 使用链接的异常将低级别的异常转化为高级别的异常。
- 考虑定义标准 DAO 异常类。Spring Framework (参阅 参考资料)提供了很好的一套预定义的 DAO 异常类。
有关异常和异常处理技术的更多信息参阅 参考资料。
实现实例: MovieDAO
MovieDAO 是一个展示本文中讨论的所有技术的 DAO:事务界定、日志和异常处理。您可以在 参考资料一节中找到 MovieDAO 源代码。代码分为三个包:
-
daoexamples.exception
-
daoexamples.movie
-
daoexamples.moviedemo
DAO 模式的这个实现包含下面列出的类和接口:
-
daoexamples.movie.MovieDAOFactory
-
daoexamples.movie.MovieDAO
-
daoexamples.movie.MovieDAOImpl
-
daoexamples.movie.MovieDAOImplJTA
-
daoexamples.movie.Movie
-
daoexamples.movie.MovieImpl
-
daoexamples.movie.MovieNotFoundException
-
daoexamples.movie.MovieUtil
MovieDAO 接口定义了 DAO 的数据操作。这个接口有五个方法,如下所示:
-
public Movie findMovieById(String id)
-
public java.util.Collection findMoviesByYear(String year)
-
public void deleteMovie(String id)
-
public Movie createMovie(String rating, String year, String, title)
-
public void updateMovie(String id, String rating, String year, String title)
daoexamples.movie 包包含 MovieDAO 接口的两个实现。每一个实现使用一种不同的方式进行事务界定,如表 3 所示:
表 3. MovieDAO 实现
|
MovieDAOImpl
|
MovieDAOImplJTA
|
实现 MovieDAO 接口? |
是 |
是 |
通过 JNDI 获得 DataSource? |
是 |
是 |
从 DataSource 获得 java.sql.Connection 对象? |
是 |
是 |
DAO 在内部界定事务? |
是 |
否 |
使用 JDBC 事务? |
是 |
否 |
使用一个 XA DataSource? |
否 |
是 |
参与 JTA 事务? |
否 |
是 |
MovieDAO 演示应用程序
这个演示应用程序是一个名为 daoexamples.moviedemo.DemoServlet 的 servlet 类。 DemoServlet 使用这两个 Movie DAO 查询和更新表中的电影数据。
这个 servlet 展示了如何将支持 JTA 的 MovieDAO 和 Java 消息服务(Java Message Service)结合到一个事务中,如清单 8 所示。
清单 8. 将 MovieDAO 和 JMS 代码结合到一个事务中
UserTransaction utx = MovieUtil.getUserTransaction();
utx.begin();
batman = dao.createMovie("R",
"2008",
"Batman Reloaded");
publisher = new MessagePublisher();
publisher.publishTextMessage("I'll be back");
dao.updateMovie(topgun.getId(),
"PG-13",
topgun.getReleaseYear(),
topgun.getTitle());
dao.deleteMovie(legallyblonde.getId());
utx.commit();
|
当前位置: 首页 >> 数据库 >> Oracle >> 我的权限控制(JBX + struts + hibernate + ORACLE) 我的权限控制(JBX + struts + hibernate + ORACLE)
-------------------------------------------------------------------------------- 作者:: 来源: 发表时间:2006-06-08 浏览次数:18 字号:大 中 小 通过过滤器判断用户权限. 第一步:建立UserPermissionFilter类.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*;
import test.system.SysUserApi; import test.vo.SysUserVO; import test.system.dao.SysUserDao; import test.Const;
public class UserPermissionFilter extends HttpServlet implements Filter {
protected FilterConfig filterConfig = null;
public void destroy() { this.filterConfig = null; }
public void doFilter( ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest req = (HttpServletRequest) request; SysUserVO userSession = new SysUserVO(); userSession = (SysUserVO)req.getSession().getAttribute(Const.SESSION_USER); if (userSession == null) { HttpServletResponse rep = (HttpServletResponse) response;
rep.sendRedirect("/admin/login.jsp"); }else{ filterChain.doFilter(request, response); } } catch (Exception e) {} } public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } public FilterConfig getFilterConfig() { return filterConfig; }
public void setFilterConfig(FilterConfig filterConfig) { this.filterConfig = filterConfig; }
}
第二步:配置WEB.xml文件 设置过滤器: <filter> <filter-name>userpermission</filter-name> <filter-class>sports.tools.UserPermissionFilter</filter-class> </filter> 设置过滤器映射,因为过滤器不能过滤全部的程序,所以可以用列表的形式来增加需要过滤的文件.如下.一个过滤器可以过滤多个映射文件. <filter-mapping> <filter-name>userpermission</filter-name> <url-pattern>/admin/index.jsp</url-pattern> </filter-mapping>
<filter-mapping> <filter-name>userpermission</filter-name> <url-pattern>/admin/edit/*</url-pattern> </filter-mapping> ====================================== http://www.itwenzhai.com/data/2006/0608/article_22958.htm ========================================= http://www.itwenzhai.com/data/2006/0626/article_25178.htm =========================================
不重复DAO ===============
由于 Java™ 5 泛型的采用,有关泛型类型安全 Data Access Object (DAO) 实现的想法变得切实可行。在本文中,系统架构师 Per Mellqvist 展示了基于 Hibernate 的泛型 DAO 实现类。然后展示如何使用 Spring AOP introductions 将类型安全接口添加到类中以便于查询执行。 对于大多数开发人员,为系统中的每个 DAO 编写几乎相同的代码到目前为止已经成为一种习惯。虽然所有人都将这种重复标识为 “代码味道”,但我们大多数都已经学会忍受它。其实有解决方案。可以使用许多 ORM 工具来避免代码重复。例如,使用 Hibernate,您可以简单地为所有的持久域对象直接使用会话操作。这种方法的缺点是损失了类型安全。
为什么您要为数据访问代码提供类型安全接口?我会争辩说,当它与现代 IDE 工具一起使用时,会减少编程错误并提高生产率。首先,类型安全接口清楚地指明哪些域对象具有可用的持久存储。其次,它消除了易出错的类型强制转换的需要(这是一个在查询操作中比在 CRUD 中更常见的问题)。最后,它有效利用了今天大多数 IDE 具备的自动完成特性。使用自动完成是记住什么查询可用于特定域类的快捷方法。
在本文中,我将为您展示如何避免再三地重复 DAO 代码,而仍保留类型安全接口的优点。事实上,您需要为每个新 DAO 编写的只是 Hibernate 映射文件、无格式旧 Java 接口以及 Spring 配置文件中的 10 行。
DAO 实现
DAO 模式对任何企业 Java 开发人员来说都应该很熟悉。但是模式的实现各不相同,所以我们来澄清一下本文提供的 DAO 实现背后的假设:
系统中的所有数据库访问都通过 DAO 进行以实现封装。 每个 DAO 实例负责一个主要域对象或实体。如果域对象具有独立生命周期,它应具有自己的 DAO。 DAO 负责域对象的创建、读取(按主键)、更新和删除(creations, reads, updates, and deletions,CRUD)。 DAO 可允许基于除主键之外的标准进行查询。我将之称为查找器方法 或查找器。查找器的返回值通常是 DAO 负责的域对象集合。 DAO 不负责处理事务、会话或连接。这些不由 DAO 处理是为了实现灵活性。 泛型 DAO 接口
泛型 DAO 的基础是其 CRUD 操作。下面的接口定义泛型 DAO 的方法:
清单 1. 泛型 DAO 接口 public interface GenericDao <T, PK extends Serializable> {
/** Persist the newInstance object into database */ PK create(T newInstance);
/** Retrieve an object that was previously persisted to the database using * the indicated id as primary key */ T read(PK id);
/** Save changes made to a persistent object. */ void update(T transientObject);
/** Remove an object from persistent storage in the database */ void delete(T persistentObject); }
实现接口
用 Hibernate 实现清单 1 中的接口十分简单,如清单 2 所示。它只需调用底层 Hibernate 方法和添加强制类型转换。Spring 负责会话和事务管理。(当然,我假设这些函数已做了适当的设置,但该主题在 Hibernate 和 Springt 手册中有详细介绍。)
清单 2. 第一个泛型 DAO 实现 public class GenericDaoHibernateImpl <T, PK extends Serializable> implements GenericDao<T, PK>, FinderExecutor { private Class<T> type;
public GenericDaoHibernateImpl(Class<T> type) { this.type = type; }
public PK create(T o) { return (PK) getSession().save(o); }
public T read(PK id) { return (T) getSession().get(type, id); }
public void update(T o) { getSession().update(o); }
public void delete(T o) { getSession().delete(o); }
// Not showing implementations of getSession() and setSessionFactory() }
Spring 配置
最后,在 Spring 配置中,我创建了 GenericDaoHibernateImpl 的一个实例。必须告诉 GenericDaoHibernateImpl 的构造函数 DAO 实例将负责哪个域类。只有这样,Hibernate 才能在运行时知道由 DAO 管理的对象类型。在清单 3 中,我将域类 Person 从示例应用程序传递给构造函数,并将先前配置的 Hibernate 会话工厂设置为已实例化的 DAO 的参数:
清单 3. 配置 DAO <bean id="personDao" class="genericdao.impl.GenericDaoHibernateImpl"> <constructor-arg> <value>genericdaotest.domain.Person</value> </constructor-arg> <property name="sessionFactory"> <ref bean="sessionFactory"/> </property> </bean> 2。 我还没有完成,但我所完成的确实已经可以使用了。在清单 4 中,可以看到原封不动使用该泛型 DAO 的示例:
清单 4. 使用 DAO public void someMethodCreatingAPerson() { ... GenericDao dao = (GenericDao) beanFactory.getBean("personDao"); // This should normally be injected
Person p = new Person("Per", 90); dao.create(p); }
现在,我有一个能够进行类型安全 CRUD 操作的泛型 DAO。让子类 GenericDaoHibernateImpl 为每个域对象添加查询能力将非常合理。因为本文的目的在于展示如何不为每个查询编写显式的 Java 代码来实现查询,但是,我将使用其他两个工具将查询引入 DAO,也就是 Spring AOP 和 Hibernate 命名的查询。
Spring AOP introductions
可以使用 Spring AOP 中的 introductions 将功能添加到现有对象,方法是将功能包装在代理中,定义应实现的接口,并将所有先前未支持的方法指派到单个处理程序。在我的 DAO 实现中,我使用 introductions 将许多查找器方法添加到现有泛型 DAO 类中。因为查找器方法是特定于每个域对象的,因此将其应用于泛型 DAO 的类型化接口。
Spring 配置如清单 5 所示:
清单 5. FinderIntroductionAdvisor 的 Spring 配置 <bean id="finderIntroductionAdvisor" class="genericdao.impl.FinderIntroductionAdvisor"/>
<bean id="abstractDaoTarget" class="genericdao.impl.GenericDaoHibernateImpl" abstract="true"> <property name="sessionFactory"> <ref bean="sessionFactory"/> </property> </bean>
<bean id="abstractDao" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true"> <property name="interceptorNames"> <list> <value>finderIntroductionAdvisor</value> </list> </property> </bean>
在清单 5 的配置文件中,我定义了三个 Spring bean。第一个 bean 是 FinderIntroductionAdvisor,它处理引入到 DAO 的所有方法,这些方法在 GenericDaoHibernateImpl 类中不可用。我稍后将详细介绍 Advisor bean。
第二个 bean 是 “抽象的”。在 Spring 中,这意味着该 bean 可在其他 bean 定义中被重用,但不被实例化。除了抽象特性之外,该 bean 定义只指出我想要 GenericDaoHibernateImpl 的实例以及该实例需要对 SessionFactory 的引用。注意,GenericDaoHibernateImpl 类仅定义一个构造函数,该构造函数接受域类作为其参数。因为该 bean 定义是抽象的,所以我将来可以无数次地重用该定义,并将构造函数参数设置为合适的域类。
最后,第三个也是最有趣的 bean 将 GenericDaoHibernateImpl 的 vanilla 实例包装在代理中,赋予其执行查找器方法的能力。该 bean 定义也是抽象的,不指定希望引入到 vanilla DAO 的接口。该接口对于每个具体的实例是不同的。注意,清单 5 显示的整个配置仅定义一次。
3。
扩展 GenericDAO
当然,每个 DAO 的接口都基于 GenericDao 接口。我只需使该接口适应特定的域类并扩展该接口以包括查找器方法。在清单 6 中,可以看到为特定目的扩展的 GenericDao 接口示例:
清单 6. PersonDao 接口 public interface PersonDao extends GenericDao<Person, Long> { List<Person> findByName(String name); }
很明显,清单 6 中定义的方法旨在按名称查找 Person。必需的 Java 实现代码全部是泛型代码,在添加更多 DAO 时不需要任何更新。
配置 PersonDao
因为 Spring 配置依赖于先前定义的 “抽象” bean,因此它变得相当简洁。我需要指出 DAO 负责哪个域类,并且需要告诉 Springs 该 DAO 应实现哪个接口(一些方法是直接使用,一些方法则是通过使用 introductions 来使用)。清单 7 展示了 PersonDAO 的 Spring 配置文件:
清单 7. PersonDao 的 Spring 配置 <bean id="personDao" parent="abstractDao"> <property name="proxyInterfaces"> <value>genericdaotest.dao.PersonDao</value> </property> <property name="target"> <bean parent="abstractDaoTarget"> <constructor-arg> <value>genericdaotest.domain.Person</value> </constructor-arg> </bean> </property> </bean>
在清单 8 中,可以看到使用了这个更新后的 DAO 版本:
清单 8. 使用类型安全接口 public void someMethodCreatingAPerson() { ... PersonDao dao = (PersonDao) beanFactory.getBean("personDao"); // This should normally be injected
Person p = new Person("Per", 90); dao.create(p);
List<Person> result = dao.findByName("Per"); // Runtime exception }
虽然清单 8 中的代码是使用类型安全 PersonDao 接口的正确方法,但 DAO 的实现并不完整。调用 findByName() 会导致运行时异常。问题在于我还没有实现调用 findByName() 所必需的查询。剩下要做的就是指定查询。为更正该问题,我使用了 Hibernate 命名查询。
Hibernate 命名查询
使用 Hibernate,可以在 Hibernate 映射文件 (hbm.xml) 中定义 HQL 查询并为其命名。稍后可以通过简单地引用给定名称来在 Java 代码中使用该查询。该方法的优点之一是能够在部署时优化查询,而无需更改代码。您一会将会看到,另一个优点是无需编写任何新 Java 实现代码,就可以实现 “完整的” DAO。清单 9 是带有命名查询的映射文件的示例:
清单 9. 带有命名查询的映射文件 <hibernate-mapping package="genericdaotest.domain"> <class name="Person"> <id name="id"> <generator class="native"/> </id> <property name="name" /> <property name="weight" /> </class>
<query name="Person.findByName"> <![CDATA[select p from Person p where p.name = ? ]]> </query> </hibernate-mapping>
清单 9 定义了域类 Person 的 Hibernate 映射,该域类具有两个属性:name 和 weight。Person 是具有上述属性的简单 POJO。该文件还包含一个在数据库中查找 Person 所有实例的查询,其中 “name” 等于提供的参数。Hibernate 不为命名查询提供任何真正的名称空间功能。出于讨论目的,我为所有查询名称都加了域类的短(非限定)名称作为前缀。在现实世界中,使用包括包名称的完全类名可能是更好的主意。
逐步概述
您已经看到了为任何域对象创建和配置新 DAO 所必需的全部步骤。三个简单的步骤是:
定义一个接口,它扩展 GenericDao 并包含所需的任何查找器方法。 将每个查找器的命名查询添加到域对象的 hbm.xml 映射文件。 为 DAO 添加 10 行 Spring 配置文件。 查看执行查找器方法的代码(只编写了一次!)来结束我的讨论。
4。
可重用的 DAO 类
使用的 Spring advisor 和 interceptor 很简单,事实上它们的工作是向后引用 GenericDaoHibernateImplClass。方法名以 “find” 打头的所有调用都传递给 DAO 和单个方法 executeFinder()。
清单 10. FinderIntroductionAdvisor 的实现 public class FinderIntroductionAdvisor extends DefaultIntroductionAdvisor { public FinderIntroductionAdvisor() { super(new FinderIntroductionInterceptor()); } }
public class FinderIntroductionInterceptor implements IntroductionInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
FinderExecutor genericDao = (FinderExecutor) methodInvocation.getThis();
String methodName = methodInvocation.getMethod().getName(); if (methodName.startsWith("find")) { Object[] arguments = methodInvocation.getArguments(); return genericDao.executeFinder(methodInvocation.getMethod(), arguments); } else { return methodInvocation.proceed(); } }
public boolean implementsInterface(Class intf) { return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf); } }
executeFinder() 方法
清单 10 的实现中惟一缺少的是 executeFinder() 实现。该代码查看调用的类和方法的名称,并使用配置上的约定将其与 Hibernate 查询的名称相匹配。还可以使用 FinderNamingStrategy 来支持其他命名查询的方法。默认实现查找叫做 “ClassName.methodName” 的查询,其中 ClassName 是不带包的短名称。清单 11 完成了泛型类型安全 DAO 实现:
清单 11. executeFinder() 的实现 public List<T> executeFinder(Method method, final Object[] queryArgs) { final String queryName = queryNameFromMethod(method); final Query namedQuery = getSession().getNamedQuery(queryName); String[] namedParameters = namedQuery.getNamedParameters(); for(int i = 0; i < queryArgs.length; i++) { Object arg = queryArgs[i]; Type argType = namedQuery.setParameter(i, arg); } return (List<T>) namedQuery.list(); }
public String queryNameFromMethod(Method finderMethod) { return type.getSimpleName() + "." + finderMethod.getName(); }
结束语
在 Java 5 之前,该语言不支持编写既类型安全又 泛型的代码,您必须只能选择其中之一。在本文中,您已经看到一个结合使用 Java 5 泛型与 Spring 和 Hibernate(以及 AOP)等工具来提高生产率的示例。泛型类型安全 DAO 类相当容易编写 —— 您只需要单个接口、一些命名查询和为 Spring 配置添加的 10 行代码 —— 而且可以极大地减少错误并节省时间。
几乎本文的所有代码都是可重用的。尽管您的 DAO 类可能包含此处没有实现的查询和操作类型(比如,批操作),但使用我所展示的技术,您至少应该能够实现其中的一部分。参阅 参考资料 了解其他泛型类型安全 DAO 类实现。
致谢
自 Java 语言中出现泛型以来,单个泛型类型安全 DAO 的概念已经成为主题。我曾在 JavaOne 2004 中与 Don Smith 简要讨论了泛型 DAO 的灵活性。本文使用的 DAO 实现类旨在作为示例实现,实际上还存在其他实现。例如,Christian Bauer 已经发布了带有 CRUD 操作和标准搜索的实现,Eric Burke 也在该领域做出了工作。我确信将会有更多的实现出现。我要额外感谢 Christian,他目睹了我编写泛型类型安全 DAO 的第一次尝试并提出改进建议。最后,我要感谢 Ramnivas Laddad 的无价帮助,他审阅了本文。
摘要: http://www.blogjava.net/zqli/archive/2006/08/29/66394.aspx
生成有4个随机数字和杂乱背景的图片,数字和背景颜色会改变,服务器端刷新(用history.go(-1)也会变)原型参考ALIBABA http://china.alibaba.com/member/showimage------------产生验证码图片的文件---... 阅读全文
做为一名大四的学生,我面试过不少的单位,有成功的也有失败的,但是对我来说所有的失败在某种意义上都是一种成功,特别是我下面写的这些,写这篇文章的时,我已经签了南京的一家软件公司,但是想起今年2月21日我面试苏州台湾的IT公司的经历联想到我们现在学习编程的一些情况我真的深有感触,这次面试使我深深的体会到了失败但也收获了很多。 <a href='http://www.best-code.com'>www.best-code.com</a> 我要说的将分成三部分, 1.是我面试的具体经过 2.是由面试想到的 3.现今我应该做的。 当然这些话很大程度上是我个人的意见,不可能完全得到大家的赞同,所以 在某些观点上如果哪位朋友觉得跟我的有很大出入,请不要介意,也不要对我攻击,就当我 没有说过,欢迎和我联系共同探讨这些问题!我的EMAIL:wutao8@263.net 1.面试经过 大约在年前我接到了台湾瑞晟(Realtek)苏州公司的面试通知,通知我2月21日到苏州工业园区面试,接到面试后的几天我把一些专业课温习了一遍,特别是C++和数据结构,由于大学几年里,我一直专研这些方面,加上通过了高级程序员的考试,对于一些常用的算法我差不多也 达到了烂熟于胸的地步,当时的感觉是如果问了我这些方面的问题我应该是没有问题的! 21日那天我被安排在4:30面试,由一位技术人员单独给我面试,在问了一些简单的问题之后 ,他给我出了一道编程题目,题目是这样的: (由于具体面试的题目比较烦琐,我将其核心思想提取出来分解成……(乱码) 1) 写一个函数计算当参数为n(n很大)时的值 1-2+3-4+5-6+7......+n 哼,我的心里冷笑一声!没想到这么简单,我有点紧张的心情顿时放松起来! 于是很快我给出我的解法: long fn(long n) { long temp=0; int i,flag=1; if(n<=0) { printf("error: n must > 0); exit(1); } for(i=1;i<=n;i++) { temp=temp+flag*i; flag=(-1)*flag; } return temp; } 搞定!当我用期待的目光看着面试官的时候,他微笑着跟我说,执行结果肯定是没有问题! 但当n很大的时候我这个程序执行效率很低,在嵌入式系统的开发中,程序的运行效率很重要 ,能让CPU少执行一条指令都是好的,他让我看看这个程序还有什么可以修改的地方,把程序 优化一下!听了这些话,我的心情当时变的有点沉重,没想到他的要求很严格,之后我对程序 进行了严格的分析,给出了改进了的方案! long fn(long n) { long temp=0; int j=1,i=1,flag=1; if(n<=0) { printf("error: n must > 0); exit(1); } while(j<=n) { temp=temp+i; i=-i; i>0?i++:i--; j++; } return temp; } 虽然我不敢保证我这个算法是最优的,但是比起上一个程序,我将所有涉及到乘法指令的语 句改为执行加法指令,既达到要题目的要求而且运算时间上缩短了很多!而代价仅仅是增加了 一个整型变量!但是我现在的信心已经受了一点打击,我将信将疑的看者面试官,他还是微笑 着跟我说:“不错,这个程序确实在效率上有了很大的提高!”我心里一阵暗喜!但他接着说这个程序仍然不能达到他的要求,要我给出更优的方案!天啊!还有优化!我当时真的有点崩 溃了,想了一会后,我请求他给出他的方案!然后他很爽快的给出了他的程序! long fn(long n) { if(n<=0) { printf("error: n must > 0); exit(1); } if(0==n%2) return (n/2)*(-1); else return (n/2)*(-1)+n; } 搞笑,当时我目瞪口呆,没想到他是这个意思,这么简单的代码我真的不会写吗,但是我为 什么没有往那方面上想呢!他说的没有错,在n很大很大的时候这三个程序运行时间的差别简 直是天壤之别!当我刚想开口说点什么的时候,他却先开口了:“不要认为CPU运算速度快就 把所有的问题都推给它去做,程序员应该将代码优化再优化,我们自己能做的决不要让CPU做 ,因为CPU是为用户服务的,不是为我们程序员服务的!”多么精辟的语言,我已经不想再说 什么了!接着是第二个问题: 他要求我用一种技巧性的编程方法来用一个函数实现两个函数的功能n为如: fn1(n)=n/2!+n/3!+n/4!+n/5!+n/6! fn2(n)=n/5!+n/6!+n/7!+n/8!+n/9! 现在用一个函数fn(int n,int flag)实现,当flag为0时 ,实现fn1功能,如果flag为1时实现fn2功能!他的要求还是效率,效率,效率!说实在话, 如果我心情好的话我应该能给出一种比较好的算法,但我那时真的没有什么心思再想了, 我在 纸上胡乱画了一些诸如6!=6*5!的公式后直截了当的跟他说要他给出他的答案!面试官也没有 说什么,给出了他的思路: 定义一个二维数组 float t[2][5]存入[2!,3!,4!,5!,6!},{5! ,6! ,7!,8!,9!]然后给出一个循环: for(i=0;i<6;i++) { temp=temp+n/t[flag][i]; } 最后得到计算值!呵呵,典型的空间换时间的算法! 这些总共花了50分钟的时间,还有十分钟我就跟他很随意的聊聊天,聊了一些编程以及生活 的问题,那时的我已经很放松了,因为我知道这次面试结果只有一个:失败。5:30的时候面试官要我等通知,于是我离开了他们公司。这就是面试的整个经过! 2.由面试想到的 真的是很失败啊!我记得那天下好大的雨,气温也很低,我边走边想,从5:30一直走到7:30 ,全身都湿透了,又冷又饿,但是我只是一直走,脑子里面充满了疑惑,我也想让雨把自己淋 醒!看到这里有些朋友可能觉得那些面试题目不算什么如果让自己做的话肯定能全部答对,我 肯定相信你,因为我从未怀疑过中国程序员的能力,我认为中国有世界上最好的程序员,我也 从未认为自己是高手,所以我做不出来不代表中国程序员比台湾或者别的地方的程序员差,所 以我就从我的角度,我的所见所想来谈一些感想: 不错全世界都有优秀的程序员,中国也不例外,但是我疑惑的是:到底中国和台湾或者国外 的优秀的程序员的比例到底是多少?台湾我不知道,中国100个程序员里有几个是优秀的呢?我 根本算不上,从上面的表现就足以说明一切了!是1个?5个?10个?50个?这个数字我不敢乱 猜,恐遭网友一顿痛骂,那么我们国内有多少人学习计算机呢?拿我们学校来说,计算机97级 4个班,98级5个班,99级10个班,2000级17个班,人多了,老师怎么办?我们学校的做法是让 研究生上课,然后呢?补考一抓一大把,大把大把的补考费落入了学校的口袋,还说现在的学 生素质低!真是好笑,我都不知道学校这么做是为了什么,为国内培养大量的程序员吗?学生 们能真正学到计算机知识吗?好了,我敢讲,在我们学校学习编程学生和优秀程序员(注意我 指的是优秀,只会编几个糟烂程序的人算不上)的比例应该是100:0.1 在这种比例下虽然我们中国学习编程的人铺天盖地,但是想想有多少个人能真正为中国软件 业发展作出贡献,有多少人能真正写出优秀的程序名扬海外! 我从学习编程以来,不管是自学还是老师指导,从来都是解决问题就好,编出程序来就行, 我的疑惑是:我们有真正的强调过程序的效率,程序的质量吗?我们有仔细分析过我们写的东 西,看看有没有可以改进的地方,看看有没有简单的方法来达到同样的目的呢?我问心自问, 我发现,我从来没有对我写出来的程序进行过优化,最多就是进行详细的测试,然后Debug, 但是这就足够了吗?这些天我偶尔发现我曾经写过的一个游戏,那是一年做为 其中一员时候,感觉应该拿点东西出来,然后花了一个星期的时间写出来的!程序不算复杂, 但是用到了不少数据结构的东西,也用到了一些精彩的算法,加上windows的界面和游戏的可 玩性,写完后受到了不少好评,我当时真的很佩服自己!但是现在看呢:没有一句注释,好多 丑陋的函数名比如:void chushihua(),好多没有必要的变量,可以用简单语句完成工作的我 使用华丽的算法,大量使用全局变量.....,说不好听的话,六百多行的程序除了能运行之外就 是一陀屎!如果一年前我能听到一些反面意见的话,大概我能早一点觉悟,但是自原代码在 网站发布以来听到的都是赞美之词,没有一个人向我提出程序改进的意见,这又说明了一个什 么问题呢?很值得思考啊! 还有一个疑惑是:我们说的和做的真的一样吗?我在学校的时候曾经受学院指派承办过一个 计算机大赛,请了一个老师出决赛的题目,主要是一些算法题目,这个老师可能是我上大学以 来唯一敬佩的老师了,从程序调试到打分,对于每个程序都仔细分析其时间效率和空间效率, 然后综合打分,四十个人的卷子,老师从下午三点一直调试到晚上十点,在有些写的精彩的语 句后还加上批注。我真是高兴很遇到这样的老师并且和他做深入的交流,但在事后,却发生了 一件不愉快的事,在比赛中获得第二名的学生找到我,说他程序全部调试成功应该给他满分, 并且应该得第一,我说不过他,最后调出了他的原程序和第一名的原程序对比,错,两个程 序都运行的很好,这时,那个同学开口了:“我的程序写的十分简捷明了,仅仅数行就完成了 题目要求,而他的却写了一大堆,为什么给他的分多过给我的分。”我当时很是气愤,如果不 是老师负责的话,那么现在第一名和第二名的位置真的要互调了,拜托,不是程序的行数越少 程序的质量就越高,我记得我跟他大谈这方面的道理,最后说服他了!哈哈,但是我,只能说 说而已,我不知道还有多少人一样,说起来头头是道,但心里却压根就从未重视过它! 3.我打算做的! 其实那天我想到的远不止上面那么多,但是我不想再说了,因为我猜想看这篇文章的网友大 概都有一肚子的感想,一肚子的抱怨,借用这篇文章发泄可不是我想达到的目的,在上面我把 自己骂的一文不值也不是妄自菲薄,但是在某些方面我真的做错了,或者说是偏离了正确方向 ,现在是矫正方向和重整旗鼓的时候了,就象我前面说过的,我相信中国有世界上最好的程序 员,我也相信我的水平不会一直保持现状,我现在就收拾起牢骚真正的实干起来! 真的很巧,就写到这里的时候我在网上偶尔发现了这篇手册,我不知道这预示着什么,但是 我想如果我照下面这个基本原则一直踏实做下去,我一定会实现我的理想---一名优秀的软件设计师! (下面这些文字不是我的原创,是我偶尔在网上发现的,我真的很幸运能看到这些,这篇文 章也随着下面的文字而结束,我真心的希望您能从这篇文章中得到启发,这篇文章欢迎大家随 意转载!) 作者:金蝶中间件公司CTO袁红岗 不知不觉做软件已经做了十年,有成功的喜悦,也有失败的痛苦,但总不敢称自己是高手, 因为和我心目中真正的高手们比起来,还差的太远。世界上并没有成为高手的捷径,但一些基 本原则是可以遵循的。 1. 扎实的基础。数据结构、离散数学、编译原理,这些是所有计算机科学的基础,如果 不掌握他们,很难写出高水平的程序。据我的观察,学计算机专业的人比学其他专业的人更能 写出高质量的软件。程序人人都会写,但当你发现写到一定程度很难再提高的时候,就应该想 想是不是要回过头来学学这些最基本的理论。不要一开始就去学OOP,即使你再精通OOP,遇到 一些基本算法的时候可能也会束手无策。 2. 丰富的想象力。不要拘泥于固定的思维方式,遇到问题的时候要多想几种解决问题的 方案,试试别人从没想过的方法。丰富的想象力是建立在丰富的知识的基础上,除计算机以外 ,多涉猎其他的学科,比如天文、物理、数学等等。另外,多看科幻电影也是一个很好的途径 。 3. 最简单的是最好的。这也许是所有科学都遵循的一条准则,如此复杂的质能互换原理 在爱因斯坦眼里不过是一个简单得不能再简单的公式:E=mc^2。简单的方法更容易被人理解, 更容易实现,也更容易维护。遇到问题时要优先考虑最简单的方案,只有简单方案不能满足要 求时再考虑复杂的方案。 4. 不钻牛角尖。当你遇到障碍的时候,不妨暂时远离电脑,看看窗外的风景,听听轻音 乐,和朋友聊聊天。当我遇到难题的时候会去玩游戏,而且是那种极暴力的打斗类游戏,当负 责游戏的那部分大脑细胞极度亢奋的时候,负责编程的那部分大脑细胞就得到了充分的休息。 当重新开始工作的时候,我会发现那些难题现在竟然可以迎刃而解。 5. 对答案的渴求。人类自然科学的发展史就是一个渴求得到答案的过程,即使只能知道 答案的一小部分也值得我们去付出。只要你坚定信念,一定要找到问题的答案,你才会付出精 力去探索,即使最后没有得到答案,在过程中你也会学到很多东西。 6. 多与别人交流。三人行必有我师,也许在一次和别人不经意的谈话中,就可以迸出灵 感的火花。多上上网,看看别人对同一问题的看法,会给你很大的启发。 7. 良好的编程风格。注意养成良好的习惯,代码的缩进编排,变量的命名规则要始终保 持一致。大家都知道如何排除代码中错误,却往往忽视了对注释的排错。注释是程序的一个重 要组成部分,它可以使你的代码更容易理解,而如果代码已经清楚地表达了你的思想,就不必 再加注释了,如果注释和代码不一致,那就更加糟糕。 www.best-code.com 8. 韧性和毅力。这也许是"高手"和一般程序员最大的区别。A good programming is 99 weat and 1ffee。高手们并不是天才,他们是在无数个日日夜夜中磨练出来的。成功能给 我们带来无比的喜悦,但过程却是无比的枯燥乏味。你不妨做个测试,找个10000以内的素数 表,把它们全都抄下来,然后再检查三遍,如果能够不间断地完成这一工作,你就可以满足这 一条。 这些是我这几年程序员生涯的一点体会,希望能够给大家有所帮助。
作者: 车东 Email: chedongATbigfoot.com/chedongATchedong.com
写于:2003/03 最后更新:
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明 http://www.chedong.com/tech/cms.html 关键词:"content manage system" cms path_info cgi php cache squid 内容管理 mod_rewrite url rewrite 系统 缓存 Cacheable "Search engine friendly" 内容摘要: 你完全不必耐心的看到最后,本文主要说明的是在设计CMS时以下2点注意事项:
- 搜索引擎友好(Search engine Friendly):基于PATH_INFO的参数解析使得动态网页在链接(URI)形式上更像静态的目录结构,大大方便网站内容被搜索引擎收录;
- 可缓存性(Cache Friendly):CMS本身不要过多考虑“效率”问题,只要页面输出设计的比较Cacheable,效率问题可以通过更前端专业的缓存服务器解决。
后面附有一个简单的利用PATH_INFO机制 + SquidWEB加速模式实现PHP动态网页的静态发布的例子,比起那些能导出静态页面的大型发布系统这种轻量级的动态缓存解决方案只需对原有的动态发布系统做少量的修改,从而大大提高了原有内容发布系统的重用度。 网站内容静态发布的重要性:Cacheable / Search Engine Friendly由于一个动态页面的速度往往会比静态页面慢2-10倍,因此对于一个访问量逐步向百万级发展的网站来说,访问速度很快成为一个瓶颈。除了优化内容发布系统的应用本身外,如果能把更新频率比较低的动态页面转存成静态网页来发布,速度上的提升效果将是显著的,而静态网页如果能被缓存在内存里,访问速度更会比原有动态网页有2-3个数量级的提高。
在国外内容管理系统(CMS)已经是一个非常成熟的行业,能够真正支撑大访问的系统中静态页面输出和缓存系统几乎是必须的。 <a href='http://www.best-code.com'>www.best-code.com</a> 此外随着互联网上的内容以惊人速度的增长也越来越突出了搜索引擎的重要性,如果网站想更好地被搜索引擎收录,网站设计除了面向用户友好(User Friendly)外,面向搜索引擎友好的设计也是非常重要的。链接地址相对固定的静态网页比较适合搜索引擎索引,动态网页后面跟的参数灵活度很大,因此很多搜索引擎都往往会忽略动态页面,比如:对于news.php?day=22&month=03&year=2003,很多搜索引擎可能只索引news.php这个页面一次,更多其他参数的页面可能都会当成相似内容滤掉;我个人一直怀疑在搜索引擎中:即使是同样内容,静态页面往往也比动态网页的PageRank高。 因此,将动态页面转换成静态页面,无论从效率上还是面向搜索引擎友好上,都是一个门户级内容发布系统必须面对的问题。
静态缓存和动态缓存的比较 静态页面的缓存可能有2种形式: - 静态缓存:是在新内容发布的同时就立刻生成相应内容的静态页面,比如:2003年3月22日,管理员通过后台内容管理界面录入一篇新闻后,就立刻生成http://www.chedong.com/tech/2003/03/22/001.html这个静态页面,并同步更新http://www.chedong.com/tech/index.html这个静态页面上的相关链接。
- 动态缓存:是在新内容发布以后,并不预先生成相应的静态页面,直到对相应内容发出请求时,如果前台缓存服务器找不到相应缓存,就向后台内容管理服务器发出请求,后台系统会生成相应内容的静态页面,用户第一次访问页面时可能会慢一点,但是以后就是直接访问缓存了。
如果去ZDNet等国外网站会发现他们使用的基于Vignette内容管理系统都有这样的页面名称:0,22342566,300458.html。其实这里的0,22342566,300458就是用逗号分割开的多个参数: 第一次访问找不到页面后,相当于会在服务器端产生一个doc_type=0&doc_id=22342566&doc_template=300458的查询, 而查询结果会生成的缓存的静态页面:0,22342566,300458.html
静态缓存的缺点: - 复杂的触发更新机制:这两种机制在内容管理系统比较简单的时候都是非常适用的。但对于一个关系比较复杂的网站来说,页面之间的逻辑引用关系就成为一个非常非常复杂的问题。最典型的例子就是一条新闻要同时出现在新闻首页和相关的3个新闻专题中,在静态缓存模式中,每发一篇新文章,除了这篇新闻内容本身的页面外,还需要系统通过触发器生成多个新的相关静态页面,这些相关逻辑的触发也往往就会成为内容管理系统中最复杂的部分之一。
- 旧内容的批量更新: 通过静态缓存发布的内容,对于以前生成的静态页面的内容很难修改,这样用户访问旧页面时,新的模板根本无法生效。
在动态缓存模式中,内容管理系统只需要关心各个页面自身,而相关的其他页面链接能自动更新,从而大大减少了设计触发器设计的需要。 VIGNETTE的动态缓存虽然很好,但是一个系统如果设计得太全面其实也是有很大危险的:如果一个频道下文章很多:比如达到十万时,如果生成的静态页面都在一个目录下,对系统文件系统是一个极大的危害,因为一个目录下文件个数超过3000效率就会非常差,甚至连rm *时都会出现too many arguments错误。 简单的说,一个好的内容管理系统应该包括: - 输入:方便的内容录入,所见即所得的编辑界面,权限控制等……
- 输出:方便的模板管理,缓存发布等……
设计或寻找这样一个系统如果考虑功能全面和高集成度,你会发现只有那些几十万$以上的专业内容发布系统才能你满足所有的需求。 以前做应用的时候也用过一些方式:应用首次访问以后将生成的内容存成一个缓存文件,下次请求时从缓存目录中读取缓存文件,内容更新时,应用把内容从缓存目录中删掉,从而减少对数据库的访问。虽然这样做也能承载比较大的负载,但这样的内容管理和缓存一体的系统是很难分离的。 如果换一个思路:通过一定的分工现内容管理和缓存机制2者的分离,你会发现无论哪一方面可选的余地都是非常大的。甚至有可能利用目前的已经是“功能”比较全面的内容管理系统,而让所有“效率”问题都由前台更专业,而且是很容易分布的缓存服务器解决:可以是通过开放源代码的SQUID做反相代理的WEB加速,可以是专门的缓存硬件设备,甚至是专业的缓存服务商。 动态缓存必须有一个基于静态链接本身的参数解析过程,很多专业内容管理系统系统都是将参数解析机制做成了WEB服务器的模块实现的。 我们可以把以前的HTTP/GET方式的?key=value改为直接用/value1/value2的方式来传递,从而实现了动态页面的静态URL形式。而缓存只需要在前端加上一层CACHE服务器,比如:Squid。网站动态内容的动态缓存发布就可以实现了。 按照这个机制实现的发布系统很好地体现了应用系统的分工:把复杂地内容管理系统分解成:内容输入和缓存这2个相对简单的系统实现。而中间的内容发布通过URL REWRITE或PATH_INFO解决动态页面的参数传递: - 后台:内容管理系统,专心的将内容发布做好,比如:复杂的工作流管理,复杂的模板规则等……
- 前台:页面的缓存管理则可以使用缓存软件(比如前台80端口使用SQUID对后台8080的内容发布管理系统进行缓存),缓存硬件,甚至交给缓存服务商。
______________________ ___________________ |Squid Software cache| |F5 Hardware cache| ---------------------- ------------------- \ / \ ________________ / |ASP |JSP |PHP | PATH_INFO Based Content Manage System ----------------
把URI地址用作参数传递:URL REWRITE和PATH_INFO最近看到很多关于面向搜索引擎URL设计优化(URI Pretty)的文章,提到了很多利用一定机制将动态网页参数变成像静态网页的形式: 比如可以将:http://www.chedong.com/phpMan.php?mode=man¶meter=ls 变成:http://www.chedong.com/phpMan.php/man/ls
最简单的是基于各种WEB服务器中的URL重写转向(Rewrite)模块的URL转换:这样几乎可以不修改程序的实现将news.asp?id=234的映射成news/234.html Apache上有一个模块(非缺省):mod_rewrite:当然URL REWRITE的强大功能还远远不止于此。
当我需要将将news.asp?id=234的映射成news/234.html时:只需设置: RewriteRule /news/(\d+)\.html /news\.asp\?id=$1 [N,I] 这样就把 /news/234.html 映射成了 /news.asp?id=234 当有对/news/234.html的请求时:web服务器会把实际请求转发给/news.asp?id=234
而在IIS也有相应的REWRITE模块:比如ISAPI REWRITE和IIS REWRITE,语法都是基于正则表达式,因此语法是几乎相同的: 比对于某一个简单应用可以是: RewriteRule /news/(\d+)? /news\.asp\?id=$1 [N,I] 这样就把 /news/234 映射到了 /news.asp?id=234
如我需要把 http://www.myhost.com/foo.php?a=A&b=B&c=C 表现成 http://www.myhost.com/foo.php/a/A/b/B/c/C。而一个更通用的能够将所有的动态页面进行参数映射的表达式是: RewriteRule (.*?\.php)(\?[^/]*)?/([^/]*)/([^/]*)(.+?)? $1(?2$2&:\?)$3=$4?5$5: [N,I]
通过URL REWRITE还有一个好处就是隐藏后台实现: 比如我们需要将应用从news.asp?id=234迁移成news.php?query=234时,前台的表现可以一直保持为news/234.html。从实现应用和前台表现的分离:保持了URL的稳定性,在实现后台应用平台的迁移时非常有用。使用mod_rewrite甚至可以把请求转发到其他后台服务器上: 另外一个方式就是基于PATH_INFO: PATH_INFO是一个CGI 1.1的标准,所有直接跟在CGI或动态页面app.cgi后面的"/value_1/value_2"就是PATH_INFO参数: 比如http://www.chedong.com/phpMan.php/man/ls,中:$PATH_INFO = "/man/ls"
PATH_INFO是CGI标准,因此PHP Servlet等都有比较好的支持。比如Servlet中就有request.getPathInfo()方法。 注意:/myapp/servlet/Hello/foo的getPathInfo()返回的是/foo,而/myapp/dir/hello.jsp/foo的getPathInfo()将返回的/hello.jsp,从这里你也可以知道jsp其实就是一个Servlet的PATH_INFO参数。ASP不支持PATH_INFO,PHP中基于PATH_INFO的参数解析的例子如下: //注意:第一个参数是空的,参数按"/"分割 if ( isset($_SERVER["PATH_INFO"]) ) { list($nothing, $day, $id) = explode('/', $_SERVER["PATH_INFO"]); } 如何隐蔽应用:例如.php,的扩展名: 在APACHE中这样配置: <FilesMatch "^app_name$"> ForceType application/x-httpd-php </FilesMatch> 如何更像静态页面:app_name/my/app.html 解析的PATH_INFO参数的时候,把最后一个参数的最后5个字符“.html”截断即可。 注意:APACHE2中缺省是不允许PATH_INFO的,需要设置AcceptPathInfo on特别是针对使用虚拟主机用户,无权安装和配置mod_rewrite的时候,PATH_INFO往往就成了唯一的选择。 虽然通过修改设置SQUID也可以对带?的动态页面进行缓存,但为了方便搜索引擎收录索引,还是将参数改成PATH_INFO比较好。 OK,这样以后看见类似于http://www.example.com/article/234这样的网页你就知道其实是article/show.php?id=234这个php程序生成的动态网页,很多站点表面看上去可能有很多静态目录,其实很有可能都是使用1,2个程序实现的内容发布。比如很多WIKIWIKI系统都使用了这个机制:整个系统就一个简单的wiki程序,而看上去的目录其实都是这个应用拿后面的地址作为参数的查询结果。 利用基于MOD_REWRITE/PATH_INFO + CACHE服务器的解决方案对原有的动态发布系统进行改造,也可以大大降低旧有系统升级到新的内容管理系统的成本。 面向缓存的页面设计让页面能够比较好的被缓存服务器缓存,必须在产生内容的WEB服务器上设置,让返回内容的HTTP HEADER中加入"Last-Modified"和"Expires"声明,比如: Last-Modified: Wed, 14 May 2003 13:06:17 GMT Expires: Fri, 13 Jun 2003 13:06:17 GMT 以允许前端SQUID服务器缓存: - 页面必须包含Last-Modified: 标记,一般纯静态页面本身都会有Last-Modified信息,动态页面需要通过函数强制加上,比如PHP中:
// always modified now header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
- 必须有Expires或Cache-Control: max-age标记设置页面的过期时间:
对于静态页面,通过apache的mod_expires根据页面的MIME类型设置缓存周期:比如图片缺省是1个月,HTML页面缺省是2天等。 <IfModule mod_expires.c> ExpiresActive on ExpiresByType image/gif "access plus 1 month" ExpiresByType text/css "now plus 2 day" ExpiresDefault "now plus 1 day" </IfModule>
对于动态页面,则可以直接通过写入HTTP返回的头信息,比如对于新闻首页index.php可以是20分钟,而对于具体的一条新闻页面可能是1天后过期。比如:在php中加入了1个月后过期: // Expires one month later header("Expires: " .gmdate ("D, d M Y H:i:s", time() + 3600 * 24 * 30). " GMT");
- 如果服务器端有基于HTTP的认证,必须有Cache-Control: public标记
ASP应用的缓存设计: 首先在公用的包含文件中(比如include.asp)加入以下公用函数: <% ' Converts date (19991022 11:08:38) to http form (Fri, 22 Oct 1999 12:08:38 GMT) Function DateToHTTPDate(ByVal OleDATE) Const GMTdiff = #08:00:00# OleDATE = OleDATE - GMTdiff DateToHTTPDate = engWeekDayName(OleDATE) & _ ", " & Right("0" & Day(OleDATE),2) & " " & engMonthName(OleDATE) & _ " " & Year(OleDATE) & " " & Right("0" & Hour(OleDATE),2) & _ ":" & Right("0" & Minute(OleDATE),2) & ":" & Right("0" & Second(OleDATE),2) & " GMT" End Function Function engWeekDayName(dt) Dim Out Select Case WeekDay(dt,1) Case 1:Out="Sun" Case 2:Out="Mon" Case 3:Out="Tue" Case 4:Out="Wed" Case 5:Out="Thu" Case 6:Out="Fri" Case 7:Out="Sat" End Select engWeekDayName = Out End Function Function engMonthName(dt) Dim Out Select Case Month(dt) Case 1:Out="Jan" Case 2:Out="Feb" Case 3:Out="Mar" Case 4:Out="Apr" Case 5:Out="May" Case 6:Out="Jun" Case 7:Out="Jul" Case 8:Out="Aug" Case 9:Out="Sep" Case 10:Out="Oct" Case 11:Out="Nov" Case 12:Out="Dec" End Select engMonthName = Out End Function %> 然后在具体的页面中,比如index.asp和news.asp的“最上面”加入以下代码:HTTP Header <!--#include file="../include.asp"--> <% ' set Page Last-Modified Header: ' Converts date (19991022 11:08:38) to http form (Fri, 22 Oct 1999 12:08:38 GMT) Response.AddHeader "Last-Modified", DateToHTTPDate(Now()) ' The Page Expires in Minutes Response.Expires = 60 ' Set cache control to externel applications Response.CacheControl = "public" %> 其中Response.Expires 是设置页面过期时间的:单位是分钟 如何检查目前站点页面的可缓存性(Cacheablility)呢?可以参考以下2个站点上的工具: http://www.ircache.net/cgi-bin/cacheability.py面向缓存的站点规划一个利用SQUID的Transparent对多个站点进行做WEB加速http acceleration方案: 原先一个站点的规划可能是这样的: 200.200.200.207 www.chedong.com 200.200.200.208 news.chedong.com 200.200.200.209 bbs.chedong.com 200.200.200.205 images.chedong.com 在SQUID模式下:所有站点都通过外部DNS指向到同一个IP:200.200.200.200/201这2台SQUID缓存服务器上(使用2台是为了冗余备份) _____________________ ________ www.chedong.com 请求 \ | Squid cache box | | | / 192.168.0.4 www.chedong.com news.chedong.com 请求 -| 200.200.200.200/201 |-|firewall| - 192.168.0.4 news.chedong.com bbs.chedong.com 请求 / | /etc/hosts | | box | \ 192.168.0.3 bbs.chedong.com --------------------- --------
编译和配置过程: - ./configure --enable-referer-log --disable-internal-dns
--disable-internal-dns:禁用SQUID的DNS解析 --enable-referer-log:便于APACHE COMBINED格式日志生成
- 配置:
http_port 80 httpd_accel_host virtual httpd_accel_port 8000 httpd_accel_uses_host_header on
# accelerater my domain only acl acceleratedHosts dstdom_regex chedong.com # accelerater http protocol on port 80 acl acceleratedProtocol protocol HTTP acl acceleratedPort port 80 # access arc acl all src 0.0.0.0/0.0.0.0
# Allow requests when they are to the accelerated machine AND to the # right port with right protocol http_access allow acceleratedProtocol acceleratedPort acceleratedHosts http_access allow all
在/etc/hosts中:加入内部的DNS解析,比如: 192.168.0.4 www.chedong.com 192.168.0.4 news.chedong.com 192.168.0.3 bbs.chedong.com 工作原理: SQUID服务器上关闭了DNS解析,这样,请求外部过来时,设置SQUID根据/etc/hosts文件进行内部DNS解析。这样,服务器请求就可以转发到我们指定的内部地址上。 使用SQUID的反相代理加速,我们不仅可以得到性能上的提升,而且还能获得额外的安全性和配置的灵活度: - 配置灵活性提高:可以自己在内部服务器上控制后台服务器的DNS解析,当需要在服务器之间做迁移调整时,就不用大量修改外部DNS配置了,只需要修改内部DNS实现服务的调整。
- 数据安全性增加:所有后台服务器可以很方便的被保护在防火墙内。
- 后台应用设计复杂程度降低:原先为了效率常常需要建立专门的图片服务器images.chedong.com和负载比较高的应用服务器bbs.chedong.com分离,在SQUID加速模式中,所有前台请求都通过SQUID服务器:实际上就都是静态页面,这样,应用设计时就不用考虑图片和应用本身分离了,也大大降低了后台内容发布系统设计的复杂程度,由于数据和应用都存放在一起,也方便了文件系统的维护和管理。
小节: - 大访问量的网站应尽可能将动态网页生成静态页面作为缓存发布,甚至对于搜索引擎这样的动态应用来说,缓存机制也是非常非常重要的。
- 利用PATH_INFO机制进行解析参数,实现动态网页链接的美化,方便搜索引擎的索引;
- 在动态页面中利用HTTP Header定义缓存更新策略。
- 利用缓存服务器获得额外的配置和安全性
- 日志非常重要:SQUID日志缺省不支持COMBINED日志,但REFERER日志对于站点分析非常重要,在GNU/Linux可以用以下方式生成:
pr -mJt access.log referer.log | awk '{print $1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "$10" \x22"$14"\x22 \x22"$11"\x22"}' > combined.log -m merge -J join line -t omit header and footer
附1:SQUID性能测试试验phpMan.php是一个基于php的man page server,每个man page需要调用后台的man命令和很多页面格式化工具,系统负载比较高,提供了Cache Friendly的URL,以下是针对同样的页面的性能测试资料: 测试环境:Redhat 8 on Cyrix 266 / 192M Mem 测试程序:使用apache的ab(apache benchmark): 测试条件:请求50次,并发50个连接 测试项目:直接通过apache 1.3 (80端口) vs squid 2.5(8000端口:加速80端口) 测试1:无CACHE的80端口动态输出: ab -n 100 -c 10 http://www.chedong.com:81/phpMan.php/man/kill/1 This is ApacheBench, Version 1.3d <$Revision: 1.58 $> apache-1.3 Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright (c) 1998-2001 The Apache Group, http://www.apache.org/ Benchmarking localhost (be patient).....done Server Software: Apache/1.3.23 Server Hostname: localhost Server Port: 80 Document Path: /phpMan.php/man/kill/1 Document Length: 4655 bytes Concurrency Level: 5 Time taken for tests: 63.164 seconds Complete requests: 50 Failed requests: 0 Broken pipe errors: 0 Total transferred: 245900 bytes HTML transferred: 232750 bytes Requests per second: 0.79 [#/sec] (mean) Time per request: 6316.40 [ms] (mean) Time per request: 1263.28 [ms] (mean, across all concurrent requests) Transfer rate: 3.89 [Kbytes/sec] received Connnection Times (ms) min mean[+/-sd] median max Connect: 0 29 106.1 0 553 Processing: 2942 6016 1845.4 6227 10796Waiting: 2941 5999 1850.7 6226 10795Total: 2942 6045 1825.9 6227 10796Percentage of the requests served within a certain time (ms) 50% 6227 66% 7069 75% 7190 80% 7474 90% 8195 95% 8898 98% 9721 99% 10796 100% 10796 (last request) 测试2:SQUID缓存输出 /home/apache/bin/ab -n50 -c5 "http://localhost:8000/phpMan.php/man/kill/1" This is ApacheBench, Version 1.3d <$Revision: 1.58 $> apache-1.3 Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright (c) 1998-2001 The Apache Group, http://www.apache.org/ Benchmarking localhost (be patient).....done Server Software: Apache/1.3.23 Server Hostname: localhost Server Port: 8000 Document Path: /phpMan.php/man/kill/1 Document Length: 4655 bytes Concurrency Level: 5 Time taken for tests: 4.265 seconds Complete requests: 50 Failed requests: 0 Broken pipe errors: 0 Total transferred: 248043 bytes HTML transferred: 232750 bytes Requests per second: 11.72 [#/sec] (mean) Time per request: 426.50 [ms] (mean) Time per request: 85.30 [ms] (mean, across all concurrent requests) Transfer rate: 58.16 [Kbytes/sec] received Connnection Times (ms) min mean[+/-sd] median max Connect: 0 1 9.5 0 68 Processing: 7 83 537.4 7 3808Waiting: 5 81 529.1 6 3748Total: 7 84 547.0 7 3876Percentage of the requests served within a certain time (ms) 50% 7 66% 7 75% 7 80% 7 90% 7 95% 7 98% 8 99% 3876 100% 3876 (last request) 结论:No Cache / Cache = 6045 / 84 = 70 结论:对于可能被缓存请求的页面,服务器速度可以有2个数量级的提高,因为SQUID是把缓存页面放在内存里的(因此几乎没有硬盘I/O操作)。 附2:一个CACHE多主机APACHE服务的SQUID安装配置:squid的编译: ./configure --enable-useragent-log --enable-referer-log --enable-default-err-language=Simplify_Chinese --enable-err-languages="Simplify_Chinese English" --disable-internal-dns make #make install #cd /usr/local/squid make dir cache chown squid.squid * vi /usr/local/squid/etc/squid.conf ---------------------cut here---------------------------------- # visible name visible_hostname cache.example.com # cache config: space use 1G and memory use 256M cache_dir ufs /usr/local/squid/cache 1024 16 256 cache_mem 256 MB cache_effective_user squid cache_effective_group squid http_port 80 httpd_accel_host virtual httpd_accel_single_host off httpd_accel_port 80 httpd_accel_uses_host_header on httpd_accel_with_proxy on # accelerater my domain only acl acceleratedHostA dstdomain .example1.com acl acceleratedHostB dstdomain .example2.com acl acceleratedHostC dstdomain .example3.com # accelerater http protocol on port 80 acl acceleratedProtocol protocol HTTP acl acceleratedPort port 80 # access arc acl all src 0.0.0.0/0.0.0.0 # Allow requests when they are to the accelerated machine AND to the # right port with right protocol http_access allow acceleratedProtocol acceleratedPort acceleratedHostA http_access allow acceleratedProtocol acceleratedPort acceleratedHostB http_access allow acceleratedProtocol acceleratedPort acceleratedHostC # logging emulate_httpd_log on referer_log /usr/local/squid/var/logs/referer.log useragent_log /usr/local/squid/var/logs/agent.log ----------------------cut here--------------------------------- 创建缓存目录: /usr/local/squid/sbin/squid -z 启动squid /usr/local/squid/sbin/squid 停止squid: /usr/local/squid/sbin/squid -k shutdown 启用新配置: /usr/local/squid/sbin/squid -k reconfig 通过crontab每天0点截断/轮循日志: 0 0 * * * (/usr/local/squid/sbin/squid -k rotate) 附3:如何在IIS上利用PHP支持PATH_INFOPHP的ISAPI模式安装备忘:只试成 php-4.2.3-Win32 解包目录 ======== php-4.2.3-Win32.zip c:\php PHP.INI初始化文件 ================= 复制:c:\php\php.ini-dist 到 c:\winnt\php.ini 配置文件关联 ============ 按照install.txt中的说明配置文件关联 运行库文件 ========== 复制 c:\php\php4ts.dll 到 c:\winnt\system32\php4ts.dll 这样运行后:会发现php把PATH_INFO映射到了物理路径上 Warning: Unknown(C:\CheDong\Downloads\ariadne\www\test.php\path): failed to create stream: No such file or directory in Unknown on line 0 Warning: Unknown(): Failed opening 'C:\CheDong\Downloads\ariadne\www\test.php\path' for inclusion (include_path='.;c:\php4\pear') in Unknown on line 0 安装ariadne的PATCH ================== 停止IIS服务 net stop iisadmin ftp://ftp.muze.nl/pub/ariadne/win/iis/php-4.2.3/php4isapi.dll 覆盖原有的c:\php\sapi\php4isapi.dll 注:ariadne是一个基于PATH_INFO的内容发布系统 PHP 4.3.2 RC2中CGI模式的PATH_INFO已经修正,照常安装即可。 www.best-code.com参考资料:
CMS行业观察 http://www.cmswatch.com
CMS讨论邮件列表 http://www.cms-list.org
一个基于PATH_INFO的开源内容管理系统 http://typo3.com/ 商业的和开源CMS项目列表: http://directory.google.com/Top/Computers/Software/Internet/Site_Management/Content_Management/
搜索引擎友好的URL设计 http://www.sitepoint.com/article/485 说不定这个URL原来就是articel.php?id=485
HTTP代理缓存 http://vancouver-webpages.com/proxy.html 可缓存的页面设计 http://linux.oreillynet.com/pub/a/linux/2002/02/28/cachefriendly.html
相关RFC文档:
可缓存性检查: http://www.web-caching.com/cacheability.html
URL Rewrite文档: http://www.isapirewrite.com/docs/ http://httpd.apache.org/docs/mod/mod_rewrite.html http://httpd.apache.org/docs-2.0/mod/mod_rewrite.html
原文出处:<a href="http://www.chedong.com/tech/cms.html">http://www.chedong.com/tech/cms.html</a>
DOCTYPE(文档类型)DOCTYPE是document type(文档类型)的简写,用来说明你用的XHTML或者HTML是什么版本。 <a href='http://www.best-code.com'>www.best-code.com</a> 他们是什么和他们为什么是重要的? 所有的HTML和XHTML文档必须有一个有效的doctype声明。
Doctype规定了文档使用的HTML或XHTML的版本。
当校验的时候doctype被校验器使用,WEB浏览器通过它来决定那种渲染模式被使用。
Doctype影响设备渲染web页面的方式。
如果文档使用了正确的doctype,一些浏览器将切换到标准模式,那意味着浏览器会遵守更多的CSS规则。
如果文档使用了正确的doctype,文档会渲染更快,因为浏览器不需要花时间去思考怎样正确地去渲染HTML。 主要的几种DOCTYPE HTML 4.01 Strict <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
在上面的声明中,声明了文档的根元素是html,它在公共标识符为"-//W3C//DTD HTML 4.01//EN"的DTD中进行了定义。浏览器将明白如何寻找匹配此公共标识符的DTD。如果找不到,浏览器将使用公共标识符后面的URL作为寻找DTD的位置。
HTML 4.01 Transitional <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
HTML 4.01 Frameset <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
XHTML 1.0 Strict <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
XHTML 1.0 Transitional <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
XHTML 1.0 Frameset <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
XHTML 1.1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
Standards compliant (or strict) and quirks modes 大多数浏览器有两种模式:怪异模式(quirks mode 使用老的规则)和严格模式(strict mode遵守标准)。Mozilla, Safari, Opera, Mac/Explorer, Windows/Explorer 6实现了这两种模式。Windows/Explorer 5和老的浏览器像Netscape 4执行的是怪异模式。
使用严格模式(strict mode 或者说遵守标准的模式)有一些非常重要的原因。例如,Windows/Explorer 6 将会使用正确的盒模型(box model)当使用strict mode 时,而使用quirks mode 时会执行错误的盒模型。另外在quirks mode下,一些现代的浏览器将不允许fonts被继承。
XML声明需要吗? W3C推荐在XHTML文档里使用XML声明,但也不是必须要这么做。所以这就要看开发者本人来决定了。如果有了这个声明,Windows/IE6将会忽略任何使用的doctype 而执行quirks mode。XML声明看起来如下示: <?xml version="1.0" encoding="utf-8"?>
head区是指首页HTML代码的<head>和</head>之间的内容。 必须加入的标签 <a href='http://www.best-code.com'>www.best-code.com</a>1.公司版权注释 <!--- The site is designed by Maketown,Inc 06/2000 ---> 2.网页显示字符集 简体中文:<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312"> 繁体中文:<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=BIG5"> 英 语:<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> 3.网页制作者信息 <META name="author" content="hhlong.com"> 4.网站简介 <META NAME="DESCRIPTION" CONTENT="xxxxxxxxxxxxxxxxxxxxxxxxxx"> 5.搜索关键字 <META NAME="keywords" CONTENT="xxxx,xxxx,xxx,xxxxx,xxxx,"> 6.网页的css规范 <LINK href="style/style.css" rel="stylesheet" type="text/css"> (参见目录及命名规范) 7.网页标题 <title>xxxxxxxxxxxxxxxxxx</title> .可以选择加入的标签 1.设定网页的到期时间。一旦网页过期,必须到服务器上重新调阅。 <META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT"> 2.禁止浏览器从本地机的缓存中调阅页面内容。 <META HTTP-EQUIV="Pragma" CONTENT="no-cache"> 3.用来防止别人在框架里调用你的页面。 <META HTTP-EQUIV="Window-target" CONTENT="_top"> 4.自动跳转。 <META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://www.hhlong.com"> 5指时间停留5秒。 5.网页搜索机器人向导.用来告诉搜索机器人哪些页面需要索引,哪些页面不需要索引。 <META NAME="robots" CONTENT="none"> CONTENT的参数有all,none,index,noindex,follow,nofollow。默认是all。 6.收藏夹图标 <link rel = "Shortcut Icon" href="favicon.ico"> 所有的javascript的调用尽量采取外部调用. <SCRIPT LANGUAGE="javascript" SRC="script/xxxxx.js"></SCRIPT> www.best-code.com附<body>标签: <body>标签不属于head区,这里强调一下,为了保证浏览器的兼容性,必须设置页面背景<body bgcolor="#FFFFFF">
|
|
想表达的意图 |
处理方式 |
(回车换行) |
<br> |
(空格符) |
|
&(AND符号) |
& |
<(左尖括号、小于号) |
< |
>(右尖括号、大于号) |
> |
°(度) |
° |
•(间隔符) |
• |
´ |
´ |
" |
" |
“ |
“ |
” |
” |
‰ |
‰ |
← |
← |
↑ |
↑ |
→ |
→ |
↓ |
↓ |
↔ |
↔ |
√ |
√ |
∝ |
∝ |
∞ |
∞ |
∠ |
∠ |
∧ |
∧ |
∨ |
∨ |
∩ |
∩ |
∪ |
∪ |
Ø |
Ø |
∫ |
∫ |
∴ |
∴ |
≈ |
≈ |
¥(RMB) |
¥ |
<a href='http://www.best-code.com'>www.best-code.com</a>
|
想表达的意图 |
处理方式 |
≠ |
≠ |
≡ |
≡ |
≤ |
≤ |
≥ |
≥ |
⊕ |
⊕ |
λ |
λ |
μ |
μ |
ν |
ν |
ξ |
ξ |
ν |
ν |
ξ |
ξ |
∏ |
∏ |
∑ |
∑ |
¥ |
¥ |
… |
… |
¹(一次方符号) |
¹ |
²(平方符号) |
² |
³(立方符号) |
³ |
nr(上标情形) |
n<sup>r</sup> |
Ci(下标情形) |
C<sub>i</sub> |
加粗
|
<b>加粗</b> |
斜体
|
<i>斜体</i> |
下划线
|
<u>下划线</u> |
±(加减符号) |
± |
×(乘法符号) |
× |
÷(除法符号) |
÷ |
©(版权所有) |
© |
®(注册商标) |
® |
™(商标符号) |
™ |
—(破折号) |
— |
|
|
1. 什么是robots.txt文件? 搜索引擎通过一种程序robot(又称spider),自动访问互联网上的网页并获取网页信息。 您可以在您的网站中创建一个纯文本文件robots.txt,在这个文件中声明该网站中不想被robot访问的部分,这样,该网站的部分或全部内容就可以不被搜索引擎收录了,或者指定搜索引擎只收录指定的内容。 <a href='http://www.best-code.com'>www.best-code.com</a> 2. robots.txt文件放在哪里? robots.txt文件应该放在网站根目录下。举例来说,当 robots访问一个网站(比如http://www.abc.com)时,首先会检查该网站中是否存在http: //www.abc.com/robots.txt这个文件,如果机器人找到这个文件,它就会根据这个文件的内容,来确定它访问权限的范围。 网站 URL 相应的 robots.txt的 URL http://www.w3.org/ http://www.w3.org/robots.txt http://www.w3.org:80/ http://www.w3.org:80/robots.txt http://www.w3.org:1234/ http://www.w3.org:1234/robots.txt http://w3.org/ http://w3.org/robots.txt 3. robots.txt文件的格式 "robots.txt"文件包含一条或更多的记录,这些记录通过空行分开(以CR,CR/NL, or NL作为结束符),每一条记录的格式如下所示: "<field>:<optionalspace><value><optionalspace>"。 在该文件中可以使用#进行注解,具体使用方法和UNIX中的惯例一样。该文件中的记录通常以一行或多行User-agent开始,后面加上若干Disallow行,详细情况如下: User-agent: 该项的值用于描述搜索引擎robot的名字,在"robots.txt"文件中,如果有多条User-agent记录说明有多个robot会受到该协议的限制,对该文件来说,至少要有一条User-agent记录。如果该项的值设为*,则该协议对任何机器人均有效,在"robots.txt"文件中, "User-agent:*"这样的记录只能有一条。 Disallow: 该项的值用于描述不希望被访问到的一个URL,这个 URL可以是一条完整的路径,也可以是部分的,任何以Disallow开头的URL均不会被robot访问到。例如"Disallow:/help"对 /help.html 和/help/index.html都不允许搜索引擎访问,而"Disallow:/help/"则允许robot访问/help.html,而不能访问 /help/index.html。任何一条Disallow记录为空,说明该网站的所有部分都允许被访问,在"/robots.txt"文件中,至少要有一条Disallow记录。如果"/robots.txt"是一个空文件,则对于所有的搜索引擎robot,该网站都是开放的。 4. robots.txt文件用法举例 例1. 禁止所有搜索引擎访问网站的任何部分 User-agent: * Disallow: / 例2. 允许所有的robot访问 (或者也可以建一个空文件 "/robots.txt" file) User-agent: * Disallow: 例3. 禁止某个搜索引擎的访问 User-agent: BadBot Disallow: / 例4. 允许某个搜索引擎的访问 User-agent: baiduspider Disallow: User-agent: * Disallow: / 例5.一个简单例子 在这个例子中,该网站有三个目录对搜索引擎的访问做了限制,即搜索引擎不会访问这三个目录。 需要注意的是对每一个目录必须分开声明,而不要写成 "Disallow: /cgi-bin/ /tmp/"。 User-agent:后的*具有特殊的含义,代表"any robot",所以在该文件中不能有"Disallow: /tmp/*" or "Disallow:*.gif"这样的记录出现. User-agent: * Disallow: /cgi-bin/ Disallow: /tmp/ Disallow: /~joe/ 5. robots.txt文件参考资料 robots.txt文件的更具体设置,请参看以下链接: · Web Server Administrator's Guide to the Robots Exclusion Protocol · HTML Author's Guide to the Robots Exclusion Protocol · The original 1994 protocol description, as currently deployed · The revised Internet-Draft specification, which is not yet completed or implemented 6. 各搜索引擎的robot Google:Crawled by Googlebot/2.1 (+http://www.google.com/bot.html) Baidu:Crawled by Baiduspider+(+http://www.baidu.com/search/spider.htm) Yahoo:Crawled by Mozilla/5.0 (compatible; Yahoo! Slurp China MSN: Crawled by msnbot/1.0 (+http://search.msn.com/msnbot.htm) Sogou: Crawled by sogou spider 中搜:Crawled by User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0) www.best-code.com sina:Crawled by Mozilla/4.0(compatible;MSIE 6.0;Windows NT 5.0;.NET CLR 1.1.432)
关于Meta的用法 <a href='http://www.best-code.com'>www.best-code.com</a>meta是用来在HTML文档中模拟HTTP协议的响应头报文。meta 标签用于网页的<head>与</head>中,meta 标签的用处很多。meta 的属性有两种:name和http-equiv。name属性主要用于描述网页,对应于content(网页内容),以便于搜索引擎机器人查找、分类(目前几乎所有的搜索引擎都使用网上机器人自动查找meta值来给网页分类)。这其中最重要的是description(站点在搜索引擎上的描述)和keywords(分类关键词),所以应该给每页加一个meta值。比较常用的有以下几个: name 属性 1、<meta name="generator" contect="">用以说明生成工具(如Microsoft FrontPage 4.0)等; 2、<meta name="keywords" contect="">向搜索引擎说明你的网页的关键词; 3、<meta name="description" contect="">告诉搜索引擎你的站点的主要内容; 4、<meta name="author" contect="你的姓名">告诉搜索引擎你的站点的制作的作者; 5、<meta name="robots" contect="all|none|index|noindex|follow|nofollow"> 其中的属性说明如下: 设定为all:文件将被检索,且页面上的链接可以被查询; 设定为none:文件将不被检索,且页面上的链接不可以被查询; 设定为index:文件将被检索; 设定为follow:页面上的链接可以被查询; 设定为noindex:文件将不被检索,但页面上的链接可以被查询; 设定为nofollow:文件将不被检索,页面上的链接可以被查询。 http-equiv属性 1、<meta http-equiv="Content-Type" contect="text/html";charset=gb_2312-80"> 和 <meta http-equiv="Content-Language" contect="zh-CN">用以说明主页制作所使用的文字以及语言; 又如英文是ISO-8859-1字符集,还有BIG5、utf-8、shift-Jis、Euc、Koi8-2等字符集; 2、<meta http-equiv="Refresh" contect="n;url=http://yourlink">定时让网页在指定的时间n内,跳转到页面http;//yourlink; 3、<meta http-equiv="Expires" contect="Mon,12 May 2001 00:20:00 GMT">可以用于设定网页的到期时间,一旦过期则必须到服务器上重新调用。需要注意的是必须使用GMT时间格式; 4、<meta http-equiv="Pragma" contect="no-cache">是用于设定禁止浏览器从本地机的缓存中调阅页面内容,设定后一旦离开网页就无法从Cache中再调出; 5、<meta http-equiv="set-cookie" contect="Mon,12 May 2001 00:20:00 GMT">cookie设定,如果网页过期,存盘的cookie将被删除。需要注意的也是必须使用GMT时间格式; 6、<meta http-equiv="Pics-label" contect="">网页等级评定,在IE的internet选项中有一项内容设置,可以防止浏览一些受限制的网站,而网站的限制级别就是通过meta属性来设置的; 7、<meta http-equiv="windows-Target" contect="_top">强制页面在当前窗口中以独立页面显示,可以防止自己的网页被别人当作一个frame页调用; 8、<meta http-equiv="Page-Enter" contect="revealTrans(duration=10,transtion=50)">和<meta http-equiv="Page-Exit" contect="revealTrans(duration=20,transtion=6)">设定进入和离开页面时的特殊效果,这个功能即FrontPage中的“格式/网页过渡”,不过所加的页面不能够是一个frame页面。 关于robots.txt的讲解 1.什么是robots.txt文件? 搜索引擎通过一种程序robot(又称spider),自动访问互联网上的网页并获取网页信息。 您可以在您的网站中创建一个纯文本文件robots.txt,在这个文件中声明该网站中不想被robot访问的部分,这样,该网站的部分或全部内容就可以不被搜索引擎收录了,或者指定搜索引擎只收录指定的内容。 2.robots.txt文件放在哪里? robots.txt文件应该放在网站根目录下。举例来说,当robots访问一个网站(比如http://www.abc.com)时,首先会检查该网站中是否存在http://www.abc.com/robots.txt 这个文件,如果机器人找到这个文件,它就会根据这个文件的内容,来确定它访问权限的范围。 1.robots.txt文件的格式? "robots.txt"文件包含一条或更多的记录,这些记录通过空行分开(以CR,CR/NL, or NL作为结束符),每一条记录的格式如下所示: "<field>lt;optionalspace><value><optionalspace>"。 在该文件中可以使用#进行注解,具体使用方法和UNIX中的惯例一样。该文件中的记录通常以一行或多行User-agent开始,后面加上若干Disallow行,详细情况如下: User-agent: 该项的值用于描述搜索引擎robot的名字,在"robots.txt"文件中,如果有多条User-agent记录说明有多个robot会受到该协议的限制,对该文件来说,至少要有一条User-agent记录。如果该项的值设为*,则该协议对任何机器人均有效,在"robots.txt"文件中,"User-agent:*"这样的记录只能有一条。 www.best-code.comDisallow: 该项的值用于描述不希望被访问到的一个URL,这个URL可以是一条完整的路径,也可以是部分的,任何以Disallow开头的URL均不会被robot访问到。例如"Disallow:/help"对/help.html 和/help/index.html都不允许搜索引擎访问,而"Disallow:/help/"则允许robot访问/help.html,而不能访问/help/index.html。任何一条Disallow记录为空,说明该网站的所有部分都允许被访问,在"/robots.txt"文件中,至少要有一条Disallow记录。如果"/robots.txt"是一个空文件,则对于所有的搜索引擎robot,该网站都是开放的。
使用robots.txt的注意事项 <a href='http://www.best-code.com'>www.best-code.com</a>robots.txt的创建很简单,只需设置User-agent与Disallow两项内容,其中User-agent项设置特定的搜索引擎Spider,Disallow项设定不允许Spider抓取和索引的内容。尽管如此,笔者却常常见一些设置不当的例子,在此把robots.txt有关的注意事项介绍一下: robots.txt文件 robots.txt只能存放于网站的根目录下,置于除此之外的任何地方均不会被Spider发现。 每个网站,或每个域名(包括子域名),只能有一个robots.txt。 文件名“robots.txt”为小写字母,其他如Robots.txt或robots.Txt是不正确的,命名错误将会被Spider忽略。 正如上篇文章中介绍的,Spider在网站内找不到robots.txt时将会被重定向到404 错误页面,这便有可能阻碍Spider抓取和收录页面。虽然这并不一定会发生,但很多时候我们没必要冒这样的风险,一般来说,即使我们对网站的所有内容都没有限制,对所有的搜索引擎Spider 都欢迎,最好也在根目录下创建一个robots.txt文件: User-agent: * Disallow: robots.txt的语法规则 在Disallow项中使用小写字母,即文件名和目录名使用小写字母,特别在对大小写敏感的Unix下更要注意。 robots.txt惟一支持的通配符是在User-agent使用的“*”,其代表所有的Spider。除此之外,别的通配符均不可用。这方面的错误常见于在文件名或目录名中使用通配符。 robots.txt的限定项 在User-agent和Disallow项的设定中,每行只允许有一个设定值,同时,注意不要有空行。至于行数,则没有限制,理论上说可以根据需要创建具有无数行的robots.txt。 下面即是一个错误的例子 User-agent: * Disallow: /dir1/ /dir2/ /dir3/ 正确设置应为: User-agent: * Disallow: /dir1/ Disallow: /dir2/ Disallow: /dir3/ robots.txt中的文件与目录 既定某个文件拒绝索引时,格式为文件名(包括扩展名),其后无“/”,而限定目录时,则需在目录名后加“/”。如下面的示例: User-agent: * Disallow: /file.html Disallow: /dir/ 特别注意的是,不要省略掉目录名后的“/”,不然,Spider便极有可能误读相应的设置。 robots.txt中限定项的顺序 请看下方的示例: User-agent: * Disallow: / User-agent: Googlebot Disallow: 该设定本意是想允许Google访问所有页面,同时禁止其他Spider的访问。但在这样的设置下,Googlebot在读取前2行后便会离开网站,后面对其的“解禁”完全失去了意义。正确的格式应为: User-agent: Googlebot Disallow: User-agent: * Disallow: / robots.txt中的注释 尽管在robots.txt的标准中,可以在限定项的后面使用“#”添加注释,如下面的例子 User-agent: Googlebot #这是对Google的设置 Disallow: 但很多研究与测试表明,不少Spider对这样格式的解读存在问题。为确保其能更好地工作,最好采用如下设置: www.best-code.com#这是对Google的设置 User-agent: Googlebot Disallow:
h1>如何提高网站在Google中的排名 ——面向搜索引擎的网站设计
作者: 车东 Email: chedongATbigfoot.com/chedongATchedong.com 写于:2003/01 最后更新:
08/05/2006 14:47:05 版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明 http://www.chedong.com/tech/google.html 关键词:Google PageRank "link popularity" "website promotion" "optimization for search engine" 内容摘要:(注意:这个网站设计本身就利用了其中一些方法)。 网站设计中面向搜索引擎的优化注意事项: 谁不想自己的网站在Google的搜索结果重排名靠前呢,但你的网站设计是Search Engine Ready的吗? 什么是PageRank Google等新一带搜索引擎的优势之一在于不仅索引量很大,而且还将最好的结果排在搜索结果的最前面,具体的原理可以参考Google の秘密 - PageRank 徹底解説一文,PageRank简单的说类似于科技论文中的引用机制:谁的论文被引用次数多,谁就是权威。在互联网上PageRank就是基于网页中相互链接关系的分析得出的,由此引出第一个要点: 链接就是一切 在互联网的海洋中,最重要的就是互联互通,不被其他网站引用的网站就是“信息孤岛”。“酒好也怕巷子深”,也许这话说起来有点像垃圾邮件广告,但事实就是这样。所以如果做网站的目的不是孤芳自赏,就需要积极的推广自己的网站。通过搜索引擎推广自己需要注意以下几个方面: - 以量取胜:不一定加入大型网站的分类目录才是网站推广,来自其他网站的任何反相链接都是有用的
网站推广比较经典的方式就是加入比较大型门户网站的分类目录,比如:Yahoo!,dmoz.org等。其实这里有一个误区:不一定非要加入大型网站的分类目录才是网站推广,因为现在搜索引擎已经不再只是网站目录的索引,而是更全面的网页索引,所以无论来自其他网站任何地方的反相链接都是非常有价值的,哪怕是出现在新闻报道,论坛,邮件列表归档中。因此在往很多大型站点的邮件列表发邮件时,一定注意在自己的签名中加上自己网站的地址。 Blogger(Weblog的简称)们也许最深刻地理解了“链接就是一切”这句话的含义,由于Blog的内容之间有大量的相互链接,因此最经常被引用的Blog页面在搜索引擎中的排名往往比一些大型商业网站的页面还要高。
- 以质取胜:被PageRank高的网站引用能更快地提高PageRank
数量只是关键因素之一,来自PageRank高的页面的链接还能更快的提高被链接目标的PageRank,以我的个人网站为例:我没有加入任何分类目录,只是将一些文章投稿在了ZDNet中国上,由于页面上有文章出处链接,相应网页和网站整体的PageRank过了一段时间后就有了很大的提升。有时候被什么样的网站引用有时候比引用次数多更重要。这里我要特别感谢的是,当时ZDNet中国是唯一遵循了我的版权声明的要求表明了文章出处,并且有反相链接的网站。
- 了解搜索引擎的"价值观":
Lucene简介这篇文章被Jakarta.apache.org的lucene项目引用以后,这篇文章就成为了所有页面中PageRank最高的页面(在Google工具栏上显示是6/10),而Google深厚的学院气氛让我一直怀疑他们对 .edu等非功利站点有特别加分 :-),毕竟.org .edu才代表了互联网精神的实质:知识的共享。 但更合理的解释是:.org很多都是开放技术平台的开发者,他们会在首页这样的地方加入Powered By Apache, Power by FreeBSD之类的链接表示对其他开源平台的尊重,所以象Apache, PHP, FreeBSD这样的开源站点在GOOGLE中都有非常高的PageRank。而在.edu这些站点中,很多都是学术性比较强的文档,以超链接形式标明参考文献的出处已经成为一种习惯,而这也无疑正是PageRank最好的依据。 注意:千万不要通过Link Farm提高自身的站点排名:Google会惩罚那些主动链接到Link Farm站点以提高自身排名站点,相应站点的页面将不会被收入到索引中。但如果你的页面被别的Link Farm链接了也不必担心,因为这种被动的链接是不会被惩罚的。
另外在推广自己网站之前也许首先需要了解自己网站目前在一些搜索引擎中的知名度,这里我做了个小工具可以用于这一目的: http://www.chedong.com/linkPopCheck.php。 原理非常简单,可以参考如何评价网站的人气:http://www.chedong.com/tech/link_pop_check.html一文。 网站推广只是手段,如何突出内容、让需要相关信息的用户能够尽快的找到你的网站才是目的,PageRank高并不代表像Yahoo!这样的门户站点就能,因为搜索引擎的结果是搜索关键词在页面中的匹配度和页面的PageRank相结合的排名结果。因此第二个要点: 如何突出关键词 - 不要空着标题:空着<title></title>无异于浪费了最有价值的一块阵地;
传统的页面中,HTML页面中会有类似以下的隐含信息,用于说明当前网页的主要内容关键字: <header> <meta name="keyword" content="mp3 download music..."> </header> 后来由于这种人工添加关键词的方式被滥用,大量网页中为了提高被搜索引擎命中的概率,经常添加一些和实际网页内容无关的热门关键比如:“music mp3 download”等,所以新一代的搜索引擎已经不再关心页面头文件中的人工meta keyword声明,而页面标题在搜索引擎的关键词的命中命中过程中往往有着更高的比重,如果一个关键词在标题中命中会比在页面中命中有更高的得分,从而在相应的搜索结果排名中更靠前。
- 标题长度和内容:不要过长,一般在40个字符以内,并充分突出关键词的比重;
如果更长的标题搜索引擎一般会忽略掉,所以要尽可能将主要关键词放在标题靠前的位置。省略掉不必要的形容词吧,毕竟用户主要通过名词来找到需要的内容。标题内容:尽量用一些别人可以通过关键词找到的字眼(也别太过头,如果标题中的字眼超过1半内容中都没有,有可能被搜索引擎排除出索引),因此基于web日志中来自其他搜索引擎的关键词查询统计非常必要。
- 如果网页很多的话,尽量使用不同的网页标题,争取让自己网站的内容更多的进入搜索引擎索引范围;
因为搜索引擎会根据页面内容的相似度把一些内容当成重复页面排除出索引范围; http://www.chedong.com/phpMan.php是我的网站上的一个小应用:一个web界面的unix命令手册(man page),在以前的设计中所有动态页面使用的都是同样的标题:"phpMan: man page /perldoc /info page web interface" ,Google索引了大约3000多个页面,后来我将页面标题改成了"phpMan: [命令名]"这样的格式,比如:"phpMan: ls",这样大部分动态页面的标题就都不一样了,一个月后Google从这个页面入口索引了大约6000个页面。因此,如果网站中很多网页都使用相同的标题,比如:“新闻频道”,“论坛”,这些页面中很大一部分就会被排重机制忽略掉。
- 除了<title></title>外,还可以用<h1></h1>标题行突出内容主题,加强标题的效果;
在我的网站设计中:我会把用<h1>[标题]</h1>这样的模板把标题突出显示,而不是通过改变字体的方式突出标题。
其他网站设计提示 - 尽量使用静态网页:目前能够像Google一样对动态网页进行索引的搜索引擎还比较少,而同样内容的动态网页其权重比静态网页也要低很多。因此无论从效率上讲还是方便搜索引擎收录,使用内容发布系统将网站内容发布成静态网页都是非常必要的。
比如:http://www.chedong.com/phpMan.php/man/intro/3 肯定比 http://www.chedong.com/phpMan.php?mode=man¶meter=intro§ion=3 更容易进入搜索引擎的索引。而且在URL中的命中有时候比在标题中还能突出关键词。
- 表现和内容的分离:“绿色”网页
网页中的javascript和css尽可能和网页分离,一方面提高代码重用度(也方便页面缓存),另外一方面,由于有效内容占网页长度的百分比高,也能提高相关关键词在页面中的比重。总之,应该鼓励遵循w3c的规范,使用更规范的XHTML和XML作为显示格式便于内容更长时间的保存。
- 让所有的页面都有能够快速入口:站点地图,方便网页爬虫(spider)快速遍历网站所有需要发布的内容。如果首页就是用Flash或图片进入的话,无异于将搜索引擎拒之门外,除了UI设计的用户友好外,spider friendly也是非常重要的。
- 保持网站自身的健康:经常利用坏链检查工具检查网站中是否有死链。
- 保持网页内容/链接的稳定性和持久性:在搜索引擎索引中网页存在的历史也是一个比较重要的因素,而且历史比较久的网页被链接的几率越高。为了保证自己网页能够被比较持久的被其他网站的页面引用,如果自己网页中有链接更新时,最好能保留旧的页面并做好链接转向,以保持内容的连续性。要知道,把一个网站和内容在搜索引擎中的排名“培养”的很高是一件非常不容易的事情,谁都不希望好不容易自己的内容被别人找到了,点击却是“404 页面不存在”吧,因此站点管理员对自身站点error.log的分析也是非常必要的。
- 文件类型因素:Google有对PDF, Word(Power Point, Excel), PS文档的索引能力,由于这种文档的内容比一般的HTML经过了更多的整理,学术价值一般比较高,所以这些类型的文档天生就比一般的HTML类型的文档PageRank要高。因此,对于比较重要的文档:技术白皮书,FAQ,安装文档等建议使用PDF PS等高级格式存取,这样在搜索结果中也能获得比较靠前的位置。
- “一人得道,鸡犬升天”:常常能发现门户站点的一条新闻往往比其他站点的首页排名还要靠前。因此一个站点总体PageRank提高了以后,往往自身一些并不重要的内容也会被同那些高PageRank的内容一起带入被搜索引擎优先查询的列表中。这点有些不是很合理,因为这样经常造成很多大站点的邮件列表归档往往比其他站点的首页PageRank还要高。
知己知彼——站点访问统计/日志分析挖掘的重要性 网站设计不仅仅只是被动的迎合搜索引擎的索引,更重要是充分利用搜索引擎带来的流量进行更深层次的用户行为分析。目前,来自搜索引擎关键词统计几乎是各种WEB日志分析工具的标准功能,相信商业日志统计工具在这方面应该会有更强化的实现。WEB日志统计这个功能如此重要,以至于新的RedHat 8中已经将日志分析工具webalizer作为标准的服务器配置应用之一。
以Apache/webalizer为例,具体的做法如下:
- 记录访问来源:
在Apache配置文件中设置日志格式为combined格式,这样的日志中会包含扩展信息:其中有一个字段就是相应访问的转向来源:HTTP_REFERER,如果用户是从某个搜索引擎的搜索结果中找到了你的网页并点击过来,日志中记录的HTTP_REFERER就是用户在搜索引擎结果页面的URL,这个URL中包含了用户查询的关键词。
- 在webalizer中缺省配置针对搜索引擎的统计:如何提取HTTP_REFERER中的关键词
webalizer中缺省有针对yahoo, google等国际流行搜索引擎的查询格式:这里我增加了针对国内门户站点的搜索引擎参数设置 SearchEngine yahoo.com p= SearchEngine altavista.com q= SearchEngine google.com q= SearchEngine sina.com.cn word= SearchEngine baidu.com word= SearchEngine sohu.com word= SearchEngine 163.com q=
通过这样设置webalizer统计时就会将HTTP_REFERER中来自搜索引擎的URL中的keyword提取出来,比如:所有来自google.com链接中,参数q的值都将被作为关键词统计下来:,从汇总统计结果中,就可以发现用户是根据什么关键词找到你的次数,以及找到你的用户最感兴趣的是那些关键词等,进一步的,在webalizer中有设置还可以将统计结果倒出成CSV格式的日志,便于以后导入数据库进行历史统计,做更深层次的数据挖掘等。 以前通过WEB日志的用户分析主要是简单的基于日志中的访问时间/IP地址来源等,很明显,基于搜索引擎关键词的统计能得到的分析结果更丰富、更直观。因此,搜索引擎服务的潜在商业价值几乎是不言而喻的,也许这也是Yahoo!Altavista等传统搜索引擎网站在门户模式后重新开始重视搜索引擎市场的原因,看看Google的年度关键词统计就知道了,在互联网上有谁比搜索引擎更了解用户对什么更感兴趣呢?
请看本站的反相链接统计:http://www.chedong.com/log/2003_2.log 需要注意的是:由于Google针对Windows 2000中的IE使用的是UTF-8方式的编码,因此很多统计有时候需要在UTF-8方式下查看才是正确字符显示。从统计中能够感受到:在使用水平比较高的IT开发人员中Google已经成为最常用的搜索引擎。而使用百度的用户也已经大大超过了传统的搜狐,新浪等门户站点,因此传统门户网站在搜索引擎上的优势将是非常脆弱的。而从技术的发展趋势来看,以后还会有更多的利用互联网媒体做更深层次数据挖掘的服务模式出现:
转载自cnblog.org——“突发”文字可能揭示社会趋势
在“新科学家”(New Scientist)在线杂志上,公布了康奈尔大学的一个新研究成果,引人注目,也许与Google 收购Pyra 的动机有关。
这所大学的计算机科学家 Jon Klenberg 开发了一个计算机算法,能够识别一篇文章中某些文字的“突发”增长,而且他发现,这些“突发”增长的文字可以用来快速识别最新的趋势和热点问题,因此能够更有效地筛选重要信息。过去很多搜索技术都采用了简单计算文字/词组出现频率的方法,却忽略了文字使用增加的速率。
Jon 特别指出,这种方法可以应用到大量Weblog上,以跟踪社会趋势,这对商业应用也很有潜力。例如,广告商可以从成千上万的个人Blog 中快速找到潜在的需求风尚。而且只要Blog 覆盖话题范围足够大(实际上发展趋势确实如此),这项技术对政治、社会、文化和经济等领域也都会有实际意义了。
虽然Google 新闻的内部算法至今没有公开,但是人们猜测这种完全由机器所搜集的头条新闻应当不是Google搜索引擎中惯用的鸽子算法,很可能与这种“突发”判断算法有关。如此说来,Google收购Blog工具供应商的举动确实还有更深层次的远见了。
- NewScientist.com news, Word 'bursts' may reveal online trends - 还没有写完这些介绍,在 SlashDot 上也看到了很多有关这个发现的讨论 <a href='http://www.best-code.com'>www.best-code.com</a>
参考资料: 面向Google搜索引擎的网站设计优化 http://www.google-search-engine-optimization.com/ 关于Google的十个神话: http://www.promotionbase.com/printTemplate.php?aid=971
如何评价一个网站的人气 http://www.chedong.com/tech/link_pop_check.html 如何提高网站在Google中的排名——面向搜索引擎的广告模式 http://www.chedong.com/tech/google_ads.html
Measuring Link Popularity http://searchenginewatch.com/webmasters/popularity.html Google の秘密 - PageRank 徹底解説 http://www.kusastro.kyoto-u.ac.jp/~baba/wais/pagerank.html 这篇文章是在查"Google PageRank"的时候查到的,这篇文章不仅有一个算法说明,也是一个Google的weblog,记录了很多关于Google的新闻和一些市场动态信息。
Google的海量处理机制:鸽子系统 http://www.google.com/technology/pigeonrank.html
WEB日值统计工具Webalizer http://www.webalizer.org
Robots的说明: http://bar.baidu.com/robots/ http://www.google.com/bot.html 搜索引擎通过一种程序robot(又称spider),自动访问互联网上的网页并获取网页信息。您可以在您的网站中创建一个纯文本文件robots.txt,在这个文件中声明该网站中哪些内容可以被robot访问,哪些不可以。 www.best-code.com 原文出处:<a href="http://www.chedong.com/tech/google.html">http://www.chedong.com/tech/google.html</a>
1. oncontextmenu="window.event.returnValue=false" 将彻底屏蔽鼠标右键 <table border oncontextmenu=return(false)><td>no</table> 可用于Table <a href='http://www.best-code.com'>www.best-code.com</a>2. <body onselectstart="return false"> 取消选取、防止复制 3. onpaste="return false" 不准粘贴 4. oncopy="return false;" oncut="return false;" 防止复制 5. <link rel="Shortcut Icon" href="favicon.ico"> IE地址栏前换成自己的图标 6. <link rel="Bookmark" href="favicon.ico"> 可以在收藏夹中显示出你的图标 7. <input style="ime-mode:disabled"> 关闭输入法 8. 永远都会带着框架 <script language="JavaScript"><!-- if (window == top)top.location.href = "frames.htm"; //frames.htm为框架网页 // --></script> 9. 防止被人frame <SCRIPT LANGUAGE=JAVASCRIPT><!-- if (top.location != self.location)top.location=self.location; // --></SCRIPT> 10. 网页将不能被另存为 <noscript><iframe src=*.html></iframe></noscript> 11. <input type=button value=查看网页源代码 onclick="window.location = "view-source:"+ "http://www.pconline.com.cn""> 12.删除时确认 <a href="javascript:if(confirm("确实要删除吗?"))location="boos.asp?&areyou=删除&page=1"">删除</a> 13. 取得控件的绝对位置 //Javascript <script language="Javascript"> function getIE(e){ var t=e.offsetTop; var l=e.offsetLeft; while(e=e.offsetParent){ t+=e.offsetTop; l+=e.offsetLeft; } alert("top="+t+"/nleft="+l); } </script> //VBScript <script language="VBScript"><!-- function getIE() dim t,l,a,b set a=document.all.img1 t=document.all.img1.offsetTop l=document.all.img1.offsetLeft while a.tagName<>"BODY" set a = a.offsetParent t=t+a.offsetTop l=l+a.offsetLeft wend msgbox "top="&t&chr(13)&"left="&l,64,"得到控件的位置" end function --></script> 14. 光标是停在文本框文字的最后 <script language="javascript"> function cc() { var e = event.srcElement; var r =e.createTextRange(); r.moveStart("character",e.value.length); r.collapse(true); r.select(); } </script> <input type=text name=text1 value="123" onfocus="cc()"> 15. 判断上一页的来源 javascript: document.referrer 16. 最小化、最大化、关闭窗口 <object id=hh1 classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11"> <param name="Command" value="Minimize"></object> <object id=hh2 classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11"> <param name="Command" value="Maximize"></object> <OBJECT id=hh3 classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11"> <PARAM NAME="Command" VALUE="Close"></OBJECT> <input type=button value=最小化 onclick=hh1.Click()> <input type=button value=最大化 onclick=hh2.Click()>7R0癳 <input type=button value=关闭 onclick=hh3.Click()> 本例适用于IE 17.屏蔽功能键Shift,Alt,Ctrl <script> function look(){ if(event.shiftKey) alert("禁止按Shift键!"); //可以换成ALT CTRL } document.onkeydown=look; </script> 18. 网页不会被缓存 <META HTTP-EQUIV="pragma" CONTENT="no-cache"> <META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate"> <META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT"> 或者<META HTTP-EQUIV="expires" CONTENT="0"> 19.怎样让表单没有凹凸感? <input type=text style="border:1 solid #000000"> 或 <input type=text style="border-left:none; border-right:none; border-top:none; border-bottom: 1 solid #000000"></textarea> 20.<div><span>&<layer>的区别? <div>(division)用来定义大段的页面元素,会产生转行 <span>用来定义同一行内的元素,跟<div>的唯一区别是不产生转行 <layer>是ns的标记,ie不支持,相当于<div> 21.让弹出窗口总是在最上面: <body onblur="this.focus();"> 22.不要滚动条? 让竖条没有: <body style="overflow:scroll;overflow-y:hidden"> </body> 让横条没有: <body style="overflow:scroll;overflow-x:hidden"> </body> 两个都去掉?更简单了 <body scroll="no"> </body> 23.怎样去掉图片链接点击后,图片周围的虚线? <a href="#" onFocus="this.blur()"><img src="logo.jpg" border=0></a> 24.电子邮件处理提交表单 <form name="form1" method="post" action="mailto:****@***.com" enctype="text/plain"> <input type=submit> </form> 25.在打开的子窗口刷新父窗口的代码里如何写? window.opener.location.reload() 26.如何设定打开页面的大小 <body onload="top.resizeTo(300,200);"> 打开页面的位置<body onload="top.moveBy(300,200);"> 27.在页面中如何加入不是满铺的背景图片,拉动页面时背景图不动 <STYLE> body {background-image:url(logo.gif); background-repeat:no-repeat; background-position:center;background-attachment: fixed} </STYLE> 28. 检查一段字符串是否全由数字组成 <script language="Javascript"><!-- function checkNum(str){return str.match(//D/)==null} alert(checkNum("1232142141")) alert(checkNum("123214214a1")) // --></script> 29. 获得一个窗口的大小 document.body.clientWidth; document.body.clientHeight 30. 怎么判断是否是字符 if (/[^/x00-/xff]/g.test(s)) alere t("含有汉字"); else alert("全是字符"); 31.TEXTAREA自适应文字行数的多少 <textarea rows=1 name=s1 cols=27 onpropertychange="this.style.posHeight=this.scrollHeight"> </textarea> 32. 日期减去天数等于第二个日期 <script language=Javascript> function cc(dd,dadd) { //可以加上错误处理 var a = new Date(dd) a = a.valueOf() a = a - dadd * 24 * 60 * 60 * 1000 a = new Date(a) alert(a.getFullYear() + "年" + (a.getMonth() + 1) + "月" + a.getDate() + "日") } cc("12/23/2002",2) </script> 33. 选择了哪一个Radio <HTML><script language="vbscript"> function checkme() for each ob in radio1 if ob.checked then window.alert ob.value next end function </script><BODY> <INPUT name="radio1" type="radio" value="style" checked>Style <INPUT name="radio1" type="radio" value="barcode">Barcode <INPUT type="button" value="check" onclick="checkme()"> </BODY></HTML> 34.脚本永不出错 <SCRIPT LANGUAGE="JavaScript"> <!-- Hide function killErrors() { return true; } window.onerror = killErrors; // --> </SCRIPT> 35.ENTER键可以让光标移到下一个输入框 <input onkeydown="if(event.keyCode==13)event.keyCode=9"> 36. 检测某个网站的链接速度: 把如下代码加入<body>区域中: <script language=Javascript> tim=1 setInterval("tim++",100) b=1 var autourl=new Array() autourl[1]="www.njcatv.net" autourl[2]="javacool.3322.net" autourl[3]="www.sina.com.cn" autourl[4]="www.nuaa.edu.cn" autourl[5]="www.cctv.com" function butt(){ document.write("<form name=autof>") for(var i=1;i<autourl.length;i++) document.write("<input type=text name=txt"+i+" size=10 value=测试中……> =》<input type=text name=url"+i+" size=40> =》<input type=button value=GO onclick=window.open(this.form.url"+i+".value)><br>") document.write("<input type=submit value=刷新></form>") } butt() function auto(url){ document.forms[0]["url"+b].value=url if(tim>200) {document.forms[0]["txt"+b].value="链接超时"} else {document.forms[0]["txt"+b].value="时间"+tim/10+"秒"} b++ } function run(){for(var i=1;i<autourl.length;i++)document.write("<img src=http://"+autourl+"/"+Math.random()+" width=1 height=1 onerror=auto("http://"+autourl+"")>")} run()</script> 37. 各种样式的光标 auto :标准光标 default :标准箭头 hand :手形光标 wait :等待光标 text :I形光标 vertical-text :水平I形光标 no-drop :不可拖动光标 not-allowed :无效光标 help :?帮助光标 all-scroll :三角方向标 move :移动标 crosshair :十字标 e-resize n-resize nw-resize w-resize s-resize se-resize sw-resize 38.页面进入和退出的特效 进入t页alere 面<meta http-equiv="Page-Enter" content="revealTrans(duration=x, transition=y)"> 推出页面<meta http-equiv="Page-Exit" content="revealTrans(duration=x, transition=y)"> 这个是页面被载入和调出时的一些特效。duration表示特效的持续时间,以秒为单位。transition表示使用哪种特效,取值为1-23: 0 矩形缩小 1 矩形扩大 2 圆形缩小 3 圆形扩大 4 下到上刷新 5 上到下刷新 6 左到右刷新 7 右到左刷新 8 竖百叶窗 9 横百叶窗 10 错位横百叶窗 11 错位竖百叶窗 12 点扩散 13 左右到中间刷新 14 中间到左右刷新 15 中间到上下 16 上下到中间 17 右下到左上 18 右上到左下 19 左上到右下 20 左下到右上 21 横条 22 竖条 23 以上22种随机选择一种 39.在规定时间内跳转 <META http-equiv=V="REFRESH" content="5;URL=http://www.51js.com"> www.best-code.com40.网页是否被检索 <meta name="ROBOTS" content="属性值"> 其中属性值有以下一些: 属性值为"all": 文件将被检索,且页上链接可被查询; 属性值为"none": 文件不被检索,而且不查询页上的链接; 属性值为"index": 文件将被检索; 属性值为"follow": 查询页上的链接; 属性值为"noindex": 文件不检索,但可被查询链接; 属性值为"nofollow": 文件不被检索,但可查询页上的链接。 41 用JavaScript获取客户端各种高宽
网页可见区域宽: document.body.clientWidth <a href='http://www.best-code.com'>www.best-code.com</a>网页可见区域高: document.body.clientHeight 网页可见区域宽(包括边线和滚动条的宽): document.body.offsetWidth 网页可见区域高(包括边线的宽): document.body.offsetHeight 网页正文全文宽: document.body.scrollWidth 网页正文全文高: document.body.scrollHeight 网页被卷去的高: document.body.scrollTop 网页被卷去的左: document.body.scrollLeft 网页正文部分上: window.screenTop 网页正文部分左: window.screenLeft 屏幕分辨率的高: window.screen.height 屏幕分辨率的宽: window.screen.width 屏幕可用工作区高度: window.screen.availHeight 屏幕可用工作区宽度: window.screen.availWidth 屏幕颜色深度: window.screen.colorDepth 你的屏幕分辨率(像素/英寸): window.screen.deviceXDPI
开发中经常遇到,字符串过长,无法完全显示的问题
这时候就需要截取我们所需要的长度,后面显示省略号或其他字符。
由于中文字符占两个字节,而英文字符占用一个字节,所以,单纯地判断字符数,效果往往不尽如人意
下面的方法通过判断字符的类型来进行截取,效果还算可以:)
如果大家有其他的解决方法欢迎贴出来,共同学习:) ********************************************************************** private String str; private int counterOfDoubleByte; private byte b[]; /** * 设置需要被限制长度的字符串 * @param str 需要被限制长度的字符串 */ public void setLimitLengthString(String str){ this.str = str; } /** * @param len 需要显示的长度(<font color="red">注意:长度是以byte为单位的,一个汉字是2个byte</font>) * @param symbol 用于表示省略的信息的字符,如“...”,“>>>”等。 * @return 返回处理后的字符串 */ public String getLimitLengthString(int len, String symbol) throws UnsupportedEncodingException { counterOfDoubleByte = 0; b = str.getBytes("GBK"); if(b.length <= len) return str; for(int i = 0; i < len; i++){ if(b[i] < 0) counterOfDoubleByte++; }
if(counterOfDoubleByte % 2 == 0) return new String(b, 0, len, "GBK") + symbol; else return new String(b, 0, len - 1, "GBK") + symbol; }
本文转贴自网友:focus2004 的文章
摘要: ajax确实是个很好的技术,在提高客户的体验度上面能做很多以前不能做或者不好做的事情。出现提示页面就是一个很好的示例。需要制作提示页面的地方其实很多,但以前大多是要求用户点击相关信息进入详细信息页面察看,然后返回,再点击其他的信息察看详细信息页面。这样就降低了客户的体验度,在没有ajax的时候,我们是劝导客户只能这么做。现在用ajax就可以很轻松的解决这个问题了。我的平台仍然是struts+spr... 阅读全文
最近由于需要用到ThreadLocal,在网上搜索了一些相关资料,发现对ThreadLocal经常会有下面几种 误解 一、ThreadLocal是java线程的一个实现 ThreadLocal的确是和java线程有关,不过它并不是java线程的一个实现,它只是用来维护本地变量。针对每个线程,提供自己的变量版本,主要是为了避免线程冲突,每个线程维护自己的版本。彼此独立,修改不会影响到对方。
二、ThreadLocal是相对于每个session的 ThreadLocal顾名思义,是针对线程。在java web编程上,每个用户从开始到会话结束,都有自己的一个session标识。但是ThreadLocal并不是在会话层上。其实,Threadlocal是独立于用户session的。它是一种服务器端行为,当服务器每生成一个新的线程时,就会维护自己的ThreadLocal。对于这个误解,个人认为应该是开发人员在本地基于一些应用服务器测试的结果。众所周知,一般的应用服务器都会维护一套线程池,也就是说,对于每次访问,并不一定就新生成一个线程。而是自己有一个线程缓存池。对于访问,先从缓存池里面找到已有的线程,如果已经用光,才去新生成新的线程。所以,由于开发人员自己在测试时,一般只有他自己在测,这样服务器的负担很小,这样导致每次访问可能是共用同样一个线程,导致会有这样的误解:每个session有一个ThreadLocal 三、ThreadLocal是相对于每个线程的,用户每次访问会有新的ThreadLocal 理论上来说,ThreadLocal是的确是相对于每个线程,每个线程会有自己的ThreadLocal。但是上面已经讲到,一般的应用服务器都会维护一套线程池。因此,不同用户访问,可能会接受到同样的线程。因此,在做基于TheadLocal时,需要谨慎,避免出现ThreadLocal变量的缓存,导致其他线程访问到本线程变量 四、对每个用户访问,ThreadLocal可以多用 可以说,ThreadLocal是一把双刃剑,用得来的话可以起到非常好的效果。但是,ThreadLocal如果用得不好,就会跟全局变量一样。代码不能重用,不能独立测试。因为,一些本来可以重用的类,现在依赖于ThreadLocal变量。如果在其他没有ThreadLocal场合,这些类就变得不可用了。个人觉得ThreadLocal用得很好的几个应用场合,值得参考 1、存放当前session用户:quake want的jert 2、存放一些context变量,比如webwork的ActionContext 3、存放session,比如Spring hibernate orm的session
究竟Spring在何时调用destroy-method="close" 这个方法close()呢?终于借助JavaEye找到了答案,原来如果Spring不在Web Container或是EJB Container中的时候,这个方法还是需要我们自己来调用的,具体就是调用BeanFactory的destroySingletons()方法,文档上的“自动调用”这几个字真是害我不浅呀,原来自动也是通过Web Container或是EJB Container才可以自动,具体做法就是要实现ServletContextListener这个接口,Spring中已经有具体的实现了: publicclass ContextLoaderListener implements ServletContextListener { private ContextLoader contextLoader; /** * Initialize the root web application context. */ publicvoid contextInitialized(ServletContextEvent event){ this.contextLoader = createContextLoader(); this.contextLoader.initWebApplicationContext(event.getServletContext()); } /** * Create the ContextLoader to use. Can be overridden in subclasses. * @return the new ContextLoader */ protected ContextLoader createContextLoader(){ returnnew ContextLoader(); } /** * Return the ContextLoader used by this listener. */ public ContextLoader getContextLoader(){ return contextLoader; } /** * Close the root web application context. */ publicvoid contextDestroyed(ServletContextEvent event){ this.contextLoader.closeWebApplicationContext(event.getServletContext()); }
}
当tomcat关闭的时候会自动调用contextDestroyed(ServletContextEvent event)这个方法。在看一下contextLoader的closeWebApplicationContext方法: publicvoid closeWebApplicationContext(ServletContext servletContext)throws ApplicationContextException { servletContext.log("Closing root WebApplicationContext"); Object wac = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if(wac instanceof ConfigurableApplicationContext){ ((ConfigurableApplicationContext) wac).close(); } }
AbstractApplicationContext.Close这个方法是要你自己调用的,在程序要结束的时候保证调用这个close方法,在这里的话就是由Listener来保证tomcat退出的时候调用close方法。 AbstractApplicationContext.Close的代码 : publicvoid close(){ logger.info("Closing application context [" + getDisplayName() + "]");
// Destroy all cached singletons in this context, // invoking DisposableBean.destroy and/or "destroy-method". getBeanFactory().destroySingletons(); // publish corresponding event publishEvent(new ContextClosedEvent(this)); } 最终还是调用到了getBeanFactory().destroySingletons(); 看来,没有容器,我们还是需要自己来搞定这个方法的调用的 !
eclipse3.2上部署jsp
|
danielhuf 发表于 2006-7-15 13:29:00
|
一.下载 J2SDK:jdk-1_5_0_04-windows-i586-p.exe ECLIPSE:eclipse-SDK-3.2-win32.zip ECLIPSE 插件: (1)中文包 NLpack1-eclipse-SDK-3.2-win32.zip (2)Tomcat插件 tomcatPluginV31.zip (3)LOMBAT:lomboz-wtp-emf-gef-jem-3.1.2.zip TOMCAT:apache-tomcat-5.5.17.exe MYSQL:Mysql-5.0.15.rar 二.安装 1.安装j2sdk 安装路径是:c:\java\jdk,c:\java\jre 2.安装eclipse 将eclipse压缩包解压到:d:\eclipse 3.安装tomcat 安装的路径是:c:\java\Tomcat5.5 4.安装eclipse插件 (1)中文包 将压缩包NLpack-eclipse-SDK-3.2-win32内features和plugins两文件夹内的内容解压到eclipse文件夹内同名文件夹 (2)Tomcat插件 将压缩包tomcatPluginV3.1解压,把com.sysdeo.eclipse.tomcat_3.1.0文件夹拷到d:\eclipse\plugins下 (3)LOMBAT插件 将压缩包lomboz-wtp-emf-gef-jem-3.1.2.zip内的features和plugins两文件夹内的内容解压到eclipse文件夹内同名文件夹 将压缩里的configuration/config.xml里下面的一段文字
#Product Runtime Configuration File osgi.splashPath=platform:/base/plugins/org.objectweb.lomboz.product,platform:/base/plugins/org.eclipse.platform eclipse.product=org.objectweb.lomboz.product.lomboz osgi.bundles.defaultStartLevel=4 拷贝粘贴到d:/eclipse/configuration/config.xml中的 # End of file marker - must be here
前。 5.安装Mysql 解压.rar文件,点击setup.exe,安装路径为c:\mysql。注,编码选为gbk。将.rar解压后得到数据库驱动文件mysql-connector-java-3.1.11-bin.jar拷贝到C:\java\Tomcat 5.5\common\lib下 三.环境配置 1.系统环境变量配置 右击“我的电脑”->高级->环境变量,
2.eclipse中java运行环境的配置 在eclipse主窗口中,“窗口”->首选项->Java->已安装的JRE,位置设置为 c:\java\jdk 3.配置Sysdeo Tomcat 插件 在eclipse主窗口中,“窗口”->首选项->Tomcat,Tomcat version 选 version 5.x(我们安装的版本),Tomcat home 处填安装tomcat的路径,这里就是c:\java\Tomcat5.5。切换到Advanced选项,在tomcat base处再填 c:\java\Tomcat5.5 。最后按应用按钮,然后再确定。 4.配置LOMBAT插件 重新启动eclipse,启动画面变为橙黄色,即表示插件安装成功。在“窗口-〉定制透视图-〉快捷方式“中选定EJB,J2EE,WEB。同样在“命令“标签下也选中这三项即可。确定后调整工具栏。 5.检查配置 检查配置是否成功,只需在eclipse主窗口中,按tomcat运行按钮,然后在浏览器里的地址栏里输入http://localhost:8080(主机和端口与Tomcat5.5的/conf/server.xml中的设定一致),如果出现tomcat 的页面,那就证明配置成功了。 四.编写程序 1.创建一个Tomcat项目 右击“包资源管理器”窗口中的空白处,新建->项目->Java->Tomcat Project, 在项目名中填写项目名字,在这里我填tomcat_test,点击“完成”即可。 2.创建一个JSP页面 在包资源管理器中,右击“tomcat_test”,新建->文件, 在文件名中填写test.jsp(扩展名不能缺),在文本编辑窗口输入如下代码:
//test.jsp <%@ page contentType="text/html;charset=gb2312"%> <%@ page import="java.sql.*"%> <html> <body> <% Class.forName("com.mysql.jdbc.Driver").newInstance(); String url ="jdbc:mysql://localhost/test?user=root&password=1qaz&useUnicode=true&characterEncoding=gbk" ; //假设test是你的数据库 Connection conn= DriverManager.getConnection(url); Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE); String sql="select * from dept;"; //从test表读数据 ResultSet rs=stmt.executeQuery(sql); while(rs.next()) { %> <BR> 您的第一个字段内容为:<%=rs.getString(1)%> <BR> 您的第二个字段内容为:<%=rs.getString(2)%> <BR> <% } %> <BR> <% out.print("数据库操作成功,恭喜你");%> <% rs.close(); stmt.close(); conn.close(); %> </body> </html> 标签将加亮显示,按ctrl+s保存。 3.创建测试数据库 start->run输入cmd,回车。到达console界面,输入mysql -u root -p1qaz,回车。 输入一下sql语句: use test; create table DEPT( DEPTNO INT NOT NULL, DNAME VARCHAR(18) NOT NULL ); --insert DEPT insert into DEPT VALUES(8,'EE'); insert into DEPT VALUES(9,'cs'); insert into DEPT VALUES(10,'AUTO'); insert into DEPT VALUES(20,'MATH'); 4.测试 只需在eclipse主窗口中,按tomcat重新启动按钮。在浏览器中地址栏中输入http://localhost:8080/tomcat_test/test.jsp浏览器显示如下:
|
Struts Tiles 我很喜欢 struts ,这是我目前最熟悉的 MVC Framework ,但是 struts 的 template Engine 和 Turbine(jakarta 另外一个 mvc framework,还有一个 tapestry )使 用的 Velocity 有异曲同工之妙,另外如果你们在 Mail List 看到 Craig R. McClanahan 这号人物, 他就是“神”的代言人! |
| MVCII Framework Cotroller是指由 Servlet 所主导,Model 为 JavaBean所开发, 最后以 JSP 做 View 端的呈现,最后 将资料返回到客户端. 而今天我要讨论的就是客户端的 Template Engine -- Tiles. |
View (Template Engine)-Tiles Tiles是由Cedric Dumoulin老大所开发的 Template Engine , 什么叫做 Template Engine呢, 他是一个版面切割控制的处理中心.通常我们在早古时代大约 ( 1995 ~ 2000 )年间 , 设计网页大多以 Frame 为切割网页的方式 , 因为当时网络带宽不足, 加上开发工具短缺,所以我们那时候对于版面的控制大 概也只是这样, 但随着宽带网络的普及化,造就了网页的复杂功能, HTML 4.0 包含了 Layer的功能,问题 来了, Layer 无法跨过 Frame变成一个浮动的控制小窗口,所以 Frame渐渐被淘汰,变成整个网页由 Table 的切割来组合而成, 但是, Table 的设计大多属于网页美工的工作,你要他们懂得如何写动态程序, 大概只有 1/10 的美工可以做到,所以我们建议是各师其职,让网页视觉大师的工作就单纯只是网页设计, 所以 Template Engine就应运而生,那比较有名的有, Velocity, Tiles, FreeMaker等等. 而 Struts 是使用 Tiles的,这次我就针对 Tiles 做初级的介绍. 基本上, 你在撰写 JSP的时候, 如果 /WEB-INF/lib/之下有放struts.jar那就代表说, 你的 JSP 可以 import struts 的组件进来, 而 struts-tiles.tld我通常会放在 /WEB-INF/tlds/目录之下,所以你在 JSP 的开始的地方就要写
<%@ taglib uri="/WEB-INF/tlds/struts-tiles.tld" prefix="tiles" %> 这意思就是说你这个网页将会通过 Struts-Tiles 这个 TagLib去调用 Tiles Template Engine , 你可以自 己打开 struts-tiles.tld 这个文件看看, 里面的定义就是说,当你调用到其中的 tag时候,他需要去调 用哪一个程序来执行你想得到的结果. 完全战略首部曲--建立模板 (template.jsp) 建立一个 template.jsp, 你先规划书面需要切割成为各个区块,本范例是切成上方标题区(top),左方主选单 (menu),右方主画面再切割上下区域各为 main 及 copyright :
<%@ page contentType="text/html;charset=BIG5" %> <%@ taglib uri="/WEB-INF/tlds/struts-tiles.tld" prefix="tiles" %>
<BODY leftmargin="0" marginheight="0" marginwidth="0" topmargin="0" bgcolor="#FFFFFF"
link="#660000"> <table border=\'0\' cellpadding=\'0\' cellspacing=\'0\' width=\'100%\'> <!-- 上方标题区 --> <tr> <td colspan=\'2\'> <img src="<%=request.getContextPath()%>/images/top.gif" border="0"> </td> <!-- 左方主选单 --> <tr valign=\'top\'>
<td width=\'120\' bgcolor=\'#FFFFFF\' align=\'center\'> <tiles:insert attribute="menu"/> </td>
<!-- 右方主画面 --> <td width=\'680\'> <table border=\'0\' cellpadding=\'0\' cellspacing=\'0\' width=\'100%\'> <tr> <td bgcolor=\'ffffff\'> <tiles:insert attribute="main"/> </td> </tr>
</table> </td> <tr> <td colspan=\'2\'> <tiles:insert attribute="copyright"/> </td> </table>
完全战略二部曲--定义 definations.xml 根据 template.jsp 定义的 InsertTag 属性名称 ( attribute )给予一个 jsp/html来显示 <definition name="test.screen" path="/admin/template.jsp"> <put name="menu" value="/menu.jsp"/> <put name="main" value="/index.jsp"/> <put name="copyright" value="/copyright.jsp"/> </definition>
完全战略三部曲--制作 ScreenServlet.java (WARN:copyrights are reserved by Softleader Copr.) 编译以下之程序(ScreenServlet.class)放到 /WEB-INF/classes/com/softleader/system/init/之下 package com.softleader.system.init;
import java.util.StringTokenizer; import java.util.HashMap; import java.util.Locale; import java.util.Map;
import java.io.IOException; import java.io.PrintWriter;
import java.net.URL;
import javax.servlet.*; import javax.servlet.ServletException; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;
import javax.naming.InitialContext; import javax.naming.NamingException;
import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.tiles.*; import org.apache.struts.tiles.TilesUtil;
public class ScreenServlet extends HttpServlet {
private ServletContext context; /** Debug flag */ public static final boolean debug = true; /** Associated definition factory */ protected DefinitionsFactory definitionFactory; protected ComponentDefinition definition; private TilesRequestProcessor trp;
public void init() throws ServletException { }
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { process(request, response); }
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { process(request, response);
}
public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // init screen String screenName = null; String selectedUrl = request.getRequestURI();
// get the screen name int lastPathSeparator = selectedUrl.lastIndexOf("/") + 1; int lastDot = selectedUrl.lastIndexOf("."); if (lastPathSeparator != -1 && lastDot != -1 && lastDot > lastPathSeparator) { screenName = selectedUrl.substring(lastPathSeparator); }
try { // Read definition from factory, but we can create it here. //ComponentDefinition definition = DefinitionsUtil.getDefinition( screenName,
request, this.getServletContext() ); //System.out.println("get Definition " + definition ); //DefinitionsUtil.setActionDefinition( request, definition); //DefinitionsFactory definitionsFactory =
DefinitionsUtil.getDefinitionsFactory(getServletContext()); DefinitionsFactory definitionsFactory = TilesUtil.getDefinitionsFactory(request,
getServletContext());
String uri=""; Controller controller; ComponentContext tileContext = null;
if( definitionsFactory != null ) { // Get definition of tiles/component corresponding to uri. ComponentDefinition definition = definitionsFactory.getDefinition(screenName, request, getServletContext());
if( definition != null ){ // We have a definition. // We use it to complete missing attribute in context. // We also get uri, controller. uri = definition.getPath(); controller = definition.getOrCreateController();
if( tileContext == null ) {
tileContext = new ComponentContext( definition.getAttributes() ); ComponentContext.setContext( tileContext, request);
} else tileContext.addMissing( definition.getAttributes() ); } // end if } // end if
RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
rd.forward(request, response);
} catch( Exception ex ) { } }
}
并且设定 web.xml增加一个 ScreenServlet <servlet> <servlet-name>ScreenServlet</servlet-name> <display-name>ScreenServlet</display-name> <servlet-class>com.softleader.system.init.ScreenServlet</servlet-class> <load-on-startup>3</load-on-startup> </servlet>
测试网页呈现 当然,你需要自己建立相关定义在 definations.xml 的 jsp文件, 接着重新启动 tomcat, 你就可以看到 http://localhost:8080/test.screen是一个整合起来的画面了
- 设定相关的 compile 环境, 基本上,可以直接使用 struts source 的 libs 和 sources
- 设定相关的 properties 及 xml,如果不太了解, 请直接查阅 oreilly 所出的 Struts
- 请尊重知识产权,本文章之原始文件不得用于商业用途,需要时请于本公司联络.
- Struts 网站: http://jakarta.apache.org/struts/
- Tiles网站: http://www.lifl.fr/~dumoulin/tiles/
- Tomcat 网站: http://jakarta.apache.org/tomcat/
- 以上程序都在 Tomcat 4.1.x以上以及 Sun JDK 1.4.x以上测试完成
单独使用 Tiles 把 tiles.jar 放到 WEB-INF/lib/ 把 tiles.tld 放到 WEB-INF/ 把 commons-digester.jar,commons-collections.jar,commons-beanutils.jar 放到 WEB-INF/lib/ 下 把 jakarta commons *.tld 放到 WEB-INF/ 下
接着在 WEB-INF/web.xml 中增加
<servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.titles.TilesServlet</servlet-class>
<init-param> <param-name>definitions-config</param-name> <param-value>/WEB-INF/tiles/tiles-definitions.xml</param-value> </init-param> <init-param> <param-name>definitions-parser-validate</param-name> <param-value>true</param-value> </init-param> </servlet>
使用 <putList> 及 <add> 简单来说, 上一篇介绍的 tiles definitions 的方法是一对一, tiles:insert 会去找 definitions 中的 put 值, 把指向的 jsp 抓进来, 一起包装成一个网页送到客户端的浏览器, 但是, 如果我希望在 template 中一次 加入多笔的页面该怎么做呢, 哪就得用 <putList> 接着使用 iterate 把他一个一个取出来显示. <titles:insert page="/template.jsp"> <tiles:putList name="items"> <tiles:add value="home"/> <tiles:add><img
src="<%=request.getContextPath()%>/images/logo.gif"></titles:add> <tiles:add value="documentation"/> </titles:putList> </titles:insert>
在 view 端 jsp 中要写 <tiles:importAttribute/> <table> <logic:iterate id="item" name="items"> <tr><td><%=item%></td></tr> </logic:iterate> </table>
RssChannel 所谓的 RssData, 是一个 webservice 的格式, 相关的介绍有 XML.com RSS 的介绍 Oreilly RSS 研究中心 RSS 教学手册 RSS 最新消息 基本上有几个好处
- 可能放到各个不同的 tiles channel 中 .
- 在同一个 page 可能放到好几个不同 channel .
- 可以简单的重新绘出 channel 画面.
- 可能符合好几个 channel , 每一个都可以各自重绘.
首先 我们先定义 tiles-definition.xml , 最重要的, 是 controllerUrl 需要设定 , 此外, 还需要得到 rss 的格式.
<definition name="examples.rssChannel.body" path="/examples/tiles/rssChannels.jsp" controllerUrl="/examples/controller/rssChannel.do"> <putList name="urls"> <add value="http://newsforge.com/newsforge.rss"/> <add value="http://xmlhack.com/rss.php"/> <add value="http://lwn.net/headlines/rss"/> </putList> </definition>
在 strut-config.xml 中定义 <action path="/examples/controller/rssChannel" type="org.apache.struts.example.tiles.rssChannel.RssChannelsAction"> </action>
接着建立一个 RssChannelsAction 的 Class public final class RssChannelsAction extends TilesAction { public static final String CHANNELS_KEY = "CHANNELS";
public static final String CHANNEL_URLS_KEY= "urls";
public ActionForward doExecute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException, Exception { org.apache.commons.digester.rss.Channel channel = null ;
List channels = (List)context.getAttribute(CHANNEL_URLS_KEY); List channelBeans = new ArrayList(channels.size());
for ( int i=0 ; i < channels.size(); i++ ) { RSSDigester digester = new RSSDigester(); String url = (String)channels.get(i);
Channel obj = (Channel) digester.parse(url); channelBeans.add(obj); } context.putAttribute(CHANNELS_KEY,channelBeans); return null; }
}
最后, 在 view 端 jsp 这样就可以看到 rssChannel 的资料啦 <%@ page language="java" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<div align="center"><font size="+1"><b>
<tiles:importAttribute name="CHANNELS" scope="page"/>
<logic:iterate name="CHANNELS" id="CHANNEL" > <TABLE border="0" cellspacing="0" cellpadding="4" width="100%" align="center" > <TR> <TD class="spanhd" ><logic:present name="CHANNEL" property="image"> <a href="<bean:write name="CHANNEL" property="link"/>"> <img src="<bean:write name="CHANNEL"
property="image.URL"/>"></logic:present></a> </TD> <TD class="spanhd" width="100%"><bean:write name="CHANNEL" property="title"/> <a href="<bean:write name="CHANNEL" property="link"/>">[home]</a></TD> </TR> <TD class="yellow" colspan="2"><bean:write name="CHANNEL"
property="description"/></TD> </TR>
<TR> <TD class="datagrey" colspan="2"> <logic:iterate name="CHANNEL" property="items" id="ITEM"> <br><b><bean:write name="ITEM" property="title"/></b> <br><bean:write name="ITEM" property="description"/> <br> [ <a href="<bean:write name="ITEM"
property="link"/>">more</a> ] <br> </logic:iterate> </TD> </TR> </TABLE> <br> </logic:iterate>
</b></font></div>
Layouts 目前 tiles-example 有提供几种不同的 layout 可以参考
Layout Name | Parameters | Use |
---|
Class Layout | - title
- header
- menu
- body
- fotter
| 使用 <tiles:getAsString attribute="title"> 取得标题外, 其余使用 <tiles:insert attribute="menu"> | Menu Layout | - title
- items
| 使用 <tiles:getAsString attribute="title"> 取得标题外, 其余使用 org.apache.struts.tiles.beans.MenuItem iterate | VBox or VStack Layout | - list
| 使用 <tiles:useAttribute classname="java.util.List" name="list" id="list"> | Multi-columns Layout | - numCols
- list1
- list2 [optional]
- list3 [optional]
- listn [optional]
| 使用 <tiles:useAttribute classname="java.util.String" name="numCols" id="numColsStr"> 接着使用 <tiles:insert> 和 <tiles:put> 将资料放进来 | Center Layout | - header
- right
- body
- left
- footer
| 使用 <tiles:insert> 和 <tiles:put> 将资料放进来 | Tabs Layout | - tabList
- selectedIndex
- parameterName
| 这个几乎以上用到的观念都会用到 |
当然, 你也可以建立自己的 Layout , 我们希望你能建立符合 MVC 观念的 Layout!!
一:说明 Struts1.1以后增加了Tiles包使得struts在页面的处理方面多了一种选择.并且更容易实现代码的重用。Tiles中对页面的划分有点象jakarta的另外一个项目Turbine中的TDK。增加了layout的概念.其实就是把一个页面划分为几块。通常的来说一个页面大概可以划分为如下几块:head页面头部:存放一个运用的公共信息:logo等,如果是网站可能是最上面的一块.menu页面菜单:放置一个运用中需要使用的菜单,或者在每一个页面都使用的连接.footer页面尾部:如版权信息等.body页面主题内容:每个页面相对独立的内容.如果按上面的划分那对每一个页面我们只要写body里面的内容,其他的就可以共享重用.如果大多数页面的布局基本相同我们甚至可以使用一个jsp文件根据不同的参数调用不同的body.
二:Tiles配置和基本配置文件介绍 Tiles有一个配置文件:tiles-defs.xml tiles-defs.xml定义了每一个页面的组成元素和形式。 下面我将说明如下所示的一个tiles-defs.xml文件 tiles-defs.xml ----------------------------------------------- <tiles-definitions> <!--定义/layouts/classicLayout.jsp的组成名称为site.mainLayout--> <!--后面将附/layouts/classicLayout.jsp的内容--> <definition name="site.mainLayout" path="/layouts/classicLayout.jsp"> <put name="title" value="Tiles Blank Site" /> <put name="header" value="/tiles/common/header.jsp" /> <put name="menu" value="site.menu.bar" /> <!--menu的组成为site.menu.bar对应的页面--> <put name="footer" value="/tiles/common/footer.jsp" /> <put name="body" value="/tiles/body.jsp" /> </definition> <!--定义site.index.page,继承site.mainLayout--> <definition name="site.index.page" extends="site.mainLayout" > <put name="title" value="Tiles Blank Site Index" /> <put name="body" value="/tiles/body.jsp" /> <!--以上两个元素将替换site.mainLayout中的元素--> </definition>
<definition name="site.menu.bar" path="/layouts/vboxLayout.jsp" > <putList name="list" > <add value="site.menu.links" /> <add value="site.menu.documentation" /> </putList> </definition> </tiles-definitions>
附:/layouts/classicLayout.jsp -------------------------------- <html> <head> <title><tiles:getAsString name="title"/> </title> </head>
<body bgcolor="#ffffff" text="#000000" link="#023264" alink="#023264" vlink="#023264"> <table border="0" width="100%" cellspacing="5"> <tr> <td colspan="2"><tiles:insert attribute="header" /></td> </tr> <tr> <td width="140" valign="top"> <tiles:insert attribute='menu'/> </td> <td valign="top" align="left"> <tiles:insert attribute='body' /> </td> </tr> <tr> <td colspan="2"> <tiles:insert attribute="footer" /> </td> </tr> </table> </body> </html>
在web.xml里面配置tiles,配置完后对应struts action servlet的配置如下: web.xml ----------------- <!-- Action Servlet Configuration --> <servlet> <servlet-name>action</servlet-name> <!-- Specify servlet class to use: - Struts1.0.x: ActionComponentServlet - Struts1.1: ActionServlet - no Struts: TilesServlet --> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<!-- Tiles Servlet parameter Specify configuration file names. There can be several comma separated file names --> <init-param> <param-name>definitions-config</param-name> <param-value>/WEB-INF/tiles-defs.xml</param-value> </init-param>
<!-- Tiles Servlet parameter Specify Tiles debug level. O : no debug information 1 : debug information 2 : more debug information --> <init-param> <param-name>definitions-debug</param-name> <param-value>1</param-value> </init-param>
<!-- Tiles Servlet parameter Specify Digester debug level. This value is passed to Digester O : no debug information 1 : debug information 2 : more debug information --> <init-param> <param-name>definitions-parser-details</param-name> <param-value>0</param-value> </init-param>
<!-- Tiles Servlet parameter Specify if xml parser should validate the Tiles configuration file. true : validate. DTD should be specified in file header. false : no validation --> <init-param> <param-name>definitions-parser-validate</param-name> <param-value>true</param-value> </init-param>
<!-- Struts configuration, if Struts is used --> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>validate</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>2</param-value> </init-param> <init-param> <param-name>detail</param-name> <param-value>2</param-value> </init-param>
<load-on-startup>2</load-on-startup> </servlet>
三:使用Tiles 如果已经配置好tiels-defs.xml,接下来就可以在jsp文件中使用这些定义了。 有如下的方式使用tiles 3.1: <tiles:insert definition="site.mainLayout" flush="true" /> 插入site.mainLayout标记的一页
3.2: <tiles:insert template="/tutorial/basic/myFramesetLayout.jsp" > <tiles:put name="title" content="My first frameset page" direct="true" /> <tiles:put name="header" content="/tutorial/common/header.jsp" direct="true"/> <tiles:put name="footer" content="/tutorial/common/footer.jsp" direct="true"/> <tiles:put name="menu" content="/tutorial/basic/menu.jsp" direct="true"/> <tiles:put name="body" content="/tutorial/basic/helloBody.jsp" direct="true"/> </tiles:insert>
/tutorial/basic/myFramesetLayout.jsp --------------------------------- <html> <head> <title><tiles:get name="title"/></title> </head>
<frameset rows="73, *, 73"> <frame src="<%=request.getContextPath()%><tiles:get name="header" />" name="header" > <frame src="<%=request.getContextPath()%><tiles:get name="body" />" name="body" > <frame src="<%=request.getContextPath()%><tiles:get name="footer" />" name="footer" > </frameset>
</html> 插入/tutorial/basic/myFramesetLayout.jsp 并把title的值设定为:My first frameset page header设定为/tutorial/common/header.jsp
四:后记 Tiles的使用在他的文档里面写的比较详细。以上是一些简单和基本的使用。具体的文档可以看Struts里面的一个tiles-documentation.war的包。但即使是这个包也不是很全。可以通过上的的连接到作者的主页上去找。个人觉得使用Tiles在做企业运用的时候可能不如在做网站那样更能体现优越性。但在系统开始设计的时候考虑并规划好整个UI,那在修改和维护的时候将节省不少的工作量,因为通常UI的确定在代码编写结束和完成,所有尽可能的使用多个子页面构成一个页面,后面的美化和维护就比直接维护一个很大的页面容易
摘要
Hibernate和struts是当前市面上几个最流行的开源的库之一。它们很有效率,是程序员在开发Java企业应用,挑选几个竞争的库的首选。虽然它们经常被一起应用,但是Hibernate的设计目标并不是和Struts一起使用,而Struts在Hibernate诞生好多年之前就发布了。为了让它们在一起工作,仍然有很多挑战。这篇文章点明了Struts和Hibernate之间的一些鸿沟,尤其关系到面向对象建模方面。文章也描述了如何在两者间搭起桥梁,给出了一个基于扩展Struts的解决方案。所有的基于Struts和Hibernate构建的Web应用都能从这个通用的扩展中获益。
在Hibernate in Action(Manning,2004十月)这本书里,作者Christian Bauer和Gavin King揭示了面向对象世界的模型和关系数据模型,两个世界的范例是不一致的。Hibernate非常成功地在存储层(persistence Layer)将两者粘合在一起。但是领域模型(domain model)(也就是Model-View-Controller的model layer)和HTML页面(MVC的View Layer)仍然存在不一致。在这篇文章中,我们将检查这种不一致,并且探索解决的方案。
范例不一致的再发现
让我们先看一个经典的parent-child关系例子(看下面的代码):product和category。Category类定义了一个类型为long的标示符id和一个类型为String的属性name。Product类也有一个类型为long的标示符id和一个类型为Category的属性category,表示了多对一的关系(也就是说很多product可以属于一个Category)
/** * @hibernate.class table="CATEGORY" */ public class Category { private Long id;
private String name;
/** * @hibernate.id generator-class="native" column="CATEGORY_ID" */ public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
/** * @hibernate.property column="NAME" */ public String getName() { return name; }
public void setName(Long name) { this.name = name; } }
/** * @hibernate.class table="PRODUCT" */ public class Product { private Long id; private Category category;
/** * @hibernate.id generator-class="native" column="PRODUCT_ID" */ public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
/** * @hibernate.many-to-one * column="CATEGORY_ID" * class="Category" * cascade="none" * not-null="false" */ public Category getCategory() { return category; }
public void setCategory(Category category) { this.category = category; } }
我们希望一个product可以被更改category,所以我们的HTML提供了一个下拉框列出所有Category。
<select name="categoryId"> <option value="">No Category</option> <option value="1">Category 1</option> <option value="2">Category 2</option> <option value="3">Category 3</option> </select>
这里我们看出了两者的不一致:在Product领域对象里,category属性是Category类型,但是ProductForm只有一个类型为long的categoryId。这种不匹配不但增加了不一致,而且导致了不必要的代码进行primitive type的标示符和对应的对象之间的转换。
这种不一致部分是由于HTML Form自己引起的:它只代表了一种关系模型,不能代表面向对象的模型。面向对象和关系模型的不一致在存储层由对象关系映射(O/RM)解决。但是类似的问题在表示层(view layer)仍然存在。解决的关键是让他们一起无缝地工作。
Struts的功能和局限
幸运的是,Struts能够生成和解释内嵌的对象属性。Category下拉框可以用Struts page-construction(html) tag library:
<html:select property="category.id"> <option value="">No Category</option> <html:options collection="categories" property="id" labelProperty="name"/> </html:select>
我们假设categories是Category对象的一个list。所以现在我们要修改ProductForm,让它变得更加“面向对象”,我们要修改ProductForm的categoryId,改成类型为Category的category。这种改变会导致在Product和ProductForm之间复制属性的工作更加繁琐,因为两者有相同的属性。
public class ProductForm extends ActionForm { private Long id; private Category category; ... }
当我们完成剩余的Struts Action, configuration, validator, jsp, hibernate层后,开始测试,我们马上在访问ProductForm.category.id时遇到了NullPointerException。这是预料中的!因为ProductForm.category还没有被设置,同时,Hibernate也会将多对一所联系的对象引用设为空(如果database field为空指)(译者:这里指Hiberate将product.category为Null,如果该Product没有联系到任何category)。Struts要求所有的对象在显示(生成HTML Form)和传播(提交HTML FORM)之前被建立。
让我们看看如何用ActionForm.reset()来架起桥梁。
(并非如此)臭名昭著的Struts ActionForm
在我第一个星期接触Struts的时候,我最大的一个疑问就是:为什么我必须为Properties, getter方法, setter方法保持几乎完全相同的两份copy, 一份在ActionForm Bean, 一份在DomainObject。这个繁琐的步骤成了Struts社区最主要的抱怨之一。
以我的观点,ActionForm存在有原因的。首先,它们可以区别于Domain Object因为他们但当了不同的角色。在MVC模式下,Domain Object是Model层的一个部分,ActionForm是View层的。因为Webpage的Field和Database的Field可能不一样,某些特制的转换是常见的。第二,ActionForm.validate()方法可以定义非常好用的验证规则。第三,可能有其他的,特定的View行为,但是又不想在domain layer实现,特别当persistence framework来管理domain object的时候。
提交Form
让我们来用ActionForm内有的方法-reset()-来解决view和model之间的不一致。这个reset()方法是在ActionForm在被Struts Controller Servlet处理request时候复制ActionForm属性之前调用的。这个方法最常见的使用是:checkbox必须被显式地设为false,让没有被选中的checkbox被正确识别。Reset()也是一个初始化用于view rending对象的合适地方。代码看起来是这样的:
public class ProductForm extends ActionForm { private Long id; private Category category; ... public void reset(ActionMapping mapping, HttpServletRequest request) { super.reset( mapping, request ); if ( category == null ) { category = new Category(); } } }
Struts在使用用户提交的值填写ProductForm之前,Struts会调用reset(),这样category属性将会被初始化。请注意,你必须检查category看它是不是null,后面我们会讨论这个。
编辑Form
到目前为止,我们已经解决了form提交时候的问题。但是当我们在生成form页面的时候呢?Html:select tag也希望有一个非空的引用,所以我们将在form生成页面之前调用reset()。我们在action类里加入了一行:
public class EditProductAction extends Action { public final ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception { ... Product product = createOrLoadProduct(); ProductForm productForm = (ProductForm)form; PropertyUtils.copyProperties( productForm, product ); productForm.reset( mapping, request ); ... } }
我假设读者已经对action类和Jakarta commons Beanutils包非常熟悉了。CreateOrLoadProduct()建立了一个新的Product实例或者从数据库里载入一个已有的实例,具体取决于这个action是建立或者修改Product的。ProductForm被赋值后(译者:也就是调用PropertyUtils.copyProperties后),productForm.category已经从Product.category复制过来了(译者:实际上只是复制了category对象引用,并没有开销),然后,ProductForm就能用来生成页面了。我们同时也必须保证:不覆盖已经被Hibernate载入的对象,所以我们必须检查(category)是不是为null。
因为reset()方法是在ActionForm中定义的,我们可以把上述代码放入一个superclass,比如CommonEditAction,来处理这些事情:
Product product = createOrLoadProduct(); PropertyUtils.copyProperties( form, product ); form.reset( mapping, request );
如果你需要一个只读的Form, 你有两个选择: 第一检查所联系的jsp对象是不是null, 第二复制domain对象到ActionForm之后调用Reset()
保存domain对象
我们解决了提交Form和生成Form页面的问题, 所以Struts可以满足了。但是Hibernate呢?当用户选择了一个null ID option – 在我们的例子中“no category”option- 并且提交form, productForm.category指向一个新建立的hibernate对象,id为null。当category属性从ProductForm复制到Hibernate控制的Product对象并且存储时,Hibernate会抱怨product.category是一个临时对象,需要在Product存储前先被存储。当然,我们知道它是Null,并且不需要被存储。所以我们需要将product.category置为Null,然后Hibernate就能存储Product了(译者:在这种情况下,数据库product.category被设成空值)。我们也不希望改变Hibernate的工作方式,所以我们选择在复制到Domain对象之前清理这些临时对象,我们在ProductForm中加了一个方法:
public class ProductForm extends ActionForm { private Long id; private Category category; ... public void reset(ActionMapping mapping, HttpServletRequest request) { super.reset( mapping, request ); if ( category == null ) { category = new Category(); } }
public void cleanupEmptyObjects() { if ( category.getId() == null ) { category = null; } } }
我们在copyProperties之前清理掉这些临时对象,所以如果ProductForm.category只是用来放Null的,则将ProductForm.category置为Null。然后Domain对象的category也会被设成null:
public class SaveProductAction extends Action { public final ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception { ... Product product = new Product(); ((ProductForm)form).cleanupEmptyObjects(); PropertyUtils.copyProperties( product, form ); SaveProduct( product ); ... } }
一对多关系
我还没有解决Category到Product的一对多关系。我们把它加入到Category的Metadata中:
public class Category { ... private Set products; ...
/** * @hibernate.set * table="PRODUCT" * lazy="true" * outer-join="auto" * inverse="true" * cascade="all-delete-orphan" * * @hibernate.collection-key * column="CATEGORY_ID" * * @hibernate.collection-one-to-many * class="Product" */ public Set getProducts() { return products; }
public void setProducts(Set products) { this.products = products; } }
注意:Hibernate的cascade属性为all-delete-orphan表明:Hibernate需要在存储包含的Category对象时候,自动存储Product对象。和parent对象一起存储child对象的情况并不常见,常见的是:分别控制child的存储和parent的存储。在我们的例子中,我们可以容易地做到这一点,如果我们允许用户在同一个html page编辑Category和ProductS。用set表示Products是非常直观的:
public class CategoryForm extends ActionForm { private Set productForms; ... public void reset(ActionMapping mapping, HttpServletRequest request) { super.reset( mapping, request );
for ( int i = 0; i < MAX_PRODUCT_NUM_ON_PAGE; i++ ) { ProductForm productForm = new ProductForm(); productForm.reset( mapping, request ); productForms.add( productForm ); } }
public void cleanupEmptyObjects() { for ( Iterator i = productForms.iterator(); i.hasNext(); ) { ProductForm productForm = (ProductForm) i.next(); productForm.cleanupEmptyObjects(); } } }
更进一步
我们已经可以察看,编辑,提交forms,并且存储相关的objects,但是为所有的ActionForm类定义CleanupEmptyObjects()和reset()方法是个累赘。我们将用一个抽象的ActionForm来完成协助完成这些工作。
作为通用的实现,我们必须遍历所有的Hibernate管理的domain对象,发现他们的identifier,并且测试id值。幸运的是:org.hibernate.metadata包已经有两个Utility类能取出domain对象的元数据。我们用ClassMetadata类检查这个object是不是Hibernate管理的。如果是:我们把它们的id Value取出来。我们用了Jakarta Commons Beanutils包来协助JavaBean元数据的操作。
import java.beans.PropertyDescriptor; import org.apache.commons.beanutils.PropertyUtils; import org.hibernate.metadata.ClassMetadata;
public abstract class AbstractForm extends ActionForm { public void reset(ActionMapping mapping, HttpServletRequest request) { super.reset( mapping, request );
// Get PropertyDescriptor of all bean properties PropertyDescriptor descriptors[] = PropertyUtils.getPropertyDescriptors( this );
for ( int i = 0; i < descriptors.length; i++ ) { Class propClass = descriptors.getPropertyType();
ClassMetadata classMetadata = HibernateUtil.getSessionFactory() .getClassMetadata( propClass );
if ( classMetadata != null ) { // This is a Hibernate object String propName = descriptors.getName(); Object propValue = PropertyUtils.getProperty( this, propName );
// Evaluate property, create new instance if it is null if ( propValue == null ) { PropertyUtils.setProperty( this, propName, propClass.newInstance() ); } } } }
public void cleanupEmptyObjects() { // Get PropertyDescriptor of all bean properties PropertyDescriptor descriptors[] = PropertyUtils.getPropertyDescriptors( this );
for ( int i = 0; i < descriptors.length; i++ ) { Class propClass = descriptors.getPropertyType(); ClassMetadata classMetadata = HibernateUtil.getSessionFactory() .getClassMetadata( propClass );
if ( classMetadata != null ) { // This is a Hibernate object Serializable id = classMetadata.getIdentifier( this, EntityMode.POJO );
// If the object id has not been set, release the object. // Define application specific rules of not-set id here, // e.g. id == null, id == 0, etc. if ( id == null ) { String propName = descriptors.getName(); PropertyUtils.setProperty( this, propName, null ); }
} } } }
为了让代码可读,我们省略了Exception的处理代码。
我们的新AbstractForm类从Struts的ActionForm类继承,并且提供了通用行为:reset和cleanup多对一关联对象。当这个关系是相反的话(也就是一对多关系),那么每个例子将会有所不同,类似在Abstract类里实现是比较好的办法。
总结
Struts和Hibernate是非常流行和强大的框架,他们可以有效地相互合作,并且弥补domain模型和MVC视图(view)之间的差别。这篇文章讨论一个解决Struts/Hibernate Project的通用的方案,并且不需要大量修改已经有的代码。
人人都爱Spring加Hibernate。 但Spring MVC+hibernate的Sample如Appfuse的代码却算不得最简洁优美好读,如果在自己的项目中继续发挥我们最擅长的依样画葫芦大法,美好愿望未必会实现。 所以,Pramatic精神不灭。这个系列就是探寻最适合自己的Spring+Hibernate模式。 I-配置文件简化 我厌倦一切配置文件繁重的框架。 最好的情况是,框架提供极端灵活复杂的配置方式,但只在你需要的时候。 Spring提供了三种可能来简化XML。随着国内用户水平的提高,这些基本的简化技巧大家都已掌握。 大家可以直接看第3,第4点--Spring 1.2, Spring 2.0的后继改进。
1.1.autowire="byName" /"byType" 假设Controller有一个属性名为customerDAO,Spring就会在配置文件里查找有没有名字为CustomerDAO的bean, 自动为Controller注入。 如果bean有两个属性,一个想默认注入,一个想自定义,只要设定了autowire,然后显式的声明那个想自定义的,就可以达到要求。这就应了需求,在需要特别配置的时候就提供配置,否则给我一个默认注入。
还有一个更懒的地方,在最最根部的<beans>节点写一句default-autovwrie="byName",可以让文件里的所有bean 都默认autowrie。 不过Rod认为开发期可以这样,但Production Server上不应该使用Autowire。而我觉得那些自定义一次的地方比如TranscationManager应该详细定义,而Dao,Controller这种大量重复定义的bean就可以偷点懒了。 1.2.<bean>节点之间抽象公共定义和 Inner Bean 这太方便懒人了,想不到两个独立的XML节点都可以玩继承和派生,子节点拥有父节点的全部属性。 最好用的地方就是那个Transtion Proxy的定义。先定义一个又长又冗的父类,然后用子类去继承它。 另外,还有一个Inner Bean的机制,可以把DAO写成Proxy的内部类。为什么要写成内部类?为了让Proxy冒名顶替它去让Controller Autowire。(详见后面的示例) 1.3. 宽松的配置, To XML or Not to XML 据说Spring比Struts的配置宽松了很多,这就给人把东西从配置文件中撤回原码中的机会。 不赞成什么都往配置文件里晒,造成了Rich Information的配置文件,修改或者查看的时候,要同时打开配置文件和原码才能清楚一切。 而我希望配置文件就集中做一些整体的配置,还有框架必须的、无需管理的冗余代码。而一些细节的变化不大的配置和逻辑,就尽量别往里塞了。因此,Success/Fail View 的配置,不建议放在里面。
2.简化后的配置文件 1.Controller只剩下一句 <bean name="customerController" class="org.springside.bookstore.web.CustomerController" autowire="byName"/> 2.DAO也只剩一句 <bean id="customerDAO" class="org.springside.bookstore.dao.CustomerDao"/> 3.Service类只剩下5行 <bean id="customerManager" parent="baseTxService"> <property name="target"> <bean class="org.springside.bookstore.service.CustomerManager"/> </property> </bean> 3.Spring 1.2后xml语法简化
最主要的简化是把属性值和引用bean从子节点变回了属性值,对不喜欢autowire的兄弟比较有用。 当然,如果value要CDATA的时候还是要用子节点。另外,list的值可以用空格隔开也比较实用。
<property name="myFriendList"> <list> <value>gigix</value> <value>wuyu</value> </list> </property> 简化为 <property name="myFriendList" value="gigix wuyu"/>
4.Spring 2.0来了 如果没什么外力刺激,spring xml 可能就这样不会变了。但现在xml成了过街老鼠,被ror的默认配置和JDK5的annotation逼得不行,当然就要继续求变。 比如有好事者认为,节点名必须以bean打头,附加一个属性id来表示bean名;属性值必须搞一个property子节点,子节点上有个属性name来表示属性名,是给机器看的很不直观的东西。
<bean id="customerDAO" class="org.springside...CustomerDAO"> <property name="maxCount" value="10"> </bean> 给人看的东西应该就写成
<customerDAO class="org.springside....CustomerDAO" maxCount="10"/> Spring 2.0正用schema实现类似的语法,具体请看它的JPetStore sample。
5.使用Spring自带的DTD使编辑器Smart. 如果没有用Eclipse的Spring插件,那至少也要使用spring自带的dtd使XML编辑器smart一些,能够自动为你生成属性,判断节点/属性名称有没有拼错等。
6.还有更变态的简化配置方法 比如autoproxy,不过我觉得更简化就不可控了,所以没有采用。
因为Spring自带的sample离我们的实际项目很远,所以官方一点的model层模式展现就靠Appfuse了。 但Appfuse的model层总共有一个DAO接口、一个DAOImpl类、一个Service接口、一个ServiceImpl类、一个DataObject.....大概只有受惯了虐待的人才会欣然接受吧。 另外,Domain-Driven逢初一、十五也会被拿出来讨论一遍。
其实无论什么模式,都不过是一种人为的划分、抽象和封装。只要在团队里理解一致,自我感觉优雅就行了。 我的建议是,一开始DO和Manager一生一旦包演全场,DO作为纯数据载体,而Manager类放置商业方法,用getHibernateTemplate()直接访问数据库,不强制基于接口编程。当某天系统复杂到你直觉上需要将DAO层和Service层分开时,再分开就好了。
1.DataObject类 好听点也可以叫Domain Object。Domain Driven Development虽然诱人,但因为Java下的ORM框架都是基于Data Mapper模式的,没有Ruby On Rails中那种Active Recorder的模式。所以,还是压下了这个欲望,Data Object纯粹作一个数据载体,而把数据库访问与商业逻辑操作统一放到Manager类中。
2.Manager类 我的Manager类是Appfuse中DAO类与Service类的结合体,因为:
2.1 不想使用纯DAO 以往的DAO是为了透明不同数据库间的差异,而现在Hibernate已经做的很好。所以目前纯DAO的更大作用是为了将来可以切换到别的ORM方案比如iBatis,但一个Pragmaic的程序员显然不会无聊到为了这个机会不大的理由,现在就去做一个纯DAO层,项目又不是Appfuse那样为了demo各种ORM方案而存在。
2.2 也不使用纯的薄Service层 在JPetStore里有一个很薄的Service层,Fascade了一堆DAO类,把这些DAO类的所有方法都僵硬的重复了一遍。而我认为Fascade的意义在二: 一是Controller调用Manager甲的时候,总会伴随着调用Manager乙的某些方法。使用Fascade可以避免Controller零散的调用一堆Manager类。 二是一个商业过程里可能需要同时调用DAO甲乙丙丁的方法。
这些时候,Fascade都是合理的。但我讨厌类膨胀,所以我宁愿在甲乙丙丁中挑一个来充当Fascade的角色。有耦合的问题吗?对一个不是死搬书的Designer来说,组件边界之内的类之间的耦合并不是耦合。
3.去除不必要的基于接口编程 众所周知,Spring是提倡基于接口编程的。 但有些Manager类,比如SaleOrderManager ,只有5%的机会再有另一个Impl实现。95%时间里这两兄弟站一起,就像C++里的.h和.cpp,徒增维护的繁琐(经常要同步两个文件的函数声明),和代码浏览跳转时的不便(比如从Controler类跟踪到Service类时,只能跳转到接口类的相应函数,还要再按一次复杂的热键才跳转到实现类) 连Martin Flower都说,强制每个类都分离接口和实现是过犹不及。只在有多个独立实现,或者需要消除对实现类的依赖时,才需要分离接口。
3.1 DAO被强制用接口的原因 Spring IOC本身是不会强制基于接口的,但DAO类一般要使用Spring的声明式事务机制,而声明式的事务机制是使用Spring AOP来实现的。Spring AOP的实现机制包括动态代理和Cgilib2,其中Spring AOP默认使用的Java动态代理是必须基于接口,所以就要求基于接口了。 3.2 解决方法 那就让Spring AOP改用CGLib2,生成目标类的子类吧,我们只要指定使用声明式事务的FactoryBean使用CGLib的方式来实现AOP,就可以不基于接口编程了。 指定的方式为设置proxyTargetClass为true。如下:
<bean class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" id="baseService" abstract="true"> <property name="transactionManager" ref="transactionManager"/> <property name="proxyTargetClass" value="true"/>
</bean> 又因为这些Service Bean都是单例,效率应该不受影响。
4.总结 对比Appfuse里面的5个类,我的Model层里只有VO作为纯数据载体,Manager类放商业方法。有人说这样太简单了,但一个应用,要划成几个JSP,一个Controller,一个Manager,一个VO,对我来说已经足够复杂,再要往上架墙叠屋,恕不奉陪,起码在我的项目范围里不需要。(但有很多项目是需要的,神佑世人)
后记:迫于世人的压力,SpringSide还是把DAO和Service层分开了,但依然坚持不搞那么多接口。
Struts与Webwork的扇子请跳过本篇。
MVC不就是把M、V、C分开么?至唯物朴素的做法是两个JSP一个负责View,一个负责Controller,再加一个负责Model的Java Bean,已经可以工作得很好,那时候一切都很简单。 而现在为了一些不是本质的功能,冒出这么多非标准的Web框架,实在让人一阵郁闷。像Ruby On Rails那样简捷开发,可用可不用,而且没有太多的限制需要学习的,比如Webwork这型还可以考虑。但像Struts那样越用框架越麻烦,或者像Tapestry那样有严重自闭倾向,额上凿着"高手专用玩具"的,用在团队里就是不负责任的行为了。
so,我的MVC方案是使用Spring MVC的Controller接口,写最普通的JavaBean作为Controller,本质就和当年拿JSP作Controller差不多,但拥有了Spring IOC的特性。 之所以用这么消极的选择标准,是因为觉得这一代MVC框架离重回RAD时代的标准还很远,注定了只是一段短暂的,过渡的技术,不值得投资太多精力和团队学习成本。 1. 原理 Spring MVC按植物分类学属于Martin Flower〈企业应用模式〉里的静态配置型Front Controler,使用DispatchServlet截获所有*.do的请求,按照xml文件的配置,调用对应的Command对象的handleRequest(request,response)函数,同时进行依赖对象的注入。 我们的Controller层,就是实现handleRequest(request,response)函数的普通JavaBean。 2. 优势 Spring MVC与struts相比的优势:
一是它的Controller有着从松到紧的类层次结构,用户可以选择实现只有一个HandleRequest()函数的接口,也可以使用它有很多回调函数的SimpleFormController类。 二是不需要Form Bean,也不需要Tapestry那所谓面向对象的页面对象,对于深怕类膨胀,改一个东西要动N个地方的人最适合不过。 三是不需要强XML配置文件,宣告式编程是好的,但如果强制成框架,什么都要在xml里面宣告,写的时候繁琐,看的时候也要代码配置两边看才能明白就比较麻烦了。 那Webwork呢?没有实战过,不过因为对MVC框架所求就不多,单用Spring MVC的Controller已经可以满足需求,就不多搞一套Webwork来给团队设坎,还有给日后维护,spring,ww2之间的版本升级添麻烦了。真有什么需要添加的,Spring MVC源代码量很少,很容易掌控和扩展。 3.化简 3.1. 直接implement Controller,实现handleRequest()函数 首先,simple form controller非我所好,一点都不simple。所以有时我会直接implement Controller接口。这个接口的唯一函数是供Front Controller调用的handleRequest(request,response)。 如果需要application对象,比如想用application.getRealPath()时,就要extends webApplicationObjectSupport。
3.2.每个Controler负责一组相关的action 我是坚决支持一个Controler负责多个action的,一个Controler一个action就像一个function一个类一样无聊。所以我用最传统的方式,用URL参数如msg="insert"把一组相关action交给一个Controler控制。ROR与制作中的Groovy On Rails都是这种模式,Spring也有MultiActionController支持。 以上三者都是把URL参数直接反射为Controller的函数,而 Stripes的设计可用annotation标注url action到响应函数的映射。 我的取舍很简单,反正Spring没有任何强制,我只在可能需要不重新编译而改变某些东西的时候,才把东西放在xml里动态注入。jsp路径之类的就统统收回到controller里面定义. 3.4.Data Binder Data Binder是Controller的必有环节,对于Spring提供的DataBinder,照理完全可用,唯一不爽是对象如果有内嵌对象,如订单对象里面包含了Customer对象,Spring需要你先自行创建了Customer对象并把它赋给了Order对象,才可能实现order.customer.customer_no这样的绑定。我偷懒,又拿Jakarta BeanUtils出来自己做了一个Binder。
3.5.提取基类 最后还是忍不住提取了一个基类,负责MultiAction和其他一些简便的方法。Sprnig的MultiActionController做得太死,规定所有函数的第1,2个参数必须是request和response,不懂动态的,温柔的进行参数注入。 经过化简再化简,已经是很简单一个Java Bean ,任谁都可以轻松上手,即使某年某月技术的大潮把现在所有MVC框架都淹没了,也不至于没人识得维护。 人生像个舞台,请良家少女离开。 同样的,Freemarker和Velocity爱好者请跳过本篇。与弃用webwork而单用Spring MVC Controller接口的理由一样, Freemarker本来是一样好东西,还跨界支持jsp 的taglib,而且得到了WebWork的全力支持,但为了它的非标准化,用户数量与IDE的缺乏,在View层我们还是使用了 保守但人人会用,IDE友好的JSP2.0 配合JSTL。
对于B/S结构的企业应用软件来说,基本的页面不外两种,一种是填Form的,一种是DataGrid 数据列表管理的,再配合一些css, js, ajax的效果,就是View层要关注的东西了。 1. JSP 2.0的EL代替<c:out>JSP2.0可以直接把EL写在html部分,而不必动用<c:out>节点后,老实说,JSP2.0+JSTL达到的页面效果,已不比Velocity相差多少了。
<p>{goods.name}</p> 代替 <p><c:out value="{goods.name}"/></p> (除了EL里面不能调用goods的函数,sun那帮老顽固始终坚持JSTL只能用于数据显示,不能进行数据操作,所以不能调用bean的get/set外的方法)
2. 最懒的form 数据绑定 Spring少得可怜的几个tag基本上是鸡肋,完全可以不要。 而Spring开发中的那些Simple Form tag又还没有发布。Spring的Tag主要用来把VO的值绑到input框上。但是,和Struts一样,需要逐个Input框绑定,而且语法极度冗长,遇到select框还要自己进行处理.....典型的Spring Sample页面让人一阵头晕. 而jodd的form tag给了我们懒人一个懒得多的方法,只要在<form>两头用<jodd:form bean="myVO"></jodd:form>包住,里面的所有input框,select框,checkBox...统统自动被绑定了,这么简单的事情,真不明白struts,spring为什么不用,为了不必要的灵活性么?
<form> <jodd:form bean="human"> <input type="text" name="name"> <input type="radiobox" name="sex" value="man"> <select name="age"> <option value="20">20</option> <option value="30">30</option> </select> </jodd:form> </form>
不过,jodd有个致命弱点是不能绑定内嵌对象的值。比如Order(订单)对象里有个Customer(顾客)对象,jodd就不能像 struts,spring一样用如下语法绑定: <input name="customer.customerNo"> 这是因为它的beanUtils比Jakata Common弱,用了一个错误的思路的缘故。 动用beanUtils修改一下就可以了,修改后的源码可以在这里下载。 3. DataGrid数据列表 DisplayTag和ValueList都属于这种形式的Tag Library。但最近出现的Extreme Table是真正的killer,他本身功能强大不说,而且从一开始就想着如何让别人进行扩展重载,比如Extend Attributes机制就是DisplayTag这样的让千人一面者不会预留。
4.css, java script, ajax 天下纷扰,没有什么特别想讲想推荐的,爱谁谁吧。Buffalo, DWR, Scriptaculous, Prototype, AjaxTags, AjaxAnywhere, Rico, Dojo, JSON-RPC,看着名字就头痛。
摘要: 优化Spring启动速度
spring的容器是提供了lazy-load,缺省设置是bean没有lazy-load,该属性处于false状态。
... 阅读全文
|
|
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|
常用链接
留言簿(5)
随笔分类
随笔档案
好的blog
好的站点
搜索
最新评论
阅读排行榜
评论排行榜
|
|