春天里,百花香...
#
摘要: 本文就分页的理由,分页的方式和MySql,Oracle中两种不同的分页技术进行了一些阐述,比较浅显。
阅读全文
摘要: 此文是“Web页面表单域验证方式的改进”的续篇。
示例页面:登录页面
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib uri="/WEB-INF/tld/struts-html.tld" prefix="html"...
阅读全文
摘要: 在基于Model2的应用中,控制层的类总会包含对业务层诸类的调用,业务层诸类不可避免的要产生各种异常,如果统一到控制层进行处理的话会导致代码变得庞大臃肿还有不少重复,这种的例子在Web应用中的Servlet和Action诸类中并不少见。
如果我们使用模板方法模式(Template Method Pattern)将业务处理和异常处理分开,能有效简化控制层诸类的代码,借用这种模式,我们可以把固定的异常处理代码放在基类中,而让子类来实现具体的业务,如果执行业务过程中出现异常如数据库无法连接,用户找不到等异常后,直接将异常抛出让基类来处理,这样做成功的把业务处理和异常处理分开到了子类和基类两种类中,涉及具体业务处理的子类代码得到了很大的简化,更方便阅读,修改和管理。
有点疑惑的是,现在还不确定这样做会有什么消极印象,如安全性或结构方面的,大家要是觉得有问题请不吝赐教。
阅读全文
摘要: 一般来说涉及数据库的应用中,表的主键有两种生成方案,一种是专门定义一个主键表,在其中放置一个自增长的字段为其它表提供主键;另一种是使用Oracle的sequence。这两种方案都有一定麻烦,Spring为此专门提供了一个ID增长器以简化具体步骤,下文就是它的相关使用方法的,使用的数据库是MySql5.
归纳
使用Spring的自增长ID生成器完成以下三步即可:
1)配置自增长id生成器,它需要一个数据源的支持。
2)根据配置将自增长id生成器注入DAO各类中。
3)使用nextStringValue,nextIntValue,nextLongValue方法得到ID。
阅读全文
注意:本文说到的log4j版本为1.2.15,使用的配置文件是属性文件(properties),如果这些与您的环境不符则请快速离开,以免耽误你的宝贵时间。
一.log4j在桌面程序中的配置
这个相对简单了,它的步骤就这样两步:
1)将log4j-1.2.15.jar引入到工程的lib目录中.
2)确保配置文件log4j.properties在程序的代码目录(如src目录,cfg目录)中,它编译后应该位于类路径classes中.
log4j.properties示例(可以拷贝使用):
- log4j.rootLogger=debug, stdout, R
-
- log4j.appender.stdout=org.apache.log4j.ConsoleAppender
- log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-
- # Pattern to output the caller's file name and line number.
- log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n
-
- log4j.appender.R=org.apache.log4j.RollingFileAppender
- log4j.appender.R.File=输出log文件.log
-
- log4j.appender.R.MaxFileSize=1000KB
- # Keep one backup file
- log4j.appender.R.MaxBackupIndex=1
-
- log4j.appender.R.layout=org.apache.log4j.PatternLayout
- log4j.appender.R.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n
要对这个文件进行修改的话,基本上改两个地方就行了。
一个是输出文件名称,一个是输出等级设置。
1) 输出文件名称:
log4j.appender.R.File=输出log文件.log
2) 输出等级:
log4j.rootLogger=debug, stdout, R
Debug说明只要是logger.debug以上的都记录
配置到这里,就结束了。下面请看如何在程序中使用log4j。
二.log4j的使用
1) 首先,那个类要用到log4j记录日志,就应该为类添加一个静态的成员变量loogger,示例如下:
- public class Main{
- private static Logger logger = Logger.getLogger(Main.class);
-
- public static void main(String[] args){
- logger.info("成员管理程序启动");
- new MemberMngCtrl();
- }
- }
2) 其次,你就可以使用logger.debug ,logger.info, logger.warn, logger.error, logger.fatal等函数(记录等级依次提高)来记录日志内容了,确实是很简单方便的。
三.log4j在Web工程中的配置
与桌面程序一样的是,properties文件也需要能被编译到classes(WEB-INF/classes/)中,建议将属性文件放在特定的目录下并设置为源码目录,另外放在WEB-INF\src下也不错。
这一步比前面稍多的是需要配置一个初始化log4j的initServlet,就是在一开始就启动的Servlet,代码如下:
- public class Log4jInit extends HttpServlet {
- private static final long serialVersionUID = -4499302208753939187L;
- static Logger logger = Logger.getLogger(Log4jInit.class);
-
- public void init(ServletConfig config) throws ServletException {
- String prefix = config.getServletContext().getRealPath("/");
- String file = config.getInitParameter("log4j");
- String filePath = prefix + file;
- Properties props = new Properties();
-
- try {
- FileInputStream istream = new FileInputStream(filePath);
- props.load(istream);
- istream.close();
-
- String logFile = prefix + props.getProperty("log4j.appender.R.File");
- props.setProperty("log4j.appender.R.File",logFile);
-
-
- PropertyConfigurator.configure(props);
- } catch (IOException e) {
- System.out.println("Could not read configuration file [" + filePath + "].");
- System.out.println("Ignoring configuration file [" + filePath + "].");
- return;
- }
- }
- }
然后,在Web.xml中配置一下,让它在一开始启动就可以了。
-
- <servlet>
- <servlet-name>log4j-init</servlet-name>
- <servlet-class>
- com.sitinspring.action.Log4jInit
- </servlet-class>
- <init-param>
- <param-name>log4j</param-name>
- <param-value>WEB-INF/classes/log4j.properties</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
全文完。
一般来说, 在创建一个应用程序之前,首先要决定这个应用程序的体系结构。应用程序体系结构(Application Architecture)由应用程序开发者设计,它指定了在各种各样的终端系统上,应用程序是如何组织在一起的。为了降低设计难度,大部分程序都以层(称为layer或level)的方式组织在一起,每一层都建立在它的下层基础上,使用下层提供的服务,下层对上层隐藏了许多服务实现的细节。这种方法几乎应用于整个计算机科学领域,也可以称为信息隐藏,数据类型抽象,数据封装,面向对象编程等。
分层即是对类进行一些规划,以流程中的类的用途和所处环节划分,把程序中将要用到的各个类分别归纳到各个包(目录)中。分层是对系统进行细分的第一步,它旨在将系统按具体功能和用途分解为相对独立的各个部分.如果说细分是将把难以解决的大问题分解成了各个容易解决的小问题的话,分层则是把解决同类小问题的类归纳到一起,这样程序的结构更加清晰,程序的可读性和可维护性越好,也更容易得到重用。
从大的尺度来讲,一个程序可粗略的分成三个层次:
界面层(UI layer),这是用户能直接感受到的,包含显示和控制两部分;
业务层(Business layer),其中包含了业务逻辑和业务处理;
持久层(Persistence layer),它用来将数据存储和将数据从持久层提取出来。
界面层(UI layer)中,包含两个层次:视图层View和控制层Controller.
视图层View是用户查看数据,输入和向用户输出结果的一层,这一层是用户唯一能够感受软件功能的窗口,它或者由Swing组件搭建(桌面系统或C/S系统中),或者由JSP搭建(B/S系统),它负责让用户输入数据和将控制层返回的数据显示给客户。其中返回的数据一般是领域对象的变体或者直接就是领域对象或其集合。在Web程序中jsp基本就属于这一层的。
控制层Controller是用来将界面和业务层联系在一起的,在系统的各层次中,应该和View层打交道一般只有Controller层, Controller层是View层和系统其它层次进行交互的中介者, View层越过中介者直接调用其它层次的行为应该尽量避免。
一般来说,为了减少耦合,提高程序的可维护性,我们一般采用MVC架构模式将业务层,视图层和控制层分开。
业务层(Business layer)中包含领域层 Domain,服务层 Service和实用工具层Util。
业务层是整个系统的关键部分,它主要由领域模型和业务逻辑组成,领域模型定义系统内相互作用的各个实体,业务逻辑则定义了领域模型所能执行的不同操作, 领域层的各个类代表了领域模型,而服务层的各个类代表了业务逻辑. 领域层和服务层是起点,其它各层都从这里起步.
领域层 Domain:领域对象是对现实世界业务处理对象的抽象和归纳,领域层中的类基本上都是实体(Entity)类,如员工管理系统中的Employee,学籍管理系统中的Student,借贷管理系统中的Contract等,系统的业务处理中用到那些实体对象,领域层中一般就应该有和这个实体对象相对应的实体类。这些类在刚开始设计时可能只有一些属性和对应的getter/setter方法,以后会不断的加入新的内容(主要是方法),如果有必要的话,可以为这些领域对象设计一些上层的抽象类或者接口,借助于泛型,反射,控制反转等高级技能能在一定程度上简化程序的编写过程。此外,领域层是程序的核心内容,因为其他层次都在很大程度上依赖Domain层的设计,如果这一层设计不够完善会使以后的工作步履蹒跚.
服务层Service:这一层就是为领域对象提供服务用的,领域对象一般不直接和表现层,持久层直接打交道而是通过服务层进行代理.服务层是UI层到持久层的中间通道,它处于上通界面下通持久层的中间环节,这个特性是使的这一层决定了软件功能的多少。
一般来说,UI层向服务层传入的是用户输入的一些参数,服务层进行验证,重组后向下层DAO传输;而服务层从Dao层收到的是领域对象或其集合,而它向UI层返回的是领域对象或者其集合的变体或者直接是领域对象或者其集合本身。Service诸类的实例在桌面程序和CS程序中一般作为Model的一个私有成员,而在Web程序中常常要用到时再创建出来。除领域层外,其余各层是在围绕它而设计.
实用工具层Util:这一层相对简单,它包含了各种工具类,类中包含的主要是静态函数和静态成员变量,这些类对共通的函数,变量进行了归纳,它旨在消除重复代码,降低主体代码的复杂程度.一般此层中类的复用程度很高.值得通过项目积累.
持久层(Persistence layer)是直接与持久介质打交道的层次,持久介质可以是常见的关系型数据库,文件甚至Web Service,它一般包含两个部分。
数据存储对象层(DAO层),sql语句一般写在这层中, 然后由它调用;DAO层是最低的一层,与持久介质直接打交道,它包含具体文件的位置,数据库连接等;
另一个部分就是持久介质,通常是关系型数据库。
Dao层中各个类一般作为Service的私有成员,供Service调用。
下图是各层间的位置关系图:
如何从需求中分析出诸个层次中的类呢,我们在大尺度上可以按照下面的步骤进行:
Domain the first:首先从业务流和业务规则中归纳总结出领域对象.
Service the second:为领域对象设计服务类。
Persistence the third:持久层的负责领域对象持久化到持久介质以及逆过程,它的设计在领域层和服务层之后,比较典型的持久层设计有数据库表的设计和ER图(实体关系图)的绘制.
View the last:最后设计表现层,表现层受领域层和服务层制约, 容易变化且易于修改,通常放在最后实现.
具体步骤如下
1.理解,分析,钻研需求,彻底了解你的客户想要什么,需要你做些什么.
2.将大系统分解成一个个子系统,细分出各个层次,搞清楚各层的任务。
3.分析业务逻辑,归纳出业务流.
4.从业务流和业务规则中总结出领域对象.
5.为领域层实现服务层.
6.以Domain层和Service层为核心设计表现层和持久层,直到形成完整的程序.
7.加入实用层消除重复代码,梳理结构和简化流程,.
8.限制跨层的调用.
软件开发过程中,唯一不变的就是变化。这是一句老生常谈,也就是说软件开发中永恒的主题就是变化。当你把代码都写好了,测试也完成了,准备交付的时候客户忽然要求你在指定时间做出变化,这种情况在外包行业中很常见;而对一些银行金融项目,边调研边开发边测试屡见不鲜;对于一些Web项目,从来就只有永远的Beta版,改来改去的事更是家常便饭。对此,程序员一定要求清晰的认识,抱怨只能是嘴上痛快,不解决实际问题。真要解决实际问题,非要动一番脑筋不可,如果合理使用了设计模式,反射或是Spring的IoC,便能变修改噩梦为一次有趣的智慧之旅。
首先我们看原始要求:客户要求将一批雇员名单存入到CSV和XML两种文件中去,以后还有可能增加别的文件格式,比如PDF,XLS等,虽然这是下一期的内容,但这一期应该考虑到变化,客户要求扩展性一定要好。
没问题,有了设计模式响应变化不难。这时我们可以用到模板方法模式:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
先请看骨架抽象类:
- public abstract class FileMaker {
-
-
-
- private List<Employee> employees;
-
-
-
-
-
-
-
- public final void makeFile(List<Employee> employees,String fileName){
- setEmployees(employees);
- makeFile(fileName);
- }
-
-
-
-
-
- protected abstract void makeFile(String fileName);
-
- public final void setEmployees(List<Employee> employees) {
- this.employees = employees;
- }
-
- public List<Employee> getEmployees() {
- return employees;
- }
- }
很好,固定的函数和步骤都在抽象基类中写定了,再看两个具体实现类,它们要实现的就是makeFile函数而已。
- public class CSVFileMaker extends FileMaker{
- protected void makeFile(String fileName){
- try {
- BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
-
- for(Employee emp:getEmployees()){
- String line="";
- line+=emp.getName()+",";
- line+=(emp.isMale()?"男":"女")+",";
- line+=emp.getAge()+",";
-
- out.write(line+"\r\n");
- }
-
- out.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- public class XMLFileMaker extends FileMaker{
- protected void makeFile(String fileName){
- try {
- Document document = DocumentHelper.createDocument();
- Element root = document.addElement("employees");
-
- for(Employee emp:getEmployees()){
- Element empElm=root.addElement("employee");
-
- Element nameElm=empElm.addElement("name");
- nameElm.setText(emp.getName());
-
- Element sexElm=empElm.addElement("sex");
- sexElm.setText(emp.isMale()?"男":"女");
-
- Element ageElm=empElm.addElement("age");
- ageElm.setText(String.valueOf(emp.getAge()));
- }
-
- OutputFormat format = OutputFormat.createPrettyPrint();
- format.setEncoding("GBK");
- XMLWriter writer = new XMLWriter(new FileWriter(fileName),format);
-
- writer.write(document);
- writer.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
这样昨完以后感觉很好,因为我们成功的把变化和不变分离开来,不变的部分放在了抽象基类中,而容易变化的部分放在了两个具体的子类中,这样如果再增加一种新文件格式,从抽象基类再扩展出一个子类即可。很好,这样就不怕变化了。客户对此也没有异议。
调用示例如下:
- List<Employee> emps=new ArrayList<Employee>();
- emps.add(new Employee("Andy",true,21));
- emps.add(new Employee("Bill",false,23));
- emps.add(new Employee("Cindy",true,25));
- emps.add(new Employee("Douglas",false,28));
-
- FileMaker fileMaker=new CSVFileMaker();
- fileMaker.makeFile(emps, "1.csv");
-
- fileMaker=new XMLFileMaker();
- fileMaker.makeFile(emps, "2.xml");
客户看到了我们的调用的例子,觉得应该更灵活一些,他说存成各种不同的文件是通过点击按钮来实现的,如果每个按钮的事件处理函数都要生成具体子类岂不是太死板了吗?这样做每个文件下载按钮的事件处理代码不是都不一样?
有点道理,如今理解到这一层的客户实在是不多见了。不过很容易满足他的需求,我们可以引入反射的方法:
- public static void main(String[] args) {
- List<Employee> emps=new ArrayList<Employee>();
- emps.add(new Employee("Andy",true,21));
- emps.add(new Employee("Bill",false,23));
- emps.add(new Employee("Cindy",true,25));
- emps.add(new Employee("Douglas",false,28));
-
- callByReflect("csv",emps,"1.csv");
- callByReflect("xml",emps,"2.xml");
- }
-
- public static void callByReflect(String type,List<Employee> emps,String fileName){
- try{
- Class cls=Class.forName("com.heyang."+type.toUpperCase()+"FileMaker");
- FileMaker fileMaker=(FileMaker)cls.newInstance();
- fileMaker.makeFile(emps, fileName);
- }
- catch(Exception ex){
- ex.printStackTrace();
- }
- }
因为按钮上的文字和类名是有关的,如下载CSV的按钮上就有CSV的文字,这可以通过正则表达式取道,再组合一下不就是类名了吗?csv到com.heyang.CSVFileMaker,xml到com.heyang.XMLFileMaker,其实变化就是三个字母而已。如果增加按钮,取出按钮中的三个字母再调用callByReflect函数即可,这个过程简直可以固化。
客户看到反射方法以后很是满意,没有意见了。待客户走后,项目经理把你拉到一边,说:
“你刚才的方法不错,确实很强,但看得懂反射并能灵活掌握的人水平要够一年经验才行,维护的活让一年经验的人去干太可惜了,最好改改,最好达到让新手也能掌握并修改的程度。”。
没办法,领导总有领导的考虑,他这么说也很合理,成本问题我可以不考虑,但如果把程序搞得复杂貌似NB,能让一些学艺不精的人产生云山雾罩的感觉,有时还能被人尊称一声“大侠”,但谁也不比谁傻多少,这声大侠不是白叫的,但是出了问题或是有了变化别人还是要找你,到头来还是给自己添乱,这些都是义务劳动,何苦来呢?还是应该改得容易些,让大家都能修改,我可不愿意半夜三更被人叫起来问问题。
用Spring的IoC就可以解决问题,写一个新类并配置到XML文件中对新手来说问题不大,这下可以让领导放心了,自己就更放心了。
IoC方案代码如下:
- public class Main {
- public static void main(String[] args) {
- List<Employee> emps=new ArrayList<Employee>();
- emps.add(new Employee("Andy",true,21));
- emps.add(new Employee("Bill",false,23));
- emps.add(new Employee("Cindy",true,25));
- emps.add(new Employee("Douglas",false,28));
-
- callByIoc("csv",emps,"1.csv");
- callByIoc("xml",emps,"2.xml");
- }
-
- public static void callByIoc(String type,List<Employee> emps,String fileName){
- try{
- ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
- FileMaker fileMaker=(FileMaker)ctx.getBean(type);
- fileMaker.makeFile(emps, fileName);
- }
- catch(Exception ex){
- ex.printStackTrace();
- }
- }
- }
Bean。xml文件内容很简单吧:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
- <beans>
- <bean id="csv" class="com.heyang.CSVFileMaker"/>
- <bean id="xml" class="com.heyang.XMLFileMaker"/>
- </beans>
好了。到这里问题就彻底结束了,终于满足了客户和上级的要求,可以回家睡个好觉了,不用担心别人打搅了。
态度改变一切,变化来了人总是要多做一些,心理当然是不愿意的,但抱怨或是消极抵制都不是解决问题之道;如果把它看做一个挑战的契机,凡事多思考一些,不但能解决问题,自己也会有所提高,这就是积极的态度带来的好处。
摘要: 在工程中经常有发送邮件的任务,如果使用JavaMail来发送邮件,用到的代码较多,过程和细节也相对复杂,而使用Spring的MailSender能相对简单方便些,这样使程序员能更快捷的完成邮件发送任务。下面请看示例代码:
注意在执行代码前,请确认已经将activation.jar,commons-logging-1.0.4.jar,mail.jar和spring.jar载入工程。...
阅读全文
在Web开发中,文本邮件发送的任务比较常见,我们可以利用它进行一些客户通知和异常通知,文本邮件发送一般用到JavaMail API,下面是一个我有时用到的邮件发送实用工具类,把其中一些参数修改一下就能为你所用。
注意:在执行代码前,请把mail.jar和activation.jar载入工程。
代码如下:
- package com.heyang;
-
- import java.util.Date;
- import java.util.Properties;
-
- import javax.mail.Address;
- import javax.mail.Message;
- import javax.mail.Session;
- import javax.mail.Transport;
- import javax.mail.internet.InternetAddress;
- import javax.mail.internet.MimeMessage;
-
-
-
-
-
-
- public final class MailUtil {
-
- private static final String SenderEmailAddr = "XXXXXXX@163.com";
-
-
- private static final String SMTPUserName = "XXXX";
-
-
- private static final String SMTPPassword = "XXXXXXX";
-
-
- private static final String SMTPServerName = "smtp.163.com";
-
-
- private static final String TransportType = "smtp";
-
-
- private static Properties props;
-
-
-
-
-
- private MailUtil() {
-
- }
-
-
-
-
- static {
- MailUtil.props = new Properties();
-
-
- MailUtil.props.put("mail.smtp.host", MailUtil.SMTPServerName);
-
- MailUtil.props.put("mail.smtp.auth", "true");
- }
-
-
-
-
-
-
-
- public static void sendMail(String emailAddr, String mailTitle,
- String mailConcept) {
-
- Session s = Session.getInstance(MailUtil.props, null);
-
-
- s.setDebug(false);
-
-
- Message message = new MimeMessage(s);
- try {
-
- Address from = new InternetAddress(MailUtil.SenderEmailAddr);
- message.setFrom(from);
-
-
- Address to = new InternetAddress(emailAddr);
- message.setRecipient(Message.RecipientType.TO, to);
-
-
- message.setSubject(mailTitle);
-
- message.setText(mailConcept);
-
- message.setSentDate(new Date());
-
- message.saveChanges();
-
- Transport transport = s.getTransport(MailUtil.TransportType);
-
- transport.connect(MailUtil.SMTPServerName, MailUtil.SMTPUserName,
- MailUtil.SMTPPassword);
-
-
- transport.sendMessage(message, message.getAllRecipients());
- transport.close();
-
- System.out.println("发送邮件,邮件地址:" + emailAddr + " 标题:" + mailTitle
- + " 内容:" + mailConcept + "成功!");
- } catch (Exception e) {
- System.out.println(e.getMessage());
- System.out.println("发送邮件,邮件地址:" + emailAddr + " 标题:" + mailTitle
- + " 内容:" + mailConcept + "失败! 原因是" + e.getMessage());
- }
- }
-
-
-
-
-
- public static void main(String[] args){
- MailUtil.sendMail("XXXXXX@gmail.com", "title", "concept");
- }
- }
sitinspring(http://www.blogjava.net)原创,转载请注明出处.