servlet笔记

381 阅读39分钟

#Servlet简介

Servlet全称 Server Applet ,服务端小应用

Servlet运行在支持java的应用服务器中,主要用作Web服务器,处理客户端(浏览器)的请求,并响应给客服端,一种B/S模型

Servlet项目部署

结构

TomCat项目目录.png

package com.test.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("Hello Servlet");
    }
}

配置web.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
           version="2.5">
        <servlet>
            <servlet-name>myServlet</servlet-name>
            <servlet-class>com.test.servlet.MyServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>myServlet</servlet-name>
            <url-pattern>/myServlet</url-pattern>
        </servlet-mapping>
</web-app>

运行项目

Tomcat项目运行图.png

Servlet继承结构

Servlet继承结构.png

Servlet 接口

Defines methods that all servlets must implement.

定义所有servlet必须实现的方法

ServletConfig接口

A servlet configuration object used by a servlet container to passinformation to a servlet during initialization.

一个servlet容器用来传递的servlet配置对象在初始化期间向servlet发送信息。

servlet运行期间需要的一些辅助信息,这些信息可以在web.xml文件中,使用一个或多个元素,进行配置

当初始化一个Servlet时,会将改Servlet配置信息,封装到一个ServletConfig中,通过调用init(ServletConfig config)方法,将ServletConfig对象传递给Servlet

GenericServlet类

Defines a generic, protocol-independent servlet. To write an HTTP servlet for use on the Web, extend {@link javax.servlet.http.HttpServlet} instead.

定义一个通用的、独立于协议的servlet。编写一个HTTP servlet 在Web上使用,扩展{@link javax.servlet.http。HttpServlet}。

GenericServlet实现了Servlet接口,是一个抽象类。

我们可以通过继承GenericServlet来编写自己的Servlet对象。

HttpServlet类

Provides an abstract class to be subclassed to create an HTTP servlet suitable for a Web site. A subclass of HttpServlet must override at least one method, usually one of these:

  • doGet, if the servlet supports HTTP GET requests
  • doPost, for HTTP POST requests
  • doPut, for HTTP PUT requests
  • doDelete, for HTTP DELETE requests
  • init and destroy, to manage resources that are held for the life of the servlet
  • getServletInfo, which the servlet uses to provide information about itself

提供要创建的子类的抽象类

适合于Web站点的HTTP servle的一个子类HttpServlet必须至少覆盖一种方法,通常是下面的一种:

  • doGet, 如果servlet支持HTTP GET请求
  • doPost, 对于HTTP POST请求
  • doPut, 对于HTTP PUT请求
  • doDelete, 对于HTTP删除请求
  • init and destroy, 管理在servle生命周期中保留的资源
  • getServletInfo, servlet使用哪个来提供关于自身的信息

HttpServlet类方法

1.Init() 创建servlet对象后立即调用该方法完成其他初始化工作

2.service(),处理客户端请求,执行业务操作,利用Response对象响应客户端请求

3.destroy(),在销毁Servlet对象之前调用该方法,释放资源

4.getServletConfig(),ServletConfig是容器向servlet传递参数的载体。

5.getServletInfo(),获取servlet相关信息。

Servlet生命周期

当浏览器第一次请求一个Servlet对象时,Servlet容器会实例这个Servlet对象(只实例一次)。然后调用Servlet对象init方法(运行时只初始化一次),并在新线程(每个请求都是一个单独线程)执行Servlet对象service()(执行多次)方法,service()方法执行完,这时Servlet对象会在缓存在Servlet容器中以单例的形式存在,直到Servlet容器被销毁之前,调用一次destory()方法(只执行一次),释放Servlet对象

实例

public class Servlet1 extends HttpServlet {
    public Servlet1(){
        System.out.println("Servlet1被构造了");
    }

    @Override
    public void init() throws ServletException {
        System.out.println("Servlet1初始化");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet1服务方法");
    }

    @Override
    public void destroy() {
        System.out.println("Servlet1销毁方法");
    }
}

线程安全问题

每一次请求都会产生一个线程,每个Servlet实例只有一次并且是唯一的。

多个线程去请求一个Servlet实例对象中的成员变量,就有可能会造成线程不安全

解决思路:将变量尽量写在方法体中,少用成员变量

Servlet处理请求的过程

没有重写sevice方法

​ 如果自定义的Servlet没有重写service方法,程序运行时请求这个Servlet会调用HttpServlet类中service中方法,service方法会判断浏览器请求方式,响应给浏览器HTTP 405 方法不允许(或HTTP 400客户端请求的语法错误,服务器无法理解,这里不会报400错误)

public class Servlet2 extends HttpServlet {
   
}

HttpServletservice方法

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);
    }
}

HttpServletdoGet()方法和doPost()方法


protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }
 protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_post_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
  }

HttpServletResponse部分源码

/ * *
* 状态码(404),指示请求的资源不是可用
* /
public static final int SC_BAD_REQUEST = 400;
/ * *
* 状态码(405),指示
* <code><em>Request-Line</em></code>对于资源是不允许的
* 由<code><em>Request-URI</em></code>识别。
* /
 public static final int SC_METHOD_NOT_ALLOWED = 405;    

重写了service方法

​ 如果自定义的Servlet重写了Service方法,程序运行时请求这个Servlet,HttpServlet对象中执行service方法,service方法中调用Servlet重写的Service方法执行,处理请求并响应给浏览器

注意于上面service方法参数不同:

  • service(HttpServletRequest req, HttpServletResponse resp)
  • +service(ServletRequest req, ServletResponse res)

HttpServlet中部分源码

public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
        service(request, response);
    }
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //响应给浏览器
        resp.getWriter().println("Hello Servlet");
    }
}

自定义处理请求2中方式

​ 1.重写sevice() 不需要区分Get 或 Post 方法处理方式不一样

​ 2.重写 doGet()doPost() 适用于明确区分Get 或 Post 方法处理方式不一样

public class Servlet2 extends HttpServlet {
/*    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //响应给浏览器
        resp.getWriter().println("Hello Servlet");
    }*/


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

HttpReuquest

封装客户端请求数据

HttpReuquest生命周期

浏览器请求一次,实例化一次HttpReuquest对象,Servlet容器调用servlet中service方法HttpReuquest对象作为service方法参数传递,service 方法处理请求并响应给浏览器,service方法执行完毕。HttpReuquest对象销毁

获取请求信息

public class Servlet3 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("URI:"+req.getRequestURI());
        //返回客户端浏览器发出请求时的完整URL。
        System.out.println("URL:"+req.getRequestURL());
        //返回发出请求的客户机的IP地址。
        System.out.println("客户端IP地址:"+req.getRemoteAddr());
        System.out.println("客户端Host:"+req.getRemoteHost());
        System.out.println("客户端Port:"+req.getRemotePort());

        System.out.println("协议名:"+req.getScheme());
        System.out.println("Serve主机名:"+req.getLocalName());
        //返回WEB服务器的IP地址。
        System.out.println("Serve主机IP:"+req.getLocalAddr());
        //返回WEB服务器处理Http协议的连接器所监听的端口。
        System.out.println("Server主机Port:"+req.getLocalPort());
        System.out.println("当前项目部署名:"+req.getContextPath());
        System.out.println("请求方式:"+req.getMethod());

        //设置Post请求数据的编码
        req.setCharacterEncoding("UTF-8");
        //根据请求头名,获取请求头对应值
        System.out.println(req.getHeader("Accept"));
        //获取请求头中所有请求名
        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String name=headerNames.nextElement();
            System.out.println(name+": "+req.getHeader(name) );
        }
    }
}

获取请求参数

public class Servlet4 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理Post乱码
        req.setCharacterEncoding("UTF-8");
        //获取提交过来的参数
        //根据参数名接收单个参数
        System.out.println("用户名:" + req.getParameter("username"));
        System.out.println("星期" + req.getParameter("week"));
        System.out.println("籍贯: " + req.getParameter("pro"));
        System.out.println("介绍: " + req.getParameter("introduce"));

        //获取复选框提交过来的数据,多个参数值使用同一个参数名
        String[] hobbies = req.getParameterValues("hobby");
        System.out.println("爱好:" + Arrays.toString(hobbies));

        //获取所有参数名
        /*username [aaa]
          hobby [1, 2, 3, 4]
          week [2]
          pro [3]
          introduce [一个小白]*/
        Enumeration<String> parameterNames = req.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String name = parameterNames.nextElement();
            System.out.println(name + " " + Arrays.toString(req.getParameterValues(name)));
        }


        //获取参数map集合
        /*username=[aaa]
          hobby=[1, 2, 3, 4]
          week=[2]
          pro=[3]
          introduce=[一个小白]*/
        Map<String, String[]> parameterMap = req.getParameterMap();
        Set<String> strings = parameterMap.keySet();
        for (String name : strings) {
            String[] value = parameterMap.get(name);
            System.out.println(name + "=" + Arrays.toString(value));
        }
    }
}

HttpResponse

封装响应给客户端的数据

public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置响应状态码
        //resp.setStatus(405);
        //指定的状态码发送错误响应到客户端,并清除缓冲区
        //resp.sendError(405,"请求方法发不允许");


        //设置响应头
        //resp.setHeader("Content-Type","text/html;");

        //设置响应类型默认是text/html
        //设置响应类型为html,浏览器以utf-8编码解析数据
        resp.setContentType("text/html;charset=UTF-8");
        //设置响应编码
        resp.setCharacterEncoding("UTF-8");

       // resp.setHeader("Content-Disposition", "attachment; filename=text.text");

        PrintWriter writer = resp.getWriter();
        writer.println("hello");
    }
}

ServletContext

​ ServletContext官方叫Servlet上下文。服务器会为每一个Web应用创建一个ServletContext对象。这个对象全局唯一,而且Web应用中的所有Servlet都共享这个对象。所以叫全局应用程序共享对象。

ServletContext生命周期

当容器启动时会创建ServletContext对象并一直缓存该对象,直到容器关闭,该对象生命周期结束。

