Code Monkey home page Code Monkey logo

the-art-of-java-web-programming-study's People

Contributors

mklinkj avatar

Watchers

 avatar

the-art-of-java-web-programming-study's Issues

Mockito 추가후 VM 경고 메시지 - Sharing is only supported for boot loader classes because bootstrap classpath has been appended

문제

프로젝트의 테스트에 Mockito 라이브러리를 추가한 후 테스트를 실행하면 다음과 같은 경고가 뜬다.

OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
  • Mockito 사용 부분
    private T mockServlet() {
      Class<T> servletClass = getServletClass();
      T mockServlet = Mockito.mock(servletClass, Mockito.CALLS_REAL_METHODS);
      Mockito.when(mockServlet.getServletConfig()).thenReturn(servletConfig);
      Mockito.when(mockServlet.getServletContext()).thenReturn(servletContext);
      return mockServlet;
    }

중간 해결

  • https://github.com/mockito/mockito/issues/2590
  • 정확하게는 잘 모르겠는데.. 무시해도 된다고 함.

링크

해결

  • ...

[개선] 파일 업로드 설정 정보를 properties 파일로 관리

현 상태

파일 업로드 설정정보가 상수로 들어가있어 파일 시스템이 바뀌였을 때, 분기가 힘들다.

  • Commons Fileupload 방식

    ...
    @Default private Path uploadDir = Paths.get("C:\\upload\\art_of_java_web");
    
    @Default private Path uploadTempDir = Paths.get("C:\\upload\\art_of_java_web\\temp");
    ...
  • 서블릿 스팩의 어노테이션 사용 방식

    @Slf4j
    @WebServlet("/upload.do")
    @MultipartConfig(
        fileSizeThreshold = MEGA_BYTE,
        maxFileSize = 10 * MEGA_BYTE,
        maxRequestSize = 15 * MEGA_BYTE,
        location = "C:\\upload\\art_of_java_web")
    public class FileUpload extends AbstractHttpServlet {
    ....

이 부분을 프로퍼터티에 저장해서 읽는 방식으로 처리해야겠다.

프로퍼티를 로드하는 정적 유틸리티 클래스가 필요.

개선

  • Commons FileUpload로 구성한 서블릿은 프로퍼티 파일에서 설정값 로드해서 넣기만 하면 되어 잘 된다.
  • @MultipartConfig로 설정한 경우는 상수 밖에 못 넣어서.. 어떻게 할 수 가 없다. 😓
    • 컨텍스트가 초기화 될 때.. 서블릿을 수동등록 해주는 방법 밖에 없음.
      @WebListener
      public class FileUploadListener implements ServletContextListener {
        @Override
        public void contextInitialized(ServletContextEvent event) {
          ServletContext context = event.getServletContext();
          Dynamic fileUpload = context.addServlet("fileUpload", FileUpload.class);
          fileUpload.addMapping("/upload.do");
          fileUpload.setMultipartConfig(getMultiPartConfig());
        }
      
        private MultipartConfigElement getMultiPartConfig() {
          String location = ProjectDataUtils.getProperty("fileUpload.uploadDir");
          long maxFileSize = ProjectDataUtils.getLongProperty("fileUpload.maxFileSize");
          long maxRequestSize = ProjectDataUtils.getLongProperty("fileUpload.maxRequestSize");
          int fileSizeThreshold = ProjectDataUtils.getIntProperty("fileUpload.threshold");
          return new MultipartConfigElement(location, maxFileSize, maxRequestSize, fileSizeThreshold);
        }
      }
    • 수동으로 등록하므로 FileUpload의 서블릿 설정 어노테이션은 전부 제거
        // ✨ 아래 내용을 동적으로 하고 싶었는데, 어노테이션 안의 설정 값은 오직 상수만 넣을 수 있어서 프로퍼티 값 처리가 불가능하다.
        // @WebServlet("/upload.do")
        // @MultipartConfig(
        //    fileSizeThreshold = MEGA_BYTE,
        //    maxFileSize = 10 * MEGA_BYTE,
        //    maxRequestSize = 15 * MEGA_BYTE,
        //    location = "C:\\upload\\art_of_java_web")
        public class FileUpload extends AbstractHttpServlet {
        ...

뭔가 복잡해지긴 했는데.. 리눅스 환경에 배포할 경우 경로 분기 처리하려고 했던 건데...
추후 좋은 방법을 알게 되면 더 수정해보자.. 😂

ProjectDataUtils을 새로 추가했는데, 이건 이후 프로젝트에서도 계속 활용하자.

참조

Oracle 드라이버 등록 해제 Listener 실행 순서 조정

문제

데이터소스를 Tomcat JDBC에서 HikariCP로 바꿨을 때...

Oracle 드라이버 해제 리스너가 먼저 실행되고, Hikari DataSource 등록해제가 일어나서 이 때 오류메시지가 많이 노출된다.

11:42:08.113 [main] INFO  org.mklinkj.taojwp.common.listener.JDBCDriverCleaner - ### oracle.jdbc.OracleDriver 드라이버 등록 해제
11:42:08.113 [main] DEBUG org.springframework.web.context.support.XmlWebApplicationContext - Closing Root WebApplicationContext, started on ...
11:42:08.114 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
11:42:08.114 [main] DEBUG com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Before shutdown stats (total=10, active=0, idle=10, waiting=0)
11:42:08.116 [HikariPool-1 connection closer] DEBUG com.zaxxer.hikari.pool.PoolBase - HikariPool-1 - Closing connection oracle.jdbc.driver.T4CConnection@5d300cf2: (connection evicted)
11:42:08.116 [HikariPool-1 connection closer] DEBUG com.zaxxer.hikari.pool.PoolBase - HikariPool-1 - Closing connection oracle.jdbc.driver.T4CConnection@5d300cf2 failed
java.lang.IllegalStateException: Timer already cancelled.
....
  • 드라이버 해제가 일어난 후, Hikari DataSource 정리작업이 일어나서 커낵션 닫을 때 예외가 많이 생김.

해결

  <listener>
    <display-name>JDBC Driver Cleaner</display-name>
    <listener-class>org.mklinkj.taojwp.common.listener.JDBCDriverCleaner</listener-class>
  </listener>

  <listener>
    <display-name>Spring Context Loader</display-name>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  • Spring Context Loader 보다 드라이버 클리너 리스너를 위에 두면 Hikari DataSource 해제 이후에 드라이버 해제가 수행된다.
  • 순서가 중요하면 @WebListener 로 사용하면 안되겠다.

경고 - Kotlin Gradle 플러그인이 여러 하위 프로젝트에서 여러 번 로드되었으므로 지원되지 않으며 빌드가 중단될 수 있습니다

빌드 경고

The Kotlin Gradle plugin was loaded multiple times in different subprojects, which is not supported and may break the build.
This might happen in subprojects that apply the Kotlin plugins with the Gradle 'plugins { ... }' DSL if they specify explicit versions, even if the versions are equal.
Please add the Kotlin plugin to the common parent project or the root project, then remove the versions in the subprojects.
If the parent project does not need the plugin, add 'apply false' to the plugin line.
See: https://docs.gradle.org/current/userguide/plugins.html#sec:subprojects_plugins_dsl
The Kotlin plugin was loaded in the following projects: ':chap19:pro19', ':chap21:pro21-spring4-ex01'
Kotlin Gradle 플러그인이 여러 하위 프로젝트에서 여러 번 로드되었으므로 지원되지 않으며 빌드가 중단될 수 있습니다.
이는 버전이 동일하더라도 명시적인 버전을 지정하는 경우 Gradle 'plugins { ... }' DSL을 사용하여 Kotlin 플러그인을 적용하는 하위 프로젝트에서 발생할 수 있습니다.
공통 상위 프로젝트 또는 루트 프로젝트에 Kotlin 플러그인을 추가한 후 하위 프로젝트에서 버전을 제거하세요.
상위 프로젝트에 플러그인이 필요하지 않은 경우 플러그인 줄에 'apply false'를 추가하세요.
참조: https://docs.gradle.org/current/userguide/plugins.html#sec:subprojects_plugins_dsl
Kotlin 플러그인은 다음 프로젝트에 로드되었습니다: ':chap19:pro19', ':chap21:pro21-spring4-ex01'

일단 빌드 / 실행에는 문제가 없긴한데...

클래스 중복 경고

Jetty로 프로젝트를 실행하면...

클래스가 중복되면 경고를 출력하는데,

27장, 28장의 메이븐 예제에서

commons-logging, javax.mail 관련해서 중복 경고가 나온다..

한번 확인해보자!

[Tomcat 로그] org.apache.jasper.servlet.TldScanner.scanJars 적어도 하나의 JAR가 TLD들을 찾기 위해 스캔되었으나 아무 것도 찾지 못했습니다.

문제

Tomcat 로그

정보 [RMI TCP Connection(3)-127.0.0.1] org.apache.jasper.servlet.TldScanner.scanJars 적어도 하나의 JAR가 TLD들을 찾기 위해 스캔되었으나 아무 것도 찾지 못했습니다. 스캔했으나 TLD가 없는 JAR들의 전체 목록을 보시려면, 로그 레벨을 디버그 레벨로 설정하십시오. 스캔 과정에서 불필요한 JAR들을 건너뛰면, 시스템 시작 시간과 JSP 컴파일 시간을 단축시킬 수 있습니다.

Tomcat 서버 시작할 때, 위와 같은 오류가 나타남.

해결

Tomcat이 프로젝트의 모든 라이브러리에 대해 TLD를 찾으려해서 제외 설정을 따로 해줘야하나보다..

  • log.properties로 대상 확인

    # 탐색하는 jar 파일 목록 출력
    org.apache.catalina.startup.ContextConfig.level = FINE
    
    # TLD 관련 로그 출력
    org.apache.jasper.servlet.TldScanner.level = FINE
  • catalina.properties

    tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\
    ...
    jakarta.servlet.jsp.jstl-api-*.jar,\
    ojdbc8-*.jar,\
    ojdbc11-*.jar,\
    spring-jcl-*.jar,\
    spring-beans-*.jar,\
    spring-jdbc-*.jar,\
    spring-tx-*.jar,\
    spring-core-*.jar,\
    jakarta.el-api-*.jar,\
    commons-text-*.jar

    TLD 못찾았다고 나오는 라이브러리 버전 부분만 와일드 카드로 추가함.

위에 제외 설정을 하더라도 아래 강제 포함하는 설정이 있는데..

tomcat.util.scan.StandardJarScanFilter.jarsToScan=\
...

확실해지면 넣어보도록 하자..

참고

어이없는 실수... java.lang.NoSuchMethodException: `서블릿클래스`.<init>() 예외

문제

서블릿 주소로 접속시 다음과 같은 예외가 나는 문제..

java.lang.NoSuchMethodException: org.mklinkj.taojwp.sec01.ex04.DispatchBindingSecondServlet.<init>()
        at java.base/java.lang.Class.getConstructor0(Class.java:3585)
        at java.base/java.lang.Class.getConstructor(Class.java:2271)
        ...

원인

@WebServlet("/dispatchBindingSecond")
class DispatchBindingSecondServlet extends HttpServlet {
....

서블릿 클래스에 public 접근 지정자를 붙이지 않음.

해결

  • public 붙인 후 해결.
  • 클래스 이름 바꿔서 정리하다가 public을 빼먹은 것 같음.. 😓
  • 처음엔 왜 생성자가 없다고 나오는지? 해깔렸음.. 🤪

Jenkins 빌드/테스트시 윈도우 경로를 사용하는 코드가 실패하는 문제

CommonsUtils
Paths를 사용한 부분이 Linux환경에서 달라지는 것 같은데...
일단은 해당 테스트가 윈도우 환경에서만 실행되도록 @EnabledOnOs(OS.WINDOWS) 붙임.

대상 메서드

  public static String fileNameOnly(String fileName) {
    return Paths.get(fileName).getFileName().toString();
  }

테스트

  @Test
  @EnabledOnOs(OS.WINDOWS) // ✨
  void testFileNameOnly() {
    assertThat(CommonUtils.fileNameOnly("C:\\Test\\a.txt")).isEqualTo("a.txt");
    assertThat(CommonUtils.fileNameOnly("C:/Test/b.txt")).isEqualTo("b.txt");
    assertThat(CommonUtils.fileNameOnly("c.txt")).isEqualTo("c.txt");
  }

기타

윈도우 환경에서만 실행하지 않게하는 @DisabledOnOs(OS.WINDOWS) 이런 어노테이션도 있다.

  @Test
  @DisabledOnOs(OS.WINDOWS)
  void testFileNameOnly_Unix() {
    assertThat(CommonUtils.fileNameOnly("/Test/a.txt")).isEqualTo("a.txt");
    assertThat(CommonUtils.fileNameOnly("/Test/b.txt")).isEqualTo("b.txt");
    assertThat(CommonUtils.fileNameOnly("c.txt")).isEqualTo("c.txt");
  }

Spring 6.1 버전업 후 발생한 문제

Spring 6.0.13에서 6.1로 버전업을 했을 때, 발생한 문제들을 적어보자!

대체적으로 컨트롤러에서 파라미터가 자동으로 처리되던 것을 수동으로 명시해야하는 식으로 바뀐 것 같음.
cc88486#r133224157

  • 다양한 JVM 호환을 위해서 그런 건가?

다른 경우는 ...

enum을 멤버 필드로 포함한 DTO에서 인자가 없는 기본 생성자가 반드시 필요한 경우가 있었다.

  • Spring Data JPA와 Hibernate 버전도 올려줘야했다.
    • Spring Data JPA: 3.2.0
    • Hibernate: 6.3.1.Final

아래 커밋의 변경사항을 참조할 것! 😜

MyBatis 트랜젝션 테스트

항상 스프링이 알아서 해주는대로 해보다보니까, MyBatis만 쓸 때는 트랜젝션을 어떻게 해야할지 궁금해서 해봤다.

  • 테스트 코드
      @Test
      void testInsertMember() throws Exception {
        final Transaction tx =
            transactionFactory().newTransaction(DBUtils.getDataSourceFromJNDI().getConnection());
        try {
          MemberVO vo0 = new MemberVO();
          vo0.setId("test00");
          vo0.setPwd("1234");
          vo0.setName("테스트유저00");
          vo0.setEmail("[email protected]");
          vo0.setJoinDate(LocalDateTime.of(2023, 12, 25, 0, 0));
          dao.insertMember(vo0, false); // autoCommit: false
    
          MemberVO vo1 = new MemberVO();
          vo1.setId("test01");
          vo1.setPwd("1234");
          vo1.setName("테스트유저01");
          vo1.setEmail("[email protected]");
          vo1.setJoinDate(LocalDateTime.of(2023, 12, 25, 1, 0));
          dao.insertMember(vo1, false); // autoCommit: false
    
          tx.commit();
        } finally {
          tx.close();
        }
    
        assertThat(dao.overlappedId("test00")).isTrue();
        assertThat(dao.overlappedId("test01")).isTrue();
      }
  1. autocommit: false인 상태에서 회원 2명 INSERT함
  2. commit 수행
  3. ID 존재여부 확인 SELECT

위와 같이 진행했는데, 잘됨...

그런데 특이한 점이...

트랜젝션 팩토리가 2가지 종류가 있는데...

  • ManagedTransactionFactory
  • JdbcTransactionFactory

ManagedTransactionFactory를 사용해야 의도대로 동작함.
지금 환경에 Tomcat에서 DataSource를 만들고 애플리케이션에서는 JNDI로 가져오는 식이여서 그런 것인지는 잘 모르겠는데, 이 부분은 천천히 확인해보자.

MyBatis 가이드의 기본 설정은 JdbcTransactionFactory를 사용하는 것이긴 했음..

Oracle 연동한 프로젝트에서 Gretty 종료시 Tomcat 메모리 누수 경고

문제

경고: The web application [pro07] appears to have started a thread named [oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
...
2월 27, 2023 9:59:35 오후 org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
경고: The web application [pro07] appears to have started a thread named [InterruptTimer] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
...
2월 27, 2023 9:59:35 오후 org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
경고: The web application [pro07] appears to have started a thread named [OJDBC-WORKER-THREAD-1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
...
  • 프로세스가 종료될 때 생기는 것이라서 별 문제는 없을 것 같은데..
  • 해결 방법들을 검색해봤을 때... ContextListener의 contextDestroyed() 메서드에 수동으로 해제하는 코드를 작성하는 식인 것 같다..

링크

진행 방향...

Gradle war생성시, 특정 파일의 버전 속성명을 실제 버전 값으로 교체하기

문제

webjars를 사용하게 되면서... 아래 처럼 사용하게 되었는데...

<script src="webjars/jquery/3.6.4/jquery.min.js"></script>

위의 버전 부분을 속성명으로 두고, 빌드시점에 교체해줄 수는 없는지?

<script src="webjars/jquery/${jqueryVersion}/jquery.min.js"></script>

정리해서 말하면... ${jqueryVersion} 부분을 war만들 때 버전으로 바꿔서 적용하는 방법은?

해결

하다 보니 아주 간단하게 되었다.. 아래 내용을 build.gradle 추가 해주면 잘 동작한다.

  • build.gradle
      tasks.register('expandPropsWebapp', Copy) {
        from 'src/main/webapp'
        into "$buildDir/expandPropsWebapp"
        filesMatching(["**/*.html", "**/*.jsp"], {
          expand(jqueryVersion: "${jqueryVersion}")
        })
        filteringCharset = 'UTF-8'
      }
      
      war {
        dependsOn('expandPropsWebapp')
        webAppDirectory = file("$buildDir/expandPropsWebapp")
      }

해결을 좀 이상하게 한 것 같은데...

아무리 해도 안되서 ...

  • 빌드 경로에 버전 속성이 실제 값으로 치환된 webapp을 빌드 경로의 expandPropsWebapp에다 만들어두고...
  • war 작업 실행시 webapp 경로를 그것을 보고 처리되도록 하였다..

Jenkins 빌드시 swing 사용한 예제가 실패하는 문제

문제

Jenkins 빌드 / 테스트 아이템으로 추가해보려하는데...

swing 사용한 예제 테스트가 실패한다.

AppTest > appHasAGreeting() FAILED
    java.awt.HeadlessException at AppTest.java:16

왠지 테스트 테스크에 대해서만 아래 옵션을 JVM 옵션으로 주면 될 것 같은데...

java.awt.headless=false

[IntelliJ] 메시지 프로퍼티 사용

문제

Eclipse에서는 Properties Editor 설치하면 한글 같은 것 저장하면 UNICODE 값으로 저장하고 읽을 때도 잘 변환해서 에디터에서 표시해줬었는데, IntelliJ에서는 어떤지?

해결

  • 추가로 설치해야할 부가 플러그인은 없음.
  • 인텔리J에서 프로퍼티 파일의 기본 인코딩을 ISO-8859-1로 유지 해야함.
  • 명확한 Native에서 ASCII로 변환에 체크 해줘야 유니코드 값을 해당 한글 값으로 변환헤서 에디터에 출력해줌.

image

의견

처음에 UTF-8로 메시지 프로퍼티 파일을 만들어서 이부분이 제대로 동작하지 않았다. 😅

Maven 프로젝트의 Jetty 12 버전업

예제의 대부분을 Gradle 프로젝트로 만들긴 했지만,

몇몇 예제는 Maven 프로젝트로 만들었다.

  • \chap27\pro27-maven
  • \chap28\pro28-maven

이때 Embedded 방식으로 WAS를 실행하기 위해

Jetty 실행으로는 jetty-maven-plugin을 사용하고

Tomcat을 실행할 때는 cargo-maven-plugin을 사용했는데,

Jetty 10을 12버전으로 버전업 하기로 하였다.

알려진 문제

프로젝트 디렉토리가 정션 링크를 통해 참조되고 있는 위치에서, Jetty를 실행할 경우..
정적 경로의 리소스를 읽지 못하는 문제가 있다.

Jetty 10, 11등에서는 문제가 없었는데,
Jetty Github에 문의를 해봤을 때..

Maven 플러그인에 Alias 체크를 설정해주는 옵션이 들어가야 한다고 했다.

Unable to Read Static Resource Files Located Under the Webapp Path in Jetty12 (EE8)

  • https://github.com/jetty/jetty.project/issues/11077

예제의 설명을 위한 index.html을 항상 웹 루트 경로에 추가해줬었는데...

정션 링크로 참조되는 경로에서 mvn clean jetty:run 으로 실행하면
http://localhost:8080/index.html 로 접근하면 404 응답을 받게된다.

보통 윈도우에서 프로젝트 경로를 정션 링크로 연결해서 사용해서 쓰는 경우가 일반적이지는 않아서, 왠지 고쳐줄 것 같진 않음 😅😅😅

해결 방향..

  • Jetty 10이 아직 지원종료 상태는 아니더라도 12를 써보자..😅
  • 내가 jetty-maven-plugin 코드를 수정할 수 있을까 싶어서 fork는 받아두긴 했는데.. 아직은 잘 모르겠음.

중간 결과

  • 프로젝트에 버전업은 정상 완료하였다.
    • 이슈는 닫고 정션 링크 관련 문제 변경시 다시 열도록 하자. 👍

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.