《JAVA WEB应用实践》
第一章 、JSP的认识
1、Tomcat7 的目录结构:
目录 | 说明 |
---|---|
/bin | 存放用于启动和停止Tomcat的脚本文件 |
/conf | 存放Tomcat服务器的各种配置文件,最重要的是service.xml文件 |
/lib | 存放Tomcat服务器需要的各种jar文件,驱动包 |
/logs | 存放Tomcat的日志文件 |
/temp | 存放Tomcat运行时的临时文件 |
/webapps | Web应用的发布目录 |
/work | Tomcat把由JSP生成的Servlet放于此目录中 |
1.1、bin:该目录下存放的是二进制可执行文件
如果是安装版,那么这个目录下会有两个exe文件:tomcat6.exe:是在控制台下启动Tomcat;tomcat6w.exe:是弹出UGI窗口启动Tomcat;如果是解压版,那么会有startup.bat和shutdown.bat文件,startup.bat用来启动Tomcat,但需要先配置JAVA_HOME环境变量才能启动,shutdawn.bat用来停止Tomcat;
1.2、conf:这是一个非常非常重要的目录,这个目录下有四个最为重要的文件:
server.xml:配置整个服务器信息。例如修改端口号,添加虚拟主机等;
tomcatusers.xml:存储tomcat用户的文件,这里保存的是tomcat的用户名及密码,以及用户的角色信息。可以按着该文件中的注释信息添加tomcat用户,然后就可以在Tomcat主页中进入Tomcat Manager页面了;
web.xml:部署描述符文件,这个文件中注册了很多MIME类型,即文档类型。这些MIME类型是客户端与服务器之间说明文档类型的,如用户请求一个html网页,那么服务器还会告诉客户端浏览器响应的文档是text/html类型的,这就是一个MIME类型。客户端浏览器通过这个MIME类型就知道如何处理它了。当然是在浏览器中显示这个html文件了。但如果服务器响应的是一个exe文件,那么浏览器就不可能显示它,而是应该弹出下载窗口才对。MIME就是用来说明文档的内容是什么类型的!
context.xml:对所有应用的统一配置,通常我们不会去配置它。
1.3、lib:Tomcat的类库,里面是一大堆jar文件。
如果需要添加Tomcat依赖的jar文件,可以把它放到这个目录中,当然也可以把应用依赖的jar文件放到这个目录中,这个目录中的jar所有项目都可以共享之,但这样你的应用放到其他Tomcat下时就不能再共享这个目录下的Jar包了,所以建议只把Tomcat需要的Jar包放到这个目录下。
1.4、logs:这个目录中都是日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,那么异常也会记录在日志文件中。
1.5、temp:存放Tomcat的临时文件,这个目录下的东西可以在停止Tomcat后删除!
1.6、webapps:存放web项目的目录,其中每个文件夹都是一个项目。
如果这个目录下已经存在了目录,那么都是tomcat自带的。项目。其中ROOT是一个特殊的项目,在地址栏中没有给出项目目录时,对应的就是ROOT项目。http://localhost:8080/examples,进入示例项目。其中examples就是项目名,即文件夹的名字。
1.7、work:运行时生成的文件,最终运行的文件都在这里。
通过webapps中的项目生成的!可以把这个目录下的内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。
1.8、LICENSE:许可证。
1.9、NOTICE:说明文件。
2、JSP内置对象及作用域
JSP工作原理:
服务器收到JSP请求 --> 对JSP文件进行翻译成.java文件 --> 再将.java文件编译成可执行的字节码.class文件 --> 进行执行阶段,将生成的结果返回给客户端。
2.1、JSP九大内置对象
- 输入输出对象:out对象、response对象、request对象
- 通信控制对象:pageContext对象、session对象、application对象
- Servlet对象:page对象、config对象
- 错误处理对象:exception对象
对象 | 功能 | 基类 | 作用域 |
---|---|---|---|
request | 请求对象:封装了来自客户端、浏览器的各种信息。 | javax.servlet.ServletRequest | Request |
response | 响应对象:封装了服务器的响应信息。 | javax.servlet.SrvletResponse | Page |
session | 会话对象:用来保存会话信息。可以实现在同一用户的不同请求之间共享数据 | javax.servlet.http.HttpSession | Session |
application | 应用程序对象:代表当前应用程序的上下文。可在不同的用户之间共享信息。 | javax.servlet.ServletContext | Application |
out | 输出对象:用于向客户端、浏览器输出数据 | javax.servlet.jsp.JspWriter | Page |
config | 配置对象:封装应用程序的配置信息。 | javax.servlet.ServletConfig | Page |
page | 页面对象:指向了当前jsp程序本身。 | javax.lang.Object | Page |
pageContext | 页面上下文对象: 提供了对jsp页面所有对象以及命名空间的访问,可以取得任何范围的参数,通过它可以获取 JSP页面的out、request、reponse、session、application 等对象。 | javax.servlet.jsp.PageContext | Page |
exception | 例外对象:封装了jsp程序执行过程中发生的异常和错误信息。 | javax.lang.Throwable | page |
2.1.1、作用域对比
作用域 | 描述 |
---|---|
Page | 它的有效范围只在当前jsp页面里。从把变量放到pageContext开始,到jsp页面结束,你都可以使用这个变量。 |
Request | 请求周期,就是指从http请求发起,到服务器处理结束,返回响应的整个过程。在这个过程中可能使用forward的方式跳转了多个jsp页面,在这些页面里你都可以使用这个变量。 |
Session | 所谓当前会话,就是指从用户打开浏览器开始,到用户关闭浏览器这中间的过程。这个过程可能包含多个请求响应,只要用户不关浏览器,服务器就有办法知道这些请求是一个人发起的,整个过程被称为一个会话(session),而放到会话中的变量,就可以在当前会话的所有请求里使用。 |
Application | 整个应用是指从应用启动,到应用结束。我们没有说“从服务器启动,到服务器关闭”,是因为一个服务器可能部署多个应用,当然你关闭了服务器,就会把上面所有的应用都关闭了。 application作用域里的变量,它们的存活时间是最长的,如果不进行手工删除,它们就一直可以使用。application里的变量可以被所有用户共用 。 |
2.1.2、post和get的区别:
比较 | POST | GET |
---|---|---|
是否在URL中显示参数 | 否 (作为HTTP消息的实体内容发送给WEB服务器) | 是 (请求会将参数跟在URL后进行传递 ) |
数据传递是否有长度限制 | 无(理论上不受限制) | 有(对传输的数据大小有限制,通常不能大于2KB) |
数据安全性 | 高(相对来说就可以避免右边的这些问题) | 低(请求的数据会被浏览器缓存起来,因此 其他人就可以从浏览器的历史记录中读取到 这些数据,例如账号和密码等。在某种情况下,GET方式会带来严重的安全问题) |
URL是否可以传播 | 否 | 是 |
应用场景:
1、若符合下列任一情况,则用POST方法:
- 请求的结果有持续性的副作用,例如,数据库内添加新的数据行。
- 若使用GET方法,则表单上收集的数据可能让URL过长。
- 要传送的数据不是采用7位的ASCII编码。
2、若符合下列任一情况,则用GET方法:
-
请求是为了查找资源,HTML表单数据仅用来帮助搜索。
-
请求结果无持续性的副作用。
-
收集的数据及HTML表单内的输入字段名称的总长不超过1024个字符。
2.1、下面用代码来说明两者的区别:
2.2、常用方法
2.2.1、request对象
方法 | 说明 |
---|---|
getParameter(String name) | 获取指定参数的值,返回类型为String,若无对应的参数,返回null |
getParameterValues(String name) | 返回一组具有相同名称的参数的值,返回类型为String类型的数组 |
几种request获取路径的方法:
- getServletPath():获取能与“url-pattern”中匹配的路径,注意是完全匹配的部分,*的部分不包括。
- getPageInfo():与getServletPath()获取的路径互补,能够得到的是“url-pattern”中*d的路径部分
- getContextPath():获取项目的根路径
- getRequestURI:获取根路径到地址结尾
- getRequestURL:获取请求的地址链接(浏览器中输入的地址)
- getServletContext().getRealPath(“/”):获取“/”在机器中的实际地址
- getScheme():获取的是使用的协议(http 或https)
- getProtocol():获取的是协议的名称(HTTP/1.11)
- getServerName():获取的是域名(xxx.com)
- getLocalName:获取到的是IP。
2.2.2、session对象
方法 | 返回值类型 | 说明 |
---|---|---|
setAttribute(String key,Object obj) | void | 将参数Object指定的对象obj添加到Session对象中 |
getAttribute(String key) | Object | 获取Session对象中含有关键字的对象 |
getId() | String | 获取Session对象编号 sessionid |
invalidate() | void | 设置Session失效 |
setMaxInactiveInterval() | void | 设置Session的有效期(单位:秒) |
removeValue(String name) | void | 移除session中指定的属性 |
2.2.3、设置session的失效时间
Session的默认失效时间是30分钟。
1、程序主动清除:
a) 使用 session.invalidate()
b) 将指定名称的属性清除 session.removeValue(String name)
2、服务器主动清除:
a) session.setMaxInactiveInterval(30 * 60);//设置单位为秒,设置为-1永不过期
b) 在Tomcat服务器中的web.xml文件中和添加如下代码:
<session-config> <session-timeout>30</session-timeout> </session-config> //设置单位为分钟
2.2.4、Cookie对象
方法 | 返回值类型 | 说明 |
---|---|---|
setValue(Strign value) | void | 创建Cookie后,为Cookie赋值 |
getName() | String | 获取Cookie的名称 |
getValue | String | 获取Cookie的值 |
getMaxAge() | int | 获取Cookie的有效期,以秒为单位 |
setMaxAge(int enpiry) | void | 设置Cookie的有效期,以秒为单位 大于0表示Cookie的有效时间;0表示删除Cookie; -1或者不设置表示Cookie会在当前窗口关闭后失效 |
方法 | 返回值类型 | 说明 |
---|---|---|
setAttribute(String key, Object value) | void | 以key-value的形式保存对象值 |
getAttribute(String key) | Object | 通过key获取对象值 |
2.2.5、getAttribute和getParameter的区别
2.2.5.1、getAttribute()方法
它只有一个参数,那个参数就是我们使用getElementById()或使用getElementByTagName()方法取出来的节点元素的属性名称。取得属性的名称之后,我们就可以用getAttribute()方法将它的属性值拿出来了。
<body>
<p id="p1" customData="pmx">ppp</p>
<script>
var p = document.getElementById("p1");
var pnode = p.getAttributeNode("customData");
console.log(pnode)
</script>
</body>
2.2.5.2、getParameter()方法
getParameter的中文意思就是获取参数,那么这个方法的作用就是用来获取参数的,参数为页面提交的参数,包括:表单提交的参数、URL重写(就是xxx?id=1中的id)传的参数等,它得到的是String类型。或者是用于读取提交的表单中的值,或是某个表单提交过去的数据。getParameter()是获取POST/GET传递的参数值;它用于客户端重定向时,即点击了链接或提交按扭时传值用,即用于在用表单或url重定向传值时接收数据用。getParameter只是应用服务器在分析你送上来的request页面的文本时,取得你设在表单或url重定向时的值。 当两个web组件之间为链接关系时,被链接的组件同个getParameter方法来获得请求参数。
2.2.5.3、getAttribute和getParameter的区别
getAttribute表示从request范围取得设置的属性,那么我们必须先setAttribute设置属性,才能获得属性,设置与取得的为string类型。HttpServletRequest类既有getAttribute()方法也有getParameter方法,这两个方法有什么区别?
1)、getAttribute是返回Object对象类型,getParameter返回String字符串类型。
2)、request.getAttribute()方法返回request范围内存在的对象,而request.getParameter()方法是获取http提交过来的数据。
3)、与getAttribute()方法对应的有setAttribute()方法,但是没有与getParameter()相对的setParameter()。
2.3、Cookie和session比较
保存机制 | 保存内容 | 有效期 | 重要性 | |
---|---|---|---|---|
session | 服务器保存用户信息 | 对象 | 随会话结束而失效 | 保存重要的信息 |
Cookie | 客户端保存用户信息 | 字符串 | 长期保存在客户端 | 通常较不重要的客户信息 |
Cookie的应用三步骤:
1、创建cookie对象:Cookie cookieName=new Cookie (String key,String value);
2、写入cookie :response.addCookie(cookieName);
3、读取cookie :Cookie[] cookies=request.getCookies();
config用于存放配置信息。
context是整个容器对象,一般用于存放全局数据,不适合存放用户信息。
cookie信息保存在客户端,如果用户清除cookie或者更换浏览器就会丢失之前保存的用户信息,如果没有指定Cookie的时效,那么默认的时效是会话级别 。
session是存放于服务器端的,无论用户怎么更换浏览器,都不会造成用户信息丢失。
2.4、四种会话跟踪方法
1.URL重写
2.隐藏表单域
3.Cookie
4.Session
2.5、常见错误
错误代码 | 说明 | 调试及解决方法 |
---|---|---|
404 | 找不但要访问的页面或资源 | 检查URL是否错误 外部启动Tomcat,未部署项目 JSP是否不在可访问的位置,如:WEB-INF目录 |
500 | JSP代码错误 | 检查JSP代码,并修改错误 |
页面无法显示 | 未启动Tomcat | 启动Tomcat |
202 | 服务器已接受了请求,但尚未对其进行处理 | |
400 | 处理器不理解请求的语法 |
3、HTTP中的重定向和请求转发的区别
3.1、调用方式
转发、重定向的语句如下:
request.getRequestDispatcher("new.jsp").forward(request, response); //转发到new.jsp
response.sendRedirect("new.jsp"); //重定向到new.jsp
在jsp页面中你也会看到通过下面的方式实现转发:
<jsp:forward page="apage.jsp" />
当然也可以在jsp页面中实现重定向:
<%response.sendRedirect("new.jsp");%> //重定向到new.jsp
3.2、转发和重定向的本质区别
比较项 | 转发 | 重定向 |
---|---|---|
请求次数 | 1次 | 2次 |
作用端 | 服务器 | 客户端 |
是否共享数据 | 是 | 否 |
是否显示新地址 | 不会显示转发后的地址 | 可以显示重定向后的地址 |
转发是服务器行为,重定向是客户端行为。看如下两个动作的工作流程:
转发过程:客户浏览器发送http请求----》web服务器接受此请求–》调用内部的一个方法在容器内部完成请求处理和转发动作----》将目标资源发送给客户;在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。
重定向过程:客户浏览器发送http请求----》web服务器接受后发送302状态码响应及对应新的location给客户浏览器–》客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址----》服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器路径栏显示的是其重定向的路径,客户可以观察到地址的变化的。重定向行为是浏览器做了至少两次的访问请求的。
例子解释:假设你去办理某个执照
重定向:你先去了A局,A局的人说:“这个事情不归我们管,去B局”,然后,你就从A退了出来,自己乘车去了B局。
转发:你先去了A局,A局看了以后,知道这个事情其实应该B局来管,但是他没有把你退回来,而是让你坐一会儿,自己到后面办公室联系了B的人,让他们办好后,送了过来。
4、编码和路径问题
4.1、urlEncoder和urlDecoder的使用
1.URLEncoder.encode(String s, String enc)
使用指定的编码机制将字符串转换为 application/x-www-form-urlencoded 格式
String info="成功";
info = URLEncoder.encode(info,"utf-8");
2.URLDecoder.decode(String s, String enc)
使用指定的编码机制对 application/x-www-form-urlencoded 字符串解码。
info = URLDecoder.decode(info,"utf-8");
4.2、设置请求和响应的编码方式
设置请求的编码方式:在执行setCharacterEncoding()之前,不能执行任何getParameter()。
request.setCharacterEncoding("UTF-8"); //设置从request中取得的值或从数据库中取出的值。
设置响应的编码方式:调用如下方法,必须在getWriter执行之前或者response被提交之前.
response.setCharacterEncoding("UTF-8"); //设置HTTP 响应的编码
response.setHeader("content-type", "text/html;charset=utf-8"); //设置浏览器的打开数据的码表
//如下一行代码就相当于上面两行代码的效果,因为在setContentType方法中已经调用了setCharacterEncoding方法 设置了Response容器的编码了。
response.setContentType("text/html;charset=UTF-8"); //返回给客户端的编码,同时指定浏览器显示的编码。
注:参数s可取text/html,application/x-msexcel,application/msword
第二章、实现数据库的访问
1、创建JDBC连接数据库步骤
- DriverManager类:负责依据数据库的不同,管理JDBC的驱动。
- Connection接口:负责连接数据库并担任传送数据的业务。
- Statement接口:由Connection产生,负责执行SQL语句。
- ResultSet接口:负责保存Statement执行后所产生的执行结果。
1.1、加载JDBC驱动程序:
在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.lang.Class类的静态方法forName(String className)实现。
例如:
try{
//加载MySql的驱动类
Class.forName("com.mysql.jdbc.Driver") ;
}catch(ClassNotFoundException e){
System.out.println("找不到驱动程序类 ,加载驱动失败!");
e.printStackTrace() ;
}
成功加载后,会将Driver类的实例注册到DriverManager类中。
不同数据库厂商的驱动类名不同:
Oracle10g:oracle.jdbc.driver.OracleDriver
MySQL5:com.mysql.jdbc.Driver
SQLServer2005:com.microsoft.sqlserver.jdbc.SQLServerDriver
1.2、提供JDBC连接的URL
•连接URL定义了连接数据库时的协议、子协议、数据源标识。
•书写形式:协议:子协议:数据源标识
协议:在JDBC中总是以jdbc开始
子协议:是桥连接的驱动程序或是数据库管理系统名称。
数据源标识:标记找到数据库来源的地址与连接端口。
例如:(MySql的连接URL)
jdbc:mysql://localhost:3306/test? ;
不同数据库产品的连接URL不同:
Oracle10g:jdbc:oracle:thin:@主机名:端口:数据库SID
jdbc:oracle:thin:@localhost:1521:ORCL
MySQL5:jdbc:mysql://主机名:端口/数据库名
jdbc:mysql://localhost:3306/test
SQLServer2005:jdbc:sqlserver://主机名:端口:DatabaseName=库名
jdbc:sqlserver://localhost:1433:DatabaseName=BookDB //数据库的用户名和密码
1.3、创建数据库的连接
要连接数据库,需要向java.sql.DriverManager请求并获得Connection对象,该对象就代表数据库的连接。
使用DriverManager的getConnectin(String url , String username , String password )方法传入指定的欲连接的数据库的路径、数据库的用户名和密码来获得。
例如:
//连接MySql数据库,用户名和密码都是root
String url = "jdbc:mysql://localhost:3306/test" ;
String username = "root" ;
String password = "root" ;
try{
Connection con = DriverManager.getConnection(url , username , password ) ;
}catch(SQLException se){
System.out.println("数据库连接失败!");
se.printStackTrace() ;
}
1.4、创建一个Statement
要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3 种类型:
1、执行静态SQL语句。通常通过Statement实例实现。
2、执行动态SQL语句。通常通过PreparedStatement实例实现。
3、执行数据库存储过程。通常通过CallableStatement实例实现。
PreparedStatement接口继承自Statement接口,都是由Connection接口产生
具体的实现方式:
通过Statement实例实现:
Statement stmt = con.createStatement() ;
ResultSet rs = stmt.executeQuery(sql);
通过PreparedStatement实例实现: //对sql语句进行预编译,其执行速度快于Statement,可以避免sql语句注入
PreparedStatement pstmt = con.prepareStatement(sql) ;
ResultSet rs = pstmt.executeQuery();
通过CallableStatement实例实现:
CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}") ;
1.5、执行SQL语句
Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate 和execute
1、ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句,返回一个结果集(ResultSet)对象。
2、int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等
3、execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的语句。
具体实现的代码:
ResultSet rs = stmt.executeQuery("SELECT * FROM ...") ;
int rows = stmt.executeUpdate("INSERT INTO ...") ;
boolean flag = stmt.execute(String sql) ;
1.6、处理结果
两种情况:
1、执行更新返回的是本次操作影响到的记录数。
2、执行查询返回的结果是一个ResultSet对象。
• ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些
行中数据的访问。
• 使用结果集(ResultSet)对象的访问方法获取数据:
while(rs.next()){
String name = rs.getString("name") ;
String pass = rs.getString(1) ; // 此方法比较高效
} //(列是从左到右编号的,并且从列1开始
1.7、关闭JDBC对象
操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声明顺序相反:
1、关闭记录集
2、关闭声明
3、关闭连接对象
public void closeAll() { // 释放资源
try {
if (rs != null)
rs.close();
if (stmt != null)
ps.close();
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
2、典型的Dao模式
主要由:Dao接口、Dao实现类、实体类、数据库连接和关闭工具类组成 ,将业务代码和数据库层面的代码隔离开,从而方便维护代码 ,分层低耦合,隔离不同数据库的实现,不同数据库都可以实现访问 。
1、DAO负责的不是业务逻辑 ,DAO是介于业务逻辑和数据持久化之间,负责操作数据访问操作。
2、DAO的实现类,是实现定义数据操作的接口,连接对应数据源,进行数据具体操作(增删改查)的,也是DAO这个模式的核心功能。
2.1、单例模式
单实例类只能存在一个该类的实例。需定义一个静态属性来保存已创建的对象,其构造方法要求是private型,外部不能直接访问构造方法创建对象。另外需要一个public方法做为该类的访问点保障只创建一个对象 。
单例模式 | 懒汉模式 | 恶汉模式 |
---|---|---|
概念 | 在类加载时不创建实例,采用延迟加载的 方式,在运行调用时创建实例 |
在类加载的时候,就完成初始化 |
特点 | 类加载速度快,但是运行时获取对象 的速度比较慢(时间换空间) |
类加载速度慢,但获取对象速度 比较快(空间换时间) |
延迟加载 | 具备 | 不具备 |
线程安全 | 线程不安全 | 线程安全 |
单例模式主要有3个特点:
1、单例类确保自己只有一个实例。
2、单例类必须自己创建自己的实例。
3、单例类必须为其他对象提供唯一的实例。
单例模式三个方面的作用:
第一、控制资源的使用,通过线程同步来控制资源的并发访问;
第二、控制实例产生的数量,达到节约资源的目的。
第三、作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信。
2.1.1、懒汉模式
只有在自身需要的时候才会行动,从来不知道及早做好准备。它在需要对象的时候,才判断是否已有对象,如果没有就立即创建一个对象,然后返回,如果已有对象就不再创建,立即返回。
public class ConfigManager {
private static ConfigManager configManager;
private static Properties properties; // 私有的静态属性
private ConfigManager() { // 私有的构造器
properties = new Properties();
InputStream in = ConfigManager.class.getClassLoader()
.getResourceAsStream("database.properties"); //固定写法
if (in == null){
throw new RuntimeException("找不到数据库参数配置文件!");
}
try {
properties.load(in); //要使用Properties对象的load()方法实现配置文件的读取
} catch (IOException e) {
e.printStackTrace();
}
}
public static ConfigManager getInstance() { // 提供对外开放的静态方法
if (configManager == null) { // 如果对象不为空直接返回
configManager = new ConfigManager();
}
return configManager;
}
public static String getString(String key) { // 根据key来获取value
return properties.getProperty(key);
}
}
获取配置文件的输入流写法有两种:
//方法一:
String fileName="database.properties"; //配置文件的文件名
InputStream in = ConfigManager.class.getClassLoader().getResourceAsStream(fileName);
//方法二:
String filePath ="resource\\database.properties"; //配置文件的路径名
InputStream in = new BufferedInputStream (new FileInputStream(filePath));
//再加载读取
params.load(is);
补充:
ConfigManager.class 是获得当前对象所属的class对象;
.getClassLoader() 是取得该Class对象的类装载器;
getResourceAsStream(“database.properties”) 调用类加载器的方法加载资源,返回的是字节流 ,使用Properties类是为了可以从.properties属性文件对应的文件输入流中,加载属性列表到Properties类对象,然后通过getProperty方法用指定的键在此属性列表中搜索属性。
2.1.2、饿汉模式
在类加载的时候就立即创建对象。
public class ConfigManager {
private static ConfigManager configManager = new ConfigManager(); // 私有的静态属性
private static Properties properties;
private ConfigManager() { // 私有的构造器
properties = new Properties();
InputStream in = ConfigManager.class.getClassLoader()
.getResourceAsStream("database.properties");
if (in == null){
throw new RuntimeException("找不到数据库参数配置文件!");
}
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
}
public static ConfigManager getInstance() { // 提供对外开放的进行方法
return configManager;
}
public static String getString(String key) { // 根据key来获取value
return properties.getProperty(key);
}
}
2.1.3、静态内部类的方式(结合懒汉和饿汉)
public class ConfigManager {
private static Properties properties; // 私有的静态属性
private static class Singleton { // 私有的静态内部类(饿汉模式)
private static ConfigManager configManager = new ConfigManager();
}
private ConfigManager(){ // 私有(private)的构造器,提供一个唯一的ConfigManager对象
properties = new Properties();
InputStream in = ConfigManager.class.getClassLoader()//也可用BaseDao.class获取加载流
.getResourceAsStream("database.properties"); //固定写法
if (in == null){
throw new RuntimeException("找不到数据库参数配置文件!");
}
try {
properties.load(in); //load()方法实现配置文件的读取
} catch (IOException e) {
e.printStackTrace();
}
}
public static ConfigManager getInstance() { // 提供对外开放(public)的进行方法
return Singleton.configManager; // 相当于懒汉
}
public static String getString(String key) { // 根据key来获取value
return properties.getProperty(key);
}
}
简单的单例模式
1、饿汉:下面是一个静态的单例模式,类创建时就生成这样一个永久实例。
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {
//私有的默认构造子
private Singleton1() {}
//已经自行实例化
private static final Singleton1 single = new Singleton1();
//静态工厂方法
public static Singleton1 getInstance() {
return single;
}
}
2、懒汉:下面是一个动态的单例模式,需要时才生成一个实例。
//懒汉式单例类.在第一次调用的时候实例化
public class Singleton2 {
//私有的默认构造子
private Singleton2() {}
//注意,这里没有final
private static Singleton2 single=null;
//静态工厂方法
public static synchronized Singleton2 getInstance() {
if (single == null) {
single = new Singleton2();
}
return single;
}
}
3、在单例中我们只是要在获取实例的时候保持同步,只有在第一次的时候才会生成实例,那么反之,在获取实例的时候如果不是第一次获取,直接返回实例即可,如果是第一次获取,那么就要生成实例再返回实例对象。
public class Singleton3{
private static Singleton3 single=null;
private Singleton3(){
//do something
}
public static Singleton3 getInstance(){
if(single==null){
synchronized(Singleton3.class){
if(single==null) {
single=new Singleton3();
}
}
}
return single;
}
}内容下方到if内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步,创建了以后就没必要了
4、使用静态内部类:看起来像是前面两种方法的结合体。既实现了线程安全,又避免了同步带来的性能影响。但实际上还是要根据实际情况来确定到底使用哪种方法。
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
5、使用枚举:Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,所以枚举来实现单例是再合适不过的了。
public enum Singleton {
INSTANCE;
private Singleton() {}
}
工厂模式
工厂方法模式有四个要素:
1、工厂接口。工厂接口是工厂方法模式的核心,与调用者直接交互用来提供产品。在实际编程中,有时候也会使用一个抽象类来作为与调用者交互的接口,其本质上是一样的。
2、工厂实现。在编程中,工厂实现决定如何实例化产品,是实现扩展的途径,需要有多少种产品,就需要有多少个具体的工厂实现。
3、产品接口。产品接口的主要目的是定义产品的规范,所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性。同样,产品接口也可以用抽象类来代替,但要注意最好不要违反里氏替换原则。
4、产品实现。实现产品接口的具体类,决定了产品在客户端中的具体行为。
适用场景:
不管是简单工厂模式,工厂方法模式还是抽象工厂模式,他们具有类似的特性,所以他们的适用场景也是类似的。
首先,作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过new就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
其次,工厂模式是一种典型的解耦模式,迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。
再次,由于工厂模式是依靠抽象架构的,它把实例化产品的任务交由实现类完成,扩展性比较好。也就是说,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。
典型应用:
要说明工厂模式的优点,可能没有比组装汽车更合适的例子了。场景是这样的:汽车由发动机、轮、底盘组成,现在需要组装一辆车交给调用者。假如不使用工厂模式,代码如下:
class Engine {
public void getStyle(){
System.out.println("这是汽车的发动机");
}
}
class Underpan {
public void getStyle(){
System.out.println("这是汽车的底盘");
}
}
class Wheel {
public void getStyle(){
System.out.println("这是汽车的轮胎");
}
}
public class Client {
public static void main(String[] args) {
Engine engine = new Engine();
Underpan underpan = new Underpan();
Wheel wheel = new Wheel();
ICar car = new Car(underpan, wheel, engine);
car.show();
}
}
可以看到,调用者为了组装汽车还需要另外实例化发动机、底盘和轮胎,而这些汽车的组件是与调用者无关的,严重违反了迪米特法则,耦合度太高。并且非常不利于扩展。另外,本例中发动机、底盘和轮胎还是比较具体的,在实际应用中,可能这些产品的组件也都是抽象的,调用者根本不知道怎样组装产品。假如使用工厂方法的话,整个架构就显得清晰了许多。
interface IFactory {
public ICar createCar();
}
class Factory implements IFactory {
public ICar createCar() {
Engine engine = new Engine();
Underpan underpan = new Underpan();
Wheel wheel = new Wheel();
ICar car = new Car(underpan, wheel, engine);
return car;
}
}
public class Client {
public static void main(String[] args) {
IFactory factory = new Factory();
ICar car = factory.createCar();
car.show();
}
}
使用工厂方法后,调用端的耦合度大大降低了。并且对于工厂来说,是可以扩展的,以后如果想组装其他的汽车,只需要再增加一个工厂类的实现就可以。无论是灵活性还是稳定性都得到了极大的提高。
2.2.数据库的连接及关闭示例
// 获取连接的通用方法(基类层)
public Connection getConnection() {
// 1、加载驱动
try { //通过ConfigManager类获取configManager对象,调用getProperity()方法
driver = ConfigManager.getInstance().getProperity("driver");
//diver=com.mysql.jdbc.Driver
url = ConfigManager.getInstance().getProperity("url");
//url="jdbc:mysql://localhost:3306/news(库名)"
username = ConfigManager.getInstance().getProperity("username");
password = ConfigManager.getInstance().getProperity("password");
Class.forName(driver);
// 2、获取连接
connection = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return connection; //将获取的连接对象返回
}
// 释放资源:后创建使用的先关闭
public void closeAll(Connection connection, PreparedStatement pstmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2.3.数据的增删改方法示例
// 增删改的通用方法
public int executeUpdate(String sql, Object[] params) {
int updateRows = 0; //影响行数
connection = getConnection(); //调用getConnection()方法获取连接
try {
pstmt = connection.prepareStatement(sql); //prepareStatement对sql语句进行预编译
if (params != null) {
for (int i = 0; i < params.length; i++) { //遍历传输参数集合,填充占位符
pstmt.setObject(i + 1, params[i]);
}
}
updateRows = pstmt.executeUpdate(); //调用executeUpdate()方法获得影响行数
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll(connection, pstmt, null);
}
return updateRows;
}
2.4.数据的查询方法示例
// 查询的通用方法
public ResultSet executeSQL(String sql, Object... params) { // ...可变参数
connection = getConnection();
try {
pstmt = connection.prepareStatement(sql);
if (params != null) {
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]); //遍历传输参数集合,填充占位符
}
}
rs = pstmt.executeQuery(); //调用查询方法executeQuery()
} catch (SQLException e) {
e.printStackTrace();
}
return rs; //将查询结果集进行返回
}
//或者是如下写法:
public ResultSet executeSQL(String sql,Object[] params) {
if(getConnection()){
try {
pstmt=connection.prepareStatement(sql);
for(int i = 0; i < params.length; i++ ){ //填充占位符
pstmt.setObject(i + 1, params[i]);
}
rs=pstmt.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
}
return rs;
}
查询所有的用户的详细信息(实现类层):
public List<NewsUsers> getAllUsers() {
String sql = "select * from news_user";
ResultSet rs = executeSQL(sql); //调用查询方法
List<NewsUsers> userList = new ArrayList<NewsUsers>(); //创建集合对象存放user信息
NewsUsers user = null; //先创建不new
try {
while (rs.next()) { //处理结果
user = new NewsUsers(); //每次都要new出一个user对象接收查询到的用户信息
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
userList.add(user); //将用户信息作为一个整体对象存放到集合中
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll(connection, pstmt, rs);
}
return userList;
}
简单工厂模式
1.介绍:
简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
2.延伸:
试想一下,当我们在coding的时候,在A类里面只要NEW了一个B类的对象,那么A类就会从某种程度上依赖B类。如果在后期需求发生变化或者是维护的时候,需要修改B类的时候,我们就需要打开源代码修改所有与这个类有关的类了,做过重构的朋友都知道,这样的事情虽然无法完全避免,但确实是一件让人心碎的事情。
3.简单工厂UML类图:
4.代码演示:
抽象产品类代码:
namespace CNBlogs.DesignPattern.Common
{
// 抽象产品类: 汽车
public interface ICar
{
void GetCar();
}
}
具体产品类代码:
namespace CNBlogs.DesignPattern.Common
{
public enum CarType
{
SportCarType = 0,
JeepCarType = 1,
HatchbackCarType = 2
}
// 具体产品类: 跑车
public class SportCar : ICar
{
public void GetCar()
{
Console.WriteLine("场务把跑车交给范·迪塞尔");
}
}
// 具体产品类: 越野车
public class JeepCar : ICar
{
public void GetCar()
{
Console.WriteLine("场务把越野车交给范·迪塞尔");
}
}
/// <summary>
/// 具体产品类: 两箱车
/// </summary>
public class HatchbackCar : ICar
{
public void GetCar()
{
Console.WriteLine("场务把两箱车交给范·迪塞尔");
}
}
}
简单工厂核心代码:
namespace CNBlogs.DesignPattern.Common
{
public class Factory
{
public ICar GetCar(CarType carType)
{
switch (carType)
{
case CarType.SportCarType:
return new SportCar();
case CarType.JeepCarType:
return new JeepCar();
case CarType.HatchbackCarType:
return new HatchbackCar();
default:
throw new Exception("爱上一匹野马,可我的家里没有草原. 你走吧!");
}
}
}
}
客户端调用代码:
namespace CNBlogs.DesignPattern
{
using System;
using CNBlogs.DesignPattern.Common;
class Program
{
static void Main(string[] args)
{
ICar car;
try
{
Factory factory = new Factory();
Console.WriteLine("范·迪塞尔下一场戏开跑车。");
car = factory.GetCar(CarType.SportCarType);
car.GetCar();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
简单工厂的简单案例就这么多,真正在项目实战的话可能还有需要改进和扩展的地方。因需求而定吧。
6.简单工厂的优点/缺点:
- 优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
- 缺点:很明显工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则
v工厂方法模式
1.介绍:
工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
2.定义:
工厂方法模式是简单工厂模式的衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭 原则’,实现了可扩展。其次更复杂的层次结构,可以应用于产品结果复杂的场合。
3.延伸:
在上面简单工厂的引入中,我们将实例化具体对象的工作全部交给了专门负责创建对象的工厂类(场务)中,这样就可以在我们得到导演的命令后创建对应的车(产品)类了。但是剧组的导演是性情比较古怪的,可能指令也是无限变化的。这样就有了新的问题,一旦导演发出的指令时我们没有预料到的,就必须得修改源代码。这也不是很合理的。工厂方法就是为了解决这类问题的。
4.模拟场景:
还是上面范·迪塞尔要去参加五环首届跑车拉力赛的场景。因为要拍摄《速度与激情8》,导演组车的种类增多了,阵容也更加豪华了,加上导演古怪的性格可能每一场戏绝对需要试驾几十种车。如果车库没有的车(具体产品类)可以由场务(具体工厂类)直接去4S店取,这样没增加一种车(具体产品类)就要对应的有一个场务(具体工厂类),他们互相之间有着各自的职责,互不影响,这样可扩展性就变强了。
5.工厂方法UML类图: (UML图是我用windows自带的paint手工画的,所以可能不是很专业
6.代码演示:
抽象工厂代码:
namespace CNBlogs.DesignPattern.Common
{
public interface IFactory
{
ICar CreateCar();
}
}
抽象产品代码:
namespace CNBlogs.DesignPattern.Common
{
public interface ICar
{
void GetCar();
}
}
具体工厂代码:
namespace CNBlogs.DesignPattern.Common
{
/// <summary>
/// 具体工厂类: 用于创建跑车类
/// </summary>
public class SportFactory : IFactory
{
public ICar CreateCar()
{
return new SportCar();
}
}
/// <summary>
/// 具体工厂类: 用于创建越野车类
/// </summary>
public class JeepFactory : IFactory
{
public ICar CreateCar()
{
return new JeepCar();
}
}
/// <summary>
/// 具体工厂类: 用于创建两厢车类
/// </summary>
public class HatchbackFactory : IFactory
{
public ICar CreateCar()
{
return new HatchbackCar();
}
}
}
具体产品代码:
namespace CNBlogs.DesignPattern.Common
{
/// <summary>
/// 具体产品类: 跑车
/// </summary>
public class SportCar : ICar
{
public void GetCar()
{
Console.WriteLine("场务把跑车交给范·迪塞尔");
}
}
/// <summary>
/// 具体产品类: 越野车
/// </summary>
public class JeepCar : ICar
{
public void GetCar()
{
Console.WriteLine("场务把越野车交给范·迪塞尔");
}
}
/// <summary>
/// 具体产品类: 两箱车
/// </summary>
public class HatchbackCar : ICar
{
public void GetCar()
{
Console.WriteLine("场务把两箱车交给范·迪塞尔");
}
}
}
客户端代码:
//------------------------------------------------------------------------------
// <copyright file="Program.cs" company="CNBlogs Corporation">
// Copyright (C) 2015-2016 All Rights Reserved
// 原博文地址: http://www.cnblogs.com/toutou/
// 作 者: 请叫我头头哥
// </copyright>
//------------------------------------------------------------------------------
namespace CNBlogs.DesignPattern
{
using System.IO;
using System.Configuration;
using System.Reflection;
using CNBlogs.DesignPattern.Common;
class Program
{
static void Main(string[] args)
{
// 工厂类的类名写在配置文件中可以方便以后修改