ServletContext对象生命周期非常长,所以使用全局容器时不建议存放业务数据

作用:

  • 全局容器
  • 相对路径转绝对路径
  • 读取配置信息
  • 获取容器附加信息
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //ServletContext 获取方式
        //一个web应用只用一个ServletContext对象
        ServletContext servletContext = this.getServletContext();
        /*ServletContext servletContext1 = req.getServletContext();
        ServletContext servletContext3 = getServletConfig().getServletContext();
        System.out.println(servletContext==servletContext1);
        System.out.println(servletContext1==servletContext3);
        */

        //获取web服务名 项目部署名 路径
        String contextPath = servletContext.getContextPath();
        System.out.println(contextPath);

        //项目真实路径 + /upload
        String realPath = servletContext.getRealPath("/upload");
        System.out.println(realPath);

        //返回Servlet容器的名称和版本
        System.out.println(servletContext.getServerInfo());
        //返回Servlet容器所支持的Servlet主版本号
        System.out.println(servletContext.getMajorVersion());
        //返回Servlet容器所支持Servlet小版本
        System.out.println(servletContext.getMinorVersion());

        //获取<context-param>标签中的参数
        System.out.println(servletContext.getInitParameter("username"));
        System.out.println(servletContext.getInitParameter("password"));

        //获取全部参数名
        Enumeration<String> pnames = servletContext.getInitParameterNames();
        while(pnames.hasMoreElements()){
            String pname = pnames.nextElement();
            System.out.println(pname+":"+servletContext.getInitParameter(pname));
        }


        //向servletContext中追加数据
        servletContext.setAttribute("hello","你好呀");

        //获取servletContext中追加数据
        Object hello = servletContext.getAttribute("hello");
        System.out.println(hello);

        //移除servletContext中追加数据
        servletContext.removeAttribute("hello");

    }
}

web.xml配置信息

  <!--配置全局信息-->
    <context-param>
        <param-name>username</param-name>
        <param-value>test</param-value>
    </context-param>
    <context-param>
        <param-name>password</param-name>
        <param-value>123456</param-value>
    </context-param>

ServletConfig对象

封装Servlet配置信息

一个Servlet对象有一个ServletConfig对象,

public class Servlet3 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*ServletContext servletContext = this.getServletContext();
        //获取全部参数名
        Enumeration<String> pnames = servletContext.getInitParameterNames();
        while(pnames.hasMoreElements()){
            String pname = pnames.nextElement();
            System.out.println(pname+":"+servletContext.getInitParameter(pname));
        }*/


        //获取ServletConfig
        //每个Servlet独有信息
        ServletConfig config = this.getServletConfig();
        System.out.println(config.getInitParameter("computer"));

        //ServletConfig对象读取全部参数
        Enumeration<String> initPNames = config.getInitParameterNames();
        while (initPNames.hasMoreElements()){
            String name =initPNames.nextElement();
            System.out.println(name+":"+config.getInitParameter(name));
        }
    }
}

web.xml配置信息

<servlet>
        <servlet-name>servlet3</servlet-name>
        <servlet-class>com.test.servlet.Servlet3</servlet-class>
        <!--
            servlet初始参数
            每个Servlet独有信息
        -->
        <init-param>
            <param-name>computer</param-name>
            <param-value>微星</param-value>
        </init-param>
        <init-param>
            <param-name>CPU</param-name>
            <param-value>i9</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>servlet3</servlet-name>
        <url-pattern>/servlet3.do</url-pattern>
    </servlet-mapping>

Servlet乱码处理

POST乱码处理

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    /*设置post方式提交的数据包字符集为UTF-8
    该字符集必须和当前项目编码一致*/
    req.setCharacterEncoding("UTF-8");
}

GET乱码处理

方式一:

 byte[] bytes = req.getParameter("username").getBytes("ISO-8859-1");
 String str = new String(bytes, "UTF-8");

方式二:

设置Tomcat/conf/servler.xml URIEncoding="UTF-8"

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" URIEncoding="UTF-8" />

响应信息乱码

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 	//设置响应头
    //resp.setHeader("Content-Type","text/html;");
    //设置响应类型默认是text/html
    //设置响应类型为html,浏览器以utf-8编码解析数据
    resp.setContentType("text/html;charset=UTF-8");
    //设置响应编码
    resp.setCharacterEncoding("UTF-8");
    PrintWriter writer = resp.getWriter();
    writer.write("<h2>你好</h2>");
}

Servlet注解开发

在Servlet3.0以及之后的版本中支持注解开发Servlet。

对一Servlet配置不依赖于web.xml配置文件,使用@WebServlet 将一继承javax.servlet.http.HttpServlet的类定义为一个Servlet组件

@WebServlet注解中属性

属性名类型作用
initParamsWebInitParam[]Servlet的init参数
nameStringServlet的名称
urlPatternsString[]Servlet的访问URL,支持多个
valueString[]Servlet的访问URL,支持多个
loadOnStartupint自启动Servlet
descriptionStringServlet的描述
displayNameStringServlet的显示名称
asyncSupportedboolean声明Servlet是否支持异步操作模式
@WebServlet(
        description = "hello",
        displayName = "servlet2",
        name = "servlet2",
        loadOnStartup = 6,
        initParams = {
                @WebInitParam(name="test",value = "test"),
                @WebInitParam(name="test2",value = "test2")
        },
        urlPatterns = {"/servlet2.do","servlet2"},
        asyncSupported = false
)

实例

/**
 * loadOnStartup 项目是启动时加载顺序
 * @WebServlet 设置url映射路径
 * */
@WebServlet(value = {"/servlet1.do","/servlet1"},loadOnStartup = 6)
@WebInitParam(name="key",value = "value")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet1服务方法");
    }
}

请求转发

请求转发是通过HttpServletRequest或者ServletContext对象获取一个转发器ResquestDispatcher,再由请求作出转发处理,通过请求转发器,当前请求可以转方法给下一个web资源

  • RequestDispatcher.forward(req,resp);
  • requestDispatcher.include(req,resp);

特点

  • 请求参数可以传递
  • 浏览器地址栏不会发生变化,浏览器只发送一次请求,只负责接收服务端的响应
  • 可以跳转页面、跳转到另一个Servlet进行响应
  • 可以通过转发访问项目中WEB-INF安全目录资源
  • 不能通过转发(跨域)访问外部资源
  • 请求转发是服务器后台内部进行处理,对浏览器屏蔽

RequestDispatcher两种获取方式区别:

  • ServletContext获得转发器Path比需要“/”字符开头
  • HttpServletResponse获取的转发器, 相对路径或绝对路径都可访问
@WebServlet("/servlet3.do")
public class Servlet3 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //获取请求转发器方式
        /*ServletContext获得转发器,Path必需要“/”字符开头
        java.lang.IllegalArgumentException: 路径[aaa.html]不以“/”字符开头*/
        RequestDispatcher requestDispatcher = this.getServletContext().getRequestDispatcher("/aaa.html");
        //HttpServletResponse获取的转发器 可写相对路径或绝对路径
        RequestDispatcher requestDispatcher1 = req.getRequestDispatcher("aaa.html");
        requestDispatcher.forward(req,resp);
        //requestDispatcher1.forward(req,resp);
    }
}

forword处理流程

1.清空Response存放响应正文数据的缓冲区

2.如果源Servlet在请求转发之前,已经提交响应(flushBuffer() ,close() 方法),forword()方法会抛出异常IllegalStateException 提交响应后无法转发。为了避免该异常,不应该在源Servlet中提交响应结果。

include处理流程

1.跳转资源为Servlet或jsp,跳转到另一个service方法的响应正文添加到源Servlet中,如果是HTML只添加到源响应结果中

2.返回到源Servlet的service方法发中,继续执行后续代码

forword 和 include 区别

include,会将跳转的资源包含到源servlet中,执行service方法,响应给浏览器

forword,会清空源Servlet中response中数据,跳转目标资源,源Servlet中对Response对象操作不会被保存,目标资源响应给浏览器

实例

@WebServlet("/servlet1.do")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String money = req.getParameter("money");

        System.out.println("Servlet1接收到请求,"+username+"要借钱"+money+"元 ");

        //java.lang.IllegalStateException: 提交响应后无法转发
        //resp.getWriter().close();
        //resp.flushBuffer();


        //获取请求转发器
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("/servlet2.do");
        //由转发器调用forward或者include作出请求转发动作
       // requestDispatcher.forward(req,resp);

        resp.getWriter().write("hello");
        requestDispatcher.include(req,resp);
    }
}
@WebServlet("/servlet2.do")
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String money = req.getParameter("money");
        System.out.println("Servlet2接收到请求,"+username+"要借钱"+money+"元 ");

        resp.getWriter().write("收到"+money+"元");

        resp.getWriter().write("bye");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8 ");
    }
}

注意:请求转发只能访问当前项目中的资源,无法访问外部资源

//请求装法只能获取当前项目内部资源,不能访问外部资源
//HTTP状态 404 - 未找到 请求的资源[/servletDemo4_2/https://www.baidu.com]不可用
req.getRequestDispatcher("https://www.baidu.com").forward(req,resp);

请求转发访问当前项目WEB-INF

WEB-INF是项目中安全目录不能直接访问,可以通过转发访问

@WebServlet("/servlet4.do")
public class Servlet4 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      // WEB-INF是项目中安全目录不能直接访问,可以通过转发访问
      req.getRequestDispatcher("WEB-INF/ccc.html").forward(req,resp);
    }
}

响应重定向

原理

1.浏览器向服务器发送请求,访问服务器某个Servlet

2.服务器就收到请求,向浏览器响应HTTP 302 状态码(让浏览器请求访问另一个web资源)

3.浏览器接收到响应后,立即自动访问另一个web资源。这个web资源可能是同一个web服务,也可能是不同web服务器资源,意味着可以访问的不是当前服务器的外部资源

4.浏览器接收到另一个web资源响应结果

