第一章 Servlet
第一节 Java Servlet概述
1、 什么是Servlet
Servlet 是标准的服务器端 Java 应用程序,它扩展了 Web 服务器的功能。
Servlet 运行在一个 Java 环境中,即 Java 虚拟机,或 JVM。servlet 将被载入到一个应用服务器中,例如 WebSphere(tm) Application Server。
由于 Servlet 是由一个用户的请求来调用,Servlet 对应一个 Web 站点单一 URL 的系统响应。一个企业级 Web 站点可能对一个请求就有多个 Servlet,例如注册、购物和支付。
由于 Servlet 运行在服务器环境中,用户永远不会直接看到它们而且 Servlet 不需要图形的用户界面。Servlet 是 Java 组件,可以在系统需要它们时被载入。
使用Servlet生成的动态文本,比CGI脚本更容易,且运行效率更高,是CGI脚本的高效替代品。
Servlet API的最大一个优点是协议的独立性,它不假定网络传输使用的协议、Servlet如何装载以及运行的服务器环境,这些特性使得Servlet API可以方便的嵌入到许多不同种类的服务器中。另外,它还具有可扩展、简单、短小、容易使用的特点。
2、 Servlet的用途
一个简单的Servlet可以处理使用HTML FORM在HTTP(S)上使用POST传递的数据,例如:一个带有信用卡数据的定单。复杂的Servlet可以用于实现定单输入和处理系统、生产及库存数据库、或者一个复杂的联机支付系统。
一个Servlet可以将请求传递给另一个服务器,这项技术在多个服务器中可以用来平衡装载相同的镜像内容。或者,可以用于将一个逻辑服务划分给几个服务器,按照任务类型或组织的边界路由请求,实现负载均衡。
3、 API的有效性
Java Servlet API是一个标准的Java扩展API。在J2EE中,它已经发展成为Java核心框架的一部分。
JavaSoft提供了一个包,可以用于在其他服务器中嵌入Servlet。
4、 Servlet API的特性
Servlet API已经被大多数基于Java的Web服务器支持,因此,当使用Servlet API时,可以继承Java的许多优点:不但代码不存在内存漏洞和难以发现的指针bug,而且还可以运行在许多不同服务器提供商的平台上。
主要功能
Servlet采用常见的接受请求和生成响应的编程模型,该模型使用了一系列的分布式系统编程工具箱,这包括从远程过程调用到向Web服务器发出HTTP请求等。
Servlet编程非常简单,主要就是实现一个Servlet接口,例如:
Public class MyServlet extends GenericServlet
{
Public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException
{
……
}
}
Service方法内可以操作request和response参数,这里封装了客户机发送的数据,提供了对参数的访问,并允许Servlet报告各种状态。通常Servlet通过输入流得到大多数参数,并使用输出流发送它们的响应。
ServletInputStream in = request.getInputStream();
ServletOutputStream out = request.getOutputStream();
环境状态
由于Servlet是一种Java对象,它们由特定的实例数据组成,这意味着正在执行的Servlet是运行在服务器内部的独立的应用程序。
在初始化期间,Servlet访问一些特定的Servlet配置数据,这允许相同Servlet类的不同实例可以用不同的数据来初始化,并当作不同名字的Servlet来管理。在初始化期间提供的数据中,包括了每个实例将在哪里保存它们自己的状态。
Servlet还可以利用ServletContext对象与它们自己的环境进行交互。
使用模式
Servlet可以使用下面几种模式,然而,不是所有服务器环境都支持以下所有这些模式:
l 基本的模式:以请求/响应协议为核心。
l 服务器可以用过滤链链接多个Servlet。
l Servlet可以支持特定的协议,例如HTTP。
l 在基于HTTP的应用程序中,Servlet是一个基于CGI扩展的完全的替代品。
l 在基于HTTP的应用程序中,Servlet还可以作为HTML的服务器端来包含动态的生成的Web文档。
主要方法
Servlet装载之后,其生命周期涉及三个主要方法:
u init方法
Servlet通过调用init方法被服务器激活,如果开发者需要,可以执行一些内在的设置,这样,无需每个请求都设置一次。
u service方法
当初始化后,Servlet处理许多请求,为每一个客户请求生成一个service调用,这些请求可能是并行的,这允许Servlet调整多个客户的行为,类的静态属性可以用于在请求中共享数据。
u destroy方法
Servlet对象将一直处理请求,除非服务器调用destroy方法显示的关闭Servlet,Servlet类还是一个合格的垃圾收集器。
安全特性
与当前任何其他的服务器扩展API不同,Java Servlet提供了强大的安全策略支持。这是因为所有的Java环境都提供了一个安全管理器,可以用于控制是否允许执行某项操作,例如网络或文件的访问。缺省的,通过网络装载的所有Servlet都是不可信的,它不允许执行诸如访问网络服务或本地文件的操作。仅仅在Java Web Server中创建的,或者在本地由服务器管理员控制的…/Servlet目录的Servlet,才是完全可信的,并赋予所有的权限。
性能特性
Servlet最突出的特性之一就是对于每一个请求不需要象CGI那样单独创建一个新的进程。在大多数环境中,许多Servlet可以并行运行在与服务器相同的进程中,处理许多已初始化的客户请求,这些初始化的开销由许多方法分担,对于该service方法所面对的所有客户请求,它们都有机会共享数据和通讯资源,并充分利用系统缓存。
Servlet的生命周期
Servlet部署在容器里,它的生命周期由容器管理。
Servlet的生命周期概括为以下几个阶段:
u 装载Servlet:这项操作一般是动态执行的。有些服务器提供了相应的管理功能,可以在启动的时候就装载Servlet并能够初始化特定的Servlet。
u 创建一个Servlet实例。
u 调用Servlet的init()方法。
u 服务:如果容器接收到对此Servlet的请求,那么它将调用Servet的service()方法。
u 销毁:实例被销毁,通过调用Servlet的destroy()方法来销毁Servlet。
在几个阶段中,对外提供服务是最重要的阶段,service()方法是我们最关心的方法,因为它才是真正处理业务的方法。
Servlet为客户端提供服务,如下图所示:
客户端 |
Web容器 |
Servlet |
1:请求 |
2:init() |
3:service() |
5:响应 |
6:destroy() |
4:返回结果 |
第二节Servlet编程示例
在进行具体Servlet编程之前,这里,先举一个最简单的Servlet程序,熟悉一下Servlet的结构。
源程序:HelloWorld.java
package com.iss.test;
import java.io.*
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorldServlet extends HttpServlet
{
//Initialize global variables
public void init()
throws ServletException
{
}
//Process the HTTP Get request
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("text/html;charset=gb2312");
PrintWriter out = response.getWriter();
out.println(“<html>”);
out.println(“<head>”);
out.println(“<title>HelloWorld</title>”);
out.println(“</head>”);
out.println(“<body>”);
out.println(“Hello, World!”);
out.println(“</body>”);
out.println(“</html>”);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doGet(request, response);
}
//Clean up resources
public void destroy()
{
}
}
编译
使用任何一种编译工具,编译为class文件。比如jdk自带的javac命令:
Javac HelloWorld.java
打包
使用任何一种打包工具,打成可发布的jar包或web组件包。
部署
将打包好的程序发布到任何支持Servlet容器的服务器上去。
这个servlet对应的部署描述符如下:(位于WEB-INF下的web.xml中)
<?xml version=” 1.0” encoding=”ISO-8859 -1” ?>
<web-app xmlns=”http://java.sun.com/xml/ns/j2ee”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://java.sun.com/xml/ns/j2ee web-app_2_4.xsd”
version=” 2.4” >
<display-name>Welcome to Tomcat</display-name>
<description>
HelloWorldServlet
</description>
<servlet>
<servlet-name>HelloWorldServlet</servlet-name>
<servlet-class>com.iss.test.HelloWorldServet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
运行
从客户端访问服务器上的应用,比如通过浏览器访问,服务器给客户端返回“Hello,world!”的信息。
第三节Servlet相关类和接口
HttpServlet类
一个抽象类,可以从该类派生出一个子类来实现一个HttpServlet,接受来自Web站点的请求,并将处理后的响应结果发回Web站点。在HttpServlet的子类中,必须至少重载下表中所列的其中一种方法。
HttpServlet子类通常重载的方法
方法名 | 用途 |
doGet | 如果Servlet支持HTTP GET请求,用于HTTP GET请求 |
doPost | 如果Servlet支持HTTP POST请求,用于HTTP POST请求 |
doPut | 如果Servlet支持HTTP PUT请求,用于HTTP PUT请求 |
doDelete | 如果Servlet支持HTTP DELETE请求,用于HTTP DELETE请求 |
init和destroy | 如果需要管理Servlet生命周期内所持有资源,可以重载这两个方法 |
通常,不重载service方法,对于上表中的每一种HTTP请求,service方法通过分派它们到相应的Handle线程(doXXX方法)来处理这些标准的HTTP请求。
通常也不重载doOptions和doTrace方法,service方法通过分派它们到doTrace和doOptions来支持HTTP1.1 TRACE和OPTIONS。
Servlet通常运行在多线程的服务器中,因此,所编写的Servlet代码必须能够处理并行请求和对数据资源的同步访问。
doGet
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
用于获取服务器信息,并将其作为响应返回给客户端。当经由web浏览器,或者通过HTML、Jsp直接访问Servlet的URL时,一般使用Get调用。
doPost
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
用于客户端把数据传送到服务器端。使用它的好处是可以隐藏发送给服务器的任何数据。适合于发送大量的数据。
doPut
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
Put的调用和Post相似,它允许客户端把真正的文件存放在服务器上,而不仅仅是传送数据。
doDelete
protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
它的调用和Post相似,它允许客户端删除服务器端的文件或者web页面。
doTrace
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
由容器调用以使此Servlet能够处理Trace请求。这个方法主要用于测试,它是不可以覆盖的方法。
doHead
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
它用于处理客户端的Head调用,并且返回一个response。当客户端只需要知道响应的Header时,它就发出一个Header请求。
doOptions
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
它用于处理客户端的Options调用,通过这个调用,客户端可以获得此Servlet支持的方法。
HttpServletRequest接口
该接口扩展了javax.servlet.servletRequest接口,用于提供更多的功能给HttpServlet。Servlet引擎实现该接口,创建HttpServletRequest对象,将来自客户浏览器的信息传递给HttpServlet的service方法。
getDateHeader
public long getDateHeader(String name)
得到一个特定请求的header的、描述Date对象的长整形值。Header是大小写敏感的。如果请求中没有该name的header,则方法返回-1,如果方法不能转换日期,则抛IllegalArgumentException异常。
getHeader
public String getHeader(String name)
得到有关请求header的值,为一个字符串,如果指定名字的header没有随请求一起发送,该方法返回null,name是大小写敏感的。
getCookies
public Cookie[] getCookies()
得到一个Cookie对象数组,含有所有发送请求的浏览器的Cookie,如果浏览器没有发送任何Cookie,则返回null。
getHeaderNames
public Enumeration getHeaderNames()
得到请求中的所有header名。
getRequestURI
public String getRequestURI()
得到HTTP请求的第一行中从协议名到查询字符串之间的URL部分。
getSession
public HttpSession getSession(Boolean create)
public HttpSession getSession()
得到与请求相联系的当前HttpSession,如果需要,则为请求创建一个新的任务。
HttpServletResponse接口
该接口扩展了OutputStream类,因此,可以使用OutputStream类中声明的方法,例如,getOutputStream()方法。
addCookie
public void addCookie(Cookie cookie)
增加指定的cookie到响应。
setHeader
public void setHeader(String name, String value)
使用给定的名字和值设置响应头。
ServletConfig接口
该接口定义了一个Servlet配置对象,Servlet引擎可以用来传递信息给Servlet以便初始化Servlet。
配置信息含有初始化参数,这些参数以“名字/值”的形式成对出现,描述关于服务器的Servlet信息。
getServletContext
public ServletContext getServletContext()
得到服务器传递给Servlet的一个ServletContext对象。
getInitParameter
public String getInitParameter(String name)
得到一个含有参数name指定的初始化参数的值,如果参数不存在则为null。
getInitParameterNames
public Enumeration getInitParameterNames()
得到Servlet的初始化参数的名字,返回值是一个Enumeration对象。
ServletContext接口
该接口定义了一套Servlet与Servlet引擎通信的方法,例如:得到MIME类型的文件、确定运行在服务器上的其他Servlet或写Servlet日志文件。
Servlet引擎通过返回一个ServetContext对象来与Servlet交流,Servlet使用ServletConfig接口的getServletContext方法得到ServletContext对象。
getContext
public ServletContext getContext(String uripath)
得到服务器上特定URL的一个ServletContext对象,该方法允许Servlet访问特定URL上的资源,并得到来自该URL的RequestDispatcher对象。
getMimeType
public String getMimeType(String file)
得到指定文件的MIME类型,如果不知道MIMI类型则为null。MIMI类型由Servlet引擎的配置决定,MIME类通常为“text/html”和“image/gif”。
getRealPath
public String getRealPath(String path)
得到参数path中对应于虚拟路径的真实路径。虚拟路径含有一个Servlet名,后跟Servlet文件名。
getServerInfo
public String getServerInfo()
得到正在允许的Servlet引擎的名字和版本。
getAttribute
public Object getAttribute(String name)
得到Servlet引擎的参数name所指定的属性。
getAttributeNames
public Enumeration getAttributeNames()
得到一个含有属性名的Enumeration对象,这些属性在Servlet上下文中有效。
setAttribute
public void setAttribute(String name, Object object)
设置Servlet上下文中属性的名字,如果指定的名字name已使用,该方法将覆盖老的名字,并将名字捆绑到新的属性中。
removeAttribute
public void removeAttribute(String name)
从Servlet上下文中移走给定名字name的属性名。
ServletInputStream类
该类是一个抽象类,它提供了从客户机请求读取二进制数据的一个输入流,对于一些协议,例如HTTP POST和PUT,可以使用ServletInputStream对象读取和发送数据。
readLine
public int readLine(byte[] b, int off, int len) throws IOException
读输入流,一次一行。如果读到输入流的尾部,则返回-1。
ServletOutputStream类
该类是一个抽象类,它提供了一个输入流,用于向客户机发送数据。
public void print(String s) throws IOException
public void print(boolean b) throws IOException
public void print(char c) throws IOException
public void print(int i) throws IOException
public void print(long l) throws IOException
public void print(float f) throws IOException
public void print(double d) throws IOException
public void println() throws IOException
public void println(String s) throws IOException
public void println(boolean b) throws IOException
public void println(char c) throws IOException
public void println(int i) throws IOException
public void println(long l) throws IOException
public void println(float f) throws IOException
public void println(double d) throws IOException
第四节Servlet的配置
名字、类和其他杂项
配置Servet时,首先必须指定Servlet的名字、Servlet的类(如果是JSP,必须指定JSP文件的位置)。另外,可以选择性的给Servlet增加一定的描述,并且指定它在部署时显示的名字,部署时显示的Icon。
上述HelloWorldServlet是这样配置的:
<servlet>
<servlet-name>HelloWorldServlet</servlet-name>
<servlet-class>com.iss.test.HelloWorldServet</servlet-class>
</servlet>
如果要配置的Servlet是一个JSP文件,那么可以这样指定:
<servlet>
<servlet-name>HelloWorldServlet</servlet-name>
<jsp-class>HelloWorldServet.jsp</jsp-class>
</servlet>
初始化参数
一个Servlet的初始化参数可以如下指定:
<servlet>
<init-param>
<param-name>parameter1</param-name >
<param-value>1000</param-value>
</init-param>
……
</servlet>
在这个配置里,指定parameter1的参数值为1000。
启动装入优先级
启动装入优先级通过<load-on-startup>来配置。如下所示:
<servlet>
<servlet-name>HelloWorldServlet1</servlet-name>
<servlet-class>com.iss.test.HelloWorldServet</servlet-class>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet>
<servlet-name>HelloWorldServlet2</servlet-name>
<jsp-class>HelloWorldServet.jsp</jsp-class>
<load-on-startup>30</load-on-startup>
</servlet>
<servlet>
<servlet-name>HelloWorldServlet3</servlet-name>
<jsp-class>HelloWorldServet.jsp</jsp-class>
<load-on-startup>AnyTime</load-on-startup>
</servlet>
这种配置可以保证HelloWorldServlet1在HelloWorldServlet2之前被载入,HelloWorldServlet3则可以在服务器启动后的任何时候载入。
Servlet的映射
可以给一个Servlet做多个映射,这样,我们可以通过不同的方式来访问这个Servlet。
一个典型的配置如下所示:
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/hello_servlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/hello/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>HelloWorldServlet</servlet-name>
<url-pattern>/helloworld/helloworld.html</url-pattern>
</servlet-mapping>
通过这些配置,我们可以使用不同的方式来访问这个Servlet。
运行安全设置
一个Servlet的安全设置如下所示:
<servlet>
<run-as>
<role-name>admin</role-name>
</run-as>
<security-role-ref>
<role-name>admin1</role-name>
<role-link>admin</role-link>
</security-role-ref>
</servlet>
第五节Filter过滤器
Web应用中的过滤器截取从客户端进来的请求,并做出处理的答复。可以说过滤器是外部进入网站的第一道关,在这个关卡里,可以验证客户是否来自可信的网络,可以对客户提交的数据进行重新编码,可以从系统里获得配置信息,过滤掉客户的某些不应当出现的词汇,验证客户是否已经登陆,验证客户端的浏览器是否支持当前的应用,记录系统日志,等等。
过滤器可以部署多个,这些过滤器组成了一个过滤链,每个过滤器只执行某个特定的操作或者检查,这样请求在达到被访问的目标之前,需要经过这个过滤链,如果由于安全问题不能访问目标资源,那么过滤器就可以把客户端的请求拦截。
Filter的开发
开发一个Filter,必须直接或间接实现Filter接口。Filter接口定义了以下的方法:
u init():用于初始化Filter,获得FilterConfig对象。
u destroy():销毁这个Filter。
u doFilter():进行过滤处理。
Filter的配置
配置Filter分为两步:一是声明Filter,二是使用Filter。
一个Filter的配置示例如下:
<filter>
<filter-name>filter1</filter-name>
<filter-class>com.iss.test.Filter1</filter-class>
<init-param>
<param-name>parameter1</param-name>
<param-value>value1</param-value>
</init-param>
</filter>
<filter>
<filter-name>filter2</filter-name>
<filter-class>com.iss.test.Filter2</filter-class>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filter2</filter-name>
<url-pattern>/security/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>filter2</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
从上面可以看出,一个Filter可以有多个Filter Mapping。一个web组件可以有多个过滤器,这些过滤器按照配置中出现的先后组成一个过滤链。每个到达的请求,经过这些过滤链中的过滤器逐个过滤,如果不通过,请求就被拦截。
第六节 Cookie编程技术
Cookie是一组短小的、服务器发送到浏览器的文本信息,当浏览器再次访问相同的网站或域时,该文本信息会原样的传回,服务器根据客户机传回的信息,可以向访问者提供许多方便。比如:识别用户、避免输入用户名和密码、定制一个站点、定向广告等等。
Servlet Cookie API
将Cookie发送到客户端,Servlet可以使用相应的名字和值创建一个或多个Cookie:
public Cookie(String name, String value)
构造器的两个参数中,不能使用下列字符:
[ ] ( ) = , “ / ? @ : ;
同时,也不能使用空格。
创建Cookie后,可以使用response.addCookie(cookie)将Cookie插入到相应的头中,在客户端,对于得到请求,可以使用response.getCookies(cookies)从响应头中提起Cookie,得到的是一个Cookie数组。
Cookie常用操作
将Cookie放进响应头
将Cookie放进响应头可以使用HttpServletResponse类的方法:
public void addCookie(Cookie cookie);
例如:
Cookie userCookie = new Cookie(“user”,”uid 1234” );
response.addCookie(userCookie);
读取来自客户端的Cookie
读取来自客户端的Cookie可以使用HttpServletRequest类的方法:
public Cookie[] getCookies();
第七节 Session编程技术
HTTP状态的维护
HTTP是一种“无状态”的协议,这会造成许多问题。为了解决“状态”的维护,通常有四种解决方案:
Ø cookie
Ø URL重写
Ø 隐藏的form域
Ø session
Session API的使用
得到HttpSession对象
HttpServletRequest有两个方法可以得到Session对象:
public HttpSession getSession(Boolean create);
public HttpSession getSession();
得到与request对象相联系的HttpSession,如果request对象中没有session对象且参数create为true,则创建一个新的。
得到session中的数据
public Enumeration getAttributeName();
public Object getAttribute(String name);
向Session中写数据
public void setAttribute(String name, Object value);
第二章 JDBC
JDBC封装了与底层数据库的通信细节,提供了与数据库相关的类和接口,为数据库开发人员提供了一种面向应用的开发平台。
第一节 JDBC概述
数据库是用于存储和处理数据的工具,数据库是构成了许多公司的重要基础。当前,由于数据库产品缤纷复杂,一个公司里经常出现同时使用多种数据库的现象。使用Java进行数据库开发时通过JDBC技术,可以一致性地访问不同的数据库,不用分别为不同平台的不同数据库编写各自不同的应用程序。
1、 JDBC的概念
JDBC(Java Data Base Connectivity,Java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。同时,JDBC也是个商标名。
JDBC向应用程序开发者提供了独立于数据库的、统一的API,这个API提供了编写的标准,并考虑了所有不同应用程序设计的标准,其关键是一组由驱动程序实现的Java接口。驱动程序负责标准的JDBC调用,当应用程序被移植到不同的平台或数据库系统,应用程序不变,改变的是驱动程序,驱动程序扮演了多层数据库设计中的中间层的角色。如下图所示:
应用程序 |
JDBC |
Oracle 驱动程序 |
Sybase 驱动程序 |
DB2 驱动程序 |
Informix 驱动程序 |
…… 驱动程序 |
Java具有坚固、安全、易于使用、易于理解和可从网络上自动下载等特征,是编写数据库应用程序的杰出语言,所需要的只是Java应用程序与各种不同数据库之间进行对话的方法,而JDBC正是作为此种用途的机制。
随着越来越多的使用Java编程语言,对从Java中便捷的访问数据库的要求也在日益增加。对于服务器端的数据库应用,JDBC往往与RMI、CORBA、JSP或Servlet,EJB等技术混合使用,用于实现多层结构的应用系统。
2、 数据库驱动程序
数据库厂商一般会提供一组API访问数据库。流行的数据库如Oracle、SQL Server、Sybase和Informix都为客户提供了专用的API。有四种类型的数据库驱动程序,它们分别是:
u JDBC-ODBC桥
u 部分Java、部分本机驱动程序
u 中间数据访问服务器
u 纯Java驱动程序
以下分别介绍。
JDBC-ODBC桥
JDBC-ODBC桥是一个JDBC驱动程序,它通过将JDBC操作转换为ODBC操作来实现JDBC操作。对ODBC,它像是通常的应用程序,桥为所有对ODBC可用的数据库实现JDBC。它作为sun.jdbc.odbc包实现,其中包含一个用来访问ODBC的本地库。桥是由Intersolv和JavaSoft联合开发的。由于ODBC被广泛地使用,该桥地优点是让JDBC能够访问几乎所有地数据库。
通过odbc子协议,使用URL打开JDBC连接即可使用桥。建立连接前,必须将桥驱动程序类sun.jdbd.odbc.JdbcOdbcDriver添加到名为jdbc.drivers的java.lang.System属性中,或用Java类加载器将其显示的加载。可以用如下方式显示加载:
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);
加载时,ODBC驱动程序将创建自己的实例,同时在JDBC驱动程序管理器中进行注册。
桥驱动程序使用odbc子协议。该子协议的URL为以下的形式:
jdbc:odbc:<data-dource-name>[<attribute-name>=<attribute-value>]*
JDBC-ODBC桥在JDBC API和ODBC API之间提供了一个桥梁,这个桥把标准的JDBC调用翻译成对应的ODBC调用,然后通过ODBC库把它们发送到ODBC数据源,如图所示:
JDBC API |
ODBC API |
JDBC-ODBC桥 |
ODBC层 |
数据源 |
Java应用程序 |
这种方式有一个明显的缺点就是效率相对低下,所以不推荐使用这种桥驱动程序,但它可以减少开发人员进行企业开发的麻烦。
部分Java、部分本机驱动程序
这种驱动使用Java实现与数据库厂商专有API的混和形式来提供数据访问。它比前一种方式要快。JDBC驱动将标准的JDBC调用转变为对数据库API的本地调用,该类型的驱动程序是本地部分Java技术性能的本机API驱动程序,如下图所示:
JDBC API |
厂商专有API |
JDBC驱动程序 |
数据源 |
Java应用程序 |
在这种方式里,驱动程序和厂商专有的API必须在每个运行Java应用程序的客户端安装。
现在大多数的数据库厂商都在其数据库产品中提供驱动程序,这种使用方式比前一种有效。
中间数据访问服务器
这种方式使用一个中间数据访问服务器,通过这种服务器,它可以把Java客户端连接到多个数据库服务器上,如下图所示:
JDBC API |
JDBC驱动程序 |
本机驱动程序 |
数据源 |
Java应用程序 |
JDBC驱动程序 |
这种方式不需要客户端的数据库驱动,而是使用网络-服务器中间层来访问一个数据库。该类型的驱动程序是网络协议完全Java技术性能的驱动程序,它为Java应用程序提供了一种进行JDBC调用的机制。
使用该类型的驱动程序是平台无关的,并且不需要客户端的安装和管理,因此很适合做Internet的应用。
纯Java驱动程序
这种方式使用厂商专有的网络协议把JDBC API调用转换成直接的网络调用,这种方式的本质是使用套接字(Socket)进行编程。纯Java驱动运行在客户端,并且直接访问数据库,因此运行这个模式要使用一个两层的体系,如下图所示:
JDBC API |
JDBC驱动程序 |
数据源 |
Java应用程序 |
该类型的驱动程序是本地协议完全Java技术性能的驱动程序,同时它的使用也比较简单,客户端不需要安装任何中间件或者运行库。现在大部分厂商都提供第四类驱动程序的支持。
3、 JDBC的用途
Ø 与数据库建立连接
Ø 发送SQL语句
Ø 处理结果
4、 JDBC URL
JDBC URL的概念
JDBC URL提供了一种标识数据库的方法,可以使相应的驱动程序能识别该数据库并与之建立连接。JDBC的作用是提供某些约定,驱动程序编程员在构造它们的JDBC URL时应该遵循这些约定。
u 由于JDBC URL要与各种不同的驱动程序一起使用,因此,这些约定应非常灵活。首先,它们应允许不同的驱动程序使用不同的方案来命名数据库。
u JDBC URL应允许驱动程序编程员将一切所需的信息编入其中。
u JDBC URL应允许某种程度的间接性。即:JDBC URL可指向逻辑主机或数据库名,而这种逻辑主机或数据库名将由网络命名系统动态的转换为实际的名称。
JDBC URL的语法格式
JDBC URL的标准语法如下所示。它由三部分组成,各部分之间用冒号分割。
jdbd:<子协议>:<子名称>
JDBC URL的三个部分可分解如下:
Ø jdbc:协议,JDBC URL中的协议总是jdbc。
Ø <子协议>:驱动程序名或数据库连接机制的名称。
Ø <子名称>:标识数据库的方法。子名称可以依不同的子协议而变化,使用子名称的目的是为定位数据库提供足够的信息。如果数据库是通过Internet来访问的,则在JDBC URL中应将网络地址作为子名称的一部分包括进去,且必须遵循如下所示的标准URL命名约定:
//主机名:端口/子协议
对于不同的数据库,厂商提供的驱动程序和连接的URL都不同,几个主要的数据库厂商与其对应的驱动程序和连接的URL如下所示:
数据库驱动程序和URL
数据库名 | 驱动程序 | URL |
MS SQL Server 2000 | com.microsoft.jdbc.sqlserver.SQLServerDriver | Jdbc:Microsoft:sqlserver://[ip]:[port];user=[user];password=[password] |
JDBC-ODBC | sun.jdbc.odbc.JdbcOdbcDriver | jdbc:odbc:[odbcsource] |
Oracle oci8 | orcle.jdbc.driver.OracleDriver | jdbc.oracle:oci8:@[sid] |
Oracle thin Driver | oracle.jdbc.driver.OracleDriver | jdbd:oracle:thin:@[ip]:[port]:[sid] |
Cloudscape | com.cloudscape.core.JDBCDriver | jdbc:cloudscape:database |
MySQL | org.gjt.mm.mysql.Driver | jdbc:mysql:/ip/database,user,password |
5、 ODBC子协议
子协议odbc是一种特殊情况。它是为指定ODBC风格的数据资源的URL而保留的,并允许在子名称后面指定任意多个属性值。
odbc子协议的完整语法为:
jdbc:odbc:<数据资源名称>[;<属性名>=<属性值>]*
例如:
jdbc:odbc:test
jdbc:odbc:test;UID=uid;PWD=pwd
6、事务
事务就是将一些SQL语句作为一个整体来执行,要么所有语句全部完成,要么一条语句都不执行。当调用方法commit或rollback时,当前事务即告结束,另一个事务谁即开始。
缺省情况下,新连接处于自动提交模式,也就是说,当执行完语句后,将自动对那个语句调用commit方法。这种情况下,由于每个语句都是被单独提交的,因此,一个事务只由一个语句组成。如果禁用自动提交模式,事务将要等到commit或rollback方法被显示调用时才结束,因此,它将包括上一次调用commit或rollback方法以来所有执行过的语句。对于第二种情况,事务中的所有语句将作为组来提交或还原。
方法commit使SQL语句对数据库所做的任何更改成为永久性的,它还将释放事务持有的全部锁,而方法rollback将放弃那些更改。
第二节 JDBC核心类和接口
Driver接口
每个数据库驱动程序必须实现Driver接口。我们在编程中要连接数据库,必须先装载特定厂商提供的数据库驱动程序(Driver),驱动程序的装载方法如下:
Class.forName(“<driver_class_name”>);
DriverManager类
对于简单的应用程序,仅需要使用该类的方法getConnection,该方法将建立与数据库的连接。JDBC允许用户调用DriverManager类的方法getDriver、getDrivers和registerDriver及Driver类的方法connect,但在多数情况下,最好让DriverManager类管理建立连接的细节。
DriverManager类是JDBC的管理层,作用于用户和驱动程序之间,用于管理JDBC驱动程序。它跟踪可用的驱动程序,并在数据库和相应的驱动程序之间建立连接,另外,DriverManager类也处理诸如驱动程序登录时间限制及登录和跟踪消息的显示等事务。
作为初始化的一部分,DriverManager将试图装载系统属性“jdbc.drivers”所指的驱动程序类,这可以让用户定制在它们的应用程序中使用的JDBC驱动程序。例如,在Windows的HOME目录的/.hotjava/properties文件中,可以指定:
Jdbc.drivers=<驱动程序>
另外,程序还可以显示的装载JDBC驱动程序。例如:下述代码用于装载my.sql.Driver
Class.forName(“my.sql.Driver”);
在DriverManager中有一个非常重要的方法,就是getConnection(parameter),通过这个方法可以获得一个连接。
getConnection
public static synchronized Connection getConnection(String url)
尝试建立一个和给定的URL的数据库连接,调用此方法时,DriverManager将在已注册的驱动中选择恰当的驱动来建立连接。
public static synchronized Connection getConnection(String url, Properties info)
与上类似,不过提供了一些属性,这些属性连接特定的数据库需要,至少包含user和password属性。
public static synchronized Connection getConnection(String url, String user,String password)
连接到指定URL的数据库,使用用户名为user,密码为password。
Connection接口
Connection对象代表与数据库的连接,也就是在已经加载的Driver和数据库之间建立连接。
连接过程包括所执行的SQL语句和在该连接上所返回的结果。一个应用程序可与单个数据库有一个或多个连接,或者可与许多数据库有连接。
close
public void close()
关闭到数据库的连接,在使用完连接之后必须关闭,否则连接会保持一段比较长的时间,直到超时。
commit
public void commit()
提交对数据库的更改,使更改生效。这个方法只有调用了setAutoCommit(false)方法后才有效,否则对数据库的更改会自动提交到数据库。
createStatement
public Statement createStatement()
throws SQLException
public Statement createStatement(int resultSetType, int resultSetConcurrency)
throws SQLException
public Statement createStatement(int resultSetType,
int resultSetConcurrency,
int resultSetHoldability)
throws SQLException
创建一个Statement,Statement用于执行SQL语句。
prepareStatement
public PreparedStatement prepareStatement(String sql)
throws SQLException
public PreparedStatement prepareStatement(String sql,
int autoGeneratedKeys)
throws SQLException
public PreparedStatement prepareStatement(String sql,
int[] columnIndexes)
throws SQLException
public PreparedStatement prepareStatement(String sql,
String[] columnNames)
throws SQLException
public PreparedStatement prepareStatement(String sql,
int resultSetType,
int resultSetConcurrency)
throws SQLException
public PreparedStatement prepareStatement(String sql,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability)
throws SQLException
public PreparedStatement prepareStatement(String sql,
String[] columnNames)
throws SQLException
使用指定的SQL语句创建一个预处理语句,sql参数中往往包含一个或者多个“?”占位符。
Statement接口
一个Statement对象仅能有一个ResultSet对象。当完成对表的操作之后,必须重新得到另一个Statement对象,才能进行其他表的操作。
Statement对象可以用于执行一个静态的SQL语句,并得到SQL语句执行后的结果。
execute
public boolean execute(String sql)
throws SQLException
运行语句,返回是否有结果集。
executeQuery
public ResultSet executeQuery(String sql)
throws SQLException
运行查询语句,返回ResultSet对象。
executeUpdate
public int executeUpdate(String sql)
throws SQLException
运行更新操作,返回更新的行数。
PreparedStatement接口
PreparedStatement对象可以代表一个预编译的SQL语句,它从Statement接口继承而来,并与之有如下两方面的不同:
u PreparedStatement实例包含已编译的SQL语句。
u 包含于PreparedStatement对象中的SQL语句可具有一个或多个IN参数。IN参数的值在SQL语句创建时未被指定。相反的,该语句为每个IN参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX方法来提供。
由于PreparedStatement对象已经预编译过,所以其执行速度要快于Statement对象。因此,需要多次执行的SQL语句经常创建为PreparedStatement对象,以提供效率。
作为Statement的子类,PreparedStatement继承了Statement的所有功能。另外它还添加了一整套方法,用于设置发送给数据库以取代IN参数占位符的值。
另外,Statement接口中的三种方法execute、executeQuery和executeUpdate已经被更改,使之不再需要参数。
所有的setXXX方法的原型如下,其中XXX是与该参数相应的类型。例如,如果参数具有Java类型long,则使用的方法就是setLong。setXXX方法的第一个参数parameterIndex为要设置的参数的序数位置,第二个参数是设置给该参数的值。
setXXX
public void setXXX(int parameterIndex, XXX x)
throws SQLException
设定指定位置的参数值。
execute
public boolean execute()
throws SQLException
运行语句,返回是否有结果集。
executeQuery
public ResultSet executeQuery()
throws SQLException
运行查询语句,返回ResultSet对象。
executeUpdate
public int executeUpdate()
throws SQLException
运行更新操作,返回更新的行数。
ResultSet接口
ResultSet提供了对数据库中表的访问,一个ResultSet对象通常由执行一个Statement而生成。
ResultSet对象维护了一个光标指针,用来描述当前记录的位置,最初,光标指向第一个记录的位置之前,next方法将光标移到到下一个记录,其方法的原型如下。
boolean next() throws SQLException
getXXX方法得到当前记录的字段的值,可以使用字段名或其索引得到自动的值,一般来说,使用字段的索引效率更高,字段的编号从1开始,1为当前记录的第一个字段,2为第二个字段,以此类推。
在ResultSet接口中,还有一系列的updateXXX方法,可以用来更新当前记录的字段值。
getXXX
public XXX getXXX(int parameterIndex)
throws SQLException
public XXX getXXX(String columnName)
throws SQLException
获取指定位置或者指定列的查询结果值。
第三节 JDBC编程实例
基本步骤
Ø 输入java.sql包
在程序的开头,必须加入下面的代码:
Import java.sql.*;
Ø 声明变量
在代码中,一般要声明三个相关的变量。Stmt用于SELECT语句,pstmt用于UPDATE语句,rs用于SELECT的结果集。
Statement stmt;
PreparedStatement pstmt;
ResultSet rs;
Ø 加载jdbc驱动程序
Class.forName(“<jabc驱动程序>”);
Ø 定义JDBC URL
String url = “jdbc:odbc:oracle:thin@ 10.10.10 .1:db 9” ;
Ø 连接数据库
Connection conn = DriverManager.getConnection(url);
Ø 进行相应的数据操作
根据需要创建Statement或PreparedStatement实例,执行SELECT操作或UPDATE操作。
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table2");
Ø 关闭数据库连接
conn.close();
查询记录信息
Vector data = new Vector();
ResultSet rs = null;
Statement stmt = null;
Connection conn = null;
//注册驱动器
try
{
//加载驱动程序
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
//Class.forName("oracle.jdbc.driver.OracleDriver");
//得到数据库联接
conn= DriverManager.getConnection("jdbc:oracle:thin:@192.168.110.52:1521:edu","study","study");
//执行sql语句
stmt = conn.createStatement();
//返回数据集合
rs = stmt.executeQuery("select * from book where bookid=‘ 0007’ ");
while (rs.next())
{
DataObject dataObject = new DataObject();
dataObject.setBookId(rs.getString("bookid"));
dataObject.setBookName(rs.getString("bookname"));
dataObject.setBookNumber(rs.getString("booknumber"));
data.addElement(dataObject);
}
}
catch (SQLException e1)
{
e1.printStackTrace();
}
catch (ClassNotFoundException e)
{
System.out.println("Driver not found");
}
//关闭联接
finally
{
try
{
rs.close();
stmt.close();
conn.close();
}
catch (SQLException e2)
{
e2.printStackTrace();
}
}
插入记录信息
String sql;
Connection conn = null;
PreparedStatement ps=null;
//注册驱动器
try
{
//加载驱动程序
Class.forName("oracle.jdbc.driver.OracleDriver");
// 得到数据库联接
conn= DriverManager.getConnection("jdbc:oracle:thin:@192.168.110.52:1521:edu","study","study");
// 执行sql语句
sql="insert into book ( bookid,bookname,booknumber) values (?,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "0021");
ps.setString(2, "数据库概论");
ps.setInt(3, 11);
ps.executeUpdate();
}
catch (SQLException e1)
{
e1.printStackTrace();
}
catch (ClassNotFoundException e)
{
System.out.println("Driver not found");
}
// 关闭联接
finally
{
try
{
ps.close();
conn.close();
}
catch (SQLException e2)
{
e2.printStackTrace();
}
}
更新记录信息
int state;
Statement stmt = null;
Connection conn = null;
//注册驱动器
try
{
//加载驱动程序
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
//Class.forName("oracle.jdbc.driver.OracleDriver");
//得到数据库联接
conn= DriverManager.getConnection("jdbc:oracle:thin:@192.168.110.52:1521:edu","study","study");
//执行sql语句
stmt = conn.createStatement();
//返回数据集合
state=stmt.executeUpdate("update book set booknumber=9 where bookname='java编程思想'");
if(state>0)
{
System.out.println("更新成功");
}
else
{
System.out.println("更新失败");
}
}
catch(SQLException e1)
{
e1.printStackTrace();
}
catch(ClassNotFoundException e)
{
System.out.println("Driver not found");
}
//关闭联接
finally
{
try
{
stmt.close();
conn.close();
}
catch(SQLException e2)
{
e2.printStackTrace();
}
}
删除记录信息
boolean state=false;
Statement stmt = null;
Connection conn = null;
//注册驱动器
try
{
//加载驱动程序
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
//Class.forName("oracle.jdbc.driver.OracleDriver");
//得到数据库联接
conn= DriverManager.getConnection("jdbc:oracle:thin:@192.168.110.52:1521:edu","study","study");
//执行sql语句
stmt = conn.createStatement();
//返回数据集合
state=stmt.execute("delete from book where bookid='0007'");
if(state)
{
System.out.println("删除成功");
}
else
{
System.out.println("删除失败");
}
}
catch (SQLException e1)
{
e1.printStackTrace();
}
catch(ClassNotFoundException e)
{
System.out.println("Driver not found");
}
//关闭联接
finally
{
try
{
stmt.close();
conn.close();
}
catch (SQLException e2)
{
e2.printStackTrace();
}
}
第四节 JDBC数据库连接池
在实际应用开发中,特别是在Web应用系统中,如果JSP、Servlet或EJB使用JDBC直接访问数据库中的数据,每一次数据访问请求都必须经历建立数据库连接、打开数据库、存取数据和关闭数据库连接等过程,而连接并打开数据库是一件既消耗资源又费时的工作,如果频繁的发生这种数据库操作,系统的性能必然会急剧的下降,甚至会导致系统崩溃。数据库连接池技术是解决这个问题最常用的方法,在许多应用程序服务器中,基本都提供了这项技术,无需自己编程。
数据库连接池技术的思想非常简单,将数据库连接作为对象存储在一个vector对象中,一旦数据库连接建立后,不同的数据库访问请求就可以共享这些连接,这样,通过复用这些已建立的数据库连接,可以克服上述缺点,极大的节省了系统资源和时间。
数据库连接池的主要操作如下:
Ø 建立数据库连接池对象
Ø 按照事先指定的参数创建初始数量的数据库连接
Ø 对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大,创建一个新的数据库连接
Ø 存取数据库
关闭数据库,释放所有数据库连接。
第三章 JSP
第一节 JSP简介
JSP概述
JSP由Sun Microsystems 公司倡导、许多公司参与一起建立的一 种动态网页技术标准,其在动态网页的建设中有其强大而特别的功能。Java Server Pages技术可以让web开发人员和设计人员非常容易的创建和维护动态网页,特别是目前的商业系统
JSP技术原理
编写一个简单的HTML文件——test.html,在这个HTML中包含一个表单,我们希望能够接收用户在这个表单中填入的字符串,并把它打印出来。
Test.html 的代码:
<html> <head> <title>show string</title> </head> <body> <form method=post action=”test.jsp”> <p>your string:</p> <input type=”text” name=”myString”> <input type=submit value=”submit”> </form> </body> </html> |
Test.jsp的代码:
<%@ page contentType="text/html; charset=gb2312" %> <html> <head><title>show my string</title></head> <body> <% String strMyString = (String)request.getParameter(“myString”); out.println(strMyStirng); %> </body> </html>
|
[注:在此我们不做任何有效性验证]
过程描述:
1、用户在test.htm中填写字符串,如“Hello , world!!!”,并点击“submit”按钮
2、浏览器将用户提交的请求(其中包含用户填写的数据信息和目标jsp)发送到Servlet容器
3、如果该jsp没有被编译过的话,Servlet容器会编译它;若jsp已被编译完成,则Servlet容器会调用这个jsp执行
4、执行的结果,通过response反映
具体流程见下图:
点击“submit”提交请求
显示服务器产生的响应 |
编译、执行JSP,生成动态内容 |
发送请求 |
发送响应 |
为什么使用JSP
JSP技术是由Servlet技术发展起来的,自从有了JSP后,在java服务器端编程中普遍采用的就是JSP技术。因为JSP在编写表示页面时远远比Servlet简单,而且不需手工编译——由Servlet容器自动编译。
而Servlet目前更多的是作为控制器来使用。
JSP构建在Servlet上,拥有Servlet所有强大的功能。
JSP特点
Ø 将内容的生成和显示进行分离
在服务器端,JSP引擎解释JSP标识和小脚本,生成所请求的内容,并且将结果以HTML(或者XML)页面的形式发送回浏览器。
Ø 强调可重用的组件
绝大多数JSP页面依赖于可重用的,跨平台的组件(JavaBeans或者Enterprise JavaBeansTM组件)来执行应用程序所要求的更为复杂的处理。
Ø 采用标识简化页面开发
Web页面开发人员不会都是熟悉脚本语言的编程人员。Java Server Page技术封装了许多功 能,这些功能是在易用的、与JSP相关的XML标识中进行动态内容生成所需要的。
动态网页技术比较
与CGI
• 后台实现逻辑是基于Java Component的,具有跨平台的特点。
• 将应用逻辑与页面表现分离,使得应用逻辑能够最大程度得到复用,从而提高开发效率。
• 运行比CGI方式高,尤其对于数据库访问时,提供了连接池缓冲机制,使运行所需资源最小。
• 安全,由于后台是完全基于Java技术的,安全性由Java的安全机制予以保障
• 内置支持XML,使用XML从而使页面具有更强的表现力和减少编程工作量。
第二节 JSP页面组成
一个典型的JSP文件
首先,我们来看一个典型的JSP文件:
<%@ page contentType="text/html; charset=gb2312" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ page import=“com.iss.scm.bizlogic.outbound.OutboundBiz” %> <!—这是一个典型的JSP,它包含了JSP中常用的元素 --> <%! String getDate() { return new java.util.Date().toString(); } %> <html> <head><title>title</title></head> <body> <% out.println(getDate()); %> </body> </html>
|
分析JSP文件中的元素
从上面的JSP文件中,可以总结出五类元素:
Ø 注释
Ø 模版元素
Ø 脚本元素
Ø 指令元素
Ø 动作元素
下面,我们分别介绍这些元素:
1、注释
JSP中有多种注释,有JSP自带的注释规范,也有HTML/XML的注释规范。
u HTML/XML注释
在客户端显示一个注释。
JSP语法:<!--comment [<%= expression %>] -->
例子:<!--当前时间为:<%= (new java.util.Date()).toLocalString() %> -->
描述:这种注释和HTML中很像,唯一不同之处就是,可以在这个注释中用表达式。
u 隐藏注释
写在JSP程序中,但不发给客户。
JSP语法:<%-- comment --%>
例子:
<%-- 下面是使用表达式的例子 --%>
<%= getDate() %>
描述:用隐藏注释标记的字符会在JSP编译时被忽略。JSP编译器不会对<%--和--%>之间的语句进行编译,它不会显示在客户的浏览器中,也不会在源代码中看到。
u Scriptlets中的注释
Scriptlets中包含的是java代码,所以java中的注释规范在Scriptlets中也能使用。
语法:// 或 /* … */
例子:
/ *
* 这是一个注释
*/
2、模版元素
模版元素是指JSP的静态HTML或XML内容,通常由美工或网页工程师来负责完成。模版元素是网页的框架,它影响页面的结构和美观程度。在JSP编译时,这些模版元素会被编译到Servlet里面。比如<body>被编译时,会用以下代码替代:
out.write(“<body>”);
3、脚本元素
脚本元素主要包括:
u 声明(Declaration)
声明是在JSP程序中声明合法的变量和方法,如:
<%! String getDate()
{
return new java.util.Date().toLocaleString();
}
int iCount = -1;
%>
u 表达式(Expression)
表达式就是位于<%=和%>之间的代码,如:
<%! String strBookName = “Design Pattern” %>
… …
<%= strBookName %>
或
<%= getDate() %>
u Scriptlets
Scriptlets位于<%和%>之间,它们是合法的java代码,如:
<% String[] strEmp= {‘aaa’,’bbb’,’ccc’}; %> <table> <% for(int i = 0;i < 3;i++) { %> <tr><td><%= strEmp[i] %></td></tr> <% } %> </table>
|
4、指令元素
JSP中有三种指令元素,它们是:
u 页面(page)指令
语法:
<%@ page
[language=”java”]
[import=”{package.class|package.*},…”]
[contentType=”TYPE;charset=CHARSET”]
[session=”True | False”]
[buffer=”none | 8kb | sizekb”]
[autoFlush=”True | False”]
[isThreadSafe=”True | false”]
[info=”text”]
[errorPage=”relativeURL”]
[isErrorPage=”true | false”]
[extends=”package.class”]
[isELIgnored=”true | false”]
[pageEncoding=”peinfo”]
%>
例子:<%@ page contentType="text/html; charset=gb2312" %>
u include指令
例子:<%@ include file=“/slof/outbound/outbound.jsp” %>
u taglib指令
例子:
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
…
<bean:define
id="sendStockNO" name="outboundForm" property= "outboundEntity.sendStockNO"/>
5、动作元素
动作可能影响当前的输出流或者用来创建、使用、改变对象。Jsp规范包含标准的动作类型,新的动作类型由taglib指令定义。动作遵循XML语法标准。
动作元素在请求处理阶段起作用。
语法:
<prefix:tag attribute=value attribute-list … />
或者
<prefix:tag attribute=value attribute-list …>
…
</prefix:tag>
JSP规范定义了一系列的标准动作,它们用jsp作为前缀,这些动作元素有:
<jsp:useBean>,<jsp:setProperty>,<jsp:getProperty>,<jsp:param>,<jsp:include>,<jsp:forward>,<jsp:plugin>,<jsp:fallback>,<jsp:params>,<jsp:attribute>,<jsp:body>,<jsp:invoke>,<jsp:doBody>,<jsp:element>,<jsp:text>,<jsp:output>。
下面我们介绍几个典型的动作元素
n <jsp:param>
jsp:param操作被用来以“名-值”对的形式为其他标签提供附加信息。
使用方式:<jsp:param name=”paramName” value=”paramValue” />。其中,name为属性相关的关键字,value为属性的值。
n <jsp:include>
jsp:include操作允许在请求时间内在现成的jsp页面里面包含静态或者动态资源。被包含的对象只有对JspWriter对象的访问权,并且它不能设置头或者Cookie。如果页面输出是缓冲的,那么缓冲区的刷新要优于包含的刷新。另外,此指令在运行上效率比<%@ page include %>指令的效率低。
使用方式:<jsp:include page=”fileName” flush=”true” />。其中,page为一个相对路径,或者是代表相对路径的表达式;flush属性的可选值包括true和false,当flush属性为true,表示在执行插入操作之前,先调用当前页面的输出流的flush()方法。
n <jsp:forward>
jsp:forward操作是运行将请求转发到另一个JSP、Servlet或者静态资源文件。请求被转向到的资源必须位于同JSP发送请求相同的上下文环境之中。每当遇到此操作时,就停止执行当前的JSP,转而执行被转发的资源。
使用方式:<jsp:forward page=”uri” />。其中,page值为一个表达式或一个字符串,用于说明将要定向的文件或URL。这个文件可以是JSP文件,也可以是程序段,或者其他能够处理request对象的文件。
n <jsp:useBean>
该标签用来在JSP页面中创建一个bean实例,并指定它的名字以及作用范围。它保证对象在标签指定的范围内可以使用。
使用方式:<jsp:useBean id=”ID” scope=”page | request | session | application” typeSpec />。
其中,id是一个大小写相关的名字,用来表示这个实例;scope表示此实例可以使用的范围;typeSpec可以是以下四者之一:
class = “className”
class = “className” type = “typeName”
beanName = “beanName” type = “typeName”
type = “typeName”
scope解释:
page
能够在JSP文件以及此文件中的所有静态包含文件中使用Bean,直到页面执行完毕向客户端发回响应或转到另一个文件为止。
request
在请求的范围内有效
session
在会话范围内有效,所有的JSP都可以访问
application
在应用服务器启动时创建,所有的JSP都可以访问
n <jsp:setProperty>
此操作和useBean一起协作,用来设置Bean的属性。该标签将会使用Bean给定的setter方法,为Bean中的属性赋值。
使用方法:<jsp:setProperty name=”beanName” propertyDetails />
其中,name的值为在useBean标签中设置的id;propertyDetails可以是下面几个中的一个:
property=”*”
property=”propertyName” param=”parameterName”
property=”propertyName”
property=”propertyName” value=”propertyValue”
n <jsp:getProperty>
此操作和useBean一起协作,用来获取Bean的属性的值。该标签将会使用Bean给定的getter方法。
使用方法:
<jsp:getProperty name=”beanName” property=”propertyName” />
n <jsp:plugin>
jsp:plugin用来产生客户端浏览器的特别标签,一般用它来插入Applet或者JavaBean。当JSP文件被编译把结果发送到浏览器时,<jsp:plugin>元素将会根据浏览器的版本替换成<object>或<embed>元素。
使用方法:
<jsp:plugin
type=”bean | applet”
code=”classFileName”
codebase=”classFileDirectoryName”
[name=”instanceName”]
[archive=”URIToArchive,…”]
[align=”bottom | top | middle | left | right”]
[height=”displayPixels”]
[width=”displayPixels”]
[hspace=”leftRightPixels”]
[vspace=”topBottomPixels”]
[nspluginurl=”URLToPlugin”]
[iepluginurl=”URLToPlugin”]
[<jsp:params>
[<jsp:param name=”parameterName” value=”{parameterValue | <%= expression %>}” />]
</jsp:params>]
[<jsp:fallback>text message for user</jsp:fallback>]
>
</jsp:plugin>
其中,type指示将被执行的插件对象的类型;code指示java类文件的名称;codebase指示运行java类的目录或指向这个目录的路径;name指示Bean或Applet的实例的名称;archive指示以逗号分隔的路径名列表;
n <jsp:fallback>
用于java插件不能启动时,显示给用户的一段文字。
使用方式:
<jsp:plugin type=applet code=”package.class” codebase=”.”>
…
<jsp:fallback>text message for user</jsp:fallback>
…
</jsp:plugin>
第三节 JSP内部对象
out
out对象被封装成javax.servlet.jsp.JspWriter接口。主要用来向客户端输出数据。
request
request对象代表请求对象,它被封装成HttpServletRequest接口。
通过getParameter方法可得到request参数,通过GET、POST、HEAD等方法可以得到request的类型,通过Cookies、Referer可得到请求的http头。
来自客户端的请求经Servlet容器处理后,由request对象进行封装。它作为jspService()方法的一个参数由容器传递给jsp页面。
response
response被包装成HttpServletResponse接口。它封装了JSP产生的响应,然后被发送到客户端以响应客户的请求。由容器生成,作为jspService()方法的参数被传入jsp。
pageContext
pageContext对象被封装成javax.servlet.jsp.PageContext接口,它为jsp页面包装成页面的上下文,管理对属于jsp中特殊可见部分中已命名对象的访问。它的创建和初始化都是由容器来完成的,jsp页面里面可以直接使用pageContext对象的句柄。
session
session对象用来保存每个用户信息,以便跟踪每个用户的操作状态。session信息保存在容器里,session的ID保存在客户机的Cookie中。一般情况下,用户首次登陆系统时容器会给此用户分配一个唯一标识的session id,这个id用于区分其他的用户,当用户退出系统时,这个session就会自动消失。
Session被封装成HttpSession接口。
application
application对象为多个应用程序保存信息,对于一个容器而言,每个用户都共同使用一个application对象,服务器启动后,就会自动创建application对象,这个对象会一直保持,直到服务器关闭为止。
config
config对象被封装成javax.servlet.ServletConfig接口,他表示Servlet的配置。当一个Servlet初始化时,容器把某些信息通过此对象传递给这个Servlet。
page
page对象是java.lang.Object类的一个实例。它指的是jsp实现类的实例,也就是说它是jsp本身,通过这个可以对它进行访问。
exception
exception对象是java.lang.Throwable类的一个实例。它指的是运行时的异常,也就是被调用的错误页面的结果,只有在错误页面才可以使用。
要使用exception对象,必须在page指令指定isErrorPage=“true”,在出错页面中,使用<%= exception.getMessage() %>来获取错误信息。
第四章 Struts
第一节 为什么使用Struts
讨论为什么使用Struts,先让我们来看一下传统单页方法的优缺点:
优点:
只要简单设置应用服务器,然后就可以开始编写代码。不需用核心文件来保持同步,也不需要冗长的库初始配置。由每个单独的 JSP 页面或 servlet 来指定连接。
缺点:
• HTML 和 Java 混合在一起
JSP 文件的编写者必须既是网页设计者,又是 Java 开发者。其结果通常要么是很糟的 Java 代码,要么是难看的网页,有时甚至 Java 代码和网页都很糟。
• Java 和 JavaScript 的不足
随着网页逐渐变大,很容易想到实现一些 JavaScript。当网页中出现 JavaScript 时,这种脚本就可能与 Java 代码产生混淆。
• 强耦合
更改业务逻辑或数据可能牵涉相关的每个网页。
• 内嵌的流程逻辑
要理解应用程序的整个流程,您必须浏览所有网页。试想一下拥有 1000 个页面的错综复杂的逻辑。
• 调试困难
除了很糟的外观之外,HTML 标记、Java 代码和 JavaScript 代码都集中在一个网页中还使调试变得相当困难。
• 页面代码美观
在很大的网页中,编码样式看起来杂乱无章。即使有彩色语法显示,阅读和理解这些代码仍然比较困难。
• 在应用程序的不同部分中重用表示很困难
一定程度上,可以使用 <jsp:include/> 标记来解决一部分重用,但它们在管理更改方面不能很好地工作。
• 公共输入和数据处理任务枯燥且重复
错误处理是普通 JSP 页面的常见问题。而且,必须手工填入表单值及手工检索请求对象中的那些值,这是一件既耗时又枯燥的工作。
第二节 设计模式MVC
什么是设计模式
简单来说,Design Patten 就是一个常用的方案。 在我们的开发过程中,经常会遇到一些相同或者相近的问题,每次我们都会去寻找一个新的解决方法,为了节省时间提高效率,我们提供一些能够解决这些常见问题的,被证实可行的方案,构成一个统一的资源库。
一个Design Patten描述了一个被证实可行的方案。这些方案非常普通,是有完整定义的最常用的模式。 这些模式可以被重用,有良好的伸缩性。
什么是MVC
MVC 通过将问题分为三个类别来帮助解决单一模块方法所遇到的某些问题,这就Model(模型)、View视图)、Controller(控制器) 。
其中:
Ø Model(模型)
模型包含了应用程序的核心,它封装了应用程序的数据结构和事务逻辑,集中体现了应用程序的状态。有时候它仅包含了状态信息,因为它并不了解视窗或控制器的信息(任何时候它对视图或控制器都一无所知 )。
JavaBean及 EJB 很适合扮演这个角色,因为它能够处理绝大部分事务逻辑和数据结构。它能够与数据库或文件系统进行交互,承担维护应用程序数据的责任。
Ø View(视图)
视图实现模块的外观,它是应用程序的外在表现。它可以访问模型的数据, 却不了解模型的情况, 同时它也不了解控制器的情况。当模型发生改变时, 视图会得到通知,它可以访问模型的数据,但不能改变这些数据 。
Ø Controller(控制器)
控制器对用户的输入做出反应并且将模型和视图联系在一起。servlet能够接受客户端的HTTP请求,并且根据需要创建所需的JavaBean或者EJB,然后将产生的变化通知给视窗 。
MVC设计模式的特点
• 代码分离,显示与逻辑解耦
• 验证处理
• 流程控制
• 更新应用程序的状态
MVC模式的结构
面向WEB应用的 MVC Model 2 模式
客户机和服务器的无状态连接
– 由于HTTP本身缺乏状态信息,客户端必须对服务器进行再查询才能发现由输入所造成的改变,在这种情况下,控制器不能将应用程序的改变通知视图。
实现视图所用的技术与实现模型或控制器的技术不同。
– 也可以使用Java代码来生成所有的Html,但这样会产生更多问题。
第三节 Struts概览
上图表示的是Struts的MVC。
其中
Client Browser是客户发送请求的地方。
Controller Servlet接收来自浏览器的请求,并决定将这个请求发往何处;在Struts中,控制器是以 servlet 实现的一个命令设计模式。
Business Logic更新模型的状态,并帮助控制应用程序的流程。就 Struts 而言,这是通过作为实际业务逻辑“瘦”包装的 Action 类完成的。
Model表示应用程序的状态。业务对象更新应用程序的状态。ActionForm bean 在会话级或请求级表示模型的状态,而不是在持久级。JSP 文件使用 JSP 标记读取来自 ActionForm bean 的信息。
View就是一个 JSP 文件。其中没有流程逻辑,没有业务逻辑,也没有模型信息, 只有标记。标记是使 Struts 有别于其他框架(如 Velocity)的因素之一。
Struts中所使用的组件
Struts中所使用的组件有:ActionServlet、ActionClass、ActionForm、ActionMapping、ActionForward、ActionError、Struts标记库。
Ø ActionServlet
Struts框架中的控制器;为org.apache.struts.action.ActionServlet,该类扩展了javax.servlet.http.HttpServlet,是Struts框架的核心。
ActionServlet (Command) 创建并使用Action、ActionForm 和 ActionForward。在初始化时读取 struts-config.xml 文件配置。
基本功能:
• 截获用户的Http请求
• 把这个请求映射到相应的Action类,如果这是此类收到的第一个请求, 将初始化实例并缓存
• 创建或发现一个ActionForm bean实例(看配置文件是否定义),然后将请求过程移植到bean.
• 调用Action实例的perform()方法并将ActioForm bean,Action Mapping对象,request和response对象传给它
• perform返回一个ActionForword对象,此对象连接到相应的jsp页面
在web.xml中的配置:
<servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</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>
|
Ø ActionClass
Struts框架中封装事务逻辑。
所有Action类都扩展org.apache.struts.action.Action类,并且覆盖类中定义的perform()方法。 Action 类是业务逻辑的一个包装 ,用途是将 HttpServletRequest 转换为业务逻辑。 当事件进展到这一步时,输入表单数据(或 HTML 表单数据)已被从请求流中提取出来并转移到 ActionForm 类中。 ActionForm 类被传递到Actino类,这一切都是自动完成的。扩展 Action 类时要注意简洁。Action 类应该控制应用程序的流程,而不应该控制应用程序的逻辑。通过将业务逻辑放在单独的包或 EJB 中,我们就可以提供更大的灵活性和可重用性。
Action类必须以“线程安全”的方式进行编程,因为控制器会令多个同时发生的请求共享同一个实例,相应的,在设计Action类时就需要注意以下几点:
• 不能使用实例或静态变量存储特定请求的状态信息,它们会在同一个操作中共享跨越请求的全局资源
• 如果要访问的资源(如JavaBean和会话变量)在并行访问时需要进行保护,那么访问就要进行同步
Action在struts-config.xml的配置:
<action path="/cautionsave" type="org.apache.struts.webapp.example.CautionSaveAction" name="cautionsaveForm" scope="session" input="/S63.jsp"> <forward name="select" path="/S64.jsp"/> </action>
|
Ø ActionForm
用于封装数据,显示或向ActionClass的方法传送数据。
ActionForm类扩展org.apache.struts.action.ActionForm类,程序开发人员创建的bean能够包含额外的属性。 框架假设用户在应用程序中为每个表单都创建了一个ActionForm bean,维护 Web 应用程序的会话状态。如果使用动态ActionForm类,则只需在struts-config.xml中进行相应的配置,框架可自动生成ActionForm bean。典型的ActionFrom bean只有属性的设置与读取方法(getXXX),而没有实现事务逻辑的方法。只有简单的输入检查逻辑,使用的目的是为了存储用户在相关表单中输入的最新数据,以便可以将同一网页进行再生,同时提供一组错误信息,这样就可以让用户修改不正确的输入数据
Struts 框架将对ActionForm执行以下操作 :
• 检查 UserActionForm 是否存在;如果不存在,它将创建该类的一个实例。
• 将使用 HttpServletRequest 中相应的域设置 ActionForm 的状态。没有太多讨厌的 request.getParameter() 调用。
• Struts 框架在将 ActionForm传递给业务包装 Action 之前将更新它的状态。
• 在将它传递给 Action 类之前,Struts 还会对 ActionForm调用 validation() 方法进行表单状态验证,但不提倡这种做法。
• 可在会话级维护 ActionForm
ActionForm在struts-config.xml中的配置:
<form-bean name="logonForm" type=" org.apache.struts.webapp.example.LogonForm "> <form-property name=“userpwd" type="java.lang.String"/> <form-property name=“username" type="java.lang.String"/> </form-bean>
|
Ø ActionMapping
帮助控制器将请求映射到操作。
struts-config.xml 配置信息被转换为一组 ActionMapping,而后者又被放入 ActionMappings 容器中。 ActionMapping对象帮助进行框架内部的流程控制,它们可将特定请求URI映射到特定Action类,并且将Action类与ActionForm bean相关联。ActionServlet (Command) 通过 perform() 方法将 ActionMapping 传递给 Action 类。这样就使 Action 可访问用于控制流程的信息。Action将使用ActionMapping的findForward()方法,此方法返回一个指定名称的ActionForward,这样Action就完成了本地转发。
Ø ActionForward
用来指示操作转移的对象。
Ø ActionError
用来存储和回收错误。
ActionError 是 Struts 保持错误列表的方式,封装了单个错误消息。 ActionError类从不独立进行错误处理,它们总是被存储在ActionErrors对象中,View 可以使用标记访问这些类 。 如下所示:
<html:errors/>
Ø Strtus标记库
用于View层显示,减轻表示层的开发工作。
JSP视图组件所使用的 struts 标记库由四类标记组成:
• Bean标记:用来在JSP页中管理bean
• Logic标记:用来在JSP页中控制流程
• HTML标记:用来生成HTML标记,在表单中显示数据,使用会话ID对URL进行编程
• 模板标记:使用动态模板构造普通格式的页
下面,分别为大家介绍这四类标记。
1.Bean标记
• 创建和复制bean的标记
<bean:define id=”lID” value=” 1234” Type=“java.long.int”/>
• 脚本变量定义标记
<bean:header id=”myHeader” name=”Accept-Language”/>
• 显示Bean属性
<bean:write name=”myBean” property=”myProperty” scope=”request” filter=”true”/>
• 消息标记和国际化
<bean:message key="message.cautionmoneysave.accountno"/>
java.util数据包中定义的Locale和ResourceBundle
2.Logic标记
• 实体常数比较标记
<logic:equal parameter=”lTransactionTypeID” value=” 12 ” > 业务类型为保证金存款</logic:equal>
• 属性存在标记
<logic:notPresent name=“collect" scope="request">
<TR><TD class=ItemBody height=20 width="10%">
</TD></TR>
</logic:notPresent>
• 匹配标记
– <logic:match>
– <logic:notMatch>
• 重复标记
<logic:iterate id="info" name="userrequest">
<TR><TD
class=ItemBody height=20 width="10%">
<bean:write name="info" property="m_lID" />
</TD> </TR>
</logic:iterate>
• 转发和重定向标记
转发标记
<logic:forward name=”myGlobalForward”/>
重定向标记
<logic:redirect>标记是一个能够执行HTTP重定向的强大工具
3.HTML标记
• 显示表单元素和输入控件
表单标记
<html:form action=“logon” focus=“username”>
表单内容…
</html:form>
其他还有按钮和取消标记、 复位和提交标记 、文本和文本区标记 、检查框和复选框标记 、文件标记 、单选钮标记 、隐藏标记 、密码标记 、选择标记等等,可查http://apache.jakarta.com网站。
• 显示错误信息的标记
– <html:errors/>
• 其他HTML标记
– <html:html> : 显示HTML元素
– <html:img> : 显示图象标记
– <html:link> : 显示HTML链接或锚点
– <html:rewrite> : 创建没有锚点标记的URI
4.模板标记
• 动态模板是模块化WEB页布局设计的强大手段
• 插入标记
<tiles:insert definition="doc.mainLayout" flush="true“/>
• 放置标记
<tiles:put name="body" value="S63body.jsp" />
• 获得标记
<tiles:get name="header" />
第四节 Struts工作流程
Struts的工作流程见下图
1、应用服务启动时读取struts-config.xml配置文件
2、根据strus-config.xml中的配置项生成Action Mappings;Mapping中的信息包括:path、action、form bean、forwards。
3、当web浏览器端发过来请求时,由充当Controller的ActionServlet获取请求信息
4、ActionServlet根据获取的request查找path,决定使用哪个form bean和哪个action;在此,我们假设使用form bean2和action2
5、ActionServlet判断是否存在form bean实例和action实例,若存在则重用,若不存在则创建
6、ActionServlet将控制权转交给Action,由Action来调用业务逻辑层的服务;form bean作为参数传入到action中,由action从中获取数据并传送到业务逻辑层
7、Action从业务逻辑层获取服务之后,返回ActionForward,控制权重新回到ActionServlet手中
8、ActionServlet根据ActionForward中的内容,查找Mappings,确定下一步显示哪个.jsp页面
9、应用服务器容器编译jsp文件,当遇到需要读取数据的tag时,调用form bean2的getter方法
10、 编译jsp为html并发送给浏览器
第五节 Struts配置
Web.xml
– 在web.xml中声明ActionServlet,并且将它配置成启动时进行加载
– 指定ActionServlet的启动配置文件如:struts-config.xml
– 指定ActionServlet的启动模板配置文件(如果需要的话)如:tiles-defs.xml
– 配置Http请求所要映射的servlet名称
– 配置所有系统及用户自定义的标签库
Struts-config.xml
– 全局转发
– ActionMapping类
– ActionForm bean
– JDBC数据源
– 指定资源文件的位置
– 配置需要的Plug in
如:
添加校验模块,并指定其配置文件validator.xml、validator-rules.xml的位置
需要的Jar包
Struts.jar
tiles.jar
jakarta-regexp-1.2.jar
commons-validator.jar
commons-beanutils.jar
commons-collections.jar
commons-dbcp.jar
commons-digester.jar
commons-logging.jar
commons-pool.jar
commons-services.jar
第五章 Hibernate
第一节 Hibernate概述
什么是Hibernate
在今日的企业环境中,把面向对象的软件和关系数据库一起使用可能是相当麻烦、浪费时间的。Hibernate 是一个面向Java 环境的对象/关系数据库映射工具。对象/关系数据库映射(object/relational mapping (ORM))这个术语表示一种技术,用来把对象模型表示的对象映射到基于SQL 的关系模型结构中去。
Hibernate 不仅仅管理Java 类到数据库表的映射,还提供数据查询和获取数据的方法,可以大幅度减少开发时人工使用SQL 和JDBC 处理数据的时间。Hibernate 的目标是对于开发者通常的数据持久化相关的编程任务,解放其中的95%。
Hibernate体系结构
对Hibernate 非常高层的概览:
这幅图展示了Hibernate 使用数据库和配置文件数据来为应用程序提供持久化服务(和持久化的对象)。
Hibernate 是比较复杂的,提供了好几种不同的运行方式。我们展示一下两种极端情况。
轻型体系结构
应用程序自己提供JDBC 连接,并且自行管理事务。这种方式使用了Hibernate API 的一个最小子集。
全面解决体系结构
对于应用程序来说,所有的底层JDBC/JTA API 都被抽象了,Hibernate 会替你照管所有的细节。
体系结构相关对象
n SessionFactory (net.sf.hibernate.SessionFactory)
对编译过的映射文件的一个线程安全的,不可变的缓存快照。它是Session 的工厂,是ConnectionProvider 的客户。可能持有事务之间重用的数据的缓存。
n 会话Session (net.sf.hibernate.Session)
单线程,生命期短促的对象,代表应用程序和持久化层之间的一次对话。封装了一个JDBC 连接,也是Transaction 的工厂。持有持久化对象的缓存。
n 持久化对象(Persistent Object)及其集合(Collection)
生命期短促的单线程的对象,包含了持久化状态和商业功能。它们可能是普通的JavaBeans,唯一特别的是他们现在从属于且仅从属于一个Session。
n 临时对象(Transient Object)及其集合(Collection)
目前没有从属于一个Session 的持久化类的实例。他们可能是刚刚被程序实例化,还没有来得及被持久化,或者是被一个已经关闭的Session 所实例化的。
n 事务,Transaction (net.sf.hibernate.Transaction)
(可选) 单线程,生命期短促的对象,应用程序用它来表示一批工作的原子操作。是底层的JDBC,JTA 或者CORBA 事务的抽象。一个Session 可能跨越多个Transaction事务。
n ConnectionProvider(net.sf.hibernate.connection.ConnectionProvider)
(可选)JDBC 连接的工厂和池。从底层的Datasource 或者 DriverManager 抽象而来。对应用程序不可见。
n TransactionFactory (net.sf.hibernate.TransactionFactory)
(可选)事务实例的工厂。对应用程序不可见。
第二节 Hibernate基础语义
Configuration
正如其名,Configuration 类负责管理Hibernate 的配置信息。Hibernate 运行时需要获取一些底层实现的基本信息,其中几个关键属性包括:
Ø 数据库URL
Ø 数据库用户
Ø 数据库用户密码
Ø 数据库JDBC驱动类
Ø 数据库dialect,用于对特定数据库提供支持,其中包含了针对特定数据库特性的实现,如Hibernate数据类型到特定数据库数据类型的映射等。
使用Hibernate 必须首先提供这些基础信息以完成初始化工作,为后继操作做好准备。这些属性在hibernate配置文件(hibernate.cfg.xml hibernate.properties)中加以设定(参见后面“Hibernate配置”中的示例配置文件内容)。
当我们调用:
Configuration config = new Configuration().configure();
时,Hibernate会自动在当前的CLASSPATH 中搜寻hibernate.cfg.xml文件并将其读取到内存中作为后继操作的基础配置。Configuration 类一般只有在获取SessionFactory时需要涉及,当获取SessionFactory 之后,由于配置信息已经由Hibernate 维护并绑定在返回的SessionFactory之上,因此一般情况下无需再对其进行操作。
我们也可以指定配置文件名,如果不希望使用默认的hibernate.cfg.xml文件作为配置文件的话:
File file = new File("c://sample//myhibernate.xml");
Configuration config = new Configuration().configure(file);
SessionFactory
SessionFactory 负责创建Session 实例。我们可以通过Configuation 实例构建SessionFactory:
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Configuration实例config会根据当前的配置信息,构造SessionFactory实例并返回。SessionFactory 一旦构造完毕,即被赋予特定的配置信息。也就是说,之后config 的任何变更将不会影响到已经创建的SessionFactory 实例(sessionFactory)。如果需要使用基于改动后的config 实例的SessionFactory,需要从config 重新构建一个SessionFactory实例。
Session
Session是持久层操作的基础,相当于JDBC中的Connection。
Session实例通过SessionFactory实例构建:
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
之后我们就可以调用Session所提供的save、find、flush等方法完成持久层操作:
Ø Find
String hql= " from TUser where name='Erica'";
List userList = session.find(hql);
Ø Save
TUser user = new TUser();
user.setName("Emma");
session.save(user);
session.flush();
最后调用Session.flush方法强制数据库同步,这里即强制Hibernate将user实例立即同步到数据库中。如果在事务中则不需要flush方法,在事务提交的时候,hibernate自动会执行flush方法,另外当Session关闭时,也会自动执行flush方法。
Transaction
Transaction 是对实际事务实现的一个抽象,这些实现包括JDBC的事务、JTA 中的UserTransaction、甚至可以是CORBA 事务。为开发者提过一个统一事务的操作界面,使得项目可以在不同的环境和容器之间方便地移值。
使用方法:
Transaction tx = session.beginTransaction();
······
tx.commit();
当执行事务提交方法时,Hibernate会发现持久数据的变化,并根据这些变化同步更新到数据库中。必须注意的是,在一个事务之中不允许出现另外一个事务,也即:事务不运行嵌套。
第三节 Hibernate配置
hibernate.cfg.xml
一个典型的hibernate.cfg.xml配置文件如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
<hibernate-configuration>
<!—- SessionFactory 配置 -->
<session-factory>
<!—- 数据库URL -->
<property name="hibernate.connection.url">
jdbc:mysql://localhost/sample
</property>
<!—- 数据库JDBC驱动 -->
<property name="hibernate.connection.driver_class">
org.gjt.mm.mysql.Driver
</property>
<!—- 数据库用户名 -->
<property name="hibernate.connection.username">
User
</property>
<!—- 数据库用户密码 -->
<property name="hibernate.connection.password">
Mypass
</property>
<!--dialect ,每个数据库都有其对应的Dialet以匹配其平台特性 -->
<property name="dialect">
net.sf.hibernate.dialect.MySQLDialect
</property>
<!—- 是否将运行期生成的SQL输出到日志以供调试 -->
<property name="hibernate.show_sql">
True
</property>
<!—- 是否使用数据库外连接 -->
<property name="hibernate.use_outer_join">
True
</property>
<!—- 事务管理类型,这里我们使用JDBC Transaction -->
<property name="hibernate.transaction.factory_class">
net.sf.hibernate.transaction.JDBCTransactionFactory
</property>
<!—映射文件配置,注意配置文件名必须包含其相对于根的全路径 -->
<mapping resource="net/xiaxin/xdoclet/TUser.hbm.xml"/>
<mapping resource="net/xiaxin/xdoclet/TGroup.hbm.xml"/>
</session-factory>
</hibernate-configuration>
hibernate.properties
一个典型的hibernate.properties配置文件如下:
hibernate.dialect net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class org.gjt.mm.mysql.Driver
hibernate.connection.driver_class com.mysql.jdbc.Driver
hibernate.connection.url jdbc:mysql:///sample
hibernate.connection.username user
hibernate.connection.password mypass
第四节 Hibernate映射
Hibernate中对象和关系数据库之间的映射是用一个XML 文档(XML document)来定义的。这个映射文档被设计为易读的,并且可以手工修改。映射语言是以Java 为中心的,意味着映射是按照持久化类的定义来创建的,而非表的定义。
虽然很多Hibernate 用户选择手工定义XML 映射文档,也有一些工具来生成映射文档,包括XDoclet,Middlegen 和AndroMDA.
一个映射的例子如下所示:
根据上面的映射文件,以下描述在Hibernate 运行之中使用到的常见的文档元素和属性。
doctype
所有的XML 映射都需要定义如上所示的doctype。DTD 可以从上述URL 中获取,或者在hibernate-x.x.x/src/net/sf/hibernate 目录中,或hibernate.jar 文件中找到。Hibernate 总是会在它的classptah 中首先搜索DTD 文件。
hibernate-mapping
这个元素包括三个可选的属性。schema 属性,指明了这个映射所引用的表所在的schema 名称。假若指定了这个属性,表名会加上所指定的schema 的名字扩展为全限定名。假若没有指定,表名就不会使用全限定名。default-cascade 指定了未明确注明cascade 属性的Java 属性和集合类Java 会采取什么样的默认级联风格。auto-import 属性默认让我们在查询语言中可以使用非全限定名的类名。
<hibernate-mapping
schema="schemaName"
default-cascade="none|save-update"
auto-import="true|false"
package="package.name"
/>
Ø schema
可选,数据库schema 名称。
Ø default-cascade
可选,默认为none,默认的级联风格。
Ø auto-import
可选,默认为 true,指定是否我们可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。
Ø package
可选,指定一个包前缀,如果在映射文档中没有指定全限定名,就使用这个包名。
假若你有两个持久化类,它们的非全限定名是一样的(就是在不同的包里面--译者注),你应该设置auto-import="false"。假若说你把一个“import” 过的名字同时对应两个类,Hibernate 会抛出一个异常。
class
你可以使用class 元素来定义一个持久化类:
Ø name
持久化类(或者接口)的Java 全限定名。
Ø table
对应的数据库表名。
Ø discriminator-value
一个用于区分不同的子类的值,在多态行为时使用。
Ø mutable
表明该类的实例可变(不可变)。
Ø schema
覆盖在根<hibernate-mapping>元素中指定的schema 名字。
Ø proxy
指定一个接口,在延迟装载时作为代理使用。你可以在这里使用该类自己的名字。
Ø dynamic-update
指定用于UPDATE 的SQL 将会在运行时动态生成,并且只更新那些改变过的字段。
Ø dynamic-insert
指定用于INSERT 的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。
Ø select-before-update
指定Hibernate 除非确定对象的确被修改了,不会执行SQL UPDATE 操作。在特定场合(实际上,只会发生在一个临时对象关联到一个新的session 中去,执行update()的时候),这说明Hibernate 会在UPDATE 之前执行一次额外的SQL SELECT 操作,来决定是否应该进行UPDATE。
Ø polymorphism
可选, 默认值为 implicit (隐式),界定是隐式还是显式的使用查询多态。
Ø where
指定一个附加的SQLWHERE 条件,在抓取这个类的对象时会一直增加这个条件。
Ø persister
指定一个定制的ClassPersister。
Ø batch-size
指定一个用于根据标识符抓取实例时使用的"batch size"(批次抓取数量)。
Ø optimistic-lock
可选,默认是version,决定乐观锁定的策略。
Ø lazy
假若设置 lazy="true",就是设置这个类自己的名字作为proxy接口的一种等价快捷形式。
若指明的持久化类实际上是一个接口,也可以被完美地接受。其后你可以用<subclass>来指定该接口的实际实现类名。你可以持久化任何static(静态的)内部类。记得应该使用标准的类名格式,就是说比如:Foo$Bar。
不可变类,mutable="false"不可以被应用程序更新或者删除。这可以让Hibernate 做一些小小的性能优化。
可选的proxy 属性可以允许延迟加载类的持久化实例。Hibernate 开始会返回实现了这个命名接口的CGLIB 代理。当代理的某个方法被实际调用的时候,真实的持久化对象才会被装载。参见下面的“用于延迟装载的代理”。
Implicit (隐式)的多态是指,如果查询中给出的是任何超类、该类实现的接口或者该类的名字,都会返回这个类的实例;如果查询中给出的是子类的名字,则会返回子类的实例。
Explicit (显式)的多态是指,只有在查询中给出的明确是该类的名字时才会返回这个类的实例;同时只有当在这个<class>的定义中作为<subclass>或者<joined-subclass>出现的子类,才会可能返回。 大多数情况下,默认的polymorphism="implicit"都是合适的。 显式的多态在有两个不同的类映射到同一个表的时候很有用。(允许一个“轻型”的类,只包含部分表字段)。
persister 属性可以让你定制这个类使用的持久化策略。你可以指定你自己实现的net.sf.hibernate.persister.EntityPersister 的子类,你甚至可以完全从头开始编写一个net.sf.hibernate.persister.ClassPersister 接口的实现,可能是用储存过程调用、序列化到文件或者LDAP 数据库来实现的。参阅net.sf.hibernate.test.CustomPersister,这是一个简单的例子(“持久化”到一个Hashtable)。
注意dynamic-update 和dynamic-insert 的设置并不会继承到子类,所以在<subclass>或者<joined-subclass>元素中可能需要再次设置。这些设置是否能够提高效率要视情形而定。
使用select-before-update 通常会降低性能.当是在防止数据库不必要的触发update 触发器,这就很有用了。
如果打开了dynamic-update,可以选择几种乐观锁定的策略:
• version(版本检查) 检查version/timestamp 字段
• all(全部) 检查全部字段
• dirty(脏检查)只检察修改过的字段
• none(不检查)不使用乐观锁定
建议你在Hibernate 中使用version/timestamp 字段来进行乐观锁定。对性能来说,这是最好的选择,并且这也是唯一能够处理在session 外进行操作的策略。
id
被映射的类必须声明对应数据库表主键字段。大多数类有一个JavaBeans 风格的属性,为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表主键字段的映射。
Ø name
标识属性的名字。
Ø type
标识Hibernate 类型的名字。
Ø column
主键字段的名字。
Ø unsaved-value
一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。这可以把这种实例和从以前的session 中装载过(可能又做过修改--译者注)但未再次持久化的实例区分开来。
Ø access
Hibernate 用来访问属性值的策略。
如果 name 属性不存在,会认为这个类没有标识属性。
unsaved-value这个属性很重要!如果你的类的标识属性不是默认为null 的,你应该指定正确的默认值。
还有一个另外的<composite-id>声明可以访问旧式的多主键数据。我们强烈不鼓励使用这种方式。
generator
必须声明的<generator>子元素是一个Java 类的名字,用来为该持久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参数,用<param>元素来传递。
<id name="id" type="long" column="uid" unsaved-value="0">
<generator class="net.sf.hibernate.id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
</id>
所有的生成器都实现net.sf.hibernate.id.IdentifierGenerator 接口。这是一个非常简单的接口;某些应用程序可以选择提供他们自己特定的实现。当然,Hibernate 提供了很多内置的实现。下面是一些内置生成器的快捷名字:
Ø increment(递增)
用于为long, short 或者int 类型生成唯一标识。只有在没有其他进程往同一张表中插入数据时才能使用。 在集群下不要使用。
Ø identity
对DB2,MySQL, MS SQL Server, Sybase 和HypersonicSQL 的内置标识字段提供支持。返回的标识符是long, short 或者int 类型的。
Ø sequence (序列)
在DB2,PostgreSQL, Oracle, SAP DB, McKoi 中使用序列(sequence),而在
Interbase 中使用生成器(generator)。返回的标识符是long, short 或者 int 类型的。
Ø hilo (高低位)
使用一个高/低位算法来高效的生成long, short 或者 int 类型的标识符。给定一个表和字段(默认分别是是hibernate_unique_key 和next)作为高位值得来源。高/低位算法生成的标识符只在一个特定的数据库中是唯一的。在使用JTA 获得的连接或者用户自行提供的连接中,不要使用这种生成器。
Ø seqhilo(使用序列的高低位)
使用一个高/低位算法来高效的生成long, short 或者 int 类型的标识符,给定一个数据库序列(sequence)的名字。
Ø uuid.hex
用一个128-bit 的UUID 算法生成字符串类型的标识符。在一个网络中唯一(使用了IP地址)。UUID 被编码为一个32 位16 进制数字的字符串。
Ø uuid.string
使用同样的UUID 算法。UUID 被编码为一个16 个字符长的任意ASCII 字符组成的字符串。不能使用在PostgreSQL 数据库中
Ø native(本地)
根据底层数据库的能力选择identity, sequence 或者hilo 中的一个。
Ø assigned(程序设置)
让应用程序在save()之前为对象分配一个标示符。
Ø foreign(外部引用)
使用另外一个相关联的对象的标识符。和<one-to-one>联合一起使用。
composite-id
Ø name
一个组件类型,持有联合标识(参见下一节)。
Ø class
可选 - 默认为通过反射(reflection)得到的属性类型,作为联合标识的组件类名。
Ø unsaved-value
可选 - 默认为 none,假如被设置为非none 的值,就表示新创建,尚未被持久化的实例将持有的值。
如果表使用联合主键,你可以把类的多个属性组合成为标识符属性。<composite-id>元素接受<key-property>属性映射和<key-many-to-one>属性映射作为子元素。
discriminator
在"一棵对象继承树对应一个表"的策略中,<discriminator>元素是必需的,它声明了表的识别器字段。识别器字段包含标志值,用于告知持久化层应该为某个特定的行创建哪一个子类的实例。只能使用如下受到限制的一些类型: string, character, integer, byte, short,boolean, yes_no, true_false.
Ø column
可选 - 默认为 class,识别器字段的名字
Ø type
可选 - 默认为 string,一个Hibernate 字段类型的名字
Ø force
可选 - 默认为 false,"强制"Hibernate 指定允许的识别器值,就算取得的所有实例都是根类的。
标识器字段的实际值是根据<class> 和<subclass>元素的discriminator-value 得来的.
force 属性仅仅是在表包含一些未指定应该映射到哪个持久化类的时候才是有用的。这种情况不是经常会遇到。
version
<version>元素是可选的,表明表中包含附带版本信息的数据。这在使用 长事务(longtransactions)的时候特别有用。
Ø column
可选 - 默认为属性名,指定持有版本号的字段名。
Ø name
持久化类的属性名。
Ø type
可选 - 默认是 integer,版本号的类型。
Ø access
可选 - 默认是 property,Hibernate 用于访问属性值的策略。
Ø unsaved-value
可选 - 默认是undefined,用于标明某个实例时刚刚被实例化的(尚未保存)版本属性值,依靠这个值就可以把这种情况和已经在先前的session 中保存或装载的实例区分开来。(undefined 指明使用标识属性值进行这种判断。)
timestamp
可选的<timestamp>元素指明了表中包含时间戳数据。这用来作为版本的替代。时间戳本质上是一种对乐观锁定的一种不是特别安全的实现。当然,有时候应用程序可能在其他方面使用时间戳。
<timestamp
column="timestamp_column"
name="propertyName"
access="field|property|ClassName"
unsaved-value="null|undefined"
/>
Ø column
可选 - 默认为属性名,持有时间戳的字段名。
Ø name
在持久化类中的JavaBeans 风格的属性名,其Java 类型是 Date 或者 Timestamp的。
Ø access
可选 - 默认是 property,Hibernate 用于访问属性值的策略。
Ø unsaved-value
可选 - 默认是null,用于标明某个实例时刚刚被实例化的(尚未保存)版本属性值,依靠这个值就可以把这种情况和已经在先前的session 中保存或装载的实例区分开来。(undefined 指明使用标识属性值进行这种判断。)
property
<property>元素为类声明了一个持久化的,JavaBean 风格的属性。
Ø name
属性的名字,以小写字母开头。
Ø column
可选 - 默认为属性名字,对应的数据库字段名。
Ø type
可选,一个Hibernate 类型的名字。
Ø update, insert
可选 - 默认为 true,表明在用于UPDATE 和/或 INSERT 的SQL 语
句中是否包含这个字段。这二者如果都设置为false 则表明这是一个“外源性(derived)”的属性,它的值来源于映射到同一个(或多个)字段的某些其他属性,或者通过一个trigger(触发器),或者其他程序。
Ø formula
可选,一个SQL 表达式,定义了这个计算(computed) 属性的值。计算属性没有和它对应的数据库字段。
Ø access
可选 - 默认值为 property,Hibernate 用来访问属性值的策略。
typename 可以是如下几种:
n Hibernate 基础类型之一(比如:integer, string, character,date, timestamp,float, binary, serializable, object, blob)。
n 一个Java 类的名字,这个类属于一种默认基础类型 (比如: int, float,char,java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。
n 一个PersistentEnum 的子类的名字。(比如:. eg.Color)。
n 一个可以序列化的Java 类的名字。
n 一个自定义类型的类的名字。(比如: com.illflow.type.MyCustomType)。
如果你没有指定类型,Hibernarte 会使用反射来得到这个名字的属性,以此来猜测正确的Hibernate 类型。Hibernate 会对属性读取器(getter 方法)的返回类进行解释,按照规则2,3,4 的顺序。然而,这并不足够。 在某些情况下你仍然需要type 属性。(比如,为了区别Hibernate.DATE 和Hibernate.TIMESTAMP,或者为了指定一个自定义类型。)
access 属性用来让你控制Hibernate 如何在运行时访问属性。在默认情况下,Hibernate 会使用属性的get/set 方法对。如果你指明access="field",Hibernate 会忽略get/set 方法对,直接使用反射来访问成员变量。你也可以指定你自己的策略,这就需要你自己实现net.sf.hibernate.property.PropertyAccessor 接口,再在access 中设置你自定义策略类的名字。
many-to-one
通过many-to-one 元素,可以定义一种常见的与另一个持久化类的关联。这种关系模型是多对一关联。(实际上是一个对象引用。)
Ø name
属性名。
Ø column
字段名。
Ø class
可选 - 默认是通过反射得到属性类型,关联的类的名字。
Ø cascade
级联,可选,指明哪些操作会从父对象级联到关联的对象。
Ø outer-join
外连接,可选 - 默认为 自动,当设置hibernate.use_outer_join 的时候,对这个关联允许外连接抓取。
Ø update, insert
可选,指定对应的字段是否在用于UPDATE 和/或 INSERT 的SQL 语句中包含。如果二者都是false,则这是一个纯粹的“外源性(derived)”关联,它的值是通过映射到同一个(或多个)字段的某些其他属性得到的,或者通过trigger(除法器),或者是其他程序。
Ø property-ref
(可选) 指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。
Ø access
可选 - 默认是 property,Hibernate 用来访问属性的策略。
cascade 属性允许下列值: all, save-update, delete, none。设置除了none 以外的其它值会传播特定的操作到关联的(子)对象中。参见后面的“Lifecycle Objects(自动管理生命周期的对象)”。
one-to-one
持久化对象之间一对一的关联关系是通过one-to-one 元素定义的。
Ø name
属性的名字。
Ø class
可选 - 默认是通过反射得到的属性类型,被关联的类的名字。
Ø cascade
级联,表明操作是否从父对象级联到被关联的对象。
Ø constrained
约束,表明该类对应的表对应的数据库表,和被关联的对象所对应的数据库表之间,通过一个外键引用对主键进行约束。这个选项影响save()和delete()在级联执行时的先后顺序(也在schema export tool 中被使用)。
Ø outer-join
外连接,可选 - 默认为 自动,当设置hibernate.use_outer_join 的时候,对这个关联允许外连接抓取。
Ø property-ref
可选,指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。
Ø access
可选,默认是 property,Hibernate 用来访问属性的策略。
component
<component>元素把子对象的一些元素与父类对应的表的一些字段映射起来。 然后组件可以声明它们自己的属性、组件或者集合。
<component
name="propertyName"
class="className"
insert="true|false"
upate="true|false"
access="field|property|ClassName">
<property ...../>
<many-to-one .... />
........
</component>
Ø name
属性名
Ø class
可选,默认为通过反射得到的属性类型,组件(子)类的名字。
Ø insert
被映射的字段是否出现在SQL 的INSERT 语句中?
Ø update
被映射的字段是否出现在SQL 的UPDATE 语句中?
Ø access
可选,默认是 property,Hibernate 用来访问属性的策略。
其<property>子标签为子类的一些属性和表字段建立映射。
<component>元素允许加入一个<parent>子元素,在组件类内部就可以有一个指向其容器的实体的反向引用。
<dynamic-component>元素允许把一个Map 映射为组件,其属性名对应map 的键值。
subclass
最后,多态持久化需要为父类的每个子类都进行声明。对于我们建议的“每一棵类继承树对应一个表”的策略来说,就需要使用<subclass>声明。
Ø name
子类的全限定名。
Ø discriminator-value
辨别标志,可选 - 默认为类名,一个用于区分每个独立的子类的值。
Ø proxy
代理,可选,指定一个类或者接口,在延迟装载时作为代理使用。
Ø lazy
延迟装载,可选,设置lazy="true"是把自己的名字作为proxy 接口的一种等价快捷方式。
每个子类都应该声明它自己的持久化属性和子类。 <version> 和<id> 属性可以从根父类继承下来。在一棵继承树上的每个子类都必须声明一个唯一的discriminator-value。如果没有指定,就会使用Java 类的全限定名。
joined-subclass
如果子类是持久化到一个属于它自己的表(每一个子类对应一个表的映射策略),那么就需要使用<joined-subclass>元素。
Ø name
子类的全限定名。
Ø proxy
可选,指定一个类或者接口,在延迟装载时作为代理使用。
Ø lazy
延迟装载,可选,设置lazy="true"是把自己的名字作为proxy 接口的一种等价快捷方式。
import
假设你的应用程序有两个同样名字的持久化类,但是你不想在Hibernate 查询中使用他们的全限定名。除了依赖auto-import="true"以外,类也可以被显式地“import(引用)”。你甚至可以引用没有明确被映射的类和接口。
Ø class
任何Java 类的全限定名。
Ø rename
可选,默认为类的全限定名,在查询语句中可以使用的名字。
第五节 Hibernate 的类型
实体(Entities)和值(values)
实体entity 独立于任何持有实体引用的对象。与通常的Java 模型相比,不再被引用的对象会被当作垃圾收集掉。实体必须被显式的保存和删除(除非保存和删除是从父实体向子实体引发的级联)。这和ODMG 模型中关于对象通过可触及保持持久性有一些不同——比较起来更加接近应用程序对象通常在一个大系统中的使用方法。实体支持循环引用和交叉引用,它们也可以加上版本信息。
实体的持久化状态包含有指向其他实体的连接和一些值 类型的实例。值是原始类型、集合、组件或者特定的不可变对象。与实体不同,值(特别是集合和组件)是通过可触及性来进行持久化和删除的。因为值对象(和原始类型数据)是随着包含它们的实体而被持久化和删除的,它们不能够被独立的加上版本信息。值没有独立的标识,所以它们不能被两个实体或者集合共享。
所有的Hibernate 对象,除了集合,都支持null 语义。
基本值类型
基本类型可以大致的分为:
integer, long, short, float, double, character, byte, boolean, yes_no,true_false
这些类型都对应Java 的原始类型或者其包装类,来适合(特定厂商的)SQL 字段类型。boolean, yes_no 和 true_false 都是Java 中boolean 或java.lang.Boolean 的另外说法。
Ø string
从java.lang.String 到 VARCHAR (或者 Oracle 的 VARCHAR2)的映射。
Ø date, time, timestamp
从java.util.Date 和其子类到SQL 类型DATE, TIME 和TIMESTAMP (或等价类型)的映射。
Ø calendar, calendar_date
从java.util.Calendar 到SQL 类型TIMESTAMP 和 DATE(或等价类型)的映射。
Ø big_decimal
从java.math.BigDecimal 到 NUMERIC (或者 Oracle 的NUMBER 类型)的映射。
Ø locale, timezone, currency
从java.util.Locale, java.util.TimeZone 和java.util.Currency 到VARCHAR(或者 Oracle 的VARCHAR2 类型)的映射. Locale 和 Currency 的实例被映射为它们的ISO 代码。TimeZone 的实例被影射为它的ID。
Ø class
从java.lang.Class 到 VARCHAR (或者 Oracle 的VARCHAR2 类型)的映射。Class被映射为它的全限定名。
Ø binary
把字节数组(byte arrays)映射为对应的 SQL 二进制类型。
Ø text
把长Java 字符串映射为SQL 的CLOB 或者TEXT 类型。
Ø serializable
把可序列化的Java 类型映射到对应的SQL 二进制类型。你也可以为一个并非默认为基本类型或者实现PersistentEnum 接口的可序列化Java 类或者接口指定Hibernate 类型serializable。
Ø clob, blob
JDBC 类 java.sql.Clob 和 java.sql.Blob 的映射。某些程序可能不适合使用这个类型,因为blob 和clob 对象可能在一个事务之外是无法重用的。(而且, 驱动程序对这种类型的支持充满着补丁和前后矛盾。)
实体及其集合的唯一标识可以是任何基础类型,除了binary、 blob 和 clob 之外。(联合标识也是允许的,后面会说到。)
在net.sf.hibernate.Hibernate 中,定义了基础类型对应的Type 常量。比如,Hibernate.STRING 代表string 类型。
第六节 Hibernate开发示例
持久化类
下面的两个持久化类表示一个weblog,和在其中张贴的一个贴子。他们是标准的父/子关系模型,但是我们会用一个排序包(ordered bag)而非集合(set)。
n 类Blog
package eg;
import java.util.List;
public class Blog {
private Long _id;
private String _name;
private List _items;
public Long getId() {
return _id;
}
public List getItems() {
return _items;
}
public String getName() {
return _name;
}
public void setId(Long long1) {
_id = long1;
}
public void setItems(List list) {
_items = list;
}
public void setName(String string) {
_name = string;
}
}
n 类BlogItem
package eg;
import java.text.DateFormat;
import java.util.Calendar;
public class BlogItem {
private Long _id;
private Calendar _datetime;
private String _text;
private String _title;
private Blog _blog;
public Blog getBlog() {
return _blog;
}
public Calendar getDatetime() {
return _datetime;
}
public Long getId() {
return _id;
}
public String getText() {
return _text;
}
public String getTitle() {
return _title;
}
public void setBlog(Blog blog) {
_blog = blog;
}
public void setDatetime(Calendar calendar) {
_datetime = calendar;
}
public void setId(Long long1) {
_id = long1;
}
public void setText(String string) {
_text = string;
}
public void setTitle(String string) {
_title = string;
}
}
关系映射
与上对应的关系映射文件如下:
n 类Blog的映射文件Blog.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping package="eg">
<class
name="Blog"
table="BLOGS"
lazy="true">
<id
name="id"
column="BLOG_ID">
<generator class="native"/>
</id>
<property
name="name"
column="NAME"
not-null="true"
unique="true"/>
<bag
name="items"
inverse="true"
lazy="true"
order-by="DATE_TIME"
cascade="all">
<key column="BLOG_ID"/>
<one-to-many class="BlogItem"/>
</bag>
</class>
</hibernate-mapping>
n 类Blog的映射文件BlogItem.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping package="eg">
<class
name="BlogItem"
table="BLOG_ITEMS"
dynamic-update="true">
<id
name="id"
column="BLOG_ITEM_ID">
<generator class="native"/>
</id>
<property
name="title"
column="TITLE"
not-null="true"/>
<property
name="text"
column="TEXT"
not-null="true"/>
<property
name="datetime"
column="DATE_TIME"
not-null="true"/>
<many-to-one
name="blog"
column="BLOG_ID"
not-null="true"/>
</class>
</hibernate-mapping>
Hibernate调用代码
下面的类演示了我们可以使用Hibernate 对这些类所进行的操作。
package eg;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Query;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.tool.hbm2ddl.SchemaExport;
public class BlogMain
{
private SessionFactory _sessions;
public void configure() throws HibernateException
{
_sessions = new Configuration()
.addClass(Blog.class)
.addClass(BlogItem.class)
.buildSessionFactory();
}
public void exportTables() throws HibernateException
{
Configuration cfg = new Configuration()
.addClass(Blog.class)
.addClass(BlogItem.class);
new SchemaExport(cfg).create(true, true);
}
public Blog createBlog(String name) throws HibernateException
{
Blog blog = new Blog();
blog.setName(name);
blog.setItems( new ArrayList() );
Session session = _sessions.openSession();
Transaction tx = null;
try
{
tx = session.beginTransaction();
session.save(blog);
tx.commit();
}
catch (HibernateException he)
{
if (tx!=null) tx.rollback();
throw he;
}
finally
{
session.close();
}
return blog;
}
public BlogItem createBlogItem(Blog blog, String title, Stringtext)
throws HibernateException
{
BlogItem item = new BlogItem();
item.setTitle(title);
item.setText(text);
item.setBlog(blog);
item.setDatetime( Calendar.getInstance() );
blog.getItems().add(item);
Session session = _sessions.openSession();
Transaction tx = null;
try
{
tx = session.beginTransaction();
session.update(blog);
tx.commit();
}
catch (HibernateException he)
{
if (tx!=null) tx.rollback();
throw he;
}
finally
{
session.close();
}
return item;
}
public BlogItem createBlogItem(Long blogid, String title, Stringtext)
throws HibernateException
{
BlogItem item = new BlogItem();
item.setTitle(title);
item.setText(text);
item.setDatetime( Calendar.getInstance() );
Session session = _sessions.openSession();
Transaction tx = null;
try
{
tx = session.beginTransaction();
Blog blog = (Blog) session.load(Blog.class, blogid);
item.setBlog(blog);
blog.getItems().add(item);
tx.commit();
}
catch (HibernateException he)
{
if (tx!=null) tx.rollback();
throw he;
}
finally
{
session.close();
}
return item;
}
public void updateBlogItem(BlogItem item, String text) throws HibernateException
{
item.setText(text);
Session session = _sessions.openSession();
Transaction tx = null;
try
{
tx = session.beginTransaction();
session.update(item);
tx.commit();
}
catch (HibernateException he)
{
if (tx!=null) tx.rollback();
throw he;
}
finally
{
session.close();
}
}
public void updateBlogItem(Long itemid, String text) throws HibernateException
{
Session session = _sessions.openSession();
Transaction tx = null;
try
{
tx = session.beginTransaction();
BlogItem item = (BlogItem) session.load(BlogItem.class,itemid);
item.setText(text);
tx.commit();
}
catch (HibernateException he)
{
if (tx!=null) tx.rollback();
throw he;
}
finally
{
session.close();
}
}
public List listAllBlogNamesAndItemCounts(int max) throws HibernateException
{
Session session = _sessions.openSession();
Transaction tx = null;
List result = null;
try
{
tx = session.beginTransaction();
Query q = session.createQuery(
"select blog.id, blog.name, count(blogItem) " +
"from Blog as blog " +
"left outer join blog.items as blogItem " +
"group by blog.name, blog.id " +
"order by max(blogItem.datetime)"
);
q.setMaxResults(max);
result = q.list();
tx.commit();
}
catch (HibernateException he)
{
if (tx!=null) tx.rollback();
throw he;
}
finally
{
session.close();
}
return result;
}
public Blog getBlogAndAllItems(Long blogid) throws HibernateException
{
Session session = _sessions.openSession();
Transaction tx = null;
Blog blog = null;
try
{
tx = session.beginTransaction();
Query q = session.createQuery(
"from Blog as blog " +
"left outer join fetch blog.items " +
"where blog.id = :blogid"
);
q.setParameter("blogid", blogid);
blog = (Blog) q.list().get(0);
tx.commit();
}
catch (HibernateException he)
{
if (tx!=null) tx.rollback();
throw he;
}
finally
{
session.close();
}
return blog;
}
public List listBlogsAndRecentItems() throws HibernateException
{
Session session = _sessions.openSession();
Transaction tx = null;
List result = null;
try
{
tx = session.beginTransaction();
Query q = session.createQuery(
"from Blog as blog " +
"inner join blog.items as blogItem " +
"where blogItem.datetime > :minDate"
);
Calendar cal = Calendar.getInstance();
cal.roll(Calendar.MONTH, false);
q.setCalendar("minDate", cal);
result = q.list();
tx.commit();
}
catch (HibernateException he)
{
if (tx!=null) tx.rollback();
throw he;
}
finally
{
session.close();
}
return result;
}
}
第六章 Spring
第一节 Spring概述
什么是Spring
Spring是一个从实际项目开发经验中抽取的,可高度重用的应用框架。采用控制反转(IOC =Inverse Of Control)和依赖注入(DI =Dependence Injection)作为其设计思想。
² Spring涵盖了应用系统开发所涉及的大多数技术范畴,包括MVC、ORM以及RemoteInterface等,这些技术往往贯穿了大多数应用系统的开发过程。Spring从开发者的角度对这些技术内容进行了进一步的封装和抽象,使得应用开发更为简便。借助Spring提供的丰富类库,相对传统开发模式,大大节省了编码量(平均1/3强,对于ORM和Remote层也许更多)。
² Spring并非一个强制性框架,它提供了很多独立的组件可供选择。
Spring体系结构
第二节 Spring 基础语义
1、 Dependency Injection
IoC(IoC = Inversion of Control),简单的说,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。正在业界为IoC争吵不休时,大师级人物Martin Fowler也站出来发话,以一篇经典文章《Inversion of Control Containers and the Dependency Injection pattern》为IoC正名,至此,IoC又获得了一个新的名字:“依赖注入DI (Dependency Injection)”
相对IoC 而言,“依赖注入”的确更加准确的描述了这种古老而又时兴的设计理念。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。
为什么称之为“古老而又时兴”的设计理念?至于“时兴”自然不必多费唇舌,看看国内外大小论坛上当红的讨论主题便知。至于“古老”……,相信大家对下面图片中的设备不会陌生:
IBM T40笔记本电脑一台、USB硬盘和U盘各一只。
这与依赖注入有什么关系?
图中三个设备都有一个共同点,都支持USB 接口。当我们需要将数据复制到外围存储设备时,可以根据情况,选择是保存在U盘还是USB硬盘,下面的操作大家也都轻车熟路,无非接通USB接口,然后在资源浏览器中将选定的文件拖放到指定的盘符。
这样的操作在过去几年中每天都在我们身边发生,而这也正是所谓依赖注入的一个典型案例,上面称之为“古老”想必也不为过分。
再看上例中,笔记本电脑与外围存储设备通过预先指定的一个接口(USB)相连,对于笔记本而言,
只是将用户指定的数据发送到USB接口,而这些数据何去何从,则由当前接入的USB设备决定。在USB设备加载之前,笔记本不可能预料用户将在USB接口上接入何种设备,只有USB设备接入之后,这种设备之间的依赖关系才开始形成。
对应上面关于依赖注入机制的描述,在运行时(系统开机,USB 设备加载)由容器(运行在笔记本中的Windows操作系统)将依赖关系(笔记本依赖USB设备进行数据存取)注入到组件中(Windows文件访问组件)。
这就是依赖注入模式在现实世界中的一个版本。
再看一段配置:
其中SampleDAO中的dataSource将由容器在运行期动态注入,而DataSource的具体配置和初始化工作也将由容器在运行期完成。
对比传统的实现方式(如通过编码初始化DataSource实例),我们可以看到,基于依赖注入的系统实现相当灵活简洁。
通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定SampleDAO中所需的DataSource实例。SampleDAO只需利用容器注入的DataSource实例,完成自身的业务逻辑,而不用关心具体的资源来自何处、由谁实现。
上面的实例中,我们假设SampleDAO是一个运行在J2EE容器中的组件(如 Weblogic)。在运行期,通过JNDI从容器中获取DataSource实例。现在假设我们的部署环境发生了变化,系统需要脱离应用服务器独立运行,这样,由于失去了容器的支持,原本通过JNDI获取DataSource的方式不再有效。我们需要如何修改以适应新的系统环境?很简单,我们只需要修改dataSource的配置:
这里我们的DataSource改为由Apache DBCP组件提供。没有编写任何代码我们即实现了DataSource的切换。回想传统编码模式中,如果要进行同样的修改,我们需要付出多大的努力。
依赖注入机制减轻了组件之间的依赖关系,同时也大大提高了组件的可移植性,这意味着,组件得到重用的机会将会更多。
依赖注入的几种实现类型
Type1 接口注入
即使用接口助接口来将调用者与实现者分离。如:
上面的代码中,ClassA依赖于InterfaceB的实现,如何获得InterfaceB实现类的实例?传统的方法是在代码中创建InterfaceB实现类的实例,并将起赋予clzB。
而这样一来,ClassA在编译期即依赖于InterfaceB的实现。为了将调用者与实现者在编译期分离,于是有了上面的代码,我们根据预先在配置文件中设定的实现类的类名(Config.BImplementation),动态加载实现类,并通过InterfaceB强制转型后为ClassA所用。这就是接口注入的一个最原始的雏形。
而对于一个Type1型IOC容器而言,加载接口实现并创建其实例的工作由容器完成。如下面这个类:
在运行期,InterfaceB实例将由容器提供。
Type2 设值注入
在各种类型的依赖注入模式中,设值注入模式在实际开发中得到了最广泛的应用(其中很大一部分得力于Spring框架的影响)。
基于设置模式的依赖注入机制更加直观、也更加自然。
Type3 构造子注入
构造子注入,即通过构造函数完成依赖关系的设定,如:
可以看到,在Type3类型的依赖注入机制中,依赖关系是通过类构造函数建立,容器通过调用类的构造方法,将其所需的依赖关系注入其中。
PicoContainer(另一种实现了依赖注入模式的轻量级容器)首先实现了Type3类型的依赖注入模式。
几种依赖注入模式的对比
接口注入模式因为历史较为悠久,在很多容器中都已经得到应用。但由于其在灵活性、易用性上不如其他两种注入模式,因而在IOC的专题世界内并不被看好。Type2和Type3型的依赖注入实现则是目前主流的IOC实现模式。这两种实现方式各有特点,也各具优势。
Type2 设值注入的优势
l 对于习惯了传统JavaBean开发的程序员而言,通过setter方法设定依赖关系显得更加直观,更加自然。
l 如果依赖关系(或继承关系)较为复杂,那么Type3模式的构造函数也会相当庞大(我们需要在构造函数中设定所有依赖关系),此时Type2模式往往更为简洁。
l 对于某些第三方类库而言,可能要求我们的组件必须提供一个默认的构造函数(如Struts中的Action),此时Type3类型的依赖注入机制就体现出其局限性,难以完成我们期望的功能。
Type3 构造子注入的优势
l “在构造期即创建一个完整、合法的对象”,对于这条Java设计原则,Type3无疑是最好的响应者。
l 避免了繁琐的setter方法的编写,所有依赖关系均在构造函数中设定,依赖关系集中呈现,更加易读。
l 由于没有setter方法,依赖关系在构造时由容器一次性设定,因此组件在被创建之后即处于相对“不变”的稳定状态,无需担心上层代码在调用过程中执行setter方法对组件依赖关系产生破坏,特别是对于Singleton模式的组件而言,这可能对整个系统产生重大的影响。
l 同样,由于关联关系仅在构造函数中表达,只有组件创建者需要关心组件内部的依赖关系。对调用者而言,组件中的依赖关系处于黑盒之中。对上层屏蔽不必要的信息,也为系统的层次清晰性提供了保证。
l 通过构造子注入,意味着我们可以在构造函数中决定依赖关系的注入顺序,对于一个大量依赖外部服务的组件而言,依赖关系的获得顺序可能非常重要,比如某个依赖关系注入的先决条件是组件的DataSource及相关资源已经被设定。
可见,Type3和Type2模式各有千秋,而Spring、PicoContainer都对Type3和Type2类型的依赖注入机制提供了良好支持。这也就为我们提供了更多的选择余地。理论上,以Type3类型为主,辅之以Type2类型机制作为补充,可以达到最好的依赖注入效果,不过对于基于Spring Framework开发的应用而言,Type2使用更加广泛。
2、Spring Bean封装机制
Spring 从核心而言,是一个DI 容器,其设计哲学是提供一种无侵入式的高扩展性框架。即无需代码中涉及Spring专有类,即可将其纳入Spring容器进行管理。
作为对比,EJB则是一种高度侵入性的框架规范,它制定了众多的接口和编码规范,要求实现者必须遵从。侵入性的后果就是,一旦系统基于侵入性框架设计开发,那么之后任何脱离这个框架的企图都将付出极大的代价。
为了避免这种情况,实现无侵入性的目标。Spring 大量引入了Java 的Reflection机制,通过动态调用的方式避免硬编码方式的约束,并在此基础上建立了其核心组件BeanFactory,以此作为其依赖注入机制的实现基础。
org.springframework.beans包中包括了这些核心组件的实现类,核心中的核心为BeanWrapper和BeanFactory类。这两个类从技术角度而言并不复杂,但对于Spring 框架而言,却是关键所在。
Bean Wrapper
所谓依赖注入,即在运行期由容器将依赖关系注入到组件之中。讲的通俗点,就是在运行期,由Spring根据配置文件,将其他对象的引用通过组件的提供的setter方法进行设定。
如果动态设置一个对象属性,可以借助Java的Reflection机制完成:
上面我们通过动态加载了User类,并通过Reflection调用了User.setName方法设置其name属性。对于这里的例子而言,出于简洁,我们将类名和方法名都以常量的方式硬编码。假设这些常量都是通过配置文件读入,那我们就实现了一个最简单的BeanWrapper。这个BeanWrapper的功能很简单,提供一个设置JavaBean属性的通用方法
Spring BeanWrapper基于同样的原理,提供了一个更加完善的实现:
诚然,通过这样的方式设定Java Bean属性实在繁琐,但它却提供了一个通用的属性设定机制,而这样的机制,也正是Spring依赖注入机制所依赖的基础。
通过BeanWrapper,我们可以无需在编码时就指定JavaBean的实现类和属性值,通过在配置文件加以设定,就可以在运行期动态创建对象并设定其属性(依赖关系)。
上面的代码中,我们仅仅指定了需要设置的属性名“name”,运行期,BeanWrapper将根据JavaBean规范,动态调用对象的“setName”方法进行属性设定。属性名可包含层次,如对于属性名“address.zipcode”,BeanWrapper会调用“getAddress().setZipcode”方法。
Bean Factory
Bean Factory,顾名思义,负责创建并维护Bean实例。
Bean Factory负责根据配置文件创建Bean实例,可以配置的项目有:
1. Bean属性值及依赖关系(对其他Bean的引用)
2. Bean创建模式(是否Singleton模式,即是否只针对指定类维持全局唯一的实例)
3. Bean初始化和销毁方法
4. Bean的依赖关系
下面是一个较为完整的Bean配置示例:
id
Java Bean在BeanFactory中的唯一标识,代码中通过BeanFactory获取JavaBean实例时需以此作为索引名称。
class
Java Bean 类名
singleton
指定此Java Bean是否采用单例(Singleton)模式,如果设为“true”,则在BeanFactory作用范围内,只维护此Java Bean的一个实例,代码通过BeanFactory获得此Java Bean实例的引用。反之,如果设为“false”,则通过BeanFactory获取此Java Bean实例时,BeanFactory每次都将创建一个新的实例返回。
init-method
初始化方法,此方法将在BeanFactory创建JavaBean实例之后,在向应用层返回引用之前执行。一般用于一些资源的初始化工作。
destroy-method
销毁方法。此方法将在BeanFactory销毁的时候执行,一般用于资源释放。
depends-on
Bean依赖关系。一般情况下无需设定。Spring会根据情况组织各个依赖关系的构建工作(这里示例中的depends-on属性非必须)。
只有某些特殊情况下,如JavaBean中的某些静态变量需要进行初始化(这是一种BadSmell,应该在设计上应该避免)。通过depends-on指定其依赖关系可保证在此Bean加载之前,首先对depends-on所指定的资源进行加载。
value
通过<value/>节点可指定属性值。BeanFactory将自动根据Java Bean对应的属性类型加以匹配。
下面的”desc”属性提供了一个null值的设定示例。注意<value></value>代表一个空字符串,如果需要将属性值设定为null,必须使用<null/>节点。
ref
指定了属性对BeanFactory中其他Bean的引用关系。示例中,TheAction的dataSource属性引用了id为dataSource的Bean。BeanFactory将在运行期创建dataSource bean实例,并将其引用传入TheAction Bean的dataSource属性。
下面的代码演示了如何通过BeanFactory获取Bean实例:
InputStream is = new FileInputStream("bean.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
Action action = (Action) factory.getBean("TheAction");
此时我们获得的Action实例,由BeanFactory进行加载,并根据配置文件进行了初始化和属性设定。
联合上面关于BeanWrapper的内容,我们可以看到,BeanWrapper实现了针对单个Bean的属性设定操作。而BeanFactory则是针对多个Bean的管理容器,根据给定的配置文件,BeanFactory从中读取类名、属性名/值,然后通过Reflection机制进行Bean加载和属性设定。
ApplicationContext
BeanFactory提供了针对Java Bean的管理功能,而ApplicationContext提供了一个更为框架化的实现(从上面的示例中可以看出,BeanFactory的使用方式更加类似一个API,而非Framework style)。
ApplicationContext覆盖了BeanFactory的所有功能,并提供了更多的特性。此外,ApplicationContext为与现有应用框架相整合,提供了更为开放式的实现(如对于Web应用,我们可以在web.xml中对ApplicationContext进行配置)。
相对BeanFactory而言,ApplicationContext提供了以下扩展功能:
n 国际化支持
我们可以在Beans.xml文件中,对程序中的语言信息(如提示信息)进行定义,将程序中的提示
信息抽取到配置文件中加以定义,为我们进行应用的各语言版本转换提供了极大的灵活性。
n 资源访问
支持对文件和URL的访问。
n 事件传播
事件传播特性为系统中状态改变时的检测提供了良好支持。
n 多实例加载
可以在同一个应用中加载多个Context实例。
下面我们就这些特性逐一进行介绍。
国际化支持
国际化支持在实际开发中可能是最常用的特性。对于一个需要支持不同语言环境的应用而言,我们所采取的最常用的策略一般是通过一个独立的资源文件(如一个properties文件)完成所有语言信息(如界面上的提示信息)的配置,Spring对这种传统的方式进行了封装,并提供了更加强大的功能,如信息的自动装配以及热部署功能(配置文件修改后自动读取,而无需重新启动应用程序)。
下面是一个典型的示例:
这里声明了一个名为messageSource的Bean(对于Message定义,Bean ID必须为messageSource,这是目前Spring的编码规约),对应类为ResourceBundleMessageSource,目前Spring中提供了两个MessageSource接口的实现,即ResourceBundleMessageSource和ReloadableResourceBundleMessageSource,后者提供了无需重启即可重新加载配置信息的特性。
在配置节点中,我们指定了一个配置名“messages”。Spring会自动在CLASSPATH根路径中按照如下顺序搜寻配置文件并进行加载(以Locale为zh_CN为例):
messages_zh_CN.properties
messages_zh.properties
messages.properties
messages_zh_CN.class
messages_zh.class
messages.class
(Spring实际上调用了JDK的ResourceBundle读取配置文件,相关内容请参见JDK文档)
示例中包含了两个配置文件,内容如下:
messages_zh_CN.properties:
messages_en_US.properties:
可以通过下面的语句进行测试:
代码中,我们将一个Object数组arg作为参数传递给ApplicationContext.getMessage方法,这个参数中包含了出现在最终文字信息中的可变内容,ApplicationContext将根据参数中的Locale信息对其进行处理(如针对不同Locale设定日期输出格式),并用其替换配置文件中的{n}标识(n代表参数数组中的索引,从1开始)。
运行上面的代码,得到以下输出的内容:
这是由于转码过程中产生的编码问题引发的。比较简单的解决办法是通过JDK提供的转码工具native2ascii.exe进行转换。
执行:
native2ascii messages_zh_CN.properties msg.txt
再用msg.txt文件替换Messages_zh_CN.properties文件。我们可以看到现在的Messages_zh_CN.properties变成了如下形式:
再次运行示例代码,得到正确输出:
可见,根据当前默认Locale“zh_CN”,getMessage方法自动加载了messages_zh_CN.properties文件。
尝试在代码中指定不同的Locale参数:
再次运行,可以看到:
这里,getMessage方法根据指定编码“en_US”加载了messages_en_US.properties文件。同时请注意登录时间部分的变化(Locale不同,时间的输出格式也随之改变)。
getMessage方法还有一个无需Locale参数的版本,JVM会根据当前系统的Locale设定进行相应处理。可以通过在JVM启动参数中追加“-Duser.language=en”来设定当前JVM语言类型,通过JVM级的设定,结合国际化支持功能,我们可以较为简单的实现多国语言系统的自动部署切换。
资源访问
ApplicationContext.getResource方法提供了对资源文件访问支持,如:
上例从CLASSPATH根路径中查找config.properties文件并获取其文件句柄。
getResource方法的参数为一个资源访问地址,如:
file:C:/config.properties
/config.properties
classpath:config.properties
注意getResource返回的Resource并不一定实际存在,可以通过Resource.exists()方法对其进行判断。
事件传播
ApplicationContext基于Observer模式(java.util包中有对应实现),提供了针对Bean的事件传播功能。通过Application. publishEvent方法,我们可以将事件通知系统内所有的ApplicationListener。
事件传播的一个典型应用是,当Bean中的操作发生异常(如数据库连接失败),则通过事件传播机制通知异常监听器进行处理。
下面是一个简单的示例,当LoginAction执行的时候,激发一个自定义消息“ActionEvent”,此ActionEvent将由ActionListener捕获,并将事件内容打印到控制台。
u LoginActoin.java
public class LoginAction implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(
ApplicationContext applicationContext
)
throws BeansException {
this.applicationContext = applicationContext;
}
public int login(String username,String password) {
ActionEvent event = new ActionEvent(username);
this.applicationContext.publishEvent(event);
return 0;
}
}
u ActionEvent.java
public class ActionEvent extends ApplicationEvent {
public ActionEvent(Object source) {
super(source);
}
}
u ActionListener.java
public class ActionListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ActionEvent) {
System.out.println(event.toString());
}
}
}
配置非常简单:
运行测试代码:
可以看到控制台输出:
net.xiaxin.beans.LoginEvent[source=Erica]
org.springframework.context.event.ApplicationEventMulticasterImpl实现了事件传播机制,目前还相对简陋。
在运行期,ApplicationContext会自动在当前的所有Bean中寻找ApplicationListener接口的实现,并将其作为事件接收对象。当Application. publishEvent方法调用时,所有的ApplicationListener接口实现都会被激发,每个ApplicationListener可根据事件的类型判断是否是自己需要处理的事件,如上面的ActionListener只处理ActionEvent事件。
Web Context
上面的示例中,ApplicationContext均通过编码加载。对于Web应用,Spring提供了可配置的ApplicationContext加载机制。
加载器目前有两种选择:ContextLoaderListener和ContextLoaderServlet。这两者在功能上完全等同,只是一个是基于Servlet2.3版本中新引入的Listener接口实现,而另一个基于Servlet接口实现。开发中可根据目标Web容器的实际情况进行选择。
配置非常简单,在web.xml中增加:
或:
通过以上配置,Web容器会自动加载/WEB-INF/applicationContext.xml初始化ApplicationContext实例,如果需要指定配置文件位置,可通过context-param加以指定:
配置完成之后,即可通过
WebApplicationContextUtils.getWebApplicationContext
方法在Web应用中获取ApplicationContext引用。
第三节 Spring事务管理
对于J2EE 应用程序而言,事务的处理一般有两种模式:
u 依赖特定事务资源的事务处理
这是应用开发中最常见的模式,即通过特定资源提供的事务机制进行事务管理。
如通过JDBC、JTA 的rollback、commit方法;Hibernate Transaction 的
rollback、commit方法等。
u 依赖容器的参数化事务管理
通过容器提供的集约式参数化事务机制,实现事务的外部管理,如EJB 中的事务管理模式。
如,下面的EJB事务定义中,将SessionBean MySession的doService方法定义为Required。
也就是说,当MySession.doServer 方法被某个线程调用时,容器将此线程纳入事务管理容器,方法调用过程中如果发生异常,当前事务将被容器自动回滚,如果方法正常结束,则容器将自动提交当前事务。
<container-transaction >
<method >
<ejb-name>MySession</ejb-name>
<method-intf>Remote</method-intf>
<method-name>doService</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
容器管理的参数化事务为程序开发提供了相当的灵活性,同时因为将事务委托给容器进行管理,应用逻辑中无需再编写事务代码,大大节省了代码量(特别是针对需要同时操作多个事务资源的应用),从而提高了生产率。
然而,使用EJB 事务管理的代价相当高昂,撇开EJB 容器不菲的价格,EJB的学习成本,部署、迁移、维护难度,以及容器本身带来的性能开销(这往往
意味着需要更高的硬件配置)都给我们带来了相当的困惑。此时事务管理所带来的优势往往还不能抵消上面这些负面影响。
对于传统的基于特定事务资源的事务处理而言(如基于JDBC 的数据库访问),Spring并不会对其产生什么影响,我们照样可以成功编写并运行这样的代码。同时,Spring还提供了一些辅助类可供我们选择使用,这些辅助类简化了传统的数据库操作流程,在一定程度上节省了工作量,提高了编码效率。
对于依赖容器的参数化事务管理而言,Spring 则表现出了极大的价值。Spring本身也是一个容器,只是相对EJB容器而言,Spring显得更为轻便小巧。我们无需付出其他方面的代价,即可通过Spring实现基于容器的事务管理(本质上来讲,Spring的事务管理是基于动态AOP)。
下面这段xml配置片断展示了Spring中的事务设定方式:
配置中包含了dataSource,transactionManager 等资源定义。这些资源都为一个名为userDAOProxy 的TransactionProxyFactoryBean 服务,而userDAOProxy 则对包含实际数据逻辑的userDAO进行了事务性封装。
可以看到,在userDAOProxy 的"transactionAttributes"属性中,我们定义了针对userDAO 的事务策略,即将所有名称以insert 开始的方法(如UserDAO.insertUser方法)纳入事务管理范围。如果此方法中抛出异常,则Spring将当前事务回滚,如果方法正常结束,则提交事务。
而对所有名称以get 开始的方法(如UserDAO.getUser 方法)则以只读的事务处理机制进行处理。(设为只读型事务,可以使持久层尝试对数据操作进行优化,如对于只读事务Hibernate将不执行flush操作,而某些数据库连接池和JDBC 驱动也对只读型操作进行了特别优化。)
Spring 的事务管理机制与EJB 中事务管理上的不同,最为关键的两点是:
Ø Spring可以将任意Java Class 纳入事务管理
这里的UserDAO只是我们编写的一个普通Java Class,其中包含了一些基本的数据应用逻辑。通过Spring,我们即可简单的实现事务的可配置化。也就是说,我们可以随意为某个类的某个方法指定事务管理机制。
与之对比,如果使用EJB容器提供的事务管理功能,我们不得不按照EJB规范编将UserDAO 进行改造,将其转换为一个标准的EJB。
Ø Spring事务管理并不依赖特定的事务资源。
EJB 容器必须依赖于JTA 提供事务支持。而Spring 的事务管理则支持JDBC、JTA 等多种事务资源。这为我们提供了更多的选择,从而也使得我们的系统部署更加灵活。
第四节 Spring AOP
Spring中提供的内置AOP支持,是基于动态AOP机制实现。从技术角度来讲,所谓动态AOP,即通过动态Proxy模式,在目标对象的方法调用前后插入相应的处理代码。
而Spring AOP中的动态Proxy模式,则是基于Java Dynamic Proxy(面向Interface)和CGLib(面向Class)实现。
Dynamic Proxy
Dynamic Proxy是JDK 1.3版本中新引入的一种动态代理机制。它是Proxy模式的一种动态实现版本。
我们先来看传统方式下一个Proxy的实现实例。
假设我们有一个UserDAO接口及其实现类UserDAOImp:
Ø UserDAO.java
public interface UserDAO {
public void saveUser(User user);
}
UserDAOImp.java:
public class UserDAOImp implements UserDAO{
public void saveUser(User user) {
……
}
}
UserDAOImp.saveUser方法中实现了针对User对象的数据库持久逻辑。
如果我们希望在UserDAOImp.saveUser方法执行前后追加一些处理过程,如启动/提交事务,而不影响外部代码的调用逻辑,那么,增加一个Proxy类是个不错的选择:
Ø UserDAOProxy.java
public class UserDAOProxy implements UserDAO {
private UserDAO userDAO;
public UserDAOProxy(UserDAO userDAO) {
this.userDAO = userDAO;
}
public void saveUser(User user) {
UserTransaction tx = null;
try {
tx = (UserTransaction) (
new InitialContext().lookup("java/tx")
);
userDAO.saveUser(user);
tx.commit();
} catch (Exception ex) {
if (null!=tx){
try {
tx.rollback();
}catch(Exception e) {
}
}
}
}
}
UserDAOProxy同样是UserDAO接口的实现,对于调用者而言,saveUser方法的使
用完全相同,不同的是内部实现机制已经发生了一些变化――我们在UserDAOProxy中为
UserDAO.saveUser方法套上了一个JTA事务管理的外壳。
上面是静态Proxy模式的一个典型实现。
现在假设系统中有20个类似的接口,针对每个接口实现一个Proxy,实在是个繁琐无味的苦力工程。
Dynamic Proxy的出现,为这个问题提供了一个更加聪明的解决方案。
我们来看看怎样通过Dynamic Proxy解决上面的问题:
public class TxHandler implements InvocationHandler {
private Object originalObject;
public Object bind(Object obj) {
this.originalObject = obj;
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
if (!method.getName().startsWith("save")) {
UserTransaction tx = null;
try {
tx = (UserTransaction) (
new InitialContext().lookup("java/tx")
);
result = method.invoke(originalObject, args);
tx.commit();
} catch (Exception ex) {
if (null != tx) {
try {
tx.rollback();
} catch (Exception e) {
}
}
}
} else {
result = method.invoke(originalObject, args);
}
return result;
}
}
首先注意到,上面这段代码中,并没有出现与具体应用层相关的接口或者类引用。也就是说,这个代理类适用于所有接口的实现。
其中的关键在两个部分:
1.
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
this);
java.lang.reflect.Proxy.newProxyInstance方法根据传入的接口类型(obj.getClass().getInterfaces())动态构造一个代理类实例返回,这个代理类是JVM在内存中动态构造的动态类,它实现了传入的接口列表中所包含的所有接口。
这里也可以看出,Dynamic Proxy要求所代理的类必须是某个接口的实现(obj.getClass().getInterfaces()不可为空),否则无法为其构造响应的动态类。这也就是为什么Spring对接口实现类通过Dynamic Proxy实现AOP,而对于没有实现任何接口的类通过CGLIB实现AOP机制的原因。
2.
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
……
result = method.invoke(originalObject, args);
……
return result;
}
InvocationHandler.invoke方法将在被代理类的方法被调用之前触发。通过这个方法中,我们可以在被代理类方法调用的前后进行一些处理,如代码中所示,InvocationHandler.invoke方法的参数中传递了当前被调用的方法(Method),以及被调用方法的参数。
同时,我们可以通过Method.invoke方法调用被代理类的原始方法实现。这样,我们就可以在被代理类的方法调用前后大做文章。
在示例代码中,我们为所有名称以“save”开头的方法追加了JTA事务管理。
Spring中Dynamic Proxy AOP实现类为:
org.springframework.aop.framework.JdkDynamicAopProxy
CGLib
Dynamic Proxy是面向接口的动态代理实现,其代理对象必须是某个接口的实现。Dynamic Proxy通过在运行期构建一个此接口的动态实现类完成对目标对象的代理(相当于在运行期动态构造一个UserDAOProxy,完成对UserDAOImp的代理任务)。
而如果目标代理对象并未实现任何接口,那么Dynamic Proxy就失去了创建动态代理类的基础依据。此时我们需要借助一些其他的机制实现动态代理机制。
Spring中,引入了CGLib作为无接口情况下的动态代理实现。
CGLib与Dynamic Proxy的代理机制基本类似,只是其动态生成的代理对象并非某个接口的实现,而是针对目标类扩展的子类。
换句话说,Dynamic Proxy返回的动态代理类,是目标类所实现的接口的另一个实现版本,它实现了对目标类的代理(如同UserDAOProxy与UserDAOImp的关系)。而CGLib返回的动态代理类,则是目标代理类的一个子类(代理类扩展了UserDAOImp类)。
与Dynamic Proxy中的Proxy和InvocationHandler相对应,Enhancer和
MethodInterceptor在CGLib中负责完成代理对象创建和方法截获处理。
下面是通过CGLib进行动态代理的示例代码:
Ø AOPInstrumenter.java
public class AOPInstrumenter implements MethodInterceptor {
private static Log logger =
LogFactory.getLog(AOPInstrumenter.class);
private Enhancer enhancer = new Enhancer();
public Object getInstrumentedClass(Class clz) {
enhancer.setSuperclass(clz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(
Object o,
Method method,
Object[] methodParameters,
MethodProxy methodProxy)
throws Throwable {
logger.debug("Before Method =>"+method.getName());
Object result = methodProxy.invokeSuper(o, methodParameters);
logger.debug("After Method =>"+method.getName());
return result;
}
}
测试代码:
AOPInstrumenter aopInst = new AOPInstrumenter();
UserDAOImp userDAO =
(UserDAOImp) aopInst.getInstrumentedClass(UserDAOImp.class);
User user = new User();
user.setName("Erica");
userDAO.saveUser(user);
Spring中,基于CGLib的AOP实现位于:
org.springframework.aop.framework.Cglib2AopProxy
第七章 J2EE核心模式
第一节 J2EE介绍
什么是J2EE
J2EE是开发分布式企业软件应用的平台
J2EE的价值地位
建立于java编程语言和java技术基础之上的J2EE平台是最适用于企业级分布式环境的应用结构
第二节 J2EE架构
架构介绍
目前大家通常将J2EE分为四个层次,分别是用户层、WEB层、业务层和EIS层。
• 用户层
用来与用户交互、并把来自系统的信息显示给用户
• WEB层
产生表示逻辑,并接受来自表示客户端的用户反馈
• 业务层
处理应用的核心业务逻辑
• EIS层
企业的信息系统服务,包括数据库系统、事务处理系统、遗产系统和企业资源计划等
J2EE环境下的WEB应用
第三节 J2EE模式
1、 表示层模式
截取过滤器(Intercepting Filter)
Ø 环境
表示层请求处理机制可以接受许多不同类型的请求,这些请求分别需要不同类型的处理。有些请求只是被简单地转发给合适的处理器组件,而有的请求在进一步处理之前必须被修改、审计或者解压缩。
Ø 问题
一般需要预处理和后处理客户端Web请求和答复。
当某请求进入Web应用程序时,通常在主处理阶段之前必须通过几个前期测试。比如:
1、客户端已经被验证了吗?
2、客户端有正确的会话吗?
3、客户端的IP来自一个可信的网络吗?
4、请求路径会违反任何限制吗?
5、客户端使用何种编码来发送数据?
6、我们支持客户端的浏览器类型吗?
典型的解决方案包括一系列条件检验,任何否定的检验结构都会放弃该请求。嵌套的if/else语句是一种标准的策略,但是这种解决方案会导致编码脆弱和拷贝-粘贴的编程样式,原因是过滤流和过滤器的动作已被编译进应用程序。
以灵活和正确的方式解决该问题的关键是拥有一种添加和删除组件的简单机制,其中每个组件负责完成一个特定的过滤动作。
Ø 作用
1、常见的处理,比如检测数据-编码方案,或者记录有关每个请求、每个请求的完成情况等信息。
2、需要集中化常见逻辑。
3、服务应该能正确地添加或者删除,而无须影响已有的组件,这样我们可以以多种方式组合使用组件,比如:
日志和验证
调试或者转换特定客户端的输出
解压缩和转换输入的编码方案
Ø 解决方案
1、过滤器管理器
过滤器管理器管理着过滤器处理。它会用合适的过滤器,按照正确顺序来创建过滤器链,并且初始化处理过程。
2、过滤器链
过滤器链是独立过滤器的有序集合
3、过滤器1、过滤器2、过滤器3
这些是被映射到某目标的单独过滤器。过滤器链会协调这些过滤器的处理。
4、目标
目标是客户端请求的资源。
Ø 例子
代码-过滤器管理器
public class FilterManager { public void processFilter(Filter target, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,java.io.IOException {
// the manager build filter chain here FilterChain filterChain = new FilterChain();
filterChain.processFilter(request , response);
// process target resource Target.execute(request , response); } } |
代码-过滤器链
Public class FilterChain { // filter chain Private Vector myFilters = new Vector();
// create new FilterChain public FilterChain() { // plug-in default filter services for example only . This would typically // be done in the FilterManager , but is done here for example purpose addFilter(new Filter1()); addFilter(new Filter2()); addFilter(new Filter3()); }
public void processFilter( javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException , java.io.IOException { Filter filter;
// apply filters Iterator filters = myFilters.iterator(); While (filters.hasNext()) { filter = (Filter) filters.next(); filter.execute(request , response); } }
public void addFilter(Filter filter) { myFilters.add(filter); } } |
前端控制器(Front Controller)
Ø 环境
表示层请求处理机制必须能够控制和协调每个用户跨越多请求的处理。这类控制机制可能集中式或者分散的方式进行管理。
Ø 问题
系统需要表示层请求处理的集中式访问点以支持系统服务的集成、内容检索、视图管理以及导航。当用户不通过集中式机制直接访问视图时,可能会产生三个问题:
1、每个视图被要求提供其自己的系统服务,这通常会导致重复的代码。
2、视图导航留给视图处理。这会导致混合视图内容和视图导航。
3、分布式控制更难维护,因为更改经常需要在多个地方实施。
Ø 作用
1、每个请求都要完成常见的系统服务处理。比如,安全服务会完成验证和授权检测
2、最适合在一个地方处理的逻辑却在多个视图中重复放置
3、决策点存在依赖于数据的检索和操作
4、可以使用多个视图以答复类似的业务请求
5、在一个集中点处理请求很有用处,比如控制和记录某用户在整个站点的活动
6、系统服务和视图管理逻辑相对比较复杂
Ø 解决方案
控制器提供一个控制和管理web请求处理的集中式入口点。通过集中化决策点和控制,控制器有助于减少嵌入在jsp中java代码的数量。
1、控制器
控制器是系统中处理所有请求的最初联系点。控制器授权某助手以完成用户的验证和授权,或者启动连接检索。
2、分发者
分发者负责视图管理和导航,管理选择那个视图提供给用户,并且提供给分发资源控制。分发者可以封装在控制器中,或者可以协同工作的单独组件。分发者提供到视图的静态分发,或者更复杂的动态分发机制。分发者使用RequestDispather对象,并且封装一些其它处理。
3、助手
助手负责帮助视图或者控制器来完成其处理。因而,助手有许多职责,包含收集该视图需要的数据,并且存储其中间模型,在这种情况下有时助手被称为值bean。除此之外,助手还可以修改视图使用的数据模型。通过简单地提供对原始数据的访问,或者把数据格式化为Web内容,助手可以服务视图对数据的请求。
视图可以操作任何数目的助手,这些助手通常被实现为javaBean和自定义标签。除此之外,助手表示一个命令对象、一个代表,或者XSL转换器,其中XSL转换器可以和样式表组合使用以修订模型,以及把模型转换成合适的形式。
4、视图
视图向客户端表示和显示信息。视图会从模型中检索信息。通过封装和修订底层数据模型以便于显示,助手可以支持视图。
Ø 例子
代码-Servlet前端策略
public class EmployeeController extends HttpServlet { // initialize the servlet public void init(ServletConfig config) throws ServletException { super.init(config); }
// Destroy the servlet. public void destroy() {}
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { String page;
|
// ApplicationResources provides a simple API for retrieving constants and // other preconfigured values ApplicationResources resource = ApplicationResources.getInstance(); try { // use a helper object to gather parameter specific information RequestHelper helper = new RequestHelper(request); Command cmdHelper = helper.getCommand();
//command helper perform custom operation page = cmdHelper.execute(request , response); } catch(Exception e) { LogManager.logMessage( “EmployeeController:exception : ” + e.getMessage()); request.setAttribute(resource.getMessageAttr(), ”Exception occurred : ” + e.getMessage()); page = resource.getErrorPage(e); } //dispatch control to view Dispatch(request , response , page); }
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { processRequest(request , response); }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { processRequest(request , response); }
protected String getServletInfo() { return “Front Controller Pattern Servlet Front Strategy Example “; }
protected void dispatch(HttpServletRequest request, HttpServletResponse response, String page) throws javax.servlet.ServletException,java.io.IOException { RequestDispatcher dispatcher = getServletContext().getRequestDispathcer(page); dispatcher.forward(request , response); } } |
视图助手(View Helper)
Ø 环境
系统创建表示内容,而其中表示内容需要动态业务数据的处理。
Ø 问题
表示层更改经常发生,并且当业务数据访问逻辑和表示格式化逻辑被混杂时表示层更改很难开发和维护。这样使系统灵活性更差,更缺乏可重用性,并且对变化的适应性更弱。混合业务和系统逻辑与视图处理会降低模块性,并且提供了Web创作和软件开发组之间角色的不良分隔。
Ø 作用
1、业务数据消化需求是很重要的
2、在视图中嵌入业务逻辑会增强拷贝-粘贴类型的重用。由于在相同视图或者不同视图中逻辑片段的重用只是在每个出现的位置复制之,因而会产生维护问题和错误
3、希望明晰和区分工作分工,使不同人分别承担不同的软件开发者和Web创作组成员的角色
4、通常使用一个视图以答复特定业务请求
Ø 解决方案
1、视图
视图向客户端显示和表示信息。用于动态显示的信息是从某模型中检索的。助手通过封装和调整模型以便于显示,可以在一定程度上减轻了视图的负担。
2、助手
助手负责帮助视图或者控制器已完成其处理。因而,助手有大量的职责,包括收集视图需要的数据,以及存储中间模型,在后面这种情况下,助手有时被称做一个值bean。除此之外,为了便于视图显示所用,助手也许会调整数据模型。通过简单地提供对原始数据的访问,或者通过把数据格式化为Web内容,助手可以服务来自于视图的数据请求。
视图也许可以操作多个助手,其中这些助手通常被实现为JavaBean和自定义标签。除此之外,助手也许代表一个命令对象、一个代表,或者一个XSL转换器,其中XSL转换器可以与样式表一起使用调整模型和转换模型到合适的格式。
3、值bean
值bean是负责存储视图所使用中间模型状态的助手的别称。
4、业务服务
业务服务是客户端试图访问的某种服务所完成的角色。通常,业务服务是通过业务代表进行访问的。业务代表的角色是给业务服务提供控制和保护。
Ø 例子
JSP视图策略
<jsp:useBean id=”welcomeHelper” scope=”request” class=”corepatterns.util.WelcomeHelper” /> <html> <body bgcolor=”#ffffff”> <% If (welcomeHepler.nameExists()) { %> <center><h3>welcome<b> <jsp:getProperty name=”welcomeHelper” property=”name” /> </b></h3></center> <% } %> </body> </html> |
复合视图(Composite View)
Ø 环境
复杂的Web页面可以展示来自多个数据源的内容,使用多个包含单显示页面的子视图。除此之外,具有不同技能的许多个人可以参与这些Web页面的开发和维护。
Ø 问题
不是把某视图的模块化的原子部分合并到一个复合体的机制,是通过直接在每个视图中嵌入格式化代码来形成这些页面的。多个视图的布局变化很难,并且容易出错,原因是存在重复的代码。
Ø 作用
1、视图内容的原子部分经常变化
2、多个复合视图使用相似的子视图,比如顾客商品目录表。这些原子部分修饰有不同的模版文本,或者他们出现在该页面的不同位置
3、当子视图被直接嵌入和重复放置在多个视图中时,布局变化更难管理,并且代码更难维护
4、直接在视图中嵌入经常变化的模版文本也会潜在地影响系统得有效性和可管理性。在客户端能够看到这些模版组件的变化或者更新之前,服务器也许需要重新启动
Ø 解决方案
1、复合视图
复合视图是多个子视图聚集的视图
2、视图管理器
视图管理器管理着把模版片段的各个部分包含在一个复合视图中。视图管理器也许是标准JSP运行时引擎的一部分,采用形式是标准的JSP包含标签,或者它也许被封装在JavaBean助手或者自定义标签助手以提供更加健壮的功能。
使用除标准包含标签的其他机制的好处是有条件包含语句很容易实现。比如,只有当用户承担特定角色,或者特定条件被满足时,特定模版片段才会被包含。更进一步,把助手组件用作视图管理器可以实现对页面结构整体更精确控制,这很有利于创建可重用的页面布局。
3、被包含视图
被包含视图是更大的整体视图的一个原子部分。这个被包含的视图也可以潜在地可能是复合的,本身也可能包含多个子视图。
Ø 例子
标准标签视图管理策略
<html> <body> <jsp:include page=”/jsp/CompositeView/javabean/banner.html” flush=”true” /> <table width=”100%”> <tr align=”left” valign=”middle”> <td width=”20%”> <jsp:include page=”/jsp/CompositeView/javabean/ProfilePane.jsp” flush=”true” /> </td> </tr> </table> <jsp:include page=”/jsp/CompositeView/javabean/footer.html” flush=”true” /> </body> </html> |
工作者服务(Service To Worker)
Ø 环境
系统控制着执行流和对业务数据的访问,其中借助于业务数据,系统可以创建表示内容。
提示:与分发者视图模式类似的是,工作者服务模式可以描述目录中其他模式的组合。这些宏描述控制器的组合,并且分发视图和助手。当描述该常见结构时,它们重点在于相关但是不同的用法模式。
Ø 问题
此处所说的问题是表示层中前端控制器和视图助手模式所要解决的问题组合。不存在管理访问控制、内容检索或者视图管理的集中化组件,并且在多个视图中存在重复的控制代码。除此之外,业务逻辑和表示格式化逻辑在这些视图中被混合,使系统灵活性更差,可重用性不好,而且一般还会使更改操作难以实施。
Ø 作用
1、对每个请求都需要进行验证和授权检测
2、视图中的scriptlet代码应该被最少化
3、业务逻辑应该被封装在除视图之外的组件中
4、控制流相对比较复杂,并且给予来自动态内容的值
5、视图管理逻辑是相对比较高级的,因为其中多个视图潜在地影射到相同的请求
Ø 解决方案
1、控制器
通常,控制器是处理请求的最初连接点。它与分发者一起来完成视图管理和导航。控制器管理验证、授权、内容检索、确认和请求处理的其他方面。它委托助手来完成这部分工作。
2、分发者
分发者负责视图管理和导航,选择下一个视图,并且提供分发资源控制的机制。
分发者可以被封装在控制器中,或者它可能是与控制器协同工作的独立组件。分发者可以提供视图的静态分发,或者也许提供更高级的动态分发机制。分发者使用RequestDispather对象,但是通常它会封装一些其他处理。该组件封装的功能越多,它越符合工作者服务模型。相反地,当分发者扮演一个更有限的角色,则它更符合分发者视图模式。
3、视图
视图向客户端表示和显示信息。显示中使用的信息是从模型中检索的。通过封装和调整显示中使用的模型,助手可以在一定程度上支持视图。
4、助手
助手帮助视图和控制器完成处理工作。因而,助手有很多职责,包括收集视图需要的数据,以及存储中间模型,在这种情况下有时助手也被称作一个值bean。除此之外,为了视图的显示所用,助手也许会调整数据模型。通过简单地提供对原始数据的访问,或者把数据格式化为Web内容,助手可以服务于来自视图的数据请求。视图也许与任何数目的助手一起工作,其中这些助手通常被实现为JavaBean和自定义标签。除此之外,助手也许表示某Command对象或者代表。
5、值bean
值bean是负责为了视图显示而保存中间模型的助手的别称。在通常的情况下,每个环境都有一个业务服务,负责返回一个值bean作为请求的答复。在这种情况下,值bean会完成值对象的角色。
6、业务服务
业务服务是负责完成客户端希望访问的服务的角色。通常,业务服务的访问是通过业务代表完成的。业务代表的角色是为业务服务提供控制和保护。
Ø 例子
下面的范例代码显示了工作者服务的模式的实现,使用了一个控制器servlet、一个命令助手、一个分发者组件以及一个视图。
具有命令和控制器策略的servlet
public class Controller extends HttpServlet {
protected void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException,IOException {
String next;
try {
LogManager.recordStrategy(request ,
“Service To Worker”,
“ServletFront Strategy;JspView Strategy;JavaBean Helper Strategy”);
LogManager.logMessage(request,
getSignature(),
”process incoming request.”);
RequestHelper helper = new RequestHelper(request,response);
LogManager.logMessage(request,
getSignature(),
“Getting command object helper”);
Command command = helper.getCommand();
next = command.execute(helper);
}
catch(Exception e) {
LogManager.logMessage(“EmployeeController(CommandStrategy)”,
e.getMessage());
next = ApplicationResources.getInstance().getErrorPage(e);
}
dispatch(request , response , next);
}
protected void doGet(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
processRequest(request,response);
}
protected void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
processRequest(request,response);
}
public String getServletInfo() {
return getSignature();
}
protected void dispatch(HttpServletReqeust request,
HttpServletResponse response,
String page) throws ServletException,IOException {
RequestDispatcher dispatcher =
getServletContext().getRequestDispatcher(page);
dispatcher.forward(request,response);
}
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void destroy() {}
private String getSignature() {
return “ServiceToWorker-Controller”;
}
}
Command接口
public interface Command {
public String execute(RequestHelper helper)
throws ServletException,IOException;
}
Command接口的实现
public class ViewAccountDetailsCommand implements Command {
public ViewAccountDetailsCommand() {}
public String execute(RequestHelper helper)
throws ServletException,IOException {
String systemError =
“/jspdefaultprocesssingerror.jsp”;
LogManager.logMessage(helper.getRequest(),
“ViewAccountDetailsCommand”,
“Get Account Details from an adapter object”);
AccountingAdapter adapter = new AccountingAdapter();
Adapter.setAcountInfo(helper);
LogManager.logMessage(helper.getRequest(),
“ViewAccountDetailsCommand”,
“processing complete”);
Dispatcher dispatcher = new Dispatcher();
dispatcher.dispatch(helper);
return systemError;
}
}
AccountingAdapter
public class AccountingAdapter {
public void setAccountInfo(RequestHelper requestHelper) {
LogManager.logMessage(requestHelper.getRequest(),
“Retrieving data from business tier”);
AccountDelegate delegate = new AccountDelegate();
AccountVO account =
delegate.getAccount(
requestHelper.getCustomerId();
requestHelper.getAccountKey();
LogManager.logMessage(
requestHelper.getRequest(),
“Store account value object in request attribute”);
requestHelper.getRequest().setAttribute(“acount”,account);
}
}
前端视图accountdetails.jsp
<html>
<head><title>AccountDetails</title></head>
<body>
<jsp:useBean id=”account” scope=”request” class=”corepatterns.util.AccountVO” />
<center>Account Detail for <jsp:getProperty name=”account” property=”owner” />
<table>
<tr>
<td>Acount Number :</td>
<td><jsp:getProperty name=”account” property=”number” /></td>
</tr>
<tr>
<td>Acount Type :</td>
<td><jsp:getProperty name=”account” property=”type” /></td>
</tr>
<tr>
<td>Acount Balance :</td>
<td><jsp:getProperty name=”account” property=”balance” /></td>
</tr>
<tr>
<td>OverDraft Limit :</td>
<td><jsp:getProperty name=”account” property=”OverdraftLimit” /></td>
</tr>
</table>
</center>
</body>
</html>
分发者视图(Dispatcher View)
Ø 环境
系统控制执行流,以及对表示层处理的访问,其中表示层处理负责产生动态内容。
提示:与工作者服务模式类似,分发者视图模型描述来自该目录中其他模式的常见组合。这些宏模式都描述了控制其和分发者与视图和助手的组合。当描述该常见结构时,它们重点在于相关但是不同的用法模式。
Ø 问题
此处所说的问题是表示层中前端控制器和视图助手模式所解决的问题组合。不存在管理访问控制、内容检索、或者视图管理的集中化组件,并且在多个视图中存在重复的控制代码。除此之外,业务逻辑和表示格式化逻辑在这些视图中被混合,使系统灵活性更差,可重用性不好,而且一般还会使更改操作难以实施。
混合业务逻辑和视图处理也会降低模块化,并且使Web创作和软件开发组中的角色划分比较差。
Ø 作用
1、对每个请求都需要进行验证和授权检测
2、视图中的scriptlet代码应该被最少化
3、业务逻辑应该被封装在除视图之外的组件中
4、控制流相对比较简单,并且通常基于封装在请求中的值
5、视图管理逻辑受限与复杂性
Ø 解决方案
把控制器和分发者与视图和助手合并起来以处理客户端请求,并且准备动态表示作为答复。控制器不把内容检索委托给助手,因为这些活动被推迟到视图处理的时候。分发者负责视图管理和导航,并且可以被封装在控制器、视图,或者单独的组件中。
1、 控制者
控制者是处理请求的最初联系点。控制者管理验证和授权,并且委托分发者来实施视图管理。
2、 分发者
分发者负责视图管理和导航,选择呈现给用户的下一个视图,并且提供分发资源控制的机制。分发者可以被封装在控制器中,或者它可能是与控制器协同工作的独立组件。分发者可以提供视图的静态分发,或者也许提供更高级的动态分发机制。
3、 视图
视图向客户端表示和显示信息。显示中的使用信息是从模型中检索得到的。通过封装和调整显示中使用的模型,助手可以在一定程度上支持视图。
4、 助手
助手负责帮助视图和控制器完成处理工作。因而,助手有很多职责,包括收集视图需要的数据,以及存储中间模型,在这种情况下有时助手也被称作一个值bean。除此之外,为了视图的显示所用,助手也许会调整数据模型。通过简单的提供对原始数据的访问,或者把数据格式化为Web内容,助手可以服务于来自于视图的数据请求。
视图也许与任何数目的助手一起工作,其中这些助手通常被实现为JavaBean和自定义标签。除此之外,助手也许表示某Command对象或者代表。
5、 值bean
值bean是负责为了视图显示而保存中间模型的助手的别称。在通常的情况下,每个环境都有一个业务服务,负责返回一个值bean作为请求的答复。在这种情况下,值bean会完成值对象的角色。
6、 业务服务
业务服务是负责完成客户端希望访问的服务的角色。通常,业务服务的访问是通过业务代表完成的。业务代表的角色是为业务服务提供控制和保护。
Ø 例子
下面的范例代码显示了分发者视图模式的一个实现,使用了一个控制器servlet、一个具有JavaBean的视图,以及自定义标签助手。
分发者视图控制器Servlet
public class Controller extends HttpServlet {
protected void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException,IOException {
String nextview;
try {
LogManager.recordStrategy(request,
“Dispather View”,
“Servlet Front Strategy;Jsp View Strategy; Custom tag helper Strategy”);
LogManger.logMessage(request,getSignature(),”process incoming request.”);
RequestHelper helper = new RequestHelper(request,response);
LogManager.logMessage(request,getSignature(),”Autherticate user”);
Authenticator auth = new BasicAuthenticator();
auth.authenticate(helper);
LogManager.logMessage(request,getSignature(),”Getting nextview”);
newxtview = request.getParameter(“nextview”);
LogManager.logMessage(request,getSignature(),”Dispatching to view:” +
nextview);
}
catch(Exception e) {
LogManager.logMessage(“Handle exception approperiately”,e.getMessage());
nextview = ApplicationResources.getInstance().getErrorPage(e);
}
dispatch(request,response,nextview);
}
protected void doGet(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
processRequest(request,response);
}
protected void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
processRequest(request,response);
}
protected void dispatch(HttpServletRequest resquest,
HttpServletResponse response,
String page) throws ServletException,IOException {
RequestDispatcher dispatcher =
getServletContext().getRequestDispatcher(page);
dispatcher.forward(request,response);
}
private String getSignature() {
return “DispatcherView-Controller”;
}
…
}
视图accountdetails.jsp
<%@ taglib uri=”/WEB-INF/corepatternstaglibrary.tld” prefix=”corepatterns” %>
<body>
<corepatterns:AccountQuery queryParams=”custid,acctkey” scope=”request” />
<center><corepatterns:account attribute=”owner”/> </center>
2、 商业层模式
业务代表(Business Delegate)
Ø 环境
多层、分布式系统需要远程方法调用来在层之间发送和接收数据。处理分布式组件的复杂性直接暴露给客户端。
Ø 问题
表示层组件直接与业务服务交互。这种直接的交互会把业务服务应用程序编程接口的底层实现细节暴露给表示层。结果,在业务服务的实现中表示层组件对变化是很敏感的:当业务服务的实现变化时,在表示层中被暴露的实现代码需要同步进行更改。除此之外,这样做对网络性能也有影响,原因是使用业务服务API的表示层组件会通过网络做太多的调用。在下面环境中就会出现这种糟糕的情况:当表示层组件直接使用低层API,而且没有客户端缓冲机制或者会聚服务。
Ø 作用
1、表示层客户需要访问业务服务
2、诸如设备、Web客户和瘦客户机等不同的客户需要访问业务服务
3、随着业务需求的发展,业务服务API会变化
4、我们希望降低表示层客户端和业务服务之间的耦合,这样可以隐藏诸如查找和访问等服务的底层实现细节
5、客户端需要为业务服务信息实现缓冲机制
6、我们希望降低客户端和业务服务之间的网络流量
Ø 解决方案
值对象(Value Object)
Ø 环境
应用程序客户端需要与企业bean之间交换数据。
Ø 问题
J2EE应用程序把服务器端业务组件实现为会话bean和实体bean。业务组件的一些方法可以向客户端返回数据。通常,客户端需要多次调用业务对象的get方法直到获得所有的属性值。
Ø 作用
1、所有对企业bean的访问是通过该bean的远程接口实现的。
2、通常,应用程序中的读事务比更新事务多得多。
3、客户端经常需要多个属性的值或者企业bean的相关对象
4、客户端对企业bean发起的调用数目影响着网络性能
Ø 解决方案
1、客户端
代表企业bean的客户。客户端可以是一个终端用户应用程序,就像已经被分配直接访问企业bean的富客户端应用程序一样。客户端可以是业务代表,或者不同的BusinessObject。
2、业务对象
业务对象代表模式中的一个角色,它可以用会话bean、实体bean或者数据访问对象来实现。
3、值对象
值对象是任意的可串行化java对象,也被称作值的对象。值对象类也许提供可以接收所有必须的属性以创建该值对象的构造器。
会话外观(Session Facade)
Ø 环境
企业bean封装了业务逻辑和业务数据,并且把它们的接口暴露给客户层,因而分布式服务的复杂性也暴露给客户层。
Ø 问题
1、紧密耦合,这会导致客户端和业务对象的直接依赖关系
2、客户端和服务器之间的网络方法调用太多,容易导致网络性能问题
3、缺乏统一的客户访问策略,容易误用业务对象
Ø 作用
1、通过隐藏业务组件之间所有的复杂交互活动,向客户端提供一个更简单的接口
2、减少通过网络并跨越服务层被直接暴露给客户端的业务对象的数目
3、向客户端隐藏业务组件之间的底层交互和相互依赖关系
4、提供统一的粗粒度服务层,以分离业务对象实现和业务对象抽象
5、避免把底层业务对象直接暴露给客户端,是两个层之间的紧密耦合最小
Ø 解决方案
1、客户端
客户端代表着会话外观的客户,其中客户需要访问业务服务。客户端可以是相同业务层中的其他会话bean,或者其他层中的业务代表。
2、会话外观
会话外观被实现为会话bean。会话外观管理着大量业务对象之间的关系,并且向客户端提供高层抽象。会话外观提供对所参与业务对象的粗粒度访问,其中这些访问是由Invoke的会话bean调用来表示的。
3、业务对象
业务对象是有利于应用不同策略的角色对象,比如会话实体bean和DAO。业务对象在类图中提供数据和/或者某些服务。会话外观与多个业务对象实例交互以提供服务。
复合实体(Composite Entity)
Ø 环境
实体bean的目的不是代表对象模型中的每个持久性对象。实体bean更适合与粗粒度的持久性业务对象。
Ø 问题
在J2EE应用程序中,客户端通过实体bean的远程接口对实体bean进行访问。因而,每个客户端调用都潜在地通过网络进行路由,即使客户端和企业bean位于相同的JVM、OS或者机器中,也不例外。当企业bean是细粒度对象时,客户端会调用更多的单个企业bean方法,这样会导致更高的网络负载。
Ø 作用
1、由于与每个实体bean相关的高负载,实体bean最好被实现为粗粒度对象
2、直接把关系数据库模式映射到实体bean的应用程序会拥有大量的细粒度实体bean
3、对象模型到EJB模型的直接映射会产生细粒度实体bean
4、为了使用和支持实体bean,客户端不必了解数据库模式的实现
Ø 解决方案
1、复合实体
复合实体是粗粒度实体bean。复合实体可能是粗粒度对象,也可以是拥有粗粒度对象的引用。
2、粗粒度对象
粗粒度对象是拥有自己的生命期的对象,并且管理与其他对象之间的关系。粗粒度对象可以是包含在复合实体中的java对象。复合实体本身也可能是拥有依赖对象的粗粒度对象。
3、依赖对象
依赖对象是依赖于粗粒度对象的对象,并且其生命期由该粗粒度对象来管理。
值对象组装器(Value Object Assembler)
Ø 环境
在J2EE应用程序中,服务器端业务组件是使用会话bean、实体bean、DAO等等实现的。应用程序客户端会频繁地需要访问由多个对象组成的数据。
Ø 问题
1、由于客户端必须逐个访问每个分布式组件,因此客户端与网络中模型的分布式组件存在紧密耦合关系
2、客户端通过网络层来访问分布式组件,如果该模型非常复杂,有大量的分布式组件,则这样做会严重降低性能
3、在从分布式组件获取模型的各部分数据之后,客户端必须重新构造该模型
4、由于客户端与该模型的紧密耦合,则该模型的变化也需要对客户端作相应的变化
Ø 作用
客户端和服务器端组件之间需要分隔业务逻辑
Ø 解决方案
值对象组装器(ValueObjectAssembler)是该模式的主要类。当客户端请求一个复合值对象时,ValueObjectAssembler会基于应用程序的需求构造一个新的值对象。然后,ValueObjectAssembler定位所需要的BusinessObject实例来检索数据,以创建该复合值对象。
值列表处理器(Value List Handler)
Ø 环境
为了表示,客户端需要来自服务的项列表。列表中项的数目是未知的,并且在许多情况下其数量非常巨大。
Ø 问题
大多数J2EE应用程序都有搜索和列出特定数据的搜索和查询需求。在有些情况下,这类搜索和查询操作可以产生相当巨大的结果。当客户端的需求是遍历结果,而不是处理完整集合时,则返回完整的结果集是不现实的。
Ø 作用
1、我们需要服务器层缓冲机制以服务于那些不能接收和处理整个结果集的客户端
2、在适度静态数据上反复执行的查询可以被优化以提供更快捷的结果
3、客户端希望在结果集中向前滚动或者向后看
Ø 解决方案
1、当客户端发起相应的查询请求时,ValueListHandler就会执行查询
2、ValueListHandler调用DAO来保持数据库访问实现的独立性
3、ValueListHandler把查询结果保存在由ValueList表示的私有集合
服务定位器(Service Locator)
Ø 环境
服务查找和创建涉及复杂接口和网络操作
Ø 问题
J2EE客户端与诸如EJB和JMS组件进行交互,这些组件提供业务服务和持久性能力。为了与这些组件进行交互,客户端必须要么定位于该服务组件,要么创建一个新的组件。所有J2EE应用程序客户端都使用JNDI公共机制来查找并创建EJB和JMS组件
Ø 作用
1、组件的查找和创建可能比较复杂,并且可能在应用程序的多个客户端中反复使用
2、如果反复需要的话,最初的环境创建和服务对象查找可能是很耗费资源的,也许会严重影响应用程序的性能
Ø 解决方案
1、服务定位器
服务定位器抽取了API查找服务、提供者依赖性、查找复杂性和业务对象创建,并且向客户端提供了一个简单的接口。这样做会降低客户端的复杂性。
2、InitialContext
是查找和创建过程的开始点。
3、ServiceFactory
该对象表示为BusinessService对象提供生命期管理的对象
4、BusinessService
该对象是由客户端正在寻求访问的服务所完成的角色
3、 集成层模式
数据访问对象(Data Access Object)
Ø 环境
根据数据源不同,数据访问也不同。根据存储类型和供应商实现不同,持久性存储的访问差别也很大。
Ø 作用
1、产品供应商不同,持久性存储API差别很大
2、组件需要透明与实际的持久性存储或者数据源实现,以便于提供到不同供应商产品、不同存储类型和不同数据源类型的更容易的移植性
Ø 解决方案
1、DAO(数据访问对象)
DAO抽取该BusinessObject的低层数据访问实现,以保证对数据源的透明访问
2、DataSource(数据源)
代表数据源的实现。可以是RDBMS的数据库、OODBMS等。