Liferay最大的好处是不仅接口强大,利于扩展,就连JSP定制扩展都提供了3种方式。

修改核心jsp代码,有3种修改方式:
1、暴力修改
直接修改(位于portal-web/docroot/html),编译部署,会带来风险,而且不能同步更新。

2、全量扩展修改
热部署jsp文件(替代原有jsp),这是v7.0下的OSGi方式,实现方式非常优雅。

3、CustomJspBag Hook方式
实现CustomJspBag接口,做jsp片段式的修改,同样是增量热部署,也是v7.0下的OSGi方式(需要增加依赖org.osgi.service.component.annotations.Activate和org.osgi.service.component.annotations.Component;),实现方式可以说更加优雅。

第一种方式不讲了。

2、全量扩展修改

需要新建fragment module。只需要注意2点:
1、在module工程的OSGi定义(bnd.bnd文件)中指定Fragment-Host声明
如下:
Bundle-Version: 1.0.0
Fragment-Host: com.liferay.login.web;bundle-version="1.0.0"
-sources: true

2、把你修改后的JSP文件放在module工程的resources目录,比如
你的module工程\src\main\resources\META-INF\resources\login.jsp

login.jsp例子:

<%--
/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */
--%>

<%@ include file="/init.jsp" %>
<p style="color: red">changed</p>
<c:choose>
    <c:when test="<%= themeDisplay.isSignedIn() %>">

        <%
        String signedInAs = HtmlUtil.escape(user.getFullName());

        if (themeDisplay.isShowMyAccountIcon() && (themeDisplay.getURLMyAccount() != null)) {
            String myAccountURL = String.valueOf(themeDisplay.getURLMyAccount());

            signedInAs = "<a class=\"signed-in\" href=\"" + HtmlUtil.escape(myAccountURL) + "\">" + signedInAs + "</a>";
        }
        %>

        <liferay-ui:message arguments="<%= signedInAs %>" key="you-are-signed-in-as-x" translateArguments="<%= false %>" />
    </c:when>
    <c:otherwise>

        <%
        String redirect = ParamUtil.getString(request, "redirect");

        String login = LoginUtil.getLogin(request, "login", company);
        String password = StringPool.BLANK;
        boolean rememberMe = ParamUtil.getBoolean(request, "rememberMe");

        if (Validator.isNull(authType)) {
            authType = company.getAuthType();
        }
        %>

        <portlet:actionURL name="/login/login" secure="<%= PropsValues.COMPANY_SECURITY_AUTH_REQUIRES_HTTPS || request.isSecure() %>" var="loginURL" />

        <aui:form action="<%= loginURL %>" autocomplete='<%= PropsValues.COMPANY_SECURITY_LOGIN_FORM_AUTOCOMPLETE ? "on" : "off" %>' cssClass="sign-in-form" method="post" name="fm" onSubmit="event.preventDefault();">
            <aui:input name="saveLastPath" type="hidden" value="<%= false %>" />
            <aui:input name="redirect" type="hidden" value="<%= redirect %>" />
            <aui:input name="doActionAfterLogin" type="hidden" value="<%= portletName.equals(PortletKeys.FAST_LOGIN) ? true : false %>" />

            <c:choose>
                <c:when test='<%= SessionMessages.contains(request, "userAdded") %>'>

                    <%
                    String userEmailAddress = (String)SessionMessages.get(request, "userAdded");
                    String userPassword = (String)SessionMessages.get(request, "userAddedPassword");
                    %>

                    <div class="alert alert-success">
                        <c:choose>
                            <c:when test="<%= company.isStrangersVerify() || Validator.isNull(userPassword) %>">
                                <liferay-ui:message key="thank-you-for-creating-an-account" />

                                <c:if test="<%= company.isStrangersVerify() %>">
                                    <liferay-ui:message arguments="<%= userEmailAddress %>" key="your-email-verification-code-has-been-sent-to-x" translateArguments="<%= false %>" />
                                </c:if>
                            </c:when>
                            <c:otherwise>
                                <liferay-ui:message arguments="<%= userPassword %>" key="thank-you-for-creating-an-account.-your-password-is-x" translateArguments="<%= false %>" />
                            </c:otherwise>
                        </c:choose>

                        <c:if test="<%= PrefsPropsUtil.getBoolean(company.getCompanyId(), PropsKeys.ADMIN_EMAIL_USER_ADDED_ENABLED) %>">
                            <liferay-ui:message arguments="<%= userEmailAddress %>" key="your-password-has-been-sent-to-x" translateArguments="<%= false %>" />
                        </c:if>
                    </div>
                </c:when>
                <c:when test='<%= SessionMessages.contains(request, "userPending") %>'>

                    <%
                    String userEmailAddress = (String)SessionMessages.get(request, "userPending");
                    %>

                    <div class="alert alert-success">
                        <liferay-ui:message arguments="<%= userEmailAddress %>" key="thank-you-for-creating-an-account.-you-will-be-notified-via-email-at-x-when-your-account-has-been-approved" translateArguments="<%= false %>" />
                    </div>
                </c:when>
            </c:choose>

            <liferay-ui:error exception="<%= AuthException.class %>" message="authentication-failed" />
            <liferay-ui:error exception="<%= CompanyMaxUsersException.class %>" message="unable-to-log-in-because-the-maximum-number-of-users-has-been-reached" />
            <liferay-ui:error exception="<%= CookieNotSupportedException.class %>" message="authentication-failed-please-enable-browser-cookies" />
            <liferay-ui:error exception="<%= NoSuchUserException.class %>" message="authentication-failed" />
            <liferay-ui:error exception="<%= PasswordExpiredException.class %>" message="your-password-has-expired" />
            <liferay-ui:error exception="<%= UserEmailAddressException.MustNotBeNull.class %>" message="please-enter-an-email-address" />
            <liferay-ui:error exception="<%= UserLockoutException.LDAPLockout.class %>" message="this-account-is-locked" />

            <liferay-ui:error exception="<%= UserLockoutException.PasswordPolicyLockout.class %>">

                <%
                UserLockoutException.PasswordPolicyLockout ule = (UserLockoutException.PasswordPolicyLockout)errorException;
                %>

                <liferay-ui:message arguments="<%= ule.user.getUnlockDate() %>" key="this-account-is-locked-until-x" translateArguments="<%= false %>" />
            </liferay-ui:error>

            <liferay-ui:error exception="<%= UserPasswordException.class %>" message="authentication-failed" />
            <liferay-ui:error exception="<%= UserScreenNameException.MustNotBeNull.class %>" message="the-screen-name-cannot-be-blank" />

            <aui:fieldset>

                <%
                String loginLabel = null;

                if (authType.equals(CompanyConstants.AUTH_TYPE_EA)) {
                    loginLabel = "email-address";
                }
                else if (authType.equals(CompanyConstants.AUTH_TYPE_SN)) {
                    loginLabel = "screen-name";
                }
                else if (authType.equals(CompanyConstants.AUTH_TYPE_ID)) {
                    loginLabel = "id";
                }
                %>

                <aui:input autoFocus="<%= windowState.equals(LiferayWindowState.EXCLUSIVE) || windowState.equals(WindowState.MAXIMIZED) %>" cssClass="clearable" label="<%= loginLabel %>" name="login" showRequiredLabel="<%= false %>" type="text" value="<%= login %>">
                    <aui:validator name="required" />
                </aui:input>

                <aui:input name="password" showRequiredLabel="<%= false %>" type="password" value="<%= password %>">
                    <aui:validator name="required" />
                </aui:input>

                <span id="<portlet:namespace />passwordCapsLockSpan" style="display: none;"><liferay-ui:message key="caps-lock-is-on" /></span>

                <c:if test="<%= company.isAutoLogin() && !PropsValues.SESSION_DISABLED %>">
                    <aui:input checked="<%= rememberMe %>" name="rememberMe" type="checkbox" />
                </c:if>
            </aui:fieldset>

            <aui:button-row>
                <aui:button type="submit" value="sign-in" />
            </aui:button-row>
        </aui:form>

        <liferay-util:include page="/navigation.jsp" servletContext="<%= application %>" />

        <aui:script sandbox="<%= true %>">
            var form = AUI.$(document.<portlet:namespace />fm);

            form.on(
                'submit',
                function(event) {
                    var redirect = form.fm('redirect');

                    if (redirect) {
                        var redirectVal = redirect.val();

                        redirect.val(redirectVal + window.location.hash);
                    }

                    submitForm(form);
                }
            );

            form.fm('password').on(
                'keypress',
                function(event) {
                    Liferay.Util.showCapsLock(event, '<portlet:namespace />passwordCapsLockSpan');
                }
            );
        </aui:script>
    </c:otherwise>
