Java Web应用实践

《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获取路径的方法:

  1. getServletPath():获取能与“url-pattern”中匹配的路径,注意是完全匹配的部分,*的部分不包括。
  2. getPageInfo():与getServletPath()获取的路径互补,能够得到的是“url-pattern”中*d的路径部分
  3. getContextPath():获取项目的根路径
  4. getRequestURI:获取根路径到地址结尾
  5. getRequestURL:获取请求的地址链接(浏览器中输入的地址)
  6. getServletContext().getRealPath(“/”):获取“/”在机器中的实际地址
  7. getScheme():获取的是使用的协议(http 或https)
  8. getProtocol():获取的是协议的名称(HTTP/1.11)
  9. getServerName():获取的是域名(xxx.com)
  10. 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类图:

C# 简单工厂模式

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手工画的,所以可能不是很专业

C# 工厂方法模式

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)
        {
            // 工厂类的类名写在配置文件中可以方便以后修改
            string factoryType = ConfigurationManager.AppSettings["FactoryType"];

            // 这里把DLL配置在数据库是因为以后数据可能发生改变
            // 比如说现在的数据是从sql server取的,以后需要从oracle取的话只需要添加一个访问oracle数据库的工程就行了
            string dllName = ConfigurationManager.AppSettings["DllName"];

            // 利用.NET提供的反射可以根据类名来创建它的实例,非常方便
            var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
            string codeBase = currentAssembly.CodeBase.ToLower().Replace(currentAssembly.ManifestModule.Name.ToLower(), string.Empty);
            IFactory factory = Assembly.LoadFrom(Path.Combine(codeBase, dllName)).CreateInstance(factoryType) as IFactory;
            ICar car = factory.CreateCar();
            car.GetCar();
        }
    }
}

7.工厂方法的优点/缺点:

  • 优点:
    • 子类提供挂钩。基类为工厂方法提供缺省实现,子类可以重写新的实现,也可以继承父类的实现。-- 加一层间接性,增加了灵活性
    • 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,只需关心产品的接口,只要接口保持不变,系统中的上层模块就不会发生变化。
    • 典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不需要关心,符合迪米特法则,符合依赖倒置原则,符合里氏替换原则。
    • 多态性:客户代码可以做到与特定应用无关,适用于任何实体类。
  • 缺点:需要Creator和相应的子类作为factory method的载体,如果应用模型确实需要creator和子类存在,则很好;否则的话,需要增加一个类层次。(不过说这个缺点好像有点吹毛求疵了)

回到顶部

v抽象工厂模式

1.介绍:

抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据里氏替换原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。

2.定义:

为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

3.模拟场景:

我们还是继续范·迪塞尔的例子,往往这些大牌生活中经常参加一些活动,或是商务活动或是公益活动。不管参加什么活动,加上老范(范·迪塞尔名字太长,以下文中简称老范)的知名度,他的车肯定不少,可能光跑车或者光越野车就有多辆。比如说有跑车(多辆,跑车系列的具体产品)、越野车(多辆,越野车系列的具体产品)、两箱车(多辆,两箱车系列的具体产品)。可能很多大牌明星都是如此的。假设老范家里,某一个车库(具体工厂)只存放某一系列的车(比如说跑车车库只存放跑车一系列具体的产品),每次要某一辆跑车的时候肯定要从这个跑车车库里开出来。用了OO(Object Oriented,面向对象)的思想去理解,所有的车库(具体工厂)都是车库类(抽象工厂)的某一个,而每一辆车又包括具体的开车时候所背的包(某一具体产品。包是也是放在车库里的,不同的车搭配不同的包,我们把车和车对应的背包称作出去参加活动的装备),这些具体的包其实也都是背包(抽象产品),具体的车其实也都是车(另一个抽象产品)。

4.场景分析:

上面的场景可能有点稀里糊涂的,但是用OO的思想结合前面的简单工厂和工厂方法的思路去理解的话,也好理解。

下面让我们来捋一捋这个思路:

  • 抽象工厂:虚拟的车库,只是所有车库的一个概念。在程序中可能是一个借口或者抽象类,对其他车库的规范,开车和取包。
  • 具体工厂:具体存在的车库,用来存放车和车对应的背包。在程序中继承抽象工厂,实现抽象工厂中的方法,可以有具体的产品。
  • 抽象产品:虚拟的装备(车和对应的背包),也只是所有装备的一个概念。在程序中可能是多个接口或者多个抽象类,对具体的装备起到规范。
  • 具体产品:活动参加的具体装备,它指的是组成装备的某一辆车或者背包。它继承自某一个抽象产品。

5.抽象工厂UML类图: (UML图是我用windows自带的paint手工画的,所以可能不是很专业)

img

6.代码演示:

抽象工厂代码:

namespace CNBlogs.DesignPattern.Common
{
    /// <summary>
    /// 抽象工厂类
    /// </summary>
    public abstract class AbstractEquipment
    {
        /// <summary>
        /// 抽象方法: 创建一辆车
        /// </summary>
        /// <returns></returns>
        public abstract AbstractCar CreateCar();

        /// <summary>
        /// 抽象方法: 创建背包
        /// </summary>
        /// <returns></returns>
        public abstract AbstractBackpack CreateBackpack();
    }
}

抽象产品代码:

namespace CNBlogs.DesignPattern.Common
{
    /// <summary>
    /// 抽象产品: 车抽象类
    /// </summary>
    public abstract class AbstractCar
    {
        /// <summary>
        /// 车的类型属性
        /// </summary>
        public abstract string Type
        {
            get;
        }

        /// <summary>
        /// 车的颜色属性
        /// </summary>
        public abstract string Color
        {
            get;
        }
    }

    /// <summary>
    /// 抽象产品: 背包抽象类
    /// </summary>
    public abstract class AbstractBackpack
    {
        /// <summary>
        /// 包的类型属性
        /// </summary>
        public abstract string Type
        {
            get;
        }

        /// <summary>
        /// 包的颜色属性
        /// </summary>
        public abstract string Color
        {
            get;
        }
    }
}

具体工厂代码:

namespace CNBlogs.DesignPattern.Common
{
    /// <summary>
    /// 运动装备
    /// </summary>
    public class SportEquipment : AbstractEquipment
    {
        public override AbstractCar CreateCar()
        {
            return new SportCar();
        }

        public override AbstractBackpack CreateBackpack()
        {
            return new SportBackpack();
        }
    }

    /// <summary>
    /// 越野装备  这里就不添加了,同运动装备一个原理,demo里只演示一个,实际项目中可以按需添加
    /// </summary>
    //public class JeepEquipment : AbstractEquipment
    //{
    //    public override AbstractCar CreateCar()
    //    {
    //        return new JeeptCar();
    //    }

    //    public override AbstractBackpack CreateBackpack()
    //    {
    //        return new JeepBackpack();
    //    }
    //}
}

具体产品代码:

namespace CNBlogs.DesignPattern.Common
{
    /// <summary>
    /// 跑车
    /// </summary>
    public class SportCar : AbstractCar
    {
        private string type = "Sport";
        private string color = "Red";

        /// <summary>
        /// 重写基类的Type属性
        /// </summary>
        public override string Type
        {
            get
            {
                return type;
            }
        }

        /// <summary>
        /// 重写基类的Color属性
        /// </summary>
        public override string Color
        {
            get
            {
                return color;
            }
        }
    }

    /// <summary>
    /// 运动背包
    /// </summary>
    public class SportBackpack : AbstractBackpack
    {
        private string type = "Sport";
        private string color = "Red";

        /// <summary>
        /// 重写基类的Type属性
        /// </summary>
        public override string Type
        {
            get
            {
                return type;
            }
        }

        /// <summary>
        /// 重写基类的Color属性
        /// </summary>
        public override string Color
        {
            get
            {
                return color;
            }
        }
    }
}
//具体产品可以有很多很多, 至于越野类的具体产品这里就不列出来了。

创建装备代码:

namespace CNBlogs.DesignPattern.Common
{
    public class CreateEquipment
    {
        private AbstractCar fanCar;
        private AbstractBackpack fanBackpack;
        public CreateEquipment(AbstractEquipment equipment)
        {
            fanCar = equipment.CreateCar();
            fanBackpack = equipment.CreateBackpack();
        }

        public void ReadyEquipment()
        {
            Console.WriteLine(string.Format("老范背着{0}色{1}包开着{2}色{3}车。", 
                fanBackpack.Color, 
                fanBackpack.Type,
                fanCar.Color,
                fanCar.Type
                ));
        }
    }
}

客户端代码:

namespace CNBlogs.DesignPattern
{
    using System;
    using System.Configuration;
    using System.Reflection;

    using CNBlogs.DesignPattern.Common;

    class Program
    {
        static void Main(string[] args)
        {
            // ***具体app.config配置如下*** //
            //<add key="assemblyName" value="CNBlogs.DesignPattern.Common"/>
            //<add key="nameSpaceName" value="CNBlogs.DesignPattern.Common"/>
            //<add key="typename" value="SportEquipment"/>
            // 创建一个工厂类的实例
            string assemblyName = ConfigurationManager.AppSettings["assemblyName"];
            string fullTypeName = string.Concat(ConfigurationManager.AppSettings["nameSpaceName"], ".", ConfigurationManager.AppSettings["typename"]);
            AbstractEquipment factory = (AbstractEquipment)Assembly.Load(assemblyName).CreateInstance(fullTypeName);
            CreateEquipment equipment = new CreateEquipment(factory);
            equipment.ReadyEquipment();
            Console.Read();
        }
    }
}

抽象工厂模式符合了六大原则中的开闭原则、里氏代换原则、依赖倒转原则等等

7.抽象工厂的优点/缺点:

  • 优点:
    • 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。
    • 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
    • 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
  • 缺点:增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。(不过说这个缺点好像有点吹毛求疵了)

3、数据源与JNDI资源实现JSP数据库连接池

​ **名词解释:**JNDI的全称是java命名与目录接口(Java Naming and Directory Interface),是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。我们可以把JNDI简单地理解为是一种将对象和名字绑定的技术,即指定一个资源名称,将该名称与某一资源或服务相关联,当需要访问其他组件和资源时,就需要使用JNDI服务进行定位,应用程序可以通过名字获取对应的对象或服务。

​ 驱动包添加到Tomcat的lib目录 。

3.1、context.xml文件设置

数据源:Tomcat根目录\conf\context.xml文件(没有直接创建也可以)或者在JSP项目WebRoot目录下的META-INF目录中创建一个context.xml文件,添加Context节点,配置在标签,如下:

<Context>
    <Resource name="jdbc/news" auth="Container" type="javax.sql.DataSource" 
    	 maxActive="100" maxIdle="30" maxWait="10000" username="root" password="123456"
         driverClassName="com.mysql.jdbc.Driver" 
         validationQuery="select 1 from dual"
         url="jdbc:mysql://localhost:3306/news?useUnicode=true&amp;chararterEncoding=utf-8" />
</Context>

**注: name:**指定Resource的JNDI名字。

​ **auth:**可以写成Container和Application。Container表示由容器创建Resource,Application表示由Web应用创建和管理Resource。

​ **type:**指定Resource所属的Java类名。

maxActive:指定数据库连接池中处于活动状态的数据库连接的最大数目,0表示没有限制,默认8。

maxIdle:指定数据库连接池中处于空闲状态的数据库连接的最大数目,取值为0表示不受限制。

minIdle:指定数据库连接池中处于空闲状态的数据库连接的最小数目。

maxWait:指定数据库连接池中数据库连接空闲状态的最长时间(单位:毫秒),超出这时间会抛出异常,取值-1表示可以无期限等待。

​ **username:**指定连接数据库的用户名。

​ **password:**指定连接数据库的口令。

​ **driverClassName:**指定连接数据库的JDBC驱动程序。

​ **url:**指定连接数据库的URL。

​ **validationQuery:**验证连接是否成功,SQL SELECT至少要返回一行

3.2、web.xml文件的配置

在Web应用程序的WEB-INF/web.xml文件中的节点下添加元素,内容如下:

 <web-app>
     ...
     <resource-ref>
         <description>news DataSource</description>
         <res-ref-name>jdbc/test</res-ref-name>
         <res-type>javax.sql.DataSource</res-type>
         <res-auth>Container</res-auth>
     </resource-ref>
 </web-app>

**注: description:**对所引用资源的说明。

​ **res-ref-name:**指定所引用资源的JNDI名字,与元素中的name属性对应。

​ **res-type:**指定所引用资源的类名字,与元素中的type属性对应。

​ **res-auth:**指定管理所引用资源的Manager,与元素中的auth属性对应。

3.3、连接池获取数据源

public Connection getConnection2(){
    try{
        Context cxt=new InitialContext();    //初始化上下文
        //获取与逻辑名相关联的数据源对象
        DataSource ds=(DataSource)cxt.lookup("java:comp/env/jdbc/news");   
        conn=ds.getConnection();
    }catch(NamingException e){
        e.printStackTrance();
    }catch(SQLException e){
        e.printStackTrance();
    }
    return conn;
}

​ 获取数据源时,java.naming.Context提供了查找JNDI Resource的接口,通过该对象的lookup()方法,就可以找到之前创建好的数据源,lookup()语法如下:

lookup("java:comp/env/jdbc/数据源名称")

​ 注:如果出现错误提示:“Name jdbc is not bound in this Context“,是因为根据JNDI名称查找数据源的时候出现错误,找不到。 原因可能有:1、在使用lookup()方法查询数据时,数据源名称和配置文件中的不一样;2、在使用lookup()方法查询数据时,没有使用前缀java:comp/env/jdbc/+数据源名称。

4、JavaBean与组件开发

把一个拥有对属性进行set和get方法的类,我们就可以称之为JavaBean。实际上JavaBean就是一个java类,它能封装数据也能封装业务,并且这个类可以被实例化,在这个java类中就默认形成了一种规则——对属性进行设置和获得。而反之将说ava类就是一个JavaBean,这种说法是错误的,因为一个java类中不一定有对属性的设置和获得的方法(也就是不一定有set和get方法)。从功能上分为两类:封装数据、封装业务。

JavaBean要满足如下要求:

​ 1.是一个共有的java类,并具有一个公开的无参构造函数 ;

​ 2.属性必须私有化,类必须公开 ;

​ 3.具有公有的getter和setter方法。

4.1、jsp:useBean标签

​ 用于在指定的域范围内查找指定名称的JavaBean对象,如果存在则直接返回该JavaBean对象的引用,如果不存在则实例化一个新的JavaBean对象,并将它以指定的名称存储到指定的域范围。

<jsp:useBean id="name" class="classname" scope="page|request|session|application"/>
  	id: 代表jsp页面中的实例对象 通过这个对象引用类中的成员,如,id="wq", wq.成员();
  	class: 代表JavaBean类,如: class="com.Test",引用com包中的Test类
  	scope:表示javabean的作用范围,默认为page范围,还有request、session、application

4.2、jsp:setProperty标签

​ 用于设置和访问JavaBean对象的属性。

<jsp:setProperty name="beanName" property="propertyName" value="value"/>
	name属性用于指定JavaBean对象的名称。 
	property属性用于指定JavaBean实例对象的属性名。
	value属性用于指定JavaBean对象的某个属性的值,value的值可以是字符串,也可以是表达式。为字符串时,该值会自动转化为JavaBean属性相应的类型,如果value的值是一个表达式,那么该表达式的计算结果必须与所要设置的JavaBean属性的类型一致。 

4.3、jsp:getProperty标签

​ 用于读取JavaBean对象的属性,也就是调用JavaBean对象的getter方法,然后将读取的属性值转换成字符串后插入进输出的响应正文中。

<jsp:getProperty name="beanInstanceName" property="PropertyName" />
	name属性用于指定JavaBean实例对象的名称,其值应与<jsp:useBean>标签的id属性值相同。 
	property属性用于指定JavaBean实例对象的属性名。

​ 注:如果一个JavaBean实例对象的某个属性的值为null,那么使用jsp:getProperty标签输出该属性的结果将是一个内容为“null”的字符串。

5、jsp页面引入其他jsp页面的方法

5.1、jstl import

 <c:import url="inlayingJsp.jsp"></c:import> 

5.2、jsp include指令实现静态包含

​ 静态包含指的是,将一个外部文件嵌入到当前JSP文件中,同时解析这个页面的JSP语句,它会把目标页面的其他编译指令也包含进来。 包含页面在编译时将完全包含被包含页面的代码。需要指出的是,静态导入还会将被包含页面的编译指令也包含进来,如果两个页面的编译指令有冲突,那么页面就会出错。

<%@ include file="inlayingJsp.jsp" %>

5.3、jsp include标签实现动态包含

​ 动态包含是不会导入include页面的编译指令的,而是仅仅将被导入页面的body内容插入本页面。

<jsp:include page="inlayingJsp.jsp" flush="true"/>

​ 注:flush属性用语指定输出缓存是否转移到被导入文件中。如果指定为true,则包含在被导入文件中,如果指定为false,则包含在原文件中,对于JSP1.1旧版本,只能设置为false。 flush默认为 false

<jsp:include page="{relativeURL|<%=expression%>}" flush="true"/>
或者
<jsp:include page="{relativeURL|<%=expression%>}" flush="true">
  <jsp:param name="parameterName" value="parameterValue"/>  //可在被导入页面中加入额外的请求参数
</jsp:include>

5.3.1、动态包含和静态包含的区别:

静态包含 动态包含
语法 <%@ include file=“url” %> <jsp:include page=“url” />
执行原理 先将页面包含,后执行页面代码,即 将一个页面的代码复制到另一个页面中 先执行页面代码,后将页面包含,即 将一个页面的运行结果包含到另一个页面中
变化是否重新编译 被包含的页面内容发生变化时,包含页面将重新编译 被包含的页面内容发生变化时,包含页面不会重新编译
说明 当包含文件为静态文件时效果二者等同
检查变化 不会检查所含文件的变化 可以检查所含文件中的变化,并且可以带参数
执行时机 在翻译阶段执行: JSP---->java文件阶段 在执行class文件阶段,即请求时处理阶段执行, 而不是此阶段结束后
文件数量 将两个jsp文件二合一,作为一个整体编译,生成一个以包含页面命名的servlet和class文件 各个jsp文件分别转换,分别编译,最终编程成多个java文件 ;如:两个jsp文件分别生成自己的servlet和class文件
执行速度 较快
变量问题 相当于将包含文件内容直接复制到主体文件中,如果出现相同的变量,就会出现覆盖等问题,导致文件出错。 相当于调用不同的jsp,变量所在的空间不同,自然不会出现覆盖等现象

