생초보도 따라할 수 있는 JDK1.8 기반 웹 어플리케이션 바닥부터 작성하기 #1

Table of Contents

[TOC]

1. 개요

이전 포스팅에서 legacy 프로젝트를 준비하면서 좀더 기초에서 시작해 보자 라는 생각이 들었다.
지난 스터디 레포트 연재물에서는 포스팅 회차내 결말을 봐야 한다는 의무감 때문에 대충 넘어간 부분이 많았다.
그래서 본 연재에서는 조금 더디더라도 완전 바닥부터 하나하나 짚어 보면서 가보겠다.

우선 본 프로젝트에서 특징부터 나열해 보자면...

  • JDK-1.8 인 이유 : 현재 최신 JDK 가 21 버젼이 넘어가는데도 아직까지 인프라 문제 때문에 10년도 넘은 구형 JDK-1.8 을 사용하는 곳이 근근히 있다.

  • Java EE vs Jakarta EE 변경문제 : 패키지명 때문에 같은 로직을 사용해도 상호 호환성이 없으며 이에 따른 의존모듈이 통째로 바뀐다 이 때문에 구형 Jeus 또는 구형 Weblogic 을 사용하는 인프라 시스템 에서는 Java EE 를 사용해야 하는 경우가 생긴다. (초반에 인프라 구성을 잘 알고 시작해야 한다.)

  • 위 사항에 연계한 패키지 호환성 문제등으로 인해 legacy 환경 (JDK-1.8, Java EE)으로 개발하는 것을 목표로 한다.

  • 추후 상위버젼의 JDK + Jakarta EEKotlin-DSL 을 활용한 예시도 연재에 추가해 보겠다.

2. 개발툴 내려받기, 설치하기

우선 JDK 를 내려 받아 설치한다. (본 포스팅에서는 1.8 기준)

https://www.oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html

아래 주소에서 gradle 을 내려받는다. (현시점 v8.14 최신)

https://gradle.org/releases/

wrapper 실행 후에는 재사용 할일이 없으므로 설치는 하지 않고 사용자 다운로드 폴더에 압축 해제해서 사용 하겠다. ( 예시: C:\사용자\XXX\다운로드\, /home/XXX/Downloads 등 )

3. 프로젝트 초기화

우선 gradle 초기화 부터 시작해 보겠다.

gradle 은 각 사용자 폴더 기준 다운로드 또는 Downloads 폴더에 압축 해제되어 있다고 가정하겠다.

(예시 : C:\Users\사용자\Downloads\gradle-8.14)

우선 커맨드( 윈도우는 cmd ) 창을 열고

리눅스 커맨드창(bash) 에서 구동시

mkdir sample-proj
cd sample-proj
~/Downloads/gradle-8.14/bin/gradle init

윈도우 커맨드창(cmd) 에서 구동시

md sample-proj
cd sample-proj
%USERPROFILE%\Downloads\gradle-8.14\bin\gradle init

그다음은 프롬프트 순서에 따른다. 4: Basic -> 2: Groovy -> no 순서대로 입력한다.

Starting a Gradle Daemon (subsequent builds will be faster)

Select type of build to generate:
  1: Application
  2: Library
  3: Gradle plugin
  4: Basic (build structure only)
Enter selection (default: Application) [1..4] 4

Project name (default: sample-proj): 

Select build script DSL:
  1: Kotlin
  2: Groovy
Enter selection (default: Kotlin) [1..2] 2

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] 

> Task :init
Learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.13/samples

BUILD SUCCESSFUL in 19s
1 actionable task: 1 executed

수행하고 나면 대충 아래 구조대로 파일들이 만들어 진다.

├── build.gradle
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle


4. 프로젝트 기본 설정

다음 build.gradle 파일을 아래와 같이 수정해 준다.

요점은

    1. gradle 플러그인 설정
    1. JDK 설정
    1. 저장소(Repository) 설정
    1. 의존 모듈(dependency jar) 설정

으로 이루어 진다 (자세한 설명은 주석으로 대신하겠다.)

/** JDK8, javax.servlet 패키지로 설치할 수 있는 SPRING-BOOT 최근 버젼  */
plugins {
  id 'org.springframework.boot' version '2.7.18'
  id 'io.spring.dependency-management' version '1.1.5'
  id 'java'
  id 'war'
}