</c:choose>

3、CustomJspBag Hook方式

这是一种覆盖原有XXXX-ext.jsp的方式,XXXX-ext.jsp是空文件,下面的例子用来覆盖 bottom.jsp ,插入了新定义 bottom-ext.jsp 片段进来
文件位于 src/main/resources/META-INF/jsps/html/common/themes/bottom-ext.jsp
bottom-ext.jsp 只有一行

<h2>HERE I AM!!!!!</h2>

然后实现CustomJspBag接口,实现类YourCustomJspBag ,有2点需要特别注意

service.ranking是优先级的意思,数值越大越优先,在例子中是100,当有另外的CustomJspBag实现类,比如定义为200,那么定义为200的类的优先级更高。

关键方法是activate,作用是为所有的需要自定义的核心JSPs添加URL(目录路径)到List列表,List列表的用途有些过滤器的含义,例子中是添加"META-INF/jsps/"下的所有文件。

import com.liferay.portal.deploy.hot.CustomJspBag;
import com.liferay.portal.kernel.url.URLContainer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

@Component(
    immediate = true,
    property = {
        "context.id=YourCustomJspBag",
        "context.name=Custom JSP Bag",
        "service.ranking:Integer=100"
    }
)
public class YourCustomJspBag implements CustomJspBag {

