Servlet开发需要注意的一些东西。

PHP和ASP等基于网页(page-based framework)的框架,是以网页本身为开发的基本单位,通过在页面中通过各种实际业务相关的逻辑拼装HTML,实现具体业务功能,这种实现在编程中直观的而简明的,自上个世纪90年代Tim Berners Lee发明HTML和万维网开始,到本世纪的2005年之前,几乎所有网站都是基于页面的,而在java体系结构下,也有JSP这种Page-based的技术相对应。

Page-based的Web系统开发,虽然结构灵活,但是由于最小的功能单位是页面,所以导致了大量的代码冗余和重复开发工作,为了提升Page-based的Web系统开发效率,社区和其背后的各大公司推出各类开发框架,以期望提高Web的开发效率,其中以典型的SSH(Strutz+Spring+Hibernate)为典型代表,但是非常可惜,虽然所有人都不愿意承认,这种遍地开花的开发框架技术,目前被使用了数百万次,做了数十万的项目,竟然没有一个可以说的上成功的商业项目。唯一值得一提的是,Facebook的初期日志主页使用了Struts 1.0,后来也迅速被PHP替代了。

由于Java在Web开发框架的应用失败,导致Java技术的式微,也间接引发了Sun的倒闭。Java直到2010年以后,伴随着SOA概念的提出和移动云计算技术的发展,以及谷歌在Android的应用,才重新焕发了生机。

2005年以后,随着Web2.0的热潮和社交网络的兴起,Page-based Framework基于页面的交互体验,已经远远不能满足用户对Web系统的期望。所以,PHP、javascript和CSS大行其道,并且直接推动了HTML5和CSS3标准的实行。在服务器端,基于面向服务的体系结构(SOA),所有的Web系统都在向服务转化,从面向对象到面向服务,从存在过程和上下文的对象交互,变成RestFUL的无状态系统调用。整个体系结构的解耦合方向,由对象转变为了函数,面向过程的系统设计,又在以谷歌为领导的面向“函数”编程思想的形式,进行了一次真正的复辟。但是这种复辟与传统的过程相比,其抽象层次更高,单次调用完成的功能更强大,更没有上下文的依赖,将面向对象的交互以“函数”的形式进行重构和隔离,以“服务”的形式体现。

在这种技术条件下,Java在95年左右推出,然后迅速被抛弃的技术,目前来看却能对此次技术革命作出回应,这就是一直藏在JSP技术背后,为JSP提供运行环境的Servlet技术。Servlet的开发是纯粹的Java语言,没有任何其他的自造框架特性,与CGI的程序相比,仅仅提供了一层多线程的系统调用的封装,并且提供了一套完整的对象生命周期的管理方案,包括产生,销毁,执行,执行前,执行后。相比于各大互联网公司的使用的Nginx和Nginx的插件模块的开发,要简单的多。通过Nginx插件的C编程,去实现复杂的业务逻辑无疑是天方夜谭,而Servlet能在性能损失小于50%的情况下,提供这种开发环境。另外,如果采用了SSH架构,其内存空间和CPU耗费,导致性能损失超过95%,Servlet能提供超过SSH至少10倍的资源利用率。

目前,纯粹的软件系统开发,已经看不到网页的概念。网页,只是测试用例的载体而已,最终会被重构掉,系统的期望形式是目前在国内外OA系统中流行的Single Page Application。以系统视角来看,目前需要介绍的几个问题如下:

1单例模式

传统的网页开发,其应用程序的生命周期是页面,并不在一个空间让系统存储一个多个网页共享的东西。一个网页执行过后,其所有的对象都会消失。而使用了Servlet的Java程序,是纯粹的Java程序,一直伴随着应用服务器存活,生命周期与应用服务器的一次执行几乎完全一致(很多应用服务器,提供重启的Java系统的功能)。所以,设计模式里面采用的单例模式的对象,将会一直存在于内存当中。我们可以把需要重复使用的对象,守护线程,配置信息等放在单例模式中,在系统需要的时候重复使用。

在Web应用中,使用单例模式,一定要注意线程安全问题,Servlet中的一次调用,就是一次独立的线程运行,肯定存在线程安全问题。所以单例模式的安全使用场景一定要考虑清晰,否则会发生不可知的系统错误。下面,介绍几种可以安全使用单例模式的情况。