特点

  • 不能共享Request对象,请求参数不能直接传递
  • 浏览器地址栏发生变化,浏览器发送多次请求
  • 不能访问当前项目WEB-INF安全目录资源
  • 能跨域访问外部资源
  • 响应重定向是浏览器自主访问其他资源的行为

实例

@WebServlet("/servlet1.do")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String money = req.getParameter("money");
        System.out.println("Servlet1接收到请求,"+username+"要借钱"+money+"元 ");

        //响应重定向到Servlet2
        //resp.sendRedirect("servlet2.do?username"+username+"&money="+money);
        resp.sendRedirect("servlet2.do");
    
@WebServlet("/servlet2.do")
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username=req.getParameter("username");
        String money = req.getParameter("money");
        System.out.println("Servlet2接收到请求,"+username+"要借钱"+money+"元 ");
        
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("收到"+money+"元");
    }
}
public class Servlet3 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取项目主路径。 /项目名
        String base = req.getServletContext().getContextPath();
        //resp.sendRedirect(base +"/servlet2.do");
        // resp.sendRedirect("aaa.html");

        //重定向不能定向到项目中WEB-INF中的资源
        //resp.sendRedirect("WEB-INF/bbb.html");

        //重定向可以访问(跨域)外部资源
        resp.sendRedirect("https://www.baidu.com/");

    }
}

转发和重定向区别

转发:

​ 请求参数可以传递

​ 可以访问WEB-INF中资源

​ 只能访问当前项目资源

​ 浏览器产生一次请求

​ 浏览器地址栏没有变化

重定向:

​ 不能定向访问WEB-INF中资源

​ 可以访问当前项目资源,也可以访问外部资源

​ 浏览器产生多次请求

​ 浏览器地址栏发生变化

​ 产生一个新的请求,原来的请求参数不可以传递

* 4.转发和重定向的不同点
* 1)语句不同
*      转发:    request.getRequestDispatcher("/WEB-INF/test.jsp").forward(request, response);
*      重定向:  response.sendRedirect("/myservlet2/login/success.jsp");
* 2)添加后的地址栏路径不同
*      转发:跳转之前的地址
*      重定向:跳转之后的地址  /login/success.jsp
* 3)是否可以获取保存在request中的数据
*      转发:可以
*      重定向:无法获取
* 4)原理不同
*      转发 :相当于110
*      重定向:呼叫转移
* 5)效率不同
*      转发:效率高
*      重定向:效率低
* 6)跳转的范围不同
*      转发:仅限当前项目跳转(最大也是当前服务器)
*      重定向:可以跳转到互联网任意位置
* 7)跳转时的路径不同
*     绝对路径
*          转发:不支持(只能在当前项目跳转)
*          重定向:支持
*     根路径 /
*          转发:不需要写上下文路径
*          重定向:需要写上下文路径
*     相对路径
*          1.重定到其他服务器,只能使用绝对路径
*          2.跳转到当前服务器的其他项目,建议使用根路径
*          3.不建议使用相对路径
* 8) 跟路径的含义不同
*      转发: /代表当前项目
*      重定向: /代表当前服务器
* 9)刷新是否导致表单重复提交
*     重定向:不会
*     转发:会
*
* 10)是否经过过滤器
*    转发:不经过
*    重定向:经过

Servlet处理路径

相对路径:相对当前文件身路径作为标准定位符

绝对路径:在跟路径为标准定位资源

绝对路径

<h3>绝对路径 ,以HTTP开始,包含主机名、端口号、项目上下文、文件名
    可以访问不同服务器的不同项目资源
</h3>
<a href="http://localhost:8081/myservlet2/path1/select.jsp">select.jsp</a>
<a href="http://localhost:8081/myservlet2/path1/sub1/update.jsp">update.jsp</a>
<a href="http://localhost:8081/myservlet2/path2//insert.jsp">insert.jsp</a>
<a href="http://localhost:8081/myservlet2/index.jsp">index.jsp</a>
<a href="http://localhost:8081/myservlet2/login/loginServlet">访问同一服务器同一个应用</a>
<a href="http://localhost:8081/myservlet/firstServlet">访问同一服务器同不同应用</a>
<a href="https://www.test.com:443/news/11662.html"> 不畏将来</a>

根路径

<h3>根路径 以/开始,可以访问当前服务器的所有项目资源。需要写上下文context</h3>
<a href="/myservlet2/path1/select.jsp">select.jsp</a>
<a href="/myservlet2/path1/sub1/update.jsp">update.jsp</a>
<a href="/myservlet2/path2//insert.jsp">insert.jsp</a>
<a href="/myservlet2/index.jsp">index.jsp</a>
<a href="/myservlet2/login/loginServlet">访问同一服务器同一个应用</a>
<a href="/myservlet/firstServlet">访问同一服务器同不同应用</a>

相对路径:相对当前文件自身

<a> </a>
<h3>相对路径1 :相对当前文件自身</h3>
<a href="./select.jsp">select.jsp</a>
<a href="sub1/update.jsp">update.jsp</a>
<a href="../path2/insert.jsp">insert.jsp</a>
<a href="../index.jsp">index.jsp</a>
<a href="../login/loginServlet">访问同一服务器同一个应用</a>
<a href="../../myservlet/firstServlet">访问同一服务器同不同应用</a>

相对路径:相对基准路径

<h3>相对路径2 :相对基准路径</h3>
<a href="path1/select.jsp">select.jsp</a>
<a href="path1/sub1/update.jsp">update.jsp</a>
<a href="path2/insert.jsp">insert.jsp</a>
<a href="index.jsp">index.jsp</a>
<a href="login/loginServlet">访问同一服务器同一个应用</a>
<a href="../myservlet/firstServlet">访问同一服务器同不同应用</a>

实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--
        如果写了base标签会自动在相对路径默认补充href中的路径,简化相对路径
        如果没有定义base标签,默认就是当前文件所在路径
    -->
</head>
<base href="http://127.0.0.1:8080/servletDemo5/a/a2/">
<body>
this is a
<!--
   相对路径不以/开头,以当前资源本省所在路径为基准路径
   绝对路径以/开头,以当前项目所在目录作为根路径(项目上级目录,项目部署目录)
-->
<a href="a2.html">相对路径找a2</a>
<a href="/servletDemo5/a/a2/a2.html">绝对路径找a2</a>
<a href="../b/b2/b.html">相对路径找b</a>
<a href="/servletDemo5/b/b2/b.html">绝对路径找b</a>
<!--基于base处理相对路径-->
<a href="a2.html">相对路径找a</a>
<a href="b/b2/b.html">相对路径找b</a>
</body>
</html>

请求转发路径处理

  • 以 / 开头的路径是绝对路径,以当前当前项目部署名径作为根路径
  • 相对路径处理,以当前Servlet自身路径作为基准路径定位其他资源,受当前Servlet映射路径影响
  • ../代表向上一层的路径
@WebServlet("/myServlet1.do")
public class MyServlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //请求转发路径处理
        /*
        * 以绝对路径处理 /开头 以当前当前项目部署名径作为根路径
        * 请求转发只能转发服务器内部资源
        * http://localhost:8080/servletDemo5 /b/b2/b.html
        *
        * */
        //req.getRequestDispatcher("/b/b2/b.html").forward(req,resp);

        /*
        * http://localhost:8080/servletDemo5/myServlet1.do
        * 以相对路径处理,以当前Servlet自身路径作为基准路径定位其他资源
        * http://localhost:8080/servletDemo5/b/b2/b.html
        * 受当前Servlet映射路径影响
        * */
        req.getRequestDispatcher("b/b2/b.html").forward(req,resp);
    }
}

响应重定向路径处理

  • 绝对路径 /开头 以项目的上一层作为根路径
  • 相对路径 相对于url-pattern当前Servlet相对路径的基准路径,受当前Servlet映射路径影响
  • ../ 代表向上一层的路径
@WebServlet("/myServlet2.do")
public class MyServlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //响应重定向路径处理方式
        /*
         * 绝对路径 /开头 以项目的上一层作为根路径
         * 响应重定向可以跳转到外部资源
         * http://localhost:8080 +  /servletDemo5/b/b2/b.html
         * */
        //resp.sendRedirect("/servletDemo5/b/b2/b.html");
        
        /*
         * 相对路径 相对于url-pattern 当前Servlet相对路径的基准路径
         * http://localhost:8080/servletDemo5/ + b/b2/b.html
         * 受当前Servlet映射路径影响
         * */
        resp.sendRedirect("b/b2/b.html");
    }
}

请求转发处理和和响应重定向路径处理不同处?

绝对路径

  • 请求转发: /开头 以项目的上一层作为根路径
  • 响应重定向:/开头 以当前当前项目部署名径作为根路径

Cookie

Cookie是一种保存少量信息至浏览器的一种技术

​ 1.浏览器请求服务器

​ 2.服务器响应给浏览器一组Cokie信息

​ 3.浏览器将Cookie信息保存到本地计算机,以备使用

​ 4.下一次请求浏览器会将Cokie信息,发送到服务器

通过这种机制可以实现在浏览器端保留一些用户信息.为服务端获取用户状态获得依据

Cookie特点

  • Cookie使用字符串存储数据
  • Cookie使用key和Value结构存储数据
  • 单个Cookie存储数据大小限制在4097字节
  • Cookie存储的数据中不支持中文,Servlet4.0中支持
  • Cookie对象保存在客户端浏览器或系统磁盘中
  • Cookie是与域名绑定所以不支持跨一级域名访问
  • 浏览器在保存同一域名所返回Cookie的数量是有限的。不同浏览器支持的数量不同
  • Cookie分为持久化Cooke与状态Cookie
  • 浏览器每次请求时都会把与当前访问的域名相关的Cookie在请求中提交到服务端
