zoukankan      html  css  js  c++  java
  • 关于JSP

    JSP的本质

    JSP本质上就是Servlet, 正常情况下, 它会在第一次被访问的时候被容器转化成Java代码, 然后再从Java代码编译成.class文件, 之后实际就和Servlet没区别了, 也就是多个请求也只有这一个实例, 最终仍然是通过多线程单实例的方式来完成任务. 所以其实就算你写的JSP代码有问题, 也只有在用户访问它的时候才能看到, 项目部署的时候是看不到的. 当然某些容器提供部署的同时完成代码编译加载的功能, 默认是不开启的.

    JSP中几种常见的元素

    JSP其实就是html中带了一些其他的元素以实现表现层(viewer)的功能, 下面是一些常见JSP元素 :

    1. Scriptlet : <%    %> : 里面可以写Java代码, 这里的Java代码在最后会被统一移到生产的类Servlet类的类似serveice的方法里面 :
    <% System.out.println(123); %>
    
    2. 表达式 : <%=   %> : 里面的内容将作为out.println()的参数, 被打印在页面上 :
    <%=
    config.getInitParameter("name")
    %>
    3. 声明 : <%! %> : 里面可以声明类的属性和方法, 用来弥补scriptlet中声明变量的均为局部变量的缺陷 
    <%!
            private static int x = 3;
    
            private void print(){
                System.out.println(123);
            }
    %>
    
    4. 指令 : <%@   %> : 向容器提供一些特殊的指示 :
    这里例子是page指令的import 和 contentType属性, 还有其他属性, 同时其实指令还有include和taglib, 之后有机会再提.
    <%@ page contentType="text/html;charset=UTF-8" language="java" %> 
    <%@ page import="java.util.LinkedList" %>  : 导入你需要在这个Jsp/Servlet 里面使用的包
    

    JSP中的Servlet

    上面说到JSP本质上就是Servlet, 那么也就是Servlet里面的一些东西它也有 :

    隐式对象

    在Scriptlet内部的一些常用的隐式对象(这里要注意的是只能在Scriptlet内部也就是那个类service方法的内部使用, 因为这些变量实际上都是在其内部定义的) :

    1. out : Servlet中需要通过resp.getWriter()来获得.
    2. request, response : Servlet中作为doGet/doPost/... 等方法的参数传入.
    3. session : 即Servlet中的HttpSession, 在Servlet通过req.getSession()来获得.
    4. application : Servlet中通过getServletContext()来获得
    5. config : Servlet中通过getServletConfig()来获得. (没错, JSP也可以设置初始化参数, 具体设置方法在后面提到)
    6. pageContext : 里面包含了其他隐式对象的引用

    可以覆盖的方法

    下面是可以覆盖的几个方法, 其作用和Servlet相同就不多说了 :

    <%!
        @Override
        public void jspInit(){
    
        }
    
        @Override
        public void jspDestroy(){
    
        }
    
    %>
    

    JSP初始化参数的设置

    然后来看看JSP如何设置初始化参数 :

        <servlet>
            <servlet-name>indexJsp</servlet-name>
            <jsp-file>/test.jsp</jsp-file>
    
            <init-param>
                <param-name>name</param-name>
                <param-value>index.jsp</param-value>
            </init-param>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>indexJsp</servlet-name>
            <!--<url-pattern>/xixi</url-pattern>-->
            <url-pattern>/test.jsp</url-pattern>
        </servlet-mapping>
    

    可以看到其实和Servlet大同小异, 要注意的是, 这里如果mapping中url-pattern不用jsp的实际路径而是使用虚拟路径的话, 会出现一个问题, 如果用户知道真实的jsp的路径, 他仍然可以访问, 但是那个jsp并不会获得初始化参数. 必须要使用url-pattern对应的路径才能获得初始化参数. 由于行为逻辑基本与Servlet相同, 所以在执行jspInit的时候已经可以使用getServletConfig()来获得初始化参数了.

    属性的设置以及获取 :

    类似于Servlet, 我们可以通过以下隐式对象来设置属性 :

    1. application 对应 ServletContext
    2. session 对应 HttpSession
    3. request 对应 HttpServletRequest
    4. 这里多了一个pageContext 也可以用来设置属性, 它设置的属性只在该page内共享, 但它也能设置/查找上面三者的属性, 具体规则就不表了.

    JSP的转折

    令人遗憾的是, 上面所说的大部分JSP元素, 实际上都是不推荐使用的 : 例如Scriptlet, 表达式 和声明(这三者均属于脚本代码), 因为这些东西使得Web的前后端之间无法很好地分离, 甚至在web.xml中存在禁用这三种元素的标记 :

        <jsp-config>
            <jsp-property-group>
                <url-pattern>*.jsp</url-pattern>
                <scripting-invalid>true</scripting-invalid>
            </jsp-property-group>
        </jsp-config>
    

    一旦设置这个标记之后, 服务器部署正常, 但是一旦访问使用了上述元素的JSP网页, 网页直接HTTP 500. 那我们该使用什么呢? 答案是标准动作 + EL(expression language) + JSTL标记...

    标准动作

    首先要对bean有个基本的认识, 如果没有认识百度一下吧, 这里我个人由于接触的比较多就不提了, 由于在Java中bean没有在语言层面上实现所以有点麻烦. 这里我使用的Student类的源代码 :

    package beanPackage;
    
    public class Student {
        private int age;
        private String name;
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    JSP中总共有三个bean标准动作 :

    1.   <jsp:useBean id="s" class="beanPackage.Student" scope="application" /> 这里scope指的是查找的范围, 默认为page, 可以设置为application, session, request, page分别对应上面提到的jsp中的4中属性设置的方式, 而你属性设置中其实是一个键值对, 这里的键就对应该标签中的id, 由于设置的属性均为Object, 所以还需要有class来识别其class, 而且这里的class必须使用完全限制名, 在上面使用page指令的import属性导入模块是无效的. 要注意的是, 这里之所以要这三个属性, 实际上是因为最终JSP还是要转化为Java代码的. 这句话也就会转化为类似 : beanPackage.Student s = (beanPackage.Student)application.getAttribute("s")的话.
    2.   <jsp:getProperty name="s" property="name"/> 这里的s对应上面已经获取到的Student的id, property对应Student中的name属性. 这句话会在html中打印这个student的名字.
    3.   <jsp:setProperty name="s" property="age" value="21"/> 这里s对应上面获取到的Student的id, age对应Student中的age属性. 这句话会将这个Student实例的age改为21.
    

    附加 :

    关于useBean

    实际上useBean总是会返回一个bean对象, 就用上面的例子来说<jsp:useBean id="s" class="beanPackage.Student" scope="application" />, 如果在application内没有这个Student, 那么它会利用bean的无参数构造函数自动构造一个然后添加到application当中. 基于这个特性, 实际上这个标签的还可以写为 :

      <jsp:useBean id="s" class="beanPackage.Student">
          <jsp:setProperty name="s" property="name" value="Fred" />
          <jsp:setProperty name="s" property="age" value="21" />
      </jsp:useBean>
    

    useBean内部的内容只有当没有在作用域内找到该对象时才会执行.

    实际上jsp:useBean之中还可以设置type, 例如你想到生成的对象是Human s = new Student()的时候, 那么这句话可以写为

    <jsp:useBean id="s" type="beanPackage.Human" class="beanPackage.Student" scope="application" />
    

    另外如果只有type没有class, 此时如果能在作用域内找到对象, 那么这个对象的引用将是type中指定的类型, 如果不能找到, 直接报错.

    关于setProperty

    实际上setProperty中还可以设置param :

      <jsp:useBean id="k" class="beanPackage.Student">
          <jsp:setProperty name="k" property="name" param="name" />
          <jsp:setProperty name="k" property="age" param="age" />
      </jsp:useBean>
    

    这个param能直接从request的参数中寻找名字为name和age的参数并且赋值给我们此处的Student实例. 更牛逼的是, 如果你request参数的名字和你实际Property的名字相同的话, 就比如上述例子中, 则可以直接省略param :

      <jsp:useBean id="k" class="beanPackage.Student">
          <jsp:setProperty name="k" property="name" />
          <jsp:setProperty name="k" property="age" />
      </jsp:useBean>
    

    当然最牛逼的还是这个, 使用*, 这种情况下容器会自动迭代所有request中的参数来尝试匹配你的bean的property, 并设置为对应的值.

      <jsp:useBean id="k" class="beanPackage.Student">
          <jsp:setProperty name="k" property="*" />
      </jsp:useBean>
    

    其实这里还有一点, 我的age的类型是int, 但是request传入的所有的参数值都为String, 但是仍然能够匹配, 这是因为容器提供了基本类型和String的自动转换. 另外如果我们实例的类有父类, 父类的属性也会得到匹配.

    EL

    EL在JSP 2.0 已经成为规范的一部分, 它有自己的语法, 属于另外一类JSP元素. 当然也有忽略EL的方法, 这里不表. 有了标准动作为什么需要EL呢? 就一个很简单的例子, 比如上面的Student有一个性质是Book, 我们想要显示这个Book的name :

    <jsp:getProperty name="k" property="book"/>   --> 会打印 book.toString
    

    这时候如果用el的话就是 :

    ${k.book.name}
    

    el表达式实际上很简单 : 任何语句都包含在${}当中, 然后可以用.[]来取值, 用点时, 左边只能是map或者JSP四个作用域中的某个作用域的属性(某个bean), 而右边必须是map的键或者是bean的Property. []则使用空间更大, 左边可以放map, bean, list 和 数组. 这里你可以直接用index来获取list或者array中的某个值. 例如${list[1]} 或者 ${list["1"]}, 这两者是一样的, 最终都会转化为1, 然后寻找list中的第二个. 如果是在四个作用域内已经设置过的属性(某个bean), 实际上并不需要使用xxxScope.xxxBean.xxxProperty或者是xxxScope["xxxBean"].xxxProperty, 直接xxxBean.xxxProperty即可. 查找顺序是 page, request, session, context依次查找.

    el中的隐式对象 :

    1. pageScope, requestScope, sessionScope, applicationScope这4个分别对应4个作用域下的保存属性的map
    2. param, paramValues 这是http请求参数的map, 后者用于存在多个同名参数情况.
    3. header, headValues 这是请求首部的map
    4. cookie 关于cookie的map, Servlet提供的是一个数组, 所以这里比Servlet要方便.
    5. initParam 这个不是Servlet的初始化参数而是context的初始化参数, 也就是整个应用的初始化参数的map.
    6. pageContext 这个不是map, 这个和上面scriptlet中那个一样. 可以用这个访问Servlet的初始化参数 : ${pageContext.servletConfig.getInitParameter("name")}

    这里还有内容暂时不提, 还有el函数和jstl没有提, 先留个坑, 以后有时间再填...

    JSP中的包含与转发

    JSP中有两种包含方式 :

    1. <%@ include  file="the_jspFile_path"%> : 这种方式的包含实际上发生在jsp被转换成java代码之前, 也就是说, 将被包含的jsp直接替换到该语句的位置, 然后将形成的新的的jsp转化为java代码, 然后编译加载并提供服务. 所以这种包含也称之为静态包含.
    
    2. <jsp:include page="the_jspFile_path" /> : 这种方式的包含实际上不算包含, 当请求来临时, 主jsp文件和被包含jsp文件各自被转换编译加载后, 由主jsp文件形成的类运行时调用被包含jsp文件形成的类, 个人感觉可以理解为请求被包含的jsp文件形成的类在运行时帮助主jsp文件形成的类完成一部分页面的构造工作. 所以这种包含也称之为动态包含. 这种包含还可以有参数.
    
    <jsp:include page="the_jspFile_path"><jsp:param name="param_name" value="param_value" />
    </jsp:include>
    
    对于被包含页面而言, 请求参数中增加了上述添加的这个.
    

    处于效率考虑, 个人感觉没有特殊要求的话, 统一使用第一个比较好...

    JSP标准动作中还存在一个用于转发的动作, 该动作和 :

    <jsp:forward page="the_page_path" /> 
    实际上该动作等同于
    req.getRequestDispatcher("/WEB-INF/jsp/login/login.jsp").forward(req, resp);
    是在服务器端进行的, 用户并不能看到url的变化.
    
    同时该动作不需要担心写在jsp中部会导致转发之后之前加载的页面会打印出来,  因为发生这个动作时容器会清空缓冲区, 理论上只要没有使用flush()显式地将缓冲区内容打印到页面都行. **当然之前提到的jsp include动作中的增加参数语句在这里同样试用.**
    
  • 相关阅读:
    剑指offer 面试26题
    剑指offer 面试25题
    剑指offer 面试24题
    剑指offer 面试23题
    剑指offer 面试22题
    剑指offer 面试19题
    剑指offer 面试20题
    剑指offer 面试21题
    剑指offer 面试18题
    剑指offer 面试17题
  • 原文地址:https://www.cnblogs.com/nzhl/p/6360849.html
Copyright © 2011-2022 走看看