编程语言 : Java的动态Web解决方案泛谈
文章概述
最近发现很久前一股脑地学习框架,发觉越发迷糊.知道了框架只是暂时的,重点是基础的技术.该文大篇幅回顾Servlet技术栈和简要的MVC框架.
至于为什么学J2EE,额,大家都用框架,可框架也是使用了标准的J2EE规范进行开发,比如SpringMVC的前端控制器是Servlet,Struts的Filter,Spring Boot项目内嵌了Tomcat 应用容器....
该文是自我学习总结,比较适合接触Java Web编程不久的朋友阅读,如果读的没意思就请直接弃之 :)
MVC framework
我知道,我明白你知道MVC框架,可是我还是叨唠一下.
Model,Java普通类对象,用来作为信息存储对象的模块.
View,服务器响应客户端请求后生成页面响应对象的模块.
Controller,处理信息类型转换以及执行业务的模块.
简单地说,就是将我们上传的信息与类型数据进行匹配转换,之后都是琐碎的加些什么数据拦截器,过滤器之类的.
因为我们大多数情况下通过一张表单打到服务器,这时表单的数据都默认是String类型的数据,这时就不适应于类型数据工程语言(C++,PHP,Java,C#).
所以,必须转换类型.
那么,有什么技术可以让我们获取表单数据?以及获取后我们该处理?
HTTP协议说了什么
HTTP协议就是一种让我们获取和返回数据的技术
关于这方面的知识建议你去看看《图解HTTP》,一本薄薄的书.这里只是作为引子做个简单的说明.
HTTP建立于TCP协议之上,但其实可以根据分层而选择忽略底层原理.
HTTP规定了应用层的请求响应规则,客户端<-->服务端的信息必须满足HTTP格式.
客户端浏览器请求,
服务端响应请求,
把HTML,CSS,Javascript等信息存储于HTTP对象载体,
响应返回至客户端,
客户端进行页面渲染显示.
Java动态web解决方案
Sun公司成为制定Java语言的先行者,使Java语言适用于多种领域开发,动态Web开发领域同样也给出了优秀的解决方案.
动态web技术--服务器根据客户端不同的请求数据来生成不同的响应数据并作返回.
Servlet体系
关于Servlet你需要弄清楚下面几个概念,你将在阅读完该文后掌握它们.
上面这张图是Java Web体系的原型技术,也就是说其他技术基本都构建在这些技术之上,它们是根基.
HTML,CSS,JavaScript三者作为页面渲染交互技术而存在;
JDBC作为连接数据库的连接技术,这样可以进行数据库信息的获取和存储;
Tomcat作为Servlet应用容器而存在,等待用户请求;
Servlet作为动态信息的Java处理类,能够将对应的Java数据结构转化为String拼接到HTML之中去;
JavaBean就是简单的Java类,它有固定的格式,很容易就能写出一个JavaBean;
Session存在是因为HTTP是无状态协议,需要Session来作为状态标示;
Request/Response是Servlet容器抽象出来的请求/响应对象,以它来获取数据和将数据写入HTTP响应;
JDBC
Java DataBase Connectivity是Java技术的核心之一,现在基本没有不连接数据库的web应用.
JDBC是一套Java定义的数据库连接接口,实现部分由各大数据库厂商进行开发.(你想要更大的市场,你就必须支持我)
软件开发其中一项精髓是抽象,暂时搁置实现细节,拿来用就行了,除非你要去做该类产品的实现.
JDBC编码步骤
1.Java语言有接口,但是没有提供实现,所以我们必须先加载实现包.
2.通过连接管理器注册驱动
3.获取数据库连接
4.得到代表SQL语句的对象
5.执行语句和获取结果
6.释放占用的资源
JDBC关键接口
DriverManager
①注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());//依赖具体的驱动类,会导致驱动被注册两次
Class.forName("com.mysql.jdbc.Driver");//替代方案,在类被引入时自动注册.
②获取数据库连接
DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
Connection
①获取数据库操作语句对象
conn.createStatement();
conn.prepareCall(sql);
conn.prepareStatement(sql,columnNames);
②事务处理
conn.commit();
conn.rollback();
Statement
①代表SQL语句
Statement s = conn.createStatement();
②执行语句,接收返回结果
ResultSet rs = s.executeQuery(sql);
s.executeUpdate(sql);
ResultSet
①查询SQL所返回的结果集对象,用来遍历操作
rs.next();
②遍历后是一条记录,可以获取记录上的数据
PrepareStatement
①优于Statement,指示SQL语句的预编译,提高数据库执行效率
②防止SQL注入,直接对象对接语句
③语句参数使用占位符?
JDBC的代码规范
1.配置文件
2.工具类
3.业务代码
分页
在web开发中,数据量分页的情况数不胜数,不同数据库分页语句不同,但是逻辑是一样的.
分页逻辑需要参数:数据总条数count(*),分页大小size,当前页面数current
Oracle
oracle中数据表中隐含了一个rownum字段,标识了每条记录在表中的行号,利用它能获取特定行数据.
select a1.* from (select student.*,rownum rn from student) a1 where rn between 3 and 5;
MySQL
MySQL中使用limit关键字来获取数据行数
select * from customer limit 10,5;//第10行开始后的前5条数据
Page类
分页逻辑实现
JDBC的其他重点
JDBC还有其他一些重点知识,包括存储过程调用,事务控制,数据库连接池实现等.由于篇幅问题不作详述,用到的时候直接查一下资料就能找到.
Servlet技术窥探
前面理解了Servlet是一个特殊的Java类,通过应用服务器运行来处理请求信息,下面应该熟悉下面的两张图
Servlet核心类:
这个类图在后面将反复使用,请查看手册,看一下方法名.
下面看一下流程图:
看4,7,8每次你访问服务器时,
Tomcat查询web.xml,查找url-pattern.
服务器会生成一个Request对象和一个Response对象来承接请求信息和响应信息,
每次访问会调用一次Servlet.service(),这个方法的逻辑就是整个响应请求的逻辑.
web应用配置
web app一般都会需要一些配置文件,在Java web应用中这个文件叫web.xml,它是用来配置该web app的.
Servlet声明及映射就是配置URL映射的标签,服务器查询标签知道我调的url是Servlet A还是Servlet B处理.
Servlet.service(req,resp)
Servlet可以由应用服务器生成,默认生成一个DefaultServlet,或者是开发者指定一个继承HttpServlet的类.
所有HTML,CSS,JavaScript等没有指定Servlet的都将默认生成一个DefaultServlet来进行处理.
每个访问都会调用一次Servlet.service(),
/** * Receives standard HTTP requests from the public * <code>service</code> method and dispatches * them to the <code>do</code><i>Method</i> methods defined in * this class. This method is an HTTP-specific version of the * {@link javax.servlet.Servlet#service} method. There's no * need to override this method. * * @param req the {@link HttpServletRequest} object that * contains the request the client made of * the servlet * * @param resp the {@link HttpServletResponse} object that * contains the response the servlet returns * to the client * * @exception IOException if an input or output error occurs * while the servlet is handling the * HTTP request * * @exception ServletException if the HTTP request * cannot be handled * * @see javax.servlet.Servlet#service */ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
根据HTTP请求的方法不同,调用相应的处理方法,现在一般只会使用到两种请求方法,GET/POST
对应这个请求的Servlet,将调用doGet(req,resp),以此类推.
至此,我们知道了关键的一点,关于Servlet编程,我们只要继承HttpServlet,重写doGet和doPost方法等待调用就行了.
该怎么重写呢?思路是,表单的提交信息一定是封装至HttpServletRequest对象中,我们通过获取信息后根据信息写入至HttpServletResponse对象.
HttpServletRequest.getParameter(String name)可以获取表单信息,处理信息.
HttpServletResponse.getWriter()可以获取到一个PrintWriter对象,可以将处理后相应的信息存储在这个对象中,然后由服务器处理写入HTTP报文主体中.
JSP视图
PrintWriter是可以完成输出操作,但是内容很是繁琐,上图简单的页面就必须需要打一大堆代码.
public class MyServlet extends HttpServlet{ public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.println("</IMG src='hackr.gif' alt='hackr.jp" width='240' height='84'/> out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close() } }
所以,Java官方推出了一个新的解决方案来替代前一方案,Servlet的应用服务器必须实现JSP编译器,用来编译jsp文件,将其转换成Servlet文件.
同样采用Servlet来进行响应处理,只不过将处理结果存于JSP文件中然后让编译器编译成结果Servlet,结果Servlet来输出信息至HTTP响应报文主体中.
这样就将原本的Servlet职责(Controller,View)分给了Servlets(Controller)和JSPs(View)这两个模块.
JSP编译后的文件存于tomcat/work目录下.
新的解决方案就多出了许多新的问题,
①一个Servlet怎么做到调用一个JSP文件(跳转);
②JSP如何知晓自己需要显示什么数据;
③JSP文件规范;
Servlet跳转
Servlet跳转就是以上的两种方式进行,
方式一使用response.sendRedirect(url); //直接输入跳转的url
方式二使用request.getRequestDispatcher(url).forward(request,response);
//输入跳转url作为定位,把request,response填入后进行跳转
因为JSP文件本质上会被编译成Servlet,所以可以使用Servlet与jsp进行跳转,只要把url填写为XXX.jsp即可.
Servlet数据存取
jsp需要获取信息来进行显示,获取的信息必定来自转换交接的Servlet那.所以Jsp显示什么数据的问题在于它能获取什么数据,Servlet存储了什么数据.
Servlet域对象指的是Servlet用来存储对象的区域,jsp可以在这些区域中获取数据,Servlet有三大域对象.
域对象存储方法{域}.setAttribute("objectName",Object);
域对象获取对象方法{域}.getAttribute("objectName");
ServletContext
这个域对象是所有Servlet的共用存储域,你保存的对象所有的Servlet都可以获取,jsp也是一种Servlet,所以它也可以获取.
Request
请求域对象,这个请求域在谁手里,谁就可以获取.只要通过转发这个Request,那么Servlet就可拿到这个域里面的对象.
Session
当客户端进行第一次访问时,应用服务器会为你创建一个会话对象;
并为你发送一个sessionID,这个SessionID会作为Cookie保存于你的浏览器中,作为访问这个会话域的凭证;
你可以在Session域中存取对象(用户信息),直至Session被销毁(一般是超时销毁);
JSP文件规范
请注意,JSP文件规范很大成分参考引入博文,非本人原创.
Apache基金会发布的J2EE规范历史版本.
下面我们使用JSP2.3版本来看一下JSP的规范.
JSP页面是动静结合来展示HTML页面内容的,静就是静态文件内容(HTML,CSS,JavaScript),动则是获取操作数据的JSP页面方法.
关于JSP你需要掌握以下内容以满足开发
脚本元素
JSP指令
JSP动作标签
内置对象
表达式语言EL
JSTL标签库
因为JSP被编译成Servlet,上述的动态语言元素都会被编译后写入静态文件中,我们通过编译前后文件来学习这些内容.
脚本元素
<%! %> 声明:定义翻译后Servlet程序的 全局变量或全局方法.内部类
<%= %> 表达式 输出内容到浏览器 效果等同于 out.print
<% %> 脚本代码块,嵌入java运行代码 ---- 不翻译
<%-- --%>JSP注释,编译成Servlet后消失
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>JSP脚本元素</h1> <%! // JSP声明 定义成员变量、成员方法 、内部类 public static void m(){} class A {} %> <!-- 表达式 等价于 会被翻译为 out.print --> <%="abcd" %> <% // JSP 脚本代码块,嵌入任何java代码 String s = "abcdefg"; s = s.toUpperCase(); out.print(s); %> <%-- JSP注释 --%> </body> </html>
/* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/7.0.42 * Generated at: 2016-09-03 12:18:11 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; public final class demo1_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { // JSP声明 定义成员变量、成员方法 、内部类 public static void m(){} class A {} private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private javax.el.ExpressionFactory _el_expressionfactory; private org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public void _jspInit() { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } public void _jspDestroy() { } public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; javax.servlet.jsp.JspWriter _jspx_out = null; javax.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html; charset=UTF-8"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("\r\n"); out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n"); out.write("<html>\r\n"); out.write("<head>\r\n"); out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\r\n"); out.write("<title>Insert title here</title>\r\n"); out.write("</head>\r\n"); out.write("<body>\r\n"); out.write("<h1>JSP脚本元素</h1>\r\n"); out.write("\r\n"); out.write("\r\n"); out.write("<!-- 表达式 等价于 会被翻译为 out.print -->\r\n"); out.print("abcd" ); out.write("\r\n"); out.write("\r\n"); // JSP 脚本代码块,嵌入任何java代码 String s = "abcdefg"; s = s.toUpperCase(); out.print(s); out.write("\r\n"); out.write("</body>\r\n"); out.write("</html>"); } catch (java.lang.Throwable t) { if (!(t instanceof javax.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { out.clearBuffer(); } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
JSP指令
语法:<%@ 指令名称 属性=值 属性=值 ... %>
page指令
page指令用来定义JSP文件的全局属性 <%@ page 属性=值 %>
在JSP页面中,只有import可以出现多次,其他属性都只能出现一次
1.language 只能为java
2.extends 表示JSP翻译后的Servlet所继承的父类,这个属性一般不设置,因为服务器内部默认使jsp继承HttpJspBase类;如果非要设置,继承类必须是Servlet实现类
3.session 定义JSP中是否可以直接使用Session隐含对象,默认为true
如果属性设置为true,在JSP翻译Servlet时,生成以下两句代码:
HttpSession session = null;
session = pageContext.getSession();
* 如果jsp中想使用HttpSession对象,使用session属性默认值true
4.import 完成 JSP翻译后 Servlet 的导包
jsp在翻译为Servlet时,默认导入三个包:
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
jre默认导入 java.lang
* 在jsp中如果使用类 不属于以上四个包,就需要导包
5.buffer和autoFlush 设置 out隐含对象属性
buffer 设置缓冲区大小
autoFlush 设置当缓冲区满后,自动刷新
6.isELIgnored 设置JSP是否执行EL表达式
isELIgnored="false" 不忽略---执行解析
isELIgnored="true" 忽略 ---- 不解析
* 一般就是默认值false
7.通过contentType和pageEncoding 设置 JSP页面编码
pageEncoding 是 JSP文件源代码在硬盘上编码集,如果设置支持中文的编码集,那么服务器就能正确读取jsp中的中文,并将翻译好的中文字符读取进内存(注意内存中保存的不是字节)
contentType 在Servlet生成HTML.传递给浏览器时采用编码
* Java内存中,是没有编码集这一说的,存的都是字符
* 这两个属性设置成支持中文的编码集即可,互相之间不打架的
pageEncoding和contentType区别:
8.通过errorPage和isErrorPage 控制 JSP页面发生错误时跳转
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%--发生错误,想让用户看到友好页面 error.jsp--%> <%@ page errorPage="/demo4/error.jsp" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 制作错误 --> <% int d = 1/0; %> </body> </html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%--当设置了当前页面是错误页面,则可以获得内置对象exception,从而获得错误信息 --%>
<%@page isErrorPage="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 错误友好信息页面 -->
<h4>对不起,服务器正在升级,请稍后访问!</h4>
<h5>错误原因:<%=exception.getMessage() %></h5>
</body>
</html>
关于错误页面配置,开发中比较常用的是在web.xml中配置<error-page>,一次配置即可.
<error-page> <error-code>500</error-code> <location>/demo5/500.jsp</location> </error-page> <error-page> <error-code>404</error-code> <location>/demo5/404.jsp</location> </error-page>
include指令
用来静态包含页面 ----- 将页面公共部分提取出来,通过include完成页面布局。
语法:<%@ include file="文件路径" %>
include包含的是目标页面的整个内容,所以被包含页面,不需要是一个完整HTML,只要编写HTML片段就可以了。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 主页面 --> <!-- 通过 include 包含 logo.jsp --> <%@ include file="/demo6/logo.jsp" %> <h1>主页面其它内容</h1> <%--包含页面必须存在的--%> <%@ include file="/demo6/footer.jsp" %> </body> </html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <h1>这是系统LOGO</h1>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String s = "computer@mail.ustc.edu.cn"; %> <%=s %>
taglib指令
用来在jsp页面引用标签库文件
* 定义标签作用为了简化 jsp页面开发
* 通过taglib 指令引入 jstl标签库,语法: <%@ taglib uri="" prefix="" %>
uri ---- 定义标签 唯一命名空间
prefixt ---- 命名空间前缀
引用jstl时,在导入的jstl.jar中 META-INF/c.tld
<short-name>c</short-name> -------- 就是prefix属性
<uri>http://java.sun.com/jsp/jstl/core</uri> ----- 就是uri属性
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%--通过 taglib 指令 引用jstl ,必须导入jstl 的 jar包--%> <%--在 javaee 5 libraries 存在 jstl-1.2.jar--%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% request.setAttribute("a",10); %> <c:if test="${requestScope.a>8}"> <h1>a的值 大于8</h1> </c:if> </body> </html>
JSP动作标签
JSP标签也称之为Jsp Action (JSP动作) 元素,它用于在Jsp页面中提供业务逻辑功能,避免在JSP页面中直接编写java代码,造成jsp页面难以维护。
注意,这些标签是默认存在的,不需要引入Jar包。
<jsp:include>
效果等价于request.getRequestDispatcher().include,原理是动态包含,区别于<%@ include file="文件路径" %>的静态包含
<jsp:forward>
<jsp:forward page="/demo11/b.jsp"></jsp:forward> 等价于 request.getRequestDispatcher("/demo11/b.jsp").forward(request,response);
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>Hello A</h1> <% // 看不到Hello A,因为在跳转之前,会清空response 缓冲区 // request.getRequestDispatcher("/demo11/b.jsp").forward(request,response); %> <% request.setAttribute("name", "lichunchun"); %> <jsp:forward page="/demo11/b.jsp"> <jsp:param value="ustc" name="school"/> </jsp:forward> </body> </html>
注:<jsp:forward>之后的代码不会被执行
<jsp:param >
绑定在<jsp:forward>中可以用来传递参数.
<jsp:forward page="/demo11/b.jsp">
<jsp:param value="ustc" name="school"/>
</jsp:forward>
<jsp:useBean>
<jsp:useBean>标签用来在jsp页面中创建一个Bean实例
<jsp:setProperty>
设置bean的属性
<jsp:getProperty>
获取bean的属性
<%@ page language="java" pageEncoding="gb2312"%> <jsp:useBean id="user" scope="page" class="com.jsp.test.TestBean"/> <jsp:setProperty name="user" property="*"/> 或者用以下,param可以不填写,其中param对应的是提交页面的表单name <jsp:setProperty property="userName" name="user" param="userName"/> <jsp:setProperty property="password" name="user" param="password"/> <jsp:setProperty property="age" name="user" param="age"/> <html> <body> 注册成功:<br> <hr> 使用Bean的属性方法<br> 用户名: <%=user.getUserName()%><br> 密码: <%=user.getPassword()%><br> 年龄: <%=user.getAge()%><br> <hr> 使用getProperty<br> 用户名:<jsp:getProperty name="user" property="userName"/><br> 密码: <jsp:getProperty name="user" property="password"/><br> 年龄: <jsp:getProperty name="user" property="age"/> 客户端名称:<%=request.getRemoteAddr() %> </body> </html>
内置对象
JSP编译为Servlet代码时,有些对象是默认已经创建好的,这类对象可以直接在jsp中使用,称之为九大内置对象.
page对象
page 代表当前jsp生成的Servlet对象
* page 是 Object类型,只能使用Object中方法 ---- 这个对象在开发中不建议使用
* 可以将page强制转换成HttpServlet对象
<%
HttpServlet httpServlet = (HttpServlet)page;
out.print(httpServlet.getServletContext().getRealPath("/"));
%>
JSP四种数据域对象
前面我们提到过,Servlet将数据存储于Request,ServletContext,Session三种域.
JSP在以上基础扩充了page对象,共有四种域对象(request,application,session,page),其中application是ServletContext的实现.
* page数据范围存放数据,只在当前jsp内有效
* 向page 范围保存数据,必须通过 pageContext对象 setAttribute方法
*pageContext还可以获取其他八个隐式对象.
out对象
out 功能向浏览器输出信息,是JspWriter类型,内部使用PrintWriter实现,拥有独立缓冲区。
out创建:out对象通过pageContext对象获得,而在创建pageContext对象时,需指定out缓冲区大小以及是否自动flush
* 通过 page指令 buffer autoFlush 设置out 缓存区大小 以及是否自动 flush,默认的缓冲区是8kb.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ page isErrorPage="true" %> <%--通过 buffer和autoFlush 设置out 对象缓冲区--%> <%--<%@page buffer="1kb" autoFlush="false" %>--%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>JSP 九个内置对象</h1> <% // 非要使用page对象 HttpServlet httpServlet = (HttpServlet)page; out.print(httpServlet.getServletContext().getRealPath("/")); %> <hr/> <% // 向四种数据范围保存数据 request.setAttribute("name","request"); session.setAttribute("name","session"); application.setAttribute("name","application"); // 向page 范围保存数据,必须通过 pageContext对象 pageContext.setAttribute("name","page"); %> <%=request.getAttribute("name") %> <%=session.getAttribute("name") %> <%=application.getAttribute("name") %> <%=pageContext.getAttribute("name") %> <% // 想在四个数据范围查询 指定名称数据 // 顺序按照 page -- request -- session -- application Object value = pageContext.findAttribute("name"); %> <h3>查找name属性 :<%=value %></h3> <h1>通过EL 取得数据</h1> ${sessionScope.name } <!-- 如果直接写name 默认会调用 pageContext.findAttribute --> ${name } </body> </html>
观察JSP编译的Servlet文件可以查看这些隐式对象.
exception对象
exception对象是java.lang.Trowable类的实例 (使用前需要在jsp页面设置page指令 isErrorPage=“true”)
exception对象用来处理JSP文件在执行时所有发生的错误和异常
exception对象可以和page指令一起使用,通过指定某一个页面为错误处理页面,对错误进行处理
<%@ page isErrorPage="true"%>的页面内使用。(最好还是用第二种配置web.xml的方式)
表达式语言EL
EL语言属于小团队开发,后来在Servlet2.4之后被并入了官方规范之中,目的是为了简化JSP代码开发.
主要功能:
获取JSP四个范围中保存的数据
${pageScope.属性名称}
${requestScope.属性名称}
${sessionScope.属性名称}
${applicationScope.属性名}
如果查找属性不存在,返回是一个 "" 空串,而不是null
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 通过el 获得四个数据范围 数据 page request session application--> <% pageContext.setAttribute("city","合肥"); request.setAttribute("name","李春春"); session.setAttribute("school","中国科学技术大学"); application.setAttribute("pnum",100); %> ${pageScope.city } ${requestScope.name } ${sessionScope.school } ${applicationScope.pnum } <h1>省略指定范围, 默认调用pageContext.findAttribute() 在四个范围依次查找</h1> ${name } ${city } <h1>EL找不到数据返回""空串、传统表达式方式找不到数据返回null</h1> <h3>abc: <%=request.getAttribute("abc") %></h3> <h3>abc: ${abc }</h3> </body> </html>
获取JavaBean属性,数组,Collection,Map等数据集合
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="ustc.lichunchun.domain.Person"%> <%@page import="ustc.lichunchun.domain.City"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 通过EL 获得 存放在四个范围内的 java对象类型 --> <% Person person = new Person(); person.setName("李春春"); person.setAge(24); City city = new City(); city.setName("合肥"); person.setCity(city); pageContext.setAttribute("person", person); %> ${pageScope.person.name } <!-- 上面写法等价于 pageContext.getAttribute("person").getName() --> ${pageScope.person.age } ${pageScope.person["age"] } ${pageScope["person"]["age"] } <!-- 获得person的city对象名称 --> ${pageScope.person.city.name } <!-- pageContext.getAttribute("person").getCity().getName() --> ${pageScope["person"]["city"]["name"] } </body> </html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="java.util.List"%> <%@page import="java.util.ArrayList"%> <%@page import="java.util.Map"%> <%@page import="java.util.HashMap"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 通过EL 取得 List 或者 Map中数据 --> <% List list = new ArrayList(); list.add("abc"); list.add("bcd"); list.add("efg"); // 将list 保存page范围 pageContext.setAttribute("list",list); %> ${pageScope.list } 取得list的第二个元素 :${pageScope.list[1] }<br/> <% Map map = new HashMap(); map.put("aaa","111"); map.put("bbb","222"); pageContext.setAttribute("map",map); %> 取得 map 中 bbb对应 value : ${pageScope.map.bbb }、${pageScope.map["bbb"] }<br/> </body> </html>
上述代码获取数组,List,Map时,可以使用.或者[]获取
. 和 [ ] 有什么区别 ?
答案:. 和 [ ] 都可以用来取得EL 属性值,.可以实现的功能[ ] 也都可以!
例如: ${pageScope.user.name} 也可以写为 ${pageScope.user["name"]}
[ ] 可以使用特殊标识信息,但是. 不可以
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="java.util.List"%> <%@page import="java.util.ArrayList"%> <%@page import="java.util.Map"%> <%@page import="java.util.HashMap"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 通过EL 取得 List 或者 Map中数据 --> <% List list = new ArrayList(); list.add("abc"); list.add("bcd"); list.add("efg"); // 将list 保存page范围 pageContext.setAttribute("list",list); %> ${pageScope.list } 取得list的第二个元素 :${pageScope.list[1] }<br/> <% Map map = new HashMap(); map.put("aaa","111"); map.put("bbb","222"); pageContext.setAttribute("map",map); %> 取得 map 中 bbb对应 value : ${pageScope.map.bbb }、${pageScope.map["bbb"] }<br/> <h1>. 和 [] 区别</h1> <% pageContext.setAttribute("0","itcast"); pageContext.setAttribute("aa.bb","特殊标识信息"); %> 特殊字符0 属性值:${pageScope["0"] } <br/> 特殊字符 aa.bb 属性值 :${pageScope["aa.bb"] } <br/> <% String ds = "aa.bb"; pageContext.setAttribute("s",ds); %> <!-- 在使用[] 进行属性取值时,要加"" , 若不加"" 则认为是一个变量 --> 特殊字符 aa.bb 属性值 :${pageScope[s] }<br/><!-- 特殊标识信息 --> 特殊字符 aa.bb 属性值 :${pageScope["s"] }<!-- aa.bb --> <!-- 利用el表达式获取web应用的名称 --> <a href="${pageContext.request.contextPath }/demo1.jsp">点我</a> </body> </html>
算术,比较,逻辑运算
在EL 执行运算时,运算语句必须写入 ${ }中
* 在EL 获得属性值 执行算术运算,自动类型转换 ---- 执行算术运算时,进行运算参数,必须都是数字
${"a"+"b"} ---- 发生数字格式化错误
empty运算符
1) 判断一个属性是否存在 , 通常empty运算符都是结合c:if 一起使用
2) 使用empty 判断List 或者 Map是否为空 (size==0)
二元表达式:${user!=null?user.name:""} ----- 三元运算符
不能使用保留字存储属性,保留字有特殊意义
EL表达式保留关键字:
<%@page import="java.util.HashMap"%> <%@page import="java.util.ArrayList"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h2>EL 执行 运算</h2> <% pageContext.setAttribute("a", "10"); pageContext.setAttribute("b", "20"); pageContext.setAttribute("10", "30"); %> ${a+b }<!-- 30 --> <%--经典错误 :${"a"+"b" }--%> ${pageScope.a }<!-- 10 --> ${pageScope["a"] }<!-- 10 --> ${pageScope[a] }<!-- 30 --> ${a }<!-- 10 --> ${"a" }<!-- a --> <h2>empty运算符</h2> ${empty name }<!-- 如果四个数据范围都没有name属性 返回true --> <c:if test="${empty name }"> <h3>根本不存在 name数据</h3> </c:if> <!-- 判断list 获得 map是否为空 --> <% pageContext.setAttribute("list", new ArrayList()); pageContext.setAttribute("map", new HashMap()); %> ${empty list } ${empty map } <h2>二元表达式</h2> ${(empty map)?"map中没有任何元素":"map不为空" } <% // 不能使用保留字 存储属性,保留字有特殊意义 pageContext.setAttribute("empty","111"); %> <%--${pageContext["empty"] }--%> </body> </html>
内置11个对象(web开发常用对象)
JSTL标签库
项目开发中,JSP开发基本都会约定引入JSTL标签库(Java Standard Tag Liberary),统一规范,简化代码开发.
下载jstl.jar和standard.jar,通过taglib指令引入jstl标签库对应的uri,也可以在web.xml中直接进行配置
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <jsp-config> <taglib> <taglib-uri>http://java.sun.com/jstl/fmt</taglib-uri> <taglib-location>/WEB-INF/fmt.tld</taglib-location> </taglib> <taglib> <taglib-uri>http://java.sun.com/jstl/fmt-rt</taglib-uri> <taglib-location>/WEB-INF/fmt-rt.tld</taglib-location> </taglib> <taglib> <taglib-uri>http://java.sun.com/jstl/core</taglib-uri> <taglib-location>/WEB-INF/c.tld</taglib-location> </taglib> <taglib> <taglib-uri>http://java.sun.com/jstl/core-rt</taglib-uri> <taglib-location>/WEB-INF/c-rt.tld</taglib-location> </taglib> <taglib> <taglib-uri>http://java.sun.com/jstl/sql</taglib-uri> <taglib-location>/WEB-INF/sql.tld</taglib-location> </taglib> <taglib> <taglib-uri>http://java.sun.com/jstl/sql-rt</taglib-uri> <taglib-location>/WEB-INF/sql-rt.tld</taglib-location> </taglib> <taglib> <taglib-uri>http://java.sun.com/jstl/x</taglib-uri> <taglib-location>/WEB-INF/x.tld</taglib-location> </taglib> <taglib> <taglib-uri>http://java.sun.com/jstl/x-rt</taglib-uri> <taglib-location>/WEB-INF/x-rt.tld</taglib-location> </taglib> </jsp-config> </web-app>
JSTL由五种主要标签组成:
具体的语法参考
过滤器,监听器
过滤器可以对请求和响应做出拦截操作.
配置多个filter:
1.继承filter类,实现init,doFilter,destroy三个类方法.
//导入必需的 java 库 import javax.servlet.*; import java.util.*; //实现 Filter 类 public class LogFilter implements Filter { public void init(FilterConfig config) throws ServletException { // 获取初始化参数 String site = config.getInitParameter("Site"); // 输出初始化参数 System.out.println("网站名称: " + site); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException { // 输出站点名称 System.out.println("站点网址:http://www.runoob.com"); // 把请求传回过滤链 chain.doFilter(request,response); } public void destroy( ){ /* 在 Filter 实例被 Web 容器从服务移除之前调用 */ } }
2.在web.xml进行配置
<filter> <filter-name>LogFilter</filter-name> <filter-class>com.runoob.test.LogFilter</filter-class> <init-param> <param-name>test-param</param-name> <param-value>Initialization Paramter</param-value> </init-param> </filter> <filter> <filter-name>AuthenFilter</filter-name> <filter-class>com.runoob.test.AuthenFilter</filter-class> <init-param> <param-name>test-param</param-name> <param-value>Initialization Paramter</param-value> </init-param> </filter> <filter-mapping> <filter-name>LogFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthenFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
监听器顾名思义,用来观察对象的状态.在Servlet规范中提供了8个监听器接口.
创建、销毁监听器3个
ServletContextListener:监听ServletContext的创建和销毁的监听器
HttpSessionListener:监听HttpSession的创建和销毁的监听器
ServletRequestListener:监听ServletRequest的创建和销毁的监听器
属性变化监听器3个
ServletContextAttributeListener:监听放到应用范围中的数据变化(新添加、修改的、删除的)
HttpSessionAttributeListener:(统计登录用户列表)
ServletRequestAttributeListener
感知型监听器2个
HttpSessionBindingListener:谁实现这个接口,就能感知自己何时被HttpSession绑定和解绑了
HttpSessionActivationListener:谁实现这个接口,就能感知自己何时随着HttpSession对象钝化和激活
web.xml中配置监听器
<listener> <listener-class> com.journaldev.listener.AppContextListener </listener-class> </listener>
文件上传下载
上传
注意,这里代码是搬运苍狼大神的博文,用于模仿学习.
文件的上传下载是使用流来进行传输,java web一般使用的是apache的file-upload组件.
1.引入commons-fileupload.jar和commons-io.jar
2.表单中需要<input>的type标为file,
enctype标为multipart/form-data,
method标为post
3. 编写Servlet
package me.gacl.web.controller; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; public class UploadHandleServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全 String savePath = this.getServletContext().getRealPath("/WEB-INF/upload"); File file = new File(savePath); //判断上传文件的保存目录是否存在 if (!file.exists() && !file.isDirectory()) { System.out.println(savePath+"目录不存在,需要创建"); //创建目录 file.mkdir(); } //消息提示 String message = ""; try{ //使用Apache文件上传组件处理文件上传步骤: //1、创建一个DiskFileItemFactory工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //2、创建一个文件上传解析器 ServletFileUpload upload = new ServletFileUpload(factory); //解决上传文件名的中文乱码 upload.setHeaderEncoding("UTF-8"); //3、判断提交上来的数据是否是上传表单的数据 if(!ServletFileUpload.isMultipartContent(request)){ //按照传统方式获取数据 return; } //4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项 List<FileItem> list = upload.parseRequest(request); for(FileItem item : list){ //如果fileitem中封装的是普通输入项的数据 if(item.isFormField()){ String name = item.getFieldName(); //解决普通输入项的数据的中文乱码问题 String value = item.getString("UTF-8"); //value = new String(value.getBytes("iso8859-1"),"UTF-8"); System.out.println(name + "=" + value); }else{//如果fileitem中封装的是上传文件 //得到上传的文件名称, String filename = item.getName(); System.out.println(filename); if(filename==null || filename.trim().equals("")){ continue; } //注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt //处理获取到的上传文件的文件名的路径部分,只保留文件名部分 filename = filename.substring(filename.lastIndexOf("\\")+1); //获取item中的上传文件的输入流 InputStream in = item.getInputStream(); //创建一个文件输出流 FileOutputStream out = new FileOutputStream(savePath + "\\" + filename); //创建一个缓冲区 byte buffer[] = new byte[1024]; //判断输入流中的数据是否已经读完的标识 int len = 0; //循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据 while((len=in.read(buffer))>0){ //使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中 out.write(buffer, 0, len); } //关闭输入流 in.close(); //关闭输出流 out.close(); //删除处理文件上传时生成的临时文件 item.delete(); message = "文件上传成功!"; } } }catch (Exception e) { message= "文件上传失败!"; e.printStackTrace(); } request.setAttribute("message",message); request.getRequestDispatcher("/message.jsp").forward(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
4.web.xml注册Servlet
<servlet> <servlet-name>UploadHandleServlet</servlet-name> <servlet-class>me.gacl.web.controller.UploadHandleServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UploadHandleServlet</servlet-name> <url-pattern>/servlet/UploadHandleServlet</url-pattern> </servlet-mapping>
5.改进
1、为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
2、为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。
3、为防止一个目录下面出现太多文件,要使用hash算法打散存储。
4、要限制上传文件的最大值。
5、要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。
package me.gacl.web.controller; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadBase; import org.apache.commons.fileupload.ProgressListener; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; /** * @ClassName: UploadHandleServlet * @Description: TODO(这里用一句话描述这个类的作用) * @author: 孤傲苍狼 * @date: 2015-1-3 下午11:35:50 * */ public class UploadHandleServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全 String savePath = this.getServletContext().getRealPath("/WEB-INF/upload"); //上传时生成的临时文件保存目录 String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp"); File tmpFile = new File(tempPath); if (!tmpFile.exists()) { //创建临时目录 tmpFile.mkdir(); } //消息提示 String message = ""; try{ //使用Apache文件上传组件处理文件上传步骤: //1、创建一个DiskFileItemFactory工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中。 factory.setSizeThreshold(1024*100);//设置缓冲区的大小为100KB,如果不指定,那么缓冲区的大小默认是10KB //设置上传时生成的临时文件的保存目录 factory.setRepository(tmpFile); //2、创建一个文件上传解析器 ServletFileUpload upload = new ServletFileUpload(factory); //监听文件上传进度 upload.setProgressListener(new ProgressListener(){ public void update(long pBytesRead, long pContentLength, int arg2) { System.out.println("文件大小为:" + pContentLength + ",当前已处理:" + pBytesRead); /** * 文件大小为:14608,当前已处理:4096 文件大小为:14608,当前已处理:7367 文件大小为:14608,当前已处理:11419 文件大小为:14608,当前已处理:14608 */ } }); //解决上传文件名的中文乱码 upload.setHeaderEncoding("UTF-8"); //3、判断提交上来的数据是否是上传表单的数据 if(!ServletFileUpload.isMultipartContent(request)){ //按照传统方式获取数据 return; } //设置上传单个文件的大小的最大值,目前是设置为1024*1024字节,也就是1MB upload.setFileSizeMax(1024*1024); //设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为10MB upload.setSizeMax(1024*1024*10); //4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项 List<FileItem> list = upload.parseRequest(request); for(FileItem item : list){ //如果fileitem中封装的是普通输入项的数据 if(item.isFormField()){ String name = item.getFieldName(); //解决普通输入项的数据的中文乱码问题 String value = item.getString("UTF-8"); //value = new String(value.getBytes("iso8859-1"),"UTF-8"); System.out.println(name + "=" + value); }else{//如果fileitem中封装的是上传文件 //得到上传的文件名称, String filename = item.getName(); System.out.println(filename); if(filename==null || filename.trim().equals("")){ continue; } //注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt //处理获取到的上传文件的文件名的路径部分,只保留文件名部分 filename = filename.substring(filename.lastIndexOf("\\")+1); //得到上传文件的扩展名 String fileExtName = filename.substring(filename.lastIndexOf(".")+1); //如果需要限制上传的文件类型,那么可以通过文件的扩展名来判断上传的文件类型是否合法 System.out.println("上传的文件的扩展名是:"+fileExtName); //获取item中的上传文件的输入流 InputStream in = item.getInputStream(); //得到文件保存的名称 String saveFilename = makeFileName(filename); //得到文件的保存目录 String realSavePath = makePath(saveFilename, savePath); //创建一个文件输出流 FileOutputStream out = new FileOutputStream(realSavePath + "\\" + saveFilename); //创建一个缓冲区 byte buffer[] = new byte[1024]; //判断输入流中的数据是否已经读完的标识 int len = 0; //循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据 while((len=in.read(buffer))>0){ //使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中 out.write(buffer, 0, len); } //关闭输入流 in.close(); //关闭输出流 out.close(); //删除处理文件上传时生成的临时文件 //item.delete(); message = "文件上传成功!"; } } }catch (FileUploadBase.FileSizeLimitExceededException e) { e.printStackTrace(); request.setAttribute("message", "单个文件超出最大值!!!"); request.getRequestDispatcher("/message.jsp").forward(request, response); return; }catch (FileUploadBase.SizeLimitExceededException e) { e.printStackTrace(); request.setAttribute("message", "上传文件的总的大小超出限制的最大值!!!"); request.getRequestDispatcher("/message.jsp").forward(request, response); return; }catch (Exception e) { message= "文件上传失败!"; e.printStackTrace(); } request.setAttribute("message",message); request.getRequestDispatcher("/message.jsp").forward(request, response); } /** * @Method: makeFileName * @Description: 生成上传文件的文件名,文件名以:uuid+"_"+文件的原始名称 * @Anthor:孤傲苍狼 * @param filename 文件的原始名称 * @return uuid+"_"+文件的原始名称 */ private String makeFileName(String filename){ //2.jpg //为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名 return UUID.randomUUID().toString() + "_" + filename; } /** * 为防止一个目录下面出现太多文件,要使用hash算法打散存储 * @Method: makePath * @Description: * @Anthor:孤傲苍狼 * * @param filename 文件名,要根据文件名生成存储目录 * @param savePath 文件存储路径 * @return 新的存储目录 */ private String makePath(String filename,String savePath){ //得到文件名的hashCode的值,得到的就是filename这个字符串对象在内存中的地址 int hashcode = filename.hashCode(); int dir1 = hashcode&0xf; //0--15 int dir2 = (hashcode&0xf0)>>4; //0-15 //构造新的保存目录 String dir = savePath + "\\" + dir1 + "\\" + dir2; //upload\2\3 upload\3\5 //File既可以代表文件也可以代表目录 File file = new File(dir); //如果目录不存在 if(!file.exists()){ //创建目录 file.mkdirs(); } return dir; } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
下载
1.下载文件页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE HTML> <html> <head> <title>下载文件显示页面</title> </head> <body> <!-- 遍历Map集合 --> <c:forEach var="me" items="${fileNameMap}"> <c:url value="/servlet/DownLoadServlet" var="downurl"> <c:param name="filename" value="${me.key}"></c:param> </c:url> ${me.value}<a href="${downurl}">下载</a> <br/> </c:forEach> </body> </html>
2.执行下载操作的Servlet
package me.gacl.web.controller; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URLEncoder; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class DownLoadServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //得到要下载的文件名 String fileName = request.getParameter("filename"); //23239283-92489-阿凡达.avi fileName = new String(fileName.getBytes("iso8859-1"),"UTF-8"); //上传的文件都是保存在/WEB-INF/upload目录下的子目录当中 String fileSaveRootPath=this.getServletContext().getRealPath("/WEB-INF/upload"); //通过文件名找出文件的所在目录 String path = findFileSavePathByFileName(fileName,fileSaveRootPath); //得到要下载的文件 File file = new File(path + "\\" + fileName); //如果文件不存在 if(!file.exists()){ request.setAttribute("message", "您要下载的资源已被删除!!"); request.getRequestDispatcher("/message.jsp").forward(request, response); return; } //处理文件名 String realname = fileName.substring(fileName.indexOf("_")+1); //设置响应头,控制浏览器下载该文件 response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8")); //读取要下载的文件,保存到文件输入流 FileInputStream in = new FileInputStream(path + "\\" + fileName); //创建输出流 OutputStream out = response.getOutputStream(); //创建缓冲区 byte buffer[] = new byte[1024]; int len = 0; //循环将输入流中的内容读取到缓冲区当中 while((len=in.read(buffer))>0){ //输出缓冲区的内容到浏览器,实现文件下载 out.write(buffer, 0, len); } //关闭文件输入流 in.close(); //关闭输出流 out.close(); } /** * @Method: findFileSavePathByFileName * @Description: 通过文件名和存储上传文件根目录找出要下载的文件的所在路径 * @Anthor:孤傲苍狼 * @param filename 要下载的文件名 * @param saveRootPath 上传文件保存的根目录,也就是/WEB-INF/upload目录 * @return 要下载的文件的存储目录 */ public String findFileSavePathByFileName(String filename,String saveRootPath){ int hashcode = filename.hashCode(); int dir1 = hashcode&0xf; //0--15 int dir2 = (hashcode&0xf0)>>4; //0-15 String dir = saveRootPath + "\\" + dir1 + "\\" + dir2; //upload\2\3 upload\3\5 File file = new File(dir); if(!file.exists()){ //创建目录 file.mkdirs(); } return dir; } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
3.web.xml配置
<servlet> <servlet-name>DownLoadServlet</servlet-name> <servlet-class>me.gacl.web.controller.DownLoadServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DownLoadServlet</servlet-name> <url-pattern>/servlet/DownLoadServlet</url-pattern> </servlet-mapping>