花了一整个晚上的时间,摸索着 google code wiki 的用法,终于磕磕碰碰地把这份文档写完了。期间还试过写了一半浏览器没有响应,我真的连杀人的心都有了。
最后,终于皇天不负有心人,总算把他给弄出来了。以后有什么功能修正,或者加什么新的 Annotation,我就直接在 wiki 上改了。
文档地址:http://code.google.com/p/easy-eao/wiki/Reference
这下谁再问过 EasyEAO 怎么用,偶就发 URL 给他,嘻嘻
花了一整个晚上的时间,摸索着 google code wiki 的用法,终于磕磕碰碰地把这份文档写完了。期间还试过写了一半浏览器没有响应,我真的连杀人的心都有了。
最后,终于皇天不负有心人,总算把他给弄出来了。以后有什么功能修正,或者加什么新的 Annotation,我就直接在 wiki 上改了。
文档地址:http://code.google.com/p/easy-eao/wiki/Reference
这下谁再问过 EasyEAO 怎么用,偶就发 URL 给他,嘻嘻
这段时间好不容易没有课要讲,可以歇一下了。于是就把一年多前的一个项目重新翻了出来重构了一下,做了个小小的开源项目(EasyEAO) 放到 Google Code 上。呵呵,所以这段时间一直都没有更新日志就是在忙这个事情咯。不过万恶的资本主义是不会让偶一直闲着的,接下来又是一个忙碌的时间段了,555…
还是说一下 EasyEAO 是做啥用的吧,相信只要做过分层结构应用开发的人,应该没有人会不知道 DAO 是什么东西的吧?DAO 这东西最初出现时是为了解决两个很重要的任务,一个就是解决应用与数据库的无关性,另一个就是把数据记录封装成 DTO 形式返回给应用。不过两这个责任在 ORM 框架日盛的今日,已经不需要 DAO 负责了。直接使用实体(Entity)进行跨层的数据传递,比使用 DTO 更为敏捷、优雅。数据库无关性的问题也不再需要开发人员考虑,ORM 框架都能很好地帮我们解决问题。这个年代的 DAO 已经变成了对实体进行存取操作,完成增、删、改、查方法的简单接口。这个时候,你不觉得他的名字不太恰当吗?因此他有了个新的名字叫 EAO (Entity Access Object)。嘻嘻,扫盲完成。
EasyEAO 的目的就是让你写 EAO 更快更好。不,应该说,让你不再需要写EAO,只需要设计EAO。他没有打算做成一个会改变你开发习惯的大个子,他很小非常小,只是一个工具,可以加入到你的开发框架当中,简化你的开发过程,让你更敏捷更高效。你可以在主流的开发框架(例如:Spring、EJB3)内使用他(目前已经完成对Spring的支持),他会提供对主流开发框架的支持。不过就算他没有提供支持也没有关系,你可以自己完成扩展。EAO 常用的存取方法,他都会支持,不过就算他没有提供支持也无所谓,你也可以自己动手添加支持。
技术的东西说是说不清楚的,还是让代码来告诉我们,EasyEAO 是什么,他可以干啥。下面就以 Spring 环境为例,示范一个实体的 EAO 使用 EasyEAO 进行开发的全过程。
创建实体类
1: public class Exam {2:
3: private Integer id;4: private String subject;5: private String student;6: private int grade;7:
8: // Getter and Setter ...9: }
完成Mapping
1: <hibernate-mapping package="org.easy.eao.example">2:
3: <class name="Exam" table="exam">4: <id name="id">5: <generator class="identity" />6: </id>7: <property name="subject" not-null="true" />8: <property name="student" not-null="true" />9: <property name="grade" not-null="true" />10: </class>11:
12: <query name="Exam.listByStudent"><![CDATA[13: from Exam e where e.student = :student order by e.grade
14: ]]></query>15:
16: </hibernate-mapping>
设计 EAO 接口
你只需要考虑你的应用需要些什么方法来存取实体,完全不需要考虑如何实现,应该是根本不需要实现。只需要在定义完方法后,给方法加上特定的 @Annotation ,并对查询方法设置正确的查询语句即可。
1: public interface ExamEao {2:
3: @Persist4: void create(Exam... exams);5:
6: @Remove7: void remove(Exam... exams);8:
9: @Merge10: void update(Exam... exams);11:
12: @Retrieve13: Exam get(Integer id);
14:
15: @Retrieve(lazy = true)16: Exam load(Integer id);
17:
18: @Query(query = "from Exam e where e.subject = ? order by e.grade")19: List<Exam> listBySubject(String subject);
20:
21: @Query(query = "from Exam e where e.subject = :s1 and e.student = :s2")22: Exam getByStudentAndSubject(
23: @Param(name="s2") String Student,24: @Param(name="s1") String subject);25:
26: @Paging(size = 5,
27: query = @Query(query = "from Exam e order by e.grade"))28: Page<Exam> pagingAll(@Number int page);29:
30: @Query(query = "select new org.easy.eao.example.ExamInfo(" +31: "e.student, max(e.grade), min(e.grade), sum(e.grade), avg(e.grade)" +32: ") from Exam e where e.student = ? group by e.student")33: ExamInfo getStudentExamInfo(String student);
34:
35: @Query(named = "Exam.listByStudent")36: List<Exam> listByStudent(@Param(name="student") String student);37: }
将 EAO 接口配置为 Spring 环境中的 Bean
除了完成必须的持久化环境配置外,只需要在 Spring Context 中加入下面的3个Bean的配置,你就可以使用你所定义的 EAO 接口了。而这3个Bean中 eaoFactory 和 abstractEao 都只需定义一次就可反复使用,如果你还有其他的 EAO Bean 就只需不断重复使用 abstractEao 进行定义即可。
1: <!-- EAO创建工厂 -->2: <bean id="eaoFactory" class="org.easy.eao.spring.SpringEaoFactory">3: <property name="actionBuilder">4: <bean class="org.easy.eao.spring.SpringActionBuilder" />5: </property>6: </bean>7:
8: <!-- 配置模板定义 -->9: <bean id="abstractEao" class="org.easy.eao.spring.hibernate.HibernateEaoBean"10: abstract="true">11: <property name="eaoFactory" ref="eaoFactory" />12: </bean>13:
14: <!-- 定义一个 EAO Bean -->15: <bean id="examEao" parent="abstractEao">16: <property name="eaoInterface" value="org.easy.eao.example.ExamEao" />17: </bean>
测试并 Enjoy 你的 EAO
测试的代码我就不列出了,如有兴趣可以下载该例子的项目源码自行测试。
依赖类库
在上一篇教程里,我们已经把一个 JSF 1.2 的项目创建出来了。那个项目已经有了最基本的 JSF 项目配置信息,在 WEB-INF 目录下的 web.xml (部署描述文件)内已经帮我们加入了 JSF 的配置信息,还在同一目录下帮我们创建了名为 faces-config.xml 的配置文件。
声明 Faces Servlet 和映射
在使用 JSF 时,请求通过 Faces Servlet 来处理。这个 Faces Servlet 的配置和普通的 Servlet 配置没有什么差别。再看下面的 mapping 设置,设置让容器把以 jsf 结尾的请求都交给 JSF 处理,JSF 会将请求转发给相应的 jsp 页面。这就是说,当我们访问 faces/hello.jsf 这个地址的时候,实际上是在访问 faces/hello.jsp 这个页面。那 JSF 在这中间,起到什么作用呢?它在显示页面之前初始化 Faces 上下文和视图根。视图根包含 JSF 组件树。Faces 上下文是与 JSF 进行交互的方式。
创建 HelloBean 类并声明为托管Bean
现在,创建一个名为 HelloBean 的POJO,然后在 faces-config.xml 文件将它声明为一个托管Bean。托管Bean的存活范围(作用域),你可以设定为none、request、session或application。设定为none时,会在需要使用的时候创建新的实例。剩下那几个作用域的含义与 jsp 内的几个作用域的含义并无不同。(在完成应用后,你可以修改该托管Bean的存活范围,观察变化)
编写 JSF 页面
接下来我们就可以开始编写 JSF 页面了,JSF 页面其实就是 JSP 页面,只是里面使用了 JSF 特有的 Taglib 而已。我们可以使用 Eclipse 的创建向导帮助我们创建该页面,注意最后在选择页面模板的时候,我们应该选择 JSF 的页面模板。这样创建出来的页面就已经帮我们导入了必要的 JSF 标记库了。
JSF 标记库分为 html 和 core 两部分,html 标记库包含用来处理表单和其他 HTML 相关元素的所有标记。core 标记库包含 JSF 特有的所有逻辑、检验、控制器和其他标记。一个JSF 页面最重要的就是要有<f:view>标记,这个标记告诉容器管理在该标记内的组件,如果没有<f:view>,JSF 就无法构建组件树,以后也无法搜索已经创建的组件树。
接下来,我们就可以在页面内完成我们的界面布局了。这就要用到大量的 html 标记库的标记来完成布局,并与托管Bean绑定。JSF 内的所有 html 标记都有一个名为 rendered 的属性,该属性可以决定是否显示该标记及其子内容。还有标记属性的赋值,我们可以通过 JSF EL(JavaServer Faces Expression Language) 进行。这是一种非常像 JSTL EL 的表达式,它可以协助我们完成界面控件与托管Bean的绑定,或使用托管Bean的属性完成赋值。
编写 HTML 页面
最后再创建一个index.html页面,使访问该应用时可以出现默认页面。该页面只需提供一个链接,以便用户可直接点击测试 JSF 页面是否工作正常。接下来就可以部署该应用,准备进行测试了。
部署与测试
打开服务器视图,添加应用到 Tomcat 服务器,然后点击启动按钮。如果 Console 窗口没有出现异常的错误信息,那么恭喜你,你的应用已经顺利启动了。
接下来,你就可以打开浏览器,输入该地址 http://127.0.0.1:8080/first 访问你的第一个 JSF 应用。
你会发现,当你第一次点击默认页面的链接进入 JSF 页面时,应用会不知道你的身份而让你输入你的名称。之后,你再次进入该 JSF 页面,应用会记住你的身份,无需你再次输入名称(这是因为托管Bean的存活范围是session)。
ps:
我的妈呀,写这种手把手的教程真是不是人干的活!太花时间了!!!
因此,后面不玩 Step by Step 了,还是解说代码比较适合偶的风格。
Java™Server Faces(JSF)技术是一种服务器端框架,它提供一种基于组件的 Web 用户界面开发方式。er…好烦,本来想写一下 JSF 的简介的,不过发现 IBM developerWorks 上的说明已经非常好了,我又不想把它们直接“借过来”,因此这里省略掉N字。(IBM developerWorks 上的 JSF 入门级文章)
那么,我们就从如何搭建 JSF 开发环境开始咯。首先当然先是去下载 JSF 1.2 的实现啦。这一步,我们可以凭个人喜好下载 Sun 的参考实现(下载地址),或 Apache 的 MyFaces (下载地址)。在解压后就可以进入 Eclipse JEE 配置 JSF Libraries 。
然后,把 JSF 的参考实现的库和 JSTL 的库都设置进去。
这样开发环境的准备工作就完成了,我们就可以正式开始开发一个 JSF 项目了。首先,先创建一个动态的 Web 项目。这个时候不要忘记设置你的部属服务器设置(Target Runtime),不过最重要的还是把项目的配置设为 "JavaServer Faces v1.2 Project” 。
在接下去就是配置 JSF Libraries 和部署文件(web.xml)中的配置信息了,然后再点击 "Finish” 一个 JSF 项目就搭建好了。
截图好累呀,下次还是直接开说代码算了。
今天有学生问到我“如何写出高性能的程序?”我就跟他说:“高性能的程序一开始的性能也不一定高的,但必须是一个可以完成功能且地球人看得懂的程序,性能是不断重构后逐步提高的。”他还是对这个软件重构提高性能的过程充满迷惑,我只好继续说:“提高性能无非就是找出问题的潜在规律……(这里省略掉N个字),然后以一种适合计算机工作的方式……(这里又省略掉N个字)对程序进行改造而已。”听完我这段话,我感觉那位同学更加迷茫了。纯理论的东西听不懂,又要老子深入浅出举例说明了。
张口就回到了前些天的“八皇后问题”的程序上了,前些日子的那个程序能工作吗?当然可以啦。性能呢?呵呵,这就不敢恭维了。那个程序就像我们为完成某一功能或解决某一问题,所写程序的第一个版本。那时候我们对问题的了解并不深入,只是不断把大问题分解成小问题,然后写出解决小问题的代码,最后再把这些代码组织起来把大问题给解决掉。随着对问题的深入认识,我们就能发现问题的更多潜在规律。仔细观察“八皇后问题”,我们就会发现其中一些潜在的规律。就拿棋盘上的对角线来说,就有公式:ROW + COL 和 ROW – COL + 7 ,分别对应左右两个方向的对角线。那么,我们就可以利用这些规律对我们的程序进行重构,让他的性能更佳。(下面就给出调整后的程序)
package com.dc.queen8; public class EightQueenDiagonal { private static int limit = 8; // 边界 private static int sum = 0; // 找到的方案数 // 棋盘数组,数组元素为 true 表示在该位置放入了皇后 private static boolean[][] chessboard = new boolean[limit][limit]; // 列冲突检测数组 private static boolean[] chkColumn = new boolean[limit]; // 左对角线冲突检测数组 private static boolean[] chkLeftDiagonal = new boolean[limit * 2 - 1]; // 右对角线冲突检测数组 private static boolean[] chkRightDiagonal = new boolean[limit * 2 - 1]; /** * 坐标点对象 * @author Frank */ static class Point { private int row; // 行坐标 private int col; // 列坐标 public Point(int row, int col) { this.row = row; this.col = col; } } public static void main(String[] args) { putLine(0); } /** * 显示棋盘的情况 */ private static void show() { System.out.println("方案:" + ++sum); for (int x = 0; x < limit; x++) { for (int y = 0; y < limit; y++) { System.out.print((chessboard[x][y] ? "●" : "○") + " "); } System.out.println(); } } /** * 放入指定行的皇后 * @param row 行号 */ private static void putLine(int row) { // 从第一列开始尝试放入皇后 for (int column = 0; column < limit; column++) { Point p = new Point(row, column); if (check(p)) { // 检查指定点是否有效 setPoint(p); if (p.row < limit - 1) { putLine(p.row + 1); // 放入下一行的皇后 } else { show(); // 显示结果 } clearPoint(p); // 回溯并清除放入的皇后 } } } /** * 向指定点放入皇后 * @param p */ private static void setPoint(Point p) { chessboard[p.row][p.col] = true; chkColumn[p.col] = true; chkLeftDiagonal[p.row + p.col] = true; chkRightDiagonal[p.row - p.col + limit - 1] = true; } /** * 清除指定点的皇后 * @param p */ private static void clearPoint(Point p) { chessboard[p.row][p.col] = false; chkColumn[p.col] = false; chkLeftDiagonal[p.row + p.col] = false; chkRightDiagonal[p.row - p.col + limit - 1] = false; } /** * 检查新放入的皇后坐标,是否会给棋盘上的其它皇后吃掉 * @param p * @return true 新坐标有效,false 新坐标无效 */ private static boolean check(Point p) { if (chkColumn[p.col] || chkLeftDiagonal[p.row + p.col] || chkRightDiagonal[p.row - p.col + limit - 1]) return false; else return true; } }
程序经过这样的重构后,性能就有了更大的提高。找出规律并用计算机的方式思考问题(计算机是用二进制思考的)然后重构,我们就可以得到性能更优的程序。
受“新年病毒”的影响,偶的学生脑袋变笨了。为了让他们尽快从“疾病”中恢复过来,今天第一天上课就给他们来了道算法题——八皇后问题。
八皇后问题
这是是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
这群家伙也真行,一个春节就把全部知识还回来了。半个多小时过去,也没有多少人的代码有点像样的感觉。没法子,只能自己DIY一份给他们参考。
package com.dc.queen8; public class EightQueen { static int limit = 8; // 边界 // 棋盘数组,数组元素为 true 表示在该位置放入了皇后 private static boolean[][] chessboard = new boolean[limit][limit]; /** * 坐标点对象 * @author Frank */ static class Point { private int x; // 行坐标 private int y; // 列坐标 public Point() {} public Point(int x, int y) { this.x = x; this.y = y; } } public static void main(String[] args) { int i = 1; Point p = new Point(); while (find(p)) { System.out.println("方案:" + i++); show(); // 显示结果 p = next(prev(last())); // 找后续可用的位置(穷举的关键) if (p == null) break; } } /** * 显示棋盘的情况 */ private static void show() { for (int x = 0; x < limit; x++) { for (int y = 0; y < limit; y++) { System.out.print((chessboard[x][y] ? "●" : "○") + " "); } System.out.println(); } } /** * 获取最后一个皇后的位置 * @return */ public static Point last() { int row = limit - 1; int y = 0; while (true) { if (chessboard[row][y]) break; else y++; } chessboard[row][y] = false; return new Point(row, y); } /** * 重指定点开始向下找解决方案 * @param p 开始 * @return true 找到解决方案,false 找不到解决方案 */ private static boolean find(Point p) { while (check(p)) { p = next(p); if (p == null) return false; } chessboard[p.x][p.y] = true; if (p.x < (limit - 1)) return find(new Point(p.x + 1, 0)); return true; } /** * 找下一个点 * @param p * @return null 表示找不到可用点 */ private static Point next(Point p) { if (p.x == 0 && p.y == limit - 1) return null; if (p.y < (limit - 1)) { p.y++; return p; } else { return next(prev(p)); } } /** * 回溯上一个点 * @param p * @return */ private static Point prev(Point p) { int row = p.x - 1; for (int i = 0; i < limit; i++) { if (chessboard[row][i]) { chessboard[row][i] = false; // 清除回溯点的值 return new Point(row, i); } } return null; } /** * 检查新放入的皇后坐标,是否会给棋盘上的其它皇后吃掉 * @param p * @return false 新坐标有效,true 新坐标无效 */ private static boolean check(Point p) { // 检查同列(使用Point.y作为检查边界,节省时间) for (int i = 0; i < p.x; i++) { if (chessboard[i][p.y]) return true; } // 检查左上方对角线,只检查上方以节省时间 int x = p.x - 1; int y = p.y - 1; while (x >= 0 && y >= 0) { if (chessboard[x--][y--]) return true; } // 检查右上方对角线,只检查上方以节省时间 x = p.x - 1; y = p.y + 1; while (x >= 0 && y < limit) { if (chessboard[x--][y++]) return true; } return false; } }
本来也不想把这东西放上来的,不过Google了一下其他经典的八皇后代码,总是觉得那些代码太为算法而算法了。虽然那些经典,都比偶这份简练很多,但根本不像一边思考一边写出来的,总是给我感觉是为了拼命简化代码行数,拼命利用特定规律和计算机的特性再三优化后代码。让初学者不好入手,无法看到如何把想法变成代码的过程。