Cookie: Hm_lvt_1391140a1403a41e22164b7215f536b7=1589980724; Hm_lpvt_1391140a1403a41e22164b7215f536b7=1589980724; _CS_USER_TAGS=s-t-172323c7f78-28f7a69f58fa41858d0253bd4fda8544; JSESSIONID=1522AE97AEAC14B45FC1507FA36C13F3

Cookie持久化和状态Cookie

​ 状态Cookie:浏览器会缓存Cookie对象。浏览器关闭后Cookie对象销毁

​ 持久化Cookie:浏览器会对Cookie做持久化处理,基于文件形式保存在系统的指定目录中,浏览器会将Cookie对象持久化到磁盘中。当失效时间到达后文件删除

Cookie安全性

​ Cookie对于存储内容是基于明文的方式存储的,所以安全性很低。不要在Cookie中存放敏感数据

​ 在数据存储时,虽然在Servlet4.0中Cookie支持中文,但是建议对Cookie中存放的内容做编码处理,也可提高安全性

Cookie对象方法

  • setMaxAge() 设置Cookie持久化时间(存活时间)
  • setPath() 设置Cookie提交路路径限定
  • getCookies() 获取一组Cookie
  • addCookie() Cookie写回给客户端浏览器
  • getName() 返回 cookie 的名称
  • getValue() cookie 关联的值
  • getPath() 获取路径

实例

@WebServlet("/servlet1.do")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //创建Cookie
        Cookie cookie = new Cookie("username", "lisi");
        //设置Cookie持久化时间(存活时间)
        cookie.setMaxAge(30);
        Cookie cookie2 = new Cookie("pwd", "lisi");
        //设置Cookie提交路路径限定 当访问/aaa 提交这个Cookie
        cookie2.setPath("/aaa");
        Cookie cookie3 = new Cookie("age", "11");
        Cookie cookie4 = new Cookie("gender", URLEncoder.encode("男","UTF-8"));

        //将Cookie响应给浏览器
        resp.addCookie(cookie);
        resp.addCookie(cookie2);
        resp.addCookie(cookie3);
        resp.addCookie(cookie4);
    }
}
@WebServlet("/servlet2.do")
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取Cookie
        Cookie[] cookies = req.getCookies();
        for (Cookie c:cookies){
            if("gender".equals(c.getName())){
                System.out.println(c.getName()+':'+URLDecoder.decode(c.getValue(),"UTF-8"));
            }else{
                System.out.println(c.getName()+':'+c.getValue());
            }
        }
    }

HttpSession

由于HTTP是一种无状态协议,客户端请求服务器,服务器不会记录当前客户端浏览器的状态

HTTPSession是一种保存少量信息至服务器端的技术

原理

​ 1.浏览器第一次请求时,服务器会创建HttpSession保存用户信息

​ 2.服务器将HttpSession的JSESSIONID以Cookie形式响应给浏览器

​ 3.浏览器下一次请求会携带之前的JSESSIONID的Cookie发送给服务器

​ 4.服务器根据JSESSIONID获取对应HttpSession对象

通过这种技术可以解决HTTP协议本身无法记录用户状态情况

特点

  • HttpSession保存在服务端
  • HttpSession可以存储任何数据
  • HttpSession使用Key和Value结构存储数据
  • HttpSession存储数据无大小限制

HttpSession生命周期

HttpSession对象生命周期中没有固定的创建时间和销毁时间

何时创建,第一次调用getSession() 或getSession(true)(每次创建Session)

HttpSession对象取决于是否达到超时时间或调用用了Invalidate()方法

如果没有超时或者没有调用Invalidate()方法,HttpSession对象会一直存储在服务器。

Tomcat配置文件web.xml 中配置了默认超时时间为30分钟

HttpSession对象方法

  • getSession() 获取HttpSession
  • getId() 获取JSESSIONID
  • setAttribute() 将数据存储到HttpSession
  • setAttribute() 根据Key修改HttpSession中的数据
  • removeAttribute() 根据key删除HttpSession中的数据
  • getCreationTime() HttpSession创建时间
  • getLastAccessedTime()HttpSession最近访问时间
  • getMaxInactiveInterval() HttpSession最大不活动时间
  • invalidate()销毁HttpSession
@WebServlet("/servlet1.do")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取HttpSession 如果不存在则创建一个Session
        HttpSession session = req.getSession();
        session.setAttribute("username", "123456");
        session.setAttribute("password", "123456");
        session.setAttribute("gender", "boy");
        session.setAttribute("age", "12");

        /*销毁session
         * 调用改方法后不能对Session进行CURD(增删改查)操作
         * */
        session.invalidate();
        //修改
        //session.setAttribute("age","13");

        //移除
        // session.removeAttribute("age");
    }
}
@WebServlet("/servlet2.do")
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取HttpSession
        HttpSession session = req.getSession();
        System.out.println(session.getAttribute("username"));
        Enumeration<String> attributeNames = session.getAttributeNames();
        while (attributeNames.hasMoreElements()){
            String name = attributeNames.nextElement();
            System.out.println(name+":"+session.getAttribute(name));
        }

        System.out.println("JSESSIONID"+session.getId());
        System.out.println("创建时间"+new Date(session.getCreationTime()));
        System.out.println("最近访问时间"+new Date(session.getLastAccessedTime()));
        System.out.println("最大不活动时间"+new Date(session.getMaxInactiveInterval()));
    }
}

HttpSession 销毁

HttpSession与Cookie的区别

存储方式不同

  • Cookie数据保存在客户端或系统文件中
  • HttpSession中的数据存放在服务器中

安全性

  • Cookie 数据不安全,浏览器中可以看到Cookie数据内容
  • HttpSession 数据安全,浏览器中只能看到Cookie数据中JSESSIONID

数据容量大小

  • 单个Cookie保存数据不能超过4K,不同浏览限制Cookie数量
  • HttpSession没有容量以及数量的限制

功能不同

  • Cookie是通过浏览器实现会话维持
  • HttpSession是通过服务器来实现会话状态

域对象

域代表区域,域对象指存储数据的区域

Request域

对应HttpServletRequest对象,也叫请求域

有效范围:一次请求内有效,请求转发数据可以传递

生命周期

​ 创建: 每发生一次请求创建一个独立的请求域

​ 使用: service方法中或者请求转发有效

​ 销毁: 请求结束,已经向浏览器响应数据

Session域

对应HttpServletSession对象,也叫请求域

有效范围:单次会话内有效,可以跨多个请求

生命周期

​ 创建: 会话的产生,第一次发生请求,会话的开始

​ 使用 :本次会话之内,浏览器和服务器之间发生多次请求和响应有效

​ 销毁 :会话结束,如:浏览器失去JSESSIONID、到达最大不活动时间、手动清除

Application域

对应ServletContext对象,也叫应用域

有效范围:当前web服务内,跨会话,跨请求

生命周期

​ 创建: 项目启动

​ 使用: 项目运行任何时间有效

​ 销毁: 项目关闭

Filter(过滤器)

过滤器.png Filter称之为过滤器,它是Servlet技术中最实用的技术,

Filter技术,拦截用户请求,对用户请求进行预处理,服务器响应进行处理

例如:文件拦截、URL级别权限访问控制,过滤敏感词汇,压缩响应信息

过滤器如何实现功能

  1. 在HttpServletRequest到达Servlet之前,拦截客户端的HttpServletRequest。根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据
  2. 在HttpServletResponse到达客户端之前,拦截HttpServletResponse。根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。
  3. Fliter 接口有一个方法doFilter,通过实现Filter接口方法,编写代码对web资源进行拦截。用户每发送一个请求,Servlet 中service方法中会先调用Fliter中doFilter方法,执行定义好的过滤请求和过滤响应功能代码

过滤器生命周期

  1. 在web应用启动进行类加载
  2. web容器创建完毕
  3. Filter对象实例化后调用init()方法后,保存到web容器中。等待用户访问资源
  4. 当用户请求资源中正好时过滤器的过滤路径,调用Filter中dofFllter方法
  5. 当web服务关闭,调用Filter中destroy方法
public class FilterA implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //初始化方法--> 执行1次
        System.out.println("FilterA初始化方法");

        System.out.println(filterConfig.getInitParameterNames());
    }
    /**
    * 功能描述:过滤器请求方法 符合路径每次请求都会执行 执行多次*/
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //到达目标资源之前的预处理操作
        System.out.println("执行预处理程序FilterA");
        //执行下一个过滤或者目标资源

        chain.doFilter(request,response);

        //离开服务器之前的处理操作
        System.out.println("离开服务器之前处理操作FilterA");
    }

    @Override
    public void destroy() {
        //销毁--> 执行1次
        System.out.println("FilterA销毁方法");
    }
}

web.xml

 <!--配置过滤器过滤的路径-->
    <filter>
        <!--过滤器明-->
        <filter-name>filterA</filter-name>
        <!--指定过滤器-->
        <filter-class>com.test.filter.FilterA</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filterA</filter-name>
        <!--过滤器筛选路径-->
        <!--<url-pattern>*.jsp</url-pattern>
        <url-pattern>*.html</url-pattern>-->
        <!--所有请求经过过滤器-->
        <!--<url-pattern>/*</url-pattern>-->
        <url-pattern>/test/</url-pattern>
        <url-pattern>*.do</url-pattern>
    </filter-mapping>

使用@WebFilter注解开发

@WebFilter(urlPatterns = {"/test/*"})
public class FilterB implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //初始化方法--> 执行1次
    }
    /**
    * 功能描述:过滤器请求方法 符合路径每次请求都会执行 执行多次
    */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //到达目标资源之前的预处理操作
        System.out.println("执行预处理程序FilterB");
        //执行下一个过滤或者目标资源
        chain.doFilter(request,response);
        //离开服务器之前的处理操作
        System.out.println("离开服务器之前处理操作FilterB");
    }
    @Override
    public void destroy() {
        //销毁--> 执行1次
        System.out.println("销毁方法");
    }
}

多个过滤器执行顺序

不使用注解开发情况下Filter按照web.xml中filter-mapping标签先后顺序

使用注解开发情况下Filter按照文件先后顺序

