전에는 maven 으로 kotlin , java 를 섞어서 프로젝트를 만들기도 했다. (최초에만 그랬고 그뒤부턴 코틀린만으로 된 프로젝트를 만들어왔긴했다)

 

다만 virtual thread 가 나온이후 java 21 에 대한 관심이 올라가면서... 그리고 테스트 목적으로 하나의 프로젝트만 만들어서 테스트해볼때 문법확인상  java, kotlin 둘다되는 프로젝트를 설정해보려고 찾아봤다  gradle , kotlin dsl  환경으로 했을때, ... 매우 간단했다;;

 

기본적은 springboot는 springboot init (kotlin, gradle kotlin dsl , jar )을 사용해서 만들었다. 거기에 한줄만 추가한 내용이다.

 

plugins 에 한줄만 추가하고  kotlin 과 동등한 패키지 레벨로 java 디렉토리(패키지)만 만들어주면되는거였다.

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "3.2.3"
    id("io.spring.dependency-management") version "1.1.4"
    java
    kotlin("jvm") version "1.9.22"
    kotlin("plugin.spring") version "1.9.22"
    kotlin("plugin.jpa") version "1.9.22"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-batch")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
//    implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
//    implementation("org.springframework.boot:spring-boot-starter-data-redis")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.apache.kafka:kafka-streams")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3")
    implementation("org.springframework.kafka:spring-kafka")
    runtimeOnly("com.h2database:h2")
    runtimeOnly("com.mysql:mysql-connector-j")
    runtimeOnly("com.oracle.database.jdbc:ojdbc11")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.3")
    testImplementation("org.springframework.batch:spring-batch-test")
    testImplementation("org.springframework.kafka:spring-kafka-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs += "-Xjsr305=strict"
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

 

그래들이 지원되지 않아서 하이라이팅이 되진 않겠지만 최초 spring init 으로 생성후 한줄만 더해줬다. 위에서 언급한것 같이 plugins 에 java 한줄만 넣었다. 이게 다였다. 만약 java 의 버전 관련 수정이 필요하다면 java { ... 이하 부분에서 수정하면된다. 아무튼 매우.. 간단했다. 

 

tree 구조는 아래와 같다

➜  springbootdemo git:(main) tree -I build
.
├── HELP.md
├── build.gradle.kts
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── springbootdemo
    │   │               └── controller
    │   │                   └── TestControllerJava.java
    │   ├── kotlin
    │   │   └── com
    │   │       └── example
    │   │           └── springbootdemo
    │   │               ├── SpringbootdemoApplication.kt
    │   │               └── controller
    │   │                   └── TestControllerKotlin.kt
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    │           └── hello.html
    └── test
        ├── java
        │   └── com
        │       └── example
        │           └── springbootdemo
        │               └── TestJava01.java
        └── kotlin
            └── com
                └── example
                    └── springbootdemo
                        ├── SpringbootdemoApplicationTests.kt
                        └── Test01.kt

보통 must not be null 뭐.. 그런게 보일겁니다.

 

예로 kotlin 에서 Map 을 만들었고, 해당 Map 의 값에  java's method 를 호출한 결과를 담는 상황이라면?

그런데 그 method 에서 null 을 리턴한다면? 에러가 날겁니다. 

 

Map 의 선언시 그럼 nullable 을 알리면 되지 않느냐? 예. 맞아요. 근데 ... 이부분에서 처음에 삽질했는데

 

var map: MutableMap<String, Any> = mutableMapOf()
map["name"] = javaMethod.getValue()

 

이런 코드가 있을경우 map 에 "name" 을 key 로 하고 자바의 method 결과를 값으로 담는 경우.. 어디에 '?' 를 붙여야 할까요? MutableMap 에 ? 를 붙이면 되는건가 싶지만 그건 mutableMapOf 에 대한 리턴을 의미하게 됩니다.

이런경우는

var map: MutableMap<String, Any?> = mutableMapOf()

.. 즉 담기는 값인 Any에 '?' 를 붙이면 됩니다. type 뒤에 ? 를 붙이는걸 기억하시죠? 

 

사실 이건 실수 레벨이긴한데, 잘못하면 꽤나 시간잡아먹는 내용입니다. orz 

 

 

 

Could not convert the JSON data from Java Object: Map [id=sun.reflect.DelegatingConstructorAccessorImpl.newInstance] com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class DTO클래스 and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: 블라블라블라

자.. 이게 뭐 이유는 obj 의 json 화의 실패이고, 막는 방법은.. 다양...까지는 아니지만 몇가지가 있다. 찾으면 바로 나온다.

단, 내가 mapper 를 설정할 수 있는 상황이 아니라면... obj -> json 으로 할때 처리할 수 있도록 해서 이 에러를 피할수 있다.

즉 해당 dto 에 final 로 의미없는 값 하나를 정의하고 getter 까지 선언해두면(어차피 final 이라서 set 안됨) 그 먹이(?)로 인해 json 파싱에러없이 잘 나온다. 아주 간단한 해결책이다. 

 

 

1. mac 에서 java 제거

https://www.java.com/ko/download/help/mac_uninstall_java.xml

 

sudo rm -fr /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin 

sudo rm -fr /Library/PreferencePanes/JavaControlPanel.prefPane 

sudo rm -fr ~/Library/Application\ Support/Oracle/Java

 

 

2. adoptopenjdk 설치 (1.8 버전을 예로 들면)

https://discourse.brew.sh/t/how-to-install-openjdk-with-brew/712/5

 

brew tap AdoptOpenJDK/openjdk

brew cask install adoptopenjdk8

 

설치 후에

 

java -version 으로 openjdk 설치유무 확인해봄 

 

    public static String informTrace() {

        String trace = "";
        try {
            StackTraceElement[] stackTrace = new Throwable().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
//            System.out.println("print::"+stackTraceElement.getClassName()+"/"+stackTraceElement.getMethodName()+"/"+stackTraceElement.getLineNumber());
                trace += stackTraceElement.getMethodName() + "<-";
            }
        } catch (Exception e) {
            LOGGER.warn("ignore traceUtil...");
        }

        return trace;
    }

 



Arrays.stream(Cons.SIMPLE_KEY.values()).map(c -> {
System.
out.println("didn't come here");
map.put(c.name().toUpperCase(),"data");
return map;
});
Arrays.
stream(Cons.SIMPLE_KEY.values()).map(c ->
{
System.
out.println("come in");
map.put(c.name().toUpperCase(),"data");
return map;
}).
count();


두 예제의 차이점이 있다.


첫번째것은 맵에 값을 못넣고 두번째는 넣는다.

didn't come here 는 찍히지 못하고 come in 은 찍힌다.


즉 map 만 하면 람다는 그뒤에 행위가 없으므로 아예 수행조차 하지 않는다.  (일반적으로 map 은 뭔가 수행한뒤 그 스트림을 이어서 뭔가 하길 기대하기 때문이랄까? 그대로 끝나면 버려지는 결과물이라 할 이유가 없다고 판단하는듯 하다. 바로 lazy 수행임 )


count 가 됐든 뭐든 map 이후로 해야하는 행위가 있으면 처음부터 수행해 나간다.


그런데 괜히 할것 없는데 (즉 내부 맵만 변경한다던지) map 을 할 필요가 있느냐? 그런경우는  .map --> .forEach 를 쓰도록 한다.




간단하다... 론 패턴.. loan pattern


package com.javaTest.lamda;

public class Person {


public Person(String name, int age) {
this.name = name;
this.age = age;
}

public Person() {
}

String name;
int age;

public String getName() {
return name;
}

public Person setName(String name) {
this.name = name;
return this;
}

public int getAge() {
return age;
}

public Person setAge(int age) {
this.age = age;
return this;
}

@Override
public String toString() {
return "{name:"+this.name+",age:"+this.age+"}";
//return super.toString();
}

public int ageDifference(Person other) {
return age - other.age;
}
}

자 흔한 vo 인데... 잘보면 return this; 즉 자신을 리턴하고 있다. 객체를...


public void chaining001() {
Person person = new Person().setAge(10).setName("dr.who");

System.out.println(person.toString());
}


자.. 이렇게 체이닝이 만들어진다. 간단하지?


근데 문제는... 만약 vo 가 확장 vo 라면?

확장시 타입을 확실히 선언해주면된다. ( 참고 https://stackoverflow.com/questions/1069528/method-chaining-inheritance-don-t-play-well-together )


// XXX base 가 되는 클래스는 타입을 선언해줘야함
class Pet<T extends Pet>
{
private String name;
private String color;

public T setName(String name) {
this.name = name;
return (T) this;
}

public String getName() {
return name;
}

public String getColor() {
return color;
}

public T setColor(String color) {
this.color = color;
return (T) this;
}
}

// XXX 확장한 클래스는 베이스 클래스에게 타입을 알려줘야함(여기서는 Cat)
class Cat extends Pet<Cat>
{
private String sand;
private String hair;

public String getSand() {
return sand;
}

public Cat setSand(String sand) {
this.sand = sand;
return this;
}

public String getHair() {
return hair;
}

public Cat setHair(String hair) {
this.hair = hair;
return this;
}
}

class Dog extends Pet<Dog>
{
private String takeawalk;
private String vitality;

public String getTakeawalk() {
return takeawalk;
}

public Dog setTakeawalk(String takeawalk) {
this.takeawalk = takeawalk;
return this;
}

public String getVitality() {
return vitality;
}

public Dog setVitality(String vitality) {
this.vitality = vitality;
return this;
}
}

@Test
public void chaining001() {
Person person = new Person().setAge(10).setName("dr.who");

System.out.println(person.toString());


Cat cat = new Cat();
cat.setName("shasha").setHair("short").setSand("normal").setColor("mix");

Dog dog = new Dog();
dog.setName("jindol").setVitality("very good").setColor("brown").setTakeawalk("daily");
}




from https://stackoverflow.com/questions/7885573/remove-on-list-created-by-arrays-aslist-throws-unsupportedoperationexception


List 를 한번에 생성할때 간단히 Arryas.asList("a","b"...) 형태로 만들 수 있다.


간단하고 좋긴한데...


해당 리스트를 clear() remove 할때 에러가 난다. 내용은 UnsupportedOperationException 


내용을 확인해보니 asList 로 만들어진 결과물은 fixed size  여서 말 그대로 지원하지 않는 동작이 되어버리는것이다. 


해서 이 부분을 해결하고 싶다면


그냥 Arrays.asList 하지 말고


List<String> friends = new ArrayList<>(Arrays.asList("tom","haddy","duck","gates","stranger"));

형태로 ArrayList 의 생성자로 전달해서 만들어지면 clear 등도 잘 지원하게 된다. 

+ Recent posts