Top Spring Framework Interview Questions | Baeldung
Baeldung
React, 사용자 인터페이스를 만들기 위한 자바스크립트 라이브러리 (웹 프레임워크). 싱글 페이지 애플리케이션이나 모바일 애플리케이션의 개발 시 토대로 사용될 수 있고, 사용자와 상호작용할 수 있는 동적인 UI를 쉽게 만들 수 있음
Spring 면접 전 살펴보기 위한 Q&A 35가지 (2024년 ver)
스프링 프레임워크란 무엇인가요?
자바 엔터프라이즈 애플리케이션 개발을 위한 가장 널리 사용되는 프레임워크입니다.
경량화, 제어 역전(IOC), 관점 지향 프로그래밍(AOP), 트랜잭션 관리 등의 기능을 제공합니다.
스프링을 사용하면 어떤 장점이 있나요?
경량화로 프레임워크로 인한 개발 오버헤드가 적습니다.
IoC 컨테이너가 객체 간 의존성 주입을 관리해줍니다.
AOP로 핵심 로직과 시스템 서비스를 분리할 수 있습니다.
대표적인 스프링 하위 프로젝트들은 무엇인가요?
스프링 코어: IoC/DI 등 프레임워크 핵심 기능 제공
스프링 JDBC: JDBC 코딩 없이 DB 연동 기능
스프링 ORM: JPA, Hibernate 등의 ORM 연동 계층
스프링 웹: 파일 업로드, 서블릿 리스너 등 웹 관련 기능
스프링 MVC: MVC 아키텍처 웹 개발 모듈
스프링 AOP: AOP 구현 및 메소드 인터셉터 정의
의존성 주입(DI)이란 무엇인가요?
객체를 직접 생성하지 않고, 생성 방법을 기술하면 IoC 컨테이너가 필요할 때 인스턴스화해서 주입하는 개념입니다.
스프링에서 빈을 주입하는 방식에는 어떤 것들이 있나요?
Setter 주입
생성자 주입
필드 주입
XML 또는 애노테이션으로 설정 가능
빈 주입 방식 중 권장되는 방식은 무엇이고 그 이유는 무엇인가요?
필수 의존성은 생성자 주입, 선택적 의존성은 Setter 주입을 권장합니다.
생성자 주입을 사용하면 immutable 필드에 값 주입이 가능하고 테스트가 용이해집니다.
BeanFactory와 ApplicationContext의 차이점은 무엇인가요?
BeanFactory는 빈 인스턴스를 제공하고 관리하는 컨테이너 인터페이스입니다. getBean()이 호출될 때 lazy하게 빈을 생성합니다.
ApplicationContext는 BeanFactory를 상속하면서 애플리케이션의 모든 정보, 메타데이터, 빈을 담고 있는 컨테이너 인터페이스입니다. 기본적으로 애플리케이션 구동 시점에 eager하게 빈을 생성합니다.
스프링 빈이란 무엇인가요?
스프링 IoC 컨테이너에 의해 인스턴스화, 관리되는 자바 오브젝트를 말합니다.
스프링 프레임워크에서 빈의 기본 스코프는 무엇인가요?
별도 설정이 없다면 스프링 빈은 singleton 스코프로 생성됩니다.
빈의 스코프는 어떻게 지정할 수 있나요?
@Scope 애노테이션이나 XML 설정 파일에서 "scope" 속성을 사용해 지정할 수 있습니다.
스프링에서 지원하는 빈 스코프에는 다음과 같은 것들이 있습니다.
singleton
prototype
request
session
global-session
싱글톤 빈은 스레드에 안전한가요?
아니오. 싱글톤 빈 자체는 스레드 세이프하지 않습니다.
스레드 안전성은 빈의 실행 방식에 달려 있고, 싱글톤은 생성 방식에 초점을 둔 디자인 패턴입니다.
빈의 구현 코드에 따라 스레드 안전성이 결정됩니다.
스프링 빈의 생명주기는 어떻게 되나요?
빈 정의를 읽고 인스턴스화 → 의존성 주입 → 초기화 콜백 메소드 호출 → 사용 → 소멸 콜백 메소드 호출 → 빈 소멸
각 단계별로 초기화 메소드, 소멸 메소드 등을 빈에 적절히 설정해서 사용할 수 있습니다.
스프링 자바 기반 설정이란 무엇인가요?
XML 기반 설정의 대안으로, 자바 클래스와 애노테이션을 사용해 설정하는 방식입니다.
타입 세이프한 방식으로 스프링 애플리케이션 구성이 가능합니다.
하나의 프로젝트에 여러 개의 스프링 설정 파일을 사용할 수 있나요?
네, 가능합니다. 큰 프로젝트에서는 모듈화와 유지보수성을 위해 여러 개의 설정 파일 사용이 권장됩니다.
자바 기반 설정에서는 @Configuration과 @Import 애노테이션을 사용해 여러 설정 클래스를 조합할 수 있습니다.
XML 기반 설정에서는 <import> 태그를 사용해 여러 XML 파일을 조합할 수 있습니다.
스프링 시큐리티란 무엇인가요?
스프링 기반 애플리케이션의 인증과 권한 부여 등 보안 기능을 담당하는 프레임워크입니다.
스프링 시큐리티를 사용하면 인증/인가 관련 표준 로직을 작성하지 않아도 되고, CSRF 공격 등을 방어할 수 있습니다.
웹 애플리케이션에 @EnableWebSecurity 애노테이션만 붙여주면 기본적인 웹 보안 기능이 작동합니다.
스프링 부트란 무엇인가요?
스프링 기반 애플리케이션을 빠르게 개발할 수 있게 도와주는 프로젝트입니다.
단독 실행 가능한 스프링 애플리케이션을 쉽게 생성할 수 있습니다.
내장 서버, 자동 설정, starter 의존성 등으로 최소한의 설정으로 개발을 시작할 수 있습니다.
스프링 프레임워크에서 사용되는 디자인 패턴에는 어떤 것들이 있나요?
싱글톤 패턴: 싱글톤 스코프의 빈
팩토리 패턴: BeanFactory 클래스
프로토타입 패턴: 프로토타입 스코프의 빈
프록시 패턴: 스프링 AOP
템플릿 메소드 패턴: JdbcTemplate, HibernateTemplate 등
프론트 컨트롤러 패턴: 스프링 MVC의 DispatcherServlet
데이터 접근 오브젝트(DAO) 패턴: 스프링 DAO 지원
프로토타입 스코프는 어떻게 동작하나요?
프로토타입 스코프로 정의된 빈은 매번 getBean() 메소드가 호출될 때마다 새로운 오브젝트를 생성해서 반환합니다.
이는 기본인 싱글톤 스코프와 대조적입니다. 싱글톤은 IoC 컨테이너당 하나의 오브젝트만 생성합니다.
스프링 빈에서 ServletContext와 ServletConfig 객체는 어떻게 얻나요?
스프링에서 제공하는 Aware 인터페이스들을 구현하는 방법이 있습니다.
@Autowired 애노테이션을 사용해서 주입받는 방법도 가능합니다.
스프링 MVC의 컨트롤러란 무엇인가요?
스프링 MVC에서 사용자의 요청을 처리하는 컴포넌트입니다.
@Controller 애노테이션이 붙은 클래스가 컨트롤러의 역할을 합니다.
컨트롤러 클래스의 메소드는 @RequestMapping 애노테이션으로 매핑된 특정 URI를 처리합니다.
@RequestMapping 애노테이션은 어떻게 사용하나요?
요청 URL을 컨트롤러의 메소드와 매핑할 때 사용하는 애노테이션입니다.
URL 패턴 외에도 HTTP 메소드 타입, 헤더, 파라미터 등을 매핑 조건으로 지정 가능합니다.
@PathVariable을 통해 URL 템플릿 변수를 메소드 파라미터로 받을 수 있습니다.
@RequestParam을 통해 HTTP 요청 파라미터를 메소드 파라미터로 받을 수 있습니다.
스프링 JDBC의 JdbcTemplate 클래스는 무엇이고 어떻게 사용하나요?
스프링에서 JDBC 프로그래밍을 쉽게 할 수 있도록 제공하는 템플릿 클래스입니다.
리소스 생성, 해지 등의 low-level 작업을 대신 처리해줍니다.
SQLException을 스프링 DataAccessException으로 변환하는 기능도 제공합니다.
JdbcTemplate을 사용하려면 DataSource를 스프링 설정 파일에 등록해야 합니다.
스프링에서 트랜잭션은 어떻게 사용하고, 어떤 이점이 있나요?
선언적 트랜잭션(@Transactional)과 프로그래밍적 트랜잭션(TransactionTemplate) 두 가지 방식을 제공합니다.
선언적 트랜잭션은 코드 침투가 없고 AOP를 사용하기 때문에 권장됩니다.
트랜잭션 전파, 격리 수준, 읽기 전용 등을 애노테이션 속성으로 제어할 수 있습니다.
트랜잭션 경계를 메소드 단위로 설정할 수 있어서 productivity가 높아집니다.
다양한 데이터 접근 기술에 대해 일관된 트랜잭션 제어가 가능합니다.
스프링 DAO란 무엇인가요?
Data Access Object의 약자로, 데이터 접근을 추상화한 객체입니다.
스프링은 일관성 있는 DAO를 작성할 수 있도록 다양한 기능을 제공합니다.
저수준 예외를 스프링의 통일된 예외 체계로 변환해줍니다.
템플릿 클래스를 통해 boilerplate 코드를 제거할 수 있습니다.
AOP(Aspect-Oriented Programming)란 무엇인가요?
관점 지향 프로그래밍이라고 하며, 스프링의 핵심 기능 중 하나입니다.
여러 객체에 공통으로 적용될 수 있는 기능(cross-cutting concern)을 분리해서 모듈화합니다.
주로 로깅, 트랜잭션, 보안 등 인프라 레벨의 공통 기능을 구현하는데 사용합니다.
AOP를 통해 객체 간 결합도를 낮추고, 코드 재사용성을 높일 수 있습니다.
AOP에서 Aspect, Advice, Pointcut, JoinPoint란 무엇인가요?
Aspect: 여러 객체에 공통으로 적용되는 공통 관심사(cross-cutting concern)를 모듈화 한 것입니다. 트랜잭션 관리 등이 대표적입니다.
Advice: 특정 JoinPoint에서 Aspect에 의해 취해지는 조치입니다. Around, Before, After 등의 타입이 있습니다.
Pointcut: Advice가 적용될 JoinPoint를 선별하는 조건입니다. 주로 정규 표현식으로 표현합니다.
JoinPoint: Advice가 적용될 수 있는 위치입니다. 메소드 호출, 예외 발생 등이 있습니다.
AOP의 Weaving이란 무엇인가요?
Aspect를 타깃 객체에 적용해서 새로운 프록시 객체를 생성하는 과정을 말합니다.
즉, Advice를 핵심 로직 코드에 삽입하는 과정입니다.
컴파일타임, 로딩타임, 런타임에 적용할 수 있는데, 스프링 AOP는 런타임 위빙을 사용합니다.
리액티브 프로그래밍이란 무엇인가요?
데이터 흐름과 변경 전파에 중점을 둔 프로그래밍 패러다임입니다.
주요 특징으로는 논블로킹(non-blocking), 이벤트 기반(event-driven), 느슨한 결합(loosely coupled), 확장성(scalable) 등이 있습니다.
Observer 패턴을 확장해서, 데이터 스트림을 비동기적으로 처리합니다.
스프링에서는 5.0 버전부터 WebFlux를 통해 리액티브 프로그래밍을 지원하고 있습니다.
스프링 WebFlux란 무엇인가요?
스프링 5.0에서 추가된 리액티브 웹 프레임워크입니다.
기존의 스프링 MVC와는 별개의 모듈로서 완전한 논블로킹 방식으로 동작합니다.
네티(Netty)를 기반으로 동작하며, 서블릿 컨테이너에서도 동작 가능합니다.
함수형 프로그래밍 방식을 지원하며, Mono와 Flux 타입을 사용해 리액티브 데이터 타입을 표현합니다.
Mono와 Flux란 무엇인가요?
스프링 5의 리액티브 프로그래밍에서 사용되는 핵심 객체 타입입니다.
Mono는 0-1개의 데이터를 발행하는 Reactive Stream 구현체입니다.
Flux는 0-N개의 데이터를 발행하는 Reactive Stream 구현체입니다.
모두 Reactive Stream 표준 인터페이스인 Publisher를 구현하고 있습니다.
WebClient와 WebTestClient는 각각 어떤 용도인가요?
WebClient는 스프링 WebFlux에서 제공하는 논블로킹 방식의 리액티브 HTTP 클라이언트입니다.
HTTP 요청을 비동기적으로 처리하며, Mono나 Flux를 사용해 응답을 받을 수 있습니다.
WebTestClient는 WebFlux 애플리케이션을 테스트할 때 사용하는 클라이언트입니다.
실제 서버를 띄우지 않고도 mock request/response를 사용해 컨트롤러 테스트를 할 수 있습니다.
리액티브 스트림 사용 시 주의할 점은 무엇인가요?
리액티브 프로그래밍은 기존 방식과 사고의 전환이 필요해 진입장벽이 높습니다.
리액티브 스트림을 디버깅하기 쉽지 않기 때문에 디버깅 방식에 대한 학습이 필요합니다.
전통적인 JDBC 기반 DB 드라이버는 리액티브 방식을 지원하지 않아 사용에 주의가 필요합니다.
스프링 5에서 리액티브 프로그래밍을 지원하기 위해 최소 자바 버전이 올라갔나요?
네, 맞습니다. 스프링 5는 자바 8 이상에서만 동작합니다.
자바 8의 람다, Stream API, CompletableFuture 등을 활용하기 위해 최소 사양을 올린 것으로 보입니다.
스프링 5는 자바 9의 모듈 시스템(Jigsaw)을 어떻게 지원하나요?
스프링 5의 프레임워크 라이브러리들은 자바 9 모듈 시스템을 따르도록 변경되었습니다.
그래서 필요한 모듈만 선택적으로 가져올 수 있게 되었습니다.
하지만 스프링 부트 2.0에서는 아직 자바 9 모듈을 완벽하게 지원하지 않습니다.
스프링 MVC와 WebFlux를 함께 사용할 수 있나요?
스프링 부트에서는 현재 둘 중 하나만 선택해서 사용하는 것을 권장합니다.
왜냐하면 둘 다 사용하면 자동 설정이 제대로 동작하지 않기 때문입니다.
또한 스프링 MVC는 서블릿 기반인 반면 WebFlux는 네티 기반이라 함께 사용하기에 적절치 않습니다.
출처 : https://www.baeldung.com/spring-interview-questions
📌 프론트엔드 개발자가 구독하는 메일링 서비스
제가 평소에 구독하고 있는 메일링 서비스를 소개드려요!
https://javascriptweekly.com/
매주 금요일, 자바스크립트 관련 소식을 받아볼 수 있어요.
2️⃣ Frontend Focus
https://frontendfoc.us/
매주 목요일, 프론트엔드 관련 소식을 받아볼 수 있어요. 최신 프레임워크, 라이브러리에 대한 정보를 얻을 수 있어요.
3️⃣ React Status
https://react.statuscode.com/
매주 목요일, React와 ReactNative 관련 소식을 받아볼 수 있어요. React 개발자라면 한 번쯤 알아두면 좋을 정보를 얻을 수 있어요.
4️⃣ Korean FE article
https://kofearticle.substack.com/about
매주 해외 아티클을 한국어로 번역해서 소개해요. 외국 커뮤니티에서 인기 있었던 글을 한국어로 번역해주는 경우가 많아서 트렌트를 파악하기 좋아요.
5️⃣ GeekNews Weekly
https://news.hada.io/weekly
매주 월요일, 일주일간의 GeekNews 중 엄선한 뉴스를 소개해요. 제목만 요약만 보고도 IT 업계와 기술의 흐름을 놓치지 않도록 도와줘요.
6️⃣ 일일일
https://oneoneone.kr/
매일 원하는 주제의 컨텐츠를 소개해요. 저는 개발 주제를 구독하고 있어요. 국내 기술 블로그나 커뮤니티에서 인기 있었던 글을 확인할 수 있어요.
7️⃣ 언섹시 비즈니스
https://maily.so/unsexybusinesskr
화려하지 않은 비즈니스들을 소개해요. 사이드 프로젝트 아이디어를 얻기에 좋아요.
8️⃣ 어피티 머니레터
https://uppity.co.kr/
경제 뉴스, 재테크 팁을 소개해요. 개발자에도 필요한 최소한의 금융 지식을 얻기 위해 구독하고 있어요.
당신은 개발자가 아니라 Frameworker 입니다.
Spring Framework 없이 자바로 서버 개발하기, 장고 없이 Python 백엔드 만들기, React 없이, Tailwind 없이 프론트 화면 만들기. 상상해본적 있으신가요?
요즘 개발은 확장성은 쉬워진 듯 하면서도 프레임워크 없이 단순하게 만들기 그 자체는 오히려 어려워 진 것 같습니다. 모던하게 만든다! 라고 생각했을때 단순한 CRUD 어플리케이션 개발을 위해 그냥 떠오르는 필요한 Framework, 모듈만 5개 이상은 사용하는 것 같습니다. 혹시 단순 SQL 쿼리 작성할때는 헷갈리지만 ORM으로는 뚝딱 만들고, javascript 함수의 기본은 잘 모르지만 next.js 로 어떻게 짜면 되는지 부터 찾아보진 않으셨나요?
물론 실용적인 프레임워크 사용법을 빠르게 익히는 것도 중요하지만, 그 기반이 되는 원리를 이해하고 단순하게 구현할 수도 있는 것을 알고 천천히 달려가는 것과 모르고 앞으로 달리는 것은 분명 다를것입니다. 그리고 생각보다 그 이해가 엄청나게 어렵지도 않고 오히려 더 재밌고 더 멀리 나아가는데 큰 도움이 될 수 있을 것입니다.
https://dev.to/themuneebh/youre-not-a-software-engineer-youre-a-frameworker-3bkk
📌 React 면접 질문과 답변 모음
300개 이상의 React 면접 질문과 답변을 모아둔 저장소 입니다. 영어로 되어 있지만 번역기의 도움을 받아 공부하면 면접 준비에 큰 도움이 될 것 같네요!
https://github.com/sudheerj/reactjs-interview-questions
🚀 Java의 미래, Virtual Thread
제가 현재 가장 관심있는 내용 중 하나가 바로 Java 의 Virtual Thread 인데요,
이번에 우아한 기술블로그에서 Virtual Thread 에 대해 기술적으로 자세히 정리를 해주셔서 공유드립니다.
Virtual Thread 의 구조와 동작원리, 기존 스레드 모델과의 차이와 성능테스트까지 자세히 정리되어있으니, 관심있으신 분들은 공유드린 원문 내용을 참고해주세요.
저도 잘 몰랐던 Virtual Thread 의 주의사항과 Project Reactor Netty 와의 성능 비교가 흥미롭네요.
저도 기회가 되면 한번 테스트해봐야겠습니다.
📚 원문
https://techblog.woowahan.com/15398/
📚 함께 보면 좋은 글
💡 Java 의 Project Loom 이 만들어지고 있어요! 조금만 기다려주세요!: https://careerly.co.kr/comments/63588
🌈 함수에도 색깔이 있는걸 아시나요?: https://careerly.co.kr/comments/63069
🎉 Java 가상 스레드가 JDK 21 에 정식 지원 됩니다: https://careerly.co.kr/comments/82046
🍀 Spring 6.1 부터 JDK 21 이 지원됩니다 🎉: https://careerly.co.kr/comments/89053
시뮬레이션을 실행하려고 해도 스크립트 문제, iPhone 버전 범위 문제, 시뮬레이터 문제가 계속 발생합니다. 어떤 도움이라도 감사합니다. ReactNative를 처음 접했습니다. 저희 팀에서 저를 도울 수 있는 사람이 없습니다. #프로젝트 환경 mac M2 ruby -v ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin23] node -v v20.10.0 pod --version 1.15.2 package.json { "name": "labts", "version": "0.0.1", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "lint": "eslint .", "start": "react-native start", "test": "jest" }, "dependencies": { "@react-native-community/async-storage": "^1.12.1", "@react-native-community/cli": "13.6.9", "@react-navigation/bottom-tabs": "^6.6.0", "@react-navigation/native": "^6.1.17", "@react-navigation/native-stack": "^6.10.0", "@tanstack/react-query": "^5.51.5", "@types/react-native-vector-icons": "^6.4.18", "axios": "^1.7.2", "date-fns": "^3.6.0", "immer": "^10.1.1", "react": "18.2.0", "react-native": "0.74.3", "react-native-calendars": "^1.1305.0", "react-native-date-picker": "^5.0.4", "react-native-dotenv": "^3.4.11", "react-native-get-random-values": "^1.11.0", "react-native-image-crop-picker": "^0.41.2", "react-native-image-zoom-viewer": "^3.0.1", "react-native-paper": "^5.12.3", "react-native-permissions": "^4.1.5", "react-native-safe-area-context": "^4.10.8", "react-native-screens": "^3.32.0", "react-native-splash-screen": "^3.3.0", "react-native-tab-view": "^3.5.2", "react-native-vector-icons": "^10.1.0", "react-native-vision-camera": "^4.5.1", "uuid": "^10.0.0", "yarn": "^1.22.22" }, "devDependencies": { "@babel/core": "^7.20.0", "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", "@react-native/babel-preset": "0.74.85", "@react-native/eslint-config": "0.74.85", "@react-native/metro-config": "0.74.85", "@react-native/typescript-config": "0.74.85", "@types/react": "^18.2.6", "@types/react-native-dotenv": "^0.2.2", "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.6.3", "babel-plugin-module-resolver": "^5.0.2", "eslint": "^8.19.0", "jest": "^29.6.3", "prettier": "2.8.8", "react-test-renderer": "18.2.0", "typescript": "5.0.4" }, "engines": { "node": ">=18" } } PodFile require Pod::Executable.execute_command('node', ['-p', 'require.resolve( "react-native/scripts/react_native_pods.rb", {paths: [process.argv[1]]}, )', __dir__]).strip platform :ios, '12.0' use_frameworks! #use_modular_headers! prepare_react_native_project! linkage = ENV['USE_FRAMEWORKS'] if linkage != nil Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green use_frameworks! :linkage => linkage.to_sym end target 'nexlabts' do config = use_native_modules! use_react_native!( :path => config[:reactNativePath], # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." ) target 'nexlabtsTests' do inherit! :complete # Pods for testing end post_install do |installer| # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 react_native_post_install( installer, config[:reactNativePath], :mac_catalyst_enabled => false, # :ccache_enabled => true ) end end 제가 아래 에러 3가지에 대해 제가 해본 방법들입니다. 1. node 재설치 node_module 폴더 삭제, package-rock.json 삭제 후 재설치 npm install --legacy-peer-deps yarn install 2. Xcode가 node 읽을 수 있도록 설정 sudo ln -s "$(which node)" /usr/local/bin/node 3. Podfile 내 platform 설정 수정 platform :ios, '12.0' or platform :ios, '14.0' 4. Pods 재설치 rm -rf ~/Library/Developer/Xcode/DerivedData or rm -rf ~/Library/Developer/Xcode/DerivedData/* rm -rf Pods rm Podfile.lock pod install --repo-update Xcode \> Product \> Clean Build Folder. cd ./ios pod cache clean -all pod install --repo-update cd ../ npx react-native run-ios --no-packager --simulator="iPhone 15" or npx react-native run-ios --simulator="iPhone 15" or yarn start > i(run ios) Err 1. cocoaPods 설치할 때 [!] CocoaPods could not find compatible versions for pod "React-RuntimeHermes": In Podfile: React-RuntimeHermes (from ../node_modules/react-native/ReactCommon/react/runtime) Specs satisfying the React-RuntimeHermes (from ../node_modules/react-native/ReactCommon/react/runtime) dependency were found, but they required a higher minimum deployment target. Err2. iOS 실행할때 run-ios --no-packager --simulator="iPhone 15" Build description signature: fc1341421f84b87c5245d346c2c17b66 Build description path: /Users/nowonjae/Library/Developer/Xcode/DerivedData/nexlabts-argvodqcybjfcybstpulfpghnzvm/Build/Intermediates.noindex/XCBuildData/fc1341421f84b87c5245d346c2c17b66.xcbuilddata /Users/nowonjae/Desktop/project/NeXLabRN/ios/nexlabts.xcodeproj:1:1: error: Unable to open base configuration reference file '/Users/nowonjae/Desktop/project/NeXLabRN/ios/Pods/Target Support Files/Pods-nexlabts/Pods-nexlabts.release.xcconfig'. (in target 'nexlabts' from project 'nexlabts') warning: Unable to read contents of XCFileList '/Target Support Files/Pods-nexlabts/Pods-nexlabts-resources-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') warning: Unable to read contents of XCFileList '/Target Support Files/Pods-nexlabts/Pods-nexlabts-frameworks-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-frameworks-Release-input-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-frameworks-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') warning: Run script build phase 'Bundle React Native code and images' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'nexlabts' from project 'nexlabts') warning: Run script build phase '[CP] Embed Pods Frameworks' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-resources-Release-input-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-resources-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') warning: Run script build phase '[CP] Copy Pods Resources' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'nexlabts' from project 'nexlabts') --- xcodebuild: WARNING: Using the first of multiple matching destinations: { platform:iOS Simulator, id:B5AA2E84-4F83-4749-A986-A1FCE5E398A3, OS:17.5, name:iPhone 15 } { platform:iOS Simulator, id:B5AA2E84-4F83-4749-A986-A1FCE5E398A3, OS:17.5, name:iPhone 15 } ** BUILD FAILED ** ] Err3. Xcode 로 Build 할때 (Any iOS Simulator Device (arm64, x86_64)) Command PhaseScriptExecution failed with a nonzero exit code
위 에러 해결되었습니다. 크몽에 범코딩님께 에러 문의 및 코칭 요청드렸고 피드백으로 에러 발생 원인은 중복실행 문제로. 아이폰 직접 연결 빌딩시 해결될 것으로 판단된다고 피드백 주셔서, 진행해보니 정상적으로 잘 빌딩되었습니다. 관련해서 추가 참고한 블로그입니다. https://pocket-dev.tistory.com/35#google_vignette
import React, { useEffect, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import { TbArrowBigUpFilled } from "react-icons/tb"; import useScroll from "../utils/useScroll"; import useProductsQuery from "../useProductsQuery"; const Nav = () => { const { isNavFixed } = useScroll(); const [activeButton, setActiveButton] = useState(0); const handleButtonClick = (index) => { setActiveButton(index); }; const categoryNames = { materials: "소재별 품목", purposes: "용도별 품목", }; const [isScrollMenuVisible, setScrollMenuVisible] = useState(false); const handleMouseEnter = () => { setScrollMenuVisible(true); }; const handleMouseLeave = () => { // console.log("마우스 이탈"); setScrollMenuVisible(false); }; const location = useLocation(); const isloginorsignup = location.pathname === "/login" || location.pathname === "/signup"; const { isLoading, isError, errorMessage, materials, purposes } = useProductsQuery(); const [hoveredOnedepth, setHoveredOnedepth] = useState(null); const [onedepthChildren, setOnedepthChildren] = useState([]); const [hoveredDivision, setHoveredDivision] = useState(null); const [twodepthDivision, setTwodepthDivision] = useState([]); const handleOnedepthMouseEnter = (item) => { setHoveredOnedepth(item); // console.log(setHoveredOnedepth) setOnedepthChildren(item.children); }; const handletwodepthMouseEnter = (child) => { setHoveredDivision(child); // console.log(setTwodepthDivision); setTwodepthDivision(child.divisions); // console.log(child.divisions); }; if (isLoading) return <>로딩 중...</>; if (isError) return <>{errorMessage}</>; return ( <> {!isloginorsignup ? ( <> {/* 대메뉴 */} <nav id="nav" className={`nav ${isNavFixed ? "fixed" : ""}`}> <div className="nav_wrap"> <div className="twobutton_wrap"> {["materials", "purposes"].map((category, index) => ( <Link type="button" className={`twobutton ${ activeButton === index ? "active" : "" }`} key={index} onClick={() => handleButtonClick(index)} to={`/${category}`} > {categoryNames[category]} </Link> ))} </div> {/* 내비게이션 1depth */} <div className="scrollnav"> <ul className="materialnav" style={{ display: activeButton === 0 ? "flex" : "none", }} > {materials.map((material) => ( <li key={material.id} className="materialnav_li" onMouseEnter={() => { handleOnedepthMouseEnter(material); handleMouseEnter(); }} onMouseLeave={handleMouseLeave} > <Link to="/">{material.name}</Link> </li> ))} </ul> <ul className="usagenav" style={{ display: activeButton === 1 ? "flex" : "none", }} > {purposes.map((purpose) => ( <li key={purpose.id} className="usagenav_li" onMouseEnter={() => { handleOnedepthMouseEnter(purpose); handleMouseEnter(); }} onMouseLeave={handleMouseLeave} > <Link to="/">{purpose.name}</Link> </li> ))} </ul> </div> <div className="call_wrap"> <div className="call"> <div className="call_title">견적 상담 · 문의 대표전화</div> <div className="top_button"> <TbArrowBigUpFilled className="top_icon" /> <p>TOP</p> </div> </div> </div> </div> </nav> {/* 2&3depth 호버 메뉴 */} <div className={`scrollmenu ${isScrollMenuVisible ? "show" : ""} ${ isNavFixed ? "fixed" : "" }`} style={{ visibility: isScrollMenuVisible ? "visible" : "hidden" }} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > {activeButton === 0 && ( <div className="scrollmenu_top"> <ul className="depth2_wrap"> {onedepthChildren.map((child) => ( <li key={child.id} className="depth2_menu" onMouseEnter={() => { handletwodepthMouseEnter(child); }} > <div className="depth2_menu_img_wrap"> <div className="depth2_menu_img"> <img src={child.images[0]._links.href} alt={child.name} /> </div> </div> <p>{child.name}</p> </li> ))} </ul> </div> )} <div className="scrollmenu_middle"> <div className="depth3_wrap"> {twodepthDivision.map((division) => ( <Link key={division.id} to="/" className="depth3_menu"> <span className="depth3_menu_img"> {/* images 속성이 없거나 비어 있어도 오류를 방지 */} {division.images && division.images.length > 0 && ( <img src={division.images[0]._links.href} alt={division.division_name} /> )} </span> <p>{division.division_name}</p> </Link> ))} </div> </div> <div className="scrollmenu_bottom"></div> </div> </> ) : null} </> ); }; export default Nav; 현재는 depth2_menu 호버해야만 depth3_menu가 보이는데 초기에 scrollmenu가 보일때부터 depth2_menu가 (onedepthChildren.map의 첫번째 순서의 데이터가) 호버되어있어 그에 매칭되는 division의 내용들이 depth3_menu에 보이게 할수있는 방법 있을까요 ?
안녕하세요! 자세하게 코드를 남겨주신덕에 호다닥 잘 살펴보고 왔습니다ㅎㅎㅎ 현재 주신 구조에서 ScrollMenu가 보이기 시작하는 시점은 material이나 purpose를 호버하는 시점으로 보이네요! 현재 코드를 유지하는 관점에서 가장 간단한 해결법으로는, handleOnedepthMouseEnter 핸들러 내에서 setOnedepthChildren을 해줌과 동시에 if (item.children.length > 0) handletwodepthMouseEnter(item.children[0]) 를 호출해주는 방법이 있을 수 있겠네요! 질문의 의도가 맞는지는 모르겠지만, 만약 activeButton이 1(purpose)일 경우 depth2를 건너뛰는게 목적이라면, usagenav_li의 onMouseEnter에서 바로 handletwodepthMouseEnter(purpose)를 호출해주는 것도 방법일 수 있습니다. 일단 코드를 유지하는 선에서 방식을 말씀 드리긴 했지만, 저의 개인적인 욕심(?)을 아주 약간 덧붙여 몇가지 리팩토링을 진행해주신다면 조금 더 좋은 코드가 될 수 있어보입니다! 1. 내부 변수 이름 개선 - 조금 더 시멘틱하고 통일된 스타일의 변수 / 핸들러 명을 지어주시면 유지보수 면에서 깔끔할 것 같습니다 2. activeButton 개선 - 카테고리 선택시 index보다는 enum(TS 사용시), 혹은 categoryNames오브젝트를 활용하여 관리해주신다면, 추후 카테고리의 추가나 순서 변화 등에 유연하게 대처 가능한 코드가 될 것 같습니다 3. depth1의 경우, style의 display속성을 통해 변환해주시는 것으로 보입니다. 이럴 경우, 렌더 트리에는 반영되지 않지만 DOM트리에는 두 컴포넌트 모두 반영되어 리소스 낭비 및 불필요한 리렌더가 예상되므로, 조건부 렌더 방식을 고려해보시는 것도 좋아보입니다. (레퍼런스: https://ssangq.netlify.app/posts/conditional-rendering-vs-diplay-none) 4. !isloginorsignup 일 경우 그냥 null로 early return 시켜버려도 깔끔할 것 같습니다 조금 욕심 부리다보니 글이 길어졌네요,,ㅎㅎㅎ 기획 의도나 스타일에 따라 해석이 달라질 수 있으니, 혹시 추가로 궁금하신 부분이나 답변의 핀트가 엇나간 부분이 있다면 편하게 알려주세요!
안녕하세요! Next.js SSR + react-query 조합을 사용하려고 하는데요, page 컴포넌트 내 getServerSideProps 함수에서 prefetching을 받아온 후에 serializing 에러가 발생합니다. (Next.js는 13버젼입니다.) 에러 내용은 다음과 같습니다. Error: Error serializing `.dehydratedState.queries[0].state.data.headers` returned from `getServerSideProps` in "/top". Reason: `object` ("[object AxiosHeaders]") cannot be serialized as JSON. Please only return JSON serializable data types. 해당 에러 내용으로 구글링을 해보니, 대부분 getServerSideProps 함수 반환 코드에서 return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; 와 같이 dehydrate(queryClient)값을 JSON화 -> Object화를 하라고 하는데요, 이와 같이 사용해도 또 다시 아래와 같은 에러가 납니다. TypeError: Converting circular structure to JSON --> starting at object with constructor 'ClientRequest' | property 'socket' -> object with constructor 'Socket' --- property '_httpMessage' closes the circle Backend API는 Express.js를 사용하고 있으며, res.status(200).json({ data: ~ })와 같은 방식으로 응답을 주고 있습니다. 어떻게 해결할 수 있을까요? 코드 첨부가 안되네요, 아래는 page 컴포넌트가 위치한 파일의 전체 코드입니다. import type { ReactElement } from 'react'; import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query'; import { format } from 'date-fns'; import TopMusicContainer from '~containers/TopMusicContainer'; import Layout from '~layouts/Layout'; import type { NextPageWithLayout } from '~pages/_app'; import TopMusicService from '~services/topMusicService'; import * as MusicType from '~types/musicType'; export async function getServerSideProps() { const queryClient = new QueryClient(); await queryClient.prefetchQuery(['fetchTopMusic'], () => { const params: MusicType.ListRequestType = { filter: 'title', keyword: '', page: 1, limit: 25, time: format(new Date(), 'yyyyMMddHH'), }; return TopMusicService.list(params); }); return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; } const Top: NextPageWithLayout = (): JSX.Element => { const { data, isLoading } = useQuery({ queryKey: ['fetchTopMusic'], queryFn: () => { const params: MusicType.ListRequestType = { filter: 'title', keyword: '', page: 1, limit: 25, time: format(new Date(), 'yyyyMMddHH'), }; return TopMusicService.list(params); }, }); return ( <section> <TopMusicContainer /> </section> ); }; Top.getLayout = function getLayout(page: ReactElement) { return <Layout>{page}</Layout>; }; export default Top;
안녕하세요. Axios response를 그대로 사용하시면 함수 등 JSON으로 변환이 불가능한 것들이 포함되어 있어서 axios response에서 data만 추출해야 하는 것으로 알고 있는데요. 혹시 TopMusicService.list(params); 의 리턴 타입이 어떻게 되나요? 참고한 SO 포스트: - https://stackoverflow.com/questions/67204170/getserversideprops-functions-response-cannot-be-serialized-as-json-in-next-js
안녕하세요. 현재 리액트 네이티브로 프로젝트를 진행하고 있습니다. 막바지 단계에 거의 도달해서 이제 배포 준비를 하려고 하는데요, 마지막으로 테스트를 하기 위해 stagingRelease 로 apk 를 만들어서 테스트를 하려고 하니, .env.staging 을 읽지를 못하고 있네요. 이상한건 stagingDebug 로 할 때에는 이런 문제가 없었습니다. 지금 환경변수는 루트 디렉토리에 env 폴더가 있고, 해당 폴더 내에는 3개의 환경변수 파일이 존재합니다. .env.development .env.staging .env.production 이렇게 3개 입니다. project.ext.envConfigFiles = [ productiondebug: "env/.env.production", productionrelease: "env/.env.production", developmentrelease: "env/.env.development", developmentdebug: "env/.env.development", stagingrelease: "env/.env.staging", stagingdebug: "env/.env.staging" ] 위의 코드는 제가 설정한 환경변수 매핑하는 부분입니다. 실제로 stagingrelease 로 빌드 시에 flavor 값이 stagingrelease 로 뜨는 걸 확인했습니다. node_modules 에서 react-native-config 폴더를 찾아서 dotenv.gradle 에서 찍으니까 확인이 가능하더라고요. 제가 궁금한 점은 왜 루트에서 .env 파일만을 읽어서 BuildConfig.java 에서 사용하려고 하는걸까요? .env.staging 을 참조하지 않는 이유가 궁금합니다. 아래는 dotenv.gradle 파일입니다. import java.util.regex.Matcher import java.util.regex.Pattern def getCurrentFlavor() { Gradle gradle = getGradle() def pattern = Pattern.compile("(?:.*:)*[a-z]+([A-Z][A-Za-z0-9]+)") def flavor = "" gradle.getStartParameter().getTaskNames().any { name -> Matcher matcher = pattern.matcher(name) if (matcher.find()) { flavor = matcher.group(1).toLowerCase() return true } } println "Current flavor: $flavor" return flavor } def loadDotEnv(flavor = getCurrentFlavor()) { def envFile = project.hasProperty("defaultEnvFile") ? project.defaultEnvFile : ".env" if (System.env['ENVFILE']) { envFile = System.env['ENVFILE'] } else if (System.getProperty('ENVFILE')) { envFile = System.getProperty('ENVFILE') } else if (project.hasProperty("envConfigFiles")) { project.ext.envConfigFiles.any { pair -> if (flavor.startsWith(pair.key.toLowerCase())) { envFile = pair.value return true } } } println "Reading env from: $envFile" def env = [:] File f = new File("$project.rootDir/../$envFile"); if (!f.exists()) { f = new File("$envFile"); } if (f.exists()) { println "Found env file: $f" f.eachLine { line -> def matcher = (line =~ /^\s*(?:export\s+|)([\w\d\.\-_]+)\s*=\s*['"]?(.*?)?['"]?\s*$/) if (matcher.getCount() == 1 && matcher[0].size() == 3) { env.put(matcher[0][1], matcher[0][2].replace('"', '\\"')) // 각 환경 변수 키-값 쌍 출력 println "Loaded env variable: ${matcher[0][1]} = ${matcher[0][2]}" } } } else { println("**************************") println("*** Missing .env file ****") println("**************************") } project.ext.set("env", env) } loadDotEnv() android { defaultConfig { project.env.each { k, v -> def escaped = v.replaceAll("%","\\\\u0025") buildConfigField "String", k, "\"$v\"" resValue "string", k, "\"$escaped\"" println "Set buildConfigField and resValue: $k = $v" } } } 안드로이드 스튜디오 터미널에서 ./gradlew assembleStagingRelease 명령어를 치게 될 경우, Current flavor: stagingrelease Reading env from: .env.staging ************************** *** Missing .env file **** ************************** 이런 로그가 뜨고 있습니다.
안녕하세요. 현재 Websocket과 stompjs v6.0.0을 활용해 채팅을 구현했습니다. roomId로 여러 채팅방을 만들 수 있게 구현했고, 현재 새로고침을 하지 않는 이상 잘 돌아갑니다. 그러나, 새로고침 할 시에는 바로 연결이 끊겨 이전의 채팅 내역도 보이지 않고, 연결, 구독 내역이 사라집니다 ... 어떻게 reconnect 해야할까요? 단순히 채팅 페이지에서 useEffect로 connect를 다시 하니 이미 연결 구독이 된 상태라고 뜨더라구요 .... ㅠㅠ (고민글을 올렸을 때 채팅방이 생성되고, 연결 구독이 됩니다. 채팅 시작 버튼을 눌렀을 경우에는 본인이 연결 구독이 되어 1대 1로 상대방과 채팅이 시작되는 구조입니다. ) import { CompatClient, Stomp } from "@stomp/stompjs"; import { createContext, useContext, useMemo, useRef } from "react"; import { useSetRecoilState } from "recoil"; import { messageState } from "../../states/chatting"; import audio from "../../assets/audios/chatting.mp3"; const ChatContext = createContext( {} as { connect: (roomId: number) => void; disconnect: () => void; send: (roomId: number, message: string) => void; }, ); export const useChatContext = () => useContext(ChatContext); export function ChatProvider({ children }: any) { const setMessages = useSetRecoilState(messageState); const token = localStorage.getItem("accessToken"); // 채팅 연결 구독 const client = useRef<CompatClient>(); const connect = (roomId: number) => { client.current = Stomp.over(() => { const sock = new WebSocket("wss://m-ssaem.com:8080/stomp/chat"); return sock; }); client.current.connect( { token: token, }, () => { client.current && client.current.subscribe( `/sub/chat/room/${roomId}`, (message) => onMessageReceived(message, roomId), { token: token!, }, ); }, ); return client; }; const onMessageReceived = (message: any, roomId: number) => { const audioElement = new Audio(audio); audioElement.play(); setMessages((prevMessages) => { const updatedMessages = { ...prevMessages, [roomId]: [...(prevMessages[roomId] || []), JSON.parse(message.body)], }; return updatedMessages; }); }; // 채팅 나가기 const disconnect = () => { if (client.current) { client.current.disconnect(() => { window.location.reload(); }); } }; // 채팅 보내기 const send = (roomId: number, message: string) => { if (client.current) { client.current.send( `/pub/chat/message`, { token: token, }, JSON.stringify({ roomId: roomId, message: message, type: "TALK", }), ); } }; const handlers = useMemo(() => ({ connect, disconnect, send }), []); return ( <ChatContext.Provider value={handlers}>{children}</ChatContext.Provider> ); } ----------이 부분은 connect 하는 부분입니다 --------- const { connect } = useChatContext(); const chatRoomId = worryBoard && worryBoard.chatRoomId; const handleStartChatting = () => { navigate(`/chatting`); connect(chatRoomId!!); }; ------------ 채팅 페이지는 따로 있습니다 --------------