编程语言 : 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> 
demo.jsp
/* 
 * 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);  
    }  
  }  
}  
demo_jsp.java

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.通过errorPageisErrorPage 控制 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>  
testErrorPage.jsp
<%@ 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>  
testIsErrorPage.jsp

关于错误页面配置,开发中比较常用的是在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> 
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"  
    pageEncoding="UTF-8"%>  
<h1>这是系统LOGO</h1>  
logo.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"  
    pageEncoding="UTF-8"%>  
<%  
    String s = "computer@mail.ustc.edu.cn";  
%>      
<%=s %>  
footer.jsp

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>  
demo.jsp

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>  
demo.jsp

注:<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>
demo.jsp

内置对象

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> 
demo.jsp

观察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> 
demo.jsp

获取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>  
getFromJavaBean.jsp
<%@ 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>  
getFromList.jsp

上述代码获取数组,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>  
difference.jsp

算术,比较,逻辑运算

在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> 
demo.jsp

内置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>
web.xml

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 容器从服务移除之前调用 */
    }
}
logFilter.java

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>
web.xml

监听器顾名思义,用来观察对象的状态.在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>
posted @ 2017-11-19 20:15  Rudolph_Browne  阅读(936)  评论(0编辑  收藏  举报