JSP의 다양한 Include 방식(<%@ include %>, <jsp:include>, <c:import>, Apache Tiles)에서 jQuery document.ready가 어떤 순서로 실행되는지 정리합니다. 실무에서 자주 쓰는 네임스페이스 패턴과 비동기 의존성 처리 패턴도 함께 다룹니다.


1. Include 방식별 비교

1-1. 지시자 Include (<%@ include %>)

컴파일 타임에 소스 코드를 물리적으로 합침

<%-- ========== parent.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <script src="/js/jquery-3.6.0.min.js"></script>
</head>
<body>

<%@ include file="header.jsp" %>

<main>
    <h1>본문 영역</h1>
</main>

<script>
$(document).ready(function() {
    console.log('parent ready');
});
</script>

</body>
</html>
<%-- ========== header.jsp ========== --%>
<header>
    <h2>헤더 영역</h2>
</header>

<script>
$(document).ready(function() {
    console.log('header ready');
});
</script>

컴파일 결과 (단일 Servlet으로 합쳐짐)

public class parent_jsp extends HttpServlet {
    public void _jspService(HttpServletRequest request, HttpServletResponse response) {
        out.write("<!DOCTYPE html>\n");
        out.write("<html>\n<head>\n");
        out.write("<script src=\"/js/jquery-3.6.0.min.js\"></script>\n");
        out.write("</head>\n<body>\n");

        // <%@ include %> - header.jsp 코드가 직접 삽입됨
        out.write("<header>\n<h2>헤더 영역</h2>\n</header>\n");
        out.write("<script>\n$(document).ready(function() {\n");
        out.write("    console.log('header ready');\n});\n</script>\n");

        out.write("<main>\n<h1>본문 영역</h1>\n</main>\n");
        out.write("<script>\n$(document).ready(function() {\n");
        out.write("    console.log('parent ready');\n});\n</script>\n");
        out.write("</body>\n</html>");
    }
}

실행 순서

header ready
parent ready

1-2. 액션 태그 Include (<jsp:include>)

런타임에 출력 결과를 삽입

<%-- ========== parent.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <script src="/js/jquery-3.6.0.min.js"></script>
</head>
<body>

<script>
$(document).ready(function() {
    console.log('1. parent ready');
});
</script>

<jsp:include page="child1.jsp">
    <jsp:param name="title" value="자식1" />
</jsp:include>

<jsp:include page="child2.jsp">
    <jsp:param name="title" value="자식2" />
</jsp:include>

<jsp:include page="child3.jsp">
    <jsp:param name="title" value="자식3" />
</jsp:include>

</body>
</html>
<%-- ========== child1.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<section>
    <h2>${param.title}</h2>
    <p>자식1 컨텐츠</p>
</section>

<script>
$(document).ready(function() {
    console.log('2. child1 ready');
});
</script>
<%-- ========== child2.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<section>
    <h2>${param.title}</h2>
    <p>자식2 컨텐츠</p>
</section>

<script>
$(document).ready(function() {
    console.log('3. child2 ready');
});
</script>
<%-- ========== child3.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<section>
    <h2>${param.title}</h2>
    <p>자식3 컨텐츠</p>
</section>

<script>
$(document).ready(function() {
    console.log('4. child3 ready');
});
</script>

브라우저가 받는 최종 HTML

<!DOCTYPE html>
<html>
<head>
    <script src="/js/jquery-3.6.0.min.js"></script>
</head>
<body>

<script>
$(document).ready(function() {
    console.log('1. parent ready');
});
</script>

<section>
    <h2>자식1</h2>
    <p>자식1 컨텐츠</p>
</section>

<script>
$(document).ready(function() {
    console.log('2. child1 ready');
});
</script>

<section>
    <h2>자식2</h2>
    <p>자식2 컨텐츠</p>
</section>

<script>
$(document).ready(function() {
    console.log('3. child2 ready');
});
</script>

<section>
    <h2>자식3</h2>
    <p>자식3 컨텐츠</p>
</section>

<script>
$(document).ready(function() {
    console.log('4. child3 ready');
});
</script>

</body>
</html>

실행 순서

1. parent ready
2. child1 ready
3. child2 ready
4. child3 ready

1-3. JSTL Import (<c:import>)

런타임 처리, 외부 URL 지원

<%-- ========== parent.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <script src="/js/jquery-3.6.0.min.js"></script>
</head>
<body>

<%-- 방법1: 직접 출력 --%>
<c:import url="header.jsp" />

<%-- 방법2: 변수에 저장  출력 --%>
<c:import url="footer.jsp" var="footerHtml" />
${footerHtml}

<%-- 방법3: 파라미터 전달 --%>
<c:import url="menu.jsp">
    <c:param name="activeMenu" value="home" />
</c:import>

<%-- 방법4: 외부 URL (jsp:include는 불가능) --%>
<c:import url="http://api.example.com/widget.html" />

</body>
</html>

2. Apache Tiles 연동

