Java EE Servlet

Servlet是运行在服务器上的小程序,作为客户端与服务器的中间层

servlet接口有四个方法

  • init:第一次访问时创建servlet对象时调用
  • service:每次访问时调用
  • destroy:当服务器停止时释放servlet调用
  • getServletInfo:获取Servlet基本信息,版权,作者

Servlet的两种创建方式

  • 继承HttpServlet,重写doPostdoGet方法
package servlet;

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

@WebServlet("/MyServlet1")
public class MyServlet1 extends HttpServlet {

    public MyServlet1(){
        super();
    }

    //Post请求
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    //get请求
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().append("Served at:").append(request.getContextPath());
        response.getWriter().append("第一种方式");
    }
}
  • 实现Servlet接口
package servlet;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet("/MyServlet2")
public class MyServlet2 implements Servlet {
    @Override
    /**
     * 初始化
     */
    public void init(ServletConfig config) throws ServletException {

    }

    @Override
    /**
     * 读取配置
     */
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    /**
     *服务方法,当浏览器请求Servlet时执行提供服务
     */
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        res.getWriter().write("创建servlet的第二种方法");
    }

    @Override
    /**
     * 获取Servlet基本信息,版权,作者
     */
    public String getServletInfo() {
        return null;
    }

    @Override
    /**
     * 服务方法
     */
    public void destroy() {

    }
}

配置Servlet的两种方法

  1. 使用注解

    @WebServlet(“/yourUrl”)

  2. 配置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_4_0.xsd"
           version="4.0">

    <!--配置Servlet-->

    <!--1.配置servlet节点-->
    <servlet>
        <!--1.1.为Servlet创建一个名字-->
        <servlet-name>MyServlet2</servlet-name>
        <!--1.2.配置类的全路径-->
        <servlet-class>servlet.MyServlet2</servlet-class>
    </servlet>
    <!--2.配置servlet-mapping节点-->
    <servlet-mapping>
        <!--2.1.引用上面定义的servlet的名字-->
        <servlet-name>MyServlet2</servlet-name>
        <!--2.2.定义映射的路径-->
        <!--2.2.1.精确匹配-->
        <url-pattern>/MyServlet2</url-pattern>
        <!--2.2.2.后缀匹配,*通配符,只需要满足后缀即可-->
        <url-pattern>*.servlet</url-pattern>
        <!--2.2.3.通配符匹配-->
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

Servlet生命周期

  1. 实例化

由Servlet容器(Tomcat服务器)调用Servlet的构造方法创建一个servlet对象,默认在收到请求之后才创建;也可以指定在服务器启动时创建
两种方法:

  • 使用注解

@WebServlet(value=”/yourUrl”,loadOnStartup=1)

  • 在xml的Servlet节点中加入

&#60;load-on-startup&#62;0&#60;load-on-startup&#62;

当其值为0或者大于0时表示在服务器启动时创建

  1. 初始化

调用init方法进行初始化

  1. 就绪/服务

对请求进行响应,底层调用service方法

  1. 销毁

服务器在销毁servlet时调用destroy释放资源,通常在服务器重启或关闭时会被执行

Servlet接收请求和响应

html文件放在web目录下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>基本信息</title>
</head>
<body>
    <h1>基本信息</h1>
    <!-- 注意action指定路径 -->
    <form action="InfoServlet" method="post">
        姓名:<input type="text" name="username"/><br>
        年龄:<input type="text" name="age"/><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>
package servlet;

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

@WebServlet("/InfoServlet")
public class InfoServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //在接收前修改请求对象的编码,不然服务器默认使用ISO-8859-1,一个字节一个字符
        request.setCharacterEncoding("utf-8");

        //修改响应编码,默认响应类型也是ISO-8859-1,输入中文会出现乱码
        response.setCharacterEncoding("utf-8");//这种方法不会改变浏览器编码,浏览器使用gbk显示,可能会乱码
        response.setContentType("text/html;charset=utf-8");//在响应头添加编码

        //request封装请求数据,response封装响应数据

        //接收数据
        String username = request.getParameter("username");
        String age = request.getParameter("age");
        System.out.println(request.getRemoteAddr()+username+"..."+age);//返回发起请求的ip地址

        //响应数据
        //输出数据的两种方式:
        PrintWriter out = response.getWriter();//字符流
        out.write("获取数据成功");
        ServletOutputStream out = response.getOutPutStream()//字节流
        out.write("获取数据成功".getBytes());
    }
}