java {
  /** 구형 프레임워크를 쓸것이므로 JDK-1.8 기준 */
  sourceCompatibility = JavaVersion.VERSION_1_8
  targetCompatibility = JavaVersion.VERSION_1_8
  compileJava.options.encoding = 'UTF-8'
  compileTestJava.options.encoding = 'UTF-8'
}

repositories {
  /** 개인 PC 로컬 저장소 */
  mavenLocal()
  /** 메이븐 중앙(인터넷) 저장소 */
  mavenCentral()
}

dependencies {

  /** 기본 Spring 의존모듈 */
  implementation 'org.springframework.boot:spring-boot-starter-security'
  implementation 'org.springframework.boot:spring-boot-starter-aop'
  implementation 'org.springframework.boot:spring-boot-starter-web'

  /** JSP, JSTL */
  implementation 'javax.servlet:javax.servlet-api:4.0.1'
  implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl'

  /** log / log4jdbc */
  implementation 'ch.qos.logback:logback-classic:1.2.13'
  implementation 'ch.qos.logback:logback-core:1.2.13'

  /** 개별 바이너리 파일 모듈 (compiled binary, 추후 JAR 파일 추가 예정) */
  implementation fileTree(dir: 'libs', include: [ ])

  /** 빌드, 개발 및 테스트 관련 */
  compileOnly 'org.projectlombok:lombok'
  developmentOnly 'org.springframework.boot:spring-boot-devtools'
  annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
  annotationProcessor 'org.projectlombok:lombok'

  testImplementation 'org.projectlombok:lombok'
  testAnnotationProcessor 'org.projectlombok:lombok'
  testImplementation 'org.junit.jupiter:junit-jupiter'
  testImplementation 'org.junit.platform:junit-platform-launcher'
  testImplementation 'org.junit.vintage:junit-vintage-engine'
}

tasks.named('test') {
  useJUnitPlatform()
}

bootRun {
  systemProperty('spring.profiles.active', System.properties['spring.profiles.active'])
  systemProperty('java.file.encoding', 'UTF-8')
}

이후 소스 폴더를 만들어 준다.

리눅스 에서 실행시

mkdir -p src/main/java
mkdir -p src/main/resources
mkdir -p src/test/resources
mkdir -p src/test/java
mkdir -p src/main/java/sample/proj

윈도우 에서 실행시

md "src\main\java"
md "src\main\resources"
md "src\test\resources"
md "src\test\java"
md "src\main\java\sample\proj"

src/main/resources 폴더에 application.yml 파일을 작성해 준다.

spring:
  application:
    name: "sample-proj"
  mvc:
    #$ JSP설정
    view:
      prefix: "/WEB-INF/views/"
      suffix: ".jsp"
  servlet:
    ## 업로드 설정 
    multipart:
      maxFileSize: "50MB"
      maxRequestSize: "50MB"
  ## REST 통신 직렬화 설정
  jackson:
    default-property-inclusion: "non-empty"
    time-zone: "Asia/Seoul"
## 로그설정
logging:
  file:
    path: "log"
    filename: "sample-proj"
  level:
    root: "DEBUG"
    jdbc: "DEBUG"
    work: "DEBUG"

같은 폴더에 application-local.yml 파일 (로컬PC 구동용 설정파일) 을 작성해 준다.

debug: "false"
server:
  ## 로컬에서 구동할 embed-tomcat 포트번호
  port: 8080
  forward-headers-strategy: "FRAMEWORK"
  servlet:
    ## 서블릿 인코딩
    encoding:
      charset: "UTF-8"
      enabled: "true"
      force: "true"

로그 설정파일(/src/main/resources/logback-spring.xml)을 만들어 준다. (설명은 주석으로 대신한다.)

<?xml version="1.0" encoding="UTF-8"?>
<!-- 60초마다 설정갱신 확인 -->
<configuration scan="true" scanPeriod="60 seconds">
  <springProperty scope="context" name="LOG_LEVEL_ROOT" source="logging.level.root"/>
  <springProperty scope="context" name="LOG_LEVEL_JDBC" source="logging.level.jdbc"/>
  <springProperty scope="context" name="LOG_LEVEL_WORK" source="logging.level.work"/>
  <springProperty scope="context" name="LOG_PATH" source="logging.file.path"/>
  <springProperty scope="context" name="LOG_FILE_NAME" source="logging.file.filename"/>
  <property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>
  <!-- Console Appender -->
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <pattern>${LOG_PATTERN}</pattern>
      <charset>UTF-8</charset>
    </encoder>
  </appender>
  <!-- File Appender -->
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 파일경로 설정 -->
    <file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
    <!-- 출력패턴 설정-->
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <pattern>${LOG_PATTERN}</pattern>
      <charset>UTF-8</charset>
    </encoder>
    <!-- Rolling 정책 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.log.gz</fileNamePattern>
      <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
        <!-- 파일당 최고 용량 kb, mb, gb -->
        <maxFileSize>1GB</maxFileSize>
      </timeBasedFileNamingAndTriggeringPolicy>
      <!-- 일자별 로그파일 최대 보관주기(~일), 해당 설정일 이상된 파일은 자동으로 제거-->
      <maxHistory>90</maxHistory>
    </rollingPolicy>
  </appender>

  <!-- root레벨 설정 -->
  <root level="${LOG_LEVEL_ROOT}">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
  </root>

  <!-- JDBC 로깅레벨 설정 -->
  <logger name="org.apache.ibatis" level="${LOG_LEVEL_JDBC}" additivity="false">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
  </logger>
  <logger name="org.springframework.jdbc.core" level="${LOG_LEVEL_JDBC}" additivity="false">
    <appender-ref ref="CONSOLE"/>
  </logger>
  <logger name="org.mybatis.spring.SqlSessionTemplate" level="${LOG_LEVEL_JDBC}" additivity="false">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
  </logger>
  <logger name="org.springframework.jdbc.core.JdbcTemplate" level="INFO" additivity="false"></logger>

  <!-- 비즈니스로직 로깅레벨 설정 -->
  <logger name="sample.proj" level="${LOG_LEVEL_WORK}" additivity="false">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
  </logger>
</configuration>

그리고 src/main/java/sample/proj 폴더에 스프링 부트 구동(Application.java) 프로그램을 작성해 준다

package sample.proj;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

import lombok.extern.slf4j.Slf4j;

@Slf4j @ServletComponentScan @SpringBootApplication
public class Application {
  public static void main(String[] args) throws Exception {
    /** 기본 프로파일이 없다면 local 프로파일로 실행되도록 */
    String profile = System.getProperty("spring.profiles.active");
    if (profile == null || "".equals(profile)) {
      System.setProperty("spring.profiles.active", "local");
    }
    SpringApplication.run(Application.class, args);
  }
}

톰캣이나 웹로직서블릿 컨테이너에서 사용될 서블릿 연계(ServletInitializer.java) 프로그램을 같은 폴더에 작성해 준다.

package sample.proj;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ServletInitializer extends SpringBootServletInitializer {
  @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(Application.class);
  }
}

여기까지 완료했으면 대충 아래와 같은 파일들이 만들어 진다.

├── build.gradle
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── sample
    │   │       └── proj
    │   │           ├── Application.java
    │   │           └── ServletInitializer.java
    │   └── resources
    │       ├── application-local.yml
    │       ├── application.yml
    │       └── logback-spring.xml
    └── test
        ├── java
        └── resources


5. 빌드 및 구동

이제 빌드를 수행해 보자

./gradlew build

아래와 같이 출력되면 (하단의 BUILD SUCCESSFUL) 성공이다

Calculating task graph as no cached configuration is available for tasks: build

[Incubating] Problems report is available at: file:///home/coder/documents/ind-works/sample-proj/build/reports/problems/problems-report.html

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.13/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 5s
5 actionable tasks: 5 up-to-date
Configuration cache entry stored.

실행은 다음과 같다.

./gradlew bootRun -Dspring.profiles.active=local

실행 결과는 다음과 같을것이다.

Reusing configuration cache.

> Task :bootRun
02:13:59.611 [Thread-0] DEBUG org.springframework.boot.devtools.restart.classloader.RestartClassLoader - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@11409ea1

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v2.7.18)

