1、servlet 就是服务器用来处理请求的辅助应用
2、简单来说,在Servlet中引入Java,就是JSP
3、什么是容器,Servlet中没有main()方法,它受控于另外一个Java应用,这个控制Servlet的应用叫做容器。
4、在Web服务器接收到请求时,首先是到达Web服务器应用(如Apache),接着服务器应用将这个请求交给Web容器应用,然后要由容器向Servlet提供HTTP请求和响应,而且要由容器调用Servlet的方法,如doPost()和doGet()。
5、使用容器能够提供哪些好处:
6、容器如何处理请求
7、所有用作Servlet的类都应该扩展 抽象类HttpServlet,然后,这个Servlet要覆盖其中的doGet() 或者是doPost()方法,这里以doGet()方法为例,doGet()方法的参数为HttpServletRequest和HttpServletResponse,这是两个接口,但实际使用的是具体的类,由容器生成。在Servlet从容器得到的HttpServletResponse对象中得到一个PrintWriter,使用这个PrintWriter能够将HTML文本输出到响应对象(除了输出PrintWriter外,还可以输出其他内容,比如输出图片而不是HTML文本)。
8、容器是怎么找到所需要的Servlet的,首先要理解Servlet有3种不同的名称,第一个是实际的类名(class文件的名字),第二个是部署Servlet的人给它取的一个特殊的部署名,第三个是客户可以看到的,要写到HTML中的公共URL名。因此需要在部署描述文件中将对应关系指定清楚。在xml中的
完全限定名对应的xml元素为
用户通过浏览器的公共url名来请求servlet,首先映射到实际servlet的类名,然后容器根据该类名查找实际的类,然后再进行相应的处理。
9、部署描述文件(Deployment Descriptor)的作用:把URL映射到实际的servlet。还可以用部署描述文件对Web应用的其他方面进行定制,包括安全角色,错误页面,标记库,初始配置信息等,如果是一个完整的J2EE服务器,甚至可以声明你要访问特定的企业JavaBean。
利用部署描述文件,可以采用一种一种方式修改应用,而无需修改源代码。此外,还有以下优点:
9、经过Servlet处理后, 将请求转发到一个JSP,然后由JSP处理响应HTML。这样还可以将业务逻辑与表示分离。
10、MVC设计模式:(model-view-control:模型-视图-控制器)。这个设计模式就是讲业务逻辑从servlet中抽离出来,把业务逻辑放到一个“模型”中,所谓模型就是一个可重用的普通Java类,模型是业务数据和处理数据的方法的组合。
在Servlet & JSP世界中,MVC就是就是:
模型(普通Java类):包含业务逻辑和状态,具体来说就是模型知道用什么来得到和更新状态。系统中只有这个部分与数据库通信(不过它也有可能使用另一个对象来完成具体的数据库通信)
视图(jsp):负责表示方面,它从控制器得到模型的状态(不过不是直接得到,控制器会把模型数据放在模型能找到的一个地方)。另外视图还要获得用户输入,并交给控制器。
控制器(servlet):从请求获得用户输入,并明确这些输入对模型有什么影响。告诉模型自行更新,并让视图(jsp)得到最新的模型状态。
11、J2EE中集成的一切:J2EE应用服务器包含一个Web容器和一个EJB容器。
解释一下JavaBean:JavaBean其实就是一个普通的Java类,是Java程序设计中的一种设计模式,是一种基于Java平台的软件组织思想。
JavaBean遵循着特定的写法,通常有以下规则:
看起来高大上,实际上JavaBean就是按照特定写法、规则编写的JavaBean对象。
为什么要使用JavaBean,使用JavaBean的好处就是:封装,重用,可读
如果把bean类与数据库联合使用,一张表使用bean类,可以使你的代码更加简洁高效,易于理解,现在大多数框架都会使用这种机制。
其他内容详见:https://segmentfault.com/a/1190000013165165
12、给出一个啤酒应用的简单步骤:
13、开发web应用的4大步骤:
最后一个步骤又分为5个小步骤,
14、servlet的生命周期:
15、Servlet的生命周期很简单:只有一个主要的状态——初始化。一个servlet要么还不存在,要么还没有初始化,要么正在初始化(运行其构造函数或init()方法)、正在撤销(运行其destroy()方法)。
其详细的生命周期如下图所示:
Servlet的生命周期:
第一步:寻找类,容器启动时,它会寻找已经部署的Web应用,然后开始搜索servlet类文件。
第二步:加载类,这可能在容器启动时发生,也可能是在第一个客户使用时进行。容器可能允许你来完成类加载,也可能希望会在它希望的任何时刻加载类。总之,在servlet没有完全初始化之前绝不能运行servlet()的service()方法。
第3步:初始化类,即运行init()函数,记住不要在servlet的构造函数中放任何东西,任何东西都可以放在init()函数里。
第4步:调用service()方法
第5步:调用destroy()方法
HttpServlet的继承关系为:
servlet生命周期中的3个重要时刻是
16、容器运行多个线程来处理对一个servlet的多个请求,对应于每个客户请求,都会生成一对新的请求和响应对象。
如果容器使用了集群分布,把应用分布在多个JVM上,那么每个JVM都会有特定servlet的一个实例,不过对于每个servlet来说,都只有该servlet的一个实例。
17、ServletConfig对象和ServletContext对象:
ServletConfig对象:
ServletContext对象:
18、HttpServletRequest和HttpServletResponse的类继承关系:
19、在使用HttpServletRequest和HttpServletResponse的过程中,要注意: 这两个接口的具体应用类由容器的开发商实现,在使用时,只需要记住这两个引用具有HttpServletRequest和HttpServletResponse的所有功能。
20、在实际的servlet使用中,只需要关心GET和POST,但同时也应该注意,还有其他的HTTP方法。
21、get方法和post方法的区别:get方法表单信息在请求行里,post方法的表单信息在消息体里:
22、非幂等请求:
首先给出幂等性的定义:就是对资源的操作,无论操作一次还是多次,其资源本身不发生变化。
其详细信息见:https://blog.csdn.net/shangrila_kun/article/details/89057968
23、如果在html中的form表单中没有指定所需的HTTP方法,默认会使用get方法,如果想让一个servlet同时支持post和get方法,通常会把逻辑放在doGet()中,如果有必要,再建立doPost()方法委托到doGet()。
24、表单中的每个参数对应于HttpServletRequest对象中的相应参数。注意一个属性可能对应多个值。这个时候需要使用request.getParameterValues(),并且这个方法返回的是一个数组String[],而request.getParameter()返回的是String。
25、HttpServletRequest中可能被用到的方法:
为什么需要从请求中得到一个InputStream呢?Post方法的请求体中,有些参数的值可能很大,并且有可能创建一个servlet来处理计算机驱动的请求,其中请求体包含要处理的文本和二进制内容,在这种情况下,可以使用getReader()或者是getInputStream()方法。这些流只包含HTTP请求的体而不包含首部。
getHeader()和getIntHeader()有什么区别:getHeader()返回的是一个String,但如果你已经确切地知道某个首部的值为整数,那么就可以使用getIntHeader()
getRemotePort()为得到“客户的端口”,也就是得到发送请求的客户的端口号,
getServerPort()为“请求原来打算发送到哪个端口”
getLocalPort()为“请求最后实际上发送到了哪个端口”
这两者是有区别的,因为尽管请求要发送到一个端口(服务器所监听的端口),但是服务器会为不同的线程找一个不同的本地端口,这样一来,一个应用就能够同时处理多个不同客户了。
26、最后再来考虑响应,响应是要返回给客户的东西,可以使用响应对象得到一个输出流(通常是一个Writer),并使用这个流写出HTML(或者是其他类型的内容)。返回给客户。
对于响应对象,常用的方法有:
27、应该使用JSP,而不是servlet把HTML发回到输出流。
28、如果客户的请求是下载一个JAR文件,下载JAR的Sevlet代码如下所示:
29、response.setContentType(),这个函数用于设置内容类型,这个内容类型告诉浏览器将要发回什么,当谈到内容类型时,指的一般都是MIME类型。内容类型是响应中必须要有的一个首部。
30、ServletResponse接口只提供了两个流可供选择,ServletOutputStream用于输出字节,PrintWriter用于输出字符数据。其实传输的都是字节,但是PrintWriter实际上是包装了ServletOutputStream,也就是说,PrintWriter有ServletOutputSteam的一个引用,而且会把调用委托给ServletOutputStream。返回给客户的输出流只有一个,但是PrintWriter会“装饰”这个流,为它增加更高层的“字符友好”方法(即适于处理字符的方法)。
如果将字节流写至ServletOutputStream,就要调用write()方法,如果写至PrintWriter(),就要使用println(),
31、可以设置响应首部,也可以增加响应首部,这其中涉及到的方法有:response.setHeader()、response.addHeader()、response.setIntHeader,
32、可以把请求重定向到一个完全不同的URL,或者可以把请求分派给web应用的另一个组件(通常是一个JSP)。
33、Servlet可以重定向让浏览器完成工作。
设置重定向后,用户得到的HTTP响应有一个状态码301,还有一个Location首部,这个首部值是一个URL。在sendRedirect()中使用绝对和相对URLs
不能在响应已经提交之后再调用sendRedirect(),如果“在响应已经提交”之后再调用这个方法,就会抛出一个IllegalStateException异常。
这里的“提交”是指:响应已经发出,即数据已经刷新输出到流中。
另外,setRedirect()方法取的是一个String,而不是一个URL对象
34、请求分派:请求分派是让服务器的其他组件来完成某项任务。
重定向=返回新的URL让用户的浏览器处理。
请求分派=安排服务器上的其他组件继续处理
35、可以在servlet中配置初始化参数,下面是配置的过程以及使用的方法:
注意,在servlet初始化之前不能使用servlet初始化参数,即在构造函数中不能使用getServletConfig()方法得到ServletConfig的一个引用,因为这时还没有ServletConfig。
在容器初始化一个servlet时(即运行init()函数),会为这个servlet建立一个唯一的ServletConfig。容器从部署描述文件(DD)中读取servlet初始化参数,并把这些参数交给ServletConfig,然后将ServletConfig传递给servlet的init()方法。
36、ServletConfig的主要任务是提供初始化参数,它还提供了一个ServletContext,但是我们一般以另外一种方式得到上下文。getServletName()方法很少使用。
37、如果想在servlet之外的web组件(如JSP中)使用初始化参数,那么可以在请求对象中设置属性。
38、request 中Parameter(参数) 和Attribute(属性)区别,详见:https://blog.csdn.net/qq_36557815/article/details/78826617
39、servlet初始化参数
还有一个上下文初始化参数,这对整个web应用可用,而不仅仅限于一个servlet,即
上下文初始参数和servlet初始化参数的对比:
40、JSP最终会变成首类servlet,所以也有自己的ServletConfig。如果应用是分布式应用,那么每个JVM有一个ServletContext
41、这两类初始化参数一经部署,就不能改变,要把初始化参数认为是部署时常量,可以在运行时得到这些初始化参数,但是不能设置。如果在配置文件web.xml中更改了初始化值,那么只有在重新部署web应用时才能看到这些更改。
42、如果没有提到是servlet初始化参数还是上下文初始化参数,那么默认是servlet初始化参数。但是最好应该指明。
43、ServletContext的一些方法:
44、注意,初始化参数的值只能是字符串,要想根据初始化参数进行一些其他的初始化操作,可以使用一个监听者(listener),能够在应用为用户提供服务之前 应用一些代码。这就是一个ServletContextListener,实现这个接口的类能够监听ServletContext一生中的两个关键事件,初始化(创建)和撤销。
实现上下文监听者很简单,继承ServletContextListener并重载contexInitialized()和contextDestroyed()方法。这两个函数的事件对象为ServletContextEvent。(可以这样想,在ServletContext完成,init()函数开始之前触发侦听器,在应用取消部署或结束之后运行侦听器。)
45、上下文监听者类的使用
46、最好保证servlet的属性是可串行化(Serializable)的:
47、注意getAttribute()方法返回的类型为Object,需要对结果进行强制类型转换。而getInitParameter返回的是String。
在部署ServletContextListener时,由于这个listener监听的是ServletContext,因此需要放在
48、下面给出web应用处理用户请求的完整步骤:
49、listener可以不止用于上下文事件。只有是生命周期里的重要时刻,总会有一个监听者在监听。除了上下文事件以外,还可以监听与上下文属性、servlet请求与属性、以及Http会话和会话属性相关的事件。
下表列出了8个监听者:
区分其中的HttpSessionBindingListener和HttpSessionAttributeListener,前者是以作为属性的对象为主体,后者是以会话为主体
注意,HttpSessionBindingListener并不用在DD里注册,它会自动运行。
50、到底什么是属性
51、属性和参数的区别:
52、三个作用域:上下围,请求和会话。
53、属性的API
53、上下文作用域不是线程安全的。并且对doGet()方法进行同步处理(对方法应用synchronized)也无法保证线程安全,因为对doGet()方法进行同步只能保证某个servlet一次只有一个请求在使用doGet(),其他servlet还是能更改上下文属性。
54、同样地,会话也存在线程安全的问题,会话(HttpSession)是为了保证服务器和某个特定用户进行交流所使用的对象,而一个用户可能同时发出多个请求(见下图),这就产生了线程安全的问题。
因此,想要保护会话的线程安全,需要对会话进行同步,确保每次只有一个servlet在使用会话。
55、SingleThreadModel被设计用来保护实例变量,以下是servlet规范关于SingleThreadModel(或STM)接口的说明:
web容器有两个方案来保证一个servlet一次只能得到一个请求。
(注意,这里是实例池而不是线程池)
但是由于servlet规范规定了servlet的单实例多线程机制,但是有些容器可能采用实例池的实现,但是实例池实现可能不会达到目的,因为存在线程安全的问题。
STM存在性能上的巨大缺陷,因此现在已经将它废除了。
56、只有请求属性和局部变量才是线程安全的。(因为这两者都只会存在于单个servlet的单个线程中)
首先,实例变量可能被多个线程访问,因此不能保证线程安全,一个写得好的servlet,不会看到任何实例变量,或者至少看不到非final变量。所以,如果需要线程安全的状态,就不要使用实例变量,因为servlet的所有线程都可以处理实例变量。
57、如果需要多个线程都能访问一个值,要确定哪个属性状态最合适,并把这个值保存在一个属性中。往往可以用下面这两种方法来解决这个问题:
把变量声明为服务方法中的局部变量,而不是一个实例变量。
或者
在最合适的作用域中使用一个属性。
58、如果希望应用的其他组件接管全部或部分请求,就可以使用请求属性,这需要使用到RequestDispatcher。
RequestDispatcher只有两个方法,forward()和include(),这个对象可以从请求得到,也可以从上下文得到,
需要注意的是,如果已经提交了响应,就不能再转发请求,看下面一个例子:
include()方法在实际应用中可能不太会用到,这个方法会把请求发送给别人(通常是另一个servlet)来完成工作,然后再返回给发送者。换句话说,include()意味着请求别人帮忙处理请求,但这不是完全的移交,只是暂时把控制交给别人,而不是永久性地让别人接管。
59、谈到会话状态,首先要明确:Web服务器没有短期记忆。要想做到跨多个请求保留客户的特定状态,就需要使用到会话。
60、要想做到跟踪客户的回答,可以使用的方法有:使用一个有状态会话的企业Javabean,使用一个数据库,以及使用HttpSession
使用会话的原因:
会话其实很简单,对客户的第一个请求,容器会生成唯一的会话ID,并通过它把响应返回给客户,客户再在以后的每一个请求中发回这个会话ID。容器看到ID后,就会匹配的会话,并把这个会话与请求关联。
这个会话ID信息是通过一种叫做cookie的机制进行的。容器会几乎会做cookie的所有工作。你必须告诉容器你想创建或是使用一个会话,但是除此之外,生成会话ID、创建新的cookie对象、把会话ID放到cookie中,把cookie设置为响应的一部分都会由容器来负责。
而且得到会话ID cookie(并把它与现有会话匹配)的方法与创建(向用户发送)会话ID的方法一样。请求的无参数方法getSession()会返回一个会话,而不论是否已经有一个会话。想要知道这个会话是不是新创建的,只有去问返回的会话。
还有另外一种方法来得到会话,就是从会话事件对象(即HttpSessionEvent)得到,所以,如果实现了与会话相关的4个监听者接口中的任何一个,那么就可以访问会话,
如果只想要一个已经有的会话呢,针对这个目的,专门有一个重载的getSession(boolean)的方法,如果不想创建一个新的会话,可以使用getSession(false),这个方法要么得到一个已经存在的HttpSession,要么返回null。在实际中,只有一种情况下你会用到getSession(false),那就是你确实不想创建一个新的会话,另外还有一点需要注意,那就是getSession(true)与getSession()的效果完全一样。
如果客户不接受cookie,就可以吧URL重写当做是一条后路,URL重写能够获得置于cookie中的会话ID,并将它附加到访问应用的各个URL的后面。如果不能使用cookie,而且只有告诉响应对url编码,url重写才能奏效。
还可以使用sedRiderect()进行URL重写。
61、一些关键的HttpSession方法:
62、会话有3种死法:
1、超时 2、你在会话对象上调用invalidate(), 3、应用结束(崩溃或者是取消部署)
63、cookie还有其他用处。 要记住,cookie实际上就是客户与服务器之间交换的一小段数据(一个名/值对),一般来说,cookie的寿命和会话一样长,但是你可以让cookie获得更长一些,甚至是在浏览器关闭之后仍然存活。
可以使用cookie在服务器和客户之间交换名/值String对。
注意,没有getcookie(String)方法,你只能使用getcookies()方法得到一个cookie数组,然后再依次遍历。
64、会话的生命周期事件:
这三个listener与会话本身相关,而不是与会话中放置的单个属性相关,而HttpBindingSessionListener则是与会话中放置的单个属性相关。
65、会话迁移:分布式web应用,应用的各个部分可以复制在网络上的每个节点上。在一个集群环境中,容器可能会完成负载均衡,取得客户的请求,将请求发送到多个JVM上。
这说明,每次客户请求时,最后有可能到达同一个servlet的不同实例,换句话说,指向ServletA的请求A可能在一个JVM中完成,而指向ServletA的请求B可能在另一个不同的JVM中完成。那么,ServletContext、ServletConfig和HttpSession对象会有什么变化。
答案很简单,但是意义很重大:只有HttpSession对象(及其属性)会从一个VM迁移到另一个VM。
在任何给定时刻,会话只存在于一个位置,对于一个给定的web应用,相同的会话ID不会同时出现在两个VM里。
应用HttpActivationListener能够使属性做好准备大搬家(在迁移前做好准备工作)。
66、与会话相关的listener总结如下:
会话相关事件的监听者与事件对象API总结:
67、进入到JSP的学习。
JSP最终会变成Servlet类,容器会首先查看你的JSP,把它转换成Java源代码,再编译成完整的Java servlet类。要弄清的是你编写的JSP元素最后会放到所生成的servlet相应源代码中的什么位置。
JSP是一种建立动态网页的技术标准。
所谓的scriptlet,就是在<%..%>中放入Java代码。
68、JSP有3种指令,page、include和taglib,指令可以在页面转换时向容器提供提供特殊的指示。
page指令的目的是为容器提供一些信息,容器把你的JSP转换为srvlet需要使用到这些信息。
每个指令都有一些相应的属性,
page有import属性(在字符串中放入要导入的包名,使用逗号来分隔。)
要想在JSP中使用java代码,还有一种方式就是使用表达式代码。
3种不同类型的JSP元素:
表达式中的代码最终会成为out.print()的参数,
69、容器会将所有代码都放在一个通用的服务方法当中,这说明scriptlet中声明的局部变量总是局部变量
还有一种被称为声明的JSP元素,
用于声明所生成的servlet类的成员,这说明变量和方法都可以声明!换句话说,<%! %>之间的部分都会增加到类中,并且置于jspservice方法之外,这意味着完全可以声明静态变量和方法。
70、用户如何处理JSP元素:
71、在JSP中有许多隐式对象,这些对象在生成的Servlet中的_jspService中声明,即JSP中的所有隐式对象都会映射到Servlet中的某个东西。
这里需要注意的是PageContext对象,稍后还将作详细说明。
72、JSP转换成的servlet中的JspWrite与一般的PrintWriter有何不同。
73、注释,可以在JSP上放上注释,在JSP中可以放两种不同的注释。
74、根据JSP生成的servlet的API
Tomcat生成的servlet要扩展:org.apache.jasper.runtime.HttpJspBase。
其中有3个关键的方法:
jspinit()
这个方法由init()方法调用,这个方法可以覆盖
jspDestroy()
这个方法由servlet的destroy()方法调用,这个方法也可以覆盖
_jspService()方法,这个方法由servlet的service方法调用,这说明,对于每个请求,它会在一个单独的线程里运行,这个方法不能覆盖。
注意:如果方法名前面没有下划线,就表示你可以覆盖它,如果方法名前面确实有下滑线,则表明你不要打算去覆盖它。
75、JSP的生命周期为:
注意在整个生命周期内,JSP的转换和编译步骤只会发生一次,JSP一旦得到转换和编译,就会和其他servlet一样了。
76、可以为JSP配置初始化参数。
这里和servlet的初始化参数配置稍微不同,
可以通过使用一个声明语句来覆盖jspInit()方法。
77、servlet和JSP中设置4个属性作用域中属性的方法对比:
使用PageContext引用可以得到任意作用域的属性,包括从页面作用域得到绑定到PageContext的属性访问其他作用域的方法要取一个int参数,这个参数用来指示是哪一个作用域。
78、JSP的3个指令,
page指令的13个属性:
79、EL(Expression language:表达式语言),原先能用scriptlet和表达式完成的事情,现在可以用EL来完成了、
EL的主要用途是提供一种更简单的方法来调用Java代码。
对于java程序员来说,EL使得设计人员可以维护一个无脚本的页面。
可以在DD中放一个
默认情况下EL是启用的,这可以通过page指令,也可以通过一个DD元素来指定。
并且Page指令优先于DD设置,
80、还有一个JSP元素:动作,迄今为止,已经出现了JSP中可能出现的5个不同类型的元素,scriptlet、指令、声明、java表达式和EL表达式。
有两种不同类型的动作:标准动作和 不标准的动作,
标准动作:
其他动作:
有些动作并不被认为是标准动作,但它们仍然是标准库的一部分。可以这么说,它们是标准化的非标准动作。
81、没有脚本的JSP。
首先应该有一个认识,MVC应用取决于属性,即通过属性来传递信息。
在这里,如果作用域的属性(attribute)对应的值是一个对象,那么对象变量称之为性质,
要记住,脚本代码包括 :声明、scriptlet和表达式。不要使用这些东西。
82、关于bean法则,到底什么才算是bean法则
还可以建立多态的bean
如果希望引用类型和没有该对象时使用的构造函数所属的对象类型不一样。可以为
如果使用了type,但没有class,bean(符合javaBean规则的对象)必须存在。bean对象不存在的话,则无论type是抽象类还是接口、实例类,最终都会报错。
如果使用了type,class必须扩展了type类或实现了type接口,且有一个无参数的公共构造函数。
83、灵活使用param属性
如果html的表单中的参数名与对象的性质名匹配,那么就可以更加简单
如果所有的请求参数名和bean对象的性质名都匹配,甚至还可以更加简单。
JavaBean的性质可以是任何东西,但是如果是String或基本类型,就能自动完成类型强制转换。
但是如果使用脚本(scriptlet、表达式、声明)
如果性质(实例变量)是一个对象,我们想要显示的是性质的性质呢,这个时候,EL表达式就派上用场了
这里的属性名可以当做"person"属性名所对应的对象使用
84、JSP的EL语言,注意不要把Java的语言/语法往EL上套。
在EL中也可以使用[ ]操作符,这种操作符比点号操作符更加强大,
如果[ ]里的是整数的字符串,会自动转换为整数,
如果[ ] 里不是String直接量,就会进行计算,
在中括号里可以使用嵌套表达式
85、EL会显示原始文本,包括HTML,
86、EL隐式对象与JSP隐式对象不同,
既然已经EL会在4个作用域内查找属性,为什么还要定义4个作用域隐式对象呢?
对于这个,可以这么理解:cookie.username得到的是一个cookie对象,cookie对象有一个value性质。
87、EL函数
TLD中的函数签名的参数应该使用完全限定类名,
88、一些有用的EL算数、关系和逻辑操作符,
EL能妥善地处理null值,
89、JSP的include指令
90、include指令和 jsp:include标准动作的区别
include指令对于位置是敏感的。
91、使用jsp标准动作还可以对包含的内容进行定制:
92、可以从jsp转发到Web应用中的任何其他资源,这需要用到
但是如果在刷新输出后再转发,那么会直接将响应(HTML页面)发送给用户,转发不会发生。
93、使用定制标记(JSTL,JSP Standard Tag Library)
94、
需要转换的HTML特殊字符
95、
这个
错误页面能够调用隐式对象exception的方法。
96、使用非JSTL的标记库,所需要知道的事情: