J2EE企业架构宏观讲述
一、软件框架方案
采用基于MVC设计模式的WEB应用框架,典型的J2EE三层结构SSH(i):Spring+Struts2+Hibernate(Ibatis)。三层体系将业务规则、数据访问及合法性校验等工作放在中间层处理。客户端不直接与数据库交互,而是通过组件与中间层建立连接,再由中间层与数据库交互,整体框架如下图:
表现层(V):主要采用传统的JSP技术,经过多年的发展,其广泛的应用和稳定的表现,为其作为表现层技术打下了坚实的基础。页面布局采用DIV+CSS,采用这个优点主要有:缩减页面代码,提高页面浏览速度,缩减带宽成本;结构清晰,容易被搜索引擎搜索到,优化了seo;表现和内容相分离,集中的在CSS文件中控制页面表现,可以轻松实现更换皮肤(themes)。开发模式采用组件式开发,将页面分块独立出来,组织成JSTL,利用Freemarker维护标签内容,提高开发效果,减少维护成本。UI控制使用Jquery,Jquery是一个成熟优秀的JS基库,目前版本为1.4,性能上有很大的提升,而且Jquery上有大量的插件可以方便利用。前后台数据交互主要采用AJAX,传输的数据结构采用JSON,JSON的数据量相对于XML小很多,而且JSON格式是JS的数据对象格式,方便使用。
控制层(C):负责控制业务逻辑层与表现层的交互,调用业务逻辑层,并将业务数据返回给表现层作组织表现,该系统的MVC框架采用Struts2,而不采用Struts1.2,以下对比一下两者,可以清楚其优点,参考附件一。
中间层:采用的是流行的Spring,Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合,不必在为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用,在Spring2.5后的版本中,提供了Annotation注释配置,再加Struts2也提供Annotation,两者结合起来,可以“零配置”,不用写一大堆的XML配置。通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付,例如通过AOP实现切面日志,也就是将日志模块从代码中抽离,独立组成一个模块,而实成插件式编程。Spring还持供了声明式事务的支持,开发是从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务统一的管理,提高开发效率和质量。
使用Spring可以方便集成各种优秀框架,降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hession、Quartz)等的直接支持。
Service层(M):也就是业务逻辑层,负责实现业务逻辑。业务逻辑层以DAO层为基础,通过对DAO组件的正面模式包装,完成系统所要求的业务逻辑。
DAO层:负责与持久化对象交互,该层封装了数据的增、删、查、改的操作。对简单数据操作时,采用Hibernate框架,Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库,对于简单的数据库操作时,可以不用写SQL代码,提高开发效率,而对于批量操作,多表操作及SQL优化方面,使用Hibernate将显示有些吃力,这时可以采用Ibatis,Ibatis只是封装了数据访问层,替我们做了部分的对象关系映射,将所有的SQL写在有执行逻辑的xml配置文件,因为xml配置文件是带有逻辑性的,所以我们可以写得很灵活的SQL语句来控制业务,特别在优化SQL语句时,更显其身手的非凡。Hibernate和Ibatis都可以很方便的集成到Spring容器中,由Spring配置统一的数据连接池,以使这两个框架使用,再由Spring的事务管理来统一管理这两者的事务,这样的DAO的组合,一般可以解决大多数的业务需求,如出现特别业务,这两者都难以解决,这时可以选择SpringJDBC, SpringJDBC对JDBC实现了更轻量的封装,编程基本上跟JDBC差不多,而且Spring也提供了一些方便操作的方面,如批量操作,PO数据绑定等,同时可以使Spring配置的数据连接池,再由Spring的事务管理来管理事务。使用这样的框架,显得整个系统很优雅。
POJO持久化对象:持久化对象。通过实体关系映射工具将关系型数据库的数据映射成对象,很方便地实现以面向对象方式操作数据库, Hibernate,Ibatis,SpringJDBC得有提供POJO持久化对象映射。
插件式权限控制:这里有三种可以选择,1、Servlet和JSP过滤器Filter,在Web.xml中配置好需要权限控制的URL,相应的Filter中获取当前用户信息,用户权限,判断用户是否有权访问当前的URL;2、利用struts2的拦截器过滤权限,先在struts.xml中配置需要权限控制的Action拦截器,在相应的拦截器中,Filter中获取当前用户信息,用户权限,判断用户是否有权限访问当前的Action。3、使用Spring权限acegi,根据acegi 的权限配置要求,编写权限配置文件,这边的权限有两种,分别是URL拦截和切面方法拦截,通过配置文件来判断用户当前的访问权限。在不少系统中,都将权限控制写到业务代码,如在写一个业务代码时,在方法前面有一段这样的代码if(是否有权限)来判断权限,这样的编写代码太随便,代码量多,维护性很差,系统升级难度大,所以不推荐采用这种方法。以上的三种方式,都是属于插件式配置权限,把权限管理独立出来,同时也可以将日志随着权限一样独立,这样使权限控制成为了第三方插件。
二、性能优化方案
当系统的数据量达到一定程度时,这里给的方案是针对于百万级别的数据量程序。系统优化,从两个方面入手:程序和数据库。
程序优化:
1、测试检查程序本身是否存在内存泄漏,程序内存泄露是每个系统的致命BUG,一旦出现泄露,系统迟早会瘫痪,在设计系统时,首先必须出系统进行内存泄漏测试,确定系统的内存都能够合理回收。在服务器的测试,有很多工具可以使用,使浏览器端的测试,是一个大难题,特别是对于IE6这浏览器本身存在很多不合理的设计,在编写JS时,一不小心就造成IE内存泄露,使得系统在使用过程慢慢的变迟钝。
2、精简业务代码量,对重复的代码进行抽象,再通过继承使用,使整个系统的代码量减少,同时可以更好的控制代码执行效率,方便于内存泄漏的测试和处理,也提高开发效率。
3、对于有大量个性化UI操作时,要对JS的执行结果进行缓冲,JS是解释性语言,执行效率都是很低的,设计合理的执行结果缓冲,来缓冲一些常用的JS操作,可以提高系统操作的实时性,增强用户体验。
4、使用缓冲器cache,首先系统的数据进行区分,分为常用数据和业务数据。常用数据也可以理解为全局性数据,如分类的类型数据,这部分的数据量一般是有限的,可以将这些数据直接缓冲到内存,不用每次都去查询数据库。业务数据一般都是随着系统的使用数据量不断增加,这部分数据需要根据实际情况来进行缓冲,可以根据实际情况直接使用Hibernate,Ibatis的缓冲配置来处理,或者独立配置ehcache与memcached,这两者都是优秀的缓冲框架,都支持集群系统缓冲。对前台页面进行缓冲,页面缓冲分为整页缓冲和局部缓冲,可以使用oscache,oscache对整页缓冲和局部缓冲都支持得不错,只使用简单的配置,写个简单的标签,则可完成缓冲处理。
数据库优化:
1、SQL语句优化,SQL语句的优化是将性能低下的SQL语句转换成目的相同的性能优异的SQL语句。
2、建立合适的索引,索引是对数据库表中一个或多个列的值进行排序的结构。在数据库插入和更新时,数据库消耗一些性能来维护索引,索引是有利于查询,但影响插入和更新操作。
3、数据库分表、分区:分表就是将一类数据根据某个属性分别存在不同的表中,这样可以减少在查询时的查询总数,但现在不少数据库版都有提供分区功能,通过分区功能,可以将表、索引和索引组织表进一步细分为段,从而能够更精确地管理和访问这些数据库对象,查询上实现与分表性能相当的速度,而且,因为在 SQL 语句中分区是完全透明的,所以该功能几乎可应用于任何应用程序。
4、数据库表结构重构:这方向的优化,是最耗工作量了,也是不得已的,但有些情况下,却是必要的,大多数原因都是早期系统设计不合理,要查询一个业务数据,需要关联到多张表,增加了查询量,表结构重构还直接影响到业务代码,需要重写代码,而且还要对原来数据直接清整到新表中。不过,一般经过这样重构的优化,系统的性能都能得到很好的提升。
附件一:Struts1与Struts2的对比
实现类对比:
• Struts1要求Action类继承一个抽象基类。Struts1的一个普遍问题是使用抽象类编程而不是接口。
• Struts 2 Action类可以实现一个Action接口,也可实现其他接口,使可选和定制的服务成为可能。Struts2提供一个ActionSupport基类去实现常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。
线程模式对比:
• Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
• Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)
Servlet 依赖对比:
• Struts1 Action 依赖于Servlet API ,因为当一个Action被调用时HttpServletRequest 和 HttpServletResponse 被传递给execute方法。
• Struts 2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。但是,其他的元素减少或者消除了直接访问HttpServetRequest 和 HttpServletResponse的必要性。
可测性对比:
• 测试Struts1 Action的一个主要问题是execute方法暴露了servlet API(这使得测试要依赖于容器)。一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)。
• Struts 2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。
捕获输入对比:
• Struts1 使用ActionForm对象捕获输入。所有的ActionForm必须继承一个基类。因为其他JavaBean不能用作ActionForm,开发者经常创建多余的类捕获输入。动态Bean(DynaBeans)可以作为创建传统ActionForm的选择,但是,开发者可能是在重新描述(创建)已经存在的JavaBean(仍然会导致有冗余的javabean)。
• Struts 2直接使用Action属性作为输入属性,消除了对第二个输入对象的需求。输入属性可能是有自己(子)属性的rich对象类型。Action属性能够通过 web页面上的taglibs访问。Struts2也支持ActionForm模式。rich对象类型,包括业务对象,能够用作输入/输出对象。这种 ModelDriven 特性简化了taglib对POJO输入对象的引用。
表达式语言对比:
• Struts1 整合了JSTL,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。
• Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--”Object Graph Notation Language” (OGNL).
绑定值到页面(view)对比:
• Struts 1使用标准JSP机制把对象绑定到页面中来访问。
• Struts 2 使用 “ValueStack”技术,使taglib能够访问值而不需要把你的页面(view)和对象绑定起来。ValueStack策略允许通过一系列名称相同但类型不同的属性重用页面(view)。
类型转换对比:
• Struts 1 ActionForm 属性通常都是String类型。Struts1使用Commons-Beanutils进行类型转换。每个类一个转换器,对每一个实例来说是不可配置的。
• Struts2 使用OGNL进行类型转换。提供基本和常用对象的转换器。
校验对比:
• Struts 1支持在ActionForm的validate方法中手动校验,或者通过Commons Validator的扩展来校验。同一个类可以有不同的校验内容,但不能校验子对象。
• Struts2支持通过validate方法和XWork校验框架来进行校验。XWork校验框架使用为属性类类型定义的校验和内容校验,来支持chain校验子属性
Action执行的控制对比:
• Struts1支持每一个模块有单独的Request Processors(生命周期),但是模块中的所有Action必须共享相同的生命周期。
• Struts2支持通过拦截器堆栈(Interceptor Stacks)为每一个Action创建不同的生命周期。堆栈能够根据需要和不同的Action一起使用。