    @Override
    public String getCustomJspDir() {
        return "META-INF/jsps/";
    }

    /**
     * 返回自定义JSP URL paths列表.
     */
    @Override
    public List<String> getCustomJsps() {
        return _customJsps;
    }

    @Override
    public URLContainer getURLContainer() {
        return _urlContainer;
    }

    @Override
    public boolean isCustomJspGlobal() {
        return true;
    }

    //osgi激活时触发的动作
    @Activate
    protected void activate(BundleContext bundleContext) {
        _bundle = bundleContext.getBundle();

        _customJsps = new ArrayList<>();

        Enumeration<URL> entries = _bundle.findEntries(
            getCustomJspDir(), "*.jsp", true);

        while (entries.hasMoreElements()) {
            URL url = entries.nextElement();
            //*.jsp全部添加进JSP URL paths
            _customJsps.add(url.getPath());
        }
    }

    private Bundle _bundle;
    private List<String> _customJsps;

    private final URLContainer _urlContainer = new URLContainer() {

        @Override
        public URL getResource(String name) {
            return _bundle.getEntry(name);
        }

        @Override
        public Set<String> getResources(String path) {
            Set<String> paths = new HashSet<>();

            for (String entry : _customJsps) {
                if (entry.startsWith(path)) {
                    paths.add(entry);
                }
            }

            return paths;
        }

    };

}

gradle的配置文件build.gradle

dependencies {
compile 'com.liferay.portal:com.liferay.portal.kernel:2.0.0'
compile 'com.liferay.portal:com.liferay.portal.impl:2.0.0'
compile 'org.osgi:org.osgi.core:6.0.0'
compile 'org.osgi:org.osgi.service.component.annotations:1.3.0'
}

version = '1.0.0'

部署后的界面:

CustomJspBag后面的秘密

在portal-web/docroot/html/common/themes/bottom.jsp 文件,在其最下方,有以下代码:

<liferay-util:include page="/html/common/themes/bottom-ext.jsp" />

原来必须要依靠原来的JSP包括了一个空的bottom-ext.jsp文件,这是前提
实际上它只是覆盖了bottom-ext.jsp,而不是它的宿主bottom.jsp

即所有类似XXXX-ext.jsp这样的文件,都是可以做定制的。

那么看到这里就很清晰第三种方式(CustomJspBag Hook)和第二种方式(全量扩展修改)之间的区别了,即 片段覆盖全量覆盖的区别,这需要您根据需求来做选择。
Liferay给开发者这两种选择,目的很清晰,即通过CustomJspBag Hook来降低风险,做合理的分离设计。

