
Liferay는 엔터프라이즈급 디지털 경험 플랫폼으로, 다양한 커스터마이징이 가능한 플러그인 아키텍처를 제공합니다. 오늘은 Portlet, Theme, Hook, Ext 플러그인의 핵심 구조와 개발 방법을 심층적으로 분석해드리겠습니다. 개발자부터 기획자까지 반드시 알아야 할 Liferay 확장 메커니즘을 7가지 키포인트로 정리했습니다.
Liferay 플러그인 시스템의 핵심 구성 요소
Liferay의 플러그인 아키텍처는 모듈화된 확장성을 위해 특수 설계되었습니다. Portlet은 비즈니스 로직 구현, Theme는 UI/UX 디자인, Hook는 코어 기능 수정, Ext는 심층 커스터마이징을 담당합니다. 각 플러그인 유형별로 독립적인 빌드 시스템과 배포 메커니즘이 존재하며, OSGi 번들과의 연동을 통해 동적 모듈 관리를 지원합니다.
최신 Liferay DXP 7.4 기준으로 플러그인 개발 시 Gradle 빌드 시스템을 우선 권장하며, Maven 호환성도 완벽하게 유지됩니다. 플러그인 프로젝트 생성 시 Liferay Workspace를 활용하면 종속성 관리와 배포 프로세스가 단순화됩니다.
플러그인 유형별 특징
- Portlet: 사용자 인터페이스 컴포넌트 개발
- Theme: 전체적인 외관과 느낌 커스터마이징
- Hook: 기존 기능의 동작 방식 수정
- Ext: 코어 시스템의 근본적인 변경
Portlet 개발의 실제: MVC 패턴 구현
Portlet 개발의 핵심은 JSR-362 표준 준수와 MVCPortlet 프레임워크 활용에 있습니다. 기본 구조는 src/main/java에 Java 클래스, WEB-INF 폴더에 XML 설정 파일, resources에 정적 자원으로 구성됩니다.
기본 Portlet 구조
@Component(
immediate = true,
property = {
"com.liferay.portlet.display-category=category.sample",
"javax.portlet.display-name=Custom Portlet",
"javax.portlet.init-param.template-path=/",
"javax.portlet.init-param.view-template=/view.jsp"
},
service = Portlet.class
)
public class CustomPortlet extends MVCPortlet {
@ProcessAction(name="handleForm")
public void handleForm(ActionRequest request, ActionResponse response) {
// 비즈니스 로직 구현
String userInput = ParamUtil.getString(request, "userInput");
// 데이터 처리 및 검증
if (Validator.isNotNull(userInput)) {
// 서비스 레이어 호출
processUserData(userInput);
}
// 리다이렉트 또는 포워드
response.setRenderParameter("success", "true");
}
@Override
public void doView(RenderRequest renderRequest, RenderResponse renderResponse)
throws IOException, PortletException {
// 뷰 렌더링 전 데이터 준비
renderRequest.setAttribute("currentTime", new Date());
super.doView(renderRequest, renderResponse);
}
}Liferay의 Portlet Filter Chain은 요청 처리 파이프라인을 제어하며, OSGi 서비스 등록을 통해 모듈 간 결합도를 낮출 수 있습니다. 2023년 기준으로 React/Vue.js 기반의 프론트엔드 통합이 대세를 이루고 있습니다.
현대적인 프론트엔드 통합
// React 컴포넌트와의 통합 예시
import React from 'react';
import ReactDOM from 'react-dom';
class PortletApp extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: true
};
}
componentDidMount() {
// Liferay 서비스 호출
Liferay.Service('/custom.service/get-data', {}, (result) => {
this.setState({
data: result,
loading: false
});
});
}
render() {
return (
{this.state.loading ?
Loading... :
}
);
}
}
// Portlet 초기화
export default function(portletElementId) {
ReactDOM.render(
,
document.getElementById(portletElementId)
);
}Theme 개발의 기술적 심층 분석
Theme는 _diffs 폴더를 통해 상속 메커니즘을 구현합니다. 부모 테마의 1,200개 이상의 Velocity 템플릿 중 필요한 부분만 오버라이드 가능한 구조입니다. CSS 커스터마이징 시 custom.css 파일을 우선적으로 로드하며, CSS 프레임워크 통합 시 webpack.config.js 수정이 필수적입니다.
Theme 구조 및 커스터마이징
/* _diffs/css/custom.css */
.portlet-header {
background: linear-gradient(45deg, #2c3e50, #3498db);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 15px 20px;
margin-bottom: 20px;
}
.portlet-content {
background: #ffffff;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 20px;
}
/* 반응형 디자인 */
@media (max-width: 768px) {
.portlet-header {
padding: 10px 15px;
font-size: 14px;
}
}Velocity 템플릿 커스터마이징
## _diffs/templates/portal_normal.vm
<!DOCTYPE html>
<html class="$root_css_class" dir="#language("lang.dir")" lang="$w3c_language_id">
<head>
<title>$the_title - $company_name</title>
<meta content="initial-scale=1.0, width=device-width" name="viewport" />
$theme.include($top_head_include)
## 커스텀 메타 태그 추가
<meta name="description" content="$!page_description" />
<meta property="og:title" content="$the_title" />
</head>
<body class="$css_class">
<div id="wrapper">
<header id="banner" role="banner">
<div class="container-fluid">
## 커스텀 네비게이션 구현
#parse("$full_templates_path/navigation.vm")
</div>
</header>
<main id="content" role="main">
$theme.include($content_include)
</main>
<footer id="footer" role="contentinfo">
## 커스텀 푸터 구현
#parse("$full_templates_path/footer.vm")
</footer>
</div>
$theme.include($bottom_include)
</body>
</html>Liferay Theme Generator 10.x 버전부터는 CLI 기반의 yo liferay-theme 명령어로 프로젝트 생성이 가능해졌습니다. Lighthouse 성능 점수 90점 이상을 위한 최적화 기법으로 Critical CSS 인라인화, WebP 이미지 자동 변환 기능을 반드시 적용해야 합니다.
Hook 플러그인의 7가지 활용 시나리오
Hook는 JSP 오버라이드, 언어 속성 수정, 스트럿츠 액션 변경 등 7가지 주요 기능을 제공합니다. 특히 portal.properties 파일 수정 시 기존 값을 완전히 대체하는 것이 아니라 추가/수정하는 방식으로 동작합니다.
Hook 설정 파일 구조
<!-- liferay-hook.xml -->
<hook>
<!-- 포털 속성 오버라이드 -->
<portal-properties>portal-ext.properties</portal-properties>
<!-- JSP 오버라이드 -->
<custom-jsp-dir>/META-INF/custom_jsps</custom-jsp-dir>
<!-- 언어 속성 수정 -->
<language-properties>content/Language-ext.properties</language-properties>
<!-- 서비스 래퍼 -->
<service>
<service-type>com.liferay.portal.kernel.service.UserLocalService</service-type>
<service-impl>com.example.service.CustomUserLocalServiceWrapper</service-impl>
</service>
<!-- 스트럿츠 액션 오버라이드 -->
<struts-action>
<struts-action-path>/login/login</struts-action-path>
<struts-action-impl>com.example.struts.CustomLoginAction</struts-action-impl>
</struts-action>
</hook>7가지 주요 Hook 활용 시나리오
- JSP 페이지 커스터마이징: 기본 UI 컴포넌트 수정
- 언어 파일 확장: 다국어 지원 및 텍스트 변경
- 포털 속성 오버라이드: 시스템 설정 변경
- 서비스 래퍼: 기존 서비스 로직 확장
- 스트럿츠 액션 수정: 사용자 액션 처리 변경
- 모델 리스너: 엔티티 변경 이벤트 처리
- 인덱서 후처리: 검색 인덱싱 로직 커스터마이징
최신 개발 트렌드에서는 Hook 대신 Fragment 컬렉션을 활용한 JSP 오버라이드를 권장합니다. 2025년 기준으로 Hook 플러그인 사용 시 모듈 간 충돌 방지를 위해 Semantic Versioning을 철저히 관리해야 합니다.
Ext 플러그인의 고급 커스터마이징
Ext 플러그인은 Liferay 코어 클래스 직접 수정이 가능한 유일한 방법입니다. ext-impl, ext-kernel, ext-util 모듈로 구성되며, 약 150개의 주요 확장 포인트를 제공합니다.
Ext 플러그인 구조
ext-plugin/
├── extImpl/
│ └── src/ (portal-impl.jar 오버라이드)
│ ├── com/liferay/portal/service/impl/
│ └── com/liferay/portal/action/
├── extKernel/
│ └── src/ (portal-kernel.jar 확장)
│ ├── com/liferay/portal/kernel/service/
│ └── com/liferay/portal/kernel/model/
└── extUtil/
└── src/ (유틸리티 클래스 수정)
└── com/liferay/portal/util/Ext 플러그인 개발 예시
// extImpl/src/com/liferay/portal/service/impl/CustomUserLocalServiceImpl.java
public class CustomUserLocalServiceImpl extends UserLocalServiceImpl {
@Override
public User addUser(
long creatorUserId, long companyId, boolean autoPassword,
String password1, String password2, boolean autoScreenName,
String screenName, String emailAddress, long facebookId,
String openId, Locale locale, String firstName, String middleName,
String lastName, long prefixId, long suffixId, boolean male,
int birthdayMonth, int birthdayDay, int birthdayYear,
String jobTitle, long[] groupIds, long[] organizationIds,
long[] roleIds, long[] userGroupIds, boolean sendEmail,
ServiceContext serviceContext) throws PortalException {
// 커스텀 사용자 생성 로직
User user = super.addUser(
creatorUserId, companyId, autoPassword, password1, password2,
autoScreenName, screenName, emailAddress, facebookId, openId,
locale, firstName, middleName, lastName, prefixId, suffixId,
male, birthdayMonth, birthdayDay, birthdayYear, jobTitle,
groupIds, organizationIds, roleIds, userGroupIds, sendEmail,
serviceContext);
// 추가 비즈니스 로직 실행
executeCustomUserCreationLogic(user);
return user;
}
private void executeCustomUserCreationLogic(User user) {
// 커스텀 로직 구현
// 예: 외부 시스템 연동, 추가 프로필 설정 등
}
}Ant 기반의 clean deploy 방식을 사용하며, 2023년 이후부터는 Gradle의 incremental compilation 기능이 도입되었습니다. 주의할 점은 Ext 플러그인 업그레이드 시 Liferay 버전별 호환성을 반드시 검증해야 한다는 것입니다.
플러그인 유형별 비교 분석
| 배포 방식 | Hot | Hot | Hot | Cold |
| 재시작 필요성 | No | No | No | Yes |
| 커스텀 범위 | App | UI | Core | Kernel |
| 개발 난이도 | ★★☆ | ★★☆ | ★★★☆ | ★★★★ |
| 유지보수성 | High | High | Medium | Low |
| 성능 영향 | Low | Low | Medium | High |
| 업그레이드 복잡도 | Low | Low | Medium | Very High |
이 표에서 볼 수 있듯이 Ext 플러그인은 최후의 수단으로만 사용해야 하며, 가능하면 Hook나 OSGi 컴포넌트로 대체하는 것이 좋습니다.
플러그인 개발 Best Practice 5선
1. 개발 환경 최적화
Liferay IDE의 Developer Studio 플러그인 활용 시 생산성 40% 향상을 기대할 수 있습니다. 통합 개발 환경에서 코드 자동 완성, 디버깅, 배포까지 원스톱으로 처리 가능합니다.
2. CI/CD 파이프라인 구축
CI/CD 파이프라인 구축 시 Jenkins보다 GitHub Actions가 더 효율적입니다. 클라우드 기반의 빌드 환경과 다양한 액션 마켓플레이스를 활용할 수 있습니다.
# .github/workflows/liferay-build.yml
name: Liferay Build and Deploy
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
- name: Build with Gradle
run: ./gradlew build
- name: Deploy to Liferay
run: ./gradlew deploy3. 모니터링 및 성능 관리
모니터링 도구로 Liferay PMC(Performance Monitoring Center) 필수 연동을 통해 실시간 성능 추적이 가능합니다.
4. 보안 검증 강화
보안 검증을 위해 OWASP Dependency-Check 빌드 통합으로 취약점을 사전에 차단할 수 있습니다.
5. 테스트 자동화
모듈 테스트 시 Mockito보다 Liferay의 Arquillian 확장 사용 권장으로 실제 Liferay 환경에서의 통합 테스트가 가능합니다.
최신 개발 트렌드 및 미래 전망
2025년 개발 스택 동향
2025년 현재 Gradle+Kotlin 조합이 점유율 68%로 가장 선호되는 개발 스택이며, Web Components 기반의 프론트엔드 아키텍처가 주목받고 있습니다.
마이크로 프론트엔드 아키텍처
// 마이크로 프론트엔드 통합 예시
import { loadMicroFrontend } from '@liferay/frontend-js-web';
class MicroFrontendPortlet extends React.Component {
componentDidMount() {
loadMicroFrontend({
moduleName: 'user-management-mf',
containerId: 'user-management-container',
props: {
userId: this.props.userId,
permissions: this.props.permissions
}
});
}
render() {
return (
<div>
<h2>사용자 관리</h2>
<div id="user-management-container"></div>
</div>
);
}
}클라우드 네이티브 배포
Kubernetes 기반의 컨테이너 배포와 Helm 차트를 활용한 자동화된 배포 파이프라인이 표준이 되고 있습니다.
마무리
Liferay 플러그인 개발은 시스템 확장의 핵심 기술입니다. Portlet으로 기능 확장, Theme로 사용자 경험 개선, Hook로 핵심 동작 수정, Ext로 심층 커스터마이징을 구현할 수 있습니다.
플러그인 개발 시 반드시 Liferay의 Semantic Versioning 정책을 준수해야 장기적인 유지보수가 가능하며, 각 플러그인 유형의 특성을 이해하고 적절한 선택을 통해 효율적인 개발이 가능합니다.
현대적인 개발 환경에서는 마이크로 프론트엔드 아키텍처와 클라우드 네이티브 배포를 고려한 설계가 중요하며, 지속적인 모니터링과 성능 최적화를 통해 안정적인 엔터프라이즈 포털을 구축할 수 있습니다.
'IT기술 > Liferay Portal' 카테고리의 다른 글
| Liferay Service Builder 완전 가이드: 엔터프라이즈급 데이터 레이어 자동 생성 도구 (6) | 2025.07.21 |
|---|---|
| Liferay Portal 첫 포틀릿 개발 완전 가이드: Hello World부터 배포까지 (10) | 2025.07.20 |
| Liferay Portal 협업 도구 완전 가이드: 기업 내부 소셜 플랫폼의 혁신 (14) | 2025.07.19 |
| Liferay Portal 검색 및 SEO 최적화 완전 가이드: 엔터프라이즈 포털의 검색 성능 극대화 (8) | 2025.07.19 |
| Liferay Portal CMS 완전 가이드: 비전문가도 쉽게 만드는 전문적인 웹사이트 (10) | 2025.07.19 |