Java Web-servlet、HTTP in servlet和捎带的Java绘图学习

Java Web-servlet、HTTP in servlet和捎带的Java绘图学习

server applet:运行在服务器端的小程序

动态项目的动态内容的java类依赖于服务器才能运行,由tomcat执行,所以需要遵守一定的规则(接口)才能被Tomcat所识别,这个接口就是servlet。

快速入门

  1. 创建一个JavaEE项目

  2. 定义一个类,实现Servlet接口

  3. 实现接口中的方法

    package com.jiading.web.servlet;
    
    import javax.servlet.*;
    import java.io.IOException;
    
    /*
    servlet快速入门
     */
    public class ServletDemo1 implements Servlet {
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
    
        }
    
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
    
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    /*
    提供服务的方法
     */
            System.out.println("hello,servlet");
        }
    
        @Override
        public String getServletInfo() {
            return null;
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    
  4. 配置Servlet:将java类映射为URL中的资源,在WEB-INF目录的web.xml文件中进行配置

    WEB-INF下的lib目录用来存放jar包

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
        <servlet>
            <servlet-name>demo1</servlet-name>
            <servlet-class>com.jiading.web.servlet.ServletDemo1</servlet-class>
        </servlet>
        <!-- 配置映射-->
        <servlet-mapping>
            <!-- 要配置的servlet的名称,与上面servelt标签下的name一致-->
            <servlet-name>demo1</servlet-name>
            <!-- 对应的URL中的路径名称-->
            <url-pattern>/demo1</url-pattern>
        </servlet-mapping>
    </web-app>
    

可以在run/configurations/Deployment下的Application context修改部署的路径,注意不是修改Server/URL,那个只控制打开浏览器时默认打开的页面。

在Deployment下还可以修改输出的项目名称

执行原理

客户端输入URL后,先通过localhost:8080找到tomcat,再通过项目名找到对应的项目,然后从web.xml中遍历查找资源名,然后通过servlet-map的映射查找到对应的servlet,再找到其下的servlet-class.

Tomcat将全类名对应的字节码文件加载进内存:Class.forName()

创建对应的对象:class.newInstance()

调用对象的service方法

所以,我们在写servlet对象时,我们不需要创建Main方法、实例化对象、自己调用service方法,这些都是服务器在收到访问请求后来完成的。借助这样的机制,我们就可以把前端和后端联系起来

Servlet的生命周期

servlet的生命周期对应着它的几个方法:

  1. 被创建
  2. 提供服务
  3. 被销毁

方法如下:

package com.jiading.web.servlet;

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

public class ServletDemo2 implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
/*
初始化方法,在servlet被创建时(也就是Servlet第一次被请求时)执行,只执行一次
 */
    }

    @Override
    public ServletConfig getServletConfig() {
        /*
        获取ServletConfig对象,是Servlet的配置对象
         */
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
/*
提供服务的方法,是主要的方法。每一次servlet被访问都会执行
 */
    }

    @Override
    public String getServletInfo() {
        /*
        获取Servlet的一些信息,版本、作者等,不常用
         */
        return null;
    }

    @Override
    public void destroy() {
/*
在Servlet被杀死时执行,也就是服务器正常关闭时执行。
和析构函数一样,是在Servlet被销毁之前执行,而不是之后
一般用来释放资源
 */
    }
}

除了默认的被第一次调用时创建之外,我们也可以在web.xml中指定创建的事件

    <servlet>
        <servlet-name>demo2</servlet-name>
        <servlet-class>com.jiading.web.servlet.ServletDemo2</servlet-class>
        <!-- 指定被创建的事件
        1. 第一次被访问时创建,是默认,配置load-on-startup配置为负数时生效(默认是-1)
        2. 在服务器启动时创建,要配置为0或者正数
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

一个Servlet在内存中只存在一个对象,说明Servlet是单例的。但是这种单例在多用户同时访问时可能存在线程安全问题,也就是共享资源的问题。但是也不能加锁,否则同时只能有一个用户访问。解决方法就是,尽量不要在Servlet中定义成员变量,从源头解决问题。即使定义了,也不要对其进行做修改值的操作

Servlet3.0的注解配置