注意:对Filter顺序由严格的要求使用web.xml中配置方式

案例:使用过滤器处理Post乱码

​ web.xml配置

<!--全局参数-->
<context-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
</context-param>

<filter>
    <filter-name>encFilter</filter-name>
    <filter-class>com.test.filter.EncFilter</filter-class>
    <init-param>
        <param-name>encoding2</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>

过滤器代码

public class EncFilter implements Filter {
    private static String en2;
    private static String encoding;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //读取初始化参数和全局参数
        //读取初始化参数
        en2 = filterConfig.getInitParameter("encoding2");
        //读取全局参数
        ServletContext servletContext = filterConfig.getServletContext();
        encoding = servletContext.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //到达目标资源之前的处理操作
        request.setCharacterEncoding(en2);
        response.setCharacterEncoding(encoding);
        response.setContentType("text/html;charset=UTF-8");

        //执行下一个过滤器或者目标资源
        chain.doFilter(request, response);
        //离开服务器之前的处理操作
    }
}

案例:使用过滤器用户是否登录过验证

web.xml配置

    <filter>
        <filter-name>loginFilter</filter-name>
        <filter-class>com.test.filter.LoginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>loginFilter</filter-name>
        <url-pattern>/test/*</url-pattern>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.html</url-pattern>
    </filter-mapping>
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //从Session获得user
        HttpServletRequest req=(HttpServletRequest) request;
        HttpServletResponse resp =  (HttpServletResponse)response;
        Object user = req.getSession().getAttribute("user");
        //如果用户访问的是login.jsp 直接放行 如果不是进行过滤
        String requestURI = req.getRequestURI();
        if(requestURI.contains("login.jsp")||requestURI.contains("test/UserServlet")){
            chain.doFilter(req,resp);
            return;
        }
        //判断是否为空
        if(user!=null){
            //直接放行
            chain.doFilter(req,resp);
        }else{
            //跳转到登录页面
            resp.sendRedirect(req.getContextPath()+"/login.jsp");
        }
    }
}

Listener(监听器)

什么是监听器?

web应用中对某些对象、信息的创建、创建销毁、增加、修改、删除等动作发生后,做出相应的响应处理。

当范围对象的状态发生变化的时候,服务器自动调用监听对象中的方法。

常用于统计在线人数和在线用户,系统加载时进行信息初始化,统计网站的访问量等等

有哪些监听器?

​ 一共有8个监听器

​ 按对象划分分别是:ServletContext对象监听器、HttpSession对象监听器、ServletRequest对象监听器

Request域2个监听器

ServletRequestListener 处理Request对象创建和销毁

方法描述
requestInitialized()Request对象初始化被触发方法发
requestDestroyed()Request对象销毁化被触发方法

ServletRequestAttributeListener 处理Request中数据添加、替换、删除

方法描述
attributeAdded()setAttribute() Request中数据第一次添加会触发该方法
attributeRemoved()removeAttribute()Request,中数据被移除时触发该方法
attributeReplaced()setAttribute(),Request中数据被修改会触发该方法

Session域4个监听器

HttpSessionListener 处理Session对象创建和销毁

方法描述
sessionCreated()Sessiond对象被创建会触发该方法
sessionDestroyed()Session销毁时触发方法

HttpSessionAttributeListener 处理Session对象数据添加、修改、删除

方法描述
attributeAddedsetAttribute(),Session数据第一次添加数据时触发方法
attributeRemovedremoveAttribute() , Session数据被移除时触发方法
attributeReplacedsetAttribute(), Session数据被修改时触发方法

HttpSessionBindingListener 针对1个对象 处理Session对象绑定和解绑定

方法描述
valueBound()session绑定对象触发方法
valueUnbound()session解除绑定对象触发方法

HttpSessionActivationListener

方法描述
sessionWillPassivate()Session序列化(保存到硬盘,持久化)触发方法
sessionDidActivate()Session反序列化(把字节序列转化为对象的过程,存放于内存)触发方法

Application域监听器2个

ServletContextListener 处理ServletContext 创建和销毁

方法描述
contextInitializedServletContext初始化触发方法
contextDestroyedServletContext 销毁时触发方法

ServletContextAttributeListener 处理ServletContext 中数据添加、修改、删除

方法描述
attributeAddedsetAttribute(),ServletContext第一次添加数据触发方法
attributeRemovedremoveAttribute(),ServletContext被移除触发方法
attributeReplacedsetAttribute(),ServletContext数据被修改触发方法

Request域实例

public class LoginListener implements ServletRequestListener, ServletRequestAttributeListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        //req对象销毁会执行的方法
    }
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        //req创建的时候执行的方法
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();

        //获取访问当前程序客户端
        String ip =request.getRemoteAddr();
        //获取当前时间
        String time = new Date().toLocaleString();
        //访问当前程程序访问位置
        StringBuffer requestURL = request.getRequestURL();
        //把日志信息保存到本地文件
        PrintWriter out = null;
        try {
            out = new PrintWriter(new FileOutputStream("Z:/IDEProjects/TomCatProject/flower2/src/log.txt",true));
             String str =  "用户IP:"+ip+"在时间"+time+"访问了"+requestURL;
            out.println(str);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            out.close();
        }
    }

    /*attribute 第一次添加*/
    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        HttpServletRequest request = (HttpServletRequest)srae.getServletRequest();
        System.out.println(request.getContextPath()+"attributeAdded被执行");
    }
    /*attribute 被移除会执行该方法*/
    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        HttpServletRequest request = (HttpServletRequest)srae.getServletRequest();
        System.out.println(request.getContextPath()+"attributeRemoved被执行");
    }
    /*同一个请求中再次设置同名key 会执行改方法 attribute 被替换*/
    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        HttpServletRequest request = (HttpServletRequest)srae.getServletRequest();
        System.out.println(request.getContextPath()+"attributeReplaced被执行");
    }
}

Session域实例

public class OnLine implements HttpSessionListener, HttpSessionAttributeListener {
    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        //session.setAtt
        //获取session中key
        String name = se.getName();
        if("user".equals(name)){
            //在线人数加1
            //获得全局对象 用户保存当前在线人数
            ServletContext servletContext = se.getSession().getServletContext();

            //获得当前在线人数
            Integer online  = (Integer)servletContext.getAttribute("online");
            if(online==null){
                //是否第一个在线人数
                online=1;
            }else{
                online++;
            }
            servletContext.setAttribute("online",online);
        }
    }
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        //session销毁时触发方法

        //人数减1
        ServletContext servletContext = se.getSession().getServletContext();
        Integer online = (Integer) servletContext.getAttribute("online");
        if(online==null){
            online=0;
        }else{
            online--;
        }
        servletContext.setAttribute("online",online);

    }
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        //session创建

    }
    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) {
        //session.removeAttribute是被触发
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {

    }
}
/**
 * HttpSessionBindingListener 针对于具体的对象,监听指定的对象从session上进行绑定和解绑的过程
 * 注意:该监听器不需要配置web.xml文件
 * */

public class User implements Serializable, HttpSessionBindingListener, HttpSessionActivationListener {
    private static final long serialVersionUID = -3532883031405078802L;
    private Integer uid;
    private String uname;
    private String pwd;
    private String alternative;


    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        System.out.println("user对象绑定到session");
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        /*
        对象从session 中解绑
        session失效
        session.invalidate() 超过30分钟
        session.removeAttribute("user")*/
        System.out.println("user对象从session解绑");
    }



    @Override
    public void sessionWillPassivate(HttpSessionEvent se) {
        System.out.println("序列化");

    }
    @Override
    public void sessionDidActivate(HttpSessionEvent se) {
        System.out.println("反序列化");
    }

}

Application域实例

@WebListener
public class ApplicationListener implements ServletContextListener, ServletContextAttributeListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //Application
        System.out.println("Application初始化");
    }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Application销毁");
    }

    @Override
    public void attributeAdded(ServletContextAttributeEvent sce) {
        System.out.println("Application attributeAdded");
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent scae) {
        System.out.println("Application attributeRemoved");
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent scae) {
        System.out.println("Application attributeReplaced");
    }
}

TheradLocal处理来SqlSession