2-1. Tiles 설정 파일

<?xml version="1.0" encoding="UTF-8"?>
<!-- ========== tiles-def.xml ========== -->
<!DOCTYPE tiles-definitions PUBLIC
    "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
    "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">

<tiles-definitions>

    <!-- 기본 레이아웃 정의 -->
    <definition name="layout.base" template="/WEB-INF/tiles/layout.jsp">
        <put-attribute name="header"  value="/WEB-INF/tiles/header.jsp" />
        <put-attribute name="menu"    value="/WEB-INF/tiles/menu.jsp" />
        <put-attribute name="content" value="" />
        <put-attribute name="footer"  value="/WEB-INF/tiles/footer.jsp" />
    </definition>

    <!-- 메인 페이지 -->
    <definition name="main" extends="layout.base">
        <put-attribute name="content" value="/WEB-INF/views/main/main.jsp" />
    </definition>

    <!-- 목록 페이지 -->
    <definition name="board.list" extends="layout.base">
        <put-attribute name="content" value="/WEB-INF/views/board/list.jsp" />
    </definition>

</tiles-definitions>

2-2. 레이아웃 및 각 컴포넌트

<%-- ========== /WEB-INF/tiles/layout.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>전자정부 프레임워크</title>

    <!-- 공통 CSS -->
    <link rel="stylesheet" href="/css/common.css">

    <!-- jQuery -->
    <script src="/js/jquery-3.6.0.min.js"></script>

    <!-- 공통 JS -->
    <script src="/js/common.js"></script>

    <script>
    $(document).ready(function() {
        console.log('[1] layout.jsp ready');
    });
    </script>
</head>
<body>

    <tiles:insertAttribute name="header" />

    <div class="container">
        <tiles:insertAttribute name="menu" />
        <tiles:insertAttribute name="content" />
    </div>

    <tiles:insertAttribute name="footer" />

</body>
</html>
<%-- ========== /WEB-INF/tiles/header.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<header id="header">
    <div class="logo">
        <a href="/">CHEMP 시스템</a>
    </div>
    <div class="user-info">
        <span>홍길동님</span>
        <a href="/logout">로그아웃</a>
    </div>
</header>

<script>
$(document).ready(function() {
    console.log('[2] header.jsp ready');

    // 헤더 초기화 로직
    $('#header .logo a').on('click', function(e) {
        console.log('로고 클릭');
    });
});
</script>
<%-- ========== /WEB-INF/tiles/menu.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<nav id="menu">
    <ul>
        <li><a href="/board/list">게시판</a></li>
        <li><a href="/product/list">제품관리</a></li>
        <li><a href="/approval/list">승인관리</a></li>
    </ul>
</nav>

<script>
$(document).ready(function() {
    console.log('[3] menu.jsp ready');

    // 현재 페이지 메뉴 활성화
    var currentPath = window.location.pathname;
    $('#menu a').each(function() {
        if (currentPath.indexOf($(this).attr('href')) === 0) {
            $(this).addClass('active');
        }
    });
});
</script>
<%-- ========== /WEB-INF/views/board/list.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<main id="content">
    <h1>게시판 목록</h1>

    <table id="boardTable">
        <thead>
            <tr>
                <th>번호</th>
                <th>제목</th>
                <th>작성자</th>
                <th>작성일</th>
            </tr>
        </thead>
        <tbody>
            <c:forEach var="item" items="${list}">
                <tr>
                    <td>${item.boardNo}</td>
                    <td>${item.title}</td>
                    <td>${item.writer}</td>
                    <td>${item.regDate}</td>
                </tr>
            </c:forEach>
        </tbody>
    </table>
</main>

<script>
$(document).ready(function() {
    console.log('[4] content(list.jsp) ready');

    // 게시판 테이블 초기화
    $('#boardTable tbody tr').on('click', function() {
        var boardNo = $(this).find('td:first').text();
        location.href = '/board/view?boardNo=' + boardNo;
    });
});
</script>
<%-- ========== /WEB-INF/tiles/footer.jsp ========== --%>
<%@ page contentType="text/html; charset=UTF-8" %>
<footer id="footer">
    <p>Copyright 2024 한국화학물질관리협회</p>
</footer>

<script>
$(document).ready(function() {
    console.log('[5] footer.jsp ready');
});
</script>

콘솔 출력 결과

[1] layout.jsp ready
[2] header.jsp ready
[3] menu.jsp ready
[4] content(list.jsp) ready
[5] footer.jsp ready

3. 실무 패턴

3-1. 전역 네임스페이스 패턴

<%-- ========== /js/common.js 또는 layout.jsp ========== --%>
<script>
/**
 * 전역 애플리케이션 객체
 */