​ 注:1、静态包含是将被导入页面的代码完全融入,两个页面融合成一个整体Servlet;而动态包含则在Servlet中使用include方法来引入被导入页面的内容。 2、静态包含时被导入页面的编译指令会起作用;而动态包含时被导入页面的编译指令则失去作用,只是插入被导入页面的body内容。

5.4、jsp:forward标签:跳转页面

​ 从一个JSP文件向另一个文件传递一个包含用户请求的request对象。<jsp:forward>标签以后的代码,将不能执行。

<jsp:forward page={"relativeURL" | "<%= expression %>"} /> 
or 
<jsp:forward page={"relativeURL" | "<%= expression %>"} >        
	<jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" /> 
    			//可在跳转页面中加入额外的请求参数
</jsp:forward> 

6、三层架构与MVC模型

6.1、model1模式

​ 就是纯Jsp,就是说页面显示,控制分发,业务逻辑,数据访 问全部通过Jsp去实现。 JSP页面中结合业务逻辑、服务端处理过程和HTML等,这样就在JSP页面中同时实现了业务逻辑和流程控制。从而快速开发。但是由于业务逻辑和表示逻辑混合在JSP页面中没有进行抽象和分离,JSP负载太大。所以非常不利于应用系统业务的重用和改动,不便于维护

优点:架构简单,比较适合小型项目开发

缺点:jsp的职责不单一,职责过重,不便于维护

Model1模式

6.2、model2模式

​ Model II模式(可以理解为简单的MVC模式)是采用JSP+Servlet+JavaBean方式开发,将数据显示、流程控制和业务逻辑处理进行分离,代码之间分层控制,减低了代码间的耦合,一个模型可以对应多个视图。主要通过两部分去实现,即Jsp与Servlet。Jsp负责页面 显示,Servlet负责控制分发,业务逻辑以及数据访问。

优点:职责清楚,较适合于大型项目架构,提高开发效率

缺点:不适合于小型项目开发

Model2模式

6.3、三层架构

​ 由界面层(UI)业务逻辑层(BLL)和数据访问层(DAL)构成;

​ 1、数据访问层:主要是对原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也就是说,是对数据的操作,而不是数据库,具体为业务逻辑层或表示层提供数据服务。

​ 2、业务逻辑层:主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理,但不直接对数据库访问,如果说数据层是积木,那逻辑层就是对这些积木的搭建。

​ 3、界面层:主要对用户的请求接受,以及数据的返回,为客户端提供应用程序的访问。主要表示WEB方式,也可以表示成WINFORM方式,WEB方式也可以表现成:aspx,如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。

​ 业务逻辑层访问数据访问层,数据访问层执行对数据库的访问,并将请求结果通知业务逻辑层,业务逻辑层再将结果通知表示层。业务逻辑层不直接对数据库访问。上一层依赖其下一层、依赖关系不跨层。

6.3.1、分层的优缺点

​ 优势包括松耦合、重用性高、生命周期成本低、可维护性高 。分层是为了能够将数据,业务,表示进行有条理的分解,再加以组合的开发方式。目标是为了能够进行“高内聚,低耦合”,让各个层专注各自的领域目标,常规情况下DAO就是对数据进行存储访问操作,而表示层则是进行数据的展示,表现数据给用户、接受用户数据的。分层虽然有以上优点,但是增加的程序的复杂度,把简单直接的操作,进行分离,归类,为后期进行维护,修改降低复杂程度 。

6.4、MVC模型

​ 由模型层(M)界面层(View)和控制层(Controller)构成。

  • M 即Model(模型层),可以简单理解就是数据层,用于提供数据。在项目中,(简单理解)一般把数据访问和操作,比如将对象关系映射这样的代码作为Model层,也就是对数据库的操作这一些列的代码作为Model层。比如代码中我们会写DAO和DTO类型的代码,那这个DAO和DTO我们可以理解为是属于Model层的代码;

  • V 即View(视图层),就是UI界面,用于跟用户进行交互。一般所有的JSP、Html等页面就是View层;

  • C 即Controller(控制器),主要用于捕获用户请求转发给模型层,经处理后把结果返回到界面展现的中间层。

    img

    ​ 实际开发中往往会加一层Service层用于存放与业务逻辑相关的操作,Service层中的接口和类对Dao类的方法进行了封装和调用,而Dao层进行数据操作。

6.4.1、MVC框架模式的优点

1、开发人员可以只关注整个结构中的其中某一层;
     2、可以很容易的用新的实现来替换原有层次的实现;
     3、可以降低层与层之间的依赖;
     4、有利于标准化;
     5、利于各层逻辑的复用。

6.4.2、MVC框架模式的缺点

(1) 增加了系统结构和实现的复杂性。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。

(2) 视图与控制器间的过于紧密的连接。视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。

​ (3)视图对模型数据的低效率访问。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。

6.5、三层架构与MVC模型的对比

​ 如果硬要给他们对应的话,那么三层架构中的UI对应MVC中的view(jsp),都是用于显示以及获取界面的数据;三层架构中的BLL层和DAL层对应MVC中的Model(javabean)层都是用于处理上层传递来的数据以及从数据库获取数据的;MVC中的Controller(Servlet)最多算是三层架构中的UI的一部分,也就我们常说的是Servlet。

第三章、第三方控件

1、ckeditor编辑的使用方法

1、下载安装Ckeditor,并将其整合到项目中

​ Ckeditor叫做富文本编辑器,可实现在录入时更改字体样式、大小、颜色并具备插入图片的功能。一般的TextBox无法满足此需求。可进入官方网站 http://ckeditor.com/,点击Download进入下载页面。

2、将下载的zip包解压后放入WebRoot下

3、在页面中引入CKEditor

<script type="text/javascript" src="<%=request.getContextPath()%>/ckeditor/ckeditor.js">
</script>

4、在要使用的文本域中添加class属性class=“ckeditor”。

<textarea rows="5" cols="6" class="ckeditor" id="myArea"></textarea>

或是要在页面中使用CKEditor

<!--先在需要使用编辑器的地方插入textarea标签 -->
描述:<textarea name="description" id="description"/></textarea>
<!--然后将相应的控件替换成编辑器代码 -->
<script type="text/javascript">
    window.onload = function()
    {
        CKEDITOR.replace( 'description');
    };
<script/>

5、CKEditor的配置:

​ 使用CKEditor提供的config.js文件进行配置,文件所在目录跟ckeditor.js的文件在同一目录下。

2、commons-fileupload上传素材

2.1、准备工作:

2.1.1、下载两个 jar 包,将jar导入项目中的WEB-INF/lib目录下
  a、commons-fileupload.jar:这是一个核心 jar包,实现文件上传的核心类及方法都在这里面。

b、commons-io.jar:这是一个依赖 jar包,是为了辅助 commons-fileupload 包,此包主要是进行 IO操作的,是 commons-fileupload 的依赖包。

2.1.2、准备一个页面

准备一个 jsp页面,此页面中要有一个 form表单,此表单的特征如下:

1)、表单form标签,提交方式必须为post,enctype 属性值必须是 multipart/form-data ,即:

method="post"  enctype="multipart/form-data"

enctype属性:规定了form表单在发送到服务器时候编码方式。他有如下的三个值。
①application/x-www-form-urlencoded。默认的编码方式。但是在用文本的传输和MP3等大型文件的时候,使用这种编码就显得 效率低下。
②multipart/form-data 。 用于上传文件,指定传输数据为二进制类型,比如图片、mp3、文件。

​ ③text/plain。纯文体的传输。空格转换为 “+” 加号,但不对特殊字符编码。

2)、表单中必须提供至少一个 这样的文件上传项。 上传图片的标签为:

<input type="file" name="picPath" value=""/>

2.1.3、在表单提交的处理页面doAdd.jsp中导入需要的包:

<%@page import="java.io.*,java.util.*,org.apache.commons.fileupload.*"%>
<%@page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%>
<%@page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%>

2.2、实现上传

2.2.1、创建实例,并设置上传文件的大小限制

//1、创建FileItemFactory工厂对象
    FileItemFactory factory = new DiskFileItemFactory();
//2、创建ServletFileUpload文件上传对象
    ServletFileUpload upload = new ServletFileUpload(factory);
补充1:setFileSizeMax()与setSizeMax()方法的使用
//设置上传 单个 文件的大小的最大值,目前是设置为1024*1024字节,也就是1MB
     upload.setFileSizeMax(1024 * 1024);
//设置上传 文件总量 的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为10MB    
     upload.setSizeMax(1024 * 1024 * 10);

此时也可以设置如下信息:

//防止文件名出现中文乱码,设置请求以及响应的内容类型以及编码方式
	request.setCharacterEncoding("UTF-8");
	response.setContentType("text/html;charset=UTF-8");
//设置工厂的 内存缓冲区 大小,默认是10K
    factory.setSizeThreshold(1024*1024);
//设置工厂的 临时文件目录 :当上传文件的大小大于缓冲区大小时,将使用临时文件目录缓存上传的文件			factory.setRepository(new File("D:\\YOHO\\"));

2.2.2、判断表单提交内容的形式

​ 解析request请求,获得FileItem集合

//3、判断表单enctype是否是文件上传类型isMultipartContent()(多部分内容表单)
    boolean isMultipart = ServletFileUpload.isMultipartContent(request);
    if (isMultipart==true) {
    //由于从客户端发送到服务器的表单中,既包含了普通表单域,又包含了文件域。应先解析请求,从而获取既包含		了普通表单域,又包含了文件域的集合。
        List<FileItem> items = upload.parseRequest(request);
        }

补充:ServletFileUpload的常用方法

​ ServletFileUpload 负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem 对象中。
1、boolean isMultipartContent(HttpServletRequest request) :判断上传表单是否为multipart/form-data类型

2、List parseRequest(HttpServletRequest request):解析request对象,并把表单中的每一个输入项包装成一个fileItem 对象,并返回一个保存了所有FileItem的list集合。

2.2.3、循环遍历集合中的数据

1、使用迭代器:
Iterator<FileItem> iter = items.iterator();    //创建迭代器
while(iter.hasNext()){
    FileItem item = (FileItem)iter.next();   //读取数据元素
    if (item.isFormField()) {省略判断内容...	 //true--表示普通表单元素,false--表示文件元素
    } else{  }
}

2、增强型for循环:
for (FileItem item : items) {
                //如果是普通表单域,获取用户名。然后输出
                if (item.isFormField()) {省略判断内容...	
                }else{  }
                }
补充2:FileItem类的常用方法

1.boolean isFormField()

​ isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。

2.String getName()

​ getName方法用于获得文件上传字段中的文件名。注意IE或FireFox中获取的文件名是不一样的,IE中是绝对路径,FireFox中只是文件名。

3.String getFieldName()

​ getFieldName方法用于返回表单标签name属性的值。如的value。

4.void write(File file)

​ write方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。

5.String getString()
getString方法用于将FileItem对象中保存的数据流内容以一个字符串返回。

fileItem.getString("utf-8") //以指定编码的方式来获取普通表单提交的值

​ 前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码名称。

6.String getContentType()
getContentType 方法用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值,如“image/jpeg”。如果FileItem类对象对应的是普通表单字段,该方法将返回null。

7.boolean isInMemory()
isInMemory方法用来判断FileItem对象封装的数据内容是存储在内存中,还是存储在临时文件中,如果存储在内存中则返回true,否则返回false。

8.void delete()

​ delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。尽管当FileItem对象被垃圾收集器收集时会自动清除临时文件,但及时调用delete方法可以更早的清除临时文件,释放系统存储资源。另外,当系统出现异常时,仍有可能造成有的临时文件被永久保存在硬盘中。

9.InputStream getInputStream():以流的形式返回上传文件的数据内容。

10.long getSize():返回该上传文件的大小(以字节为单位)。

2.2.4、调用isFormField方法判断文件类型

2.2.4.1、 如果为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值。

//如果是普通表单域,获取用户名,然后输出
if (item.isFormField()) {
	if (item.getFieldName().equals("uname")) {
		String uname = item.getString("UTF-8");
		out.print("用户名为:" + uname + ",该用户上传了文件<br/>");
			}
	} 

2.2.4.2、如果是文件域,就上传到tomcat(服务器)从而读取数据。创建上传文件的文件夹,目的是用来接收上传的文件:

​ 1、手动在webroot根目录下创建一个空的uploads文件夹,当项目部署运行的时候,该文件夹会自动在webapps本项目下创建;

​ 2、File file = new File(xxx); 如果 if(!file.exists()){ file.mkdirs();}

​ 3、调用getInputStream方法得到数据输入流,如下:

else{
 	//注意IE或FireFox中获取的文件名是不一样的,IE中是绝对路径,FireFox中只是文件名。
     String fileName=item.getName();
     System.out.println(fileName);
     //返回表单标签name属性的值
     String namede=item.getFieldName();
     System.out.println(namede);

    //方法一:保存上传文件到指定的文件路径
    InputStream is=item.getInputStream();
    FileOutputStream fos=new FileOutputStream("D:\\wps\\"+fileName);
    byte[] buff=new byte[1024];
    int len=0;
    while((len=is.read(buff))>0){
    fos.write(buff);
   		 }

    //方法二:保存到指定的路径
    //将FileItem对象中保存的主体内容保存到某个指定的文件中。
    // 如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除
    item.write(new File("D:\\sohucache\\"+fileName));
    is.close();
    fos.close();
    	}
  }
补充3:创建文件和目录的方法

1)、File类的createNewFile根据抽象路径创建一个新的空文件,当抽象路径制定的文件存在时,创建失败
2)、File类的mkdir方法根据抽象路径创建目录
3)、File类的mkdirs方法根据抽象路径创建目录,包括创建必需但不存在的父目录
4)、File类的createTempFile方法创建临时文件,可以制定临时文件的文件名前缀、后缀及文件所在的目录,如果不指定目录,则存放在系统的临时文件夹下。
5)、除mkdirs方法外,以上方法在创建文件和目录时,必须保证目标文件不存在,而且父目录存在,否则会创建失败。

补充4:Arrays.asList()方法
  1. 、该方法可将一个变长参数或者数组转换成List,但不适用于八大基本数据类型。
    2)、 当使用asList()方法时,数组就和列表链接在一起了.当更新其中之一时,另一个将自动获得更新。注意:仅仅针对对象数组类型,基本数据类型数组不具备该特性。
    3)、 asList得到的集合是没有add和remove方法的。 具体见如下实例:
//1、对象数组类型(支持)
    String s[]={"aa","bb","cc"};   
    List<String> sList=Arrays.asList(s);
    for(String str:sList){   //能遍历出各个元素
    System.out.print(str+"\t");
    }
    System.out.println(sList.size());//为3
//2、基本数据数组类型(不支持)
    int i[]={11,22,33};   
    List intList=Arrays.asList(i);	
    //intList中就有一个Integer数组类型的对象,整个数组作为一个元素存进去的
    for(Object o:intList){   //就一个元素
    System.out.println(o.toString());
    }
//3、包装类数据数组(支持)
    Integer ob[]={11,22,33};   
    List<Integer> objList=Arrays.asList(ob);  //数组里的每一个元素都是作为list中的一个元素
    for(int a:objList){
    System.out.print(a+"\t");
    }
    
