Portlet生命周期
如前所述,Portlet在概念上与Servlet非常相似,因为它们只能在容器内运行。
Servlet和Portlet都有其设计必须满足的义务,以允许它们与容器进行交互。
当您应该实现doget(),doPost(),doDelete()等时,您还必须实现Portlet的特定方法,例如doView(),doHelp(),doEdit()。
等
首先,我们通过实现Portlet
接口而不是使用GenericPortlet
来开发我们的第一个Portlet,并重点介绍最重要的事情。
实现Portlet接口
通常,您可以通过扩展GenericPortlet
,扩展了GenericPortlet的任何类或者实现Portlet接口来开发Portlet。
package com.theitroad.portlet; import java.io.IOException; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.Portlet; import javax.portlet.PortletConfig; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class LifecyclePortlet implements Portlet { private static int renderCount = 0; private static int actionCount = 0; private static int initCount = 0; @Override public void init(PortletConfig config) throws PortletException { initCount++; } @Override public void processAction(ActionRequest request, ActionResponse response) throws PortletException, IOException { synchronized(this){ actionCount++; } } @Override public void render(RenderRequest request, RenderResponse response) throws PortletException, IOException { synchronized(this){ renderCount++; } response.getWriter().print("<form action="+response.createActionURL()+">" + "<p> The number of initiated Portlets by the container is :: "+initCount+"</p>" + "<p> The number of processed actions by the container is :: "+actionCount+"</p>" + "<p> The number of achieved render by the container is :: "+renderCount+"</p>" +"<input value='Submit' type='submit'<br" + "<a href='"+response.createRenderURL()+"'>Render Again</a>" + "</form>"); } @Override public void destroy() { initCount--; System.out.println("The number of Portlets get deployed :: "+initCount); } }
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.theitroad</groupId> <artifactId>LifecyclePortlet</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>LifecyclePortlet</name> <url>https://maven.apache.org</url> <properties> <deployFolder>D:/Apache Pluto/pluto-2.0.3/webapps</deployFolder> </properties> <dependencies> <!-- Java Portlet Specification V2.0 --> <dependency> <groupId>org.apache.portals</groupId> <artifactId>portlet-api_2.0_spec</artifactId> <version>1.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <!-- bind 'pluto2:assemble' goal to 'process-resources' lifecycle --> <!-- This plugin will read your portlet.xml and web.xml and injects required lines --> <plugin> <groupId>org.apache.portals.pluto</groupId> <artifactId>maven-pluto-plugin</artifactId> <version>2.1.0-M3</version> <executions> <execution> <phase>generate-resources</phase> <goals> <goal>assemble</goal> </goals> </execution> </executions> </plugin> <!-- configure maven-war-plugin to use updated web.xml --> <!-- This plugin will make sure your WAR will contain the updated web.xml --> <plugin> <artifactId>maven-war-plugin</artifactId> <version>2.1.1</version> <configuration> <webXml>${project.build.directory}/pluto-resources/web.xml</webXml> </configuration> </plugin> <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>integration-test</phase> <configuration> <tasks> <copy file="target/${project.build.finalName}.war" tofile="${deployFolder}/${project.build.finalName}.war" </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> <execution> <id>delete</id> <phase>clean</phase> <configuration> <tasks> <delete file="${deployFolder}/${project.build.finalName}.war" <delete dir="${deployFolder}/${project.build.finalName}" </tasks> <detail>true</detail> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> <finalName>${project.artifactId}</finalName> </build> </project>
portlet.xml
<?xml version="1.0" encoding="UTF-8"?> <portlet-app xmlns="https://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" version="1.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd https://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"> <portlet> <description>First Portlet</description> <portlet-name>PortletOne</portlet-name> <display-name>First Portlet</display-name> <portlet-class>com.theitroad.portlet.LifecyclePortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>Lifecycle Portlet</title> <short-title>Lifecycle Portlet</short-title> <keywords>Lifecycle</keywords> </portlet-info> </portlet> <portlet> <description>Second Portlet</description> <portlet-name>PortletTwo</portlet-name> <display-name>Second Portlet</display-name> <portlet-class>com.theitroad.portlet.LifecyclePortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>Lifecycle Portlet</title> <short-title>Lifecycle Portlet</short-title> <keywords>Lifecycle</keywords> </portlet-info> </portlet> </portlet-app>
以下是上面列出的代码的详细说明:
执行`mvn clean Integration-test package'将使您的项目打包并针对Apache Pluto进行部署。
LifecyclePortlet已实现了Portal界面,因为显然必须实现所有合同方法。
Portlet部署描述符
portlet.xml
定义了两个引用相同Portlet类的不同Portlet。
这样的部署是可以接受的,因为可以部署使用相同名称引用相同Portlet类的任意数量的Portlet。
也可能是部署两个具有相同名称的Portlet,但要确保将它们部署到两个不同的上下文中。当它通过实现
Portlet
接口来创建Portlet时,应重写render()
,processAction()
,init()
,destroy()
。当部署Portlet时,Portlet容器将在初始化阶段调用
init()
。当客户激活了显示在Portlet上的Submit按钮时,Portlet容器已收到这样的调用,该调用又称为
processAction()
方法。当客户端激活了在Portlet上渲染的renderAgain链接时,Portlet容器已收到这样的调用,并且该调用又称为
render()
方法。如果您对LifecyclePortlet项目执行了" mvn clean",则应注意,您的WAR文件及其未打包的格式(WAR文件夹)已删除。
删除上下文将导致您定义的Portlet被销毁。为了删除您的WAR文件,在
maven-antrun-plugin
中添加了新的执行程序。
现在,让我们看看如果删除了已部署的WAR及其未打包的文件夹会发生什么。
绝对,删除已部署的应用程序将使上下文侦听器被调用,因为它将破坏已部署的所有资源,包括portlet.xml文件中的这些已定义Portlet。
因为我们有两个已部署的Portlet(PortletOne,PortletTwo),所以我们在destroy()方法中打印出了剩余Portlet的数量。
此方法的调用类似于Servlet的destroy()
方法,该方法在取消部署所包含的项目后就会调用。
如果您仔细查看过控制台消息,则应该注意到Portlet上下文/LifecyclePortlet-0.0.1-SNAPSHOT已被删除。
通常,取消部署和上下文删除的概念可以互换使用。
Portlet生命周期
如此简单,我们可以将Portlet生命周期分解为以下几个阶段:
创建Portlet。
处理许多用户请求(或者可能没有)。
Portlet的除去和垃圾回收。
让我们深入研究这些阶段。
Portlet创建阶段
Portlet的创建是上述阶段中最昂贵,最复杂的阶段,因为它包含三个不同的步骤:加载类,调用构造函数和初始化Portlet。
通常,容器能够在构造函数调用之前加载Portlet所需的类。
与可能包含许多类和库的整个应用程序相比,已加载的Portlet被视为次要部分。
因此,规范要求必须使用与加载的Portlet所引用的Portlet应用程序其余部分相同的" classloader"来完成Portlet类的加载。
当Portlet容器启动Portlet应用程序(WAR)或者容器确定Portlet用于满足某些请求时,Portlet容器可能会决定创建已加载的Portlet类的实例。
您必须注意的最重要的事情是初始化Portlet资源所需的时间,Portlet将等待该时间直到完成为第一个请求提供服务。
满足第一个请求后,随后的每个请求都将正常处理。
在初始化Portlet时,会传递一个PortletConfig接口的Object实例,该实例被视为Portlet定义的唯一实例,并提供对初始化参数和为Portlet定义中的Portlet配置的资源束的访问。
在成功调用init()
方法之前,该Portlet不会被视为活动Portlet。
如果您的Portlet类提供了一些静态初始化块,则它们不得触发任何进行此假设的方法。
您可以使用Portlet配置对象(PortleConfig
)提供许多针对Portlet本身的配置,例如数据库连接URL等。
Init()方法的性质很容易出错,您可能会访问已关闭的数据库,或者您没有适当的权限。
对于这种情况,init()
已定义为抛出" PortletException"。
您也可以在两者之间抛出UnavailableException,但是无论抛出何种异常,都必须确保释放所有获取的资源,因为随后不会调用destroy()方法。
但是,初始化失败确实意味着Portlet容器不会将Portlet对象置于活动服务中,并且必须释放Portlet对象。
根据Java Portlet Specification 2.0,Portlet容器可以在失败后的任何时间重新尝试实例化和初始化Portlet。
如果您已使用UnavilableException
并提供了等待时间,则Portlet容器必须等待指定的时间才能创建和初始化新的Portlet对象。
实际上,此功能
如果在初始化过程中出现" RuntimeException",则必须将其作为" PortletException"处理。
请求处理阶段
Portlet初始化后,就开始等待用户交互。
与Portlet的交互可以通过一组请求完成,这些请求被转换为render()
和processAction()
请求。
动作请求正在要求Portlet更改其基础应用程序的状态,而渲染请求正在显示具有当前状态的Portlet。
实际上,一旦processAction()完成后,便会启动后续的render()请求。
如前所述,您可以分别针对传递的响应对象调用createRenderURL()
和createActionURL()
来提出渲染请求和动作请求。
通常,为render()
方法传递的RenderRequest负责提供:
Portlet窗口的状态(最小化,最大化等)
Portlet的模式(例如VIEW,EDIT等)
Portlet的上下文。
与Portlet关联的会话(包括授权信息)。
与Portlet关联的首选项信息。
从发布表单在渲染URL上设置的所有渲染参数,或者在processAction()方法期间设置的任何渲染参数。
RenderResponse还负责将Portlet的内容片段写入门户页面,因为它还可以将URL渲染到该内容中,这将调用对Portlet的操作(例如createActionURL(),createRenderURL()等)。
" ActionRequest"对象代表更改Portlet状态的机会,它提供了PortletRequest已经提供的所有内容,以及对门户用户发出的HTTP请求内容的直接访问。
为了响应processAction(),Portlet应该更新其ActionResponse对象,该对象提供以下方法:
将客户端重定向到新的页面。
更改Portlet的模式。
添加或者修改用户会话的呈现参数。
上面建议的processAction()
对actionCount计数器进行了微不足道的更改,因此不必通过响应将任何更改通知容器。
以下示例向您展示了如何使用ActionResponse
来更改响应。
package com.theitroad.portlet; import java.io.IOException; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.Portlet; import javax.portlet.PortletConfig; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class LifecyclePortlet implements Portlet { private static int renderCount = 0; private static int actionCount = 0; private static int initCount = 0; @Override public void init(PortletConfig config) throws PortletException { initCount++; } @Override public void processAction(ActionRequest request, ActionResponse response) throws PortletException, IOException { synchronized(this){ actionCount++; } response.sendRedirect(request.getContextPath()+"/index.html"); } @Override public void render(RenderRequest request, RenderResponse response) throws PortletException, IOException { synchronized(this){ renderCount++; } response.getWriter().print("<form action="+response.createActionURL()+">" + "<p> The number of initiated Portlets by the container is :: "+initCount+"</p>" + "<p> The number of processed actions by the container is :: "+actionCount+"</p>" + "<p> The number of achieved render by the container is :: "+renderCount+"</p>" +"<input value='Submit' type='submit'<br" + "<a href='"+response.createRenderURL()+"'>Render Again</a>" + "</form>"); } @Override public void destroy() { initCount--; System.out.println("The number of Portlets get deployed :: "+initCount); } }
index.html
<!DOCTYPE html> <html> <head> <meta charset="ISO-8859-1"> <title>ActionResponse Sample</title> </head> <body> <p>ActionResponse redirects the client into this page</p> </body> </html>
这里有一些重要的注意事项:
已通过使用" ActionRequest"确定了Portlet应用程序的上下文路径。
" ActionResponse"会通过使用其他内容(即index.html)来重定向要处理的操作请求。
Portlet销毁阶段
如前所述,初始化阶段失败后,不会将destroy()方法作为后续阶段调用。
为了允许Portlet容器销毁某个Portlet,必须成功实例化和初始化Portlet,并且必须完成Portlet实例上的所有处理线程。
如果您已与第三方集成,则destroy()
方法是通知这些第三方有关Portlet变得不可用的最佳位置。
根据Java Portlet Specification 2.0,在destroy()方法完成后,Portlet容器必须释放Portlet对象,以便可以进行垃圾收集。
Portlet实施不应使用终结器。
线程问题
根据Java Portlet Specification 2.0,Portlet容器能够同时处理请求。
Portlet开发人员必须设计自己的Portlet,以处理processAction()和render()方法或者任何可选生命周期方法(例如prcoessEvent()或者serveResource())中多个线程的并发执行。
任何特定的时间。
实际上,不能保证请求和响应对象的实现都是线程安全的。
这意味着它们只能在调用processAction(),render(),processEvent()和serveResource()方法的线程范围内使用。
为了保持可移植性,Portlet应用程序不应将请求和响应对象的引用提供给在其他线程中执行的对象,因为结果可能是不确定的。
换句话说,除非Portlet使用实例变量和/或者外部资源,否则同时调用" render()"和/或者" processAction()"的任何组合和数量都是安全的。
正如我们之前在提供的示例中所指出的那样,所有计数器都已定义为实例变量,因此,当它们被并发线程处理时并不安全。
为了同时处理所有这些增量,我们在一个同步块中声明了它们。