《Tomcat与Java Web开发技术详解》阅读梳理 第五章 Servlet技术(下)

点击查看合集

下载文件

//下载文件Servlet
public class UploadFileServlet extends HttpServlet {
    
    
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        doPost(req,resp);
    }
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    
    
        //获取读取本地文件的输入流
        InputStream is;
        ServletContext context = getServletContext();
        is = context.getResourceAsStream("/NVRFE60YF.png");
        //设置输出的MIME类型
        resp.setContentType("application/force-download");
        //设置响应头
        resp.setHeader("Content-Length",String.valueOf(is.available()));//设置文件文件长度
        resp.setHeader("Content-Disposition","attachment;filename=wenjian.png");//设置文件需要用户下载而不是展示(attachment)并且文件默认名为wenjian.png
        //获取向客户端的输出流
        ServletOutputStream os = resp.getOutputStream();
        //缓冲512字节
        byte[] bytes = new byte[512];
        int len = -1;
        //写到输出流
        while((len = is.read(bytes))!=-1){
    
    
            os.write(bytes,0,len);
        }
        //关闭流
        is.close();
        os.close();
    }
}

上传文件

通过Apache开源类库

Apache提供了两个和软件上传相关的软件包
fileupload软件包和I/O软件包
fileupload的主要接口和类的类框图
6
对于一个请求正文为“multipar/form-data”类型的HTTP请求。upload软件包把请求正文的复合表单的每个子部分看成一个FileItem对象。
FileItem对象有两种类型formFild类型(普通表单域)非fromField类型(文件类型)
ServletFileUpload类是文件上传处理器。

//上传文件Servlet
public class DownLoadFileServlet extends HttpServlet {
    
    
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        doPost(req,resp);
    }
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    
    
         //设置响应正文编码
        resp.setCharacterEncoding("utf-8");
        //创建一个DiskFileItemFactory对象(用来制造DiskFileItem对象)
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //设置向硬盘写数据时所用的缓冲区大小
        factory.setSizeThreshold(1024);
        //获取Context对象
        ServletContext context = getServletContext();
        //获取临时目录
        String tempPath = context.getRealPath("/temp");
        //设置一个临时目录
        factory.setRepository(new File(tempPath));
        //创建一个文件上传处理器(与factory产生联系)
        ServletFileUpload fileUpload = new ServletFileUpload(factory);
        //设置最大上传文件大小
        fileUpload.setSizeMax(10*1024*1024);
        PrintWriter writer = resp.getWriter();
        //遍历fileItem
        try {
    
    
            Map<String, List<FileItem>> listMap = fileUpload.parseParameterMap(req);
            for (String s : listMap.keySet()) {
    
    
                List<FileItem> fileItems = listMap.get(s);
                for (FileItem fileItem : fileItems) {
    
    
                    if(fileItem.isFormField()){
    
    //普通表单域
                        writer.println(fileItem.getFieldName()+":"+fileItem.getString());
                    }else {
    
    
                        //获取文件名
                        String fN = fileItem.getName();
                        //保存
                        fileItem.write(new File(context.getRealPath("/downFile")+"/"+fN));
                        writer.println(fN+"已经保存");
                    }
                }
            }
        } catch (FileUploadException e) {
    
    
            e.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

待解决:不知道为什么通过fileItem不能获取中文字符

读写Cookie

Cookie是服务器存放在客户端的一些信息。
Web服务器为了支持Cookie需要具备以下数据
1.在HTTP响应结果中添加Cookie数据
2.解析HTTP请求中的Cookie信息
浏览器为了支持Cookie,需要具备以下功能。
1.解析HTTP响应中的Cookie数据
2.把Cookie数据保存到本地硬盘
3.读取本地硬盘上的Cookie数据,把他添加到HTTP请求中。

添加Cookie

//创建一个Cookie对象
        Cookie cookie = new Cookie("key","value");
        //设置失效时间(单位:秒)1.大于0时,代表浏览器在客户硬盘上保存Cookie的时间 2.等于0,告诉浏览器删除客户端中的这个Cookie 3.小于0,不把Cookie存入硬盘,该Cookie生命周期和此浏览器进程相当。
        //默认为-1
        cookie.setMaxAge(1);

设置cookie共享范围

//tomcat根目录下所有Web应用间可访问
        cookie.setPath("/");
        //app1应用可访问
        cookie.setPath("/app1");
        //.baidu.com域名下可访问
        cookie.setDomain(".baidu.com");

获取Cookie对象

//获取HTTP请求中的所有Cookie信息
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
    
    
            System.out.println(cookie.getName());
            System.out.println(cookie.getValue());
            System.out.println(cookie.getMaxAge());
        }