//以上输出结果如下:   
        aa  bb  cc  3
        [I@287efdd8
        11  22  33

2.3、文件上传综合写法

<%
	//1、创建FileItemFactory工厂对象
	FileItemFactory factory = new DiskFileItemFactory();
	//2、创建ServletFileUpload文件上传对象
	ServletFileUpload upload = new ServletFileUpload(factory);
	upload.setSizeMax(100 * 1024);
	try {
		//3、判断表单enctype是否是文件上传类型isMultipartContent()(多部分内容表单)
		boolean isMultipart = ServletFileUpload.isMultipartContent(request);
		if (isMultipart==true) {
			//由于从客户端发送到服务器的表单中,既包含了普通表单域,又包含了文件域。
			//那么应先解析请求,从而获取既包含了普通表单域,又包含了文件域的集合
			List<FileItem> items = upload.parseRequest(request);
			for (FileItem item : items) {
				//如果是普通表单域,获取用户名,然后输出
				if (item.isFormField()) {
					if (item.getFieldName().equals("uname")) {
						String uname = item.getString("UTF-8");
						out.print("用户名为:" + uname + ",该用户上传了文件<br/>");
					}
				} else {
					//文件名获取
					String fileName = item.getName();
					//判断用户上传的文件是否符合规范,如:指定图片文件格式
					List<String> suffixes = Arrays.asList("jpg", "png", "jpeg", "pneg");
					//获取文件上传的文件后缀名
					int atPoint = fileName.lastIndexOf(".") + 1;
					//获取文件名的后缀
					String suffix = fileName.substring(atPoint);
					if (suffixes.contains(suffix)) {
						//如果后缀名包含,说明:符合要求
						//如果是文件域,那么就进行上传,上传到tomcat(服务器)
						//上传文件的文件夹,目的是用来接收上传的文件:1、手动在webroot根目录下创建一个空的uploads文件夹,当项目部署运行的时候,该文件夹会自动在webapps——本项目下创建,
2、File file = new File(xxx); 如果(!file.exists()) ———> file.mkdirs()。
						
						String filePath = request.getSession().getServletContext()
									.getRealPath("uploads");
 						//request.getSession().getServletContext() 相当于application,获取的是Servlet容器对象,相当于tomcat容器。ServletContext是一个web应用的上下文,是一个全局信息的存储空间,代表当前web应用。getRealPath("/") 获取实际路径,“/”指代项目根目录,所以代码返回的是项目在容器中的实际发布运行的根路径。
						File file = new File(filePath);
						if (!file.exists()) {
							file.mkdirs();
						}

						// 创建中转File对象 
						//原因:由于在IE浏览器中,会将文件的全路径作为文件名;而在FF、Chorme中只保留文件的名称。所以创建中转对象,通过该对象的getName()方法返回文件名
						File tempFile = new File(fileName);
						//表示在filePath路径下创建名字为tempFile.getName()的空文件
						File saveFile = new File(filePath,tempFile.getName());
						//上传!fileItem write()   写  将文件写入到指定的路径下
						item.write(saveFile);
						out.print("上传文件成功!文件的名称为:" + tempFile.getName()
								+ "\t文件的路径为:" + filePath + "<br/>");
					} else {
			out.print("文件上传失败!文件类型必须是\"jpg\", \"png\",\"jpeg\", \"pneg\"<br/>");
					}
				}
			}
		}
	} catch (FileUploadBase.SizeLimitExceededException ex) {  //超过大小限制的异常
		out.print("文件上传失败!必须小于" + upload.getSizeMax() / 1024 + "kb");
    }
%>

​ 在Windows下的路径分隔符(\)和在Linux下的路径分隔符(/)是不一样的,当直接使用绝对路径时,跨平台会报No Such file or diretory异常。File中的separator可根据系统自适应不同的分隔符,如下:

File file = new File("G:"+ File.separator +"demo.txt");

3、分页查询

3.1、查询方法

​ 先创建page类,有4个属性:当前页,页大小,总条数,总页数;2个方法:获取总条数的方法,根据page获取news的集合。

3.1.1、查询新闻的总条数

public int getTotalCounts() {     // 查询总条数
		String sql = "select count(1) from news";    //count(1)获取总条数
		ResultSet rs = executeQuery(sql);
		int count = 0;
		try {
			while (rs.next()) {
				count = rs.getInt(1);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			closeAll(connection, pstmt, rs);
		}
		return count;
	}

3.1.2、计算判断页面总页数

//计算总页数(重点方法)
	public void setTotalPageCountByRs(){   //if语句判断
		if(this.totalCount % this.pageSize == 0){
			this.totalPageCount = this.totalCount / this.pageSize;
		}else if(this.totalCount % this.pageSize != 0){
			this.totalPageCount = this.totalCount / this.pageSize + 1;
		}else{
			this.totalPageCount = 0;
		}
	}
	
	public void setTotalPageCountByRs(){  //三元运算符判断
		this.totalCount % this.pageSize == 0?(this.totalPageCount = this.totalCount / 				this.pageSize):(this.totalPageCount = this.totalCount / this.pageSize + 1)
	}	
	//流程:先去数据库查询新闻总数量 --> 设置总新闻数量:计算总页数setTotalPageCount --> 
		   拿到总页数:getTotalPageCount()

3.1.3、根据分页信息获取要显示页面的信息集合

​ 这里要用到如下计算分页查询时的起始记录数公式:

​ 起始记录的下标(偏移量)=(当前页码 - 1)×页面容量

// 根据分页信息获取新闻的集合
public List<News> getNewsListByPage(Page page) {
	String sql = "SELECT n.`nid`,n.`nauthor`,n.`ntitle`,n.`ncreateDate`,t.`tname` FROM news n			topic t WHERE n.`ntid` = t.`tid` ORDER BY n.`ncreateDate` DESC LIMIT ?,?";
	Object[] params = { (page.getCurrPageNo() - 1) * page.getPageSize(),page.getPageSize() };
	ResultSet rs = executeQuery(sql, params);
	List<News> newsList = new ArrayList<News>();
	News news = null;
	try {
		while (rs.next()) {  //遍历新闻集合
            news = new News();
            news.setNid(rs.getInt("nid"));
            news.setNauthor(rs.getString("nauthor"));
            news.setNtitle(rs.getString("ntitle"));
            news.setNcreateDate(rs.getDate("ncreateDate"));
            Topic topic = new Topic();
            topic.setTname(rs.getString("tname"));
            news.setTopic(topic);
            newsList.add(news);
		}
	} catch (SQLException e) {
		e.printStackTrace();
	} finally {
		closeAll(connection, pstmt, rs);
	}
	return newsList;
}

3.1.4、JSP页面中对首页、上一页、下一页和末页的判断

​ 要准备两个页面,一个showPage,一个doPage

//页码这个变量在传参时使用隐藏域在页面中进行传递
<input type="hidden" value="<%=page_.getTotalPageCount()%>" id="totalPageCount" />
		
//页面跳转链接(携带页码参数)
<% if (page_.getCurrPageNo() > 1) {  %>   //当页面大于1才显示首页和下一页
	<a href="doPage.jsp?currentPageNo=1"> 首页 </a> 
	<a href="doPage.jsp?currentPageNo=<%=page_.getCurrPageNo() - 1%>">上一页</a>
	<%  }  %>
<% if (page_.getCurrPageNo() < page_.getTotalPageCount()) { %> //当页面小于总页面才显示上一页和末页
	<a href="doPage.jsp?currentPageNo=<%=page_.getCurrPageNo() + 1%>">下一页</a>
	<a href="doPage.jsp?currentPageNo=<%=page_.getTotalPageCount()%>">末页</a>
	<%  }  %>

第四章、EL和JSTL

1、EL表达式应用

​ EL( Expression Language )表达式语言,EL中是对jsp页面的优化技术 。它可用于访问javaBean中存储的数据,也可访问请求参数、cookie等。

${EL表达式} 

应用示例:

1、获取用户的登录名:
	${user.name} 等价于 <%=user.getName()%>
2、访问对象的属性:
	${news[“title”]}  可以访问news对象的title属性
		注:如果访问了一个不存在的属性,就会抛出PropertyNotFoundException.
3、访问数组:
	${newsList[0]}  可以访问newsList数组中的第一个元素
4、如果是map集合,则获取map值的时候如果key是数字,则不能直接map.kwy,只能map[key],且要求该key不能为int类型,只能为long类型,如  map(1L,value),获取时:map[1];

1.1、EL中11个隐式对象

隐含对象 描述
pageScope page 作用域,代表页面域中的Map对象
requestScope request 作用域,代表请求域中的Map对象
sessionScope session 作用域,代表会话域中的Map对象
applicationScope application 作用域,代表上下文域中的Map对象
param Request 对象的参数,字符串,功能与:request.getParameter()相同
paramValues Request对象的参数,字符串集合,与:String[] request.getParameterValues()相同
header HTTP 信息头,字符串,得到请求头的数据 request.getHeader(“名字”)
headerValues HTTP 信息头,字符串集合,得到请求头的数据 request.getHeaders(“名字”)
initParam 上下文初始化参数 ,相当于config.getInitParamter()得到web.xml中配置的参数
cookie Cookie值,得到请求的Cookie信息
pageContext 当前页面的pageContext,代表页面上下文对象,可以在页面上调用get方法

获取指定域数据的表达式对比

作用域 Java代码 EL的写法
页面域 pageContext.getAttribute(“color”); ${pageScope.color}
请求域 request.getAttribute(“color”); ${requestScope.color}
会话域 session.getAttribute(“color”); ${sessionScope.color}
上下文域 application.getAttribute(“color”); ${applicationScope.color}
自动查找 pageContext.findAttribute(“color”); ${color}
作用 JSP表达式 EL表达式
当前工程路径 <%=request.getContextPath() %> ${pageContext.request.contextPath}
当前会话的ID <%=request.getSession().getId() %> ${pageContext.request.session.id}

1.2、EL表达式的运算符

1.算术运算符

  • 例如: 6 + 6 。 注 意 : 在 E L 表 达 式 中 的 ‘ + ’ 只 有 数 学 运 算 的 功 能 , 没 有 连 接 符 的 功 能 , 它 会 试 着 把 运 算 符 两 边 的 操 作 数 转 换 为 数 值 类 型 , 进 而 进 行 数 学 加 法 运 算 , 最 后 把 结 果 输 出 。 若 出 现 {6+6} 。注意:在EL表达式中的‘+’只有数学运算的功能,没有连接符的功能,它会试着把运算符两边的操作数转换为数值类型,进而进行数学加法运算,最后把结果输出。若出现 6+6EL+{‘a’+‘b’}则会出现异常。
  • 例如:${4-3}
  • 例如: 4 ∗ 3   、 {4*3} 、 43 {9/3}

2.关系运算符

>  或者 gt, 例如:${8>9}  或者 ${8 gt 9 }
>= 或者 ge, 例如:${45>=9}或者 ${45 ge 9 }
<  或者 lt, 例如:${4<9}  或者 ${4 lt 9 }
<= 或者 le, 例如:${9<=8} 或者 ${9 le 8 }
== 或者 eq, 例如:${4==4} 或者 ${4 eq 4 }
!= 或者 ne, 例如:${4!=3} 或者 ${4 ne 3 }

3.逻辑运算符

&& 或者 and, 例如:${false && false} 或者 ${false and false }
|| 或者 or,  例如:${true || false} 或者 ${true or false }
!  或者 not, 例如:${!true}(相当于${false})或者 ${not true }

4.三元运算符

? :   例如:${3>2?'是':'不是'}

5.特殊运算符

empty 判断EL表达式中的表达式是否为空,  例如:${empty sessionScope.user} 

​ “.” 是我们最常用的,作用相当于执行Bean中的get方法。 例如: s e s s i o n S c o p e . u s e r . u s e r N a m e 意 思 是 : 在 会 话 中 得 到 名 称 为 u s e r 的 B e a n 对 象 , 通 过 “ . ” 运 算 符 执 行 g e t U s e r N a m e ( ) ; 方 法 , 返 回 存 放 在 B e a n 中 的 用 户 名 属 性 的 值 。 [ ] 作 用 和 “ . ” 运 算 符 的 一 样 , 只 不 过 [ ] 运 算 符 可 以 执 行 一 些 不 规 则 的 标 识 符 。 例 如 : {sessionScope.user.userName}意思是:在会话中得到名称为user的Bean对象,通过“.”运算符执行getUserName();方法,返回存放在Bean中的用户名属性的值。 [] 作用和“.”运算符的一样,只不过[]运算符可以执行一些不规则的标识符。 例如: sessionScope.user.userNameuserBean.getUserName();Bean[].[]{requestScope.user[“score-math”]},这个表达式中有不规则的标识符,是不能使用“.”来访问的。

2、JSP标准标签库(JSTL)

​ JSTL支持通用的、结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签,SQL标签。 除了这些,它还提供了一个框架来使用集成JSTL的自定义标签。根据JSTL标签所提供的功能,可以将其分为5个类别。

标签库 资源标识符(url) 前缀(prefix)
核心标签 http://java.sun.com/jsp/jstl/core c
格式化/国际化标签 http://java.sun.com/jsp/jstl/fmt fmt
数据库SQL 标签 http://java.sun.com/jsp/jstl/sql sql
XML 标签 http://java.sun.com/jsp/jstl/xml x
JSTL 函数标签库 http://java.sun.com/jsp/jstl/functions fn

​ 其中,核心标签库是对于 JSP 页面一般处理的封装。在该标签库中的标签一共有 14 个,被分为了四类:

  • 多用途表达式核心标签:<c:out > 、<c:set > 、<c:remove > 、<c:catch > 。

  • 条件控制标签:<c:if > 、<c:choose > (switch)、<c:when > (case)、<c:otherwise > (default)。

  • 循环控制标签:<c:forEach > 、<c:forTokens > 。

  • URL操作标签:<c:import > 、<c:url > 、<c:redirect > 、<c:param > 。

​ EL不能连接数据库,而JSTL中的SQL标签库可以连接数据库

2.1、使用JSTL的前提准备工作

1、下载JSTL所需要的jstl.jar和standard.jar文件;

2、将两个jar文件复制到WEB-INF/lib目录下,并添加到项目中;

3、在JSP中添加标签指令,Taglib 指令是定义一个标签库以及其自定义标签的前缀,如下:

<%@ taglib uri="" prefix="c"%>
	uri : 指资源定位符并不是单纯的指项目的哪个位置,必填
	prefix :是需要使用的标签库命名前缀(一个标签库别名),必填
例:	
<%@ taglib  uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %> //引入jstl的XML标签库
<%@ taglib  uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> //引入jstl的核心标签库

2.1.1、JSTL常用标签

1、<c:out >标签:输出显示

<c:out value="value" default="default" escapeXml="true|false" />

属性 value:需要输出显示的表达式
	default:默认输出显示的值,如果value为null,则输出default的内容
	escapeXml:是否对输出的内容进行转义,如:为false时,会将内容<br>标签不进行转义,也就是<br>标签被浏览器解析了变成了换行。默认为true。

2、<c:set >标签:设置变量

<c:set value="value" var="name" scope="scope" />

属性 value:变量的值
	var:变量的名称
	scope:变量存在的作用域范围,可为:page、request、session、application

3、<c:set >标签:设置对象属性

<c:set value="value" target="target" property="propertyName" />

属性 value:属性的值
	target:对象的名称
	property:对象的属性名称

4、<c:remove >标签:删除变量

<c:remove var="name" scope="scope" />

属性 var:变量的名称
	scope:变量存在的作用域范围,可为:page、request、session、application

5、<c:forEach >标签 :循环迭代,不仅可以处理集合,也可以处理数组

<c:forEach var="name" items="expression" varStatus="name" begin="expression" end="expression" step="expression">...
</c:forEach>

属性 var:代表当前条目的变量名称
	items:要被循环的信息
	varStatus:代表循环状态的变量名称,取值index\count\first\last\current 
	begin:开始的元素(0=第一个元素,1=第二个元素)
	end:最后一个元素(0=第一个元素,1=第二个元素)
	step:每一次迭代的步长,默认为1

varStatus属性常用参数如下:

​ current:当前这次迭代的(集合中的)项
​ index:当前这次迭代从 0 开始的迭代计数
​ count:当前这次迭代从 1 开始的迭代计数
​ first:用来表明当前这轮迭代是否为第一次迭代的标志,返回true/false
​ last:用来表明当前这轮迭代是否为最后一次迭代的标志,返回true/false

varStatus属性可以方便我们实现一些与行数相关的功能,如:奇数行、偶数行差异;最后一行特殊处理等等。

<span style="font-size:18px;"><span style="font-size:14px;">
    <c:forEach var="e" items="${ss.list}" varStatus="status">
        <!--实现隔行变色效果--> 
        <c:if test="${status.count%2==0}" >
        	<tr  style="color: red; font-size: 20px;">
        </c:if>
        <c:if test="${status.count%2!=0}" >
        	<tr>
        </c:if> 
        	测试
        </tr> 
    </c:forEach>
</span>

6、<c:if >标签 :条件判断

<c:if test="condition" var="varName" scope="scope">...</c:if>

属性 test:判断的条件
	var:判断的结果
	scope:判断结果存放的作用域范围,可为:page、request、session、application

if标签要判断是否为空,要以下写法,test判断条件要${ empty}:

<c:if test="${empty sale_list}">  ale_list为空  </c:if>
<c:if test="${not empty sale_list}">  ale_list不为空  </c:if>

7、<c:url >标签 :根据URL规则创建一个URL

<c:if test="condition" var="varName" scope="scope">...</c:if>

属性 test:判断的条件
	var:判断的结果
	scope:判断结果存放的作用域范围,可为:page、request、session、application

8、<c:param >标签 :对超链接进行参数设置

<c:param name="name" value="value"/>

属性 name:参数的名称
	value:参数的值

取值:${param.name}

9、<c:import >标签 :在页面中导入一个基于URL的页面资源

<c:import url="URL"/>

属性 url:导入资源的URL路径

10、<fmt:formatDate >标签 :在页面中导入一个基于URL的页面资源

<fmt:formatDate value="date" pattern="yyyy-MM-dd HH:mm:ss"/>

属性 value:时间对象
	pattern:显示格式

11、<c:catch > 标签:主要用来处理产生错误的异常状况,并且将错误信息储存起来。

<c:catch var="<string>">
...
</c:catch>

属性: var:用来储存错误信息的变量

示例:

<c:catch var ="catchException">
   <% int x = 5/0;%>
</c:catch>

<c:if test = "${catchException != null}">
   <p>异常为 : ${catchException} <br />
   发生了异常: ${catchException.message}</p>
</c:if>

运行结果如下:
异常为 : java.lang.ArithmeticException: / by zero 
发生了异常: / by zero

第五章、Servlet、过滤器及监听器

1、Servlet

​ Servlet 也是java程序,运行在服务器端,需要WEB容器的支持(即运行需要开启Tomcat服务器),本身不做任何业务处理,起到中间调度作用。jsp在被web容器解析时会被编译.Java类型的一个servlet类 ,所以说jsp是servlet 的扩展,jsp一定是个servlet 。

Servlet 处理web请求的过程,主要包括以下几个步骤:

​ A、服务器接受从客户端发送的请求;

​ B、服务器将请求信息发送至Servlet ;

​ C、Servlet 经过处理后,生成响应的内容;

​ D、服务器将相应的内容返回客户端。

到此将项目分为以下几个部分:

  • JSP:显示数据

  • Servlet:接受请求,调用JavaBean去处理请求

  • JavaBean: dao层:数据访问接口层 —> daoImpl:具体的实现操作类

    ​ service层:业务逻辑层 —> serviceImpl:负责与逻辑的相关操作,对dao层进行封装和调用

1.1、Servlet API

名称 说明 所在包
Servlet接口 java Servlet的基础类,定义了Servlet必须实现的方法 javax.servlet
GenericServlet抽象类 继承自Servlet接口,属于通用的、不依赖协议的Servlet javax.servlet
HttpServlet抽象类 继承自GenericServlet类,是在其基础上扩展了HTTP协议的Servlet javax.servlet.http
HttpServletResquest接口 继承自ServletRequest接口,用于获取请求数据 javax.servlet.http
HttpServletResponse接口 继承自ServletResponse接口,用于获响应数据 javax.servlet.http

1.1.1、Servlet与JSP九大内置对象的对应关系

JSP对象 Servlet中怎样获得
out response.getWriter
response.getWriter().print(),不仅可以打印输出文本格式的(包括html标签),
还可以将一个对象以默认的编码方式转换为二进制字节输出
response.getWriter().writer(),只能打印输出文本格式的(包括html标签),
不可以打印对象。
request service方法中的req参数
response service方法中的resp参数
session request.getSession()函数
application request.getSession.getServetContext()函数
exception Throable
page this
pageContext PageContext
config getServletConfig

1.2、Servlet 创建方法

1.2.1、实现 Servlet 接口

​ 因为是实现 Servlet 接口,所以我们需要实现接口里的方法。

下面我们也说明了 Servlet 的执行过程,也就是 Servlet 的生命周期。

//Servlet的生命周期:从Servlet被创建到Servlet被销毁的过程
//一次创建,到处服务
//一个Servlet只会有一个对象,服务所有的请求
/*
 * 1.实例化  (使用构造方法创建对象,在Servlet容器创建Servlet的实例)
 * 2.初始化  (容器调用init方法)
 * 3.服务    (如果客户端请求Servlet,则容器执行service方法}
 * 4.销毁    (销毁实例之前,调用destroy方法)
 */
public class ServletDemo1 implements Servlet {

    //public ServletDemo1(){}

    //生命周期方法:当Servlet第一次被创建对象时执行该方法,该方法在整个生命周期中只执行一次
    public void init(ServletConfig arg0) throws ServletException {
                System.out.println("=======init=========");
    }

    //生命周期方法:对客户端响应的方法,该方法会被执行多次,每次请求该servlet都会执行该方法
    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        System.out.println("hehe");
    }

    //生命周期方法:当Servlet被销毁时执行该方法
    public void destroy() {
        System.out.println("******destroy**********");
    }
    
	//当停止tomcat时也就销毁的servlet。
    public ServletConfig getServletConfig() {
        return null;
    }

    public String getServletInfo() {
        return null;
    }
}

1.2.2、继承 GenericServlet 类

​ 它实现了 Servlet 接口除了 service 的方法,不过这种方法极少用。

public class ServletDemo2 extends GenericServlet {

    @Override
    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        System.out.println("heihei");
    }
}