public class DBUtil {
    private static SqlSessionFactory factory;
    private static ThreadLocal<SqlSession> tl = new ThreadLocal<>();
    static {
        InputStream inputStream = null;
        try {
            //[1]解析myBatis.xml文件
            inputStream = Resources.getResourceAsStream("mybatis.xml");
            //[2]获得sqlsession工厂
            factory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    //获得sqlsession对象
    public static SqlSession getSqlSession() {
        //获得ThreadLoacl中的sqlsession对象
        SqlSession sqlSession = tl.get();
        if (sqlSession == null) {
            sqlSession = factory.openSession(true);

            //把创建好的对象放到ThreadLoacl
            tl.set(sqlSession);
        }
        return tl.get();
    }


    //关闭sqlsession
    public static void closeAll() {
        SqlSession sqlSession = tl.get();
        if (sqlSession != null) {
            sqlSession.close();
        }
        tl.set(null);
    }
}

分页

分页.png

什么是分页?

当大量数据无法一次性全部显示在网页上,只能将数据分成几段。每一段用一个网页显示,也就是一页。

分页就是将大量数据分成很多页的一种处理手段

分页有什么好处?

  • 减少数据库的IO数据量传输,降低数据库读写压力,提高数据库响应速度不用一次性将所有数据查出来,只需要查一部分。
  • 减少浏览器和服务器之前大量数据的IO传输,从而提高服务器响应速度
  • 占用内存少
  • 分页可以减少资源浪费

小案例

mapper层

public interface StuMapper {
    //分页查询操作
    List<Stu> selectPage(Integer start,Integer size,String name,String age);
    //查询数据库总条数
    Integer getCount(String name,String age);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.StuMapper">
    <select id="selectPage" resultType="stu">
        select * from stu
        <where>
            <if test="param3!=null and param3!=''">
                sname=#{param3}
            </if>
            <if test="param4!=null and param4!=''">
                and age>#{param4}
            </if>
        </where>
        limit #{param1},#{param2}
  </select>

<select id="getCount" resultType="int">
        select COUNT(*) from stu
        <where>
            <if test="param1!=null and param1!=''">
                sname=#{param1}
            </if>
            <if test="param2!=null and param2!=''">
                and age>#{param2}
            </if>
        </where>
  </select>

util

package com.test.util;

import java.util.List;

/**
 * 分页的三个基本属性
 * 1.每页几条记录size  可以有默认值5
 * 2.当前页号  index    可以有默认值1
 * 3.记录总数totalCount:不可能有默认值,需要查询数据库获取真正的记录总数
 * 
 * 4.一共多少页 :totalPageCount=totalCount/size+1
 * 		5  30  31 32 33 34 35
 * 5.上一页    index-1  当前页1,上一页1
 * 6.下一页   index+1  当前页是最后一页  下一页:还是最后一页
 * 
 * 扩展
 * 分页Bean还可以放要查询的数据  protected List<T> list;
 * 分页Bean还可以放页码列表     [1]  2  3  4  5   private int[] numbers;
 * 
 * @author Administrator
 *
 * @param <T>
 */
public class PageBean<T> {
	private int size = 2;//每页显示记录     //ok
	private int index = 1;// 当前页号       ok       
	private int totalPageCount = 1;// 总页数     ok
	private int totalCount = 0;// 记录总数    //ok             2
	
	private int[] numbers;//展示页数集合 
	protected List<T> list;//要显示到页面的数据集   

	/**
	 * 得到开始记录
	 * @return
	 */
	public int getStartRow() {
		return (index - 1) * size;
	}

	/**
	 * 得到结束记录
	 * @return
	 */
	public int getEndRow() {		
		return index * size;
	}

	/**
	 * @return Returns the size.
	 */
	public int getSize() {		
		return size;
	}

	/**
	 * @param size
	 * The size to set.
	 */
	public void setSize(int size) {
		if (size > 0) {
			this.size = size;
		}
	}
	/**
	 * @return Returns the currentPageNo.
	 */
	public int getIndex() {
		if (totalPageCount == 0) {
			
			return 0;
		}
		return index;
	}
	/**
	 * @param currentPageNo
	 * The currentPageNo to set.
	 */
	public void setIndex(int index) {
	
		if (index > 0) {
			
			this.index = index;
		}
	}

	/**
	 * @return Returns the totalCount.
	 */
	public int getTotalCount() {
		return totalCount;
	}

	/**
	 * @param totalCount
	 *  The totalCount to set.
	 */
	public void setTotalCount(int totalCount) {
		if (totalCount >= 0) {
			this.totalCount = totalCount;
			setTotalPageCountByRs();//根据总记录数计算总页�?
		}
	}

	
	public int getTotalPageCount() {
		return this.totalPageCount;
	}

	/**
	 * 根据总记录数计算总页?
	 * 5   
	 * 20    4
	 * 23    5
	 */
	private void setTotalPageCountByRs() {
		if (this.size > 0 && this.totalCount > 0 && this.totalCount % this.size == 0) {
			this.totalPageCount = this.totalCount / this.size;
		} else if (this.size > 0 && this.totalCount > 0 && this.totalCount % this.size > 0) {
			this.totalPageCount = (this.totalCount / this.size) + 1;
		} else {
			this.totalPageCount = 0;
		}
		setNumbers(totalPageCount);//获取展示页数集合
	}

	public int[] getNumbers() {
		return numbers;
	}	
	/**
	 * 设置显示页数集合
	 * 
	 * 默认显示10个页码
	 * 41  42  43  44    [45 ]   46  47  48  49  50
	 * 
	 * 
	 *  1 2  3 [4]  5 6 7 8  9  10
	 *  
	 *  41  42  43  44    45    46  47  [48]  49  50
	 * @param totalPageCount
	 */
	public void setNumbers(int totalPageCount) {
		if(totalPageCount>0){
			//!.当前数组的长度
			int[] numbers = new int[totalPageCount>10?10:totalPageCount];//页面要显示的页数集合
			int k =0;
			//
			//1.数组长度<10   1 2 3 4 ....   7
			//2.数组长度>=10
			//     当前页<=6  1 2 3 4    10
			//     当前页>=总页数-5           ......12 13 14 15  
			//     其他                                5  6  7 8   9 当前页(10)  10  11 12  13
			for(int i = 0;i < totalPageCount;i++){
				//保证当前页为集合的中�?
				if((i>=index- (numbers.length/2+1) || i >= totalPageCount-numbers.length) && k<numbers.length){
					numbers[k] = i+1;
					k++;
				}else if(k>=numbers.length){
					break;
				}				
			}
			
			this.numbers = numbers;
		}
		
	}
	
	public void setNumbers(int[] numbers) {
		this.numbers = numbers;
	}

	public List<T> getList() {
		return list;
	}

	public void setList(List<T> list) {
		this.list = list;
	}
}

pojo、service 层省略

controller层

@WebServlet("/test/StuFindPage")
public class StuFindPage extends HttpServlet {
    private StuService stuService = new StuServiceImpl();
    private Integer size=2;
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //接收前台页面中的数据
        String sname = req.getParameter("sname");
        String age = req.getParameter("age");

        //从前台获得当前页数
        String ind = req.getParameter("index");
        int index =1;
        if(null!=ind&&!"".equals(ind)){
           index = Integer.parseInt(ind);
        }

        //从前台获得size
        String si = req.getParameter("size");
        if(null!=si&&!"".equals(si)){
            size = Integer.parseInt(si);

        }
        
        
        PageBean pg = new PageBean();
        //把当前的页数设置到PageBean 中
        pg.setIndex(index);

        pg.setSize(size);
        //总条数设置到PageBean 中
        pg.setTotalCount(stuService.getCount(sname,age));


        //数据的处理 调用service层返回结果
        List<Stu> list = stuService.findPage(pg.getStartRow(),pg.getSize(),sname,age);

        //根据返回结果做出响应
        req.setAttribute("list",list);
        req.setAttribute("pg",pg);
        req.setAttribute("sname",sname);
        req.setAttribute("age",age);
        req.getRequestDispatcher("/main.jsp").forward(req,resp);
    }
}

view层

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Title</title>
    <style>
        table{
            margin: auto;
            border: 1px solid ;
            width: 40%;
        }
        table th,td{
            border: 1px solid;
        }
    </style>
    <base href="<%=request.getContextPath()+'/'%>">
    
    <script type="text/javascript">
        function chang(index,size) {
            var sname= document.getElementById("sname").value
            var age=document.getElementById("age").value
            window.location.href="test/StuFindPage?size="+size+"&index="+index+"&sname="+sname+"&age="+age;
        }
    </script>
    
</head>
<body>
 <h3 align="center">学生信息</h3>

 <center>
     <form action="">
         姓名<input type="text" value="${sname}" name="sname" id="sname"/>

         年龄<input type="text" value="${age}" name="age" id="age"/>
         <input type="submit" value="查询">
     </form>
 </center>
 <hr>
<table cellspacing="0px" cellpadding="0px">
    <tr>
        <th>学生ID</th>
        <th>学生姓名</th>
        <th>学生年龄</th>
        <th>学生头像</th>
        <th>操作</th>
    </tr>
    <c:forEach items="${requestScope.list}" var="stu">
        <tr>
            <td>${stu.sid}</td>
            <td>${stu.sname}</td>
            <td>${stu.age}</td>
            <td><img src="upload/${stu.filename}" width="90px" /></td>
            <td><a href="test/FileDownLoad?filename=${stu.filename}&filetype=${stu.filetype}" >下载</a></td>
        </tr>
    </c:forEach>
    <tr>
        <th colspan="5">
            <a href="javascript:chang(1,${pg.size})">首页</a>
            <c:if test="${pg.index > 1}">
                <a href="javascript:chang(${pt.index-1},${pg.size})">上页</a>
            </c:if>

            <c:forEach items="${pg.numbers}" var="i">
                <c:if test="${i==pg.index}">
                    [<a href="javascript:chang(${i},${pg.size})">${i}</a>]
                </c:if>
                <c:if test="${i!=pg.index}">
                    <a href="javascript:chang(${i},${pg.size})">${i}</a>
                </c:if>
            </c:forEach>

            <c:if test="${pg.index<pg.totalPageCount}">
                <a href="javascript:chang(${pg.index+1},${pg.size})">下页</a>
            </c:if>

            <a href="javascript:chang(${pg.totalPageCount},${pg.size})">尾页</a>

            每页显示<select onchange="chang(1,this.value)">
               <c:forEach begin="2" end="10" step="2" var="i">
                   <c:if test="${pg.size==i}">
                       <option value="${i}" selected="selected" >${i}</option>
                   </c:if>
                   <c:if test="${pg.size!=i}">
                       <option value="${i}" >${i}</option>
                   </c:if>
               </c:forEach>
            </select>条记录
            ,总条数:${pg.totalCount}
        </th>
    </tr>
</table>
</body>
</html>

文件上传和下载

文件上传下载

使用Comm组件上传文件

/*
 * f.getName()  文件名
 * f.getFieldName() 表单name属性
 * f.getSize() 文件大小
 * f.getContentType() 文件类型
 * isFormField() 是file表单项 false 不是则true
 * getString 获取表单项值
 * getString("UTF-8") 获取表单项值,并解决中文乱码
 * */
@WebServlet("/test/FileUpLoad")
public class FileUpLoad extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //中文乱码解决
        //req.setCharacterEncoding("UTF-8");

        //创建文件上传工厂对象
        FileItemFactory factory = new DiskFileItemFactory();
        //创建文件上组件对象
        ServletFileUpload upload = new ServletFileUpload(factory);
        //解决上传文件 文件名中文乱码问题
        upload.setHeaderEncoding("UTF-8");

        //设置文件大写 异常无法捕获
        //upload.setSizeMax(20*1024);
        String sname = "";
        Integer age = 0;
        String filename = "";
        String filetype = "";
        try {
            //接收前台提交的所有的表单对象
            List<FileItem> list = upload.parseRequest(req);
            for (FileItem f : list) {
                System.out.println(f.getName() + "--" + f.getFieldName() + "--" + f.getSize() + "--" + f.getContentType() + "--" + f.isFormField());

                if (!f.isFormField()) {
                    String substring = f.getName().substring(f.getName().lastIndexOf("."));
                    //获取文件类型
                    filetype = f.getContentType();

                    //指定上传文件类型
                    if (!".jpg".equals(substring) || !".jpg".equals(substring) || !".jpg".equals(substring)) {
                        req.setAttribute("msg", "文件只能是图片");
                        req.getRequestDispatcher("/save.jsp").forward(req, resp);
                        return;
                    }

                    //指定文件大小
                    if (f.getSize()>20*1024) {
                        req.setAttribute("msg", "文件最大为20K");
                        req.getRequestDispatcher("/save.jsp").forward(req, resp);
                        return;
                    }

                    //重新命名 UUIID
                    //文件名称
                    String uuid = UUID.randomUUID().toString();
                    filename = uuid + substring;

                    //动态获取服务器目录
                    String realPath = req.getServletContext().getRealPath("/upload");

                    //判断上传文件目录是否存在
                    File file = new File(realPath);
                    if (!file.exists()) {
                        file.mkdirs();
                    }
                    //文件表单项
                    f.write(new File(file, filename));
                } else {
                    if ("sname".equals(f.getFieldName())) {
                        //获取表单现内容,解决中文乱码
                        sname = f.getString("UTF-8");
                    }
                    if ("age".equals(f.getFieldName())) {
                        //获取表单内容
                        age = Integer.parseInt(f.getString());
                    }
                }
            }
            Stu stu = new Stu(sname, age, filename, filetype);

            //添加
            StuService stuService = new StuServiceImpl();
            int save = stuService.save(stu);
            if (save > 0) {
                resp.sendRedirect(req.getContextPath() + "/test/StuFindPage");
            } else {
                req.setAttribute("msg", "注册失败");
                req.getRequestDispatcher("/save.jsp");
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

基于Part接口文件的上传

/**
 * 基于Part接口方式实现文件上传
 * */
@WebServlet("/test/FileUpLoad2")
@MultipartConfig
public class FileUpload2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //接收前台的图片
        Part part = req.getPart("image");

        //form-data; name="image"; filename="111111.jpg"
        String header = part.getHeader("content-disposition");

        //对上传文件重命名
        //获取后缀
        String su = header.substring(header.lastIndexOf("."),header.length()-1);

        String uuid = UUID.randomUUID().toString();
        String filename = uuid + su;

        //把 part 写到文件
        //part.write("D:/img/"+filename);
        part.write(req.getServletContext().getRealPath("/upload/")+filename);

        System.out.println(header);
    }
}

文件下载

@WebServlet("/test/FileDownLoad")
public class FileDownLoad extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //获取需要下载的文件
        String filename = req.getParameter("filename");

        //把需要下载的文件读取
        //获得服务器目录
        String realPath = req.getServletContext().getRealPath("/upload");
        File file = new File(realPath+'/'+filename);
        InputStream input = new FileInputStream(file);

        //把文件写入到本地
        //OutputStream output = new FileOutputStream("");
        //写到客户端
        OutputStream output = resp.getOutputStream();

        //设置下载文件大小
        resp.setContentLength((int)file.length());
        //设置下载文件类型
        resp.setContentType(req.getParameter("filetype"));
        //设置响应头
        resp.setHeader("Content-Disposition","attachment;filename="+filename);

        IOUtils.copy(input,output);
        
        //关闭资源
        output.close();
        input.close();
    }
}