Servlet线程安全问题

因为每次请求都会创建一个线程,如果多人同时请求,就会存在多个线程操作一个Servlet对象,如果在对应的方法中操作了成员变量,就有可能产生线程安全问题

保证线程安全:

  • 将存在线程安全问题的代码放到同步代码块中synchronized
  • 尽可能使用基本不变量,防止使用成员变量

结合JDBC注册案例

javaWeb使用JDBC时需要在web文件夹的lib文件下导入需要的包

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>
    <h1>用户注册</h1>
    <form action="RegisterServlet" method="post">
    <table>
        <tr>
            <td>用户名</td>
            <td>
                <input type="text" name="username">
            </td>
        </tr>
        <tr>
            <td>密码</td>
            <td>
                <input type="password" name="pwd">
            </td>
        </tr>
        <tr>
            <td>确认密码</td>
            <td>
                <input type="password" name="repwd">
            </td>
        </tr>
        <tr>
            <td>邮箱</td>
            <td>
                <input type="email" name="email">
            </td>
        </tr>
        <tr>
            <td>性别</td>
            <td>
                <input type="radio" name="gender" value="男" checked="checked">男
                <input type="radio" name="gender" value="女">女
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="提交">
                <input type="reset" value="重置">
            </td>
        </tr>
    </table>
    </form>
</body>
</html>
package com.xm.utils;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

public class ActiveCodeUtils {
    //创建随机激活码
    public static String createActiveCode(){
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String s1 = simpleDateFormat.format(date);
        String s2 = Integer.toHexString(new Random().nextInt(900)+100);
        return s1+s2;
    }
}

package com.xm.servlet;

import com.xm.utils.ActiveCodeUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

@WebServlet("/RegisterServlet")
public class RegisterServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        //获取数据
        String username = request.getParameter("username");
        String pwd = request.getParameter("pwd");
        String repwd = request.getParameter("repwd");
        String email = request.getParameter("email");
        String gender = request.getParameter("gender");

        //验证数据
        PrintWriter writer = response.getWriter();
        //用户名为空或者输入空格(trim表示去掉空格)
        if(username == null || username.trim().equals("")){
            writer.write("用户名不能为空");
        }
        if(!pwd.equals(repwd)){
            writer.write("两次密码输入不一致");
        }
        //使用JDBC
        Connection conn = null;
        PreparedStatement pstat = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(
                    "jdbc:mysql://127.0.0.1:3306/shop?useSSL=false",
                    "root",
                    "zm19980225");
            pstat = conn.prepareStatement(
                    "insert into users(username,password,email,gender,flag,role,code) values (?,?,?,?,?,?,?)"
            );
            pstat.setString(1,username);
            pstat.setString(2,pwd);
            pstat.setString(3,email);
            pstat.setString(4,gender);
            pstat.setInt(5,0);
            pstat.setInt(6,1);
            pstat.setString(7, ActiveCodeUtils.createActiveCode());//使用工具类生成随机激活码
            int res = pstat.executeUpdate();
            if(res>0){
                writer.write("注册成功");
            }else{
                writer.write("注册失败");
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
            writer.write("注册失败");
        }finally {
            if(pstat!=null){
                try {
                    pstat.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn!=null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

重定向

package com.xm.servlet;

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

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //接受数据
        String username = request.getParameter("username");
        String pwd = request.getParameter("pwd");
        System.out.println("用户名:"+username+"密码:"+pwd);
        //判断
        if(username.equals("join") && pwd.equals("8888")){
            //重定向,指明地址为web目录下的路径
            response.sendRedirect("info.html");
            return;
        }else{
            response.sendRedirect("register.html");
        }
    }
}

转发

只能转发到同一个服务器(web容器)下的另一个url,不能是其他网站

//转发,应该使用request,因为是在请求的时候进行转发
//获取转发器
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/ListServlet");//在服务器端,/表示web目录
//转发
requestDispatcher.forward(request,response);//使用转发浏览器的url不会改变

关于路径问题

  • 绝对路径:用在不同网站之间的跳转
  • 相对路径:容易出现混乱
  • 根路径:如果在浏览器中使用,/表示域名,如果在服务器中使用,/表示web项目

Response类

设置响应头的几种方法:

response.setCharacterEncoding();
//设置tomcat编码,将数据写在html中
response.setHeader("Content-type","text/html;charset=UTF-8")
response.setContntType("text/html;charset=UTF-8")

Request类常用API

获取请求路径相关参数:
getRequestURL:返回客户端发出请求时的完整URL
getRequestURI:返回请求行中的资源名部分
getQueryString:返回请求行中的参数部分
getRmoteHost:返回请求的客户机的完整主机名
getLocalAddr:返回WEB服务器的IP地址
getLocaleName:返回WEB服务的主机名
getMethod:客户机请求方式
获取请求头信息:
getHead(name)
getHeaders(String name)
getHeaderNames
获取请求正文参数:
getParameter(name)
getParameterValues(String name)
getParameterNames
getParameterMap
getInputStream

下载文件

package com.xm.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;

@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //request.setCharacterEncoding("utf-8");

        //这种相对路径方法不行
        //File file = new File("images/test.jpg");

        //使用类加载器,也不行
        //DownloadServlet.class.getClassLoader().getResourceAsStream("images/test.jpg");

        //使用绝对路径(可以)
        //File file = new File("/media/M_fM__VM__GM_fM_!M_#/编程语言/Java/JavaWebStudy/web/images/test.jpg");

        //一般使用这种方式
        ServletContext application = getServletContext();
        String realpath = application.getRealPath("images/图片.jpg");
        System.out.println(realpath);
        File file = new File(realpath);
        FileInputStream fis = new FileInputStream(realpath);

        //中文乱码:将文件名变成utf-8编码,再转换成iso-8859-1编码传给服务器,服务器读取后浏览器再用utf-8
        //两种方法:
        String fileName = new String(file.getName().getBytes("utf-8"),"iso-8859-1");
        //String fileName = URLEncoder.encode(file.getName(),"utf-8");
        System.out.println(file.getName());
        System.out.println(fileName);
        //发送给服务器之前要设置请求头,不设置会有文件名问题
        response.setHeader("Content-Disposition","attachment;filename="+fileName);
        ServletOutputStream sos = response.getOutputStream();
        byte[] bytes = new byte[1024*4];
        int len=0;
        while ((len = fis.read(bytes))!=-1){
            sos.write(bytes,0,len);
        }
    }
}

Cookie

package com.xm.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;

@WebServlet("/cookieServlet")
public class CookieServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        //设置cookie
        Cookie cookie = new Cookie("username","hongdou");
        //如果有中文需要设置编码
        Cookie cookie2 = new Cookie("username", URLEncoder.encode("红豆","utf-8"));
        //设置cookie的有效期,负数表示文件存储在浏览器中,关闭浏览器时即被删除,默认是-1;0表示删除cookie;正数表示有效期,以秒为单位
        cookie.setMaxAge(10*60);
        //设置cookie路径:默认为当前程序指定url往上一级
        cookie.setPath("/xxxx");
        //获取cookie的路径
        System.out.println(cookie.getPath());
        //设置cookie只有http才能读取,默认脚本可读
        cookie.setHttpOnly(true);
        //将cookie添加到response发给浏览器(颁发cookie)
        response.addCookie(cookie);
        response.addCookie(cookie2);
        //读取浏览器发回来的cookie
        Cookie[] cookies = request.getCookies();
        if(cookies!=null){
            for(Cookie c:cookies){
                //对于中文需要转码
                System.out.println(c.getName()+":"+ URLDecoder.decode(c.getValue(),"utf-8"));
            }
        }
    }
}

Session

session有效时间默认是无操作半小时

package com.xm.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

@WebServlet("/sessionServlet")
public class SessionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        //获取session,一旦获取session,服务器就会创建cookie JSESSIONID,其值是一个十六进制字符串
        HttpSession session =request.getSession();

        System.out.println("sessionId:"+session.getId());
        System.out.println("session创建时间:"+new SimpleDateFormat().format(new Date(session.getCreationTime())));
        System.out.println("最后一次访问时间:"+new SimpleDateFormat().format(new Date(session.getLastAccessedTime())));
        System.out.println("session过时时间:"+session.getMaxInactiveInterval());

        //向session中存放数据
        session.setAttribute("username","ormosia");

        //获取session数据
        System.out.println(session.getAttribute("username"));

        //设置session无效
        session.invalidate();
        
        //修改过期时间
        session.setMaxInactiveInterval(20*60);
    }
}

设置session过期的另一种方法是在xml文件中配置

<session-config>
    <!--单位为分钟-->
    <session-timeout>20</session-timeout>
<session-confin>

cookie和session的创建都是在服务器,但是cookie其状态是保存在客户端中的,session是保存在服务器中的。创建session时,服务器会在浏览器上创建一个cookie存储sessionID,后面客户端请求时服务器将读取sessionId找到对应的session。当浏览器关闭时,cookie会被删除,所以下次再访问时无法根据sessionId找到session,就会导致失效(但是实际上session还在,只是找不到了)

将表单数据封装成javaBean的三种方法

package com.xm.servlet;

import com.xm.domain.User;
import com.xm.utils.ActiveCodeUtils;
import org.apache.commons.beanutils.BeanUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 封装从表单获取的javabean类
 */
@WebServlet("/beanServlet")
public class BeanServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        //第一种方法
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String email = request.getParameter("email");
        String gender = request.getParameter("gender");

        User user = new User(1,username,password,email,gender,0,1, ActiveCodeUtils.createActiveCode());
        System.out.println(user.toString());

        第二种方法
        Map<String, String[]> paraMap = request.getParameterMap();
        User user = new User();
        for(Map.Entry<String,String[]> entry:paraMap.entrySet()){
            System.out.println(entry.getKey());
            System.out.println(entry.getValue().toString());
            System.out.println(entry.getValue().length);

            String key = entry.getKey();
            try {
                //属性描述符
                PropertyDescriptor pd = new PropertyDescriptor(key,User.class);//获取指定属性(key)的属性描述符
                if(pd != null){
                    Method method  = pd.getWriteMethod();//获取该属性的写方法
                    if(entry.getValue().length == 1){
                        method.invoke(user,entry.getValue()[0]);
                    }else{
                        method.invoke(user,entry.getValue());
                    }
                }
            } catch (IntrospectionException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        System.out.println(user.toString());

        //第三种方法:使用已经封装好的BeanUtils包
        /**
        * 需要三个包:
        * 1. commons-beanutils
        * 2. commons-collections
        * 3. commons-logging
        */
        User user  = new User();
        try {
            //d第一个参数是类对象,第二个是表单的所有内容,会自动把值赋给对象
            BeanUtils.populate(user,request.getParameterMap());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println(user.toString());

    }
}

ServletContext

ServletContext又叫Servlet上下文,代表当前整个应用程序,web服务器启动时,会为每一个Web应用程序创建的一个共享的存储区域,在服务器启动时创建,关闭时销毁

ServletContext的获取:

//第一种方法:
ServletContext application = this.getServletContext();
//第二种方式:
ServletContext application2 = getServletConfig().getServletContext();
//第三种方法:通过session获取,常用
ServletContext application3 = request.getSession().getServletContext();
//第四种方式:通过Request,最常用
ServletContext application4 = request.getServletContext();

获取信息

//获取资源路径
String path = application.getRealPath("");//代表应用程序所在目录,即服务器的web目录
String path2 = application.getRealPath("WEB-INF/classes/books.xml");//放在src下的文件,应该这样写
//获取路径信息(上下文路径)
String path3 = application.getContextPath();//项目根路径
//在上下文存储信息
application.setAttribute("welcome","在其他页面获取上下文也可以读到");
//读取上下文信息
Object attribute = application.getAttribute("welcome");

过滤器Filter

初始化Filter的两种方法:

使用注解

@WebFilter(“/yourFilter”)

使用xml文件

<filter>
    <filter-name>firstFilter</filter-name>
    <filter-class>com.xm.filter.FirstFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>firstFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

注解优先级别设定:
在xml中,排前面的filter-mapping优先执行
在注解中按照类名的字符串排序进行作用

过滤器处理浏览器缓存

当向服务器请求html界面时,第一次服务器会创建一个缓存区,将资源放入缓存区,当客户端第二次进行访问时,服务器根据时间判断如果是同一个资源且没有更新,就直接返回缓存区资源并且返回304状态码,同时浏览器也会在本地缓存资源,当关闭浏览器重新打开或打开新页面时进行访问时,浏览器会从本地缓存查找,如果有就返回200(from disk cache),再次刷新返回304

避免浏览器缓存的方法:浏览器还是会缓存,只是控制每次都是第一次访问

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    System.out.println("开始执行过滤");
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;
    response.setDateHeader("Expires",-1);
    response.setHeader("Cache-Control","no-cache");
    response.setHeader("Pragma","no-cache");
    chain.doFilter(req, resp);//资源放行,只有这句话执行才能继续访问
    
    System.out.println("过滤完成");
    }

请求和响应都需要经过过滤器,请求经过的为chain.daFilter之前,其后为响应的过滤

全站压缩功能

正常情况下调用httpResponse的write方法会直接将资源发给客户端,可以使用装饰者设计模式将write方法的写入内容先写进缓存,压缩后再进行发送

package com.xm.filter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 继承HttpServletResponseWrapper这个专门用来封装新功能的类
 */
public class GzipResponse extends HttpServletResponseWrapper {

    private ByteArrayOutputStream baos;//内存流
    private PrintWriter pw;
    /**
     * Constructs a response adaptor wrapping the given response.
     *
     * @param response The response to be wrapped
     * @throws IllegalArgumentException if the response is null
     */
    public GzipResponse(HttpServletResponse response) {
        super(response);
        baos = new ByteArrayOutputStream();
    }

    public ByteArrayOutputStream getBaos(){
        if(pw!=null){
            pw.flush();//将pw的缓存写入baos
        }
        return baos;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        pw = new PrintWriter(baos,true);//获取一个写入内存流的打印流
        return pw;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return super.getOutputStream();
    }
}
package com.xm.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;

@WebFilter("/gzipServlet")
/**
 * 全站压缩
 */
public class GzipFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //压缩前进行处理
        HttpServletRequest request = (HttpServletRequest) req;
        GzipResponse response = new GzipResponse((HttpServletResponse)resp);

        //处理后发给目标地址
        chain.doFilter(request, response);

        //将目标地址操作后要发送的资源进行压缩
        ByteArrayOutputStream baos = response.getBaos();
        System.out.println("未压缩前的资源大小:"+baos.size());
        ByteArrayOutputStream newbaos = new ByteArrayOutputStream();//保存压缩后的数据
        //压缩类
        GZIPOutputStream gzip = new GZIPOutputStream(newbaos);
        //压缩操作
        gzip.write(baos.toByteArray());
        gzip.flush();
        gzip.close();
        System.out.println("压缩后的数据大小:"+newbaos.size());

        //把压缩后的数据发给浏览器
        response.setHeader("Content-Encoding","gzip");
        response.getOutputStream().write(newbaos.toByteArray());
    }

    public void init(FilterConfig config) throws ServletException {

    }
}
package com.xm.servlet;

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