1.2.3、继承 HttpServlet 方法

​ 如下创建 Servlet 的第三种方法,也是我们经常用的方法。

public class ServletDemo3 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("haha");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("ee");
        doGet(req,resp);
    }
}

实例示例:

//使用创建 Servlet 的第三种方法,继承HttpServlet类
public class DelServlet extends HttpServlet {   
	//实现重写doGet方法
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			this.doPost(request, response);   //此处调用doPost方法,实现两个方法的自动选择跳入
	}
	//实现重写doPost方法
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		int id = Integer.parseInt(request.getParameter("id"));
		//调用后台方法根据id去查询新闻    以下示例为删除新闻的操作方法
		News news = new News();
		news.setId(id);
		String flag="failed";
		
		NewsService newsService=new NewsServiceImpl();
		if(newsService.delete(news)){
			flag="success";
		}
		response.sendRedirect("/news/jsp/admin/newsDetailList.jsp?flag="+flag);     
	}
	//另外两个init()、destroy()方法一般不需要重写,此处省略
}

1.3、servlet配置

1.3.1、在web.xml中配置

​ 创建完servlet后,需要在web.xml中做如下配置,配置中各节点的含义:

<!-- 配置一个servlet -->
<!-- servlet的配置 -->
<servlet>
    	<!-- servlet的内部名称,自定义。尽量有意义 -->
    <servlet-name>ServletDemo</servlet-name>
    	<!-- servlet的类全名: 包名+简单类名 -->
    <servlet-class>lm.practice.ServletDemo</servlet-class>
    <init-param>   <-- 局部参数标签-->
   			<!-- 以初始化的参数方式设置默认的字符编码为UTF-8 -->
    	<param-name>charSetContent</param-name>         
    	<param-value>utf-8</param-value>
    </init-param>
</servlet>
<!-- servlet的映射配置 -->
<servlet-mapping>
    	<!-- servlet的内部名称,一定要和上面的内部名称保持一致!! -->
    <servlet-name>ServletDemo</servlet-name>
   		<!-- servlet的映射路径(也是要访问servlet的路径名称) -->
    <url-pattern>/servlet</url-pattern>
</servlet-mapping>
 
<context-param>    <!-- 全局参数标签-->
	<param-name>charSetContent</param-name>         
     <param-value>utf-8</param-value>
</context-param>

​ 的3种配置方法:

1、精确匹配 /testServlet             √
2、模糊匹配:a、目录匹配 /abc/*
		   b、后缀名匹配*.do         √
3、全局匹配:/*

​ 获取上述配置信息的初始化参数的方法如下:

public void init(ServletConfig config) throws ServletException {
		//获取局部参数
        String initParam = config.getInitParameter("charSetContent");
        System.out.print(initParam);   //即可打印出utf-8
        
        //获取全局参数
        String initParam2 =request.getSession().getServletContext()
        					   .getInitParameter("charSetContent");
    }

1.3.2、注解配置(就不用再web.xml里面配置了)

@WebServlet(name="Servlet的类名",urlPatterns={“/testServlet”})

1.4、Servlet 生命周期

Servlet 加载实例化—>初始化—>提供服务—>销毁。

1、init():初始化
在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init()。
2、service():提供服务(派发器功能)
它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能(触发org.web.servlet.UserServlet 的do方法 )。

3、destroy():销毁
仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。

步骤:
1:Web Client向Servlet容器(tomcat)发出Http请求
2:Servlet容器接收Web Client的请求
3:Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中。
4:Servlet容器创建一个HttpResponse对象
5:Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传递给HttpServlet对象。
6:HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息
7:HttpServlet调用HttpResponse对象的有关方法,生成响应数据 8:Servlet容器把HttpServlet的响应结果传入Web Client。

ServletContext补充:

​ WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象。Servlet上下文是运行Servlet的逻辑容器,同一个上下文中共享存有其中的信息和属性, 在Servlet中定义了一个ServletContext接口,用于存取Servlet运行的环境或者上下文信息。 ServletContext对象可以通过使用ServletConfig对象的getServletContext()方法获得,在Servlet中提供的getServletContext()方法也可是直接获得ServletContext对象。

​ ServletContext,是一个全局的储存信息的空间,服务器开始,其就存在,服务器关闭,其才释放。request,一个用户可有多个;session,一个用户一个;而servletContext,所有用户共用一个。所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。

1.5、路径跳转问题

1.5.1、HttpServletResponse 重定向

假设原绝对路径:http://localhost:8080/news/servlet/AddServlet

使用response.sendRedirect()跳转页面:

(1)相对路径

response.sendRedirect("newsDetailList.jsp");   //同级目录下,直接写文件名即可
以上即可跳转到此地址:http://localhost:8080/news/servlet/newsDetailList.jsp

(2)绝对路径

response.sendRedirect("/news/jsp/admin/newsDetailList.jsp");  //根目录从项目名称news开始写
以上即可跳转到此地址:http://localhost:8080/news/jsp/admin/newsDetailList.jsp

1.5.2、HttpServletRequest 转发

假设原绝对路径:http://localhost:8080/news/servlet/AddServlet

使用request.getRequestDispatcher().forward().跳转页面:

(1)相对路径

request.getRequestDispatcher("newsDetailList.jsp").forward(....);//同级目录直接写文件名即可
以上即可跳转到此地址:http://localhost:8080/news/servlet/newsDetailList.jsp

(2)绝对路径

							//根目录从项目名称的第一级存放页面文件目录jsp开始写,不用写项目名称news
request.getRequestDispatcher("/jsp/admin/newsDetailList.jsp").forward(....);
以上即可跳转到此地址:http://localhost:8080/news/jsp/admin/newsDetailList.jsp

​ 所以,尽量都写绝对路径,以免找不到页面出错。

1.6、接口的常用方法

1.6.1、ServletRequest接口

方法 说明
Object getAttribute(String name) 获取名称为那么的属性值
void setAttribute(String name , Object object) 在请求中保存名称为name的属性值
void removeAttribute(String name) 清除请求中名称为name的属性
String getParameter(String name) 获取请求中传递的参数

1.6.2、HttpServletRequest接口

​ HttpServletRequest接口继承自ServletRequest接口,用于读取用户请求中的数据

方法 说明
String getContextPath() 返回请求URL中表示请求上下文的路径是请求URL的开始部分
Cookie[] getCookies() 返回客户端在此次请求中发送的所有Cookie对象
HttpSession getSession() 返回和此次请求相关联的session对象,如果没有客户端分配
session的对象,则创建一个新的session对象
String getMethod() 返回此次请求所使用的HTTP方法的名字,如:GET、POST

​ 在jsp、servlet中可以通过request.getSession().和servlet.getServletContext()都可以获取session对象。

​ request 对象的 request.getParameter(“name”) 取出页面传递过来标记为 name的值

​ request 对象的 request. getAttribute (“name”) 取出存入在属性中的值

1.6.3、ServletResponse接口

方法 说明
PrintWriter getWriter() 返回PrintWriter对象,用于向客户端发送文本
String getCharacterEncoding() 返回在相应中发送的征文所使用的字符编码
void setCharacterEncoding(String charset) 设置发送到客户端的响应所使用的字符编码
void setContentType(String type) 设置发送到客户端的响应的内容类型

1.6.4、HttpServletResponse接口

​ HttpServletResponse接口继承自ServletResponse接口,用于对客户端的请求进行响应

方法 说明
void addCookie(Cookie cookie) 增加一个 Cookie到响应中,这个方法可多次调用,用于设置多个Cookie
void addHeader(String name,String value) 将一个名称为name,值为value的响应报头添加到响应中
void sendRedirect(String location) 发送一个临时的重定向响应到客户端,以便客户端访问新的URL,该方法会抛出一个IOException
String encodeURL(String url) 使用Session ID对用于重定向的URL进行编码,以便用于sendRedirect()方法中

​ 同一个请求周期中,HttpServletResponse的getWriter()与getOutputStream()只能择一使用,否则会丢出IllegalStateException。

2、过滤器Filter

​ 过滤器是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改。也可以对响应进行过滤,拦截或修改响应。

2.1、过滤器的创建

​ 1:创建过滤器

新建一个class,实现接口Filter(注意:是javax.servlet中的Filter)。

​ 2:重写过滤器的doFilter(request,response,chain)方法。另外两个init()、destroy()方法一般不需要重写。在doFilter方法中进行过滤操作。

​ 常用代码有:获取请求、获取响应、获取session、放行。

​ 剩下的代码就是根据session的属性进行过滤操作、设置编码格式等等了,看情况而定。

HttpServletRequest request=(HttpServletRequest) arg0;//获取request对象
HttpServletResponse response=(HttpServletResponse) arg1;//获取response对象
HttpSession session=request.getSession();//获取session对象

过滤操作代码......

chain.doFilter(request, response);//放行,通过了当前过滤器,递交给下一个filter进行过滤

2.2、Filter生命周期

​ 程序启动调用Filter的init()方法(永远只调用一次,具体看启动日志),

程序停止调用Filter的destroy()方法(永远只调用一次,具体看关闭日志),

doFilter()方法每次的访问请求如果符合拦截条件都会调用(程序第一次运行,会在servlet调用init()方法以后调用,不管第几次,都在调用doGet(),doPost()方法之前)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XnLwCp9i-1584172618942)(https://img-my.csdn.net/uploads/201204/10/1334063070_3553.png)]

2.3、过滤器的配置

​ 在web.xml中配置过滤器。这里要谨记一条原则:在web.xml中,监听器>过滤器>servlet。也就是说web.xml中监听器配置在过滤器之前,过滤器配置在servlet之前,否则会出错。

<filter>  
    <filter-name>loginFilter</filter-name>//过滤器名称  
    <filter-class>com.ygj.control.loginFilter</filter-class>//过滤器类路径(完全限定名,要加包名)
<init—param> //可选 
    <param—name>参数名</param-name>//过滤器初始化参数
    <param-value>参数值</param-value>  
</init—pamm>  
</filter> 
 
<filter-mapping>  //过滤器映射  
    <filter-name>loginFilter</filter-name>  //过滤器名
	<url—pattern>指定过滤器作用的对象</url-pattern>   //过滤器映射的web资源,全部就写"/*"
</filter-mapping>

//如果一个过滤器需要过滤多种文件,则可以配置多个<filter-mapping>,一个mapping定义一个url-pattern来定义过滤规则。

配置信息注意点:

​ 在配置中需要注意的有两处:一是指明过滤器类所在的包路径。二是处定义过滤器作用的对象。一般有以下规则:

​ 1:作用与所有web资源:<url—pattern>/*。则客户端请求访问任意资源文件时都要经过过滤器过滤,通过则访问文件,否则拦截。

​ 2:作用于某一文件夹下所有文件:<url—pattern>/dir/*

​ 3:作用于某一种类型的文件:<url—pattern>.扩展名。比如<url—pattern>.jsp过滤所有对jsp文件的访问请求。

​ 4:作用于某一文件夹下某一类型文件:<url—pattern>/dir/*.扩展名

获取配置参数方法:

​ FilterConfig对象具有一个getInitParameter方法,它能够访问部署描述符文件(web.xml)中分配的过滤器初始化参数。

	a、String getFilterName():得到filter的名称。
   b、String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
   c、Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
	d、public ServletContext getServletContext():返回Servlet上下文对象的引用。

例1:用过滤器实现登录验证,没登录则驳回访问请求并重定向到登录页面。

public void doFilter(ServletRequest arg0, ServletResponse arg1,
        FilterChain arg2) throws IOException, ServletException {
        
        HttpServletRequest request=(HttpServletRequest) arg0;
        HttpServletResponse response=(HttpServletResponse) arg1;
        HttpSession session=request.getSession();
        
        String path=request.getRequestURI();
        
        Integer uid=(Integer)session.getAttribute("userid");
        
        if(path.indexOf("/login.jsp")>-1){  //登录页面不过滤
            arg2.doFilter(arg0, arg1);   //递交给下一个过滤器
            return;
        }
        if(path.indexOf("/register.jsp")>-1){   //注册页面不过滤
            arg2.doFilter(request, response);
            return;
        }
        
        if(uid!=null){   //已经登录
            arg2.doFilter(request, response);  //放行,递交给下一个过滤器 
        }else{
            response.sendRedirect("login.jsp");
        }
    }

​ 例2:设置字符编码

public void doFilter(ServletRequest request, ServletResponse response,
            		FilterChain chain) throws IOException, ServletException {
          HttpServletRequest request=(HttpServletRequest) request;
          HttpServletResponse response=(HttpServletResponse) response;
          
          request.setCharacterEncoding("UTF-8");  //设置请求时的编码方式
          response.setCharacterEncoding("UTF-8");   //设置响应时的编码方式
          chain.doFilter(request, response);  //调用web资源,也可以调用其他过滤器
    }

​ **注:**Filter的初始化比Servlet的初始化早!

2.4、过滤器Filter作用场景

  • 防止未登录就进入界面

  • 控制应用编码

  • 过滤敏感词汇等场景对数据进行查找替换

3、监听器Listener

​ 监听器也叫Listener,是Servlet的监听器,它可以监听客户端的请求、服务端的操作等。通过监听器,可以自动激发一些操作,比如监听在线的用户的数量。

3.1、Listener生命周期

一直从程序启动到程序停止运行。

ServletRequestListener:每次访问一个Request资源前,都会执行requestInitialized()方法,方法访问完毕,都会执行requestDestroyed()方法。

HttpSessionListener:每次调用request.getSession(),都会执行sessionCreated()方法,执行session.invalidate()方法,都会执行sessionDestroyed()方法。

ServletRequestAttributeListener:每次调用request.setAttribute()都会执行attributeAdded()方法,如果set的key在request里面存在,就会执行attributeReplacerd()方法,调用request.removeAttribute()方法,都会执行attributeRemoved()方法。

3.2、主要的监听器接口

监听器接口(javax.servlet) 说明
ServletContextListener 监听ServletContext对象,在Servlet上下文对象初始化或者销毁时得到通知
ServletContextAttributeListener 监听对ServletContext属性的操作,在Servlet上下文的属性列表发生变化时得到通知,比如增加、删除、修改
HttpSessionListener 监听Session对象,在session创建后或者失效前得到通知
HttpSessionActivationListener 监听HTTP会话的active和passivate情况,passivate是指非活动的session被写入持久设备(比如硬盘)得到通知,active相反。
HttpSessionAttributeListener 监听Session中的属性,在session中的属性列表发生变化时得到通知
HttpSessionBindingListener 实现该接口,可以使一个对象在绑定session或者从session中删除、失效、或超时的时候得到通知
ServletRequestListener 监听Request对象,可以在请求对象初始化时或者被销毁时得到通知
ServletRequestAttributeListener 监听Requset中的属性操作,在请求对象中的属性发生变化时得到通知

以上接口分为三类:

​ 1)、对象的创建和销毁:request(HttpServletRequest)、session(HttpSession)、 application(ServletContext),对应的接口为:ServletContextListener、HttpSessionListener、ServletRequestListener;(需要在web.xml中配置)

​ 2)、属性的创建、替换、移除,对应的接口为:ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener;(需要在web.xml中配置)

​ 3)、感知型:(钝化活化、绑定解绑)当前对象是否添加到request、session、application作用域中,对应的接口为:HttpSessionActivationListener、HttpSessionBindingListener;(不需要在web.xml中配置)

3.3、三大类监听器接口使用方法示例

3.3.1、ServletContextListener接口

方法 说明
void contextInitialized(ServletCntextEvent arg) 在web应用程序初始化开始时,由web容器调用
void contextDestroyed(ServletCntextEvent arg) 在Servlet上下文将要关闭时,由web容器调用

​ ServletContextListener可应用在web的初始化,如下示例:

public class TopicInitListener implements ServletContextListener {
	//销毁
	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		// 暂无销毁需求
	}

	//初始化
	@Override
	public void contextInitialized(ServletContextEvent arg0) {
		ServletContext application = arg0.getServletContext();
		TopicService service = new TopicServiceImpl();   //实例化标题类
		List<Topic> topicList = service.getAllTopic();   
		application.setAttribute("ALL_TOPICS", topicList);    
		System.out.println("加载了" + topicList.size() + "条主题");
	}
}

3.3.2、HttpSessionAttributeListener接口

public class LoginSessionAttributeListener implements HttpSessionAttributeListener {

	private Logger logger = Logger.getLogger(LoginSessionAttributeListener.class);

	// 当session中的属性被添加进来的时候会触发该方法
	@Override
	public void attributeAdded(HttpSessionBindingEvent arg0) {
		logger.info(arg0.getSession() + "中的属性:" + arg0.getName() + ",值为:"
				+ arg0.getValue() + "被添加进来了");
	}

	// 当session中的属性被移除的时候会触发该方法
	@Override
	public void attributeRemoved(HttpSessionBindingEvent arg0) {
		logger.info(arg0.getSession() + "中的属性:" + arg0.getName() + "被移除了,旧值为:"
				+ arg0.getValue());
	}

	// 当session中的属性被替换的时候会触发该方法
	@Override
	public void attributeReplaced(HttpSessionBindingEvent arg0) {
		logger.info("变量:" + arg0.getName() + "被替换了,其旧值为:" + arg0.getValue());
	}
}

3.3.3、HttpSessionBindingListener接口的方法

​ 此接口中共定义了如下两个方法,分别对应数据绑定,和取消绑定两个事件。

方法 说明
void valueBound(HttpSessionBindingEvent event) 当对象被添加到session中,由容器调用该方法来通知对象
void valueUnbound(HttpSessionBindingEvent event) 当对象从session中删除时,由容器调用该方法来通知对象

valueUnbound的触发条件是以下三种情况:

  1. 执行session.invalidate()时。
  2. session超时,自动销毁时。
    . 执行session.setAttribute(“User”, “其他对象”);或session.removeAttribute(“User”);将listener从session中删除时。因此,只要不将listener从session中删除,就可以监听到session的销毁。

示例:

/*
 * 该类的作用就是:感知User对象什么时候与session进行绑定或者从session中解绑
 * 思路:
 * 	1)准备一个空的集合,来放 在线注册的用户信息。
 * 	问题:集合哪里来?
 * 		解决:当容器一启动的时候就准备一个空的集合 来接受(add)在线注册的用户。
 * 			编写一个实现了ServletContextListener接口的监听器类,来准备一个集合
 *  2)准备一个私有的User属性,并生成setter/getter方法。表示在线注册的用户信息
 *  问题:User对象哪里来?
 *  	解决:从表单中提交到servlet,然后将用户名和密码封装成User对象。然后通过实例化该监听器类
 *  		调用setUser()方法,将user对象传进来
 * */
public class OnlineUserLinstener implements HttpSessionBindingListener {

	private NewsUsers user;
	public NewsUsers getUser() {
		return user;
	}

	public void setUser(NewsUsers user) {
		this.user = user;
	}

	//当用户添加到session中的时候,会自动触发该方法
	@Override
	public void valueBound(HttpSessionBindingEvent arg0) {
		HttpSession session = arg0.getSession();
		//获取键为"ONLINE_USERS"的空集合
		@SuppressWarnings("unchecked")
		List<NewsUsers> userList = (List<NewsUsers>)session.getServletContext()
								.getAttribute("ONLINE_USERS");
		userList.add(user);
		System.out.println("用户:"+user.getUname()+"注册了");
	}
	
	//当session对象销毁的时候,也就意味着user在线退出了,此时会激活该方法
	@Override
	public void valueUnbound(HttpSessionBindingEvent arg0) {
		HttpSession session = arg0.getSession();
		@SuppressWarnings("unchecked")
		List<NewsUsers> userList = (List<NewsUsers>)session.getServletContext()
								.getAttribute("ONLINE_USERS");
		userList.remove(user);
		System.out.println("用户:"+user.getUname()+"移除了");
	}
}

3.4、HttpSessionListener和HttpSessionBindingListener监听器的区别:

3.4.1、 使用HttpSessionListener

​ UserCountListener实现了HttpSessionListener定义的两个方法:sessionCreated()和sessionDestroyed()。这两个方法可以监听到当前应用中session的创建和销毁情况。

//创建单独的类实现HttpSessionListener接口
public class UserCountListener implements HttpSessionListener {     
	public void sessionCreated(HttpSessionEvent event) {    //重写sessionCreated()方
		ServletContext ctx = event.getSession().getServletContext();  //获取ServletContext对象
		Integer userCount = (Integer)ctx.getAttribute("userCount");   //获取共享的数据
		if(null == userCount){     //创建时空即表示没有人,此时为0
			userCount = new Integer(1);
		}else{                     //创建时不为空即表示没有人,此时再+1
			userCount = new Integer(userCount.intValue()+1);
		}
		ctx.setAttribute("userCount", userCount);   //存入共享的数据
	}

	public void sessionDestroyed(HttpSessionEvent event) {   //重写sessionDestroyed()方法
		ServletContext ctx = event.getSession().getServletContext();
		Integer userCount = (Integer)ctx.getAttribute("userCount");
		if(null == userCount){                 //销毁时空即表示没有人,此时为0
			userCount = new Integer(0);
		}else{                                 //销毁时不为空即表示没有人,此时-1
			userCount = new Integer(userCount.intValue()-1);
		}
		ctx.setAttribute("userCount", userCount);
	}
}

//还要创建单独的用户User类进行用户属性设置(省略)

// 在处理登录页面进行判断及用户session设置		
    String userName = request.getParameter("userName");  //获取到用户信息
    if(userName == null || userName.equals("")){
        response.sendRedirect("enter.jsp");      //如果为空则重新进入登录页面
    }else{
        User user = new User();
        user.setUserName(userName);
        session.setAttribute("user", user);     //把用户名放入在线列表
        response.sendRedirect("online.jsp");
    }

3.4.2、 使用HttpSessionBindingListener

​ OnlineUserBindingListener实现了HttpSessionBindingListener接口,接口中共定义了两个方法:valueBound()和valueUnbound(),分别对应数据绑定,和取消绑定两个事件。对session进行数据绑定,就是调用session.setAttribute()把HttpSessionBindingListener保存进session中。

//1、定义Constants中有一个常量
public class Constants {     
	public static int USER_COUNT=0;
	}

//2、定义user类实现HttpSessionBindingListener接口  
//如下例子为统计在线人数,对session进行监听
public class User implements HttpSessionBindingListener{   
	//此处省略user属性设置及set\get方法
	public void valueBound(HttpSessionBindingEvent event) {     //重写valueBound()方法
		Constants.USER_COUNT++;
	}
	public void valueUnbound(HttpSessionBindingEvent event) {    //重写valueUnbound()方法
		Constants.USER_COUNT--;
	}

//3、在处理登录页面进行判断及用户session设置
    String userName = request.getParameter("userName");//获取到用户信息
    if(userName == null || userName.equals("")){
        response.sendRedirect("enter.jsp");   //如果为空则重新进入登录页面
    }else{
        User user = new User();
        user.setUserName(userName);
        session.setAttribute("user", user);    //把用户名放入在线列表
        response.sendRedirect("online.jsp");
    }

3.4.3、HttpSessionBindingListener和HttpSessionListener之间的区别

监听器 配置 监听范围
HttpSessionListener 需要在web.xml中进行配置 设置一次就可以监听所有session
HttpSessionBindingListener 无需配置,实例化后即可 一对一监听

​ 这种区别成就了HttpSessionBindingListener的优势,我们可以让每个listener对应一个username,这样就不需要每次再去session中读取username,进一步可以将所有操作在线列表的代码都移入listener,更容易维护。

3.5、监听器的配置

​ 为了让监听器发挥作用,我们将它添加到web.xml中进行配置:

<listener>
  <listener-class>
  	anni.OnlineUserListener(监听器实例的全类名)
  </listener-class>
</listener>

以下两种情况下就会发生sessionDestoryed(会话销毁)事件:

1.执行session.invalidate()方法时。

​ 注销登录LogoutServlet.java中执行session.invalidate()时,会触发sessionDestory()从在线用户列表中清除当前用户,我们就不必在LogoutServlet.java中对在线列表进行操作了,所以LogoutServlet.java的内容现在是这样。

public void doGet(HttpServletRequest request,HttpServletResponse response)
    throws ServletException, IOException {
    request.getSession().invalidate();   // 销毁session
    response.sendRedirect("index.jsp");    // 成功
}

2.如果用户长时间没有访问服务器,超过了会话最大超时时间,服务器就会自动销毁超时的session。

​ 会话超时时间可以在web.xml中进行设置

<session-config>
    <session-timeout>1</session-timeout>  
</session-config>

​ 注:时间单位为:分钟,并且只能是整数,如果是零或负数,那么会话就永远不会超时。

JQuery的优缺点

(1).JQuery优点

<1>.JQuery实现脚本与页面的分离
在HTML代码中,我们还经常看到类似这样的代码:
<form id="myform" onsubmit=return validate();">
即使validate()函数可以被放置在一个外部文件中,实际上我们依然是把页面与逻辑和事件混杂在一起。jQuery让你可以将这两部分分离。借助于jQuery,页面代码将如下所示:

接下来,一个单独的JS文件将包含以下事件提交代码: $("myform").submit(function(){ ...your code here )} 这样我们可以实现灵活性非常强的清晰页面代码。jQuery让JavaScript代码从HTML页面代码中分离出来,就像数年前CSS让样式代码与页面代码分离开一样。 **<2>.最少的代码做最多的事情** 最少的代码做最多的事情,这是jQuery的口号,而且名副其实。使用它的高级selector,开发者只需编写几行代码就能实现令人惊奇的效果。开发者无需过于担忧浏览器差异,它除了还完全支持Ajax,而且拥有许多提高开发者编程效率的其它抽象概念。jQuery把JavaScript带到了一个更高的层次。以下是一个非常简单的示例: $("p.neat").addClass("ohmy").show("slow"); 通过以上简短的代码,开发者可以遍历“neat”类中所有的

元素,然后向其增加“ohmy”类,同时以动画效果缓缓显示每一个段落。开发者无需检查客户端浏览器类型,无需编写循环代码,无需编写复杂的动画函数,仅仅通过一行代码就能实现上述效果。 **<3>.性能支持比较好** 在大型JavaScript框架中,jQuery对性能的理解最好。尽管不同版本拥有众多新功能,其最精简版本只有18KB大小,这个数字已经很难再减少。jQuery的每一个版本都有重大性能提高。如果将其与新一代具有更快JavaScript引擎的浏览器(如火狐3和谷歌Chrome)配合使用,开发者在创建富体验Web应用时将拥有全新速度优势。 **<4>.它是一个“标准”** 之所以使用引号,是以为jQuery并非一个官方标准。但是业内对jQuery的支持已经非常广泛。谷歌不但自己使用它,还提供给用户使用。另外戴尔、新闻聚合网站Digg、WordPress、Mozilla和许多其它厂商也在使用它。微软甚至将它整合到Visual Studio中。如此多的重量级厂商支持该框架,用户大可以对其未来放心,大胆的对其投入时间。 **<5>.插件发开** 基于jQuery开发的插件目前已经有大约数千个。开发者可使用插件来进行表单确认、图表种类、字段提示、动画、进度条等任务。jQuery社区已经成长为一个生态系统。这一点进一步证明了上一条理由,它是一个安全的选择。而且,jQuery正在主动与“竞争对手”合作,例如Prototype。它们似乎在推进JavaScript的整体发展,而不仅仅是在图谋一己之私。 **<6>.节约学习成本** 当然要想真正学习jQuery,开发者还是需要投入一点时间,尤其是如果要编写大量代码或自主插件的话,更是如此。但是,开发者可以采取“各个击破”的方式,而且jQuery提供了大量示例代码,入门是一件非常容易的事情。建议开发者在编写某类代码前,首先看一下是否有类似插件,然后看一下实际的插件代码,了解一下其工作原理。简而言之,学习jQuery不需要开发者投入太多,就能够迅速开始开发工作,然后逐渐提高技巧。 **<7>.让JS编程变得有趣** 发现使用jQuery是一件充满乐趣的事情。它简洁而强大,开发者能够迅速得到自己想要的结果。它解决了许多JavaScript问题和难题。通过一些基础性的改进,开发者可以真正去思考开发下一代Web应用,不再因为语言或工具的差劲而烦恼。相信它的“最少的代码做最多的事情”口号。

(2).JQuery的缺点

<1>.不能向后兼容。
每一个新版本不能兼容早期的版本。举例来说,有些新版本不再支持某些selector,新版jQuery却没有保留对它们的支持,而只是简单的将其移除。这可能会影响到开发者已经编写好的代码或插件。
<2>.插件兼容性。
与上一点类似,当新版jQuery推出后,如果开发者想升级的话,要看插件作者是否支持。通常情况下,在最新版jQuery版本下,现有插件可能无法正常使用。开发者使用的插件越多,这种情况发生的几率也越高。我有一次为了升级到jQuery 1.3,不得不自己动手修改了一个第三方插件。
<3>.多个插件冲突。
在同一页面上使用多个插件时,很容易碰到冲突现象,尤其是这些插件依赖相同事件或selector时最为明显。这虽然不是jQuery自身的问题,但却又确实是一个难于调试和解决的问题。
<4>.jQuery的稳定性。
jQuery没有让浏览器崩溃,这里指的是其版本发布策略。jQuery 1.3版发布后仅过数天,就发布了一个漏洞修正版1.3.1。他们还移除了对某些功能的支持,可能会影响许多代码的正常运行。我希望类似修改不要再出现。
<5>.对动画和特效的支持差。
在大型框架中,jQuery核心代码库对动画和特效的支持相对较差。但是实际上这不是一个问题。目前在这方面有一个单独的jQuery UI项目和众多插件来弥补此点。

第六章、Ajax

1、Ajax概念

​ Ajax:Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。Ajax不是某种编程语言,是一种在无需重新加载整个网页的情况下能够更新部分网页的技术。使用Ajax技术的网页,通过在后台跟服务器进行少量的数据交互,网页就可以实现异步局部更新,其async属性. 默认是true,即为异步方式 。udian

1.1、Ajax的缺点

​ 1)、ajax不支持浏览器back按钮。
​ 2)、安全问题 AJAX暴露了与服务器交互的细节。
​ 3)、对搜索引擎的支持比较弱。
​ 4)、破坏了程序的异常机制。
​ 5)、不容易调试。

1.2、请求处理响应的步骤

​ 1)、创建XMLHttpRequest对象,通过window.XMLHttpRequest的返回值判断创建XMLHttpRequest对象的方式。
​ 2)、设置回调函数,通过onreadystatechange属性设置回调函数,其中回调函数需要自定义。
​ 3)、初始化XMLHttpRequest对象,通过open()方法设置请求的发送方式和路径。
​ 4)、发送请求。

1.3、XMLHttpRequest对象创建

​ 传统的ajax实现方式需要使用XMLHttpRequest + JavaScript技术。

​ 所有现代浏览器 (IE7+、Firefox、Chrome、Safari 以及 Opera) 都内建了 XMLHttpRequest 对象。创建 XMLHttpRequest 对象的语法:

xmlHttpRequest = new XMLHttpRequest();

​ 但是对于老版本的 Internet Explorer (IE5 和 IE6),要使用 ActiveX 对象:

xmlHttpRequest = new ActiveXObject("Microsoft.XMLHTTP");

​ 为了应对所有的现代浏览器,包括 IE5 和 IE6,检查浏览器是否支持XMLHttpRequest 对象。如果支持,则创建 XMLHttpRequest 对象。如果不支持,则创建 ActiveXObject :

function createXmlHttpRequest() {
		if (window.XMLHttpRequest) {      //返回值为true时说明是新版本IE或其他浏览器
			return new XMLHttpRequest();
		} else {       //返回值为false时说明是老版本IE浏览器(IE5和IE6)
			return new ActiveXObject("Microsoft.XMLHTTP");
		}
	}

​ 创建XMLHttpRequest对象,允许通过客户端脚本发送HTTP请求;服务器响应状态后,有客户端处理函数执行;XMLHttpRequest获取服务器响应的状态 ,打开连接并发送数据;

1.4、XMLHttpRequest对象的方法

方法名称 说明
abort() 停止当前请求
getAllResponseHeaders() 这个方法返回一个String串,其中包含HTTP请求的所有响应首部,首部包括Content-Length、Date和URL。
getResponseHeader(String header) 返回指定的HTTP响应首部
open(String method,String url,boolean asynch,String username,String password) 建立对服务器新的HTTP请求,method参数可以是get、post或put,URL参数可以是相对URL,也可以是绝对URL,这是必要的参数;还有三个可选的参数:传递一个Boolean值,指示这个调用是异步还是同步的,默认值为true,表示请求是异步的,如果这个参数设置为false,处理就会等待,直到服务器返回响应为止,最后两个参数允许指定一个特定的用户名和密码。
send(content) 向服务器发请求,如果请求声明是异步的,这个方法就会立即返回,否则它会等待直到接收到响应为止。传入这个方法的内容会作为请求整体的一部分发送。
setRequestHeader(String header,String value) 把指定首部设置为所提供的值,在设置任何首部之前必须先调用open();header表示要设置的首部,value表示要在首部中放置的值;post方式才需要设置,get方式不需要设置。

1.5、XMLHttpRequest对象的属性

属性名称 说明
onreadystatechange
(事件)
每个状态改变都会触发这个事件处理器,通常会调用一个JavaScript函数
readyState 请求的状态,有5个可取值:
0=未初始化,1=正在加载,2=已加载,3=交互中,4=完成
responseText 以文本形式获取服务器的响应值
responseXML 服务器的响应,表示为XML,这个对象可以解析为一个DOM对象
status 服务器的HTTP状态码:
200=正确返回,404=找不到访问对象(请求资源不存在)
403=没有访问权限,500=服务器内部错误
statusText HTTP状态码的响应文本,OK或Not Found等等

2、Ajax中POST和GET的区别

区别 GET方式 POST方式
初始化XMLHttpRequest对象 指定XMLHttpRequest对象中的open()方法中的method参数为“get” a、指定XMLHttpRequest对象中的open()方法中的method参数为“post”;b、指定XMLHttpRequest对象要请求的HTTP头信息,该HTTP请求头信息为福鼎写法
发送请求 指定XMLHttpRequest对象中的send()方法中的data参数为“null” 可以指定XMLHttpRequest对象中的send()方法中的data参数的值,即该请求需要携带的具体参数,多个可用“&”连接
初始化XMLHttpRequest对象 代码 XMLHttpRequest.open(“GET”,url,true); XMLHttpRequest.open(“POST”,url,true)
XMLHttpRequest.setRequestHeader(“Content-Type”,“application/x-www-form-urlencoded”);
发送请求 代码 XMLHttpRequest.send(null); XMLHttpRequest.send(“key=xxx&type=11&year=2018”);