获取RequestDispa对象

		//方式1(绝对路径)
        ServletContext servletContext = getServletContext();
        servletContext.getRequestDispatcher("路径");
        //方式2(绝对路径/相对路径)
        req.getRequestDispatcher("路径");

方式1的路径是绝对路径
方式2的路径可以是绝对路径也可以是相对路径
以/开头的为绝对路径。Web应用的根目录

请求转发和包含

由于无法通过Servlet获取其他的Servlet。因此当我们需要在一次请求中调用多个Servlet参与的话。。就可以通过请求转发或包含来完成
特点:
1.原组件和目标组件处理的都是一个请求。
2.目标组件可以是Servlet JSP或HTML

请求转发:

forward方法:把请求转发给其他组件,并由其他组件完成后续的生成响应结果等操作。
在执行requestDispatcher.forwardf方法时,会清空用于存放响应正文的缓冲区。也就是说在源组件中不能向响应正文中添加数据。如果调用了与提交缓冲区相关的方法。会导致IllegalStateException异常。
在这里插入图片描述

包含:

include方法:把包含的组件产生的响应结果包含到自身的响应结果中。
特点:
1.源组件与被包含组件的输出数据都会被添加到响应结果中
2.在目标组件中对响应状态代码或者响应头所作的修改都会被忽略。

重定向:

运作流程

1.用户通过URL访问某个Web组件
2.服务器端的组件返回一个状态代码为302的响应结果。该结果的含义是让客户端再请求访问另一个Web组件
3.浏览器接收响应结果后自动访问另一个Web组件
4.浏览器端接收另一个Web组件的响应结果

重定向特点(response.sendRedirect方法)

1.源组件生成的响应结果不会被发送到客户端。只有重定向的Web组件的响应结果发送到客户端
2.如果源组件在重定向前调用了提交缓冲区的方法,在执行sendRedirect时,会抛出IllegalStateException异常。
3.源组件和重定向的web组件不是一次请求。因此他们不共享request和response
4.参数可以是任意有效的地址

异步处理HTTP请求

Servlet会为每个HTTP请求分配一个工作线程。即每次Servlet容器都会从主线程池中取出一个空闲的工作线程,由该线程从头到尾处理这次请求。如果在HTTP请求中涉及到I/O操作或者数据库的操作等耗时的操作,那么该工作线程就会被长时间占用。
因此我们可以采取异步的方式解决这一问题。

Servlet异步处理机制

Servlet从HTTPServletRequest对象中获取一个AsyncContext对象。该对象表示异步处理上下文。AsyncContext把响应当前请求的任务传给一个新的线程。最初的由Servlet容器分配的主线程就可以先被释放掉。从而可以处理更多的请求。所以所谓的Servlet异步处理机制,就是把任务从Servlet容器的主线程传给另一个线程。

异步处理流程

一:首先需要配置Servlet为支持Async的Servlet
方法1注解

@WebServlet(
            asyncSupported = true
    )

方法2web.xml文件

<servlet>
    <servlet-name>upload</servlet-name>
    <servlet-class>com.sea.UploadFileServlet</servlet-class>
    <async-supported>true</async-supported>//这里
  </servlet>

二:获取AsyncContext对象

AsyncContext asyncContext = httpServletRequest.startAsync();

asyncContext为处理异步操作提供了上下文,他拥有的方法为:

在这里插入图片描述
三:启动异步线程
有多种方式启动异步线程,每种方式都有各自的特点
方式1:

		AsyncContext asyncContext = req.startAsync();
        //设置异步操作超时时间
        asyncContext.setTimeout(60*5);
        //开启异步线程
        asyncContext.start(new MyThread(asyncContext));

asyncContext的start方法的实现方式和Servlet容器有关。有的是直接从Servlet的工作线程中取一个空闲的线程(相当于并发效率并没有改变,取一个放回一个)。另一种实现是维护一个专门的线程池来给他分配线程。
方式2

AsyncContext asyncContext = req.startAsync();
        //开启异步线程
        new Thread(new MyThread(asyncContext)).start();

手动创建新线程,如果访问量大的话,可能同时存在很多的线程,大大降低运行效率。
方式3