l  系统启动时写入信息,以后再也不更改,只读取和执行的对象。可以直接使用单例模式,不要做任何处理。

l  在Web系统中,使用单例模式,不要使用LazyLoad的单例写法,而是用静态写法。这样安全性更高。LazyLoad虽然性能更好,但是Java的线程同步对对象的非空的判断还是做不到100%的安全,可能出现对象泄露,导致系统信息丢失,引起故障。

l  使用Serlvet的系统,鲁棒性较差,如果单例模式的对象存在系统错误,很难像PHP网页一样,自动回复正常,往往需要重启动服务器才能解决。所以,一定要保证单例模式里面的代码100%安全正确。

l  使用单例模式可以实现系统级别的数据缓冲。不过使用的时候,要注意算好内存占用。在多语种翻译的时候,可以在系统启动时,将翻译字典提前加载到单例模式管理的缓冲对象当中。

2数据库链接的管理

由于需要实现多业务数据库选择,所以在实现数据库链接管理方面,做了调整。根据用户可以选择自己的用户对应的数据库,一开始需要先连接一下数据库,所以一开始首先要有一个数据库连接。这个数据库连接信息存储在一个配置文件当中。系统启动时读取,并建立数据库连接池,对这个连接池我们采用SYSDBInst单例对象进行管理。

然后再通过这个连接池,去访问用户信息,得到用户访问的数据库,生成用户的数据库连接池,对这个连接池,采用MultiDbInst单例对象进行管理。

由于不同的用户,对于不同的数据库,这种对应是一次用户登陆和使用到退出的生命周期,所以数据库连接池本身也是有一个Cache进行管理的。这个Cache通过ConconrrentHashMap实现,保证了多线程情况下的数据安全。

数据库连接池选择的是c3p0连接池。

3事务处理

在Java里面,事务是跟数据库连接Connection一一对应的。但是Connection是数据库级别的对象,在业务逻辑中呈现违背了系统设计中最本质的单向依赖原则,需要进行再设计。另外,在常见的非金融类Java Web系统中,对事务安全进行完整实现的系统非常少。

这种再设计,有两种解决方案,目前个人倾向选择第二种。

l  统一工厂模式

   采用统一工厂设计模式,所有DAO对象的生产和销毁都由一个统一工厂管理,所有Servlet都调用这个统一工厂,生产DAO对象,在这个工厂生产DAO对象的时候,选择注入合适的Connection对象,完全透明的实现事务安全,这种实现方法,优点是事务控制,在业务逻辑层完全不可见,使用方便,SpringFramework就是这种实现。缺点是,所有参与项目的程序员都要修改这个统一工厂的源代码,添加自己需要的DAO生产和销毁方法,在项目管理方面是一个非常困难的问题,代码冲突有可能严重影响系统开发效率。

l  基于IOC依赖注射

采用反向依赖注射技术,将Connection对象的生命周期交予Transaction类进行管理,将Transaction通过构造器注射入DAO中,通过控制Transaction的外部句柄,操作DAO的事务逻辑,最后Transaction提交一个Save原语,实现事务安全和数据完整性保证。

 

4Servlet的使用原则

常见系统设计中,一个Servlet往往对应了不同的操作,一个对象的增删改查,都放在了一个Servlet里面实现,而非增删改查的操作,会通过添加新操作参数的方式实现。这种请求往往如下所示:

请求:

http://localhost/UserServlet?opName=login&userName=jy&password=pass

处理:if(request.getParameter(“opName”).equals(“login”))

{

//   

}else

if(request.getParameter(“opName”).equals(“add”))

{

}else

….

这样设计,能够显著降低系统中的Servlet的个数。

   但是,这种设计会造成以下这种麻烦情况:

l  首先直接看Servlet名字,不知道这个Servlet实现了哪些功能和方法,需要再点进去看源代码,Servlet的对象名缺乏自描述性。

l  经验不够丰富的情况下,容易犯Servlet中使用成员变量的错误,造成数据的线程安全问题。因为Servlet的生命周期是系统,只有doGet和doPost函数内部才是某用户访问的那个线程。

错误写法:

Private String name;

@Override

    protected void doGet(HttpServletRequest request, HttpServletResponse response)

            throws ServletException, IOException {

                       name= request.getParameter(“opName”);

                       if(name.equals(“jy”))

{

  PrintWriter out = response.getWriter();

        try {

                       out.println(“ok”);

         

        } finally {

            out.close();

        }}  

    }

