一, 容器(container)
servelt 没有main()方法,它们受控于另一个Java应用,这个Java应用称为容器(container)。Tomcat就是这样一个容器。Web服务器应用(如Apache)得到一个指向servlet的请求时,服务器不是把这个请求交给servlet本身,而是交给部署该servlet的容器。要由容器向servlet提供HTTP请求和响应,而且要由容器调用servlet的方法,如doPost或doGet。
容器为Web应用提供了通信支持、生命周期管理、多线程支持、生命方式的安全,还支持JSP,这样就可以使开发人员专心于业务逻辑。
容器多线程支持:容器会自动地为接收的每个servlet请求创建一个新的Java线程。针对客户的请求,如果servlet已经运行完相应的HTTP服务方法,这个线程就会结束。
容器会创建一个请求对象和一个响应对象,servlet可以用这些对象来得到有关请求的信息,并把信息发送给客户。
二 ,Servlet类
典型的servlet是一个exteds HttpServlet的类,并且覆盖了一个或多个服务方法(doGet或doPost)。
部署人员可以把一个servlet类映射到一个URL,这样客户可以使用这个URL来请求该servlet。
三,MVC设计模式
采用MVC,不仅是要求业务逻辑与表示分离,实际上,业务逻辑甚至根本不知道有表示层的存在。
模型*视图*控制器就是把业务逻辑从servlet中抽出来,把它放到一个模型中,所谓模型就是一个可重用的普通Java类。模型是业务数据和方法的组合。
控制器:从请求获得用户输入,并明确这些输入对模型有什么影响。告诉模型自行更新,并且要让视图得到新的模型状态。
视图:负责表示。它从控制器得到模型的状态(不过不是直接得到,控制器会把模型数据放在视图能找到的一个地方)。另外视图还要获得用户输入,交给控制器。
四,J2EE 应用服务器
J2EE应用服务器包括一个Web容器和一个EJB容器。Tomcat只是一个Web容器,而不是一个完整的J2EE容器,因为Tomcat没有EJB容器。Tomcat是一个独立的Web容器,通常配置为与一个HTTP Web服务器(如Apache)协作。所以最常见的非EJB Web应用通常会结合使用Apache和Tomcat。
五,Servlet的映射
a, 用户提交表单,发送请求(SelectBeer.do)给Web容器,浏览器会为请求添加应用的根目录,如/Beer-v1,所以实际发送的请求是 /Beer-v1/SelectBeer.do
b, 容器搜索部署文件web.xml,找到<url-pattern>与/SelectBeer.do匹配的一个<servlet-mapping>,找到对应的<servlet-name>是Ch3 Beer。
c, 容器查找<servlet-name>为Ch3 Beer的<servlet>标记,从而找到真正的<servlet-class>,如果这个servlet还没有初始化,就会加载类,并初始化这个servlet
d, 容器开始一个新线程来处理这个请求,并把请求传递给线程,传递给servlet的service方法。
e, 请求处理完后,容器把响应通过Web服务器发回给客户。
六,Servlet与JSP工作流程
1,用户点击一个链接,链接的URL指向一个servlet。
2,容器“看出”这个请求指向一个servlet,所以容器创建两个对象:HttpServletRequest和HttpServletResponse。
3,容器根据URL找到正确的servlet,创建新的线程,并把请求和响应对象作为参数传递给这个servlet。
4,servlet的service()根据客户发出的HTTP方法(GET,POST)确定调用哪个servlet方法。例如调用doGet(),把HttpServletRequest和HttpServletResponse传递给这个方法。
5,调用模型类寻求帮助。
String c = request.getParameter("color");
4,模型类返回一个回答,servlet把这个回答增加到请求对象。
BeerExpert be = new BeerExpert();
List result = be.getBrands(c);
request.setAttribute("styles", result)
5,servlet把请求转发给JSP。
RequestDispatcher dispatcher = request.getRequestDispatcher("result.jsp");
dispatcher.forword(request, response);
==========
request.getRequestDispatcher("result.jsp").forward(request, response);
6,service()方法结束,所以线程要么撤销,要么返回容器管理的一个线程池。请求和响应对象也没有意义了,可以当做垃圾回收了。
7,JSP从请求对象得到回答。
<%
List styles = (List)request.getAttribute("style");
Iterator it = styles.iterator();
while(it.hasNext()) {
out.print("<br>try:" + it.next());
}
%>
<%%>标记里有一些标准Java代码,成为scriptlet代码。
8,JSP为容器生成一个页面。
9,容器把这个页面返回给心满意足的客户。
七,servlet的一生
1,servlet的生命周期
2,加载servlet
容器启动时,它会寻找已经部署的Web应用,然后开始搜索servlet类文件。然后开始加载类。加载类可能发生在容器启动时,也可能在第一个客户使用时进行。不管哪种方式加载,servlet总是会在为第一个客户请求提供服务之前得到加载和初始化。
3,初始化servlet
servlet从不存在状态迁移到初始化状态,首先是从构造函数开始,但是构造函数只是使之成为一个对象,而不是一个servlet。要想成为一个servlet,对象必须得到servlet该有的所有特权:
a,ServletConfig对象
每个servlet都有一个ServletConfig对象。
用于向servlet传递部署时信息(例如数据库或企业bean的查找名),而开发又不想把这个信息硬编码到servlet中。
用于访问ServletContext
参数,在部署描述文件中配置,一旦servlet被部署并运行,ServletConfig参数就不能再被改变。
b,ServletContext
每个Web应用有一个ServletContext(应该叫AppContext才对)
用于访问Web应用参数
相当于应用中的一个公告栏,可以在这里放消息,应用的其他部分可以访问这些消息。
用于得到服务器信息,包括容器的名字和版本,以及所支持API的版本等。
所以Init方法使servlet可以访问ServletConfig和ServletContext对象。servlet需要从这些对象得到有关servlet配置和Web应用的信息。
4,servlet一生中的三个重要时刻
三大重要时刻 | 何时调用 | 作用 | 是否覆盖 |
init() | servlet实例被创建后,并在servlet能为客户请求提供服务之前,容器要对servlet调用init方法 | 使你在servlet处理客户请求之前有机会对其进行初始化 | 如果有初始化代码,就要覆盖servlet类中的init方法。 |
service() | 当第一个客户请求到来时,容器会开始一个新线程,或者从线程池中分配一个线程,并调用servlet的service方法 | 这个方法会查看请求,确定HTTP方法,并在servlet上调用对应的犯法,如doGet、doPost等。 | 不应该覆盖service()方法,你的任务是覆盖doGet或doPost方法,而由HttpServlet中的service方法来考虑该调用doGet还是doPost。 |
doGet()或doPost() | servie方法根据请求的Http方法来调用 | 要在这里开始写处理逻辑。 | 至少覆盖其中的一个 |
注意:每次来一个请求时,都会完成一个service()-->doGet()/doPost()调用序列,在任何时刻,有多少客户请求就至少有多少可运行的线程,这只受容器资源或策略/配置的限制(例如,你的容器允许你指定最多有多少个并发线程,当客户请求超过这个上限时,有些客户就必须等待)。
八,HttpServletRequest和HttpServletResponse
1,谁来实现HttpServletRequest和HttpServletResponse?
容器来实现,这些实现类不在API中,因为它们要由容器开发商来实现,只要相信一点,调用servlet的service方法时,肯定会向它传递两个对象引用,这两个对象分别实现了HttpServletRequest和HttpServletResponse,而完全不用考虑实际的实现类名或类型,只要关心的是你肯定会得到这两个对象,它们拥有HttpServletRequest和HttpServletResponse的所有功能。
2,HttpServletRequest:
可以得到的值
a, String colorParam = request.getParameter("color");
b, String[] sizes = request.getParameterValues("sizes");
c, String client = request.getHeader("User-Agent");
d, Cookie[] cookies = request.getCookies();
e, HttpSession session = request.getSession();
f. String theMethod = request.getMethod();
g, InputStream input = request.getInputStream();
如果HTML表单没有明确地指出method=POST,请求就会作为GET请求发送。
如果对应一个给定的参数名有多个参数值,要使用getParameterValues(“parametername”)方法返回一个String[]数组。
5,HttpServletResponse
大多数情况下,使用响应只是为了向客户发回数据。会对响应调用两个方法:setContentType()和getWriter(),在此之后,只需要完成I/O以便把HTML写到流中,不过,也可以使用响应设置其他首部、发送错误,以及增加cookie。
对HttpServletResponse调用的最常用的方法是setContentType()和getWriter(): PrintWriter writer = response.getWriter(); writer.println("some text and HTML");
重定向:response.sendRedirect("http://www.oreilly.com"); 参数只能是String,不能是URL对象。
请求分派:重定向让客户来完成工作,而请求分派要求服务器上的某某来完成任务。request.getRequestDispater("url").forward(request, response);
九, 属性和监视器
1,servlet初始化参数
<servlet> <servlet-name>BeerParamTests</servlet-name> <servlet-class>TestInitParams</servlet-class> <init-param> <param-name>adminEmail</param-name> <param-value>ivy.sue@wickedlysmart.com</param-value> </init-param> </servlet>
在Servlet类的代码中:
String email = getServletConfig().getInitParameter("adminEmail");
servlet继承了getServletConfig(),所以可以从servlet中的任何方法调用getServletConfig来得到ServletConfig的一个引用。不过不能从构造函数调用这个方法,因为在容器调用init()方法之前,它还不能算一个完整的servlet,更无从谈起调用getServletConfig()了。
容器初始化一个servlet时,会为这个servlet建一个唯一的ServletConfig。容器从部署描述文件中读出servlet初始化参数,并把这些参数交给ServletConfig,然后把ServletConfig传递给servlet的init方法。
servlet初始化参数只能读一次,就是容器初始化servlet的时候。容器建立一个servlet时,它会读web.xml,并为servletConfig创建名值对,容器不会再读初始化参数了,一旦参数放在ServletConfig中,就不会再读了,除非重新部署servlet。
容器初始化servlet的过程:
- 容器读取这个servlet的web.xml,包括servlet初始化参数(<init-param>)
- 容器为这个servlet创建一个ServletConfig实例。
- 容器为每个servlet初始化参数创建一个名值对。
- 容器向ServletConfig提供名值对参数
- 容器调用servlet的构造函数
- 容器调用servlet的init方法,传入ServletConfig的引用。
2,ServletContext初始化参数
上下文初始化参数与servlet初始化参数很类似,只不过上下文参数对整个web应用而不只是一个servlet可用。
<servlet>......</servlet> <context-param> <param-name>adminEmail</param-name> <param-value>clientheaderror@wickedlysmart.com</param-value> </context-param>
在servlet代码中:
getServletContext().getInitParameter("adminEmail");
web应用初始化过程:
容器读web.xml,为每个<context-param>创建一个名值对。
容器创建ServletContext的一个实例。
容器为ServletContext提供上下文初始化参数各个名值对的引用
在Web应用中部署的各个servlet和JSP都能访问同样的servletContext
要把初始化参数认为是部署时常量,可以在运行时得到这些参数,但不能设置。
3, 监听者 ServletContextListener
- 创建一个监听者类,监听ServletContext事件,写一个实现ServletContextListener的监听者
public class MyServletContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { ServletContext sc = event.getServletContext(); String dogBreed = sc.getInitParameter("breed"); Dog d = new Dog(dogBreed); sc.setAttribute("dog", d); } public void contextDestroyed(ServletContextEvent event) { // do nothing } }
- 创建模型类
public class Dog { private String breed; public Dog(String breed) { this.breed = breed; } public String getBreed() { return breed; } }
- 创建Servlet
public class MyListenerServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { response.setContentType("text/html"); PrintWriter writer = response.getWriter(); Dog dog = (Dog)getServletContext().getAttribute("dog"); out.println("Dog's breed is :" + dog.getBreed()); } }
- 把它们放在WEB-INF/classes目录下
- 在部署描述文件中放一个<listener>元素来告诉容器。
<web-app ....> <servlet> <servlet-name>MyListenerServlet</servlet-name> <servlet-class>MyListenerServlet</servlet-class> <servlet> <servlet-mapping> <servlet-name>MyListenerServlet</servlet-name> <url-pattern>/ListenTest.do</url-pattern> </servlet-mapping> <context-param> <param-name>breed</param-name> <param-value>Great Dane</param-value> <context-param> <listener> <listener-class>MyServletContextListenr</listener-class> </listener> </web-app>
4, servlet初始化完整的故事
- 容器读web.xml,包括<listener>和<context-param>
- 容器为这个应用创建一个新的ServlertContext,应用的所有部分都会共享这个上下文。
- 容器为每个上下文初始化参数创建一个String名值对
- 容器将名值参数的引用交给ServletContext
- 容器创建MyServletContextListener的一个实例
- 容器调用监听者的contextInitialized()方法,传入新的ServletContextEvent,这个事件对象有一个ServletContext引用,所以事件处理代码可以从事件得到上下文,并从上下文得到上下文初始化参数。
- 监听者向ServletContextEvent要ServletContext的一个引用
- 监听者向ServletContext要上下文初始化参数breed
- 监听者使用初始化参数构造一个新的Dog对象。
- 监听者把Dog设置为ServletContext中的一个属性。
- 容器建立一个新的servlet
- servlet得到一个请求,向ServletContext请求属性dog
- servlet在Dog上调用getBreed(),并将结果打印到HttpServletResponse
5, 其他事件监听者
- 监听对象/销毁的监听器接口
Interface ServletRequestListener
监听request对象的创建或销毁
Interface HttpSessionListener
监听Session对象的创建或销毁
sessionCreated(HttpSessionEvent se)
sessionDestroyed(HttpSessionEvent se)
Interface ServletContextListener
监听ServletContext对象的创建或销毁
- 监听对象属性的变化
Interface ServletRequestAttributeListener
监听request对象属性变化:添加,移除,修改
Interface HttpSessionAttributeListener
监听session对象属性变化:添加,移除,修改
Interface ServletContextAttributeListener
监听servletContext对象属性变化
- Session相关监听器
Interface HttpSessionBindingListener
监听对象绑定到Session上的事件
Interface HttpSessionActivationListener(了解)
监听Session序列化及反序列化的事件
6,属性
- 属性的三个作用域:上下文/请求/会话
- 3个属性作用域分别由ServletContext/ServletRequest/HttpSession接口处理,每个接口中的属性API方法完全相同。
- Object getAttribute(String name);
- setAttribute(String name, Object value);
- removeAttribute(String name);
- Enumeration getAttributeNames();
3, 属性的缺点:上下文作用域不是线程安全的,应用中的每一部分都能访问上下文属性,这意味着可能有多个servlet,也就是有多个线程可能在访问同一个上下文属性。
4,怎样让上下文属性做到线程安全?
保护上下文属性的一般做法是对上下文对象本身同步。
synchronized(getServletContext()) { getServletContext().setAttribute("foo", "22"); out.println(getServletContext().getAttribute("foo")); }
Session也没法做到线程安全,只有请求属性和局部变量是线程安全的,所以 对于处理Servlet和线程安全,推荐的做法是完全使用局部变量,而不是属性,如果必须使用实例变量,则同步对实例变量的访问。
十 HttpSession
1, HttpSession的是什么?
HttpSession对象用来保存跨多个请求的会话状态,也就是说与一个特定客户的整个会话期间,HttpSession对象会持久存储。对于会话期间客户做的所有请求,从中得到的所有信息都可以用HttpSession对象来保存。
2,容器如何识别用户并匹配HttpSession对象?
Http协议使用的是无状态连接,客户浏览器与服务器建立连接,发出请求,得到响应,然后关闭连接。连接只针对请求和响应。因为连接不会持久保留,所以容器认不出做第二个请求的客户与做前一个请求的客户是同一个客户。对容器来说,每个请求都来自一个新的客户。
为了让容器能够识别请求来自同一个客户,客户需要一个ID来标明身份。所以,对客户的第一个请求,容器会生成一个唯一的会话ID,并通过response返回给客户。客户会再以后的每个请求中都发送这个会话ID,容器看到ID之后,就会找到匹配的会话,并把这个会话和请求关联起来。
客户和容器交换会话ID最简单最常用的方式就是使用Cookie。客户先告诉容器想要创建或使用一个会话,然后容器会负责生成会话ID,创建新的Cookie对象,把会话ID放到cookie中,把cookie设置为响应的一部分返回给客户。对于之后的每个请求,容器也会从请求的cookie中得到会话ID,将这个会话ID与一个现有的会话匹配,并把会话与当前请求关联。
3, HttpSession 和 Cookie的结合实现方式
a. 在响应中发送一个会话cookie:
HttpSession session = request.getSession();
向请求要一个会话,容器会负责剩下的所有事情:
- 建立一个新的HttpSession对象;
- 生成唯一的会话ID;
- 建立新的Cookie对象;
- 把会话与cookie关联
- 在响应中设置cookie(在Set-Cookie Header中)
b, 第二次请求从请求中得到会话ID
HttpSession session = request.getSession();
和为响应发送会话ID使用的方法完全一样,我们不会看到具体的会话ID.
- 下面是容器处理getSession的伪代码:
if (请求包含一个会话ID cookie) {
找到与该ID匹配的会话;
} else {
创建一个新会话;
}
- 如果想要知道一个session是不是新创建的,可以使用session.isNew();
HttpSession session = request.getSession(); if (session.isNew()) { out.println("This is a new session."); } else { out.println("Welcome back !"); }
- 如果只想要一个已经有的会话,可以使用request.getSession(false); 这样要么得到null, 要么得到一个已经存在的session.
HttpSession session = request.getSession(false); if (session == null) { out.println("no session was avaliable"); out.println("making one"); session = request.getSession(); else { out.println("there was a session!"); }
getSession() 和 getSession(true) 完全一样,有就用,没有就创建。
- 如果cookie被禁用,可以使用URL重写
如果在请求上调用request.getSession(), 容器就会尝试使用cookie, 如果没有启用cookie, 这说明用户不会加入会话,换句话说,会话的isNew()方法总会返回true。
禁用cookie的客户会忽略"Set-Cookie" 响应首部,如果客户不接受cookie, 你不会得到异常,没有警告,也没有提示。客户也不会返回一个有会话ID cookie首部的请求。
不过对于禁用cookie客户的请求,容器还留了一条后路:URL重写。URL重写能取得会话ID,并把会话ID附加到访问应用的各个URL的最后。不过只有告诉响应要对URL编码,URL重写才能奏效。
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); HttpSession session = request.getSession(); out.println("<html><body>"); out.println("<a href=\"" + response.encodeURL("/BeerTest.do" + "\">click me</a>")); out.println("</body></html>"); }
如果想把请求重定向到另外一个URL,但是还是想使用一个会话,有一个特殊的URL编码方式:
response.encodeRedirectURL("/BeerTest.do");
使用URL重写只有一种可能,这就是作为会话一部分的所有页面都是动态生成的,不能硬编码会话ID,因为ID在运行时之前并不存在,所以根本不能对静态页面完成URL
重写。
会话迁移
分布式Web应用,会把应用的各个部分复制在网络中的多个节点上,在一个集群环境中,容器可能会完成负载均衡,取得客户的请求,并把请求发到多个JVM上,这说明每次同一客户发请求时,最后这个请求都有可能到达同一个servlet的不同实例,也就是说客户发向servletA 的请求A可能在JVM 1上,而同样发给servletA 的请求B 可能在JVM 2上。所以问题是,ServletContext,ServletConfig和HttpSession对象会变成什么样呢?
只有HttpSession对象会从一个JVM移到另一个JVM,每个JVM有一个ServletContext,每个JVM每个Servlet有一个ServletConfig。但是对于每个Web应用的一个给定会话ID,只有一个HttpSession对象,而不论应用分布在多少个JVM中。
应用的其他部分会在每个节点上复制,但会话对象不会复制,而只是移动。
十一 Cookie
- 创建cookie
Cookie cookie = new Cookie("username", name);
- 设置cookie存活时间
cookie.setMaxAge(30*60);
- 向request增加cookie
response.addCookie(cookie);
- 取得请求的cookie
Cookie[] cookies = request.getCookies(); for (int i=0; i<cookies.length; i++) { Cookie cookie = cookies[i]; if (cookie.getName().equals("username")) { String username = cookie.getValue(); out.println("Hello " + username); break; } }
不要把cookie和header混为一谈,增加header使用的是 response.addHeader("foo", "bar");,但是向响应增加Cookie时,增加的是Cookie对象,需要通过Cookie名和值先构造出一个 Cookie对象,在添加进去: Cookie cookie = new Cookie("foo", "bar"); response.addCookie(cookie); 而且,对于header既有setHeader方法,也有addHeader方法,但是cookie只有addCookie()一个方法。
十二 使用JSP
1, import指令
<%@ page import="com.ivy.jsptest.*, com.ivy.servlettest.*" %>
2,表达式
<%
out.println(Counter.getCount());
%>
------->
<%= Counter.getCount() %>
scriptlet: <%%>
指令: <%@ %>
表达式: <%= %>
为什么表达式没有分号?
因为容器会拿到<%= %>之间的所有内容作为参数传递给out.println(), 所以
<%= Counter.getCount() %>
相当于
out.println(Counter.getCount());
所以,如果在表达式加上分号,就会变成out.println(Counter.getCount();); 就会报错。
在表达式中,如果方法没有返回会发生什么?
绝对不能把void的方法用作表达式。
3,JSP声明
可以看到所有的scriptlet和表达式代码都放在服务方法中,说明scriptlet中声明的变量总是局部变量。也就意味着每次运行服务方法时count变量都会重新初始化。于是有了JSP声明。
<%! int count=0 %>
JSP声明用于声明servlet类的成员,也就是可以声明成员变量和方法。换句话说,<%! 和%>标记之间的所有内容都会增加到类中,而且置于服务方法之外。可以声明静态变量和方法,还可以声明实例变量和方法。
4,容器如何将JSP转化成Servlet
- 查看指令,得到转换时可能需要的信息。
- 创建一个HttpServlet子类。
- 如果一个page指令有import属性,它会再类文件最上面写import语句。
- 如果有生命,容器将这些声明写到类文件中,通常放在类声明下面,并在服务方法前面。
- 建立服务方法,服务方法具体方法名是_jspService()。所生成的servlet会覆盖servlet超类的service()方法。_jspService()就由这个service()调用,要接收HttpServletRequest和HttpServletResponse参数。在建立这个方法时,容器会声明并初始化所有隐式对象。
- 将普通HTML/scriptlet和表达式放到服务方法中,完成格式化,并写到PrintWriter响应输出。
解释:
PageContext封装了其他隐式对象,所以可以通过PageContext引用得到其他隐式对象的引用。
_jspxFactory.getPageContext() 参数详细:
PageContext getPageContext(Servlet servlet, ServletRequest request, ServletResponse response, String errorPageURL, boolean needsSession, int bufferSize, boolean autoflush)
容器转化JSP生成的servlet类,这个类实现了HttpJspPage接口。它有三个关键方法:
jspInit(): 这个方法在init()方法中调用。可以覆盖这个方法。
jspDestroy(): 这个方法在servlet的destroy()方法中调用。 这个方法也可以覆盖。
_jspService():这个方法在servlet的service()方法中调用,对于每个请求,它会再一个单独的线程中运行。容器将Request和Response对象传递给这个方法。不能覆盖_jspService()。
5, JSP注释
<!-- HTML 注释 -->
<%-- JSP 注释 -->
6, JSP 生命周期
1. 有一个jsp文件部署在容器中。容器启动读取web.xml,但对.jsp文件不做任何处理,直到得到第一个请求。
2. 客户请求这个jsp文件,容器尝试将.jsp转换成一个servlet类的.java源代码。
3. 容器尝试把这个servlet.jsp源文件编译为一个.class文件。
4. 容器加载新生成的servlet类。
5. 容器实例化servlet,并导致servlet的 jspInit()方法运行。对象现在成为一个完整的servlet,准备就绪,可以接受客户请求了。
6, 容器创建一个新线程来处理这个客户的请求,servlet的_jspService()方法运行,此后发生的所有事情都只是普通的servlet请求处理。最终servlet向客户发回一个响应。
如果Web应用有JSP,部署这个应用时,在JSP生命周期中,整个转换和编译步骤只发生一次,JSP一旦得到转换和编译,就和其他servlet一样了。所以只有第一个请求需要的时间久一点。
7. 为JSP配置servlet初始化参数
<web-app ...> ... <servlet> <servlet-name>MyTestInit</servlet> <jsp-file>/TestInit.jsp</jsp-file> <init-param> <param-name>email</param-name> <param-value>wecare@wickedlysmart.com</param-value> </init-param> </servlet> ... </web-app>
8. 覆盖jspInit()
<%! public void jspInit() { ServletConfig sConfig = getServletConfig(); String emailAddr = sConfig.getInitParameter("email"); ServletContext ctx = getServletContext(); ctx.setAttribute("mail", emailAddr); } %>
9 JSP 属性
设置一个页面作用域属性
<% pageContext.setAttribute("foo", one); %>
获得一个页面作用域属性
<%= pageContext.getAttribute("foo") %>
使用pageContext设置一个会话作用域属性
<% pageContext.setAttribute("foo", two, PageContext.SESSION_SCOPE);
使用pageContext获得一个会话作用域属性
<%= pageContext.getAttribute("foo", PageContext.SESSION_SCOPE); %>
等价于
<%= session.getAttribute("foo") %>
使用pageContext获得一个应用作用域属性
<%= pageContext.getAttribute("mail", PageContext.APPLICATION_SCOPE) %>
等价于
<%= application.getAttribute("foo") %>
使用pageContext查找一个不知道作用域的属性
<%= pageContext.findAttribute("foo") %>
会先在pageContext中找,找不到会按照请求作用域,会话作用域,应用作用域的顺序找,直到找到。
10. page指令
重要属性:
import
isThreadSafe:默认是true
contentType:定义JSP响应的MIME类型。
errorPage: 定义一个资源的URL,如果有未捕获的Throwable,就会发送到这个资源。如果这里指定了一个JSP,该JSP的page指令中会有一个isErrorPage="true"属性。
11. 标准动作
<jsp:useBean>
<jsp:getProperty>
<jsp:setProperty>
如果在<jsp:useBean>或<jsp:getProperty>标记中没有指定作用域,容器会使用默认作用域“page”。
- <jsp:useBean> 和 <jsp:getProperty>
<jsp:useBean id="person" class="foo.Person" scope="request" /> Person created by servelt: <jsp:getProperty name="person" property="name" />
<jsp:useBean>还能创建一个bean,如果<jsp:useBean>找不到一个名为“person”的属性对象,它就会创建一个。
转化为_jspService()方法中以下代码:
foo.Person person = null; synchronized(request) { person=(foo.Person)_jspx_page_context.getAttribute("person", PageContext.REQUEST_SCOPE); if (person == null) { person = new foo.Person(); _jspx_page_context.setAttribute("person", person, PageContext.REQUEST_SCOPE); } }
<jsp:setProperty>
<jsp:useBean id="person" class="foo.Person" scope="request"/>
<jsp:setProperty name="person" property="name" value="Fred" />
这样会重置person的属性,如果不想重置,只是想为新的bean设置属性,可以为<jsp:setProperty>加body
<jsp:useBean id="person" class="foo.Person" scope="request">
<jsp:setProperty name="person" property="name" value="Fred" />
</jsp:useBean>
为 <jsp:setProperty> 加 body后生成的servlet:
foo.Person person = null; synchronized(request) { person=(foo.Person)_jspx_page_context.getAttribute("person", PageContext.REQUEST_SCOPE); if (person == null) { person = new foo.Person(); _jspx_page_context.setAttribute("person", person, PageContext.REQUEST_SCOPE); JspRuntimeLibrary.introspecthelper(_jspx_page_context.findAttribute("person", "name", "Fred", null, null, false)); //这段代码相当于 person.setName("Fred"); } }
12. EL表达式语言
如果某个bean有个属性是Object,然后想打印这个Object的一个属性,使用<jsp:getProperty>是没法做到的 ,它只能访问bean的属性,不能访问嵌套属性。但是可以使用EL来达到目的。
EL的用途是提供一种更简单的方法来调用Java代码,但是代码本身放在别的地方。
${person.dog.name} 就相当于 <%= ((foo.Person)request.getAttribute("person")).getDog().getName() %>
EL 表达式总是放在大括号里,而且前面有一个美元符前缀
${firstThing.secondThing}
firstThing 可以是隐式对象,也可以是属性。
如果表达式变量后面是一个点号,左边变量必须是一个map或者bean。
如果表达式中变量后有一个中括号[], 左边的变量则有更多选择,可以是Map/bean/List或数组。例如: ${musicList["something"]}
如果中括号左边是一个数组或List,而且索引是一个String直接量,那么这个索引会强制转换为int。