Java Portlet示例教程
在有关Apache Pluto的介绍中,我们主要讨论了您可能拥有的最简单的Portlet应用程序。
业务场景比正常情况要复杂得多,它们需要有关Portlet,Portlet生命周期以及如何提供符合Portlet领域最新标准的兼容增强Portlet的全面知识。
有关GenericPortlet的更多信息
正如我们在建议的引言中指出的那样,我们开发的Portlet被扩展为GenericPortlet类,因为它是抽象类。
根据Portlet Javadoc,GenericPortlet类为Portlet接口提供了默认实现(稍后将在此处进行讨论)。
它提供了一个抽象类,可以将其抽象化以创建Portlet。
GenericPortlet的子类应使用以下注释之一:@ ProcessAction,@ ProcessEvent和@RenderMode或者重写以下至少一种方法:
processAction
:处理动作请求(将在本教程后面讨论)。doView
:在Portlet处于View模式时处理渲染请求。doEdit:当Portlet处于Edit模式时处理渲染请求。
doHelp
:在Portlet处于帮助模式时处理渲染请求。" init"和" destroy":管理在Servlet生命周期内保留的资源。
通常,不需要重写render
或者doDispatch
方法,因为render
方法处理渲染请求,在响应中设置Portlet的标题并调用doDispatch
。
反过来,使用doDispatch方法,根据请求中指示的Portlet模式,将请求分派到doView,doEdit或者doHelp方法之一。
您可能会发现它如此重要的主要问题是Portlet以多线程方式运行,因此在处理共享资源时要小心。
共享资源可以采用不同的形式;它包括内存数据(例如实例或者类变量),外部资源(例如文件),数据库连接和网络连接。
在您所介绍的示例中,我们重写了doView方法来显示消息。
我们对示例本身做了一些修改,因此您应该注意以下几点:
由于使用了@RenderMode批注来注释称为inspectRenderMode的新方法,因此我们消除了doView方法的重载。
我们已经更新了pom.xml文件,因此无需每次获取生成的WAR并将其复制到Tomcat的webapp部署文件夹中。
这是通过使用"集成测试"阶段来完成的,该阶段要求" Ant"将其置于您的后面。
package com.theitroad.portlet; import java.io.IOException; import javax.portlet.GenericPortlet; import javax.portlet.PortletConfig; import javax.portlet.PortletException; import javax.portlet.RenderMode; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class HelloWorldPortlet extends GenericPortlet { @RenderMode(name="View") public void inspectRenderMode(RenderRequest request, RenderResponse response) throws PortletException, IOException { response.getWriter().print( "Jounral Dev Provides you Hello World Portlet Using Annotations !"); } public void init( PortletConfig config ) throws PortletException { super.init( config ); } }
<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>HelloWorldPortlet</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>HelloWorldPortlet Maven Webapp</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> <phase>integration-test</phase> <configuration> <tasks> <!-- Use this to build and deploy the testsuite war. PORTLET_DEPLOY_DIR is an environmental variable pointing to the hot-deploy directory of your portal. You can also use -Ddeploy.dir=<path to deployment dir> on the command line when invoking maven: mvn -Ddeploy.dir=/pluto-1.1.4/webapps integration-test --> <property environment="env" <property name="deploy.dir" value="${env.PORTLET_DEPLOY_DIR}" <copy file="target/${pom.build.finalName}.war" tofile="${deployFolder}/${pom.build.finalName}.war" </tasks> </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> </build> </project>
以下是上面列出的代码的详细说明:
JSR-286引入了使用注解来控制事物的方法,这些事物通常是通过重写GenericPortlet方法来控制的。
我们使用了@renderMode而不是像以前那样覆盖doView方法。我们添加了" antrun"插件,以取代以前使用的旧的部署方式。
现在,您真正想要做的是执行mvn clean集成测试软件包以将WAR复制并直接部署在Tomcat的webapp文件夹下。
此后将使用此过程。
最后,如果您想提供这种类型的集成的特殊业务,则可以制作GenericPortlet的子类并让您的开发人员扩展它。
关于PortletRequest和PortletResponse的更多信息
Portlet API中的" PortletRequest"接口代表ActionRequest和RenderRequest中的常用功能,它们实际上都是PortletRequest的子类。
" PortletRequest"提供了有关用户请求(例如请求的参数),用户会话,门户,Portlet应用程序以及有关Portlet本身的其他信息的所有必需信息。
每当请求Portlet时,Portlet容器都会将所有Portlet请求和响应对象传递给Portlet。
另一方面,Portlet在每个请求之后将响应对象发送回Portal。
通常,响应对象包含Portlet的内容片段,任何请求的Portlet模式或者窗口状态以及稍后讨论的其他几条信息。
实际上,Portlet开发人员尚未使用PortletRequest和PortletResponse的实际实例。
但是,开发人员将使用RenderRequest,ActionRequest,RenderResponse和ActionResponse。
您可能会问RenderRequest,RenderResponse和ActionRequest,ActionResponse之间的区别,以及为什么要这样分类。
下图向您展示了主要的区别,不久将在接下来的各节中进行所有详细的说明。
Type | Description | Who's Created It |
---|---|---|
RenderRequest | The RenderRequest represents the request sent to the Portlet to handle a render. | The Portlet container creates a RenderRequest object and passes it as argument to the Portlet's render() method. |
RenderResponse | The RenderResponse defines an object to assist a Portlet in sending a response to the Portal. | The Portlet container creates a RenderResponse object and passes it as argument to the Portlet's render() method. |
ActionRequest | The ActionRequest represents the request sent to the Portlet to handle an action. | The Portlet container creates an ActionRequest object and passes it as argument to the Portlet's processAction() method. |
ActionResponse | The ActionResponse represents the response for the response to an action request. | The Portlet container creates an ActionResponse object and passes it as argument to the Portlet's processAction() method. |
正如您在图中所看到的,Portlet规范定义了两种不同的请求/响应类型,渲染的请求/响应和操作的请求/响应主要是您每次进入Portlet开发任务时可以围绕的唯一对象。
这种区别在处理Portlet业务时非常有帮助,因为Portlet可能会在每次刷新所包含的Portal页面时呈现,而操作是在用户要求的特定时间执行的。
为了清楚起见,下面的示例向您展示了调用render()
和processAction()
之间的区别。
package com.theitroad.portlet; import java.io.IOException; 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; public class HelloWorldPortlet extends GenericPortlet { private static int renderCount = 0; private static int actionCount = 0; public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException { synchronized(this){ renderCount++; } response.getWriter().print("<form action="+response.createActionURL()+">" + "<p>Render has executed "+renderCount+"</p>" + "<p>Render has executed "+actionCount+"</p>" + "<input type='submit'" + "</form>"); } public void processAction(ActionRequest actionRequest, ActionResponse actionResponse){ synchronized(this){ actionCount++; } } public void init( PortletConfig config ) throws PortletException { super.init( config ); } }
以下是上面列出的代码的详细说明:
您可能会注意到,每次渲染门户页面时,渲染计数器都会增加一。
" GenericPortlet"渲染方法已将控件委托给" doView",该控件已在" HelloWorldPortlet"中覆盖。在您点击提交操作之前,操作计数器不会增加。
" GenericPortlet""processAction`方法已将控件委托给" ProcessAction"方法,该方法已在" HelloWorldPortlet"中被覆盖。由于未使用批注,因此我们已使用Portlet规范1.0实现了渲染和动作。
由于每次刷新门户页面时都会调用一次render方法,因此很明显这就是为什么`render'不是放置您可能需要的任何业务逻辑的合适位置。
如果假设我们有一个类似邮件发送的业务逻辑,那么每次刷新门户页面时都会发送邮件。
增强Portlet
Portlet功能并没有在这里停止,我们将进行一些改进以展示这些功能的其他方面。
通常,Portlet不会呈现纯HTML,它包含JavaScript,图像和StyleSheets等其他资源。
此外,查看模式不是让最终用户联系的唯一模式,Portlet还支持EDIT和HELP模式。
主要是,您可以为一种配置提供EDIT模式,同时,HELP模式是提供有关Portlet本身的有用信息的好地方。
除此之外,提供Portlet标题并不是那么复杂的任务,因为您可以使用portlet-info
XML标签提供默认值,所以您还可以通过编程方式更改标题。
更改Portlet标题
通常,Portlet规范引入了portlet-info XML标记,以允许开发人员指定与Portlet的标题,简称和关键字相关的这些信息,这些信息可用于分类目的。
您可能需要使用三种最简单的方法让Portlet获得其标题,这可能是最简单的方法。
在" portlet-info"内的" portlet.xml"文件上定义标题,或者使用资源包获取国际化描述性标题或者以编程方式提供标题。
以下示例向您展示了如何利用两个其他方案来确保像我们前面第一个方案那样设置了所需的标题。
package com.theitroad.portlet; import java.util.Locale; import java.util.ResourceBundle; import javax.portlet.GenericPortlet; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class TitleChangedPortlet extends GenericPortlet{ public void doView(RenderRequest request, RenderResponse response) throws PortletException, java.io.IOException { response.getWriter().println("My Name Is:"+this.getPortletName()); } public java.lang.String getTitle(RenderRequest request) { //Check whether the name of the portlet is programmaticTitleChangePortlet if(this.getPortletName().equals("ProgrammaticTitleChangePortlet")){ //If it's like that, just get the defined bundle ResourceBundle bundle = this.getPortletConfig().getResourceBundle(new Locale("en")); //Retrun the string that's corresponded for anyTitle property return (String)bundle.getObject("anyTitle"); } //else return the default, either that's defined in the portlet.xml or that's defined in the bundle return super.getTitle(request); } }
package com.theitroad.portlet; import java.io.IOException; 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; public class HelloWorldPortlet extends GenericPortlet { private static int renderCount = 0; private static int actionCount = 0; public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException { synchronized(this){ renderCount++; } response.getWriter().print("<form action="+response.createActionURL()+">" + "<p>Render has executed "+renderCount+"</p>" + "<p>Action has executed "+actionCount+"</p>" + "<input type='submit'" + "</form>"); } public void processAction(ActionRequest actionRequest, ActionResponse actionResponse){ synchronized(this){ actionCount++; } } public void init( PortletConfig config ) throws PortletException { super.init( config ); } }
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>Write here a short description about your portlet.</description> <portlet-name>HelloWorldPortlet</portlet-name> <display-name>HelloWorldPortlet Portlet</display-name> <portlet-class>com.theitroad.portlet.HelloWorldPortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>Title Is Set Using Simplest Way Portlet-Info XML Tag</title> <short-title>Hello World</short-title> <keywords>Hello,pluto</keywords> </portlet-info> </portlet> <portlet> <description>It's a Portlet that's setting title using Resource Bundle</description> <portlet-name>ResourceBundleTitleChangePortlet</portlet-name> <display-name>ResourceBundleTitleChangePortlet</display-name> <portlet-class>com.theitroad.portlet.TitleChangedPortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <supported-locale>en</supported-locale> <resource-bundle>message</resource-bundle> </portlet> <portlet> <description>It's a Portlet that's setting title programmatically</description> <portlet-name>ProgrammaticTitleChangePortlet</portlet-name> <display-name>ProgrammaticTitleChangePortlet</display-name> <portlet-class>com.theitroad.portlet.TitleChangedPortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <supported-locale>en</supported-locale> <resource-bundle>message</resource-bundle> </portlet> </portlet-app>
message.properties
javax.portlet.title=Title Is Set By Resource Bundle anyTitle=Title Is Set Programmatically
以下是上面显示的代码的详细说明:
可以将相同的Portlet使用不同的名称。
我们已经两次使用TitleChangedPortlet
;一次以ResourceBundleTitleChangePortlet的名称命名,第二次以ProgrammaticTitleChangePortlet的名称命名。" HelloWorldPortlet"使用在" portlet.xml"文件中定义的标题。
ResourceBundleTitleChangePortlet
使用在资源包内定义的标题。
您已经注意到,必须使用众所周知的常量,因为它们必须符合Portlet规范。
以下是Portlet级别资源束的常量:
Constant | Description |
---|---|
javax.portlet.title | The,title that should be displayed in the titlebar of,this portlet. Only one title per locale is allowed. Note,that this title Jan be overrided by the portal or,programmatically by the portlet. |
javax.portlet.short-title | A short,version of the title that Jan be used for,devices with limited display,capabilities. Only one,short title per locale is allowed. |
javax.portlet.keywords | Keywords,describing the functionality of the portlet.,Portals that allow users to search for portlets based,on keywords Jan use these keywords. Multiple,keywords per locale are allowed, but must be,separated by commas ‘,’. |
javax.portlet.description | Description,of the portlet. |
javax.portlet.display-name | Name,under which this portlet is displayed at,deployment time or to tools. The display name need,not be unique. |
javax.portlet.app.custom-portletmode.<,name>.decoration-name | Decoration,name for the portlet managed custom,portlet mode . |
通常,您应该在与Portlet相同的包中定义资源,以确保每个Portlet都使用了其相关消息。
" ProgrammaticTitleChangePortlet"使用以编程方式更改的标题。
即使我们可以提供静态文本,也可以提供捆绑消息的另一种用法。如果您需要以编程方式提供标题,则必须对已经从GenericPortlet继承的getTitle()方法进行覆盖。
如果您使用资源包来定义Portlet的伴随文本,则必须确保在" portlet.xml"中定义资源文件(例如message.properties)后,该文件位于类路径中。
以编程方式更改标题的另一种方法是,您可以在任何委托的方法(即doView,doEdit和doHelp)中针对" renderResponse"对象调用" setTitle()"。
Portlet URL
众所周知,Portlet不是像Servlet这样的独立资源,可以通过定义良好的URL(即https://servername:port/web-context/servlet-mapping)进行访问。
Portlet只能通过位于其上的Portal页面进行访问。
但是,Portlet可以在Portlet容器中创建一个以自身为目标的URL(即,通过单击链接或者提交表单),结果是向Portal的门户发送新的客户端请求,就像我们在上面提交HelloWorldPortlet时所做的那样。
无论如何,这些URL称为Portlet URL。
这样,Portlet URL将在门户页面中引用该Portlet的实例(如果您有两个不同的Portlet引用了相同的Portlet类,则它们将具有不同的Portlet URL)。
这些URL的创建是Portlet容器的职责,因为它还将Portlet URL解析为Portlet请求的参数。
Portlet创建代表Portlet URL的PortletURL对象。
Portlet本身可以使用RenderResponse类上的两种方法之一来创建这些PortletURL对象:
createActionURL():用于为HTML表单或者链接创建操作URL。
这些操作URL对于在Portlet上处理操作(即Portlet的状态修改)(即对Portlet调用processAction()覆盖方法)很有用。createRenderURL()
:用于为任何不包含Portlet状态修改的任务创建渲染URL。
即使您可以在不带参数的情况下将Portlet URL添加到内容中,但是您也可以通过对调用createActionURL()
后返回的PortletURL
对象调用setParameter(name,value)
来在Portlet URL上设置参数。
或者createRenderURL()。
在下一个教程中将提供更多的说明和示例,该教程旨在涵盖渲染器请求/响应和操作请求/响应的所有技术细节,以及之后如何设置和使用参数。
doEdit和doHelp方法
实际上,我们没有对任何类型的普通格式都使用doEdit
方法,它主要用于指定Portlet的状态,以控制使用doView
完成的普通格式调用时Portlte的行为。
同时,一些Portlet提供了如此复杂的业务,用户可能需要某种帮助才能使Portlet正常工作,并为此提供了doHelp
。
定义Portlet的模式并不是那么复杂的任务,您所需要做的就是在supports标签中使用" portlet-mode"来指定Portlet支持的模式。
以下示例向您展示了查看"编辑和帮助"模式的方式。
package com.theitroad.portlet; import java.util.Locale; import java.util.ResourceBundle; import javax.portlet.GenericPortlet; import javax.portlet.PortletException; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; public class TitleChangedPortlet extends GenericPortlet{ public void doView(RenderRequest request, RenderResponse response) throws PortletException, java.io.IOException { response.getWriter().println("My Name Is:"+this.getPortletName()); } public void doEdit(RenderRequest request, RenderResponse response) throws PortletException, java.io.IOException { response.getWriter().println("Edit Mode On Portlet Name :"+this.getPortletName()); } public void doHelp(RenderRequest request, RenderResponse response) throws PortletException, java.io.IOException { response.getWriter().println("Help Mode On Portlet Name :"+this.getPortletName()); } public java.lang.String getTitle(RenderRequest request) { //Check whether the name of the portlet is programmaticTitleChangePortlet if(this.getPortletName().equals("ProgrammaticTitleChangePortlet")){ //If it's like that, just get the defined bundle ResourceBundle bundle = this.getPortletConfig().getResourceBundle(new Locale("en")); //Retrun the string that's corresponded for anyTitle property return (String)bundle.getObject("anyTitle"); } //else return the default, either that's defined in the portlet.xml or that's defined in the bundle return super.getTitle(request); } }
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>Write here a short description about your portlet.</description> <portlet-name>HelloWorldPortlet</portlet-name> <display-name>HelloWorldPortlet Portlet</display-name> <portlet-class>com.theitroad.portlet.HelloWorldPortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> </supports> <portlet-info> <title>Title Is Set Using Simplest Way Portlet-Info XML Tag</title> <short-title>Hello World</short-title> <keywords>Hello,pluto</keywords> </portlet-info> </portlet> <portlet> <description>It's a Portlet that's setting title using Resource Bundle</description> <portlet-name>ResourceBundleTitleChangePortlet</portlet-name> <display-name>ResourceBundleTitleChangePortlet</display-name> <portlet-class>com.theitroad.portlet.TitleChangedPortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> <portlet-mode>EDIT</portlet-mode> <portlet-mode>HELP</portlet-mode> </supports> <supported-locale>en</supported-locale> <resource-bundle>message</resource-bundle> </portlet> <portlet> <description>It's a Portlet that's setting title programmatically</description> <portlet-name>ProgrammaticTitleChangePortlet</portlet-name> <display-name>ProgrammaticTitleChangePortlet</display-name> <portlet-class>com.theitroad.portlet.TitleChangedPortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>VIEW</portlet-mode> <portlet-mode>EDIT</portlet-mode> <portlet-mode>HELP</portlet-mode> </supports> <supported-locale>en</supported-locale> <resource-bundle>message</resource-bundle> </portlet> </portlet-app>
以下是上面列出的代码的详细说明:
您必须定义要支持Portlet的方式。
HelloWorldPortlet确实支持查看模式,因为它的Portlet没有编辑或者帮助。
另外两个Portlet定义了所有可用模式。
Portal提供了模式用户界面,用于在Portlet支持的模式之间切换。