1、发送Ajax POST请求

function postMethod(){
    var xhr = new createXHR();
    var userName = document.getElementById("userName").value;
    var age = document.getElementById("age").value;
    var data = "userName=" + encodeURTComponent( userName ) + "&age=" + encodeURTComponent( age );
    xhr.open( "post", "example.php", true );   //不用担心缓存问题
    //必须设置,否则服务器端收不到参数
    xhr.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );

    xhr.onreadystatechange = function(){   //设置回调函数
        if( xhr.readyState = 4 && xhr.status == 200 ){
            document.getElementById("result").innerHTML = xhr.responseText;
        }
    }
    xhr.send( data );     //发送请求,要data数据
}

2、发送Ajax GET请求

function getMethod(){
    var xhr = new createXHR();
    var userName = document.getElementById("userName").value;
    var age = document.getElementById("age").value;

    //添加参数,以求每次访问不同的url,以避免缓存问题
    xhr.open( "get", "example.php?userName=" + encodeURTComponent( userName ) + "&age=" + encodeURTComponent( age ) + "&random=" + Math.random(), true );

    xhr.onreadystatechange = function(){
        if( xhr.readyState == 4 && xhr.status == 200 ){
            document.getElementById("result").innerHTML = xhr.responseText;
        }
    }
    xhr.send( null );      //发送请求,参数为null
}	

3、对Ajax进行封装

//专门提取出来的方法
function ajax(url,method,data,success,error){
    xmlHttpRequest = createXMLRequest();  //调用创建XMLHttpRequest对象方法
    xmlHttpRequest.onreadystatechange=callback;  //设置回调函数
    if(method.toUpperCase()=="GET")    //初始化请求
        xmlHttpRequest.open("GET",url+"?"+data,true);
    else{
        xmlHttpRequest.open("POST",url+"?"+data,true);
        xmlHttpRequest.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
    }
    
    if(method.toUpperCase()=="GET")    //发送请求
        xmlHttpRequest.send(null);
    else
        xmlHttpRequest.send(data);
    
    function callback(){   //回调函数
        if(xmlHttpRequest.readyState==4){
            if(xmlHttpRequest.status==200){   //响应正确完成
                success(xmlHttpRequest.responseText);  //调用自定义成功函数,执行
            }else if(xmlHttpRequest.status==404){
               //显示一句话,页面不存在。。。
            }else if(xmlHttpRequest.status==500){
                error();   //调用自定义出错函数
            }
        }else{
            //显示一个转圈的gif图,请求处理中
        }
    }
   
    function createXMLRequest(){         //创建XMLHttpRequest对象方法
       if (window.XMLHttpRequest) {      //返回值为true时说明是新版本IE或其他浏览器
			return new XMLHttpRequest();
		} else {                        //返回值为false时说明是老版本IE浏览器(IE5和IE6)
			return new ActiveXObject("Microsoft.XMLHTTP");
		}
    }
}
//前台调用上述方法
function validate(){
   var name = $("#name").val();
   if(name==null||name=""){
        $("#nameDiv").html("用户名不能为空!");
   }else{
   		var url ="userServlet";
    	ajax(url,method,data,success,error); //调用执行函数,参数后面error可选
    
    function ifSuccess(data){    //自定义请求成功函数,此处只能使用带参函数来调用xmlHttpRequest对象
        if(data="true"){
             $("#nameDiv").html("用户名已被使用!");
        }else{
             $("#nameDiv").html("用户名可以使用!");
        }
    }
    
    function ifError(){    //自定义出错函数
        //此处省略...
    	}
    }
}	


//使用JSON改造上述方法:
$(document).ready(function(){
	$("#nameDiv").blur(function(){
		var name=this.value;
		if(name==null||name=""){
              $("#nameDiv").html("用户名不能为空!");
		}else{
            var url ="userServlet";
    		
    		$ajax({
    		"url"           :  url,    //请求的url地址
    		"type"          :  "GET"   //请求发送方式
    		"data"          :  "name"+name,    //随请求发送的参数数据
    		"dataType"      :  "text",    //服务器返回的数据类型
    		"success"       :  ifSuccess, //响应成功后要执行的代码(方法也可直接在此处写)
    		"error"         :  function(){alert("验证出现错误,请稍后再试");}
    		});
    
   		 function ifSuccess(data){    //自定义请求成功函数
        	if(data="true"){
             	  	$("#nameDiv").html("用户名已被使用!");
       		 }else{
           		    $("#nameDiv").html("用户名可以使用!");
        	 	}
          	 }
		}
	})		
})

3、jQuery调用Ajax

3.1、语法

$.ajax(*{name:value, name:value, ... }*)

3.2、常用属性参数与函数参数

属性参数 类型 说明
url String (默认为当前页地址)发送请求的地址
type String 请求方式(post或get)默认为get。注意其他http请求方法,例如put和delete也可以使用,但仅部分浏览器支持
data Object或String或Array 发送到服务器的数据。
dataType String 预期服务器返回的数据类型。如果不指定,JQuery将自动根据http包mime信息返回responseXML或responseText,并作为回调函数参数传递。可用的类型有:
xml:返回XML文档,可用JQuery处理。
html:返回纯文本HTML信息,包含的script标签会在插入DOM时执行。
script:返回纯文本JavaScript代码,不会自动缓存结果。除非设置了cache参数。注意在远程请求时(不在同一个域下),所有post请求都将转为get请求。
json:返回JSON数据。
jsonp:JSONP格式,使用SONP形式调用函数时,例如myurl?callback=?,JQuery将自动替换后一个“?”为正确的函数名,以执行回调函数。
text:返回纯文本字符串。
timeout Number 设置请求超时时间(毫秒)。此设置将覆盖$.ajaxSetup()方法的全局设置
global Boolean 表示是否触发全局ajax事件。设置为false将不会触发全局ajax事件,ajaxStart或ajaxStop可用于控制各种ajax事件。默认为true。
函数参数 类型 说明
beforeSend Function
(jqXHR jqxhr,PlainObject srttings)
发送请求前可以修改XMLHttpRequest对象的函数,例如添加自定义HTTP头。在beforeSend中如果返回false可以取消本次ajax请求。jqxhr :可选,jqXHR是XMLHttpRequest的超集
srttings:可选,当前ajax()方法的settings对象
success(即:当readyState=4,status=200) Function(任意类型 result, String textStatus, jqXHR jqxhr) 请求成功后调用的回调函数:
result:表示由服务器返回,并根据dataType参数处理后的数据 textStatus:可选,表示描述状态的字符串。
jqxhr:可选
error Function(jqXHR jqxhr, String textStatus,String errorThrown) 请求失败时被调用的函数:
textStatus:可选,描述错误信息。
jqxhr:可选
errorThrown:可选,表示用文本描述的HTTP状态
complete Function(jqXHR jqxhr, String textStatus) 请求完成后调用的回调函数(请求成功或失败时均调用):
textStatus:可选,描述请求状态的字符串
jqxhr:可选

Ajax回调函数共有5个,分别为beforeSend ,error ,dataFilter ,success ,complete。

补充:

1.async:
要求为Boolean类型的参数,默认设置为true,所有请求均为异步请求, . A j a x 执 行 后 , 会 继 续 执 行 a j a x 后 面 的 脚 本 , 直 到 服 务 器 端 返 回 数 据 后 , 触 发 .Ajax执行后,会继续执行ajax后面的脚本,直到服务器端返回数据后,触发 .Ajaxajax.Ajax里的success方法,这时候执行的是两个线程。若要将其设置为false,则所有的请求均为同步请求,在没有返回值之前,同步请求将锁住浏览器,用户其它操作必须等待请求完成才可以执行。下面查看一个示例:

var temp;
$.ajax({
    async       :   false,
    type        :   "POST",
    url         :   defaultPostData.url,
    dataType    :   'json',
    success     :   function(data) {
          temp=data; }
});

alert(temp);

​ 这个ajax请求为同步请求,在没有返回值之前,alert(temp)是不会执行的。如果async设置为:true,则不会等待ajax请求返回的结果,会直接执行ajax后面的语句。

2.cache:
要求为Boolean类型的参数,默认为true(当dataType为script时,默认为false),设置为false将不会从浏览器缓存中加载请求信息。

3.contentType
要求为String类型的参数,当发送信息至服务器时,内容编码类型默认为"application/x-www-form-urlencoded"。该默认值适合大多数应用场合。

4.dataFilter
要求为Function类型的参数,给Ajax返回的原始数据进行预处理的函数。提供data和type两个参数。data是Ajax返回的原始数据,type是调用jQuery.ajax时提供的dataType参数。函数返回的值将由jQuery进一步处理。

 function(data, type){
                //返回处理后的数据
                return data;
            }

5.dataFilter
要求为Function类型的参数,给Ajax返回的原始数据进行预处理的函数。提供data和type两个参数。data是Ajax返回的原始数据,type是调用jQuery.ajax时提供的dataType参数。函数返回的值将由jQuery进一步处理。

 function(data, type){
                //返回处理后的数据
                return data;
            }

6.ifModified
要求为Boolean类型的参数,默认为false。仅在服务器数据改变时获取新数据。服务器数据改变判断的依据是Last-Modified头信息。默认值是false,即忽略头信息。

7.jsonp
要求为String类型的参数,在一个jsonp请求中重写回调函数的名字。该值用来替代在"callback=?"这种GET或POST请求中URL参数里的"callback"部分,例如{jsonp:‘onJsonPLoad’}会导致将"onJsonPLoad=?"传给服务器。

8.username
要求为String类型的参数,用于响应HTTP访问认证请求的用户名。

9.password
要求为String类型的参数,用于响应HTTP访问认证请求的密码。

10.processData
要求为Boolean类型的参数,默认为true。默认情况下,发送的数据将被转换为对象(从技术角度来讲并非字符串)以配合默认内容类型"application/x-www-form-urlencoded"。如果要发送DOM树信息或者其他不希望转换的信息,请设置为false。

11.scriptCharset
要求为String类型的参数,只有当请求时dataType为"jsonp"或者"script",并且type是GET时才会用于强制修改字符集(charset)。通常在本地和远程的内容编码不同时使用。

3.2.1、$.parseJSON()方法

​ 用来将JSON格式的字符串解析为JSON对象

//用JavaScript定义一个数组如下,然后转换成json传到后台:
var student = new Object(); 
student.name = "Lanny"; 
student.age = "25"; 
student.location = "China"; 
students = (students || []).push(student); 
var json = JSON.stringify(students); 

var students = []; 
students[students.length] = new Object(); 
students[students.length] .name = "Lanny"; 
students[students.length] .age = "25"; 
students[students.length] .location = "China"; 
var json = JSON.stringify(students); 

3.2.2、定义JSON对象和数组

定义JSON对象:

var JSON对象 = {name:value ,name:value,...};
var person = {"name":"张三","age":30}

定义JSON数组

var JSON数组 = [value,value,...];
字符串数组:["中国","美国","俄罗斯"]
对象数组:[{"name":"张三","age":30},{"name":"李四","age":25}]

3.3、jQuery ajax 方法

3.3.1、$.get() 方法

​ get() 方法通过远程 HTTP GET 请求载入信息。这是一个简单的 GET 请求功能以取代复杂 $.ajax 。请求成功时可调用回调函数。如果需要在出错时执行函数,请使用 $.ajax。语法如下:

$(selector).get(url,data,success(response,status,xhr),dataType)
参数 描述
url 必需。规定将请求发送的哪个 URL。
data 可选。规定连同请求发送到服务器的数据。
success(response,status,xhr) 可选。规定当请求成功时运行的函数。response - 表示来自请求的结果数据;status - 表示请求的状态;xhr - 表示 XMLHttpRequest 对象
dataType 可选。规定预计的服务器响应的数据类型。默认地,jQuery 将智能判断。可能的类型:“xml”“html”“text”“script”“json”“jsonp”,默认为"text"

​ 上述语法等价于如下Ajax 函数写法:

$.ajax({
  url: url,
  type: 'GET',   //$.get()即说明type=get,所以没有这个属性
  data: data,
  success: success,
  dataType: dataType
});

示例:点击button按钮获取文本内容,并填充到div元素中

$("button").click(function(){
    $.get("demo_ajax_load.text" ,function(result){
        $("div").html(result);
    });
})

3.3.2、$.post() 方法

​ post() 方法通过 HTTP POST 请求从服务器载入数据。一般用于向指定的资源提交要处理的数据,语法如下:

jQuery.post(url,data,success(data, textStatus, jqXHR),dataType)
参数 描述
url 必需。规定把请求发送到哪个 URL。
data 可选。映射或字符串值。规定连同请求发送到服务器的数据。
success(data, textStatus, jqXHR) 可选。请求成功时执行的回调函数。额外的参数:response - 表示来自请求的结果数据;status - 表示请求的状态;xhr - 表示 XMLHttpRequest 对象
dataType 可选。规定预期的服务器响应的数据类型。默认执行智能判断(xml、json、script 或 html)。

​ 上述语法等价于如下Ajax 函数写法:

$.ajax({
  type: 'POST',   //$.post()即说明type=post,所以没有这个属性
  url: url,
  data: data,
  success: success,
  dataType: dataType
});

​ post,get这两者都是明文传送,但$.post方法更安全是因为GET的URL会被放在浏览器历史和WEB 服务器日志里面。而POST发完不会放在日志WEB等留下痕迹,直接是没有了。

​ get()方法可能会缓存数据,Post()方法不会缓存数据。

3.3.3、$.getJSON() 方法

​ 通过使用 JSONP 形式的回调函数来加载其他网域的 JSON 数据,如 “myurl?callback=?”。jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。 注意:此行以后的代码将在这个回调函数执行前执行。语法如下:

jQuery.getJSON(url,data,success(data,status,xhr))
注:其返回的数据类型只能是JSON格式,且只能以get方法进行提交。
参数 描述
url 必需。规定将请求发送的哪个 URL。
data 可选。规定连同请求发送到服务器的数据。
success(data,status,xhr) 可选。规定当请求成功时运行的函数。额外的参数:response - 表示来自请求的结果数据;status - 表示请求的状态;xhr - 表示 XMLHttpRequest 对象

​ 上述语法等价于如下Ajax 函数写法:

$.ajax({
  url: url,
  data: data,
  success: callback,
  dataType: json    //$.getJSON()即说明dataType=json,所以没有这个属性
});

​ 发送到服务器的数据可作为查询字符串附加到 URL 之后。如果 data 参数的值是对象(映射),那么在附加到 URL 之前将转换为字符串,并进行 URL 编码。传递给 callback 的返回数据,可以是 JavaScript 对象,或以 JSON 结构定义的数组,并使用 $.parseJSON() 方法进行解析。

​ 从服务器端获取的json对象,可以直接访问对象中的属性

//从服务器端PHP页面获取json数据,并显示对象age属性的值。
$.getJSON("$.getJSON.PHP",{},function(response){
    alert(response.age);
});

3.3.4、$().load() 方法

​ load() 方法通过 AJAX 请求从服务器加载HTML内容,并把返回的数据放置到指定的元素中。语法如下:

$(selector).load(url,data,function(response,status,xhr))
参数 描述
url 规定要将请求发送到哪个 URL。
data 可选。规定连同请求发送到服务器的数据。
function(response,status,xhr) 可选。规定当请求完成时运行的函数。额外的参数:response - 包含来自请求的结果数据status - 包含请求的状态(“success”, “notmodified”, “error”, “timeout” 或 “parsererror”)xhr - 包含 XMLHttpRequest 对象

​ 该方法是最简单的从服务器获取数据的方法。它几乎与 $.get(url, data, success) 等价,不同的是它不是全局函数,并且它拥有隐式的回调函数。当侦测到成功的响应时(比如,当 textStatus 为 “success” 或 “notmodified” 时),.load() 将匹配元素的 HTML 内容设置为返回的数据。这意味着该方法的大多数使用会非常简单:

$("#result").load("ajax/test.html",data);

​ 该方法默认使用GET方式发送请求,除非提供的data参数是一个对象,则使用POST方式发送(自动切换方式),故以上代码等同于如下:

$.get("ajax/test.html",data,function(responseText){
    $("#result").html(responseText);
});

​ 如果提供回调函数,则会在执行 post-processing 之后执行该函数:

$("#result").load("ajax/test.html", data, function() {
  alert("Load was performed.");
});

​ 上面的两个例子中,如果当前文档不包含 “result” ID,则不会执行 .load() 方法。

特别地,.load()方法可加载页面片段

​ .load() 方法,与 $.get() 不同,允许我们规定要插入的远程文档的某个部分。这一点是通过 url 参数的特殊语法实现的。如果该字符串中包含一个或多个空格,紧接第一个空格的字符串则是决定所加载内容的 jQuery 选择器。我们可以修改上面的例子,这样就可以使用所获得文档的某部分:

$("#result").load("ajax/test.html #container");  //对获取的页面进行筛选

//如果执行该方法,则会取回 ajax/test.html 的内容,不过然后,jQuery 会解析被返回的文档,来查找带有容器 ID 的元素。该元素,连同其内容,会被插入带有结果 ID 的元素中,所取回文档的其余部分会被丢弃。

3.3.5、$().serializeArray()方法

​ serializeArray() 方法通过序列化表单值来创建对象数组(名称和值)。可以选择一个或多个表单元素(比如 input 及/或 textarea),或者 form 元素本身。语法如下:

$(selector).serializeArray();

​ serializeArray() 方法序列化表单元素(类似 .serialize() 方法),返回 JSON 数据结构数据。注意:此方法返回的是 JSON 对象而非 JSON 字符串。此方法会从一组表单元素中检测有效控件,将其序列化成由name和value两个属性组成的JSON对象的数组(如果 value 不为空的话)。举例如下来说:

[{name: 'firstname', value: 'Hello'}, 
  {name: 'lastname', value: 'World'},
  {name: 'alias'} // 值为空]

​ .serializeArray() 方法使用了 W3C 关于 successful controls(有效控件) 的标准来检测哪些元素应当包括在内。有效控件的规包括如下:a、元素不能被禁用;b、元素必须有name属性;c、选中的checkbox才是有效的;d、选中的radio才是有效的;e、只有触发提交事件的submit按钮才是有效的,其他按钮不行;f、file元素不会被序列化。

​ 该方法可以对已选择单独表单元素的对象进行操作,比如 , , 和 。不过,更方便的方法是,直接选择 标签自身来进行序列化操作。

$("form").submit(function() {
  console.log($(this).serializeArray());
  return false;
});

​ 上面的代码产生下面的数据结构(假设浏览器支持 console.log):

[
  {
    name: a
    value: 1
  },
  {
    name: b
    value: 2
  },
  {
    name: c
    value: 3
  },
  {
    name: d
    value: 4
  },
  {
    name: e
    value: 5
  }
]

3.3.6、$.param()方法

​ param() 方法创建数组或对象的序列化表示。用于在内部将元素值转换为序列化的字符串表示,该序列化值可在进行 AJAX 请求时在 URL 查询字符串中使用。语法如下:

jQuery.param(object,traditional)
参数 描述
object 要进行序列化的数组或对象。
traditional 规定是否使用传统的方式浅层进行序列化(参数序列化)。

​ 对于 jQuery 1.3,如果传递的参数是一个函数,那么用 .param() 会得到这个函数的返回值,而不是把这个函数作为一个字符串来返回。

​ 对于 jQuery 1.4,.param() 方法将会通过深度递归的方式序列化对象,以便符合现代化脚本语言的需求,比如 PHP、Ruby on Rails 等。你可以通过设置 jQuery.ajaxSettings.traditional = true; 来全局地禁用这个功能。

​ 如果被传递的对象在数组中,则必须是以 .serializeArray() 的返回值为格式的对象数组:

[{name:"first",value:"Rick"},
{name:"last",value:"Astley"},
{name:"job",value:"Rock Star"}]

​ 注意:因为有些框架在解析序列化数字的时候能力有限,所以当传递一些含有对象或嵌套数组的数组作为参数时,请务必小心!在 jQuery 1.4 中,HTML5 的 input 元素也会被序列化。

3.3.7、$().serialize() 方法

​ serialize() 方法通过序列化表单值,创建 URL 编码文本字符串。

$(selector).serialize()

​ 实际上.serialize() 方法内部就是使用$.param()方法对.serializeArray()方法做了简单包装,对于不需要中间环节的情景, 可以更方便地完成表单数据的序列化。

3.3.8、each()遍历方法

​ each() 方法规定为每个匹配元素规定运行的函数。返回 false 可用于及早停止循环。语法如下:

$(selector).each(function(index,element))
参数 描述
function(index,element) 必需。为每个匹配元素规定运行的函数。
index - 选择器的 index 位置,下标从0开始;
element - 当前的元素(也可使用 “this” 选择器)

​ 实例,输出每个 li 元素的文本:

$("button").click(function(){
  $("li").each(function(){
    alert($(this).text())
  });
});

1、选择器+遍历

$('div').each(function (i){
   i就是索引值
   this 表示获取遍历每一个dom对象
});

2、选择器+遍历

$('div').each(function (index,domEle){
   index就是索引值
  domEle 表示获取遍历每一个dom对象
});

3、更适用的遍历方法

​ 1)先获取某个集合对象

​ 2)遍历集合对象的每一个元素

var d=$("div");
$.each(d,function (index,domEle){
  d是要遍历的集合
  index就是索引值
  domEle 表示获取遍历每一个dom对象
});

示例:

// each处理一维数组
  var arr1 = [ "aaa", "bbb", "ccc" ];      
  $.each(arr1, function(i,val){      
      alert(i);   
      alert(val);
  });

//处理一维数组
vararr = [ "one","two", "three","four"];
$.each(arr,function(){
alert(this);//this 特指当前数据(具体某个)
});
//上面这个each输出的结果分别为:one,two,three,four

//处理二维数组
vararr1 = [[1, 4, 3], [4, 6, 6], [7, 20, 9]]
$.each(arr1,function(i, item){
alert(item[0]);
});
//其实arr1为一个二维数组,item相当于取每一个一维数组,
//item[0]相对于取每一个一维数组里的第一个值
//所以上面这个each输出分别为:1 4 7 

// 处理json数据,例如ajax的返回值     
  var obj = { one:1, two:2, three:3};      
 $.each(obj, function(key, val) {      
      alert(key); 
      alert(val); 
  });

3.3.9、补充 .on() 方法

​ on() 方法在被选元素及子元素上添加一个或多个事件处理程序。

$(父元素).on(事件,针对子元素的选择器,data,function);
$(selector).on(event,childSelector,data,function);
参数 描述
event 必需。规定要从被选元素移除的一个或多个事件或命名空间。 由空格分隔多个事件值,也可以是数组。必须是有效的事件。
childSelector 可选。一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素(且不是选择器本身,比如已废弃的 delegate() 方法)。如果选择器是 null 或者忽略了该选择器,那么被选中的元素总是能触发事件。
data 可选。 当一个事件被触发时,要传递给事件处理函数的 event.data。
function 可选。 事件被触发时,执行的函数。若该函数只是要执行return false的话,那么该参数位置可以直接简写成 false

示例:

多个事件绑定同一个函数
$("table.planning_grid").on({ 
    mouseenter: function() { 
        // Handle mouseenter... 
    }, 
    mouseleave: function() { 
        // Handle mouseleave... 
    }, 
    click: function() { 
        // Handle click... 
    } 
}, "td");