异步(Asynchronous)

什么是异步(Asynchronous)?

异步是指:当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响。

异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。

什么是同步(Synchronous)?

同步是指:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。

同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。

同步和异步的区别?

举例 :普通B/S模式(同步) Ajax技术(异步)

同步:提交请求--->等待服务器处理--->处理完毕响应给浏览器(这期间客户端浏览器不能干任何事)

异步:请求通过事件触发--->服务器处理(这期间浏览器可以做其他事情) --->处理完毕

举个例子 打电话时同步 发消息是异步

Ajax

  • Ajax 即 Asynchronous javascript And XML(异步JavaScript 和XML)
  • Ajax是一种无需重新加载整个页面情况下,能够更新部分网页的技术
  • AJAX的最大的特点: 异步访问,局部刷新

AJAX异步原生js提交请求的步骤为:

1 获取XMLHTTPRequest对象xhr=new XMLHttpRequest();

2打开链接 xhr.open("GET","loginServlet?uname="+uname,true);

3设置回调函数xhr.onreadystatechange=showRnturnInfo;

4提交数据 xhr.send(data)

XMLHTTPequest的几种状态

readyState代表请求的状态或者形情

0:请求没有发出(在调用open()之前)

1:请求已经建立但是还没有发出(调用send()之前)

2:请求已经发出正在处理之中(这里通常可以从响应的到内容头部)

3请求已经处理,响应中有数据可用,但是服务器还没有完成响应

4:响应已经完成,可以访问服务器响应并使用它

在AJAX中有以上五种状态,但是通常只使用状态4

status代表响应状态码

​ 200

​ 404

Ajax案例-验证用户名是否被占用

页面代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>用户注册</title>
    <base href="<%=request.getContextPath()+'/'%>">
    <script type="text/javascript">

        function checkZh() {
           var zh= document.getElementById("zh").value
            if(null==zh||""==zh){
                document.getElementById("zh_sapn").innerText="用户名不能为空"
            }else{
                var xhr
                //发送ajax请求 判断用户名是否可用
                if(window.ActiveXObject){
                    xhr= new ActiveXObject("Microsoft.XMLHTTP")
                }else{
                    //创建 XMLHttpRequest
                    xhr= new XMLHttpRequest();
                }
                //和服务器建立连接
                xhr.open("post","test/CheckName",true)
                xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")

                //执行回掉函数
                xhr.onreadystatechange= function () {
                    if(xhr.status==200){
                        //接收响应数据
                        var text=xhr.responseText
                        //把响应内容给span标签
                        document.getElementById("zh_sapn").innerText=text
                    }else if(xhr.status==404){
                        alert("路径找不到");
                    }else if(xhr.status==500){
                        alert("运行异常");
                    }else{
                        alert("ajax响应失败");
                    }
                };
                //发送请求数据
                xhr.send("zh="+zh)
            }
        }
        /*
             老版IE创建方式
             xhr= new ActiveXObject("Microsoft.XMLHTTP")

            xhr.open(method,url,是否异步,服务器用户,服务器密码)
            get方式
            xhr.open("get","test/CheckName?zh="+zh,true)
            xhr.send(null);

            post 方式
            xhr.open("post","test/CheckName",true)
            xhr.send("zh="+zh)
            前台接收服务器响应数据
            var text=xhr.responseText
            status 状态码
        */
    </script>
</head>
<body>
    <h1>用户注册页面</h1>
    <hr>
    <form action="test/UserServlet" method="post">
        <p>
            用户名:<input type="text" name="uname"/>${requestScope.msg}
        </p>
        <p>
            账号名:<input type="text" name="zh"  id="zh"  onblur="checkZh()"/>${requestScope.msg}
            <span id="zh_sapn"></span>
        </p>
        <p>
            密码:<input type="password" name="pwd"/>
        </p>
        <p>
           <input type="submit" value="提交" />
           <input type="reset" value="清空"/>
        </p>
    </form>
</body>
</html>

后台Servlet代码

@WebServlet("/test/CheckName")
public class CheckName extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

      resp.setContentType("text/html;charset=UTF-8;");
        //接收前台页面数据
        String zh = req.getParameter("zh");
        //System.out.println(zh);
        boolean flag = false;

        if("test".equals(zh)){
            flag=true;
        }
        //根据返回结果 给用户做出响应
        if(flag){//账号在数据中存在
            resp.getWriter().println("账号被占用");
        }else{
            resp.getWriter().write("账号可以使用");
        }
    }
}

AJAX数据格式处理

  • 响应普通文本
  • 响应json
  • 响应xml

页面代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Ajax响应数据类型</title>
    <base href="<%=request.getContextPath()+'/'%>">
    <script type="text/javascript">
        function demo1() {
            var xhr = new XMLHttpRequest();
            xhr.open("get","test/AjaxResponseType",true)
            xhr.onreadystatechange=function () {
                if(xhr.readyState==4&xhr.status==200){
                    //接收响应的数据
                    //var text = xhr.responseText;

                    //console.log(text);

                   /* //方式1:把字符串转为可识别js对象
                    eval("var user="+text)
                    console.log(user.uid);

                    /!*方式2:字符串转JSON对象
                    * 注意 字符串中key必须用""括住
                    * *!/
                    var user2 = JSON.parse(text);
                    console.log(user2.uid);*/

                   //接收XML格式的数据
                    var xml = xhr.responseXML;

                    alert(xml.getElementsByTagName("sname")[0].innerHTML)
                }
            }
            xhr.send(null)
        }

        function jsonTst(){
            //json 是一种数据格式
            var json1 ={sid:1,sname:'zs',age:18}
            console.log(json1.sid)

            var json2 ={"sid":1,"sname":'zs',"age":18}
            console.log(json2.sname)
            
            var json3={
                sid:1,
                sname:'zs',
                age:18,
                study:function () {
                    console.log("--study--")
                }
            }
            
            console.log(json3.study)//函数体
            console.log(json3.study())//执行函数 获得方法返回值没有输出undefined
            //多个json
            var json4=[{sid:1,sname:'zs',age:18},
                {sid:2,sname:'zs2',age:18},
                {sid:3,sname:'zs3',age:18}]

            for (var i in json4) {
                console.log(json4[i].sname)
            }
        }
    </script>