@WebServlet("/gzipServlet")
public class GzipServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();//因为经过了过滤器,获取到的是自定义压缩里面的打印流
        writer.write("一二三四五六七八九十十一十二十三十四十五十六十七十八十九二十");
    }
}

监听器

监听器在服务器创建时启动,服务器关闭时销毁
监听器的两种配置方法:

  • 注解@WebListener()
  • 使用xml文件
<listener>
        <listener-class>类所在的路径(com.xm.xxxx)</listener-class>
    </listener>

监听器主要有以下几种:

  • ServletContextListener:监听Servlet上下文
package com.xm.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener()
/**
 * Servlet上下文监听器
 * 事件源:ServletContext
 * 监听器
 */
public class MyServletContextListener implements ServletContextListener {

    // Public constructor is required by servlet spec
    public MyServletContextListener() {
    }

    // -------------------------------------------------------
    // ServletContextListener implementation
    // -------------------------------------------------------

    //初始化
    public void contextInitialized(ServletContextEvent sce) {
      /* This method is called when the servlet context is
         initialized(when the Web application is deployed). 
         You can initialize servlet context related data here.
      */
      System.out.println(sce.getSource().hashCode());
    }

    //销毁
    public void contextDestroyed(ServletContextEvent sce) {
      /* This method is invoked when the Servlet Context 
         (the Web application) is undeployed or 
         Application Server shuts down.
      */
    }
}
  • ServletContextAttributeListener:监听ServletContext中变量的变化
package com.xm.listener;

import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.annotation.WebListener;


@WebListener()
public class MyServletContextAttributeListener implements ServletContextAttributeListener{

    // Public constructor is required by servlet spec
    public MyServletContextAttributeListener() {
    }

    @Override
    //添加了某个变量
    public void attributeAdded(ServletContextAttributeEvent scae) {
        System.out.println("添加了某个变量"+scae.getName()+":"+scae.getValue());
    }

    @Override
    //删除了某个变量
    public void attributeRemoved(ServletContextAttributeEvent scae) {
        System.out.println("删除了某个变量"+scae.getName()+":"+scae.getValue());

    }

    @Override
    //替换了某个变量
    public void attributeReplaced(ServletContextAttributeEvent scae) {
        System.out.println("更新了某个变量"+scae.getName()+":"+scae.getValue());
    }
}
  • HttpSessionListener:session监听器
package com.xm.listener;

import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener()
public class MyHttpSessionListener implements ServletContextListener,
        HttpSessionListener, HttpSessionAttributeListener {

    // Public constructor is required by servlet spec
    public MyHttpSessionListener() {
    }

    // -------------------------------------------------------
    // HttpSessionListener implementation
    // -------------------------------------------------------
    public void sessionCreated(HttpSessionEvent se) {
        /* Session is created. */
        System.out.println("创建了session:"+se.getSession());
    }

    public void sessionDestroyed(HttpSessionEvent se) {
        /* Session is destroyed. */
        //服务器关闭或者session过期
        System.out.println("销毁了session:"+se.getSession());
    }
}
  • HttpSessionAttributeListener:监听session的变量变化
  • HttpSessionBindingListener:监听对象的添加和删除,该类不需要写监听器,将需要监听的对象继承该接口
package com.xm.domain;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

public class User implements HttpSessionBindingListener {
    private String username;
    private String password;
    private String email;

    public User() {
    }