INFO  25-04-30 02:13:59[restartedMain] [Application:55] - Starting Application using Java 17.0.8 on VSCD_A0000 with PID 1272988 (/home/coder/documents/ind-works/sample-proj/build/classes/java/main started by coder in /home/coder/documents/ind-works/sample-proj)
DEBUG 25-04-30 02:13:59[restartedMain] [Application:56] - Running with Spring Boot v2.7.18, Spring v5.3.31
INFO  25-04-30 02:13:59[restartedMain] [Application:638] - The following 1 profile is active: "local"
INFO  25-04-30 02:13:59[restartedMain] [DevToolsPropertyDefaultsPostProcessor:255] - Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
INFO  25-04-30 02:13:59[restartedMain] [DevToolsPropertyDefaultsPostProcessor:255] - For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
INFO  25-04-30 02:14:00[restartedMain] [TomcatWebServer:108] - Tomcat initialized with port(s): 8080 (http)
INFO  25-04-30 02:14:00[restartedMain] [Http11NioProtocol:173] - Initializing ProtocolHandler ["http-nio-8080"]
INFO  25-04-30 02:14:00[restartedMain] [StandardService:173] - Starting service [Tomcat]
INFO  25-04-30 02:14:00[restartedMain] [StandardEngine:173] - Starting Servlet engine: [Apache Tomcat/9.0.83]
INFO  25-04-30 02:14:00[restartedMain] [[/]:173] - Initializing Spring embedded WebApplicationContext
INFO  25-04-30 02:14:00[restartedMain] [ServletWebServerApplicationContext:292] - Root WebApplicationContext: initialization completed in 687 ms
WARN  25-04-30 02:14:00[restartedMain] [UserDetailsServiceAutoConfiguration:89] - 

Using generated security password: df3375fe-60d0-4e3e-94c1-3b55685070e0

This generated password is for development use only. Your security configuration must be updated before running your application in production.

INFO  25-04-30 02:14:00[restartedMain] [DefaultSecurityFilterChain:55] - Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@6419c353, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1f97a9e6, org.springframework.security.web.context.SecurityContextPersistenceFilter@3f2061dc, org.springframework.security.web.header.HeaderWriterFilter@f423af6, org.springframework.security.web.csrf.CsrfFilter@4c61dd01, org.springframework.security.web.authentication.logout.LogoutFilter@5c5dec06, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@1740edc3, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@b6bb875, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@1be8e101, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@3e28bba5, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1ca6be27, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@62551595, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@57e91ec2, org.springframework.security.web.session.SessionManagementFilter@3dc4da31, org.springframework.security.web.access.ExceptionTranslationFilter@65f59562, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3deb4c5d]
INFO  25-04-30 02:14:01[restartedMain] [OptionalLiveReloadServer:59] - LiveReload server is running on port 35729
INFO  25-04-30 02:14:01[restartedMain] [Http11NioProtocol:173] - Starting ProtocolHandler ["http-nio-8080"]
INFO  25-04-30 02:14:01[restartedMain] [TomcatWebServer:220] - Tomcat started on port(s): 8080 (http) with context path ''
INFO  25-04-30 02:14:01[restartedMain] [Application:61] - Started Application in 1.414 seconds (JVM running for 1.658)
<==========---> 80% EXECUTING [16s]
> :bootRun

이제 브라우저를 열고 주소창에 http://localhost:8080 을 입력해 보자

아래 화면과 같은 화면이 뜨면 성공이다.


6. 다음편에서는..

이번 포스팅에서는 초보자도 따라 할 수 있는 자바 프로젝트 를 목표로

JDK-1.8 Java EE 를 활용해 gradle프로젝트 기초 까지만 작성해 보았다.

생초보는 커맨드창 (cli) 이 익숙하지 않을수 있으나.... 되도록 익숙해 지길 권장한다 (노파심에서....)

다음 포스팅에서는 기능 덧붙이기 이전에 이클립스VS-CODE 같은 통합 개발환경 에서 프로젝트를 불러오고 폐쇄망 셋팅, 형상관리 연동 및 구동 방법 까지 알아보려 한다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다