</head>
<body>
<button onclick="demo1()">ajax请求</button>
<button onclick="jsonTst()">jsonTest</button>
</body>
</html>

后台代码

/**
 * 响应数据类型
 *  普通文本
 * 
 *  json
 *      1.手动更改toString方法
 *      2.将对象属性拼接成json字符串
 *      3.使用gson包
 * 
 *  xml
 */
@WebServlet("/test/AjaxResponseType")
public class AjaxResponseType extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //resp.setContentType("text/html;charset=utf-8");

        /*响应普通文本*/
        //resp.getWriter().write("ajax响应成功");
        /******************************************************/

        /*响应JSON*/
        User user = new User(10, "ls", 19);
        //resp.getWriter().println(user);

       /* String json = '{' + "uid:" + user.getUid() + ',' + "uname:" + user.getUname() + ',' + "age:" + user.getAge() + '}';
        resp.getWriter().println(json);*/


      /*  Gson gson = new Gson();
        String json = gson.toJson(user);
        resp.getWriter().println(json);*/

        /******************************************************/
        /*响应XML*/
        String xml = "<students>" +
                        "<student>" +
                            "<sid>1</sid>"+
                            "<sname>zs</sname>"+
                            "<age>19</age>"+
                        "</student>" +
                        "<student>" +
                            "<sid>2</sid>"+
                            "<sname>ls</sname>"+
                            "<age>19</age>"+
                         "</student>"+
                     "</students>";
        resp.setContentType("text/xml;charset=utf-8");
        resp.getWriter().println(xml);
    }
}

jQuery Ajax使用

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>ajax结合jQuery使用</title>
    <base href="<%=request.getContextPath()+'/'%>">
    <script type="text/javascript" src="js/jquery-1.12.3.min.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#but").click(function () {
               $.ajax({
                   //发送请求是否试异步的
                   async:true,
                   //文件类型
                   //contentType:"application/x-www-form-urlencoded",
                   //请求路径
                   url:"test/AjaxJQ",
                   //请求方式
                   type:"get",
                   //请求参数
                   data:"name=zs&pwd=123",

                   //json格式
                   //data:{name:'zs',pwd:123},

                   //服务器返回数据类型
                   dataType:"json",
                   //回调函数接收响应数据
                   success:function(e){
                        //e 响应结果
                       console.log(e)
                   },
                   //在创建xhr对象之前进行修改
                   boforeSend:function (xhr) {
                       
                   },
                   error:function () {
                       alert("ajax响应失败")
                   }
               })
            })
        })
    </script>
</head>
<body>
    <button id="but">ajax请求发送</button>
</body>
</html>
 $(function () {
            $("#but").click(function () {
                //加载js文件中代码
                $.getScript("js/my.js")
                //获得json格式数据
                $.getJSON("","",function (result) {
                })

            })
        })
 $(function () {
            $("#but").click(function () {

                //Ajax请求加载数据 请求默认get
                /*
                 // URL请求路径 data发送参数 callback回调函数,成功时执行
                 $("#div1").load("test/AjaxLoad", "uname=zs", function () {
                 })
                 */
                /*$("#div1").load("test/AjaxLoad", "uname=zs")*/

                /*****************************************************/

                /*
                  简单版 ajax请求 get方式
                  URL        请求路径
                  data       请求参数
                  callback   回调函数 ,成功时执行
                  type       返回内容格式
                 */
                $.load(
                    "test/Task",
                    "",
                    function (re) {

                    }
                    //, "json"
                )

                /*****************************************************/
                /*$.post(
                    "test/AjaxLoad",
                    "uname=zs",
                    function (result) {
                        alert(result)
                    }
                    , "json"
                )*/

            })
        })

jsonp跨域处理

/*不能跨域*/
$(function () {

    $("#bu1").click(function () {

        $.ajax({
            url:"http://127.0.0.1:8080/ajax01/test/AjaxKY",
            type:"get",
            //jsonp 代表支持跨域请求
            dataType:"jsonp",
            data:{funName:"h1"},
            success:function () {
                alert("123");
            }
        })
    })
})

function h1(te) {
    alert(te);
}

web.xml配置文件

设置默认访问页面

<!--
        设置默认访问页面
        如果没有配置 <welcome-file-list>
        默认会使用全局配置文件TomCat\conf\web.xml中 <welcome-file-list>配置
    -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>login.html</welcome-file>
    </welcome-file-list>

服务启动时加载顺序

<servlet>
        <servlet-name>servlet1</servlet-name>
        <servlet-class>com.test.servlet.Servlet1</servlet-class>
        <!--服务启动加载顺序-->
        <load-on-startup>6</load-on-startup>
    </servlet>

servlet上下文初始化参数

<!--配置全局信息-->  
<context-param>
        <param-name>username</param-name>
        <param-value>test</param-value>
    </context-param>
    <context-param>
        <param-name>password</param-name>
        <param-value>123456</param-value>
    </context-param>

设置描述信息

<servlet>
    <!--描述信息-->
    <description>test servlet life cycle</description>
    <!--显示名字-->
    <display-name>LifeServlet</display-name>
    <servlet-name>lifeServlet</servlet-name>
    <servlet-class>com.test.servlet.LifeServlet</servlet-class>
</servlet>

设置初始化参数

 <servlet>
        <servlet-name>servlet3</servlet-name>
        <servlet-class>com.test.servlet.Servlet3</servlet-class>
        <!--
            servlet初始参数
            每个Servlet独有信息
        -->
        <init-param>
            <param-name>computer</param-name>
            <param-value>微星</param-value>
        </init-param>
        <init-param>
            <param-name>CPU</param-name>
            <param-value>i9</param-value>
        </init-param>
    </servlet>

url-pattern请求映射路径

  • 精确匹配 /servlet3.do
  • 扩展名匹配 *.abc
  • 路径匹配 /aaa/*
  • 任意匹配 /
  • 匹配所有 /*

优先级:精确匹配 >长路径> 扩展名匹配

精确匹配

精确匹配是指中配置的值必须与url完全精确匹配。

 <servlet-mapping>
        <servlet-name>servlet3</servlet-name>
        <url-pattern>/servlet3.do</url-pattern>
 </servlet-mapping>

扩展名匹配

“*”表示匹配任意字符。在扩展名匹配中只要扩展名相同都会被匹配和路径无关

 <servlet-mapping>
        <servlet-name>servlet2</servlet-name>
        <!--后缀名.d的访问的是servlet2-->
        <url-pattern>*.d</url-pattern>
 </servlet-mapping>

路径匹配

根据请求路径进行匹配,在请求中只要包含该路径都匹配。“*”表示任意路径以及子路径。

<servlet-mapping>
     <servlet-name>servlet2</servlet-name>    
<!--以/aaa/开头访问的都是(不包含web.xml配置的Servlet路径)servlet2-->	
    <url-pattern>/aaa/*</url-pattern>
 </servlet-mapping>  

任意匹配

匹配所有但不包含JSP页面、web.xml中配置的其他servlet路径

<servlet-mapping>
    <servlet-name>servlet1</servlet-name>
    <!--/匹配所有映射路径 不包括jsp
        /后面在web.xml中没有配置servlet的路径
        一律访问的是当前servlet1
        例如/servlet2.do 访问的是servlet2
           /aaa.html 访问的是servlet1
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

匹配所有

以/开头访问的都是(不包含web.xml配置的Servlet路径)访问当前servlet

<servlet-mapping>
     <servlet-name>servlet2</servlet-name>    
<!--以/开头访问的都是(不包含web.xml配置的Servlet路径)servlet2-->
 	<url-pattern>/*</url-pattern>
 </servlet-mapping>   

URL映射方式

在web.xml文件中支持将多个URL映射到一个Servlet中,但是相同的URL不能同时映射到两个Servlet中

方式一:在一个标签中添加多个url映射

<servlet-mapping>
        <servlet-name>servlet2</servlet-name>
        <!--后缀名.d的访问的是servlet2-->
        <url-pattern>*.d</url-pattern>
        <!--以/abc开头的访问的都是servlet2-->
        <url-pattern>/abc*</url-pattern>
        <!--以/开头访问的都是(不包含web.xml配置的Servlet路径)servlet2-->
        <url-pattern>/*</url-pattern>
        <url-pattern>/aaa/*</url-pattern>
        <url-pattern>/servlet2.do</url-pattern>
  </servlet-mapping>

方式二::在多个标签中添加1个url映射

  <servlet-mapping>
        <servlet-name>servlet3</servlet-name>
        <url-pattern>/servlet3.do</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>servlet3</servlet-name>
        <url-pattern>/servlet3</url-pattern>
    </servlet-mapping>

设置Session会话超时

HttpSession超时后销毁

<session-config>
     <!--最大不活动时间-->
     <!--Session超过1分钟自动销毁-->
    <session-timeout>1</session-timeout>
</session-config>

设置过滤器

<!--配置过滤器过滤的路径-->
    <filter>
        <!--过滤器明-->
        <filter-name>filterA</filter-name>
        <!--指定过滤器-->
        <filter-class>com.test.filter.FilterA</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filterA</filter-name>
        <!--过滤器筛选路径-->
        <!--<url-pattern>*.jsp</url-pattern>
        <url-pattern>*.html</url-pattern>-->
        <!--所有请求经过过滤器-->
        <!--<url-pattern>/*</url-pattern>-->
        <url-pattern>/test</url-pattern>
        <url-pattern>*.do</url-pattern>
    </filter-mapping>

配置监听器

 <!--声明当前监听器-->
    <listener>
        <listener-class>com.test.listener.LoginListener</listener-class>
    </listener>

    <listener>
        <listener-class>com.test.listener.OnLine</listener-class>
    </listener>

设置错误提示页面

<error-page>
    <error-code>404</error-code>
    <location>/404.html</location>
</error-page>
<error-page>
    <error-code>500</error-code>
    <location>/500.html</location>
</error-page>