JavaWeb基础

动态Web

JavaWeb基础_第1张图片
服务器端不再直接使用Web服务器进行接收了,而是通过了一个WEB服务插件,用于区分是动态请求还是静态请求。

  • 静态·请求直接交给Web服务器,并调用文件系统。
  • 动态请求则直接进入到一个Web容器,进行代码的拼凑工作。

企业开发架构

JavaWeb基础_第2张图片
所谓企业开发实际上就是给数据库加了一层壳,因为直接操作数据库不方便。

Tomcat服务器的配置

虚拟目录的配置

配置Tomcat的conf/server.xml为如下:


<Server port="8005" shutdown="SHUTDOWN">
    <Engine defaultHost="localhost" name="Catalina">
        <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
            
            
            <Context docBase="E:\MyJspDemo" path="/hello" />
        Host>
    Engine>
  Service>
Server>

如果你的真实目录下没有index.jsp或者index.html则可能会出现404错误,这时就需要配置conf/web.xml的listings为true。

<servlet>
        <servlet-name>defaultservlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServletservlet-class>
        <init-param>
            <param-name>listingsparam-name>
            
            <param-value>falseparam-value>
        init-param>
servlet>  

重启服务器出现以下目录列表表示虚拟目录配置完成。
JavaWeb基础_第3张图片

默认主页的配置

在Tomcat的conf/web.xml的最后几行有这样的配置

<welcome-file-list>
    
    <welcome-file>index.htmlwelcome-file>
    <welcome-file>index.htmwelcome-file>
    <welcome-file>index.jspwelcome-file>
welcome-file-list>

最好不要修改公共的配置。因此,我们在各自的虚拟目录中配置以上的xml即可。

JSP基础语法

注释

  • 显式注释(客户端可见,实际上就是html注释)。语法:
  • 隐式注释(客户端不可见)。语法:
    • 语法一(单行注释):// 注释内容
    • 语法二(多行注释):/* 注释内容 */
    • 语法三(JSP注释):<%-- 注释内容 --%>

Scriptlet

Scriptlet表示的是脚本小程序,所有嵌套在html代码中的java程序都必须使用scriptlet标记。一共分为3种:
- <%%>:可以定义局部变量,缩写语句等。
- <%!%>:定义全局变量、方法,类。
- <%=%>:用户输出一个变量或者具体内容。
例如:

<%
    int x = 10;                                                 // 每次请求都被重新初始化
%>

<%!
    int a = 0;                                                  // 这里的a是全局的
    public static final  String INFO = "你若安好,便是晴天!";    // 全局常量

    class Student{
        private String name;
        private int age;

        public Student(String name,int age){

            this.name = name;
            this.age = age;
        }

        public String toString(){
            return "name = " + name + ",age = " + age;
        }
    }
%>

<%
    out.println("x是局部变量,每次请求都是这个值" + ++x + "
"
); out.println("刷新页面,a的值会不断增加!a = " + a++); out.println("
INFO是全局常量,INFO = "
+ INFO + "
"
); out.println(new Student("张三",15)); %>

运行结果:
JavaWeb基础_第4张图片
一般而言,使用<%!%>声明全局常量较多,而所谓的定义方法或者定义类在实际开发中压根不存在!

scriptlet标签

在程序中过多使用<%%>会发现代码非常混乱,所以在新版本的JSP中专门提供了scriptlet的标签,语法如下:

<jsp:scriptlet>
    Java的Scriptlet代码    
jsp:scriptlet>

例如:


    String url = "wwww.baidu.com";  

<%= url %>

page指令

使用该指令可以指定一个JSP页面的相关属性,包括MIME类型、需要导入的包、错误页的指定等。该指令的语法:

<%@ page 属性名="属性值"%>

所有可以使用的MIME类型在conf/web.xml中有定义:
例如:

<mime-mapping>
    <extension>htmextension>
    <mime-type>text/htmlmime-type>
mime-mapping>
<mime-mapping>
    <extension>htmlextension>
    <mime-type>text/htmlmime-type>
mime-mapping>

如果我们制定了一个在web.xml中没有定义的MIME类型,打开页面将会出现下载框。例如我们指定下面的MIME类型:

<%@ page language="java" import="java.util.*"  contentType="test/html; charset=utf-8"%>

JavaWeb基础_第5张图片
依据这个思想,我们甚至可以调用客户端的word程序将一个jsp页面作为word文档打开,只需要设置以下的MIME即可:

<mime-mapping>
    <extension>docextension>
    <mime-type>application/mswordmime-type>
mime-mapping>

JavaWeb基础_第6张图片,此时的文件将以word文档格式保存。但是文件名看着不怎么爽,可以使用response内置对象设置头信息。

<%@ page language="java" import="java.util.*"  contentType="application/msword; charset=utf-8"%>

<%response.setHeader("Content-Disposition", "attachemnet;filename=hello.doc"); %>

JavaWeb基础_第7张图片

一定要注意:如果一个jsp页面想要换一种形式显示,就可以设置MIME类型。

错误页的配置
错误页的配置需要以下2个条件:
- 指定出现错误时的跳转页,errorPage属性。
- 错误处理页必须有明确的标识,isErrorPage属性为true
例如我们在show.jsp中这样写:

<%@ page language="java" import="java.util.*"  contentType="text/html; charset=utf-8"%>

<%@ page errorPage="error.jsp"%>
<%
    int a = 5 / 0; // 肯定出错!【运行时异常】
%>

error.jsp中这样写:

<%@ page language="java" import="java.util.*" contentType="text/html;charset=utf-8"%>
<%@ page isErrorPage="true"%>
<% out.print("

错误,请联系管理员!

"
); %>

JavaWeb基础_第8张图片
注意页面的URL并没有变,只是将错误页的内容显示出来了。——不改变页面URL的跳转都称为服务器内部跳转(使用频率在95%以上)!
有时候可能出现错误页没有显示出来的情况,我们就需要依靠response对象设置http状态码。在error.jsp中写上如下的配置:

<%@ page language="java" import="java.util.*" contentType="text/html;charset=utf-8"%>
<%@ page isErrorPage="true"%>
<% 
    response.setStatus(200);        // 设置状态码,表示这是一个正常的页面
    out.print("

错误,请联系管理员!

"
); %>

JavaWeb基础_第9张图片
每个页面都这样进行指定错误页面就会比较麻烦,要想集中处理也是可以的,这时就需要配置项目的web.xml


<error-page>
    <error-code>404error-code>
    <location>/error.jsplocation>
error-page>
<error-page>
    <exception-type>java.lang.NullPointerExceptionexception-type>
    <location>/error.jsplocation>
error-page>

JavaWeb基础_第10张图片

包含

一般的页面分为4个区域:
JavaWeb基础_第11张图片
由于只是具体内容发生了变化,我们就可以将页面头部、工具栏、页面尾部分离成3个独立的文件,需要使用的时候直接包含即可。
在JSP中如果想要实现包含操作有2种方式:

  • 静态包含:<%@ include file="文件的路径"%>【指令】
  • 动态包含【动作】:
    • 不传递参数:
    • 传递参数:



      ……可以向被包含的页面中传递多个参数

例:静态包含。
新建3个文件info.jspinfo.htmlinfo.inc分别以不同的颜色显示各自的文件名,在include_demo01.jsp中使用<%@ include%>指令将以上的3个文件包含:

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
<title>静态包含title>
head>
<body>


<%@ include file="info.html" %>
<%@ include file="info.jsp" %>
<%@ include file="info.inc" %>
body>

html>

运行结果:
JavaWeb基础_第12张图片
3个不同的源文件都在同一个页面得到显示!查看此网页的源代码:
JavaWeb基础_第13张图片
静态包含就是将内容进行了直接的替换。

例:动态包含:
将之前的静态包含换成动态包含:

<body>


<jsp:include page="info.html" />
<jsp:include page="info.jsp" />
<jsp:include page="info.inc" />
body>

页面的输出没有任何区别,查看源代码:发现动态包含也仅仅做了一个替换。

在include_demo03.jsp中我们将两个参数传递到receive_param.jsp页面中:


<jsp:include page="receive_param.jsp">
    <jsp:param value="Tom" name="name"/>
    <jsp:param value="www.apache.org" name="info"/>
jsp:include>

在receive_param.jsp中接收include_demo03.jsp传递的参数:

<h1>参数一:<%=request.getParameter("name") %>h1>
<h1>参数二:<%=request.getParameter("info") %>h1>

如果需要传递的参数是一个变量,就必须使用表达式的形式了:

<%!String name = "www.youku.com"; %>

<jsp:include page="receive_param.jsp">
    <jsp:param value="Tom" name="name"/>
    
    <jsp:param value="<%=name %>" name="info"/>
jsp:include>

静态包含和动态包含的区别:
静态包含是先包含后处理,动态包含是先处理后包含——包含的是处理好的结果。例如:如果包含的页面和被包含的页面中出现了同名变量,静态包含就不会编译通过;动态包含则不会出现这种情况。

JSP内置对象

4种属性范围

  • pageContext:同一页面有效,跳转之后无效;
  • request:同一次请求有效(服务器内部跳转之后仍然有效);
  • session:同一次会话有效,无论任何形式的跳转都有效,新开浏览器无法使用;
  • application:整个服务器保存,所有用户都可以使用。
    以上的4个内置对象都有以下的4个方法:
public void setAttribute(String name,Object value)  // 设置属性
public Object getAttribute(String name)             // 取得属性
public void removeAttribute(String name)            // 移除属性

特别注意一点:在pageContext有一个重载的setAttribute()方法可以设置4中作用范围

public abstract void setAttribute(String name,.Object value, int scope)

/* scope为javax.servlet.jsp.PageContext中的4个常量 */

request内置对象

该内置对象是javax.servlet.http.HttpServletRequest接口的实例化对象,该接口的定义如下:

public interface HttpServletRequest extends ServletRequest

通过文档可以发现ServletRequest接口只有一个子接口,那就是HttpServletRequest,那么为什么不将两个接口合并为一个接口?——现在主要使用的协议是http协议,但是以后可能会出现新的协议,只需要继承ServletRequest接口即可。
常用方法:

java.lang.String getParameter(java.lang.String name)            // 取得参数的值
java.util.Enumeration getParameterNames()     // 取得参数名,返回的是Enumeration类型
java.lang.String[] getParameterValues(java.lang.String name)    // 取得参数所代表值的字符串数组【复选框等】 

例如网站的前端有这样一个页面:
JavaWeb基础_第14张图片
但是我们并不知道它有哪些参数,我们就可以通过getParameterNames()方法取得所有的参数的名字,根据参数的名字进而取得传进的参数的值(根据单选框还是复选框需要分别使用getParameter()方法和getParameterValues()方法)。见下面的代码:

<%@page import="java.util.Enumeration"%>
<%@page contentType="text/html; charset=utf-8" %>
<%
    request.setCharacterEncoding("utf-8");
    Enumeration enu = request.getParameterNames();
%>
<table border="1">
    <tr>
        <td>参数名td>
        <td>参数值td>
    tr>
<%  
    while(enu.hasMoreElements()){
        String paraName = (String)enu.nextElement();
%>  
        <tr><td><%=paraName %>td><td>
<%
        // 前台多选框的name属性需要设置以**开头
        if(paraName.startsWith("**")){
            String[] paraValue = request.getParameterValues(paraName);
                for(String v:paraValue){
%>
                <%=v %>
<%      
                }           
        }else{
%>
            <%=request.getParameter(paraName) %>        
<%                      
        }
%>      
    td>   
<%  
    } 
%>
table>

运行结果:
JavaWeb基础_第15张图片
头信息
web开发中主要使用的协议是http协议,是基于请求和回应的,但是在请求和回应的时候还会包含一些其他的信息(例如:客户端的ip、Cookie、语言等)这些信息称为头信息。java取得头信息主要依靠以下几个方法:

java.util.Enumeration getHeaderNames()    // 取得头信息的名称
java.lang.String getHeader(java.lang.String name)           // 取得头信息的内容

例如:下面的代码将取得全部的头信息:

<%@page import="java.util.Enumeration"%>
<%@page contentType="text/html; charset=utf-8" %>
<%
    request.setCharacterEncoding("utf-8");
    Enumeration enu = request.getHeaderNames(); // 取得全部的头信息
    while(enu.hasMoreElements()){
        String headerName = (String)enu.nextElement();
        String headerValue = request.getHeader(headerName);
%>
            <%=headerName %>--><%=headerValue %><br/>
<%  
    }
%>

JavaWeb基础_第16张图片
request对象的一些其他方法

<%@page import="java.util.Enumeration"%>
<%@page contentType="text/html; charset=utf-8" %>
<%
    String method = request.getMethod();
    String remoteHostname = request.getRemoteAddr();//客户端主机名
    String remoteIp = request.getRemoteAddr();
    int remotePort = request.getRemotePort();
    String context = request.getContextPath(); // 上下文路径
    String serverIp = request.getLocalAddr();
    String serverName = request.getLocalName();
    int serverPort = request.getLocalPort();
    String protocol = request.getProtocol();
%>
请求方式:<%=method %><br />
客户端主机名:<%=remoteHostname %><br />
客户端IP:<%=remoteIp %><br />
客户端端口:<%=remotePort %><br />
请求的上下文路径:<%=context %><br />
服务器名:<%=serverName %><br />
服务器IP:<%=serverIp %><br />
服务器端口:<%=serverPort %><br />
协议:<%=protocol %><br />

通过浏览器的URL访问得到如下:
JavaWeb基础_第17张图片

response内置对象

该内置对象是javax.servlet.http.HttpServletResponse对象的实例,该接口的定义如下(和HttpServletRequest接口的定义十分类似):

public interface HttpServletResponse extends ServletResponse

常用方法:

void addCookie(Cookie cookie)               // 向浏览器设置Cookie
void sendRedirect(String location)          // 重定向
void setHeader(String name,String value)    // 设置响应头信息  

设置头信息

页面定时刷新(refresh属性):

<%@ page contentType="text/html; charset=utf-8" %>
<%!
    int count = 0;
%>
<%
    response.setHeader("refresh", "1"); // 每1s刷新页面
 %>
<h1>该页面每秒自动刷新,您已经访问了该页面<font color="red"><%=++count %>font>!h1>

JavaWeb基础_第18张图片,基于以上的例子,还可以实现页面的定时跳转,可以让一个页面定时跳转到指定的页面。这种应用非常广泛——例如在论坛注册用户的时候提示“用户注册成功,3秒之内将会跳转到……”。

<%@ page contentType="text/html; charset=utf-8" %>
<%
    response.setHeader("refresh", "3;url=hello.html"); // 3秒之后跳转到指定url
 %>
<h1>3秒之后将跳转到hello.html,如果没有自动跳转,请点击<a href="hello.html">这里a>h1>

运行效果:
JavaWeb基础_第19张图片
但是以上的代码并不是万能的,有时候根本无法完成跳转的操作,所以一些站点加入了这样一句话:“如果没有发生跳转,请点击这里”来进行手工的跳转。
不只是动态页面可以实现页面的定时跳转,普通的静态html也可以实现页面的定时跳转:(我们只需要在一个普通的html的头信息中添加以下代码即可):

<meta http-equiv="refresh" content="3;hello.html" />

以上的是客户端的跳转。

设置Cookie

JavaWeb基础_第20张图片
由于客户端在每次请求的时候都会发送一个Cookie的头信息,所以我们需要依靠response对象取得客户端的cookie
setCookie.jsp

<%@page contentType="text/html; charset=utf-8" %>
<%
    Cookie c1 = new Cookie("name","陶伟华");
    Cookie c2 = new Cookie("pass","admin");

    // 设置Cookie
    response.addCookie(c1);
    response.addCookie(c2);
%>

getCookie.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    Cookie[] cookies = request.getCookies();    // 取得Cookie
    for(Cookie c:cookies){
%>
        <%=c.getName() %>--><%=c.getValue() %>
<%  
    }
%>

JavaWeb基础_第21张图片
取得的Cookie中出现了我们没有设置的JSESSIONID,这是服务器自动设置上去的。
按照Cookie的理论来讲,即使浏览器关闭,已经设置在浏览器中的Cookie当浏览器重新打开的时候,还是会取得。但是当我们关闭浏览器重新打开页面就会出现服务器500错误,原因是空指针异常。因为我们设置Cookie的时候没有指定有效期——默认是浏览器窗口的生存期(浏览器关闭,cookie失效!)。可以使用setMaxAge(int second)设置Cookie的生存期。
为Cookie设置合理的有效期,关闭浏览器,重新打开getCookie.jsp,运行结果:
JavaWeb基础_第22张图片
当我们再次请求该页面的时候:
JavaWeb基础_第23张图片
出现了JSESSIONID——JSESSIONID是客户端第一次请求之后才设置在客户端的。

Session内置对象

session内置对象是javax.servlet.http.HttpSession接口的实例化对象,但是该接口并不像HttpServletRequestHttpServletResponse那样本身还有一个父接口,它只是一个纯粹的接口,属于http协议的范畴。该接口的常用方法:

public String getId()                   // 取得session ID
public long getCreationTime()           // 取得session创建时间
public long getLastAcessTime()          // 取得session最后一次创建的时间
public boolean isNew()                  // 判断是否是新的session(新用户)
public void invalidate()                // 使session失效
public Enummeration getAttributeNames() // 得到全部的属性名名称

sessionID
每一个通过浏览器连接到服务器的用户都会由服务器自动创建一个唯一的sessionId用来区分不同的用户。

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    String id = session.getId();
%>
sessionID:<%=id %><br/>sessionID的长度:<%=id.length() %><br />
自动设置的Cookie JSESSIONID:<br />
<%
    Cookie[] cookies = request.getCookies();
    for(Cookie c:cookies){
        String name = c.getName();
        String value = c.getValue();
%>
    <%=name %><%=value %>
<%
    }