var $optArea = $("#opt_area");
//如果$optArea域中有加载了#addTopicSubmit子元素,则点击此元素时会激发执行function函数
$optArea.on("click", "#addTopicSubmit", function() {
		var $tname = $optArea.find("#tname");
		var tnameValue = $tname.val();
		if (tnameValue == "") {
			$msg.html("请输入主题名称!").fadeIn(1000).fadeOut(5000);
			$tname.focus();
			return false;
		}

4、fastjson

4.1、JSON规范说明

​ Fastjson是一个Java语言编写的JSON处理器,由阿里巴巴公司开发。fastjson是目前java语言中最快的json库,比自称最快的jackson速度快。 FastJson是一个Json处理工具包,包括“序列化”和“反序列化”两部分,Fastjson是一个Java语言编写的高性能功能完善的JSON库。Fastjson支持java bean的直接序列化。 有如下几个特点优势:

​ 1、遵循http://json.org标准,为其官方网站收录的参考实现之一。
​ 2、功能强大,支持JDK的各种类型,包括基本的JavaBean、Collection、Map、Date、Enum、泛型。
​ 3、无依赖,不需要额外的jar,能够直接跑在JDK上。
​ 4、开源,使用Apache License 2.0协议开源。http://code.alibabatech.com/wiki/display/FastJSON/Home

获得Fastjson的途径有如下:
SVN:http://code.alibabatech.com/svn/fastjson/trunk/
WIKI:http://code.alibabatech.com/wiki/display/FastJSON/Home
Issue Tracking:http://code.alibabatech.com/jira/browse/FASTJSON

4.2、主要的使用入口

​ Fastjson API入口类是com.alibaba.fastjson.JSON,常用的序列化操作都可以在JSON类上的静态方法直接完成。具体有如下:

A、public static final Object toJSON(Object javaObject); 
	//将JavaBean转换为JSONObject或者JSONArray。

B、public static final String toJSONString(Object object); 
	// 将JavaBean序列化为JSON文本 

C、public static final String toJSONString(Object object, boolean prettyFormat); 
	// 当prettyFormat为true时将JavaBean序列化为带格式的JSON文本,当prettyFormat为false时,
	功能同	toJSONString(Object object)

D、public static final String toJSONString(Object object, SerializerFeature... features); 
	// 可以通过features参数指定更多序列化规则。

E、public static final String toJSONStringWithDateFormat(Object object, String DateFormat, 
	SerializerFeature... features); 
	//可以通过DateFormat参数指定日期的输出格式

F、public static final Object parse(String text); 
	// 把JSON文本parse为JSONObject或者JSONArray 

G、public static final JSONObject parseObject(String text); 
	// 把JSON文本parse成JSONObject    

H、public static final <T> T parseObject(String text, Class<T> clazz); 
	// 把JSON文本parse为JavaBean 

I、public static final JSONArray parseArray(String text); 
	// 把JSON文本parse成JSONArray 

J、public static final <T> List<T> parseArray(String text, Class<T> clazz); 
	//把JSON文本parse成JavaBean集合 

注:如下关于有关类库的一些说明:

SerializeWriter:相当于StringBuffer
JSONArray:相当于List<Object>
JSONObject:相当于Map<String, Object>
JSON反序列化没有真正数组,本质类型都是List<Object>

4.3、序列化API

package com.alibaba.fastjson;

public abstract class JSON {
    // 将Java对象序列化为JSON字符串,支持各种各种Java基本类型和JavaBean
    public static String toJSONString(Object object, SerializerFeature... features);

    // 将Java对象序列化为JSON字符串,返回JSON字符串的utf-8 bytes
    public static byte[] toJSONBytes(Object object, SerializerFeature... features);

    // 将Java对象序列化为JSON字符串,写入到Writer中
    public static void writeJSONString(Writer writer, Object object, 
                                       SerializerFeature... features);

    // 将Java对象序列化为JSON字符串,按UTF-8编码写入到OutputStream中
    public static final int writeJSONString(OutputStream os, Object object, 
                                            SerializerFeature... features);
}

4.4、JSON字符串反序列化API

public abstract class JSON {
    // 将JSON字符串反序列化为JavaBean
    public static <T> T parseObject(String jsonStr, Class<T> clazz, 
                                    Feature... features);

    // 将JSON字符串反序列化为JavaBean
    public static <T> T parseObject(byte[] jsonBytes,  // UTF-8格式的JSON字符串
                                    Class<T> clazz, 
                                    Feature... features);

    // 将JSON字符串反序列化为泛型类型的JavaBean
    public static <T> T parseObject(String text, TypeReference<T> type, 
                                    Feature... features);

    // 将JSON字符串反序列为JSONObject
    public static JSONObject parseObject(String text);
}

4.5、枚举类型SerializerFeature的多序列化属性

​ SerializerFeature中定义了多种序列化属性,常用的见如下(使用时直接 “SerializerFeature.属性”):

枚举值 说明
QuoteFieldNames 输出key时是否使用双引号,默认为true,即:要使用
WriteMapNullValue 是否输出值为null的字段,默认为false,不输出
WriteNullListAsEmpty List字段如果为null,输出为[ ],而非null
WriteNullStringAsEmpty 字符类型字段如果为null,输出为"",而非null
WriteNullNumberAsZero 数值字段如果为null,输出为0,而非null
WriteNullBooleanAsFalse Boolean字段如果为null,输出为false,而非null
SkipTransientField 如果是true,类中的Get方法对应的Field是@transient(此注解表示当前的属性不参与序列化),序列化时会被忽略。默认为true
PrettyFormat 结果是否格式化,默认为false
UseSingleQuotes 使用单引号而不是双引号,默认为false
WriteEnumUsingToString Enum输出name()或者original,默认为false
UseISO8601DateFormat Date使用ISO8601格式输出,默认为false
SortField 按字段名称排序后输出。默认为false
WriteTabAsSpecial 把\t做转义输出,默认为false
WriteClassName 序列化时写入类型信息,默认为false。反序列化时需用到
DisableCircularReferenceDetect 消除对同一对象循环引用的问题,默认为false
WriteSlashAsSpecial 对斜杠’/'进行转义
BrowserCompatible 将中文都会序列化为\uXXXX格式,字节数会多一些,但是能兼容IE 6,默认为false
WriteDateUseDateFormat 全局修改日期格式,默认为false。JSON.DEFFAULT_DATE_FORMAT = “yyyy-MM-dd”;JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);
DisableCheckSpecialChar 一个对象的字符串属性中如果有特殊字符如双引号,将会在转成json时带有反斜杠转移符。如果不需要转义,可以使用这个属性。默认为false

5、jQuery让渡“$”操作符

​ jQuery不是唯一使用 的 脚 本 库 , 项 目 中 如 果 有 其 他 同 样 使 用 的脚本库,项目中如果有其他同样使用 使的脚本库时就会引起冲突,比如以下情况会进行符号覆盖,所以有必要对$作让渡使用:

//按导入的包的先后顺序,后引入的会覆盖先引入的,即Propotype的$会覆盖jQuery的$
<script type="text/javasscript" src="../js/jquery-1.12.4.min.js"/>
<script type="text/javasscript" src="../js/propotype的.js"/>

//jQuery的$会覆盖Propotype的$
<script type="text/javasscript" src="../js/propotype的.js"/>
<script type="text/javasscript" src="../js/jquery-1.12.4.min.js"/>

5.1、使用jQuery替代$

jQuery.noConflict();     //让渡"$"的使用权,其他脚本库可以使用"$"
jQuery("#show").click(...);

5.2、使用其他符号替代

var $j=jQuery.noConflict();    //让渡"$"的使用权,其他脚本库可以使用"$"
$j("#show").click(...);

5.3、在代码块中继续使用

​ 以上两个方法改变了jQuery的编码风格,繁琐且不利于重用已有代码,所以可使用如下方法:

jQuery.noConflict();     //让渡"$"的使用权,其他脚本库可以使用"$"
jQuery(document).ready(function($){   //此处的function($)的$也可以使用其他符号
    //在此代码块中可以继续使用"$"编写jQuery代码
    ...//省略其他代码
});

或者如下写法:

jQuery.noConflict();    //让渡"$"的使用权,其他脚本库可以使用"$"
(function($){    
    //在此代码块中可以继续使用"$"编写jQuery代码
    $(document).ready(function(){
   ...//省略其他代码
   });
})(jQuery);

传统web和Ajax的差异:

技术 发送请求方式 服务器响应 客户端处理的响应方式
传统web 通过浏览器发送同步请求 一个完整页面 需等待服务器响应完成后重新
加载整个页面后用户才可以进行操作
Ajax技术 异步引擎对象发送请求 只是需要的数据 可以动态更新页面中的部分内容
不影响用户在页面进行其他操作

6、java中JSONObject,JSONArray,Map,String之间转换

各个JSON技术的简介和优劣

1、json-lib

​ json-lib最开始的也是应用最广泛的json解析工具,json-lib 不好的地方确实是依赖于很多第三方包, 包括commons-beanutils.jar,commons-collections-3.2.jar,commons-lang-2.6.jar,commons-logging-1.1.1.jar,ezmorph-1.0.6.jar, 对于复杂类型的转换,json-lib对于json转换成bean还有缺陷,比如一个类里面会出现另一个类的list或者map集合,json-lib从json到bean的转换就会出现问题。 json-lib在功能和性能上面都不能满足现在互联网化的需求。

2、阿里巴巴的FastJson

​ Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。 无依赖,不需要例外额外的jar,能够直接跑在JDK上。 FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。 FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。

6.1、String转JSONObject

(1).

String jsonMessage = "{\"语文\":\"88\",\"数学\":\"78\",\"计算机\":\"99\"}";
JSONObject  myJson = JSONObject.fromObject(jsonMessage);

(2).用阿里巴巴的fastjson的jar包

String str = "{\"baid\":null,\"32d3:\":\"null\",433:\"0x32\",032:\"ju9fw\"}";
com.alibaba.fastjson.JSONObject jm = com.alibaba.fastjson.JSON.parseObject(str);

6.2、String转JSONArray

String jsonMessage = "[
{'num':'成绩', '外语':88, '历史':65, '地理':99, 'object':{'aaa':'1111','bbb':'2222','cccc':'3333'}}," +
"{'num':'兴趣', '外语':28, '历史':45, '地理':19, 'object':{'aaa':'11a11','bbb':'2222','cccc':'3333'}}," +
"{'num':'爱好', '外语':48, '历史':62, '地理':39, 'object':{'aaa':'11c11','bbb':'2222','cccc':'3333'}}]";
 JSONArray myJsonArray = JSONArray.fromObject(jsonMessage);
 System.out.println(myJsonArray);

6.3、String转Map

(1)

String jsonMessage = "{\"语文\":\"88\",\"数学\":\"78\",\"计算机\":\"99\"}";
JSONObject  myJson = JSONObject.fromObject(jsonMessage);
Map m = myJson; 

(2) 用阿里巴巴的fastjson的jar包

String str = "{\"baid\":null,\"32d3:\":\"null\",433:\"0x32\",032:\"ju9fw\"}";
Map mapTypes = com.alibaba.fastjson.JSON.parseObject(str);

(3) 需要引入jackjson的core、databind、annotations三个jar包

String json = "{\"PayPal key2\":\"PayPal value2\",\"PayPal key1\":\"PayPal value1\",\"PayPal key3\":\"PayPalvalue3\"}";  

ObjectMapper mapper = new ObjectMapper();    
 Map<String,Object> m = mapper.readValue(json, Map.class);  

(4) 特殊格式的String

String a ="{se=2016, format=xml, at=en co=3}";

a =  a.substring(1, a.length()-1);
Map docType = new HashMap();  
Java.util.StringTokenizer items;  
for(StringTokenizer entrys = new StringTokenizer(a, ", ");entrys.hasMoreTokens();   
docType.put(items.nextToken(), items.hasMoreTokens() ? ((Object) (items.nextToken())) : null)){ 
     items = new StringTokenizer(entrys.nextToken(), "=");  
   }

6.4、JSONObject、JSONArray,Map转String

6.4.1、JSONObject——String:

System.out.println(myJsonObject);//可直接输出JSONObject的内容

myJsonObject.toString();

阿里巴巴fastjson:

String str = JSON.toJSONString(obj,SerializerFeature.BrowserCompatible);

6.4.2、JSONArray——String:

System.out.println(myJsonArray);//可直接输出myJsonArray的内容
myJsonArray.toString();

6.4.3、Map——String:

System.out.println(map);//可直接输出map的内容
map.toString(); 

6.5、JSONObject转JSONArray

6.6、JSONObject转Map

JSONObject  myJson = JSONObject.fromObject(jsonString);
Map m = myJson; 

6.7、JSONArray转JSONObject

for(int i=0 ; i < myJsonArray.length() ;i++)
   {
    //获取每一个JsonObject对象
    JSONObject myjObject = myJsonArray.getJSONObject(i);
}

6.8、JSONArray转Map

6.9、Map转JSONObject

JSONObject json = JSONObject.fromObject( map );   
或
String jsonString = JSON.toJSONString(map,SerializerFeature.WriteMapNullValue,
									SerializerFeature.WriteNullStringAsEmpty);
JSONObject jm = JSON.parseObject(str);
(此为alibaba的fastjson)

Map转json格式的String
JSON.toJSONString(map, SerializerFeature.BrowserCompatible).replace("\\\\u", "\\u");
//需要引入alibaba的fastjson包

6.10、Map转JSONArray

 JSONArray.fromObject(map);

6.11、List转JSONArray

JSONArray jsonArray2 = JSONArray.fromObject( list );  

fastjson:List转JSONArray

(1)List<Object> list1 = new ArrayList<Object>();
        list1.add("false");
        list1.add(true);
        list1.add(null);
        list1.add(0x13e);
        list1.add(0123);
        JSONArray array1 = JSONArray.parseArray(JSON.toJSONString(list1));

(2)JSONArray jsonArray = new JSONArray(list1) ;

6.12、JSONArray转List

List<Map<String,Object>> mapListJson = (List)jsonArray;

或

public static List<Map<String, Object>> jsonArrayToList(JSONArray ja){  
        return JSONArray.toJavaObject(ja, List.class);  
    } 

fastjson:JSONArray转List

JSONArray arr = new JSONArray();
        arr.add(0,"13");
        arr.add(1,"jo");
        arr.add(2,"kpo");
        List<String> list = JSONObject.parseArray(array.toJSONString(), String.class); 

JSONArray array = new JSONArray();
List<T> list = JSONObject.parseArray(array.toJSONString(), T.class);

6.13、String转数组

String string = "a,b,c";
String [] stringArr= string.split(",");  //注意分隔符是需要转译滴...

如果是"abc"这种字符串,就直接

String string = "abc" ;
char [] stringArr = string.toCharArray(); //注意返回值是char数组

如果要返回byte数组就直接使用getBytes方法就ok了~~

String string = "abc" ;
byte [] stringArr = string.getBytes();

String转List

String str = "";
List<T> list = JSONObject.parseArray(str, T.class);

数组转String

char[] data={a,b,c}; 
String s=new String(data); 

6.14、java实体类的List转String

JSON与String关系比较近,而JSON的子类JSONArray又和List关系比较近,故把JSONArray做中介过渡:

import com.alibaba.fastjson.JSONArray;

public String list2str(List<?> list){
JSONArray jsonArray = (JSONArray) JSONArray.toJSON(list);   //List转JSONArray
System.out.println(jsonArray.toString());
return jsonArray.toJSONString();    //JSONArray比较容易转String
}

6.15、Array、List、Set之间转换:

String[] arr = new String[]{"Tom", "Bob", "Jane"};
//Array转List
List<String> arr2list = Arrays.asList(arr);
//Array转Set
Set<String> arr2set = new HashSet<String>(Arrays.asList(arr));
//List转Array
Object[] list2arr = arr2list.toArray();
//List转Set
Set<String> list2set = new HashSet<>(arr2list);
//Set转Array
Object[] set2arr = list2set.toArray();
//Set转List
List<String> set2list = new ArrayList<>(arr2set);

List<String> list = new ArrayList<String>(new HashSet<String>());//

6.16、JSON.stringify() 方法

​ 用于将 JavaScript 值转换为 JSON 字符串。

JSON.stringify(value[, replacer[, space]])

参数说明:

  • value: 必需, 要转换的 JavaScript 值(通常为对象或数组)。

  • replacer: 可选。用于转换结果的函数或数组。

    如果 replacer 为函数,则 JSON.stringify 将调用该函数,并传入每个成员的键和值。使用返回值而不是原始值。如果此函数返回 undefined,则排除成员。根对象的键是一个空字符串:""。

    如果 replacer 是一个数组,则仅转换该数组中具有键值的成员。成员的转换顺序与键在数组中的顺序一样。

  • space: 可选,文本添加缩进、空格和换行符,如果 space 是一个数字,则返回值文本在每个级别缩进指定数目的空格,如果 space 大于 10,则文本缩进 10 个空格。space 也可以使用非数字,如:\t。

例子:
JSON.parse()【从一个字符串中解析出json对象】
var data='{"name":"goatling"}'  //定义一个字符串
JSON.parse(data)  //解析对象
结果是:name:"goatling"

JSON.stringify()【从一个对象中解析出字符串】
var data={name:'goatling'}
JSON.stringify(data)
结果是:'{"name":"goatling"}'

7、各JSON技术比较如下:

7.1、json-lib

​ json-ib是最早也是应用最广泛的JSON解析工具,它的缺点就是依赖很多的第三方包,如commons-beanutils.jar、commons- collections-3.2.jar、 commons-lang-2.6.jar、commons-logging-1.1.1.jar、 ezmorph-1.0.6.jar。并且对于复杂类型的转换,json-lib 在将JSON转换成Bean时存在缺陷,如当一个类里包含另一个类的List或者Map集合,json-ib 从JSON到Bean的转换就会出现问题。所以json-lib在功能和性能上都不能满足现在互联网化的需求。

7.2、Jackson

​ 开源的Jackson是Spring MVC内置的JSON转换工具。相比json-lib框架,Jackson 所依赖的jar文件较少,简单易用并且性能也要相对好些。Jackson社区比较活跃,更新速度也比较快。但是Jackson对于复杂类型的JSON转换Bean会出现问题,一些集合Map, List 的转换也不能正常实现;而Jackson 对于复杂类型的Bean转换JSON,转换的JSON格式不是标准格式。

7.3、Gson

​ Gson是目前功能最全的JSON解析器。Gson 最初是应Google公司内部需求而由Google自行研发的,自从2008年5月公开发布第1版后已被许多公司或用户应用。

​ Gson的主要应用是toJson与fromJson两个转换函数,无依赖且不需要额外的jar文件,能够直接运行在JDK上。Gson 可以完成复杂类型的JSON到Bean或Bean到JSON的转换,是JSON解析的利器。其在功能上无可挑别,但是性能比FastJson稍差。

7.4、FastJson

​ FastJson 是一个用Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。它的特点也是无依赖、不需要额外的jar文件,能够直接运行在JDK上。但是FastJson在复杂类型的Bean转换JSON上会出现一些问题,会出现引用的类型不当,导致JSON转换出错,因而需要指定引用。FastJson采用独创的算法,将解析的速度提升到极致,超过所有JSON库。

**总结:**通过以上4种JSON技术的比较,在项目选型的时候可以并行使用Google的Gson和阿里巴巴的Fastson。若只有功能要求,没有性能要求,可以使用Google的Gson;若有性能上面的要求可以使用Gson将Bean转换到JSON以确保数据的正确,再使用FasUson 将JSON转换到Bean。

反射机制

​ 反射是指在程序运行期间,能够观察和修改类或者类的对象的属性和行为的特性。

常见的使用场景为:使用JDBC连接数据库(class.forname)、Servlet在Web容器中的加载和运行

Java反射机制提供了以下的功能:

​ ◆ 在运行时获取类的修饰符,包名,类名,实现的接口,继承的父类

​ ◆ 在运行时获取类的所有属性名,修饰符,属性类型

​ ◆ 在运行时获取所有方法,方法的返回值类型,方法名,方法参数数量,方法参数类型

​ ◆ 在运行时根据类名动态调用加载类的方法,可以降低耦合度。

反射API常用的类:

​ ◆ java.lang.Class(返回与带有给定字符串名的类或接口相关联的Class对象)

//通过反射获取类的信息:
    Class cls = Class.forName(className);
    //获取类所在的包名
    Pakeage pkg = cls.getPackage();  
    //获取此对象所表示的实体(类、接口、基本类型或void)的超类Class
    //如果此对象是0bject类、一个接口、一个基本类型或void,他的超类返回Null
    //如果此对象是一个数组类,返回表示Object类的Class对象
    Class superClass = cls.getSuperclass();
    superClass.getName();// 获取超类的名称
    cls。getSimpleName();  //返回源代码中给出的底层类的简称

    //获取Person类所实现的接口
    //如果Person类没有实现任何的接口,返回一个长度为0的数组
    //如果Person类是一个基本类型或者是void,那么也返回一个长度为0的数组
    Class[] interfaces = cls.getInterfaces();
    System.out.print("所实现的数组有: ");
        for(Class c : interfaces){
            System.out.println(c.getName());
   		}

//创建此Class对象所表示的类的一个新实例
Object obj = cls,newInstance();

public void test2() throws Exception{
    Class cls = Class.forName( " entity. Person") ;
    //获取类的构造函数
    Constructor[ ] consts = cls. getDeclaredConstructors() ;
    System.out. println("=============构造函数展示============");
    for(Constructor con : consts){
        Class[] params = con. getParameterTypes();
        if(params .length == 0){
        	System . out . println("此构造函数没有参数");
        }else{
        	System. out. println("此构造函数的参数列表为: [") ;
        for(int i=0; i<params.length; i++){
       		if(i!=0){
        		System. out . println(",");
        	}
        //获取无参构造
        Constructor c1 = cls. getConstructor();
        //通过无参构造获取Person类的对象实例
        object obj = c1. newInstance() ;
        System. out . println(obj);
        //获取有参构造
        Constructor c2 = cls.getDeclaredConstructors(string.class, string.class, String.class);
        //c2是私有的构造方法
        C2. setAccessible(true);//true表示将私有的方法设置为可获取的
        obj = c2. newInstance("新手", "beijing" , "欢迎您");
    }

​ 通过反射获取类的属性信息:

public void invokeSetter(String clsName, string propName, String propType,0bject propValue) {
    try {
        //通过反射创建一个实例
        Class cls = Class .forName( clsName);
        Object obj = cls . newInstance();
        String firstLetter = propName. substring(e, 1). toUpperCase() ;
        String methodName = "set" + firstLetter + propName . substring(1);
        //根据方法名和参数列表获取setter方法
        Method method = cls. getDeclaredMethod(methodName, Class .forName(propType));
        //如果需要,可以通过setaccessable方法(method. setAccessible(true);),设定为可以访问
        //调用方法并传参
        method . invoke(obj, propValue);
        System. out . println(obj);
        }
        System. out . println(obj );
        } catch (InstantiationException e) {
        	e. printstackTrace();
        } catch (IllegalAccessException e) {
        	e. printStackTrace();
        } catch (Clas sNotFoundException e) {
        	e. printStackTrace();
        } catch (SecurityException e) {
        	e. printStackTrace(); :
        } catch (NoSuchMethodException e) {
        	e. printStackTrace();
        } catch (IllegalArgumentException e) {
        	e. printstackTrace();
        }
   }

​ 上述的测试类:

public static void main(String[] args) {
    GetClassMethodsInfo med = new GetClassMethodsInfo() ;
    //展示方法调用
    med . invokeSetter("entity. Person","name", "java.lang.string", "张三");
    //展示Person类中的方法信息
    // med. showMethod();
    }

◆ java.lang.Constructor(获取构造函数的信息)用Class类的方法获取构造方法:

//返回一个Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
getConstructor (Class<?>... paraneterTypes)
//返回一个包含某些Constructor对象的数组,这些对象反映此Class对象所表示的类的所有公共构造方法。
getConstructors()
//返回Constructor对象的一个数组,这些对象反映此Class对象表示的类声明的所有构造方法。
getDeclaredConstructors()

​ ◆ java.lang.reflect.Method

​ ◆ java.lang.Field(类的属性)

​ ◆ java.lang.Modifier(类的修饰符)

以json-lib在功能和性能上都不能满足现在互联网化的需求。

7.2、Jackson

​ 开源的Jackson是Spring MVC内置的JSON转换工具。相比json-lib框架,Jackson 所依赖的jar文件较少,简单易用并且性能也要相对好些。Jackson社区比较活跃,更新速度也比较快。但是Jackson对于复杂类型的JSON转换Bean会出现问题,一些集合Map, List 的转换也不能正常实现;而Jackson 对于复杂类型的Bean转换JSON,转换的JSON格式不是标准格式。

7.3、Gson

​ Gson是目前功能最全的JSON解析器。Gson 最初是应Google公司内部需求而由Google自行研发的,自从2008年5月公开发布第1版后已被许多公司或用户应用。

​ Gson的主要应用是toJson与fromJson两个转换函数,无依赖且不需要额外的jar文件,能够直接运行在JDK上。Gson 可以完成复杂类型的JSON到Bean或Bean到JSON的转换,是JSON解析的利器。其在功能上无可挑别,但是性能比FastJson稍差。

7.4、FastJson

​ FastJson 是一个用Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。它的特点也是无依赖、不需要额外的jar文件,能够直接运行在JDK上。但是FastJson在复杂类型的Bean转换JSON上会出现一些问题,会出现引用的类型不当,导致JSON转换出错,因而需要指定引用。FastJson采用独创的算法,将解析的速度提升到极致,超过所有JSON库。

**总结:**通过以上4种JSON技术的比较,在项目选型的时候可以并行使用Google的Gson和阿里巴巴的Fastson。若只有功能要求,没有性能要求,可以使用Google的Gson;若有性能上面的要求可以使用Gson将Bean转换到JSON以确保数据的正确,再使用FasUson 将JSON转换到Bean。

反射机制

​ 反射是指在程序运行期间,能够观察和修改类或者类的对象的属性和行为的特性。

常见的使用场景为:使用JDBC连接数据库(class.forname)、Servlet在Web容器中的加载和运行

Java反射机制提供了以下的功能:

​ ◆ 在运行时获取类的修饰符,包名,类名,实现的接口,继承的父类

​ ◆ 在运行时获取类的所有属性名,修饰符,属性类型

​ ◆ 在运行时获取所有方法,方法的返回值类型,方法名,方法参数数量,方法参数类型

​ ◆ 在运行时根据类名动态调用加载类的方法,可以降低耦合度。

反射API常用的类:

​ ◆ java.lang.Class(返回与带有给定字符串名的类或接口相关联的Class对象)

//通过反射获取类的信息:
    Class cls = Class.forName(className);
    //获取类所在的包名
    Pakeage pkg = cls.getPackage();  
    //获取此对象所表示的实体(类、接口、基本类型或void)的超类Class
    //如果此对象是0bject类、一个接口、一个基本类型或void,他的超类返回Null
    //如果此对象是一个数组类,返回表示Object类的Class对象
    Class superClass = cls.getSuperclass();
    superClass.getName();// 获取超类的名称
    cls。getSimpleName();  //返回源代码中给出的底层类的简称

    //获取Person类所实现的接口
    //如果Person类没有实现任何的接口,返回一个长度为0的数组
    //如果Person类是一个基本类型或者是void,那么也返回一个长度为0的数组
    Class[] interfaces = cls.getInterfaces();
    System.out.print("所实现的数组有: ");
        for(Class c : interfaces){
            System.out.println(c.getName());
   		}

//创建此Class对象所表示的类的一个新实例
Object obj = cls,newInstance();

public void test2() throws Exception{
    Class cls = Class.forName( " entity. Person") ;
    //获取类的构造函数
    Constructor[ ] consts = cls. getDeclaredConstructors() ;
    System.out. println("=============构造函数展示============");
    for(Constructor con : consts){
        Class[] params = con. getParameterTypes();
        if(params .length == 0){
        	System . out . println("此构造函数没有参数");
        }else{
        	System. out. println("此构造函数的参数列表为: [") ;
        for(int i=0; i<params.length; i++){
       		if(i!=0){
        		System. out . println(",");
        	}
        //获取无参构造
        Constructor c1 = cls. getConstructor();
        //通过无参构造获取Person类的对象实例
        object obj = c1. newInstance() ;
        System. out . println(obj);
        //获取有参构造
        Constructor c2 = cls.getDeclaredConstructors(string.class, string.class, String.class);
        //c2是私有的构造方法
        C2. setAccessible(true);//true表示将私有的方法设置为可获取的
        obj = c2. newInstance("新手", "beijing" , "欢迎您");
    }

​ 通过反射获取类的属性信息:

public void invokeSetter(String clsName, string propName, String propType,0bject propValue) {
    try {
        //通过反射创建一个实例
        Class cls = Class .forName( clsName);
        Object obj = cls . newInstance();
        String firstLetter = propName. substring(e, 1). toUpperCase() ;
        String methodName = "set" + firstLetter + propName . substring(1);
        //根据方法名和参数列表获取setter方法
        Method method = cls. getDeclaredMethod(methodName, Class .forName(propType));
        //如果需要,可以通过setaccessable方法(method. setAccessible(true);),设定为可以访问
        //调用方法并传参
        method . invoke(obj, propValue);
        System. out . println(obj);
        }
        System. out . println(obj );
        } catch (InstantiationException e) {
        	e. printstackTrace();
        } catch (IllegalAccessException e) {
        	e. printStackTrace();
        } catch (Clas sNotFoundException e) {
        	e. printStackTrace();
        } catch (SecurityException e) {
        	e. printStackTrace(); :
        } catch (NoSuchMethodException e) {
        	e. printStackTrace();
        } catch (IllegalArgumentException e) {
        	e. printstackTrace();
        }
   }

​ 上述的测试类:

public static void main(String[] args) {
    GetClassMethodsInfo med = new GetClassMethodsInfo() ;
    //展示方法调用
    med . invokeSetter("entity. Person","name", "java.lang.string", "张三");
    //展示Person类中的方法信息
    // med. showMethod();
    }

◆ java.lang.Constructor(获取构造函数的信息)用Class类的方法获取构造方法:

//返回一个Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
getConstructor (Class<?>... paraneterTypes)
//返回一个包含某些Constructor对象的数组,这些对象反映此Class对象所表示的类的所有公共构造方法。
getConstructors()
//返回Constructor对象的一个数组,这些对象反映此Class对象表示的类声明的所有构造方法。
getDeclaredConstructors()

​ ◆ java.lang.reflect.Method

​ ◆ java.lang.Field(类的属性)

​ ◆ java.lang.Modifier(类的修饰符)

猜你喜欢

转载自blog.csdn.net/muzihao6/article/details/104861880