묻고 답해요
158만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결AWS Certified Solutions Architect - Associate 자격증 준비하기
아마존 클라우드프론트 설명에서 엣지 로케이션을 콘텐츠를 캐시 하는데 사용 이라고 적혀 있는데 여기서 나오는 캐시의 스펠링과 뜻을 알 수 있을가요?
아마존 클라우드프론트 설명에서 엣지 로케이션을 콘텐츠를 캐시 하는데 사용 이라고 적혀 있는데 여기서 나오는 캐시의 스펠링과 뜻을 알 수 있을가요?
-
미해결10주완성 C++ 코딩테스트 | 알고리즘 코딩테스트
트리순회 강의 코드 관련 질문드려요!
2주차개념 #12 트리순회(Tree traversal) 후위순회, 전위순회, 중위순회 안녕하세요! 위 강의에서 사용된 순회 코드에서 보통 다른 코드들 보면 자식노드가 1개인 경우 2개인 경우 이렇게 나누지 않고 아래처럼 짜여져 있는 코드가 많던대 저렇게 자식노드 수에 따라 나누신 이유가 있으실까요? void preorder(node<T>* run_point) { if (!run_point) return; cout << run_point->data << endl; preorder(run_point->left); preorder(run_point->right); }
-
해결됨스프링 DB 1편 - 데이터 접근 핵심 원리
<예외 포함과 스택 트레이스> 강의 중 이해안가는 부분이 있어요.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.log.info("ex", ex);이 예의 경우에는 파라미터가 없기 때문에 스택트레이스에 로그를 출력할 수 없다고 하셨는데 위에 코드랑 연결되는건가요? 지금 예외 객체 참조변수명이 e인데 ex라고 해서 존재하지 않는거라 출력이 안되는걸까요? 코드에 있는 log.info("ex", e);의 경우 출력되고log.info("message={}", "message", ex); 도 출력되는데log.info("ex", ex);는 출력이 안된다는 부분이 이해가 잘 안갑니다 ㅠㅠ
-
미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진
11분 50초 실행결과
NullReferenceException: Object reference not set to an instance of an objectManagers.get_Scene () (at Assets/Script/Mangers/Managers.cs:52)->return Instance._ui;LoginScene.Update () (at Assets/Script/Scenes/LoginScene.cs:21)-> 오류 CS:21Managers.Scene.LoadScene(Define.Scene.Game);public class Managers : MonoBehaviour{ static Managers s_lnstance; //static이라는 전역변수를 통해 오브젝트인 @manager의 유일성이 보장된다 static Managers Instance { get { lnit(); return s_lnstance; } } SceneManagerEx _scene = new SceneManagerEx(); public static SceneManagerEx Scene { get { return Instance._scene; }static void lnit() //Instance가 널인 상태를 해결하기 위해 사용됨 { if (s_lnstance == null) { GameObject go= GameObject.Find("@Managers"); if (go == null) { go = new GameObject { name = "@Managers" }; go.GetComponent<Managers>(); } DontDestroyOnLoad(go); s_lnstance = go.GetComponent<Managers>(); } } } public class LoginScene : BaseScene{ protected override void Init() { base.Init(); SceneType = Define.Scene.Login; } private void Update() { if (Input.GetKeyDown(KeyCode.Q)) { public class LoginScene : BaseScene{ protected override void Init() { base.Init(); SceneType = Define.Scene.Login; } private void Update() { if (Input.GetKeyDown(KeyCode.Q)) { Managers.Scene.LoadScene(Define.Scene.Game); } } public override void Clear() { Debug.Log("Login Scene CLEAR!!!!!"); } }}뭐가 문제인지 잘 모르겠습니다 . 구글링 해본 결과 널의 값을 가질 수 없는 오브젝트에 널을 할당했다고 그러는데 잘 모르겠습니다 ㅠㅠ
-
미해결스프링 시큐리티 OAuth2
질문 사항
17:30 ppt를 보면 back chaennel에서 '클라이언트가 최종 사용자를 가지고 있는가?' 에 대한 말씀을 하시면서 '클라이언트가 사용자인 동시에 클라이언트의 역할을 수행하는 경우를 말한다'고 하셨는데 '아니오'일 때 Client Credentials Grant Type의 방식을 사용하게 되는 것이 이해가 잘 가지 않습니다.22:10'token, id_token의 경우 권한 부여 유형에서 지원해야 한다.' 라고 하셨는데 해당 방식의 경우 인가 서버의 구현 여부에 따라 사용할 수 있는지가 달라지는걸 말씀 하신건가요?25:25임시 코드 요청시와 액세스 토큰 요청 시에 같은 uri를 보내야 한다고 하셨는데, 이 값은 리소스 서버에 등록되어 있는 클라이언트의 redirect_uri 값과 항상 동일해야 하나요? 서버에 등록된 값과 다를 수 있는지 궁금합니다.
-
미해결Vue.js + TypeScript 완벽 가이드
두 번째 프로젝트 권한 요청 부탁드립니다.
osh9403@gmail.com
-
미해결자바스크립트 알고리즘 문제풀이 입문(코딩테스트 대비)
코드 리뷰 부탁드립니다
const solution = (m, songs) => { let lt = Math.max(...songs), rt = songs.reduce((prev, cur) => prev + cur); let mid = parseInt((lt + rt) / 2); let nowCount; let minMinutes = Number.MAX_SAFE_INTEGER; const getCount = (minutes, songs) => { let count = 0; let remainMinutes = 0; for (let song of songs) { if (remainMinutes < song) { count++; remainMinutes = minutes - song; } else { remainMinutes -= song; } } return count; }; while (lt <= rt) { nowCount = getCount(mid, songs); console.log(mid, nowCount, minMinutes); if (nowCount > m) { lt = mid + 1; } else { rt = mid - 1; // if (nowCount === m) minMinutes = Math.min(minMinutes, mid); minMinutes = mid; } mid = parseInt((lt + rt) / 2); } return minMinutes; };먼저 getCount 부분을 다르게 작성해봤는데 반례가 있을지 궁금합니다.그리고 제 원래 코드는 while문 내에서 구한 nowCount값이 m과 같을 때만 minMinutes(정답)을 minMinutes와 mid 중 더 작은 값으로 대입해줬는데, nowCount가 m보다 작거나 같은 경우에 무조건 정답으로 대입해도 괜찮은 이유가 무엇인가요?
-
미해결나도코딩의 자바 기본편 - 풀코스 (20시간)
나도코딩님! 퀴즈 7번을 다음과 같이 풀어봤는데, 피드백 부탁드립니다 😄
package chap_07; public class _Quiz_07 { public static void main(String[] args) { HamBurger[] hamBurgers = new HamBurger[3]; hamBurgers[0] = new HamBurger(); hamBurgers[1] = new CheeseBurger(); hamBurgers[2] = new ShrimpBurger(); System.out.println("주문하신 메뉴를 만듭니다."); System.out.println("---------------"); for (HamBurger hamBurger : hamBurgers) { hamBurger.cook(); System.out.println("-----------------"); } System.out.println("메뉴 준비가 완료되었습니다."); } } class HamBurger{ public String name; public String[] ingredients = {"양상추", "패티", "피클"}; // 기본 재료를 배열로 초기화 public HamBurger(){ this("햄버거"); // 다른 클래스를 호출 } public HamBurger(String name){ this.name = name; } public void cook(){ System.out.println(this.name + "를 만듭니다."); System.out.println("빵 사이에 들어가는 재료는?"); System.out.println(" > " + ingredients[0]); for (int i = 1; i < ingredients.length; i++) { System.out.println(" + " + ingredients[i]); } } } class CheeseBurger extends HamBurger{ private String ingre = "치즈"; public CheeseBurger(){ super("치즈버거"); // 부모클래스를 호출하여 "치즈버거"로 name 초기화 } public void cook() { super.cook(); System.out.println(" + " + getIngre() ); } public String getIngre() { return ingre; } } class ShrimpBurger extends HamBurger{ private String ingre = "새우"; // 새우 재료를 은닉 public ShrimpBurger(){ super("새우버거"); } public void cook(){ super.cook(); System.out.println(" + " + getIngre()); } public String getIngre() { return ingre; } }사실 저는, this()와 super()로 다른 클래스를 호출하는 방법을 잘 몰라서, 클래스마다 this.name = "str"; 으로 다 초기화했거든요 😅 "양상추", "패티", "피클" 이 기본재료라서 배열로 초기화해줬고, 여기서 애로사항이 for-each문으로 출력을 하려고 했는데, " < " 와 " + " 부분이 달라서 그냥 for문으로 위와 같이 출력했습니다.제가 궁금한 점은 첫째로, 만약 저렇게 재료들을 배열에 초기화시켰다면, 자식클래스에서 부모클래스의 배열에 접근해서 요소들을 추가할 수 있나요??둘째로, 자식클래스의 재료들을 private으로 데이터은닉을 시킨다음에 getter함수로 출력을 해도 괜찮나요?P.S: 항상 양질의 강의를 제공해주셔서 감사합니다! 퀴즈덕분에 고민하는데 시간이 오래걸렸지만, 제가 모르는 부분도 수정할 수 있어서 좋은 것 같아요!!
-
미해결실전! Querydsl
querydsl 외래키제약조건
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/)[질문 내용]JPA를 사용하는데외래키를 설정안하고 사용하는 경우가 있을까요? 제 질문은 A테이블 과 B테이블이 부모 자식 관계인데외래키 제약조건을 걸지 않고 사용하는 경우입니다. JPA에선 각 엔티티가 연결고리가 없는 경우입니다A엔티티(B엔티티로 가는 필드없음)B엔티티 (A엔티티로 가는 필드없음)이런식으로
-
해결됨스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
모든 WAS가 멀티쓰레드를 지원하는게 맞나요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]WAS가 멀티쓰레드를 지원하기 때문에 개발자는 쓰레드 관리에 신경을 크게 신경을 쓰지 않아도 된다고 하셨는데, 톰캣뿐만 아니라 모든 WAS는 멀티쓰레드를 지원하는 건가요??
-
미해결지금 당장 데브옵스 AWS
(확인) "시작 유형 호환성 선택 : Fargate 선택" 경로 확인 부탁드립니다.
제 작업 화면에서 못 찾겠네요좌측 제일 상단 버튼이 있었네요...
-
미해결설계독학맛비's 실전 AI HW 설계를 위한 바이블, CNN 연산 완전정복 (Verilog HDL + FPGA 를 이용한 가속기 실습)
CNN 연산 모듈 질문입니다.
안녕하세요 맛비님.맛비님께서 3차원 Convolution 연산 모듈을 설계하기 위해서 3개의 계층을 가진 모듈로 설계하셨던 것에 궁금한 게 생겨서 질문드립니다.1차원 연산을 위한 최하위 계층, 그 위의 2차원 연산을 위한 하위 계층, 그 위의 3차원 연산을 위한 Top 계층, 이렇게 세 개의 계층을 나누셨는데,1.한 모듈에서 Register를 많이 추가하여 이 기능을 전부 할 순 없나요? (FSM으로) 즉 하나의 계층을 가진 모듈로 3차원 연산 모듈을 구현할 수는 없는 건가요?2. 이와 같이 할 경우 단점이란 것이 존재할까요?
-
미해결[코드팩토리] [초급] Flutter 3.0 앱 개발 - 10개의 프로젝트로 오늘 초보 탈출!
runApp 관련 호출자 관련
안녕하세요, Hello world 를 찍는 강의에서 runApp 메소드는 binding.dart 파일에서,Scaffold 는 scaffold.dart 에서 참조해서 가지고 오더라구요. 다만 이 파일들은 import 한적이 없는데 코드에서는 어떻게 참조하여 사용할 수 있는 건가요? 감사합니다.
-
미해결스프링 DB 2편 - 데이터 접근 활용 기술
커넥션 풀 관련 질문 드립니다.
안녕하세요 항상 강의 잘 듣고 있습니다. 커넥션풀을 기본으로 사용해야된다고 이해를 했는데hikaricp 의 경우 기본이 10개입니다.그런데 tomcat thread 의 경우 기본이 200개던데요청이 많이 오는 경우 둘다 기본으로 사용하면 thread pool에 비해 thread가 너무 많이 생성이 돼서 서비스 운영시 문제가 될거 같은데실 서비스에서 저렇게 기본 값으로 많이 사용하는지 궁금합니다!
-
미해결[왕초보편] 앱 8개를 만들면서 배우는 안드로이드 코틀린(Android Kotlin)
SentenceActivity에서 Binding 사용 안됨(?)
안녕하세요 :) 수업 잘 듣고 있습니다!질문 내용은...1. SentenceActivity에서는 binding을 사용하지 않으신 이유가 궁금합니다.2. 혼자서 해봤는데SentenceActivity에서는 binding에서 listview의 id 값이 찾아지질 않더라구요..이유가 궁금합니다.미리 감사드립니다. :)좋은 하루되세요.
-
미해결앨런 iOS 앱 개발 (15개의 앱을 만들면서 근본원리부터 배우는 UIKit) - MVVM까지
weightTextField에서 엔터를 눌러도 키보드가 안내려가는 문제(활용앱 37강 07:00)
extension ViewController: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if Int(string) != nil || string == "" { return true }// 글자 입력을 허용 return false // 글자 입력을 허용하지 않음 } func textFieldShouldReturn(_ textField: UITextField) -> Bool { // 두개의 텍스트필드를 모두 종료 (키보드 내려가기) if heightTextField.text != "", weightTextField.text != "" { weightTextField.resignFirstResponder() return true // 두번째 텍스트필드로 넘어가도록 } else if heightTextField.text != "" { weightTextField.becomeFirstResponder() return true } return false } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { heightTextField.resignFirstResponder() weightTextField.resignFirstResponder() } } 위와 같이 입력을 했는데 두 텍스트필드를 입력하고 엔터를 누르면 밑 사진처럼 숫자 입력 키보드에서 한글 입력 키보드로 바뀌기만 할 뿐 키보드가 내려가지 않습니다.. 무엇이 문제일까요??저 상태에서 화면 다른 부분을 터치했을 때 내려가긴 합니다.
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
post 에서 작성 후 mainPosts.map 에서 key={post.id} 가 undefined 로 나옵니다.
에러메세지:ADD_POST_SUCCESS 까지 잘 되었고 content 에 id 도 잘 들어가 있는걸로 보입니다. pages/index.jsimport React, { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import AppLayout from "../components/AppLayout"; import PostCard from "../components/PostCard"; import PostForm from "../components/postForm"; import { LOAD_POSTS_REQUEST } from "../reducers/post"; import { LOAD_USER_REQUEST } from "../reducers/user"; const Home = () => { const me = useSelector((state) => state.user.me); const { mainPosts, hasMorePosts, isLoadingPosts } = useSelector( (state) => state.post ); const dispatch = useDispatch(); useEffect(() => { dispatch({ type: LOAD_USER_REQUEST, }); dispatch({ type: LOAD_POSTS_REQUEST, }); }, []); useEffect(() => { function onScroll() { if ( window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300 ) { if (hasMorePosts && !isLoadingPosts) { dispatch({ type: LOAD_POSTS_REQUEST, }); } } } window.addEventListener("scroll", onScroll); return () => { window.removeEventListener("scroll", onScroll); }; }, []); return ( <AppLayout> {me && <PostForm />} {mainPosts.map((post) => ( <PostCard key={post.id} post={post} /> ))} </AppLayout> ); }; export default Home; reducers/post.jsimport produce from "immer"; export const initialState = { mainPosts: [], imagePaths: [], hasMorePosts: true, isAddingPost: false, isAddedPost: false, isAddPostErr: null, isRemovingPost: false, isRemovedPost: false, isRemovePostErr: null, isAddingComment: false, isAddedComment: false, isAddCommentErr: null, isRemovingComment: false, isRemovedComment: false, isRemoveCommentErr: null, isLoadingPosts: false, isLoadedPosts: false, isLoadPostsErr: null, }; export const LOAD_POSTS_REQUEST = "LOAD_POSTS_REQUEST"; export const LOAD_POSTS_SUCCESS = "LOAD_POSTS_SUCCESS"; export const LOAD_POSTS_FAILURE = "LOAD_POSTS_FAILURE"; export const ADD_POST_REQUEST = "ADD_POST_REQUEST"; export const ADD_POST_SUCCESS = "ADD_POST_SUCCESS"; export const ADD_POST_FAILURE = "ADD_POST_FAILURE"; export const REMOVE_POST_REQUEST = "REMOVE_POST_REQUEST"; export const REMOVE_POST_SUCCESS = "REMOVE_POST_SUCCESS"; export const REMOVE_POST_FAILURE = "REMOVE_POST_FAILURE"; export const ADD_COMMENT_REQUEST = "ADD_COMMENT_REQUEST"; export const ADD_COMMENT_SUCCESS = "ADD_COMMENT_SUCCESS"; export const ADD_COMMENT_FAILURE = "ADD_COMMENT_FAILURE"; export const addPost = (data) => ({ type: ADD_POST_REQUEST, data, }); const reducer = (state = initialState, action) => { return produce(state, (draft) => { switch (action.type) { case ADD_POST_REQUEST: draft.isAddingPost = true; draft.isAddedPost = false; draft.isAddPostErr = null; break; case ADD_POST_SUCCESS: draft.isAddingPost = false; draft.mainPosts.unshift(action.data); draft.isAddedPost = true; break; case ADD_POST_FAILURE: draft.isAddingPost = false; draft.isAddedPost = false; draft.isAddPostErr = action.error; break; case REMOVE_POST_REQUEST: draft.isRemovingPost = true; draft.isRemovedPost = false; draft.isRemovePostErr = null; break; case REMOVE_POST_SUCCESS: draft.isRemovingPost = false; draft.mainPosts = state.mainPosts.filter((v) => v.id !== action.data); draft.isRemovedPost = true; draft.isRemovePostErr = null; break; case REMOVE_POST_FAILURE: draft.isRemovingPost = false; draft.isRemovedPost = false; draft.isRemovePostErr = action.error; break; case ADD_COMMENT_REQUEST: draft.isAddingComment = true; draft.isAddedComment = false; draft.isAddCommentErr = null; break; case ADD_COMMENT_SUCCESS: draft.isAddingComment = false; const post = draft.mainPosts.find((v) => v.id === action.data.PostId); post.Comments.unshift(action.data); draft.isAddedComment = true; break; case ADD_COMMENT_FAILURE: draft.isAddingComment = false; draft.isAddedComment = false; draft.isAddCommentErr = action.error; break; case LOAD_POSTS_REQUEST: draft.isLoadingPosts = true; draft.isLoadedPosts = false; draft.isLoadPostsErr = null; break; case LOAD_POSTS_SUCCESS: draft.isLoadingPosts = false; draft.mainPosts = draft.mainPosts.concat(action.data); draft.isLoadedPosts = true; break; case LOAD_POSTS_FAILURE: draft.isLoadingPosts = false; draft.isLoadedPosts = false; draft.isLoadPostsErr = action.error; break; default: break; } }); }; export default reducer;sagas/post.jsimport { delay, all, fork, put, takeLatest, call } from "redux-saga/effects"; import axios from "axios"; import { ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE, ADD_COMMENT_FAILURE, ADD_COMMENT_SUCCESS, ADD_COMMENT_REQUEST, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE, LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, } from "../reducers/post"; import { ADD_POST_TO_ME, REMOVE_POST_FROM_ME } from "../reducers/user"; function addPostAPI(data) { return axios.post( "/post", { content: data }, { withCredentials: true, } ); } function* addPost(action) { try { const result = yield call(addPostAPI, action.data); yield put({ type: ADD_POST_SUCCESS, content: result.data }); yield put({ type: ADD_POST_TO_ME, data: result.data.id, }); } catch (err) { console.log(err); yield put({ type: ADD_POST_FAILURE, error: err.response.data, }); } } function removePostAPI(data) { return axios.delete("/post", data); } function* removePost(action) { try { yield delay(1000); yield put({ type: REMOVE_POST_SUCCESS, data: action.data, }); yield put({ type: REMOVE_POST_FROM_ME, data: action.data, }); } catch (err) { yield put({ type: REMOVE_POST_FAILURE, error: err.response.data, }); } } function addCommentAPI(data) { return axios.post(`/post/${data.postId}/comment`, data); //POST /post/1/comment } function* addComment(action) { try { const result = yield call(addCommentAPI, action.data); yield put({ type: ADD_COMMENT_SUCCESS, data: result.data }); } catch (err) { console.log(err); yield put({ type: ADD_COMMENT_FAILURE, error: err, }); } } function loadPostsAPI() { return axios.get(`/posts`); } function* loadPosts(action) { try { const result = yield call(loadPostsAPI, action.data); yield put({ type: LOAD_POSTS_SUCCESS, data: result.data }); } catch (err) { console.log(err); yield put({ type: LOAD_POSTS_FAILURE, error: err.response.data, }); } } function* watchAddPost() { yield takeLatest(ADD_POST_REQUEST, addPost); } function* watchRemovePost() { yield takeLatest(REMOVE_POST_REQUEST, removePost); } function* watchAddComment() { yield takeLatest(ADD_COMMENT_REQUEST, addComment); } function* watchLoadPosts() { yield takeLatest(LOAD_POSTS_REQUEST, loadPosts); } export default function* postSaga() { yield all([ fork(watchAddPost), fork(watchRemovePost), fork(watchAddComment), fork(watchLoadPosts), ]); }routes/posts.jsconst express = require("express"); const { Post, Image, User, Comment } = require("../models"); const router = express.Router(); router.get("/", async (req, res, next) => { // GET /posts try { const posts = await Post.findAll({ limit: 10, order: [ ["createdAt", "DESC"], [Comment, "createdAt", "DESC"], ], include: [ { model: User, attributes: ["id", "nickname"], }, { model: Image, }, { model: Comment, include: [{ model: User, attributes: ["id", "nickname"] }], }, ], }); res.status(200).json(posts); } catch (err) { console.error(err); next(err); } }); module.exports = router; sql 테이블도 잘 들어가있고, 새로 고침 하면 포스팅은 잘 되어 있습니다.
-
미해결[핵집] 2025 빅데이터 분석기사(필기)_과목 3~4
3~4과목 파일
1~2과목 교안 통합 파일은 받았습니다~3~4과목 파일도 요청드립니다!goodtc377@gmail.com입니다. 감사합니다.
-
미해결스프링 핵심 원리 - 고급편
자동 프록시 생성용 빈 후처리기의 반환된 프록시 빈 타입
토비의 스프링을 참고로 스프링 AOP를 공부하는 중 막히는 부분이 있어서 질문 남깁니다.현재 DefaultAdvisorAutoProxyCreator라는 자동 프록시 생성기를 스프링 빈으로 등록하고, 포인트컷과 트랜잭션 부가기능을 적용할 어드바이스를 조합한 Advisor를 통해 트랜잭션이 적용되는지 테스트를 하고 있습니다. 빈 후처리기를 이용한 프록시 자동생성 방식에서 빈 후처리기는 프록시 적용 대상에 대해 프록시를 생성하고, 생성된 프록시 오브젝트를 컨테이너에게 돌려준다고 알고 있습니다. 현재 프록시 자동생성기를 이용해 예외상황에서 트랜잭션 롤백이 되는지 테스트를 돌려보았고, 여기까지 잘 통과했습니다. 그리고 나서 컨테이너가 돌려준 서비스 빈의 타입을 확인하는 과정에서 질문이 있어 남깁니다.토비의 스프링 책에서 확인해보니 자동 프록시 생성기에 의해 프록시 오브젝트가 생성되어 돌려주기 때문에 해당 빈의 타입이 java.lang.reflect.Proxy.class이어야 한다고 했습니다. 그래서 다음과 같이 테스트를 진행했더니 오류가 발생했습니다.혹시 프록시 생성기를 통해 돌려줘야 하는 빈의 타입이 Proxy.class 타입이 맞는 것인지, 제 코드에 문제가 있어서 오류가 발생한 것인지 알고 싶습니다. 트랜잭션 롤백 테스트를 통과한 것을 보면 프록시가 잘 생성되어 어드바이저와 연결된 것 같은데, 어째서 타입이 다르다고 나오는지 알고 싶습니다.#추가스프링의 AOP 프록시를 찾다보니, 프록시를 만드는 방법이 JDK와 CGLIB 두 방식이 있는 것 같습니다. ProxyFactoryBean의 Proxy.newInstance 방식으로 만든 프록시가 아닌 경우 프록시가 CGLIB 방식으로 만들어지는 것인지 궁금합니다. 빈 후처리기가 내장된 프록시 생성기를 통해 프록시를 생성한다고 했는데, DefaultAdvisorAutoProxyCreator 후처리기는 CGLIB 방식을 사용하는 것일까요? 질문이 다소 두서없지만,, 답변 기다리겠습니다. 항상 감사합니다 :)##추추가JDK 다이내믹 프록시를 이용한 트랜잭션 테스트에 대해서 프록시 빈 타입을 확인해보니 java.lang.reflect.Proxy.class와 동일한 것을 확인했습니다!!! DefaultAdvisorAutoProxyCreator 후처리기가 반환하는 프록시는 CGLIB proxy가 맞는 걸 보아하니 스프링은 JDK 다이내믹 프록시가 아닌 프록시 생성은 CGLIB가 디폴트일까요?@Test public void advisorAutoProxyCreator() { assertThat(testUserService).isInstanceOf(java.lang.reflect.Proxy.class); }제 질문만으로 이해가 부족할 것 같아서 테스트 관련한 코드들을 같이 올립니다.@Configuration public class BeanPostProcessorConfig { @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { return new DefaultAdvisorAutoProxyCreator(); } }@Configuration public class TxAdvisorConfig { private final TransactionAdvice advice; private final UserService userService; @Autowired public TxAdvisorConfig(TransactionAdvice advice, @Qualifier("userServiceImpl") UserService userService) { this.advice = advice; this.userService = userService; } @Bean public NameMatchClassMethodPointcut pointcut() { NameMatchClassMethodPointcut pointcut = new NameMatchClassMethodPointcut(); pointcut.setMappedClassName("*ServiceImpl"); pointcut.setMappedName("upgrade*"); return pointcut; } @Bean public DefaultPointcutAdvisor advisor() { return new DefaultPointcutAdvisor(pointcut(), this.advice); } }@Component public class TransactionAdvice implements MethodInterceptor { private PlatformTransactionManager transactionManager; @Autowired public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } /** * 타깃을 호출하는 기능을 가진 콜백 오브젝트를 프록시로부터 받는다. * 덕분에 어드바이스는 특정 타깃에 의존하지 않고 재사용 가능하다. */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition()); try { /** * 콜백을 호출해서 타깃의 메서드를 실행한다. * 타깃 메서드 호출 전후로 필요한 부가기능을 넣을 수 있다. * 경우에 따라서 타깃이 아예 호출되지 않게 하거나 재시도를 위한 반복적인 호출도 가능하다. */ Object ret = invocation.proceed(); this.transactionManager.commit(status); return ret; } catch (RuntimeException e) { this.transactionManager.rollback(status); throw e; } } }@Service public class UserServiceImpl implements UserService { public static final int MIN_LOGCOUNT_FOR_SILVER = 50; public static final int MIN_RECOMMEND_FOR_GOLD = 30; private final UserDao userDao; private UserLevelUpgradePolicy policy; @Autowired public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } @Autowired public void setPolicy(UserLevelUpgradePolicy policy) { this.policy = policy; } public void add(User user) { if (user.getLevel() == null) { user.setLevel(Level.BASIC); } userDao.add(user); } public void upgradeLevels() { List<User> users = userDao.getAll(); for (User user : users) { if (policy.canUpgradeLevel(user)) { policy.upgradeLevel(user); } } } }@Autowired @Qualifier("testUserService") private UserService testUserService; @Autowired private UserDao userDao; @Component @Primary @Qualifier("testPolicy") static class TestUserLevelPolicy extends UserLevelUpgradePolicyImpl { private String id = "madDitto"; @Autowired private TestUserLevelPolicy(UserDao userDao, EmailPolicy emailPolicy) { super(userDao, emailPolicy); } public void upgradeLevel(User user) { if (user.getId().equals(this.id)) throw new TestUserPolicyException(); super.upgradeLevel(user); } } @Component @Qualifier("testUserService") static class TestUserServiceImpl extends UserServiceImpl { private UserLevelUpgradePolicy testPolicy; @Autowired public TestUserServiceImpl(UserDao userDao, @Qualifier("testPolicy") UserLevelUpgradePolicy testPolicy) { super(userDao); this.testPolicy = testPolicy; super.setPolicy(this.testPolicy); } } @Test @DisplayName("자동 프록시 생성 테스트") public void upgradeAllOrNothingAutoProxy() { userDao.deleteAll(); for (User user : users) { userDao.add(user); } try { this.testUserService.upgradeLevels(); fail("TestUserPolicyException expected"); } catch (TestUserPolicyException e) { } checkLevelUpgraded(users.get(1), false); }
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
.next파일
로컬에서 Next를 빌드해서 aws 인스턴스에서 Npm start를 하려고 하는데 .next파일을 git에 푸쉬했을때 문제가 생기는 부분이 있을까요? 보안이라던지