//创建一个线程池。
 private static ThreadPoolExecutor pool = new ThreadPoolExecutor(
                100,//线程池维护的线程的最小数量
                200,//线程池维护的线程的最大数量
                5000L,//线程池维护的线程所允许的空闲时间
                TimeUnit.MILLISECONDS,//线程池维护的线程所允许的空闲时间的单位
                new ArrayBlockingQueue<Runnable>(100)//线程池使用的缓冲队列
                );
AsyncContext asyncContext = req.startAsync();
        //开启线程
        pool.execute(new MyThread(asyncContext));

处理请求的具体线程

public class MyThread implements Runnable{
    
    
    //HTTP异步请求上下文
    AsyncContext asyncContext = null;
    public MyThread(AsyncContext context){
    
    
        asyncContext = context;
    }
    @Override
    public void run() {
    
    
        System.out.println("异步线程执行耗时操作");
        //执行完毕后调用该方法通知Servlet
        asyncContext.complete();
    }
}

异步监听器

AsyncContext asyncContext = req.startAsync();
        //添加监听器
        asyncContext.addListener(new AsyncListener() {
    
    
            @Override
            public void onComplete(AsyncEvent asyncEvent) throws IOException {
    
    
                System.out.println("异步线程执行完时调用");
            }

            @Override
            public void onTimeout(AsyncEvent asyncEvent) throws IOException {
    
    
                System.out.println("异步线程执行超时时调用");
            }

            @Override
            public void onError(AsyncEvent asyncEvent) throws IOException {
    
    
                System.out.println("异步线程出错时调用");
            }


            @Override
            public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
    
    
                System.out.println("异步线程开始时");
            }
        });

非阻塞I/O

阻塞I/O:当要读取的数据还没准备好,或者要写的数据还没准备好,线程会进入阻塞状态。
非阻塞I/O:当要读取的数据还没准备好,或者要写的数据还没准备好,线程会直接退出读/写方法。
Servlet有两个监听器

ReadListener接口:

监听ServletInputservlet输入流的行为。
接口的方法有:
在这里插入图片描述

//ServletInputStrem监听器
public class MyReadListener implements ReadListener {
    
    
    StringBuffer sb = new StringBuffer();
    ServletInputStream is = null;
    AsyncContext context = null;
    public MyReadListener(ServletInputStream is,AsyncContext context){
    
    
        this.context = context;
        this.is = is;
    }
    //servletInputStream流中有数据时触发
    @Override
    public void onDataAvailable() throws IOException {
    
    
        System.out.println("输入流中有可读数据");
        int len = -1;
        byte[] bytes = new byte[1024];
     while(is.isReady()&&(len = is.read(bytes))!=-1){
    
    
            System.out.println(len);
            sb.append(new String(bytes,"utf-8"));
        }
        System.out.println("结束");
    }

    //读完
    @Override
    public void onAllDataRead() throws IOException {
    
    
        System.out.println("读取完毕");
        System.out.println(sb.toString());
        //或者转发给其他组件
        //context.dispatch("web组件");
    }

    @Override
    public void onError(Throwable throwable) {
    
    

    }
}

遇到的坑:is.isReady()&&(len = is.read(bytes))!=-1一定要写前面的is.isReady()他在is没留数据时会返回false。而这个is.read方法是重写过的,当没有数据可读时他会抛出异常。。。。

WriteListener接口:监听ServletOutputStream输出流的行为。

//ServletOutputStream监听器
public class MyWriteListener implements WriteListener {
    
    
    AsyncContext context = null;
    ServletOutputStream os = null;
    public MyWriteListener(AsyncContext context,ServletOutputStream os){
    
    
        this.context = context;
        this.os = os;
    }
    @Override
    public void onWritePossible() throws IOException {
    
    
        System.out.println("可以向输出流写数据了");
    }

    @Override
    public void onError(Throwable throwable) {
    
    
        System.out.println("发生错误");
    }
}

服务器端推送

//推送测试
@WebServlet(urlPatterns = "/push")
public class PushServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        //获取pushBuilder对象
        PushBuilder pushBuilder = req.newPushBuilder();
        PrintWriter writer = resp.getWriter();
        pushBuilder.path("NVRFE60YF.png").push();
        if(pushBuilder!=null){
    
    
            writer.write("<img src=\"NVRFE60YF.png\">");
        }else {
    
    
            writer.write("不支持");
        }
    }
}

(由于我的tomcat是8.5.51不支持推送,所以推送的代码我没有具体试过。若想支持推送需要配置tomcat9)

猜你喜欢

转载自blog.csdn.net/qq_30033509/article/details/109697923