以上的错误的写法,当同时有一个叫jy的人和yj的人登陆系统的时候,jy先更新了name叫jy,然后yj又更新了,name叫yj。而在判断name值之前,后更新的先获得了CPU的时间片,叫jy的用户就得不到ok的输出了。

正确写法:

@Override

    protected void doGet(HttpServletRequest request, HttpServletResponse response)

            throws ServletException, IOException {

                      String name= request.getParameter(“opName”);

                       if(name.equals(“jy”))

{

  PrintWriter out = response.getWriter();

        try {

                       out.println(“ok”);

         

        } finally {

            out.close();

        }}  

    }

 

l  Servlet应该是一次无状态的更新,面向的服务的体系结构设计应当去掉传统网页中的session,而是应该对每一次请求的合法性都要进行安全验证。这里的可以通过Servlet中的Filter机制进行解决。

5ServletContextListener的使用

Servlet的生命周期是应用程序级别的,当Tomcat应用服务器启动的时候,会将系统中的所有Servlet进行初始化,并且将doGet,doPost方法放入线程池中,准备响应用户操作。而ServletContextListener会监听Servlet的创建,当发生Servlet创建的是,ServletContextListener会自动响应,执行contextInitialized方法,所以在contextInitialized可以加入单例对象的初始化,系统环境准备,配置读取等操作。同理,在contextDestroyed中,可以监控系统的关闭,释放该系统占用其他系统的资源,比如数据库连接等等。本系统使用的是com.jyapp.sys.misc.SystemStartListener。

Filter是对Servlet一次doGet或者doPost执行的生命周期的管理,提供面向切面的编程借口,可以进行一些预处理和后处理操作,主要是字符过滤,权限验证,翻译和日志记录。

l  字符编码统一

       通过在Fitler里面加入

request.setCharacterEncoding(this.encodingName);

         response.setCharacterEncoding(this.encodingName);

   可以将用户的提交转化为数据库需要的编码,防止数据库输入和输出乱码的出现。

l  权限处理

由于所有Servlet执行之前都需要调用符合Pattern的Filter,所以在Filter加入对权限的验证,可以降低权限系统的开发难度,将功能权限无侵入的实现。防止在业务逻辑中加入大量权限判断的代码,导致业务逻辑的混乱。让开发人员能专注于业务逻辑实现,提高业务逻辑的实现质量。

l  翻译和多语言

Fitler会对所有的Java系统的jsp,servlet甚至html文件进行过滤,所以采用Filter可以无侵入的实现多语言的自动替换。实现思路如下:

1. 在前处理中,将默认的httpResponse的输出流的句柄保留,

2. 将输出流替换为自己的输出流对象。

3.调用Serlvet处理

4.在后处理中,把替换过的输出流输出为字符串,然后替换文本

5.将替换后的文本,写入到之前保留的默认的httpResponse的输出流。

l  配置文件读写

配置文件读写的存在线程安全问题,所以必须在系统启动时刻加载完毕,如果要在Servlet直接读写配置文件,需要加入线程同步机制。

l  Gson库的使用

使用Gson库可以将数据库获得的Info对象转化为Json,直接供Javascript使用。前端Javascript可以直接使用Gson转化完毕的对象。

6命名空间

命名空间是用来组织和重用代码的编译单元。如同名字一样的意思,NameSpace(名字空间),命名空间是为了防止名字污染和元素命名冲突在标准 C++ 中引入的,它可以将其中定义的名字隐藏起来,不同的名字空间中可以有相同的名字而互不干扰。

 Javascript并没有真正实现面向对象的编程,虽然采用Hack手法可以使用面向对象的机制,但是涉及到业务开发,会影响代码的可读性,所以在业务开发里面,要使用过程机制,以函数为单位进行开发。如果有过C语言开发经验就知道,过程机制的最大问题,在于缺少命名空间机制,从而导致函数和变量名冲突。因此借鉴C语言开发的经验,对于Html对象的ID,函数名,全局变量名,都要以下划线为分割实现模拟的命名空间,比如用户功能的添加函数应该起名为:sys_usermanagement_add_user()

Sys表明子系统,

Usermanagement是用户管理

Add表示的是添加操作

User表示的是添加的是用户


转载于:https://my.oschina.net/brinlike/blog/192416

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值