%>

运行结果:
JavaWeb基础_第24张图片
发现自动为我们设置的那个cookie就是sessionID,session在操作的时候使用到了Cookie的处理机制!
但是对于Session,有一点必须强调:如果服务器重启了,sessionid肯定要重新分配。但是我们在浏览网页的时候,即使服务器重启,对用户而言信息仍然得到的保存,这是因为将session对象进行了序列化,在项目的work目录会有一个SESSION.ser文件用于保存用户的session。服务器开机的时候从这里读取session并删除,这样用户的状态就得到了保存!
登陆和注销
这是session应用最多的一种场景。见以下的一个例子:用户在login.jsp中提交表单到自身页面(login.jsp)中进行验证,如果验证成功则2秒之后自动跳转到welcome.jsp,如果验证失败则提示错误的用户名和密码;在welcome.jsp中可以判断用户是否登录。如果用户已经登录可以进行注销,如果没有登录则提示用户登录。注销的操作放在logout.jsp中。
login.jsp

<%@ page contentType="text/html; charset=utf-8" %>
<form action="login.jsp" method="post">
    用户名:<input type="text" name="username" /><br />
    密码:<input type="password" name="password" /><br />
    <input type="submit" value="提交" /><input type="reset" value="重置">
form>
<%
    String username = request.getParameter("username");
    String password = request.getParameter("password");

    if(!(username==null||"".equals(username)||password==null||"".equals(password))){
        if(username.equals("root")&&password.equals("123456")){
%>
            登录成功,2秒之后将跳转到欢迎页,如果没有跳转,请点击<a href="welcome.jsp">这里。a>
<%  
            session.setAttribute("username", username);
            response.setHeader("refresh", "2;url=welcome.jsp");
        }else{
%>
            错误的用户名和密码!
<%  
        }
    }
%>

welcome.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    String username;
    if((username = (String)session.getAttribute("username"))!=null){
%>
        <h3>欢迎<font color="red"><%=username %>font>光临h3>
        <h3>点击<a href="logout.jsp">此处注销a>h3>
<%  
    }else{
%>
        <h3>亲,你还没有登录哦,请先进行<a href="login.jsp">登录a>h3>
<%      
    }
%>

logout.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    session.invalidate(); // 让session失效
    response.setHeader("refresh", "2;url=login.jsp");
%>
<h3>您已经成功退出本系统,2秒后跳转到首页。h3>
如果没有自动跳转,请点击<a href="login.jsp">这里a>。

运行效果:
JavaWeb基础_第25张图片
只有成功登录的用户才可以访问welcome.jsp

除了session可以用于会话的跟踪,还有以下3种方式:

  1. 通过Cookie
  2. 通过表单的隐藏域
  3. URL重写

判断新用户
可以用session的isNew()方法判断一个用户是否是第一次访问该页面。

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    if(session.isNew()){
%>
        <h1>你是新用户h1>
<%  
    }else{
%>
        <h1>你是老用户h1>
<%  
    }
%>

取得用户的操作时间

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    long startTime = session.getCreationTime();
    long endTime = session.getLastAccessedTime();
%>
您已经停留该页面<%=(endTime-startTime)/1000 %>

application内置对象

application内置对象是javax.servlet.ServletContext接口的实例化对象,代表的是整个Web容器,常用方法:

String getRealPath(String path)         // 取得虚拟目录对应的绝对路径
public Enummeration getAttributeNames() // 取得所有属性的名称
public String getContextPath()          // 取得当前的虚拟路径的名称

取得绝对路径

String path = application.getRealPath("/");
path = getServletContext().getRealPath("/"); // 省略了this(当前容器)
path = this.getServletContext().getRealPath("/");

取得了绝对路径就可以进行文件的操作了!
用户在input_content.html中输入文件名和文件内容,提交到服务器,服务器将文件保存到项目绝对路径的file目录下,并显示出来:
input_content.html


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>请输入信息title>
head>
<body>
    <form action="input_content.jsp">
        文件名:<input type="text" name = "filename"><br />
        文件内容:<textarea rows="3" cols="30" name="filecontent">textarea><br />
        <input type="submit" value="保存"><input type="reset" value="重置">  
    form>
body>
html>

input_content.jsp

<%@page import="java.io.FileInputStream"%>
<%@page import="java.util.Scanner"%>
<%@page import="java.io.FileOutputStream"%>
<%@page import="java.io.PrintStream"%>
<%@page import="java.io.File"%>
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    // 保存文件
    request.setCharacterEncoding("utf-8");
    String name = request.getParameter("filename");
    String filecontent = request.getParameter("filecontent");

    // 要想操作文件必须先要有绝对路径
    String filename = getServletContext().getRealPath("/") + "file" + File.separator + name;

    File file = new File(filename);
    if(!file.getParentFile().exists()){
        file.getParentFile().mkdir();   // 建立目录
    }
    PrintStream ps = new PrintStream(new FileOutputStream(file));
    ps.println(filecontent);
    ps.close();
%>
<%
    // 重新读取文件
    Scanner scanner = new Scanner(new FileInputStream(file));
    scanner.useDelimiter("\n");
    StringBuilder sb = new StringBuilder();
    while(scanner.hasNext()){
        sb.append(scanner.next());
    }
    scanner.close();
%>
<%=sb %> 

运行结果:
JavaWeb基础_第26张图片
案例讲解:网站计数器
在进行编码之前首先应该注意以下的3个问题:

  1. 网站的来访人数可能会有很多,所以应该使用大整数——BigInteger来完成;
  2. 用户在每新开一个浏览器才需要进行计数的操作,因此在执行计数器加法之前应该先进行isNew()判断。
  3. 在进行更改、保存的时候应该进行同步操作。

简易实现:

