mklinkj / the-art-of-java-web-programming-study Goto Github PK
View Code? Open in Web Editor NEW자바 웹을 다루는 기술 - 스터디
자바 웹을 다루는 기술 - 스터디
프로젝트의 테스트에 Mockito 라이브러리를 추가한 후 테스트를 실행하면 다음과 같은 경고가 뜬다.
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
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
web.xml 설정에 metadata-complete="true"
가 포함되면 @WebServlet
설정 인식이 안됨.
이 옵션을 제거해야 서블릿 클래스위에 어노테이션으로 설정한 @WebServlet
설정이 정상 인식됨
파일 업로드 설정정보가 상수로 들어가있어 파일 시스템이 바뀌였을 때, 분기가 힘들다.
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 {
....
이 부분을 프로퍼터티에 저장해서 읽는 방식으로 처리해야겠다.
프로퍼티를 로드하는 정적 유틸리티 클래스가 필요.
@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);
}
}
// ✨ 아래 내용을 동적으로 하고 싶었는데, 어노테이션 안의 설정 값은 오직 상수만 넣을 수 있어서 프로퍼티 값 처리가 불가능하다.
// @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을 새로 추가했는데, 이건 이후 프로젝트에서도 계속 활용하자.
데이터소스를 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.
....
<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>
@WebListener
로 사용하면 안되겠다.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 관련해서 중복 경고가 나온다..
한번 확인해보자!
정보 [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: 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 접근 지정자를 붙이지 않음.
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");
}
대체적으로 컨트롤러에서 파라미터가 자동으로 처리되던 것을 수동으로 명시해야하는 식으로 바뀐 것 같음.
cc88486#r133224157
다른 경우는 ...
enum을 멤버 필드로 포함한 DTO에서 인자가 없는 기본 생성자가 반드시 필요한 경우가 있었다.
아래 커밋의 변경사항을 참조할 것! 😜
항상 스프링이 알아서 해주는대로 해보다보니까, 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();
}
위와 같이 진행했는데, 잘됨...
그런데 특이한 점이...
트랜젝션 팩토리가 2가지 종류가 있는데...
ManagedTransactionFactory를 사용해야 의도대로 동작함.
지금 환경에 Tomcat에서 DataSource를 만들고 애플리케이션에서는 JNDI로 가져오는 식이여서 그런 것인지는 잘 모르겠는데, 이 부분은 천천히 확인해보자.
MyBatis 가이드의 기본 설정은 JdbcTransactionFactory를 사용하는 것이긴 했음..
경고: 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:
...
https://github.com/spring-projects/spring-boot/issues/2612
https://github.com/spring-projects/spring-boot/issues/21221
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 추가 해주면 잘 동작한다.
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")
}
해결을 좀 이상하게 한 것 같은데...
아무리 해도 안되서 ...
Jenkins 빌드 / 테스트 아이템으로 추가해보려하는데...
swing 사용한 예제 테스트가 실패한다.
AppTest > appHasAGreeting() FAILED
java.awt.HeadlessException at AppTest.java:16
왠지 테스트 테스크에 대해서만 아래 옵션을 JVM 옵션으로 주면 될 것 같은데...
java.awt.headless=false
예제의 대부분을 Gradle 프로젝트로 만들긴 했지만,
몇몇 예제는 Maven 프로젝트로 만들었다.
이때 Embedded 방식으로 WAS를 실행하기 위해
Jetty 실행으로는 jetty-maven-plugin을 사용하고
Tomcat을 실행할 때는 cargo-maven-plugin을 사용했는데,
Jetty 10을 12버전으로 버전업 하기로 하였다.
프로젝트 디렉토리가 정션 링크를 통해 참조되고 있는 위치에서, Jetty를 실행할 경우..
정적 경로의 리소스를 읽지 못하는 문제가 있다.
Jetty 10, 11등에서는 문제가 없었는데,
Jetty Github에 문의를 해봤을 때..
Maven 플러그인에 Alias 체크를 설정해주는 옵션이 들어가야 한다고 했다.
https://github.com/jetty/jetty.project/issues/11077
예제의 설명을 위한 index.html
을 항상 웹 루트 경로에 추가해줬었는데...
정션 링크로 참조되는 경로에서 mvn clean jetty:run
으로 실행하면
http://localhost:8080/index.html
로 접근하면 404 응답을 받게된다.
보통 윈도우에서 프로젝트 경로를 정션 링크로 연결해서 사용해서 쓰는 경우가 일반적이지는 않아서, 왠지 고쳐줄 것 같진 않음 😅😅😅
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.