为了简化每次对新的Servlet类都要进行配置这一操作,Servlet3.0支持了注解配置,不需要使用web.xml进行配置了。从JavaEE6开始支持Servlet3.0

步骤:

  1. 创建JavaEE项目,注意选择3.0以上的Servlet版本。此时再创建就不需要创建web.xml了

  2. 定义一个类,实现Servlet接口

  3. 复写方法

  4. 在类上使用@WebServlet注解。在注解上配置URL pattern,例如:

    @WebServlet(urlPatterns="/demo")
    

    其可以简写为:

    @WebServlet("/demo")
    

    这是因为@WebServlet类中有个value属性,可以输入一些很重要的配置,当然最重要的就是urlPattern,所以如果只输入一个值给value的话默认就是给urlPattern赋值。而value的输入又可以不写value=,所以可以简化到上面那一步

注解配置的最大优点就是方便

Servlet体系结构

除了直接继承Servlet之外,我们其实还有其他的选择。Servlet已经提供了两个子类GenericServlet和HttpServlet,他们都是抽象类,并且HttpServlet是GenericServlet的子类。

  • 对于GenericServlet,继承时只需要复写Service方法,因为该类对于其他方法都已经做了默认的空实现,只有service方法还是抽象的:

Qa5R6x.png

这样做的好处就是不需要每次都对所有方法进行复写了,而只需要复写我们最经常使用的service方法即可。当然,其他的方法也不是不允许复写,想复写还是可以随意复写的

  • 对于HttpServlet,它实际上就是对HTTP协议的封装和描述。这个类出现的原因是:HTTP访问有get和post两种方式,两种方式的参数传递方法是不一样的(get直接接在URL后面,而post是放到数据包其他标签下的),所以获取数据的方式也不一样。如果我们直接使用service方法,就需要每次都去判断目前的访问是get还是post以获取传进来的参数,这显然是很麻烦的。而HttpServlet已经将判断的这个过程写入了service方法,并提供了doGet和doPost两个方法,我们继承这个类后直接在这两个方法下写业务代码就好了,不需要显式地复写service方法

    Qa5hnK.png

    其实提供的不只是doGet和doPost,只是这两个比较常用而已

    注意,通过浏览器直接访问,默认是get方式