var APP = window.APP || {
    // 초기화 콜백 큐
    readyQueue: [],

    // 공통 데이터 저장소
    data: {},

    // 이벤트 저장소
    events: {},

    /**
     * 초기화 함수 등록
     */
    onReady: function(callback) {
        this.readyQueue.push(callback);
    },

    /**
     * 모든 초기화 함수 실행
     */
    init: function() {
        for (var i = 0; i < this.readyQueue.length; i++) {
            try {
                this.readyQueue[i]();
            } catch (e) {
                console.error('초기화 오류:', e);
            }
        }
    },

    /**
     * 커스텀 이벤트 등록
     */
    on: function(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push(callback);
    },

    /**
     * 커스텀 이벤트 발생
     */
    trigger: function(eventName, data) {
        var callbacks = this.events[eventName] || [];
        for (var i = 0; i < callbacks.length; i++) {
            callbacks[i](data);
        }
    }
};

// DOM Ready 시 모든 컴포넌트 초기화
$(document).ready(function() {
    APP.init();
});
</script>
<%-- ========== header.jsp ========== --%>
<script>
APP.onReady(function() {
    console.log('header 초기화');

    // 헤더 관련 초기화 로직
});
</script>
<%-- ========== content.jsp ========== --%>
<script>
APP.onReady(function() {
    console.log('content 초기화');

    // 본문 관련 초기화 로직
});
</script>

3-2. 비동기 의존성 처리 패턴

<%-- ========== layout.jsp ========== --%>
<script>
$(document).ready(function() {
    // 공통 코드 데이터 로드
    $.ajax({
        url: '/api/common/codes',
        method: 'GET',
        success: function(response) {
            APP.data.codes = response;
            APP.trigger('codesLoaded', response);
        },
        error: function() {
            console.error('공통 코드 로드 실패');
        }
    });
});
</script>
<%-- ========== content.jsp ========== --%>
<script>
// 공통 코드 로드 완료 후 실행
APP.on('codesLoaded', function(codes) {
    console.log('공통 코드 수신 완료');

    // 셀렉트박스 초기화
    var statusCodes = codes.STATUS || [];
    var $select = $('#statusSelect');

    statusCodes.forEach(function(code) {
        $select.append('<option value="' + code.codeId + '">' + code.codeName + '</option>');
    });
});
</script>

3-3. 중복 초기화 방지 패턴

<%-- ========== common-datepicker.jsp (여러 곳에서 include됨) ========== --%>
<script>
(function() {
    // 이미 초기화되었으면 종료
    if (window._datepickerInitialized) {
        return;
    }
    window._datepickerInitialized = true;

    $(document).ready(function() {
        // 날짜 선택기 초기화 (한 번만 실행)
        $('.datepicker').datepicker({
            dateFormat: 'yy-mm-dd',
            changeMonth: true,
            changeYear: true
        });
    });
})();
</script>

3-4. 컴포넌트별 모듈 패턴

<%-- ========== /WEB-INF/views/board/list.jsp ========== --%>
<script>
/**
 * 게시판 목록 모듈
 */
var BoardList = (function() {

    // private 변수
    var $table = null;
    var $searchForm = null;

    // private 함수
    function bindEvents() {
        // 행 클릭
        $table.on('click', 'tbody tr', function() {
            var boardNo = $(this).data('board-no');
            goDetail(boardNo);
        });

        // 검색 폼 제출
        $searchForm.on('submit', function(e) {
            e.preventDefault();
            search();
        });
    }

    function goDetail(boardNo) {
        location.href = '/board/view?boardNo=' + boardNo;
    }

    function search() {
        $searchForm.get(0).submit();
    }

    // public 함수
    function init() {
        $table = $('#boardTable');
        $searchForm = $('#searchForm');

        bindEvents();

        console.log('BoardList 초기화 완료');
    }

    // 외부 공개 API
    return {
        init: init
    };

})();

// DOM Ready 시 초기화
$(document).ready(function() {
    BoardList.init();
});
</script>

4. 비교 요약표

구분 <%@ include %> <jsp:include> <c:import> <tiles:insertAttribute>
처리 시점 컴파일 타임 런타임 런타임 런타임
결과물 단일 Servlet 별도 Servlet 호출 별도 요청 RequestDispatcher
파라미터 불가 <jsp:param> <c:param> <tiles:putAttribute>
외부 URL 불가 불가 가능 불가
변수 공유 동일 스코프 request 스코프 request 스코프 request 스코프
변경 반영 재컴파일 필요 즉시 반영 즉시 반영 즉시 반영
ready 순서 선언 순서 HTML 출력 순서 HTML 출력 순서 HTML 출력 순서

5. 핵심 정리

jQuery의 ready 핸들러는 등록 순서대로 실행되며, 이는 최종 HTML에서 스크립트가 나타나는 순서와 일치합니다.

상황 document.ready 동작
<%@ include %> 단일 파일로 합쳐짐, 선언 순서대로 실행
<jsp:include> HTML 출력 순서대로 큐에 등록, 순차 실행
Tiles insertAttribute jsp:include와 동일
AJAX 동적 로딩 DOM이 이미 ready면 즉시 실행

출처