    public User(String username, String password, String email) {
        this.username = username;
        this.password = password;
        this.email = email;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

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

    @Override
    public String toString() {
        return "User{" +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                '}';
    }

    @Override
    /**
     * 把user放入session时执行
     */
    public void valueBound(HttpSessionBindingEvent event) {
        System.out.println("向session中添加对象:"+event.getName()+":"+event.getValue());
    }

    @Override
    /**
     * 把user从session中移除时执行
     */
    public void valueUnbound(HttpSessionBindingEvent event) {
        System.out.println("向session中删除对象:"+event.getName()+":"+event.getValue());
    }
}
  • ServletRequestListener:监听请求生命周期
  • ServletRequestAttributeListener:监听请求中的变量属性

自定义session管理

package com.xm.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.*;

/**
 * session管理
 */
@WebListener
public class SessionManagerListener implements HttpSessionListener, ServletContextListener {
    private Timer timer;//计时器工具类
    private Object lock = new Object();//一把锁,防止同步问题
    private List<HttpSession> sessions = new LinkedList<HttpSession>();
    private class MyTimerTask extends TimerTask{
        @Override
        public void run() {
            Iterator<HttpSession> iterator = sessions.iterator();
            synchronized (lock){
                while (iterator.hasNext()) {
                    HttpSession session = iterator.next();
                    if(System.currentTimeMillis()-session.getLastAccessedTime()>(60*1000)){
                        session.invalidate();//失效
                        iterator.remove();//把元素移除
                    }
                }
            }
        }
    }
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        timer = new Timer();
        timer.schedule(new MyTimerTask(),0,60*1000);//启动计时器
        sce.getServletContext().setAttribute("session",sessions);
        System.out.println("定时器执行了");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        synchronized (lock){
            sessions.add(se.getSession());
            System.out.println("添加了一个session"+se.getSession().hashCode());
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("session销毁了");
    }
}

文件上传

package com.xm.utils;

import java.io.File;
import java.util.UUID;

/**
 * 文件上传工具类
 */
public class FileUploadUtils {
    /**
     * 生成文件名
     * @param oldFileName
     * @return
     */
    public static String getNewFileName(String oldFileName){
        String uuid = UUID.randomUUID().toString().replaceAll("-","");//返回唯一通用识别码
        System.out.println(uuid);
        return uuid+"_"+oldFileName;
    }

    /**
     * 产生一个新的目录
     * @param basedir
     * @param oldFileName
     * @return
     */
    public static String getNewPath(String basedir,String oldFileName){
        int hashCode = oldFileName.hashCode();
        int dir1 = hashCode&0xf;//与15与运算取后四位
        int dir2 = (hashCode>>4)&0xf;//右移4位再取四位

        String path = basedir+"\\"+dir1+"\\"+dir2;
        File d = new File(path);
        if(!d.exists()){
            d.mkdirs();
        }
        return path;
    }
}


package com.xm.servlet;

import com.xm.utils.FileUploadUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
import java.util.Collection;

@WebServlet("/uploadFile")
//location:存储临时文件的位置
//maxFileSize:上传文件允许的最大大小(以字节为单位)
//maxRequstSize:multipart/form-data允许的最大请求大小
//fileThreadThreshold:文件将写入磁盘的大小阈值,缓存,数据量大于该值时写入文件
@MultipartConfig(location = "",maxFileSize = 1024*1024*2,maxRequestSize = 1024*1024*20,fileSizeThreshold = 1024*100)
public class UpLoadFileServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        String basePath = request.getServletContext().getRealPath("/WEB-INF/upload");
        System.out.println(basePath);
        Collection<Part> parts = request.getParts();
        if(parts!=null){
            for (Part part : parts) {
                String fileName = part.getSubmittedFileName();
                if (fileName != null) {
                    //该项是文件

                    //获取新的文件名
                    String newFileName = FileUploadUtils.getNewFileName(fileName);
                    //获取存储目录
                    String newPath = FileUploadUtils.getNewPath(basePath,fileName);
                    part.write(newPath+"\\"+newFileName);
                    System.out.println(fileName+"上传成功");
                    response.getWriter().write("上传成功");
                }else{
                    //不是文件,是其他属性
                    String name = part.getName();//获取属性名
                    String value = request.getParameter(name);
                    System.out.println(name+":"+value);
                }
            }
        }
    }
}