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的初始化参数 : $

这里还有内容暂时不提, 还有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动作中的增加参数语句在这里同样试用.**
posted on 2017-02-02 05:35  内脏坏了  阅读(201)  评论(0编辑  收藏  举报