Liferay7 BPM门户开发之42: Liferay核心JSP定制扩展的更多相关文章

  1. Liferay7 BPM门户开发之37: Liferay7下的OSGi Hook集成开发

    hook开发是Liferay客制扩展的一种方式,比插件灵活,即可以扩展liferay门户,也能对原有特性进行更改,Liferay有许多内置的服务,比如用hook甚至可以覆盖Liferay服务. 可作为 ...

  2. Liferay7 BPM门户开发之46: 集成Activiti用户、用户组、成员关系同步

    在实际的BPM集成开发过程中,Liferay和Activiti这两个异构的系统之间,用户.组的同步需求非常重要,用来实现签收组的概念,比如指定签收组.会签.抢签都需要用到. Activiti可以通过自 ...

  3. Liferay7 BPM门户开发之26: 集成Activiti到Liferay7

    开发顺序: 实战任务1,开发BPM管理后台(用于在Liferay管理中心管理Activiti模型上传) 一个熟悉Portlet操作的项目,为开发打好基础. http://www.cnblogs.com ...

  4. Liferay7 BPM门户开发之38: OSGi模块化Bndtools、Maven、Gradle开发构建入门

    前言 OSGi是目前动态模块系统的事实上的工业标准,它适用于任何需要模块化.面向服务.面向组件的应用程序.Eclipse如此庞大和复杂的插件体系,就是基于OSGi.Liferay也是基于OSGi.OS ...

  5. Liferay7 BPM门户开发之29: 核心kernel.util包下面的通用帮助类ParamUtil、GetterUtil使用

    与其闭门造车,不如直接开动原装.进口.免费的法拉利. -- 作者说 不多说废话,直接上代码. ParamUtil ParamUtil.GetterUtil是Liferay最重要的帮助类 ParamUt ...

  6. Liferay7 BPM门户开发之32: 实现自定义认证登陆(定制Authentication Hook)

    第一步:修改liferay-hook.xml <?xml version="1.0"?> <!DOCTYPE hook PUBLIC "-//Lifer ...

  7. Liferay7 BPM门户开发之44: 集成Activiti展示流程列表

    处理依赖关系 集成Activiti之前,必须搞清楚其中的依赖关系,才能在Gradle里进行配置. 依赖关系: 例如,其中activiti-engine依赖于activiti-bpmn-converte ...

  8. Liferay7 BPM门户开发之36: 使用Portlet filters过滤器做切面AOP

    使用Portlet filters过滤器做切面AOP Portlet Filters定义于JSR286 Java Portlet Specification 2.0 Portlet Filters是为 ...

  9. Liferay7 BPM门户开发之34: liferay7对外服务类生成(RestService Get Url)

    在liferay7中开发不依赖Service Builder的对外服务类,非常简洁,只需要2点注解: 在类的前部定义: @ApplicationPath("/PathXXX") 方 ...

随机推荐

  1. C#+arcengine10.0+SP5实现鹰眼(加载的是mdb数据库中的数据)

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  2. Error 403--Forbidden

    转自他人:From RFC 2068 Hypertext Transfer Protocol -- HTTP/1.1:10.4.4 403 ForbiddenThe server understood ...

  3. C++学习进度0

    昨天,又把<C++ primer> 刷了一遍,这一次看的是陈硕大大的评注版,重点看了陈硕的注释,<Accelerated C++>去年就把代码巧了一遍,<C++ prim ...

  4. IOS开发--常用工具类收集整理(Objective-C)(持续更新)

    前言:整理和收集了IOS项目开发常用的工具类,最后也给出了源码下载链接. 这些可复用的工具,一定会给你实际项目开发工作锦上添花,会给你带来大大的工作效率. 重复造轮子的事情,除却自我多练习编码之外,就 ...

  5. js中的this中使用

    请先查看:http://www.jb51.net/article/41656.htm 情况一:纯粹的函数调用 这是函数的最通常用法,属于全局性调用,因此this就代表全局对象Global. 情况二:作 ...

  6. 把Java程序打包成jar文件包并执行

    1.首先要确认自己写的程序有没有报错. 2.第一次我写的是Web Project到现在,我一直没有执行成功,所以最好创建的是java Project 打包步骤: 1.在项目上,右键,选择Export. ...

  7. partition by

    在row_number() over()中的over使用,其后需要配合order by   ROW_NUMBER () over (partition by username   order by i ...

  8. 七牛云存储Python SDK使用教程 - 上传策略详解

    文 七牛云存储Python SDK使用教程 - 上传策略详解 七牛云存储 python-sdk 七牛云存储教程 jemygraw 2015年01月04日发布 推荐 1 推荐 收藏 2 收藏,2.7k  ...

  9. 原生jdbc执行存储过程

    //定时任务,结转 . //表名 fys_sch_lvyou2 ,存储过程名:fys_sch_lvyou2_carrayover //无参调用:{call insertLine} //有参调用:{ca ...

  10. ViewPager中GridView问题

    GridView 嵌套在ViewPager中问题. 1. GridView属性设置无法显示. 正常显示方式 <GridView android:padding="8dip" ...