<%@page import="java.io.FileOutputStream"%>
<%@page import="java.io.PrintStream"%>
<%@page import="java.io.FileInputStream"%>
<%@page import="java.util.Scanner"%>
<%@page import="java.io.File"%>
<%@page import="java.math.BigInteger"%>
<%@ page contentType="text/html; charset=utf-8" %>
<%!
    BigInteger count = null;
 %>
<%!
    // 为了简化开发,所有的操作定义在方法中
    public BigInteger load(File file){

        try{
            if(file.exists()){
                Scanner scanner = new Scanner(new FileInputStream(file));
                if(scanner.hasNext()){
                    count = new BigInteger(scanner.next());
                }
                scanner.close();
            }else{ // 应该保存一个新的,从0开始
                count = new BigInteger("0");
                save(file, count); // 保存一个新的文件
            }
        }catch(Exception e){ 
            e.printStackTrace();
        }
        return count;
    }

    public void save(File file, BigInteger count) {
        PrintStream ps = null;
        try {
            ps = new PrintStream(new FileOutputStream(file));
            ps.println(count);
            ps.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }%>
<%
    String filename = this.getServletContext().getRealPath("/") + "count.txt"; // 保存计数结果
    File file = new File(filename);
    if(session.isNew()){
        synchronized(this){
            count = load(file); // 读取
            count = count.add(new BigInteger("1")); // 计数器加1
            save(file, count);
        }
    }
%>
您是第<%=count==null?0:count%>位访问本站的用户

查看属性

<%@page import="java.util.Enumeration"%>
<%@page contentType="text/html; charset=utf-8" %>
<%
    Enumeration enu = this.getServletContext().getAttributeNames();;
    while(enu.hasMoreElements()){
        String name = (String)enu.nextElement();
%>
        <%=name %>--><font color="red"><%=getServletContext().getAttribute(name) %>font><br/>
<%  
    }
%>

可以发现所有通过tomcat配置的第三方jar文件都是通过application属性设置上去的,所以每次配置好新的开发包文件,服务器必须重启。

web安全性和config内置对象

在配置Tomcat的时候会发现虚拟目录有一个WEB-INF目录,该目录一般是不让用户看见的,安全性肯定是最高的。如果想要访问该目录,可以使用映射路径的方式完成。——修改项目中的web.xml即可。


<servlet>
    <servlet-name>heservlet-name>
    <jsp-file>/WEB-INF/hello.jspjsp-file>
servlet>
<servlet-mapping>
    <servlet-name>heservlet-name>
    <url-pattern>/hello.loveurl-pattern>
servlet-mapping>

servlet-mapping表示的是一个路径的配置,在访问的时候直接输入url-pattern指定的内容就可以找到servlet-name,通过servlet-name找到servlet节点中的jsp文件,从而实现WEB-INF目录下的jsp文件。
JavaWeb基础_第27张图片

config对象和web安全性的关系

congig对象只有对映射后的jsp文件才有作用。config对象是javax.servlet.ServletConfig接口的实例化对象,主要功能是取得一些初始化的配置信息。常用方法:

String getInitParameter(String name)        // 得到一个初始化参数的内容
Enumeration getInitParameterNames() // 得到所有的初始化参数的名称

所有的初始化参数都应该在web.xml中配置。

<servlet>
    <servlet-name>dbinitservlet-name>
    <jsp-file>/WEB-INF/config.jspjsp-file>
    
    <init-param>
        <param-name>driverparam-name>
        <param-value>com.mysql.jdbc.Driverparam-value>
    init-param>
    <init-param>
        <param-name>urlparam-name>
        <param-value>jdbc:mysql://localhost:3306/testparam-value>
    init-param>
servlet>
<servlet-mapping>
    <servlet-name>dbinitservlet-name>
    <url-pattern>/config.loveurl-pattern>
servlet-mapping>

在config.jsp中取得web.xml中配置的初始化参数:

<%
    String  dbDriver = config.getInitParameter("driver");
    String dbUrl = config.getInitParameter("url");
%>
驱动程序:<h3><%=dbDriver %>h3>
连接地址:<h3><%=dbUrl %>h3>

运行结果:
JavaWeb基础_第28张图片
取得初始化参数必须在web.xml中配置映射路径。

out对象

out内置对象是javax.servlet.jsp.JspWriter类的实例化对象,主要功能就是使用print()println()方法进行页面的输出,但是实际的开发中多用表达式输出。out对象定义了以下几个方法:

public int getBufferSize()  // 得到缓冲区大小【字节】
public int getRemaining()   // 得到剩余的缓冲区【字节】
<%@ page contentType="text/html; charset=utf-8" %>
<%
    int buffer = out.getBufferSize();
    int remain = out.getRemaining();
    int use = buffer - remain;
 %>
缓冲区大小:<%=buffer%><br/>
剩余的缓冲区:<%=remain%><br />
已经用掉的缓冲区:<%=use%>

JavaWeb基础_第29张图片

pageContext内置对象

pageContext内置对象是javax.servlet.jsp.PageContext接口的实例化对象,表示的是一个jsp页面的上下文,在此类中除了定义了属性的操作还定义了如下的方法:

public abstract void forward(jString relativeUrlPath)           
public abstract void include(java.lang.String relativeUrlPath)
public abstract ServletConfig getServletConfig()
public abstract ServletContext getServletContext()
public abstract ServletRequest getRequest()
public abstract ServletResponse getResponse()
public abstract HttpSession getSession()

pageContext功能强大,几乎可以操作任何内置对象。
使用pageContext完成服务器内部转发
pageContext01.jsp

<%@ page contentType="text/html; charset=utf-8" %>
<%
    pageContext.forward("pageContext02.jsp?name=root&password=123456");
 %>

pageContext02.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    String name = pageContext.getRequest().getParameter("name");
    String password = pageContext.getRequest().getParameter("password");
    String path = pageContext.getServletContext().getRealPath("/");
%>
用户名:<%=name%>
密码:<%=password%>
物理路径:<%=path%>

运行结果:
JavaWeb基础_第30张图片

实例讲解(注册验证-使用JavaBean)

完成程序所需要的页面:

  1. index.jsp:注册信息填写页,输入错误的数据会进行提示。
  2. check.jsp:将输入的表单自动赋值给JavaBean,同时进行验证,如果验证失败则返回index.jsp。
  3. success.jsp:注册成功页,显示用户信息
  4. Register.java:注册所使用的JavaBean,可以接收参数,同时进行判断,并返回错误的结果。

在本程序中,由于错误不固定,所以使用Map接口保存所有的错误信息。
Register.java

package org.gpf;

import java.util.HashMap;
import java.util.Map;
/**
 * JavaBean:用于完成信息的验证
 * @author gaopengfei
 * @date 2015-4-19 下午9:06:09
 */
public class Register {

    private String name;
    private String age; // 使用字符串,防止NumberFormatException
    private String email;
    private Map errors = null; // 存放错误信息

    /**
     * 初始化值
     */
    public Register() {

        this.name = "";
        this.age = "";
        this.email = "";
        this.errors = new HashMap();
    }

    /**
     * 验证方法
     * @return
     */
    public boolean isValidate(){

        boolean flag = true;
        if(!this.name.matches("\\w{6,15}")){
            flag = false;
            this.name = "";//清空name
            System.out.println("用户名");
            errors.put("errorname", "用户名是6~15位的字母或者数字!");
        }
        if (!this.email.matches("\\w+@\\w+\\.\\w+\\.?\\w*")) {
            flag = false;
            System.out.println("Email");
            this.email = "";//清空email
            errors.put("erroremail", "Email地址不合法!");
        }
        if (!this.age.matches("\\d+")) {
            flag = false;
            this.age = "";//清空age
            System.out.println("age");
            errors.put("errorage", "年龄只能是数字!");
        }

        return flag;
    }

    /**
     * 得到错误信息
     * @return
     */
    public String getErrorMsg(String key){

        String error = this.errors.get(key);
        return error==null?"":error;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Map getErrors() {
        return errors;
    }

    public void setErrors(Map errors) {
        this.errors = errors;
    }

}

index.jsp

<%@ page contentType="text/html; charset=utf-8" %>
<%
    request.setCharacterEncoding("utf-8");
 %>
<jsp:useBean id="reg" class="org.gpf.Register" scope="request" />
<form action="check.jsp" method="post">
    用户名:<input type="text" name = "name" value=''><%=reg.getErrorMsg("errorname") %><br />
    年龄:<input type="text" name = "age" value=''><%=reg.getErrorMsg("errorage") %><br />
    E-Mail<input type="text" name = "email" value=''><%=reg.getErrorMsg("erroremail") %><br />
    <input type="submit" value="提交" /><input type="reset" value="重置" />
form>

check.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    request.setCharacterEncoding("utf-8");
%>
<jsp:useBean id="reg" class="org.gpf.Register" scope="request" />

<jsp:setProperty property="*" name="reg"/>
<%
    if(reg.isValidate()){
%>
        <jsp:forward page="success.jsp" />
<%  
    }else{
%>
        <jsp:forward page="index.jsp" />
<%  
    }
%>

success.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%
    request.setCharacterEncoding("utf-8");
 %>
<jsp:useBean id="reg" scope="request" class="org.gpf.Register" />
用户名:<jsp:getProperty property="name" name="reg"/><br />
年龄:<jsp:getProperty property="age" name="reg"/><br />
邮箱:<jsp:getProperty property="email" name="reg"/><br />

运行结果:
JavaWeb基础_第31张图片
有运行结果可以看到即使用户提交的字段有一部分不正确,会保存正确的字段。而仅仅清空不正确的字段——提高了用户体验。

DAO设计模式

JavaWeb基础_第32张图片

DAO的组成

在整个DAO中实际上都是以接口作为标准的。即:客户端依靠DAO实现的接口进行操作,而服务器端要将接口进行具体的实现,DAO通常由以下几部分组成。这是运用DAO实现的人员管理系统。https://git.oschina.net/gaopengfei/JavaUserAdmin.git

  1. DatabaseConnection:专门负责数据库的打开和关闭连接的类;
  2. VO:主要由属性、getter、setter方法组成;VO中的属性和表中的字段一一对应,每一个VO类的对象都表示表中每一条记录;
  3. DAO:主要定义操作的接口,定义一系列数据库的原子操作(CRUD)
  4. Impl:DAO操作的真实实现类,完成具体的数据库操作,但是不负责数据库的打开和关闭;
  5. Proxy:代理实现类。完成数据库的连接和关闭操作,并且调用真实实现类对对象进行操作;
  6. Factory:工厂类,通过工厂取得DAO的一个实例化对象。

文件上传

SmartUpload组件

上传文件的时候表单必须要进行封装,因为传递的不再是普通的文本数据了。封装需要依靠enctype完成。

 <form action="smart_upload_01.jsp" method="post" enctype="multipart/form-data">
        请选择要上传的文件:<input type="file" name="pic">
        <input type="submit" value="上传">
   form>

在网站大额根目录下建立一个upload_file目录,该目录专门用于存放上传的文件。
通过SmartUpload上传文件的步骤:

  1. 实例化SmartUpload对象;
  2. 初始化上传操作;
  3. 准备上传;
  4. 保存文件。

smart_upload_01.jsp

<%@ page import="org.lxh.smart.*"%>
<%
    SmartUpload smart = new SmartUpload();
    smart.initialize(pageContext);          // 初始化上传操作
    smart.upload();                         // 上传准备
    smart.save("upload_file");              // 文件保存
%>

以上代码虽然完成了表单的封装,但是有一个新的问题出现了:要使用文件的上传就必须封装表单——表单封装之后会导致request.getParameter()无法使用——因为所有的数据不再是文本,而是二进制比特流了。如果想要接收参数必须依靠SmartUpload中提供的方法支持。

<form action="smart_upload_02.jsp" method="post" enctype="multipart/form-data">
        姓名:<input type="text" name="uname"><br />
        照片:<input type="file" name="pic"><br />
        <input type="submit" value="上传">
        <input type="reset" value="重置">
form>

smart_upload_02.jsp

<%
    request.setCharacterEncoding("utf-8");

    SmartUpload smart = new SmartUpload();
    smart.initialize(pageContext);          // 初始化上传操作
    smart.upload();                         // 上传准备
    smart.save("upload_file");              // 文件保存

    // 通过SmartUpload中封装的方法取得表单中的文本字段
    String name = smart.getRequest().getParameter("uname");
%>

request无法取得:<%=request.getParameter("uname")  %><br>
通过SmartUpload中封装的方法取得姓名:<%=name  %>

如果多个用户指定的文件名完全一样,肯定会发生文件的覆盖问题!为了解决这个问题可以采用自动文件重命名的方式防止文件重名,自动命名的格式如下:

IP地址 + 时间戳 + 三位随机数

要完成文件名的拼凑,最好专门定义一个类:IPTimeStamp

public class IPTimeStamp {

    private SimpleDateFormat sdf;
    private String ip;

    public IPTimeStamp() {

    }

    public IPTimeStamp(String ip) {
        this.ip = ip;
    }

    /**
     * IP+时间戳+随机数
     */
    public String getIPTimeRand() {

        StringBuffer sb = new StringBuffer();

        if (this.ip != null) {
            String[] s = this.ip.split("\\."); // 注意:.需要转义操作
            for (String string : s) {
                sb.append(this.addZero(string, 3));
            }
        }

        sb.append(this.getTimeStamp());
        Random r = new Random();
        for (int i = 0; i < 3; i++) {
            sb.append(r.nextInt(10));
        }
        return sb.toString();
    }

    /**
     * 返回时间戳
     * @return
     */
    public String getTimeStamp() {

        this.sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        return this.sdf.format(new Date());
    }

    /**
     * 返回指定位数的数字,位数不够前面补0
     */
    private String addZero(String str, int len) {

        StringBuffer sb = new StringBuffer();
        sb.append(str);
        while (sb.length() < len) {
            sb.insert(0, 0); // 在第0个位置补0
        }
        return sb.toString();
    }

}

smart_upload_03.html

<form action="smart_upload_03.jsp" method="post" enctype="multipart/form-data">
        照片1:<input type="file" name="pic1"><br />
        照片2:<input type="file" name="pic2"><br />
        照片3:<input type="file" name="pic3"><br />
        照片4:<input type="file" name="pic4"><br />
        照片5:<input type="file" name="pic5"><br />
        <input type="submit" value="上传">
        <input type="reset" value="重置">
   form>

smart_upload_03.jsp

<%
    request.setCharacterEncoding("utf-8");

    SmartUpload smart = new SmartUpload();
    smart.initialize(pageContext);          
    smart.upload();                         
    IPTimeStamp ipts = new IPTimeStamp(request.getRemoteAddr());    // 取得客户端ip

    for(int i = 0;iString ext = smart.getFiles().getFile(i).getFileExt();          // 得到文件拓展名
        String filename = ipts.getIPTimeRand() + "." + ext;
        smart.getFiles().getFile(i).saveAs(this.getServletContext().getRealPath("/") + "upload_file" + File.separator + filename);
%>



<%  
    // 利用正则匹配
        if(smart.getFiles().getFile(i).getFileName().matches("^\\w+.(jpg|gif|png)$")){
%>
            <img src="upload_file/<%=filename %>" />
<%  
        }
    }
 %>

如果服务器报错可以尝试使用127.0.0.1访问,而不是localhost访问。

FileUpload组件

如果没有使用框架又需要做一个上传,最好使用SmartUpload组件。
FileUpload是apache组织提供的免费上传组件,下载地址:http://commons.apache.org/proper/commons-fileupload/ 但是此组件还依赖于commons组件,所以从apache下载组件的时候还需要连同commons组件的IO包一起下载http://commons.apache.org/proper/commons-io/。解压,将jar文件添加到Tomcat的lib目录。
FileUpload的使用步骤如下:

  1. 创建磁盘工厂。DiskFileItemFactory factory = new DiskFileItemFactory()
  2. 创建处理工具。ServletFileUpload upload = new ServletFileUpload(factory)
  3. 设置上传文件大小。upload.setFileMaxSize(3145728)
  4. 接收全部内容。List items = upload.parseRequest(request)

在FileUpload中不管是基本数据还是上传的文件都是按照FileItem表示出来,通过List一起接收。通过FileItem的getFieldName()方法可以区分文件和文本。

区分文本字段和文件
fileupload_demo01.html

<form action="fileupload_demo01.jsp" method="post" enctype="multipart/form-data">
    姓名:<input type="text" name="uname"><br>
    照片:<input type="file" name="pic"><br>
    <input type="submit" value="提交"><input type="reset" value="重置">
form> 

fileupload_demo01.jsp

 <body>
  <ul>
<%
    DiskFileItemFactory factory = new DiskFileItemFactory();
    ServletFileUpload upload = new ServletFileUpload(factory);
    upload.setFileSizeMax(3*1024*1024); //  只能上传3M

    List items = upload.parseRequest(request); // 接收全部内容
    Iterator iterator = items.iterator();
    while(iterator.hasNext()){
        FileItem item = iterator.next();
        String fieldName = item.getFieldName();  // 取得表单控件的名字
%>
        <h3><%=fieldName %>---<%=item.isFormField() %>h3>
<%      
        if(!item.isFormField()){
            String filename = item.getName();           // 文件名
            String contentType = item.getContentType(); // 文件类型
            long sizeInByte = item.getSize();           // 文件大小
%>  
            <li><h4>文件名称:<%=filename %>h4>li>
            <li><h4>文件类型:<%=contentType %>h4>li>
            <li><h4>文件大小:<%=sizeInByte %>h4>li>
<%          
        }else{
            String value = item.getString();
%>
            <li><h4>普通参数:<%=value %>h4>li>
<%          
        }
    }
%>
    ul>
  body>

运行结果:
JavaWeb基础_第33张图片
文件的保存

<%
    DiskFileItemFactory factory = new DiskFileItemFactory();
    factory.setRepository(new File(getServletContext().getRealPath("/") + "uploadtemp")); // 临时文件
    ServletFileUpload upload = new ServletFileUpload(factory);
    upload.setFileSizeMax(3*1024*1024); //  只能上传3M

    List items = upload.parseRequest(request); // 接收全部内容
    Iterator iterator = items.iterator();
    IPTimeStamp ipts = new IPTimeStamp(request.getRemoteAddr());
    while(iterator.hasNext()){
        FileItem item = iterator.next();
        String fieldName = item.getFieldName();  // 取得表单控件的名字
        if(!item.isFormField()){
            File saveFile = null;
            InputStream input = null;
            OutputStream output = null;
            input = item.getInputStream();
            output = new FileOutputStream(new File(this.getServletContext().getRealPath("/") + "upload" + File.separator + ipts.getIPTimeRand() + "." + item.getName().split("\\.")[1]));
            int temp = 0;
            byte[] data = new byte[512];
            while((temp = input.read(data, 0, data.length))!=-1){
                output.write(data, 0, temp);
            }
            input.close();
            output.close();
        }else{
            String value = item.getString();
        }
    }
%>

FileUpload的问题

  1. 无法使用request.getParameter()准确获得提交的参数;
  2. 无法使用request.getParameterValues()获得一组提交的参数;
  3. 所有上传的文件都需要依次进行判断,才能够分别保存,不能一次性批量保存。

将此类进行包装:
JavaWeb基础_第34张图片

public class FileUploadTool {

    private HttpServletRequest request = null;
    private List items = null;
    private Map> params = new HashMap>();
    private Map files = new HashMap();

    @SuppressWarnings("unchecked")
    public FileUploadTool(HttpServletRequest request,long maxSize,String tmpDir) throws FileUploadException {
        this.request = request;
        DiskFileItemFactory factory = new DiskFileItemFactory();
        if (tmpDir != null) {
            factory.setRepository(new File(tmpDir));
        }
        ServletFileUpload upload = new ServletFileUpload(factory);
        if (maxSize > 0) {
            upload.setSizeMax(maxSize);
        }
        this.items = upload.parseRequest(request);
        this.init();
    }

    public void init(){
        Iterator iterator = this.items.iterator();
        IPTimeStamp ipts = new IPTimeStamp(request.getRemoteAddr());
        while (iterator.hasNext()) {
            FileItem fileItem = (FileItem) iterator.next();

            if (fileItem.isFormField()) {               // 普通参数
                String name = fileItem.getFieldName();
                String value = fileItem.getString();
                Listtemp = null;
                if (this.params.containsKey(name)) {
                    temp = this.params.get(name);
                }else{
                    temp = new ArrayList();
                }
                temp.add(value);
                this.params.put(name, temp);
            }else {                                     // 文件
                String filename = ipts.getIPTimeRand() + "." + fileItem.getName().split("\\.")[1];
                this.files.put(filename, fileItem);
            }
        }
    }

    /**
     * 根据名称取得参数名称取得内容
     * @param name
     * @return
     */
    public String getParameter(String name){

        String ret = null;
        List temp = this.params.get(name);
        if (temp!=null) {
            ret = temp.get(0);
        }
        return ret;
    }

    public String[]  getParameterValues(String name){

        String[] ret = null;
        List temp = this.params.get(name);
        if (temp!=null) {
            ret = temp.toArray(new String[]{});
        }
        return ret;
    }

    public Map getUploadFiles(){

        return this.files;
    }

    public List saveAll(String saveDir) throws IOException {

        List names = new ArrayList();
        if (this.files.size() > 0) {
            Set keys = this.files.keySet();     // key是文件名称
            Iterator iterator = keys.iterator();
            File saveFile = null;
            InputStream input = null;
            OutputStream out = null;

            while (iterator.hasNext()) {
                FileItem item = this.files.get(iterator.next());
                String filename = new IPTimeStamp(request.getRemoteAddr()).getIPTimeRand() + "." + item.getName().split("\\.")[1];
                saveFile = new File(saveDir + filename);
                names.add(filename);    // 名字不返回后期无法操作

                try {
                    input = item.getInputStream();
                    out = new FileOutputStream(saveFile);
                    int temp = 0;
                    byte[] buf = new byte[1024];
                    while ((temp = input.read(buf, 0, buf.length))!=-1) {
                        out.write(buf, 0, temp);
                    }
                } catch (IOException e) {
                    throw e;
                }finally{
                    input.close();
                    out.close();
                }
            }
        }
        return names;
    }
}

在JSP页面中就可以这样简单调用了:
fileupload_demo01.html

<form action="fileupload_demo01.jsp" method="post" enctype="multipart/form-data">
    姓名:<input type="text" name="uname"><br>
    兴趣:<input type="checkbox" name="inst" value="swing">游泳 
        <input type="checkbox" name="inst" value="song">唱歌
        <input type="checkbox" name="inst" value="run">跑步 
        <input type="checkbox" name="inst" value="basketball">篮球 <br>
    照片:<input type="file" name="pic1"><br>
    照片:<input type="file" name="pic2"><br>
    照片:<input type="file" name="pic3"><br>
    照片:<input type="file" name="pic4"><br>
    <input type="submit" value="提交"><input type="reset" value="重置">
form>

fileupload_demo01.jsp

<% 
    FileUploadTool fut = new FileUploadTool(request,3*1024*1024,getServletContext().getRealPath("/") + "uploadtemp");
    String name = fut.getParameter("uname");
    String[] inst = fut.getParameterValues("inst");
    List<String> all = fut.saveAll(getServletContext().getRealPath("/") + "upload" + File.separator);

%>
    姓名:<%=name %><br>
    兴趣:
<%
    for(String s:inst){
%>
        <%=s %><%  
    }
    Iterator<String> iterator = all.iterator();
    while(iterator.hasNext()){

%>
        <img alt="" src="upload/<%=iterator.next() %>">
<%  
    }
 %>

你可能感兴趣的:(Web开发,java)