묻고 답해요
156만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기 (Java 프로젝트 리팩토링)
./gradlew test 실행시 인식할수 없다고 뜹니다.
제목 그대로 ./gradlew test 실행시 인식할수 없다고 뜹니다. 이 경우 gradlew이 설치되지 않아서 발생하는 문제인지 궁금합니다.
-
미해결실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기 (Java 프로젝트 리팩토링)
test 코드 실행시 경고가 발생합니다.
test 코드 실행시 12월 30, 2024 10:47:24 오후 org.junit.platform.launcher.core.EngineDiscoveryOrchestrator lambda$logTestDescriptorExclusionReasons$7INFO: 0 containers and 3 tests were Method or class mismatch라는 경고 메시지가 뜹니다. test 코드가 돌아가는 데 큰 무리는 없지만 개인적으로 궁금해서 질문드립니다.
-
해결됨코틀린 코루틴 완전 정복
[코틀린 코루틴의 정석 책 추첨 이벤트] 강의 구매 기간 관련 문의
안녕하세요 강사님! 책 추첨 이벤트 관련 내용을 보고 강의 구매 기간에 관련하여 여쭤보고 싶은 사항이 있어서 글을 남기게 됐습니다. 여기에 말씀해주신 기간(25.12.19 ~ 26.1.18)에 강의를 구매한 사람만 해당 이벤트에 참여할 수 있는 걸까요 ?? (기간 이전에 강의를 구매한 사람은 해당되지 않는 지 궁금합니다) 감사합니다.
-
해결됨코틀린 코루틴 완전 정복
[코루틴 테스트 심화] runTest의 스레드 관련 문의
안녕하세요 강사님!강의 복습 중에 runTest 관련 문의가 있어서 다음과 같이 질문을 남기게 됐습니다. 코루틴 테스트 심화강의의 4:07초에 시작되는 부분을 보면 runTest를 호출해 실행되는 코루틴은 메인 스레드를 사용한다라고 해주셨습니다. 제가 실제로 runTest를 이용해 코드를 실행시켜 보니 Test worker스레드를 사용하고 있다고 나왔습니다. 실행 코드 @Test fun `메인 스레드만 사용하는 runTest`() = runTest { println("[${Thread.currentThread().name}] 메인 시작") delay(100) println("[${Thread.currentThread().name}] 메인 종료") }결과[Test worker @kotlinx.coroutines.test runner#2] 메인 시작 [Test worker @kotlinx.coroutines.test runner#2] 메인 종료 실제 사용되는 스레드는 Test worker 스레드 이지만, 테스트 코드에서 실행될 때 Test worker 스레드가 메인 스레드 처럼 동작하기 때문에 메인이라고 말씀해주신 걸까요?? 감사합니다!
-
미해결2시간으로 끝내는 코루틴
suspend 함수에 관해 추가적인 질문 있습니다! (runcatching, Result)
9강을 듣고 이해한 바로는, suspend 함수는 Continuation으로 resumeWith의 Result를 통해 내부적으로 콜백을 진행한다고 이해하였습니다!그렇다면 suspend 함수를 사용할때에는 runCatching을 사용하거나, 반환값을 Result로 묶는것은 불필요한 행위라는 결론을 내렸는데, 맞는 생각인지가 궁금합니다!왜냐면 runCatching과 Result 반환값을 활용하게 된다면, 애초에 suspend로 한번 예외처리를 진행했기에 중복된 작업이라고 생각하였기 때문입니다!팀원들과 부트캠프를 통해 진행한 프로젝트를 개선하고자 하는데, suspend 함수에 runCatching을 사용 후 Result로 반환을 하였어서 불필요한 작업이라고 생각되어 개선하고자 하기에 질문드립니다!긴 글 읽어주셔서 감사합니다!
-
해결됨[LV1] Jetpack Compose - UI 연습하기
메인액티비티의 내용이 강의와 다른 것 같습니다.
프로젝트를 만들 때 사진의 Empty Activity를 통해 만들었는데강의에 있는 내용과 메인 액티비티의 내용이 다른 것 같습니다.현재 최신의 안드로이드 스튜디오를 사용 중인데 이것 때문에 그런 것인지그새 바뀐 내용들이 있는 건지 궁금합니다.
-
미해결코틀린 함수형 프로그래밍 - 입문편
실습 자료 부분 업로드 다시 부탁드립니다!
지금 올려주신 실습 자료가 문제가 있는 것 같습니다! 압축을 해제하면 .idea 디렉터리만 존재하고 내부 코드는 없는데 이 확인 부탁드려요!
-
해결됨
jooq로 auditing 구현하는방법
jpa, querydsl 을 완전 걷어내고 jooq 만 사용해보려 하는중인데, 아무래도 모든테이블에 "작성일, 작성자, 수정일, 수정자" 정보가 들어갈거 같아 auditing 기능을 구현 하려 하는중입니다.프로페셔널 상품을 사용하기는 어려운 상황이고, RecordListener 를 이용하여 구현해보려 하는데 리스너가 제대로 등록이 되질 않고 있네요 ㅠㅠ@Repository class UserRepository( private var dslContext: DSLContext, ) { init { val config = dslContext.configuration().derive() config.set(userRecordAuditListener()) dslContext = DSL.using(config) } private fun userRecordAuditListener(): RecordListener { val createAudit: (UsersRecord) -> Unit = { it.createdAt = LocalDateTime.now() it.createdBy = 1 } val updateAudit: (UsersRecord) -> Unit = { it.updatedAt = LocalDateTime.now() it.updatedBy = 2 } return RecordAuditListenerGenerator<UsersRecord>().generate( UsersRecord::class.java, createAudit, updateAudit, ) } fun insertUser(user: UsersRecord): Long? { return dslContext .insertInto( USERS, USERS.NAME, USERS.AGE, ).values( user.name, user.age ).returningResult(USERS.ID) .fetchOneInto(Long::class.java) } } class RecordAuditListenerGenerator<R : UpdatableRecordImpl<R>>( ) { fun generate( recordClass: Class<R>, insertStart: (R) -> Unit, updateStart: (R) -> Unit, ): RecordListener { return object : RecordListener { override fun insertStart(ctx: RecordContext) { println("============== 1 ==============") insertStart(recordClass.cast(ctx.record()) as R) } override fun updateStart(ctx: RecordContext) { println("============= 2 ===============") updateStart(recordClass.cast(ctx.record()) as R) } } } } 데이터는 insert 되는데 작성자, 작성일이 안들어가고 있네요. 찍어놓은 프린트도 호출이 안되구요..혹시 왜그런지 알 수 있을까요..?
-
해결됨코틀린 코루틴 완전 정복
job과 코루틴의 관계?
- 학습 관련 질문을 남겨주세요. 질문을 상세히 작성하면 더 좋습니다.- 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 먼저 좋은 강의 감사합니다! 혼자서 다음과 같이 마구마구 찍어보고있었습니다.이때 저희 예상과 다르게 돌아가서 질문을 드립니다.저의 가설로는 "withContext는 코루틴을 생성하지 않고 Context만 바꾸니 코루틴을 제어하고 추적하는데 사용되는 Job은 새로 만들어지지 않을 것이다. 따라서 job1과 job5는 같을 것이다." 이었습니다.하지만 결과는 새로운 job을 만들고 job의 부모에 job1을 연결시키더군요.동일한 코루틴에서 동작하는데 왜 새로운 Job을 만드는 것인가요?fun main() = runBlocking { val job1 = coroutineContext[Job] // 1 coroutineScope { val job2 = coroutineContext[Job.Key] val job3 = launch {} val job4 = launch {} println("job2 = ${job2}") // 2 println("job2 parent = ${job2?.parent}") println("job3 parent = ${job3.parent}") println("job4 parent = ${job4.parent}") } withContext(Dispatchers.Default) { val job5 = coroutineContext[Job.Key] println("job5 = ${job5}") println("job5 parent = ${job5?.parent}") } println("job1 = ${job1}") } // // job5 = DispatchedCoroutine{Active}@4311e223 // job5 parent = BlockingCoroutine{Active}@1c2c22f3 // job1 = BlockingCoroutine{Active}@1c2c22f3coroutineScope도 주석 1, 2 부분이 같은 코루틴이라 job1이랑 job2가 동일하게 나올 거라 생각했는데 다르군요.그렇다면 현재 상황에서 "하나의 코루틴에 여러 개의 job(?)을 가진 것 아닌가? 이러면 job으로 코루틴을 제어할 수 있나?" 라는 의문이 듭니다. job과 코루틴의 관계가 헷갈립니다😱
-
해결됨코틀린 고급편
지식공유자님 inline에 대해서의 질문과 추가적인 질문 1가지가 있습니다!
먼저 강의 정말 감사합니다.. 질문 폭탄을 드려서 매번 죄송할따름입니다..1번 질문// inline 프로퍼티 class InLinePropertyPerson(val name: String) { inline val uppercaseName: String get() = this.name.uppercase() // 가능 // get() = field.uppercase() // 불가능 }위의 코드처럼 inline 프로퍼티의 경우 this로의 접근은 가능한 반면, backing field 사용시에는 불가능하더라구요!이렇게 되는 이유or원리가 궁금합니다 2번 질문inline 사용의 경우 디버깅이 어렵다는 말을 어느 컨퍼런스의 코틀린 주제 네트워킹 세션때 들은 것 같습니다그런 경우 강의에서 나온 대로 invoke를 유발할 수 있는 함수 파라미터의 경우 noinline을 붙이면 해결되는 부분일지, 아니면 inline을 제외한 일반 함수로 만들어서 디버깅으로 문제점을 찾고 다시 inline fun으로 만드는게 나은지 궁금합니다(어떤 경우 차라리 메서드 콜스택을 만드는 편이 진입점을 파악하기 좋으니깐요!) 3번 질문inline 키워드와는 관계 없는 질문이긴하지만..!// 1번 listOf(1, 2, 3) .map { num -> val result = num + 1 result } // 2번 listOf(1, 2, 3) .map { num -> val result = num + 1 return@map result }위와 같은 코드에서 보면 map의 중간연산에 최종적으로 result가 반환될거야 라고 명시적으로 나타내는 2번 방식과kotlin의 특징인 scope { } 의 최종 line 값을 반환하는 자동기능이 쓰인 1번 방식이 있는데코틀린을 처음 접하거나 모르는 사람이 보면 저기 왜 뜬금없이 쓰이지도 않는 변수인 result가 있지? 이상하군! 이렇게 생각할수도있을것같아요(그냥 예시 중 하나.. 아마 이렇게 생각하시는 분은 없다고 예상)오히려 2번 방식에서 아 여기 map에서의 최종연산 결과는 result구나 하고 map라벨링을 달고 리턴하는게 좀 더 가독성이 있어보이는데요!2번 방식은 라벨링을 해서 jump를 하는 방식이라 안티패턴으로 봐야하는지? 아니면 그렇지 않은지?가 궁금합니다 강의 잘 보고있습니다! 감사합니다 :)
-
해결됨코틀린 고급편
안녕하세요 JMH 설정 관련 팁을 드릴까 합니다! (24년 12월 16일 기준)
jmh { threads = 1 fork = 1 warmupIterations = 1 iterations = 1 } JVM/JDK toolchain: 21kotlin { jvmToolchain(21) }.gradle 버전: 8.8kotlin 버전: 2.0(컴파일러, 언어, API) 위 세 버전 기준으로는id("me.champeau.jmh") version "0.7.2"버전으로 하고$ ./gradlew jmh 실행이 에러없이 잘 작동하고실행결과는 build/reports/jmh/results.txt의 경로가 아닌build/results/jmh/results.txt 경로에 파일이 생긴 것을 확인했습니다
-
해결됨코틀린 고급편
Generic Func 질문이 있습니다
강사님 Generic Func에서// origin fun <T> List<T>.hasIntersection(other: List<T>): Boolean { return (this.toSet() intersect other.toSet()).isNotEmpty() } // error fun <T> List<T: Any>.hasIntersection(other: List<T>): Boolean { return (this.toSet() intersect other.toSet()).isNotEmpty() } // error fun <T> List<T>.hasIntersection(other: List<T : Any>): Boolean { return (this.toSet() intersect other.toSet()).isNotEmpty() } // error fun <T> List<T: Any>.hasIntersection(other: List<T : Any>): Boolean { return (this.toSet() intersect other.toSet()).isNotEmpty() } // ok fun <T : Any> List<T>.hasIntersection(other: List<T>): Boolean { return (this.toSet() intersect other.toSet()).isNotEmpty() }이렇게 되더라구요! 맨 앞에 <T>가 반환타입(List<T>)과 인자타입(other: List<T>) 까지 함께 선언해주는 declaration-site variance의 형태를 띄고 있어서 그런건가요?생각해보면 들어갈 수 있는 타입은 nullable인데 반환타입은 not null이거나 그 반대가 되면 헷갈릴 것 같아요. 그것을 막아둔것같기도 하구요!이렇게 제네릭 메서드를 만드는 경우에도 generic 제약을 줄 수 있는거죠!?좀 더 실 사용에 가깝게 타입가드까지 하려면fun <T : Number> List<T>.hasIntersection2(other: List<T>): Boolean { return (this.toSet() intersect other.toSet()).isNotEmpty() }이런 느낌으로 쓰일것같은데.. 맞나요? 코틀린 고급편 잘 듣고있습니다 ~_~
-
미해결자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)
코틀린 val 질문
클래스 속성에 val로 정의할 경우 속성 값을 외부로 반환할 때 getter 메소드를 만들어서 반환하시나요? 아니면 속성 그대로 인스턴스.속성 으로 바로 반환하시나요??만약에 후자일 경우 캡슐화 위반이 아닌지 생각이 듭니다...
-
해결됨자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)
완강 후 Kotlin 이런저런 질문 드려봅니다!
안녕하세요! 최태현 지식공유자님 코틀린 강의 만들어주셔서 감사합니다! 💚실제로 코프링 개발자로 1년정도 근무했던 경험이 있었는데.. 일했던 날짜와 한참 늦은 2024년 말이지만 추후에 강의를 완강하게 됐는데 매우 유익했습니다 몇가지 질문이 있어서 질문 남깁니다!!질문들TabWidth에 대해서IntelliJ에 보시면 tabWidth를 2로 설정하셨는데, 특별한 이유가 있으신가요?보통 저는 4로 설정하는 편이어서요!순수함수에 대해서제가 알기로는 내부의 scope에서 동작할때 외부의 요인이 내부의 값을 바꾸지 못하거나 내부의 요인으로 외부의 값이 바뀌지 못하계 경계가 있어서 side effect가 없는 함수를 뜻하는 걸로 알고있는데요 Java는 그렇기에 Stream api를 사용할때 final이 아닌 외부의 값을 참조하지 못하기때문에 좀 더 안전하게 작성할 수 있는데, 코틀린은 Closure, Currying 등을 허용하기 때문에 개발자의 실수를 더 많이 일으킬 가능성을 열어둔 것은 아닌지 궁금합니다OOP와 FP에 대해서자바같은 경우는 함수형 인터페이스또는 인터페이스 구현체를 익명클래스나 익명함수 등으로 넘겨서 FP를 모방하는걸로 알고있는데요, 대신 최소한의 getter/setter를 추가하고 행위를 나타내는 메서드명을 외부로 노출하고(public..), 그 메서드는 내부호출(private)로 캡슐화를 할 수 있는데, 코틀린처럼 invoke가 될 수 있는 함수를 인자로 넘기면 아래와 특정한 행위를 나타내는 이름이 아닌 행위 자체를 넘기게 돼서 객체의 역할과 책임을 나타내는 메서드가 없게 되서 rich domain model이 아닌 anemic domain model이 되는게 아닌지 궁금합니다// OOP fun doHobby(person: Person?) { println("${person?.hobby}생활을 즐기고 있습니다") } fun beAging(person: Person?) { println("내년에 ${person?.age?.plus(1)}만큼 늙어갈 예정입니다") } fun mainOOP() { val person = Person("보키", 20) doHobby(person) beAging(person) } // FP fun doSomething(person: Person?, run: () -> Unit) { } fun mainFP() { Person("보키", 20).run { doSomething(this) { println("${hobby}생활을 즐기고 있습니다") } } Person("보키", 20).run { doSomething(this) { println("내년에 ${age+1}만큼 늙어갈 예정입니다") } } Person("보키", 20).run { doSomething(this) { println("런닝중입니다 건들지마시오") } } } data class에 대해서대부분의 기능들이 강의에 녹아있기는 하지만 data class의 강력한 기능 중 하나인 copy 메서드는 백엔드의 프로덕션 개발과 테스트 코드에서 어디 부분에 사용될 수 있을까요?이건 많이 강의 주제를 넘어간 질문이긴 하지만.... Kotlin+Springboot+JPA 를 사용할때 data class/class 중 어떤 클래스로 Entity를 만드시는지 궁금합니다.MySQL or MongoDB or PostgreSQL에서 어떤 클래스와 궁합이 좋은지. data class를 사용한다면 copy를 어느 부분에 사용하면 좋을지도 알고싶습니다!강의내용과는 많이 무관하기에(코틀린이 아닌 서버, 타 라이브러리, 인프라(DB)까지 논의확장을 하였음) 답하기 어렵다면 안해주셔도 괜찮습니다Scope Func에 대해서저는 Kafka, Configuration 등에서 기본객체를 가져와서 apply를 이용해서 초기값 -> 덮어씌우는 방식을 주로 사용했고 with구문을 이용해서 log를 편하게 했었는데 scope func의 또 다른 좋은 선례도 알고싶습니다! Ex1) private fun <K : Any, V : Any> initConsumerProps( keyDeSerClass: Class<out Deserializer<K>>, valueDeSerClass: Class<out Deserializer<V>> ): Properties = Properties().apply { put(BOOTSTRAP_SERVERS_CONFIG, "localhost:9092") put(KEY_DESERIALIZER_CLASS_CONFIG, keyDeSerClass.name) put(VALUE_DESERIALIZER_CLASS_CONFIG, valueDeSerClass.name) put(GROUP_ID_CONFIG, "group-02") put(MAX_POLL_INTERVAL_MS_CONFIG, "60000") } Ex2) for (record in consumerRecords) { with(record) { logger.info { "key: ${key()}, partition: ${partition()}, offset: ${offset()}, value: ${value()}" } } }method reference에 대해서어떤 코드에서는 let(::XX), 또 다른 코드에서는 let { ... }방식이 사용될 때가 있는데, 어떤게 성능상 또는 가독성에서 좋은지도 궁금합니다!
-
해결됨자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)
확장함수 스타일 질문
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 강사님 9:54쯤에 질문이 있습니다val 또는 fun으로 확장함수를 만들 수 있는데..두가지 방식의 차이점이 있나요?또는 이펙티브 코틀린 등등에서 더 선호하는 방식이 있는지, 안티패턴이나 best practice가 있다면 알고 싶습니다.차이점이 있다면 확장함수에 val을 사용한 경우 custom getter방식으로 구현되고 호출부에 ()가 없는 반면, fun키워드를 사용한 경우에는 get()이 사용되지 않고 호출부에 ()를 해줘야 되는 것 같습니다 1번방식: 강의에서 나온대로 val_ 정의부:val List<Fruit>.samePriceFilter: List<Fruit> get() = this.filter(Fruit::isSamePrice)_ 호출부:val samePriceFruits2 = fruitsInList.flatMap { it.samePriceFilter }2번방식: fun으로 사용_ 정의부:fun List<Fruit>.samePriceFilter(): List<Fruit> { return this.filter(Fruit::isSamePrice) }_ 호출부:val samePriceFruits2 = fruitsInList.flatMap { it.samePriceFilter() }
-
미해결입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기
도커로 크롬에서 프로그램 열기
안녕하세요.크롬에서 프로그램을 열 수 없습니다. (11:00)https://github.com/bohuiKang/portfolio_bohui아래에 에러 메시지입니다. 어떤 부분이 문제인지 찾지 못했습니다.. 어디가 문제인지 알 수 있을까요?2024-12-12 18:14:04 2024-12-12T09:14:04.434Z INFO 1 --- [nio-8080-exec-8] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...2024-12-12 18:14:04 2024-12-12T09:14:04.434Z WARN 1 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 08S012024-12-12 18:14:04 2024-12-12T09:14:04.434Z ERROR 1 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Communications link failure2024-12-12 18:14:04 2024-12-12 18:14:04 The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.2024-12-12 18:14:04 2024-12-12T09:14:04.436Z WARN 1 --- [ main] o.h.e.j.e.i.JdbcEnvironmentInitiator : HHH000342: Could not obtain connection to query metadata2024-12-12 18:14:04 2024-12-12 18:14:04 org.hibernate.exception.JDBCConnectionException: unable to obtain isolated JDBC connection [Communications link failure...2024-12-12 18:14:04 2024-12-12T09:14:04.574Z WARN 1 --- [ main] org.hibernate.orm.deprecation : HHH90000025: MySQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)...2024-12-12 18:14:21 2024-12-12T09:14:21.680Z WARN 1 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 08S012024-12-12 18:14:21 2024-12-12T09:14:21.680Z ERROR 1 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : Communications link failure2024-12-12 18:14:21 2024-12-12 18:14:21 The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.2024-12-12 18:14:21 2024-12-12T09:14:21.742Z INFO 1 --- [nio-8080-exec-1] c.b.p.a.advice.AdminApiControllerAdvice : Could not open JPA EntityManager for transaction2024-12-12 18:14:21 2024-12-12 18:14:21 org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction...
-
해결됨코틀린 코루틴 완전 정복
KTOR Server 에서 delay
- 학습 관련 질문을 남겨주세요. 질문을 상세히 작성하면 더 좋습니다.- 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요, coroutine 강의를 듣고 ktor server 공부를 하는 중에 delay 관련 질의가 생겨 글 남깁니다. delay 의 경우 coroutine 이 스레드를 양보하고, 일정 시간 후에 다시 스레드가 비어있으면 점유하는 형식으로 진행될텐데, 아래와 같은 경우 delay 이후 코드가 실행되지 않습니다. 사용자 → KTOR Server → suspend function call → loop { logic → 너무 많은 동작을 제한하기위한 delay } ktor 는 기본적으로 요청이 IO Dispatcher 를 사용하는 것 같은데, coroutine 에서 delay 이후 기능이 동작하지 않는 것에 대한 이유가 있을까요..? (강의랑 무관한 내용이라 죄송합니다 ㅠ.ㅠ) /* Routing.kt */ fun Application.configureRouting(searcher: BaseSearcher) { routing { post("/search") { val params = call.receive<Map<String, String>>() searcher.logging("Received POST request with params: $params") val result: List<BaseDTO> = searcher.search(params) searcher.logging("POST Result: $result") call.respond(HttpStatusCode.OK, mapOf("result" to result)) } get("/") { call.respondText("Hello World!") } } } /* Searcher.kt */ /** * 함수 이름 : search * 내용 설명 : 입력된 map<string, string> param 을 바탕으로 요청 전송 및 응답 반환 */ override suspend fun search(searchParam: Map<String, String>): List<SimpleSearchLandResultDTO> { // 별도의 Job 으로 분리해서 오류시 상위 전파 제한 return HttpClient(CIO).use { client -> val parseResultLst: MutableList<SimpleSearchLandResultDTO> = mutableListOf() try { val initRequestResultDto: SimpleSearchResultDTO = sendFormedRequest(client, SearcherConst.URL_MAIN_PAGE, false) // 기본 페이지 요청이 성공했을 때 다음 요청을 진행 if (initRequestResultDto.isSuccess) { val mutableSearchParam = searchParam.toMutableMap() delay(300) while (true) { val eachPageSearchResultDto: SimpleSearchResultDTO = sendFormedRequest(client, makeGetUrl(mutableSearchParam), true) // 실패한 경우 종료 if (!eachPageSearchResultDto.isSuccess) { logging("[search] 조회 중 오류가 발생하였습니다.") break } // 결과가 빈 경우도 종료 else if (eachPageSearchResultDto.landSearchResultLst.isEmpty()) { break } // 결과가 있는 경우엔 전부 더해줌 parseResultLst.addAll(eachPageSearchResultDto.landSearchResultLst) logging("[search] result(${eachPageSearchResultDto.landSearchResultLst[0].currentPage} : ${eachPageSearchResultDto.landSearchResultLst}") // 마지막 페이지인 경우 종료 if (eachPageSearchResultDto.landSearchResultLst[0].currentPage == eachPageSearchResultDto.landSearchResultLst[0].maxPage ) { break } else { // 다음 페이지 수집 진행 mutableSearchParam["currentPage"] = (eachPageSearchResultDto.landSearchResultLst[0].currentPage + 1).toString() delay(300) } } } } catch (e: Exception) { logging("[search] search 함수 중 오류가 발생하였습니다. param: $searchParam, exception: $e") } logging("[search] Result : $parseResultLst") // parseResultLst 반환 parseResultLst } } private suspend fun sendFormedRequest(client: HttpClient, url: String, isResultNeed: Boolean): SimpleSearchResultDTO { return try { val response = client.get(url) { headers { append(HttpHeaders.Cookie, cookie.toCookieString()) append(HttpHeaders.Accept, SearcherConst.DEFAULT_HTTP_ACCEPT) append(HttpHeaders.AcceptEncoding, SearcherConst.DEFAULT_HTTP_ACCEPT_ENCODING) append(HttpHeaders.Connection, SearcherConst.DEFAULT_HTTP_CONNECTION) append(HttpHeaders.AcceptLanguage, SearcherConst.DEFAULT_HTTP_ACCEPT_LANGUAGE) append(HttpHeaders.UserAgent, SearcherConst.DEFAULT_HTTP_USER_AGENT) } } cookie.putAll(response.headers[HttpHeaders.SetCookie]?.split(";")?.map { it.split("=") }?. filter { it.size == 2 }?.filter { it[1] != "/" && it[1].isNotEmpty() }?.associate { it[0] to it[1] } ?: mapOf()) if (response.status != HttpStatusCode.OK) { SimpleSearchResultDTO(false) } else { SimpleSearchResultDTO(true, if(isResultNeed) parseData(response.bodyAsText()) else mutableListOf()) } } catch(e: Exception) { logging("[sendFormedRequest] Error : $e", Level.ERROR) SimpleSearchResultDTO(false) } }
-
미해결[LV1] Jetpack Compose - UI 연습하기
TextField에서 Cursor의 두께를 조절하는 방법이 있을까요?
안녕하세요 TextField 커스텀 관련하여 여러 기능을 살펴보고 있는데요. 구글에서 찾아봐도 Cursor의 두께를 설정하는 프로퍼티를 찾지 못하여서 질문드립니다. Cursor를 커스텀하는 방법이 있나요? 두께를 줄이거나 높이를 낮추는 등의 커스텀을 진행하고 싶습니다. 감사합니다.
-
해결됨코틀린 코루틴 완전 정복
CoroutineDispatcher(Default, IO)의 limitedParallelism 관련 질문
안녕하세요. 강사님코루틴 강의를 1번 완강하고, 최근에는 전체적으로 강의를 복습을 하고 있습니다.복습을 하다가 CoroutineDispatcher의 limitedParallelism 에 대해 몇가지 궁금한 점이 생겨서 질문을 남기게 됐습니다.Dispatchers.Default 관련Dispatchers.Default의 스레드풀 수를 넘어서는 숫자 혹은 스레드풀 수에 딱 맞게 스레드 수를 지정하여 limitedParallelism 함수를 실행하는 경우,Dispatchers.Default의 모든 스레드들은 제가 지정한 해당 작업을 처리하기 위해 계속 작업을 하고, 해당 작업이 시작된 이후에 Dispatchers.Default로 지정하여 실행한 코루틴 작업들은 제가 지정한 작업이 끝나기 전까지는 모두 대기하게 되는 것인가요??예를 들어, CPU 코어가 4개인 컴퓨터라면 Dispatchers.Default의 스레드 풀에 들어있는 스레드 수는 4개가 될테고, 해당 상황에서 Dispatchers.Default.limitedParallelism(4) 혹은 스레드가 몇개 있는지 몰라 Dispatchers.Default.limitedParallelism(8) 이렇게 지정하는 경우에아래 코드를 기준으로 하면 제가 먼저 시킨 작업(superCpuIntensiveTask())이 끝나기 전에는 lightCpuIntensiveTask()는 실행되지 않는 것일까요?(※ 멀티 스레드 작업이기 때문에 아래의 lightCpuIntensiveTask()가 먼저 실행될 가능성도 있지만 아주 재수가 나쁘게 superCpuIntensiveTask()가 먼저 실행이 된다면 어떻게 되는지가 궁금합니다)// CPU 코어가 4개인 컴퓨터 fun main() = runBlocking { launch(Dispatchers.Default.limitedParallelism(8)) { superCpuIntensiveTask() } launch(Dispatchers.Default) { lightCpuIntensiveTask() } println("Done") } 1번과 비슷한 질문입니다. CPU 코어가 4개인 컴퓨터에서 Dispatchers.Default.limitedParallelism를 여러번 사용한 경우, 코드상으로 보면 사용해야 하는 스레드의 수가 전체 스레드풀 수를 넘어서는 숫자가 되는데 이 경우에는 어떻게 스레드를 나눠서 사용하는 걸까요 ?// CPU 코어가 4개인 컴퓨터 fun main() = runBlocking { launch(Dispatchers.Default.limitedParallelism(3)) { superCpuIntensiveTask() } launch(Dispatchers.Default.limitedParallelism(3)) { superCpuIntensiveTask2() } launch(Dispatchers.Default.limitedParallelism(2)) { CpuIntensiveTask() } println("Done") }Dispatchers.IO 관련Dispatchers.IO.limitedParallelism를 사용하면 새로운 Thread 집합을 만든다고 말씀해주셨습니다. 그림에서 표현해주신 것처럼 기존에는 개별로 존재하는 Thread들을 새로 그룹으로 묶어낸다고 이해를 하면 되는 것일까요 ?? Dispatchers.IO.limitedParallelism으로 묶여있던 Thread들은 해당 코루틴이 끝나면 다시 그룹이 풀리는 것일까요 ??만약에 Dispatchers.IO.limitedParallelism를 많이 사용하여 이미 기존의 스레드들 모두가 집합으로 구성이 되어있는 경우에는 신규로 집합을 만들 수가 없는 경우에는 해당 Dispatchers.IO.limitedParallelism 요청이 어떻게 되는지 궁금합니다.limitedParallelism의 Thread 수 지정해당 코드와 같이 Dispatchers.Default.limitedParallelism(4) Thread 수를 지정해야 할때, 어떤 기준으로 어떻게 Thread 수를 지정해야 좋을까요 ?? 강사님만의 팁이 있으시면 공유가 가능하실까요??어떻게 수를 정해서 넣어야 할지 감이 잘 잡히지 않습니다. 😢1번과 관련된 질문입니다. Thread 수를 지정할 때 현재 Thread Pool에 존재하거나 남아있는 쓰레드가 몇개인지 충분히 고려를 하면서 코드를 짜야할까요 ??실수로 스레드 풀의 전체 Thread 수를 넘어선 요청을 하게 되면 어떻게 되는지 궁금합니다. 질문이 많아서 정말 죄송합니다. 🙇♂🙇♂🙇♂
-
미해결[왕초보편] 앱 8개를 만들면서 배우는 안드로이드 코틀린(Android Kotlin)
다이얼로그 띄우구
저는 다이어트는 내일부터 하는거 아닙니까 창 뜬이후로 창이 꺼져버리는데 이유가 멀까요?