Servlet的相关配置

  1. urlPattern

    它是url的访问路径,但注意它不是唯一的,可以为一个servlet定义多个访问路径

    路径配置规则:

    1. /xxx,至少要有一个/,当然也可以写多层
    2. *.<后缀>:只通过后缀名进行区分,这种前面不能加/
    3. /*,这种就是所有的都匹配。这种方式在匹配时优先级是最低的,只要有冲突都以其他的为先

HTTP in Servlet

连接复用(Http1.1)

和1.0不一样,1.1中有连接复用的设置:建立连接之后,当数据传输完成后连接不会马上断开,而是先等待一段时间。如果在这段时间内有其他数据再发送,就使用这个连接,并且传输完之后再等待

HTTP数据格式

请求消息:servletRequest

  1. 请求行

    请求方式 请求url 请求的协议/版本

    eg.GET /login.html HHTP/1.1

    请求方式有七种,常用的是get和post,他们的区别是:

    1. get的参数在请求行(就是请求行的url部分,用?将url和要传递的参数隔开)中,而post在请求体中
    2. get方式请求的url长度是有限制的,而post方式对于url长度是没有限制的,所以例如文件的上传,就只能用post方式
    3. get请求不安全,post相对安全点:说的是参数是否显式传输(其实一抓包都不安全)
  2. 请求头

    键值对格式,用冒号分割,值有多个的话用逗号分割

    Qa557D.png

    常见的请求头:

    1. host:表示请求的主机
    2. User-Agent:浏览器版本信息:用来解决浏览器兼容性问题(对资源的解析不一样,使得页面显示不一样)
    3. Accept:告诉服务器本机可以解析的文件类型
    4. Accept-Language:支持的语言环境,例如zh-CN、zh-TW、zh-HK、en-US等等
    5. Accept-Encoding:支持的压缩格式
    6. Refer:告诉服务器当前请求从哪里来。作用是:
      1. 防盗链:不是从我自己的网站来访问该资源的的就不返回该资源,例如图片、视频网站
      2. 统计:统计访问量,例如网站主页用来统计从各个搜索引擎引入的流量数量
    7. Connection:keep-alive表示连接在数据传输完成后不立即端口(连接复用)
    8. upgrade-Insecure-Request:关于这个参数,可以看这篇文章: https://blog.csdn.net/qq_33019839/article/details/101513886
  3. 请求空行

    就是一个空行,用来分割请求头和请求体

  4. 请求体

    GET方式没有请求体。

    POST方式的请求体中其实就是要传递的参数

ServletRequest

Request对象和Ressponse对象的原理
  1. tomcat服务器根据请求url中的资源路径创建对应的Servlet对象
  2. tomcat创建两个request和response对象,request对象封装请求消息数据(相当于数据预处理,这样当我们需要请求数据时直接从对象中调用就好了)
  3. tomcat将request和response对象作为参数传递给service方法,并且调用service方法
  4. 程序员对service进行编写,并且通过response对象设置响应消息数据
  5. tomcat将response取出响应消息数据,然后给浏览器做出响应
Request对象的继承体系结构

ServletRequest和HttpServletRequest是两个接口,HttpServletRequest是对ServletRequest的继承。而org.apache.catalina.connetcor.RequestFacade是对HttpServletRequest的实现类,由tomcat编写,并进行实例化、传递给service方法

Request对象的功能
  1. 获取请求消息数据

    1. 获取请求行

      • 获取请求方式:getMethod()

      • 获取虚拟目录(项目所在路径):getContextPath()

      • 获取Servlet路径:getServletPath()

      • 获取虚拟目录+Servlet路径(也就是URI):getRequestURI()\getRequestURL:

        ​ URL和URI的区别:

        来源: https://www.php.cn/div-tutorial-413616.html

        URI(统一资源标识符)是标识逻辑或物理资源的字符序列,与URL类似,也是一串字符。通过使用位置,名称或两者来标识Internet上的资源;它允许统一识别资源。

        有两种类型的URI,统一资源标识符(URL)和统一资源名称(URN), 可以说URL是URI的子集

        URL指定要使用的协议类型,而URI不涉及协议规范。

        例如对一个项目下的资源,用getRequestURI()得到的是"/test/demo1",而用getRequestURL()得到的就是"http://localhost/test/demo1"

      • 获取请求参数:getQueryString()

      • 获取协议和版本:getProtocol()

      • 获取客户机的ip地址:getRemoteAddr()

    2. 请求头

      • getHeader(String name):通过请求头的名称获取对应的值
      • Enumeration getHeaderNames():获取所有的请求头名称.注意这个返回值类型:一种迭代器类型,只有两个方法:hasMoreElements()判断此枚举是否包括更多元素、nextElement()获取下一个元素
    3. 请求体

      请求体重封装了Post请求的请求参数

      请求体对象被封装为流对象。

      1. 获取流对象:

        • 字符流(字符对象,例如表单上传):getReader()

        • 字节流(例如文件、图片、视频上传):getInputStream().注意字节流也可以用来处理字符流对象,只是一般没有人这么做

      2. 读取流对象

        • 字符流,readLine()
        • 字节流,例如read()
  2. 其他功能

    1. 获取请求参数的通用方式(指对于get方法和post方法都适用,可想而知比上面的方法要流行):

      • getParameter(String name),根据参数名称获取参数值
      • getParameterValues(String name):根据参数名称获取参数值的数组,多用于一些复选框
      • getParameterNames():获取所有请求的参数名称
      • Map<String,String[]>getParameterMap():获取所有参数名称和参数值的键值对
    2. 请求转发(forward):

      请求转发可以理解为在服务器内部的资源跳转方式,就是在servlet的内部再调用另外一个servlet,这样做的目的是降低程序的耦合度,不要把所有的功能都写在一个类里面,从而保持类的功能的单一、便于分工协作和降低出错

      步骤:

      1. 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
      2. 使用得到的转发器对象进行转发:forward(ServletRequest request,ServletResponse response),将request和response要传递进去。这样转发器对象就会创建目标Servlet类的对象并调用其service方法

      特点:和网页跳转相区分

      1. 资源路径(也就是浏览器地址栏中的路径)不发生变化
      2. 只能转发访问服务器内部的HTML、servlet等资源
      3. 客户机只对服务器发送一次请求
    3. 共享数据:

      在servlet之间进行数据通信,例如请求转发的servlet之间

      概念:域对象:一个有作用范围的对象,可以在范围内共享数据

      • request域:代表一次请求能达到的范围,一般用于请求转发的多个资源中共享数据

      方法:

      1. setAttribute(String name,Object obj):存储要共享的数据
      2. Object getAttribute(String name):通过键获取值
      3. removeAttribute(String name):通过键来移除键值对
    4. 获取ServletContext对象:

      ServletContext getServletContext()

      ServletContext对象

      它代表整个web应用,可以和程序的容器来通信

      获取:

      ​ 除了上面提到的、使用request对象的getServletContext()方法之外来获取ServletContext对象,我们还可以通过HttpServlet的this.getServletContext()方法来获取.

      功能:

      1. 获取MIME类型

        MIME类型是互联网通信过程中的定义的一种文件数据类型。例如HTTP就遵循了MIME类型的要求

        其格式为:大类型/小类型,例如我们之前用过的text/html,还有image/jpeg等

        我们可以通过获取MIME类型来设置response的context-Type

        获取方法:

        • getMimeType(String file)

        该方法实际上是通过获取文件的拓展名来返回MIME类型的:

        例如:服务器的web.xml中的配置文件中就设置了MIME的映射关系。(每个项目都有一个web.xml,但是服务器的web.xml是所有项目的web.xml的源文件

        Qa5oAe.png

      2. 域对象:共享数据

        方法:

        setAttrbute(String name,Object value)

        getAttribute(String name)

        removeAttribute(String name)

        范围:

        范围是整个web应用,是最大的域对象,可以共享同一个项目下所有用户、所有请求的数据。共享范围最大!共享数据的对象之间只需要在同一个web项目下即可、不需要任何关系

        所以,该方法使用起来要很谨慎:容易信息冲突、信息泄露、内存溢出(因为它的生命周期非常长,只有服务器关机才会结束,所以如果一直往里面写参数的话会一直驻留在内存中)

      3. 获取文件的真实路径(服务器路径)

        web项目会在本地工作空间和tomcat服务器下都存在一份。使用该对象,我们可以获取到项目文件在tomcat服务器下的真实路径:

        String getRealPath(String path)

        输入的path是相对路径,默认当前目录是web目录,也就是说如果是直接放在web目录下的文件,直接用/<文件名>就可以了,输出的RealPath就是在服务器上的绝对路径

  3. 常见问题

    1. 中文乱码问题;指在表单等输入中文后传递给服务器的是乱码。

      解决:对流对象设置编码:request.setCharacterEncoding("utf-8"),注意具体的编码格式要和HTML页面上说设置的保持一致

  4. BeanUtils工具类

    这个工具类的作用是什么?

    例如对一个注册页面,可能需要传递十几个参数。我们可以将这些参数对象化,从而在之后只传递对象即可,不需要直接传递参数。但是这就要求我们在每一个相关页面都要手动地创建这样的一个类、将其实例化并传入参数,这是很重复的步骤。

    一个更好的做法是使用getParameterMap()直接以map的形式获取所有参数,而BeanUtils的作用就是将map转换为我们所要的对象,也就是实现自动的数据封装.但是注意类的定义还是需要我们去做的,这个类需要满足javaBean的规范

    BeanUtils包可以直接下载或者使用maven等框架

    Map<String,String[]>map=req.getParameterMap();
    User loginUser=new User();//我们先自定义User类
    BeanUtils.populate(loginUser,map);//完事了
    

    相关内容:

    1. 什么是JavaBean?

      标准的、遵守规范的Java类:

      1. 类必须被public修饰
      2. 必须提供空参的构造器
      3. 成员变量必须使用private修饰
      4. 提供public修饰的setter和getter

      JavaBean一般放在domain、entity包下

    2. JavaBean的功能

      一般用来封装数据

    3. 属性和成员变量的区别

      属性,严格上说,指的是setter和getter方法截取后的产物。属性和成员变量的名称可以是不一样的,例如setName对应的属性是name,但是这个setter可以操作叫任意名字的成员变量,不一定叫name。

    4. BeanUtils相关的方法:

      1. setProperty(Object bean,String name,Object value):给java bean对象的特定属性赋值
      2. getProperty():相应的,就是获取java bean对象的特定属性值
      3. populate()

响应消息:servletResponse

响应消息的请求格式

  1. 响应行

    1. 组成:协议/版本 响应的状态码 状态码的描述

      例如: HTTP/1.1 200 OK

    2. 响应的状态码:服务器去告诉客户端浏览器本次请求和响应的一个状态。状态码都是三位数字,分为5类。

      分类:

      1. 1xx:服务器接收客户端消息,但是没有接收完成,等待一段时间后发送1xx状态码,表示处于等待状态

      2. 2xx:表示成功:200

      3. 3xx:表示重定向:302(重定向)、304(访问缓存)

        访问缓存:例如传输图片类的二进制文件,占用时间比较长。如果同一用户连续请求一个图片,并且在请求间隔中该图片没有发生变化,那么服务器就给浏览器发生304,让浏览器从自己的缓存中去找该图片而不是自己重新发送一遍

      4. 4xx:客户端错误(请求错误,比如请求不存在的资源:404;405:该请求方式不支持,例如没有写doGet或者doPost方法)

      5. 5xx:服务器端错误:例如500

  2. 响应头

    1. 格式: 头名称:值
    2. 常见的响应头
      1. Content-Type:服务器告诉浏览器本次的响应消息体的数据格式和编码格式,例如contentType="text/html;charset=UTF-8"
      2. Content-Length:响应体的长度(字节个数)
      3. Date
      4. Content-disposition:服务器告诉客户端以什么格式打开响应体数据,默认值是in-line(在当前页面内打开),可以修改为attachment;filename=xxx(以附件形式打开响应体,例如文件下载页面)
  3. 响应空行

  4. 响应体

    响应体就是我们要发送的数据,例如HTML的文本和图片、视频的二进制文件

Response对象

功能:设置响应消息

  1. 设置响应行

    1. 设置状态码:setStatus(int sc)
  2. 设置响应头

    setHeader(String name,String value)

  3. 设置响应体

    同样是通过流的方式进行传输

    1. 获取输出流对象

      • 字符输出流PrintWriter getWriter()

      • 字节输出流ServletOutputStream getOutputStream()

    2. 使用输出流输出数据

案例

  1. 完成重定向(redirect)

    • 完成重定向的步骤

      设置状态码返回302

      设置响应头location:要转发的资源的路径

    • 重定向和请求转发(forward)的区别:

      1. 浏览器访问路径,即地址栏路径发生变化
      2. 不仅可以访问当前服务器资源,也可以访问外网资源
      3. 重定向需要由浏览器进行两次访问,不能使用request对象共享数据:因为是两次访问,是两个不一样的request对象
    • 代码

      Servlet.java

      @WebServlet("/Servlet")
      public class Servlet extends HttpServlet {
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              System.out.println("Servlet1被访问了");
              //通过重定向,访问Servlet2
              //1.设置状态码为302
              response.setStatus(302);
              //2.设置响应头location为Servlet2
              response.setHeader("location","/ServletTest/Servlet2");
          }
      
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doPost(request,response);
          }
      }
      

      Servlet2.java

      @WebServlet("/Servlet2")
      public class Servlet2 extends HttpServlet {
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              System.out.println("Servlet2被访问了");
          }
      
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doPost(request,response);
          }
      }
      

      上面是一个手写的方式,但是由于重定向的步骤也是很固定的,所以更加简单的方法已经被创建出来了

      Servlet.java:

      @WebServlet("/Servlet")
      public class Servlet extends HttpServlet {
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              System.out.println("Servlet1被访问了");
              //简化操作的重定向
              response.sendRedirect("/ServletTest/Servlet2");
          }
      
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doPost(request,response);
          }
      }
      
  2. 服务器输出字符数据到浏览器

    1. 步骤

      1. 获取字符输出流

        注意,我们不需要在传输完成后清空流,因为这个输出流是由response获取的,它在response传输完成后就会被销毁,不需要手动清空

      2. 输出数据

    2. 代码

      protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              System.out.println("Servlet2被访问了");
      
              //如果不指定使用的字符集,输出中文会造成乱码,因为输出流的默认编码是ISO编码
              //【注意,设置流对象的编码要在获取之前!】
              //response.setCharacterEncoding("GBK");
              //但是我们不能提前知道客户浏览器默认使用的是什么字符集,所以我们需要:
              // 【通过设置响应消息头,告诉浏览器服务器发送的消息体数据的编码,建议浏览器使用该编码进行解码】
              //text/html表示文本类型是text下的html类型
              response.setHeader("content-Type","text/html;charset=UTF-8");
              //注意,设置了content-Type之后,服务器传输的字符串类型也随之确定下来了,所以我们就不需要再设置response.setCharacterEncoding了
              
              //1.获取字符输出流
              PrintWriter pw=response.getWriter();
              //2.输出数据,其实它就是响应的消息体
              //pw.write("Hello,world!");
              //写HTML标签也可以
              pw.write("<h1>Test head</h1>");
              //如果不指定使用的字符集,输出中文会造成乱码,因为输出流的默认编码是ISO编码
              pw.write("你好");
          }
      

      同样的,response对象中也有方法可以简单地进行字符集设置:

       response.setContentType("text/html;charset=utf-8");
      

      为了避免可能的乱码,建议在获取所有的字符输出流之前都进行编码的设置

  3. 服务器输出字节数据到浏览器

    和输出字符数据差别不大

    代码:

        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("Servlet2被访问了");
            response.setContentType("text/html;charset=utf-8");
            ServletOutputStream outputStream = response.getOutputStream();
            //要将字符串转换为字节数组的形式
            outputStream.write("hello".getBytes());
            //输出中文同样有字符集的问题,这里我们要设置一下转换为字符数组时候的编码
            outputStream.write("您好".getBytes("utf-8"));
        }
    
  4. 验证码案例

    在验证码案例中,我们也会学到java绘图的方法

    验证码是为了防止恶意表单注册

    • 验证码是如何生成的?

      1. 保存海量验证码
      2. 动态生成验证码

      显然第二种方法更好

    • 我们这里要生成的验证码效果:

      Qa5211.png

      代码:

      protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              int width=100;
              int height=50;
              ServletOutputStream outputStream = response.getOutputStream();
              /*
              生成验证码
               */
              //1.创建在内存中验证码图片对象
              BufferedImage img=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
              //2.绘制验证码
              //2.1获取画笔对象
              Graphics g = img.getGraphics();
              //2.2使用画笔绘制验证码
              g.setColor(Color.PINK);//设置画笔颜色
              g.fillRect(0,0,width,height);//填充背景色
              g.setColor(Color.BLUE);
              g.drawRect(0,0,width-1,height-1);//绘制边框,注意边框的开始位置是width-1(如果边框宽度是1的话)
              String str="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567989";//验证码的字符
              for(int i=0;i<4;i++){
                  //创建随机数
                  Random ran=new Random();
                  int index = ran.nextInt(str.length());
                  g.drawString(String.valueOf(str.charAt(index)),20+20*i,25);//绘制验证码的字符
              }
              //绘制干扰线
              g.setColor(Color.green);
              //随机生成坐标点
              Random ran=new Random();
              int times=ran.nextInt(3)+3;
              for(int i=0;i<times;i++){
                  int xBegin=ran.nextInt(width);
                  int xEnd=ran.nextInt(width);
                  int yBegin=ran.nextInt(height);
                  int yEnd=ran.nextInt(height);
                  g.drawLine(xBegin,xEnd,yBegin,yEnd);
              }
              //3.输出验证码
              ImageIO.setUseCache(false);
              //为什么要写这句话呢?看这篇文章:https://blog.csdn.net/cwfreebird/article/details/51820993
              //究其原因应该是没有temp文件夹所致,所以这里换成使用内存缓存
              ImageIO.write(img,"jpg",outputStream);
          }
      
posted @ 2019-12-08 19:47  别再闹了  阅读(501)  评论(0编辑  收藏  举报