Portlet概念详细示例教程
在Portlet概念详细教程–第一部分中,我们解释了有关诸如PortletRequest,PortletResponse,RenderRequest,ActionRequest,RenderResponse,ActionReponse和它们各自的功能的概念的许多细节了解了他们在处理用户请求时如何扮演角色。
本教程是对我们从PART-I开始的后续工作。
Portlet上下文,会话,Port-Portlet间的通信和缓存,这里将讨论所有这些概念,并且必须引入一组示例,使这些概念非常易于理解。
Portlet上下文
与Servlet一样,Portlet使用Portlet上下文对象来获取对Portlet应用程序(WAR)中定义的日志记录,资源,属性和初始化参数的访问。
Portlet上下文对象可用于每个已部署的Portlet应用程序,如果您在Portlet应用程序中部署了多个Portlet,则它们可以共享Portlet上下文对象,因为它们可以用于设置或者检索应用程序范围的数据。
与Servlet和JSP相似,它们也通过使用ServletContext共享Portlet上下文实例。
如果您在Portlet应用程序中提供了Servlet和JSP,则它们(Portlet,JSP,Servlet)可以获取相同的Portlet上下文。
以下示例向您简单演示了如何从在同一Portlet应用程序中部署的不同资源中访问Portlet上下文。
HelloPortlet.java
package com.theitroad.portlet; import java.io.IOException; import javax.portlet.GenericPortlet; import javax.portlet.PortletConfig; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class HelloPortlet extends GenericPortlet{ public void init(PortletConfig config) throws PortletException { super.init(config); //Set variable along context scope config.getPortletContext().setAttribute("anonymousVariable", "Just Variable"); } public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException{ response.getWriter().print("<p>Hello Portlet</p>"); } }
HelloServlet.java
package com.theitroad.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class HelloServlet */ public class HelloServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public HelloServlet() { super(); } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().print("<p>Portlet context anonymous variable value Is "+getServletContext().getAttribute("anonymousVariable")+"</p>"); } }
以下是上面列出的代码的详细说明:
您可以通过对Portlet容器已经提供的配置对象(当对portlet进行初始化时)访问" getPortletContext()"来获取" PortletContext"实例。
而且,任何扩展GenericPortlet的Portlet都可以通过从Portlet内部的任何位置调用this.getPortletContext()来访问Portlet上下文。可以通过位于同一Portlet应用程序(WAR)中的所有Portlet,Servlet和JSP访问沿上下文范围设置变量。
Servlet上下文是访问Portlet上下文变量所必须使用的方式,因为它们都与同一个Portlet应用程序相关。
同样,Portlet上下文可以访问Servlet上下文已经设置的任何变量。Portlet上下文中的属性是Portlet应用程序中Portlet和Servlet的共享数据。
这些属性是名称/值对,name是一个String对象,value是一个Object,可以是您域内的任何类型的对象。SetAttributes(),getAttributes(),getAttributesNames()和removeAttributes()是用于沿上下文范围设置,读取和删除属性的方法。
上下文初始化参数
上下文初始化参数是在web.xml文件中定义的那些变量。
由于可以在同一Portlet应用程序中定义Portlet和Servlet,因此Portlet也可以访问上下文初始化参数。
后面的示例只是上面显示的示例的简单更新,但是这次Servlet和Portlet都将读取上下文参数。
HelloPortlet.java
package com.theitroad.portlet; import java.io.IOException; import javax.portlet.GenericPortlet; import javax.portlet.PortletConfig; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class HelloPortlet extends GenericPortlet{ public void init(PortletConfig config) throws PortletException { super.init(config); } public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException{ response.getWriter().print("<p>Anonymous Context Variable Value Is "+this.getPortletContext().getInitParameter("contextParam")+"</p>"); } }
HelloServlet.java
package com.theitroad.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class HelloServlet */ public class HelloServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public HelloServlet() { super(); } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().print("<p>Portlet context anonymous variable value Is "+getServletContext().getInitParameter("contextParam")+"</p>"); } }
web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "https://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.theitroad.servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <context-param> <param-name>contextParam</param-name> <param-value>contextValue</param-value> </context-param> </web-app>
以下是上面列出的代码的详细说明:
通过Servlet获取上下文参数非常简单。
但是您必须使用已经传递给Portlet的PortletContext对象来读取上下文参数。针对PortletContext实例使用getInitParameterNames()将向您列出所有已定义的上下文参数。
Portlet可能具有自己的初始化参数,可以通过对PortletConfig()对象使用getInitParameter()来访问它们。
您可以在下面找到定义了初始化参数的示例Portlet部署描述符。
portlet.xml
<?xml version="1.0" encoding="UTF-8"?> <portlet-app xmlns="https://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"> <portlet> <init-param> <name>PortletParam</name> <value>ParamValue</value> </init-param> <display-name>Hello Portlet</display-name> <portlet-name>Hello</portlet-name> <portlet-class>com.theitroad.portlet.HelloPortlet</portlet-class> <description>Hello Portlet</description> <portlet-info> <title>Hello Portlet</title> <keywords>Hello, Portlet</keywords> <short-title>Hello Portlet</short-title> </portlet-info> </portlet> </portlet-app>
访问资源
除了PortletContext提供的所有这些讨论的功能之外,它还适用于您使用getResource(),getResourceAsStream()和getRealPath()访问Web应用程序中定义的资源或者系统文件夹中位于任何路径的资源。
以下示例向您展示了如何使用PortletContext对象读取以不同方式定义的资源。
HelloPortlet.java
package com.theitroad.portlet; import java.io.IOException; import java.io.InputStream; import javax.portlet.GenericPortlet; import javax.portlet.PortletConfig; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class HelloPortlet extends GenericPortlet{ public void init(PortletConfig config) throws PortletException { super.init(config); } public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException{ System.out.println("Got File :: "+this.getPortletContext().getResource("/index.html")); System.out.println("Got File :: "+this.getPortletContext().getRealPath("file:///c:/index.html")); InputStream input = this.getPortletContext().getResourceAsStream("/index.html"); byte [] bytes = new byte[input.available()]; input.read(bytes, 0, input.available()); response.getPortletOutputStream().write(bytes); } }
以下是上面列出的代码的详细说明:
获取资源适用于使用Portlet Context实例。
如果要通过getResources或者与此相关的任何方法读取资源,则应在其余路径前加一个正斜杠。
GetRealPath用于读取位于外部(即Portlet应用程序外部)的文件。
GetResourcePaths()方法将部分路径作为参数,并返回以该路径开头的所有资源的路径。
记录中
Portlet可以使用日志记录机制来编写消息。
像Servlet日志记录一样,日志消息的位置取决于容器的实现,因为它可以是文件,数据库条目,控制台或者容器可以选择的任何其他方式。
Apache Pluto已配置为将消息打印到控制台上。
Apache一直偏爱使用Apache的Log4j,而不是使用默认提供的实现。
以下示例向您展示了我们如何使用两种API(默认日志消息或者Log4j)来获取消息。
HelloPortlet.java
package com.theitroad.portlet; import java.io.IOException; import java.io.InputStream; import javax.portlet.GenericPortlet; import javax.portlet.PortletConfig; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import org.apache.log4j.Logger; public class HelloPortlet extends GenericPortlet{ Logger logger = Logger.getLogger(HelloPortlet.class); public void init(PortletConfig config) throws PortletException { super.init(config); } public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException{ this.getPortletContext().log("Got File :: "+this.getPortletContext().getResource("/index.html")); this.getPortletContext().log("Got File :: "+this.getPortletContext().getRealPath("file:///c:/index.html")); logger.debug("Got File :: "+this.getPortletContext().getResource("/index.html")); logger.debug("Got File :: "+this.getPortletContext().getRealPath("file:///c:/index.html")); InputStream input = this.getPortletContext().getResourceAsStream("/index.html"); byte [] bytes = new byte[input.available()]; input.read(bytes, 0, input.available()); response.getPortletOutputStream().write(bytes); } }
log4j.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration debug="true" xmlns:log4j='https://jakarta.apache.org/log4j/'> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" </layout> </appender> <appender name="file" class="org.apache.log4j.FileAppender"> <param name="file" value="${catalina.home}/logs/log.log" <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" </layout> </appender> <root> <level value="ALL" <appender-ref ref="console" <appender-ref ref="file" </root> </log4j:configuration>
log.log
2014-09-22 21:31:26 DEBUG HelloPortlet:24 - Got File :: jndi:/localhost/PortletConceptsPartTwo/index.html 2014-09-22 21:31:27 DEBUG HelloPortlet:25 - Got File :: D:\Apache Pluto\pluto-2.0.3\webapps\PortletConceptsPartTwo\file:\c:\index.html
您应该注意以下重要说明:
我们使用了Apache Pluto提供的默认日志记录机制,因为如上面的INFO消息所示,这些消息将被注销到您的控制台上。
我们还使用Log4J API注销了相同的消息。
Apache一直建议您在应用程序中采用它。
您可以在新创建的文件log.log旁边的控制台上看到消息。
这些调试消息将使用Log4J日志记录机制打印出来。Log4j要求定义Appender,因此,定义了两个Appender。
一个用于控制台日志记录,另一个用于文件记录。请考虑一下,一旦记录器对象初始化,log4j.xml文件应该位于您的WEB-INF/classs下,以供考虑。
届会
Portlet应用程序可以使用PortletSession来跨多个客户端请求访问Portal页面来跟踪用户。
Portlet采用的" PortletSession"跟踪机制与Servlet API中已定义的机制非常相似。
Portlet可以通过针对PortletRequest实例(即RenderRequest或者ActionRequest)调用getPortletSession()方法来获取对PortletSession对象的访问权限。
与Servlet类似,为getPortletSession()提供了两种不同的支持。
getPortletSession()和getPortletSession(boolean)。
调用第一种方法将帮助您获取已经创建的PortletSession,如果没有创建" PortletSession",则会创建一个新的PortletSession。
第二种方法对于确定当前是否有PortletSession有用。
真值会使getPortletSession(boolean)
与getPortletSession()
类似,而假值将不允许在没有默认值的情况下创建PortletSession。 如果那里没有
PortletSession`,将返回空值。
每个Portlet应用程序都有自己的PortletSession
,因为您无法在不同Portlet应用程序之间共享PortletSession
。
除此之外,每个用户都有自己的" PortletSession"对象,因为您不能在多个用户之间共享同一" PortletSession"实例。
会议– Portlet范围与应用程序范围
如前所述,Portal既不在用户之间也不在Portlet应用程序之间共享PortletSession对象。
但是重要的是要知道,在使用PortletSession保留属性时,除非您有其他说明,否则这些属性将保留在PortletScope级别。
下面的示例向您展示了如何使用PortletSession针对PortletSession/PortletScope级别保留对象,以及这种方式对其他Portlet的影响是什么。
HelloPortlet.java
package com.theitroad.portlet; import java.io.IOException; import java.util.Enumeration; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.GenericPortlet; import javax.portlet.PortletConfig; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import org.apache.log4j.Logger; public class HelloPortlet extends GenericPortlet{ Logger logger = Logger.getLogger(HelloPortlet.class); public void init(PortletConfig config) throws PortletException { super.init(config); } public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException{ Enumeration<String> names = request.getPortletSession().getAttributeNames(); StringBuffer buffer = new StringBuffer(); while(names.hasMoreElements()){ String name = names.nextElement(); buffer.append(name+" :: "+request.getPortletSession().getAttribute(name)+"\n"); } response.getWriter().print("<form action="+response.createActionURL()+">" + "<p>Portlet Session Attributes</p>" + buffer + "<input type='submit' value='Just Do Action'" + "</form>"); } public void processAction(ActionRequest request, ActionResponse response) throws PortletException, IOException{ if(this.getPortletName().equals("HelloOne")){ request.getPortletSession().setAttribute("anonymousObject", "PortletSession Attribute"); } } }
portlet.xml
<?xml version="1.0" encoding="UTF-8"?> <portlet-app xmlns="https://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"> <portlet> <init-param> <name>PortletParam</name> <value>ParamValue</value> </init-param> <display-name>Hello Portlet One</display-name> <portlet-name>HelloOne</portlet-name> <portlet-class>com.theitroad.portlet.HelloPortlet</portlet-class> <description>Hello Portlet One</description> <portlet-info> <title>Hello Portlet One</title> <keywords>Hello, Portlet, One</keywords> <short-title>Hello Portlet One</short-title> </portlet-info> </portlet> <portlet> <init-param> <name>PortletParam</name> <value>ParamValue</value> </init-param> <display-name>Hello Portlet Two</display-name> <portlet-name>HelloTwo</portlet-name> <portlet-class>com.theitroad.portlet.HelloPortlet</portlet-class> <description>Hello Portlet Two</description> <portlet-info> <title>Hello Portlet Two</title> <keywords>Hello, Portlet, Two</keywords> <short-title>Hello Portlet Two</short-title> </portlet-info> </portlet> </portlet-app>
您可能需要注意一些重要事项:
我们使用相同的
HelloPortlet
实例定义了两个不同的Portlet。
" HelloOne"和" HelloTwo"是两个使用相同Portlet实例的不同Portlet。他们两个都定义了一个提交动作。
只有一个Portlet能够针对PortletSession添加新属性,并且可以肯定的是,该名称具有名称" HelloOne"。" HelloTwo" Portlet无法读取由" HelloOne"设置的PotletSession属性。
尽管" HelloOne"已设置" anonymousObject"属性,但" HelloTwo" Portlet的呈现阶段找不到针对" PortletSession"注册的任何属性。针对PortletSession调用setAttribute(name,value)将保留针对PortletSession/PortletScope范围的属性。
PortletScope属性存储在Portal页面上给定的Portlet实例中,如果页面上有多个Portlet实例,则PortletScope的每个属性都会有所不同。
当对象保存在PortletScope中的Session上时,该对象将在属性名称后附加一个"名称空间"前缀。
页面上每个Portlet实例的名称空间都不同。
现在,让我们看看如果将属性保存在" PortletSession/ApplicationScope"上,我们会有什么区别。
下面的示例向您展示了如何使用一个Portlet保留针对PortletSession/ApplicationScope的对象,并使用另一个Portlet读取对象。
HelloPortlet.java
package com.theitroad.portlet; import java.io.IOException; import java.util.Enumeration; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.GenericPortlet; import javax.portlet.PortletConfig; import javax.portlet.PortletException; import javax.portlet.PortletSession; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import org.apache.log4j.Logger; public class HelloPortlet extends GenericPortlet{ Logger logger = Logger.getLogger(HelloPortlet.class); public void init(PortletConfig config) throws PortletException { super.init(config); } public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException{ //Get all attributes' names that are defined along side of Application Scope Enumeration<String> names = request.getPortletSession().getAttributeNames(PortletSession.APPLICATION_SCOPE); StringBuffer buffer = new StringBuffer(); while(names.hasMoreElements()){ String name = names.nextElement(); //Get the attribute's value buffer.append(name+" :: "+request.getPortletSession().getAttribute(name,PortletSession.APPLICATION_SCOPE)+"\n"); } response.getWriter().print("<form action="+response.createActionURL()+">" + "<p>Portlet Session Attributes</p>" + buffer + "<input type='submit' value='Just Do Action'" + "</form>"); } public void processAction(ActionRequest request, ActionResponse response) throws PortletException, IOException{ if(this.getPortletName().equals("HelloOne")){ //Define attribute along side of Application Scope request.getPortletSession().setAttribute("anonymousObject", "PortletSession Attribute",PortletSession.APPLICATION_SCOPE); } } }
以下是上面列出的代码的详细说明:
我们没有使用默认的setAttribute(),而是使用了将范围作为参数的重载方法。
PortletSession定义两个常量; PORTLET_SCOPE和APPLICATION_SCOPE。
" HelloOne"已设置属性," HelloTwo"已读取属性。
Portlet应用程序中的任何Portlet都可以检索或者修改用户会话中存储为应用程序范围的任何属性。
您可以使用
getAttributesNames()
来获取在PortletSession端定义的所有属性名称。要从" PortletSession"中删除属性,可以使用" removeAttributes()"。
对于每个PortletSession的attribute方法,均使用范围作为参数的重载版本。
PortletSession与Servlet会话和JSP无缝集成。
稍后将对此概念进行深入讨论。
小区间通信
在启动Java Portlet规范之前,每个门户供应商都提供了自己的专有机制来实现门户间通信。
发布Java Portlet规范后,已经以一种标准的方式组织了端口间通信。
PortletSession已用于此目的,因为您必须注意以下问题:
Portlet将参数放置在会话的应用程序范围内。
如果您使用的Portlet容器支持缓存机制,请确保不要为Portlet启用缓存。
某些Portal不会实现缓存,如果支持缓存的Portlet在不支持缓存的Portal上运行,则该Portlet将正常运行。Portlet的修改必须在操作请求期间进行,以确保更新在呈现请求阶段传播到每个Portlet。