• web概述12


    Servlet

    Servlet是一个用于扩展服务器端功能的服务器端组件技术

    • 运行在服务器端,所以调用执行是由服务器负责
      • 在web.xml中需要进行配置,就是将servlet和一个请求地址建立对应关系,当浏览器对地址发起请求时,服务器则按照规则调用serlvet类中的方法
    • 服务器端的组件技术
      • 直接或者间接的实现Servlet接口
    • 用于扩展服务器端功能,可以实现动态网页的开发

    Servlet接口

    1、定义类实现Servlet接口

    public class HelloServlet implements Servlet {
    //在Servlet类实例化后自动执行的方法,这个方法在servlet对象的整个生命周期中运行且只运
    行一次。主要用于实现Servlet的初始化操作,例如读取配置文件中当前Servlet的初始化配置信息。
    当服务器调用该方法时会将当前servlet的初始化配置存储在cnfig对象中
    public void init(ServletConfig config) throws ServletException {
    this.config=config; //缓存config对象
    }
    //用于供其它位置获取config对象时进行调用
    private ServletConfig config;
    public ServletConfig getServletConfig() {
    return config;
    }
    //在init方法执行后,服务器调用service方法用于生成针对客户端的响应信息。服务器采用多
    线程的方式运行service方法,一个客户端请求对应一个线程。服务器调用service方法时会传入2个参
    数对象,req用于封装客户端的请求信息,resp用于封装服务器的响应信息。Servlet默认采用单实例
    多线程的方式对客户端浏览器请求提供服务,service执行完成后不会自动销毁,而是常驻内存
    public void service(ServletRequest req, ServletResponse resp) throws
    ServletException, IOException {
    }
    //一般供获取当前servlet对象的说明信息
    public String getServletInfo() {
    return "当前servlet对象的说明信息";
    }
    //在servlet对象销毁之前执行,用于进行资源回收。一个servlet对象在整个生命周期运行且只
    运行一次。servlet对象默认是常驻内存,只有在服务器关闭或者内存严重不足而当前Servlet对象被
    GC机制选中时才会被销毁
    public void destroy() {
    }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2、在web.xml中针对servlet类进行配置,将servlet和一个或者多个地址建立对应关系

    
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
    http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    id="WebApp_ID" version="4.0"> xml文件头用于说明当前xml文件的语法规则
    <servlet> 将servlet类和一个名称建立对应关系
    <servlet-name>helloservlet-name>
    <servlet-class>com.yan.action.HelloServletservlet-class>
    servlet>
    <servlet-mapping> 将一个servlet名称和请求地址建立对应关系
    <servlet-name>helloservlet-name>
    <url-pattern>/hello.dourl-pattern>
    servlet-mapping>
    web-app>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    servlet三生命周期

    • init
    • service
      • 单实例多线程的方式对外提供服务
      • 常驻内存
    • detroy

    Servlet开发

    • 直接实现Servlet接口
    • 抽象类GenericServlet用于非标准协议开发,例如游戏的服务器端
    • 抽象类HttpServlet是GenericServlet的子类,用于http协议应用开发,例如网站
    HttpServlet的运行原理
    public abstract class HttpServlet extends GenericServlet,针对Servlet接口的5种
    方法都提供了默认实现
    //按照Servlet的运行规则,服务器会调用service方法对外提供服务。
    protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    String method = req.getMethod(); //获取请求方法,http协议定义了7种请求,
    例如get/post/put/delete,...
    if (method.equals(METHOD_GET)) { //如果请求方法为GET,则调用doGet方法进
    行处理
    doGet(req, resp);
    } else if (method.equals(METHOD_HEAD)) {
    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 {
    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);
    }
    }
    //针对不同的请求方法提供了对应的扩展点,运行子类进行覆盖定义
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    String protocol = req.getProtocol(); //获取请求协议,例如http
    String msg = lStrings.getString("http.method_get_not_supported");
    //根据参数获取默认的报错信息
    if (protocol.endsWith("1.1")) { 判断http协议的版本号,发送报错信息
    resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
    } else {
    resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    Servlet初始化配置

    在服务器调用Servlet的 init方法时会传入一个ServletConfig对象,其中包含当前Servlet的所有初始化配置

    public void init(ServletConfig config) throws ServletException {}
    //GenericServlet中的方法实现
    public void init(ServletConfig config) throws ServletException {
    this.config = config; //缓存服务器提供的config对象
    this.init(); //提供扩展点
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    配置位于web.xml文件

    <servlet>
    <servlet-name>hello3servlet-name>
    <servlet-class>com.yan.action.Hello3Servletservlet-class>
    <init-param> 初始化参数,可以配置多个
    <param-name>rowsparam-name> 配置的参数名称
    <param-value>3param-value> 对应配置参数名称的配置值
    init-param>
    <init-param>
    <param-name>widthparam-name>
    <param-value>120param-value>
    init-param>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在servlet中获取配置参数
    可以在GenericServlet提供的扩展点中获取配置参数

    @Override
    public void init() throws ServletException {
    //获取对应名称的初始化参数值,如果没有配置则返回为null
    //这个获取初始化参数的方法也可以在其它方法中进行调用,从而获取配置参数
    String rows = this.getServletConfig().getInitParameter("rows");
    System.out.println(rows);
    //获取当前servlet的配置名称,对应web.xml中的
    name>hello3</servlet-name>
    System.out.println(
    this.getServletConfig().getServletName()
    ):
    //获取所有当前servlet的初始化配置参数名称的迭代器,类似Iterator
    Enumeration<String> initParameterNames =
    this.getServletConfig().getInitParameterNames();
    while(initParameterNames.hasMoreElements()){
    String name=initParameterNames.nextElement(); //获取每个初始化配置
    参数名称
    String value=this.getServletConfig().getInitParameter(name);//根
    据配置参数名称读取对应的参数配置值
    System.out.println(name+"-->"+value);
    }
    //可以通过config对象获取当前应用的上下文
    final ServletContext context =
    this.getServletConfig().getServletContext();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    接收客户端请求

    • request对象中封装的有客户端的请求参数信息
      • getParamter(" 参数名称"):String
      • getParameterValues(“参数名称”):String[]
      • getParameterMap():Map
    • request对象是HttpServletRequest接口类型,由服务器提供的实现,服务器在调用service方法时传入
    • request中的数据有3个不同的部分,请求头信息header【浏览器发送的请求信息】、请求参数parameter【用户发送的相关参数信息】、属性信息attribute【一般用于编程中的数据传递】,前两个只是用于读取,并没有提供修改方法,属性信息提供了读写操作方法
    • 特殊方法
      • getReader()或者getInputStream()获取输入流,直接操作流

    请求头

    一般数据是传送给服务器使用的,不是给应用直接使用的。只有在特殊需求时使用,例如收集客户端所
    使用的浏览器信息【User-Agent】

    public class AddServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse
    response) throws ServletException, IOException {
    // 操作请求头参数
    final String host = request.getHeader("Host"); // 根据名称获取请求头中指
    定的参数值
    System.out.println(host);//localhost:80
    //另外根据发送数据的类型 request.getIntHeader("名称"):Int
    request.getDateHeader():long
    //获取所有的请求头参数名称的枚举对象
    final Enumeration<String> headerNames = request.getHeaderNames();
    while(headerNames.hasMoreElements()){
    String headerName=headerNames.nextElement();
    String headerValue=request.getHeader(headerName);
    System.out.println(headerName+"-->"+headerValue);
    }
    String name="Accept-Encoding"; //遍历一个名称对应的多个配置值的情形
    final Enumeration<String> enumeration = request.getHeaders("Accept-
    Encoding");//请求头中一个名称对应多个参数值
    while(enumeration.hasMoreElements()){
    String tmp=enumeration.nextElement();
    System.out.println(name+"<-->"+tmp);
    }
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    请求参数请求参数

    无论是get请求还是post请求,获取请求参数的方法一致,只是编程时需要注意对应的doXxx方法

    • 如果针对请求没有对应的doXxx方法,则会从父类中继承得到默认实现。实现是报错405
    • 如果get请求和post请求处理逻辑一致,则需要自行编码实现
    public class AddServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse
    response) throws ServletException, IOException {
    this.doPost(request,response); //将get请求转发给doPost进行处理
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse
    resp) throws ServletException, IOException {
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    获取请求参数数据的方法
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse
    resp) throws ServletException, IOException {
    //根据参数名称获取对应的参数值
    String username=request.getParameter("username");//在网络数据传输中只有
    String类型,其它类型需要自行编码实现数据类型转换
    //因为DateFormat不是线程安全的,所以DateFormat不能定义为属性
    System.out.println(username);
    //获取用于遍历所有请求参数名称的迭代器对象
    final Enumeration<String> parameterNames =
    request.getParameterNames();
    while(parameterNames.hasMoreElements()){
    String parameterName=parameterNames.nextElement();
    String parameterValue=request.getParameter(parameterName);
    System.out.println(parameterName+"-->"+parameterValue);
    }
    //获取所有请求参数
    final Map<String, String[]> parameterMap =
    request.getParameterMap();
    for(String tmpName:parameterMap.keySet()){
    String[] params=parameterMap.get(tmpName);
    System.out.println(tmpName+"<===>"+ Arrays.toString(params
    ));
    }
    //针对checkbox获取数据的方法
    final String[] hobbies = request.getParameterValues("hobby");
    for(String tmp:hobbies)
    System.out.println(tmp);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    针对一个名称传递多个数据的情形
    <form action="add.do" method="post">
    用户名称:<input name="username"/><br/>
    <!-- checkbox复选框允许用户选中多个,name是提交参数名称,value是选中这个选项的提交
    值,checked默认选中状态 -->
    用户爱好:<input type="checkbox" name="hobby" value="篮球">篮球
    <input type="checkbox" name="hobby" value="111">足球
    <input type="checkbox" name="hobby" value="222" checked>乒乓球
    <input type="checkbox" name="hobby" value="333">其它<br/>
    <input type="submit" value="提交数据"/>
    </form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    针对checkbox使用一个名称传递多个数据则必须使用getParameterValues获取数据,如果使用getParameter则会有数据丢失

    //针对checkbox获取数据的方法
    final String[] hobbies = request.getParameterValues("hobby");
    for(String tmp:hobbies)
    System.out.println(tmp)
    
    • 1
    • 2
    • 3
    • 4

    常见问题1:类型转换

    数据传输都是String类型获取数据后需要进行类型转换,但是转换会有异常
    实际处理中有2种不同的处理方案:方案1记录日志并继续向上抛出;方案2采用默认值

    String sage=request.getParameter("age");
    Integer age=null;
    try {
    age = Integer.parseInt(sage); //NumberFormatException运行时异常
    } catch(Exception e){
    //记录日志,如果需要程序中断,可以不做异常处理,因为NumberFormatException运行时异常
    throw new ServletException(e);
    age=18;//设定默认值。不是任何时候都可以采用自定义默认值的方式进行容错
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    常见问题2:一个名字对应多个数据的存储和显示

    MySQL中并不建议使用enum枚举类型和set集合

    多个选项的存储方案:

    -- 一个专门用于存放各种常量的独立的表,字典表
    create table tb_dict(
    id bigint primary key auto_increment comment '代理主键',
    type_ varchar(32) not null comment '用于标识数据的具体类型,例如sex表示用于性别
    信息',
    val_ varchar(20) not null comment '显示的数据'
    -- key_ comment '存放具体提交数据'
    )engine=innodb default charset utf8;
    insert into tb_dict(type_,val_) values('sex','男');
    insert into tb_dict(type_,val_) values('sex','女');
    -- 注意具体存储对应数据时可以采用id编号值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    具体入库数据

    一种直接存储选项的显示值,优势在于方便显示
    另外的存储方式存储的时字典表中对应的id值,优势在于方便修改,最大的缺陷在于额外查询

    请求参数的中文问题
    为了重现问题使用tomcat7插件

    <plugin>
    <groupId>org.apache.tomcat.mavengroupId>
    <artifactId>tomcat7-maven-pluginartifactId>
    <version>2.2version>
    <configuration>
    <path>/pppath> 上下文路径名称,也就是访问应用的路径为/pp
    <port>9090port> 端口号的配置,localhost:9090/pp/
    <uriEncoding>UTF-8uriEncoding> 默认的编码字符集
    configuration>
    plugin>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    因为在整个数据提交过程中会涉及多处编码字符集问题,1、提交所采用的编码字符集。
    2、在互联网上传输数据默认编码字符集为ISO-8859-1。
    3、服务器接收数据后的编码处理

    请求参数的中文乱码问题有3种解决方案

    • 针对get请求,在具体应用中基本不可用

    以tomcat为例的解决方案: conf/server.xml
    需要掌握的配置参数1:修改请求的端口号,默认端口8080

    <Connector port="8080连接端口号,默认http端口号为80,https默认443"
    protocol="所支持的协议HTTP/1.1" connectionTimeout="20000连接超时时长,默
    认单位为ms" redirectPort="8443重定向端口号" />
    
    • 1
    • 2
    • 3

    针对连接子 添加一个配置参数URIEncoding=“GBK”,这里的GBK来自于页面编码字符集

    • 针对post请求

    在所有的接收请求参数方法执行之前,先调用方法 request.setCharacterEncoding(“UTF-
    8”); ,注意UTF-8就是页面提交所采用的编码字符集

    • 通用解决方案
    //根据参数名称获取对应的参数值
    String username=request.getParameter("username");//在网络数据传输中只有
    String类型,其它类型需要自行编码实现数据类型转换
    //使用ISO8859-1进行解码转换,转换为原始的字节数组
    byte[] arr=username.getBytes(StandardCharsets.ISO_8859_1);
    //再次使用UTF-8进行编码
    username=new String(arr, Charset.forName("UTF-8"));
    System.out.println(username
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    理论上来说采用通用解决方案可以处理get和post的中文乱码问题,但是一般不采用这种方式,因为处理比较繁琐。具体开发实践中注意一些小细节即可,不用通用解决方案

    • 一般不采用get提供中文,如果需要传递数据有限考虑采用代码值之类的方式。例如查询教学部的所有雇员,并不传递中文的【教学部】参数,而是传递教学部对应的编号值
    • 提交数据的html页面编码字符集统一为UTF-8
      如果需要传递中文信息,可以采用
      提交数据,在所有接收数据request.getPamrameter 之前先进行编码转换request.setCharacterEncoding(“UTF-8”);

    属性操作

    request的属性操作就是操作attribute,主要用于实现在两个页面之间之间使用编程的方式进行数据传

    //向request的attribute种存储数据,如果名称已经存在则后盖前,否则新增
    request.setAttribute("name","具体数据Object");
    //从request的attribute中获取数据
    Object obj=request.getAttribute("name");
    //按照key值删除指定的数据
    request.removeAttribute("name");
    //遍历attribute中的所有数据对应的key值
    Enumeration<String> attributeNames = request.getAttributeNames();
    while(attributeNames.hasMoreElements()){
    String attrName=attributeNames.nextElement();
    Object attrValue=request.getAttribute(attrName);
    System.out.println(attrName+"<-->"+attrValue)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    请求转发和动态包含
    • 请求转发 request.getRequestDispatcher(“新页面地址”).forward(request,response);
    • 动态包含 request.getRequestDispatcher(“新页面地址”).include(request,response);
      转发或者包含的地址要求:
    • 地址应该位于当前应用范围内,不能是其它应用中地址
    request.getRequestDispatcher("https://www.baidu.com").forward(request,resp
    onse); 执行会有报错 HTTP Status 404 - /pp/https:/www.baidu.com ,这是在请求转发
    时会在地址上自动附加当前应用的路径 http://localhost:9090/pp/
    
    • 1
    • 2
    • 3
    • 允许在请求转发的地址中包含额外附加的请求参数
    request.getRequestDispatcher("add2.do?id=100").forward(request,response);
    请求当前页面的参数仍旧存在。如果参数名称一致,则时附加,也就是名称相同的多个参数
    如果昂前页面的请求方法为post,请求转发请求方法仍旧为post
    
    • 1
    • 2
    • 3
    • 除了parameter还可以使用attribute传递数据

    原因是共享request

    其它方法

    获取客户端的地址

    • request.getRemoteAddr():String
    //获取远程客户端的IP地址
    System.out.println(request.getRemoteAddr());
    stem.out.println(request.getRemoteHost());
    //获取远程客户端的连接端口号
    System.out.println(request.getRemotePort());
    //获取服务器的IP地址
    System.out.println(request.getLocalAddr());
    //获取本地机的主机名称
    System.out.println(request.getLocalName());
    //获取本地机的连接端口号
    System.out.println(request.getLocalPort());
    //获取服务器的信息
    System.out.println(request.getServerName());
    System.out.println(request.getServerPort()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    问题:IP地址定位

    问题:IP地址定位
    IPv4的地址采用的是4位点分十进制的计法,例如192.168.123.4。如果需要进行范围判断,建议将4位点分十进制的计法的字符串当作一个256进制的整数,可以转换为long类型数据进行存储

    String ss="192.168.123.4";
    long res=0;
    String[] arr=ss.split("[.]");
    for(int i=0;i<arr.length;i++){
    int kk=Integer.parseInt(arr[i]);
    res=res*256+kk;
    }
    System.out.println(res)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    获取请求地址相关信息

    //String统一资源标识符 /pp/add.do
    System.out.println(request.getRequestURI());
    //StringBuffer统一资源定位器 http://localhost:9090/pp/add.do
    System.out.println(request.getRequestURL());
    //获取上下文路径 /pp
    System.out.println(request.getContextPath());
    //获取请求参数 对应get请求时?号后面的内容,例如id=999。如果没有请求参数或者post参数则为
    null
    System.out.println(request.getQueryString(
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    流式处理
    • request.getReader():BufferredReader
    • request.getInputStream():ServletInputStream
      使用输入流的方式处理请求信息仅仅用于文件上传。具体的上传处理中一般建议优先考虑使用上传组件。例如 jspsmartupload和apache的commons-fileupload
      上传处理的要点
    <form method="post" enctype="multipart/form-data">
    <input type="file" name="pic"/>
    
    • 1
    • 2
    • 根据不同的上传组件接收提交数据
      • 一般默认上传文件的最大大小为2MB,如果文件超大一般建议使用ftp类型的工具,而不建议使用页面上传

    生成响应信息

    响应头
    重定向

    response.sendRedirect(“新地址”);
    重定向的地址要求:

    • 重定向的目标地址可以不是当前应用范围内的地址
    response.sendRedirect("https://www.baidu.com"); 正确
    
    • 1
    • 允许在重定向的地址中包含额外请求参数
    response.sendRedirect("add2.do?id=999"); 正确的地址。但是由于重定向是重新发起
    请求,所以不会共享request,以前的提交数据全部丢失
    由于不共享request,所以不能依赖于request传递数据
    
    • 1
    • 2
    • 3
    • 重定向的请求和当前请求的方法无关,新地址请求是Get
    输出流

    向response的输出流中写出数据,就是向客户端浏览器发送的数据
    response.getOutptutStream():ServletOutputStream 一般用于下载处理或者图片、声音、视频之类的二进制数据处理
    response.getWriter():PrintWriter 一般用于生成html文档
    文件下载处理的要点

    • Content-Disposition,对应值为attachment;filename=默认文件名称
    异常处理
    web页面之间的3种关系:包含,重定向和请求转发
    包含

    包含就是在一个页面的生成结果中包含另外一个页面。包含可以有静态包含和动态包含两种

    • 静态包含就是将被包含页的源代码先复制到包含页中,再编译执行

    page1.jsp被包含页

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    this is page one!<br/>
    <% int kk=100; %>
    
    • 1
    • 2
    • 3

    page2.jsp包含页,在page2中包含page1的内容

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    this is page two begin...<br/>
    <%@ include file="page1.jsp"%> 静态包含,先包含后编译执行
    this is page two end...<br/>
    <%=kk%> 如果没有包含page1则kk变量没有定义,语法错误
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 动态包含就是包含被包含页面的执行结果,并不包含源代码。两个页面实际上是各自独立运行的

    上面的编码方式则不可用,因为page2中的 kk没有定义而直接使用,语法错误

    page1.jsp被包含页

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    this is page one!<br/>
    <% int kk=100; %>
    
    • 1
    • 2
    • 3

    page2.jsp包含页,在page2中包含page1的独立执行结果,就是包含生成的html文档

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    this is page 3 begin...<br/>
    <jsp:include page="page1.jsp"/>
    this is page 3 end...<br/>
    <%=kk%> 编译失败,HTTP Status 500 - Unable to compile class for JSP
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在servlet中编程实现动态包含

    //动态包含page1.jsp页面
    request.getRequestDispatcher("page1.jsp").include(request,respon
    
    • 1
    • 2
    重定向和请求转发

    效果类似,可以从一个页面跳转到另外一个页面
    请求转发就是将请求的处理委托给另外一个页面进行处理;重定向就是重新向另外一个页面发起请求

    请求转发

    request.getRequestDispatcher("page1.jsp").forward(request,respons
    
    • 1

    重定向

    response.sendRedirect("show.do");
    
    • 1

    区别【重点】

    • 是否共享request,请求转发共享,重定向不共享
    • 客户端是否参与。请求转发客户端不参与,浏览器中的地址不会发生变化;重定向客户端参与,浏
      览器中的地址自动变化为新地址
    • 允许跳转的目标地址
      选用
    • 如果请求转发和重定向都可以的时候,优先使用请求转发,因为效率高
    • 如果需要使用request传递共享数据,则需要使用请求转发,否则使用重定向
    典型应用

    用户登录login.jsp

    <form action="login.do" method="post">
    <input name="username"/>
    <input name="password" type="password"/>
    <input type="submit" value="登录系统"/>
    </form>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在地址为login.do的servlet中接收数据

    public class LoginServlet extends HttpServlet {
    public void doPost(HttpServletRequest request,HttpServletResponse
    response)throws ServletException,IOException {
    String username=request.getParameter("username");
    String password=request.getParameter("password");
    //需要对用户提交的数据进行验证
    Map<String,String> errs=new HashMap<>();
    if(username==null || username.trim().length()<6){ //要求用户名称必须填
    写,并且长度不能少于6个字符
    errs.put("username","用户名称不合法!");
    }
    if(password==null || password.trim().length()<6){
    errs.put("password","用户口令不合法!");
    }
    //如果有报错信息则需要返回到输入页面中进行报错显示。报错信息可以使用request的
    attribute进行传递,同时使用请求转发保证实现页面的数据回填操作
    if(errs.size()>0){
    request.setAttribute("errors",errs);
    request.getRequestDispatcher("login.jsp").forward(request,response);
    return; //如果提前执行页面跳转,则必须紧邻return,以保证后续代码不执行
    }
    ... ...
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    修改login.jsp页面实现报错信息显示和数据回填操作

    <%
    Object obj=request.getAttribute("errors");
    if(obj!=null && obj instanceof Map){
    Map<String,String> map=(Map<String,String>)obj;
    if(map.containsKey("username")){ //判断保存报错信息的Map种是否有username对应
    的报错信息
    out.println(map.get("username"))
    }
    }
    %>
    如果用户输入的正确信息,则不应该要求用户重新输入,所以需要从request种获取上次提交的数据---
    数据回填操作
    <input name="username" value='<%=request.getParameter("username")%>'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    跟踪用户的4种方法
    • 隐藏域
    • URL重写
    • 客户端跟踪用户:Cookie
    • 服务器端跟踪用户:session
    会话对象

    session对应类型为HttpSession,用于实现服务器端跟踪用户。一般针对一个浏览器窗口,服务器会提供一个session对象,不管在这个浏览器窗口中以何种方式访问当前应用中的多少的页面,这些页面都共享同一个session对象

    获取session对象

    HttpSession session=request.getSession(true); //Boolean类型的参数,如果值为
    true,则表示有当前窗口对应的session对象,则获取并重用;如果没有则新建。值为false则有则重
    用,没有返回为null,不会自动新建
    HttpSession session=request.getSession(); //等价于getSession(true)
    
    • 1
    • 2
    • 3
    • 4

    操作数据

    setAttribute("名称",存放的值Object 存储或者修改session中存储的数据
    getAttribute("名称"):Object 获取存储在session中指定名称对应的数据
    removeAttribute("名称") 删除指定名称对应的数据
    getAttibuteNames():Enumeration<String>
    
    • 1
    • 2
    • 3
    • 4
    具体应用

    应用场景1:跨多个页面传递数据
    PicServlet负责生成图片

    String checkcode=this.generateCheckcode(6); //生成验证码
    HttpSession session=request.getSession();
    session.setAttribute("checkcode",checkcode);//在session中存储了验证码
    
    • 1
    • 2
    • 3

    LoginServlet中接收客户端输入的验证码,并和session中存储的验证码进行比对,以判断用户输入的验证码是否正确

    String checkcode=request.getParameter("checkcode");
    HttpSession session= request.getSession();
    if(checkcode==null || checkcode.trim().length()<1)
    errs.put("checkcode","必须输入校验码!");
    else{
    //如何判断验证码输入正确?要求PicServlet能够以某种方式将动态成成的验证码传递到
    LoginServlet
    Object obj=session.getAttribute("checkcode");
    if(obj!=null && !checkcode.endsWith(obj.toString()))
    errs.put("checkcode","验证码输入错误!");
    session.removeAttribute("checkcode");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    应用场景2:使用session标识当前用户状态,并跨页面数据传输

    boolean bb= userDao.login(user);
    if(bb){
    session.setAttribute("loginInfo",user); //用于标识当前窗口的用户已经登录成功
    //登录成功后,则没有必要继续向下传递login.jsp页面提交的用户名称和口令,所以考虑使用重
    定向
    response.sendRedirect("show.do");
    }else {
    //因为登录失败后返回原始的输入页面login.jsp,需要对用户输入的数据进行回填处理,供用户
    修改再次登录
    request.setAttribute("msg","登录失败!请重新登录");
    request.getRequestDispatcher("login.jsp").forward(request,response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在需要判断用户是否登录的servlet中首先判断用户的登录状态

    public class ShowServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse
    response) throws ServletException, IOException {
    HttpSession session = request.getSession();
    Object obj = session.getAttribute("loginInfo");
    if (obj == null) {
    session.setAttribute("msg","请登录后使用本系统");
    response.sendRedirect("login.do");
    } else {
    request.getRequestDispatcher("show.jsp").forward(request,
    response);
    }
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    应用场景3:内存购物车

    工作原理

    数据校验

    用户提交的数据在使用之前必须进行合法性验证

    • 客户端校验:可以使用 JavaScript 实现,没有网络数据传输,不会对服务器造成压力。安全性差,
      可以很方便的绕过
    • 服务器端校验:在接收客户端提交数据的组件中执行,使用 java 实现。基本校验规则和客户端校
      验一致,可以理解为客户端校验的服务器端再次执行。安全性高,除非黑了服务器否则不能绕过。有网络数据传输,会对服务器造成压力
      • 注意在具体开发中,除非时间充裕,否则开始时可以不在服务器端实现复杂验证。可以在功能
        开发完成后再完善
    • 业务校验:在调用业务组件的方法时执行的验证。使用 java 实现,执行的是和业务规则紧密相关
      的验证
  • 相关阅读:
    “轻松实现文件夹批量重命名:使用顺序编号批量改名“
    后流量时代的跨境风口:Facebook广告
    592. Fraction Addition and Subtraction
    通过nodeJS如何实现爬虫功能
    Kotlin设计模式:享元模式(Flyweight Pattern)
    【Docker】常用网址与常用命令
    跟着老杜学MyBatis+第4天+MyBatis核心配置文件详解
    预训练模型相对位置编码和绝对位置编码的通俗理解
    vue3中播放flv流视频,以及组件封装超全
    Spire.Office 7.8.4 for NET --2022-08-17
  • 原文地址:https://blog.csdn.net/weixin_44251806/article/details/127736356