묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결스프링 핵심 원리 - 기본편
전체 테스트 오류
그림과 같이 저는 여기저기서 오류가 나는데, 해결을 못하고 있습니다..rateDiscountPolicy로 바꿔서 하니 안되는데, 왜이런지 모르겠어요.
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
데이터베이스 생성한게 약간 복잡해졌습니다ㅠㅠ
==================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]원래는 Item테이블에 book, album, movie가 들어가야 하는데@Inheritance(strategy = InheritanceType.JOINED)를 입력해서 album, book등 테이블이 따로 생성이 되었습니다..그래서 다시 @Inheritance(strategy = InheritanceType.SINGLE_TABLE)로 바꾸고 새로고침해도 변한게 없어서spring.jpa.hibernate.ddl-auto=updatecreate에서 update로 바꾸고 재실행 했더니따로 테이블이 생성된게 남아있고 item에도 들어가 버렸습니다.. 이럴땐 테이블을 따로 삭제하는 방법밖에 없을까요??
-
해결됨스프링 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);는 출력이 안된다는 부분이 이해가 잘 안갑니다 ㅠㅠ
-
미해결스프링 시큐리티 OAuth2
질문 사항
17:30 ppt를 보면 back chaennel에서 '클라이언트가 최종 사용자를 가지고 있는가?' 에 대한 말씀을 하시면서 '클라이언트가 사용자인 동시에 클라이언트의 역할을 수행하는 경우를 말한다'고 하셨는데 '아니오'일 때 Client Credentials Grant Type의 방식을 사용하게 되는 것이 이해가 잘 가지 않습니다.22:10'token, id_token의 경우 권한 부여 유형에서 지원해야 한다.' 라고 하셨는데 해당 방식의 경우 인가 서버의 구현 여부에 따라 사용할 수 있는지가 달라지는걸 말씀 하신건가요?25:25임시 코드 요청시와 액세스 토큰 요청 시에 같은 uri를 보내야 한다고 하셨는데, 이 값은 리소스 서버에 등록되어 있는 클라이언트의 redirect_uri 값과 항상 동일해야 하나요? 서버에 등록된 값과 다를 수 있는지 궁금합니다.
-
해결됨스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
모든 WAS가 멀티쓰레드를 지원하는게 맞나요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]WAS가 멀티쓰레드를 지원하기 때문에 개발자는 쓰레드 관리에 신경을 크게 신경을 쓰지 않아도 된다고 하셨는데, 톰캣뿐만 아니라 모든 WAS는 멀티쓰레드를 지원하는 건가요??
-
미해결스프링 DB 2편 - 데이터 접근 활용 기술
커넥션 풀 관련 질문 드립니다.
안녕하세요 항상 강의 잘 듣고 있습니다. 커넥션풀을 기본으로 사용해야된다고 이해를 했는데hikaricp 의 경우 기본이 10개입니다.그런데 tomcat thread 의 경우 기본이 200개던데요청이 많이 오는 경우 둘다 기본으로 사용하면 thread pool에 비해 thread가 너무 많이 생성이 돼서 서비스 운영시 문제가 될거 같은데실 서비스에서 저렇게 기본 값으로 많이 사용하는지 궁금합니다!
-
미해결스프링 핵심 원리 - 고급편
자동 프록시 생성용 빈 후처리기의 반환된 프록시 빈 타입
토비의 스프링을 참고로 스프링 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); }
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
서블릿 내장객체 관련 질문 드립니다
저는 request, response가 서블릿 내장 객체라고 알고 있었는데요, 강의 내용에 따르면 request와 response는 요청마다 새로 생성되고 서블릿 객체는 요청마다 생성되지 않고 싱글톤으로 재사용한다고 알게 되었습니다.그럼 서블릿 내장 객체와 서블릿 객체가 다른 것인가요? 지금까지는 서블릿 내장 객체라는 것이 서블릿 객체 안에 포함되어 있다고 생각했는데 이건 아닌 것 같고 ..서블릿 컨테이너가 서블릿 객체를 관리하는 것이면 서블릿 내장 객체는 누가 관리해주나요? 서블릿 내장 객체와 서블릿 객체의 차이점을 알고 싶습니다.
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
V1과 V2의 차이
V1은 컨트롤러에서 바로 렌더링을 진행하는 거고 V2는 MyView 객체를 생성해서 MyView에서 렌더링을 진행하는데요 이러면 얻는 이점이 뭐가 있나요? 코드만 더 작성해야되는데 손해 아닌가요 ?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
IllegalStateException
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니요)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]강의들으면서 코드를 따라쳐도 안되서 프로젝트 파일 삭제후 강의 자료에 있는 코드들을 복사해서 해도 계속 같은 오류가 나옵니다. 해결방법 알려주시면 감사하겠습니다!
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
Map에 들어가는 ControllerV1 객체
는 싱글톤이 아닌 것인가요 ? 스프링 빈으로 저장도 안해줬고, static을 선언해서 클래스 변수도 아닌데 이럴 경우에는 호출 할 때마다 ControllerV1 객체가 생성되고 process를 진행되는 형식인가요 ?
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
Locale 관련 질문입니다.
@Test void formattingConversionService() { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); conversionService.addConverter(new StringToIpPortConverter()); conversionService.addConverter(new IpPortToStringConverter()); conversionService.addFormatter(new MyNumberFormatter()); IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class); assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1", 8080)); assertThat(conversionService.convert(1000, String.class)).isEqualTo("1,000"); assertThat(conversionService.convert("1,000", Long.class)).isEqualTo(1000L); }여기서 따로 Locale을 설정하지 않았는 데도 자동으로 Locale.KOREA가 들어가서 1000을 넣었을 시 "1,000"으로 나오는 것 같은 데 이 Locale 값은 스프링이 자동으로 넣어주는 건가요??
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
th:value=""; 로 이전 데이터 가져오지 못하는 문제
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]@nullable로 잡혀있는 rejectedValue 을 addItemV1에서 new FieldError 매개변수에 넣지 않으니 가격이나 수량에 넣어줬던 값이 error 발생 시 그대로 저장이 안되더라구요. 그래서 구글링 후에bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, null, null, "상품가격 범위 초과(1000~1000000)"));이런 식으로 넣으니까 강의처럼 값이 그대로 넘겨지긴 하네요. 영상에서는 해당 부분말고 상품명까지만 확인한거같은데 참고 부탁드립니다~
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
RedirectAttribute Url인코딩여부
return에 redirect:/ + 값 위와같이 URL매핑주소에 파라미터를 그대로 넘겨서 반환하게 된다면 URL 인코딩에 대한 위험성을 함께 지적하시면서 RedirectAttribute수업을 진행하였는데요.수업 중 status를 통한 유효성 검증 처리만 하고 인코딩 여부를 어떤식으로 확인할 수 있는지에 대한 내용이 생략된거 같습니다.혹시 따로 글로라도 확인시켜주실수는 있을까요?
-
미해결스프링 핵심 원리 - 고급편
데코레이터 구성 시 AOP를 사용해도 되는가
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]안녕하세요. 강의 잘 듣고 있습니다.질문은 "단순 데코레이터를 만들고 싶을 때 SpringAOP를 사용해도 되는가?"입니다.질문 매뉴얼의 답이 없는 질문에 포함되는 것 같아 부가설명을 적어보겠습니다. 개인적으로 이전부터 관심사와 의존성을 분리하고 싶어 데코레이터 패턴을 한번씩 쓸 때가 있었습니다.예를 들면 다음과 같습니다.게시글이 작성되면 특정 유저들에게 알림 메세지를 전송해야 한다.게시글이 수정되면 읽기 전용 모델의 캐시를 갱신해야 한다.그럼 [컨트롤러 - 알림데코레이터 - 캐시데코레이터 - 서비스 - 리포지토리 ]가 되는 거죠.이 때 데코레이터-서비스 체인을 구성해야 하는데 2가지 방법이 있었습니다.@Configuration에서 매뉴얼하게 체인 구성한 뒤 빈 생성가장 앞단의 데코레이터에 Primary를 달고 이후 순서에 따라 생성자 파라미터 주입시 @Qualifier로 구현체 주입1번 같은 경우에는 각 데코레이터마다 의존성이 많아질 수록 작성해야 하는 코드가 많아져서 제외를 했습니다.그래서 2번 방법을 사용하고 있고 다음과 같은 문제를 대면했습니다.특정 구현체가 뒷 순서 구현체를 알아야 한다. (의존성 발생) 그것도 컴파일 에러가 나지 않는 문자열(빈 이름)의 형태로.public class PostServiceMessageDecorator implements PostService { private final PostService postService; public PostServiceMessageDecorator( @Qualifier("postServiceCacheDecorator") PostService postService ) { this.postService = postService; } }체인 순서를 구성하는 것이 다소 번거롭고, 순서가 변경되거나 추가, 제거되면 코드를 바꿔야 한다. (다시 한 번 컴파일 에러가 나지 않는 문자열의 형태로)서비스 내에서 데코레이터가 붙지 않는 메소드도 구현을 해줘야 한다.그런데 SpringAOP를 사용하면 3가지 문제를 모두 해결할 수 있는 것이 아닌가 하는 생각이 듭니다.포인트컷도 잡는 것만 잡으면 되니까 데코레이터가 굳이 안붙어도 되는 메소드를 구현할 필요도 없구요.Order로 순서도 간편하게 변경이 가능하니까요.앞서 강의에서 패턴은 의도가 중요하지 실제 구현체는 다양한 방법으로 구현될 수 있다고 하신 말씀이 머릿속에 맴도는데...Aspect를 만들고 네이밍만 EntityServiceSomethingDecorator 라고 이름만 붙이면 되는게 아닌가 하는 생각이 듭니다.그러나 이 방법을 사용하는데 약간의 거부감이 있는데 AOP가 태생적으로 횡단 관심사를 해결하기 위한 기술이라는 사실 때문입니다.저는 흩어져 있는 공통 관심사 코드를 여기 저기 작성하지 않고 한 군데에서 작성하도록 한 게 개발 의도라고 생각했거든요.하지만 예시의 경우는 흩어져 있는 관심사가 아니라 특정 로직에 부가 로직을 몇 개 붙였다 뗐다 하고 싶을 뿐입니다.그렇다면 단순 데코레이터를 만들기 위해 SpringAOP를 사용하는 것은 SpringAOP의 개발 의도와는 약간 다른 사용법이 될 수 있고,보통 특정 기능을 개발 의도와 다른 방향으로 사용하면 예상하지 못한 부작용이 발생하더라구요.이 지점에서 혹시 인사이트를 얻을 수 있을지 질문을 올려봅니다.질문을 조금 다르게 얘기하면 "특정 메서드 혹은 클래스만을 위한 Aspect를 만들어도 되는가?"가 되겠네요.만약 이 경우에 AOP사용은 지양하는 것이 좋다고 생각하신다면, 매뉴얼하게 데코레이터 클래스를 작성하는 것 외에 권장하실만한 방법이 있을까요?
-
미해결실전! 스프링 데이터 JPA
의존성 관련 질문
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 의존성에 대해 갑자기 헷갈려서 총 4가지 질문드립니다.MemberService에 아래와 같은 메서드(Member findMemberById(Long id))가 있고 PostService(다른 Service 클래스)에서 memberId로 Member를 조회해야 할 일이 있습니다. 이 경우 아래 메서드를 이용하기 위해 PostService 클래스에서 private final MemberService memberService 형태로 멤버변수로 포함한다고 가정하겠습니다.(memberService.findMemberByMember(id) 로 이용하기위해)public Member findMemberById(Long id) { return memberRepository.findById(id) .orElseThrow(() -> new BusinessLogicException(MEMBER_NOT_FOUND)); } Q1) PostService에서 MemberService를 생성자 주입을 통해 받아서 이용할 경우 MemberService의 의존성(예를 들면 MemberRepository 등의 MemberService 클래스에서 사용하는 클래스들)까지 PostService에 포함되는 것일까요? Q2) 의존성이라는게 단순히 생성자 주입으로 받았던 멤버 클래스들 뿐 아니라 내부 메소드에서 매개변수로 받은 클래스가 있다면 이 또한 의존성인가요?(import 로 포함된 클래스들을 모두 의존클래스로 보면 될지, 아니면 내부에서 사용하는 모든 객체를 의존성으로 보면될지 >> 같은 패키지의 경우 import 안하는걸 고려했을때 내부에서 객체로 이용하지만 같은 패키지라 import 안되는걸 고려) Q3-1) Q1의 답변에서 MemberService의 의존성까지 PostService에 포함되는 거라면 PostService에서 Member를 조회하기 위해서는 memberService.findMemberById(id) 로 조회하기 보단 MemberRepository를 주입받아서 memberRepository.findById(id).orElseThrow(() -> new BusinessLogicException(MEMBER_NOT_FOUND)); 형태로 변경하는게 맞을지? Q3-2) Q1의 답변에서 MemberService의 의존성까지 PostService에 포함되는게 아니라면 PostService에서 MemberService가 아닌 MemberRepository로 조회하는게 맞을지? Member 조회가 여러곳에서 사용되고 Member findMemberById(Long id) 메소드의 내용이 여러곳에서 중복되는 걸 생각했을때처음에는 단순히 member 조회가 여러곳에서 일어나고 공통된 내용이 반복되어 MemberService의 findMemberById 로 묶어서 사용하는게 맞다고 생각했는데 의문이 들었고 예전에 의존성은 최대한 줄이라고 하셨던게 생각나서 질문드립니다.
-
해결됨스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
애플리케이션 런 했을때 에러 발생
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.안녕하세요 vs코드에서 자바 배우려고 intellj(무료버전)를 설치 후 강의를 따라가고 있습니다. 그런데 HelloSpringApplication을 런 했을때 3가지 에러가 발생하는데 핸들링 부탁드립니다.크게는 이 3가지가 나오고, 이건 첫번째 에러인데 해결하지 못했습니다 ㅠㅠ답변 부탁드립니다.
-
해결됨스프링 핵심 원리 - 기본편
test 쪽만 들어가면 앞에 이상한 코드가 잔뜩 들어가는데 도저히 수정을 못하겠습니다.
강사님 화면은 위쪽처럼 깔끔한데 저는 아래쪽에 DEBUG라는 메시지가 잔뜩 나옵니다. 여기저기 아무리 뒤져봐도 해결책을 찾지 못하고 있습니다. 설정 파일을 잘못 건드려서 그런걸까요?
-
해결됨토비의 스프링 부트 - 이해와 원리
회사 비지니스 공통업무처리 관련 유용한 라이브러리 들이 있는지 여쭤봅니다
안녕하세요 토비님~이번에도 강의 내용과 상관 없는 질문 드립니다회사에서 타임리프 + JPA + 마이바티스 + 스프링/스프링부트 + 오라클 환경에서 개발하고 있습니다 MVC CRUD, API 송수신, 특이업무를 제외하고는 보통 회사에서 일어나는 공통업무는 아래와 같은 부분이 있다고 생각 합니다*.엑셀다운*.엑셀업로드*.이메일전송(첨부파일포함)*.PDF다운*.FAX전송*.출력 다른 분들이 먼저 개발해 놓은 소스를 참고해 가며개발 수정 운영을 하고 있는데요 제가 느끼기에는 뭔가 불필요한 소스 코드가 많고 긴 건 아닐까?..누군가 잘 만들어 놓은 라이브러리 메서드에 파라미터만 담아주고호출 하면 되진 않을까 생각이 들었습니다 혹시, 아래와 같은 공통 업무 사항들에 대해서 스프링 진영에서 Util 성격으로 잘 만들어 미리 만들어 놓은 좋은 라이브러리가 있지는 않 을까 생각이 듭니다 (엑셀업로드,다운로드/이메일송수신/PDF/FAX/출력 .. )회사마다 환경이나 요구 상황에 따라서 다르겠지만, 토비님은 이런 공통 비지니스 업무 관련 엑셀업로드,다운로드/이메일송수신/PDF/FAX/출력 관련 공통 비지니스 업무 관련해서 스프링 진영에서 혹시 이미 만들어 놓은 라이브러리 의존 관계를 추가해서사용하고 계시는 부분이 있나 여쭤봅니다 만약 사용하고 계신다면 어떠 어떠한 것들이 있는지 소개 부탁 드립니다 급한 질문 아닙니 시간 나 실 때알려주시면 감사하겠습니다. 수고하세요.--█●●--------------------------------------------#엑셀#이메일#PDF#FAX#출력#스프링#스프링부트#부트#spring #sping-boot#springboot#토비#공통#라이브러리--█●●--------------------------------------------
-
미해결스프링 시큐리티 OAuth2
Spring Authorization Server 1.0.1 state 문의
authorization_code grantType 으로 인가를 진행한다고 가 정하였을때 authorization_code 발급 요청에서 사용한 state 와 token 발급 요청해서 사용하는 state가 다르면 token 발급이 실패 해야 하는데 성공하고 있어서 분석해보니 oauth2_authorization 테이블의 state 필드가 null 로 저장되고 있습니다. JdbcOAuth2AuthorizationService를 사용하지 않고 커스텀한 구현체를 만들어서 사용하고 있는것이 원인일 수 있어 디버깅 해보니 OAuth2Authorization 의 소스를 분석해보면 state 가 포함되고 있지 않아서 저장이 안되고 있는데 아직 Spring Authorization Server는 state 가 구현되지 않은 걸까요? 추가로 소스를 분석해보니 nonce 도 아직 구현되지 않은거 같습니다.Spring Authorization Server 1.0.1 의 OAuth2Authorization 소스package org.springframework.security.oauth2.server.authorization; import java.io.Serializable; import java.time.Instant; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; import org.springframework.lang.Nullable; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * A representation of an OAuth 2.0 Authorization, which holds state related to the authorization granted * to a {@link #getRegisteredClientId() client}, by the {@link #getPrincipalName() resource owner} * or itself in the case of the {@code client_credentials} grant type. * * @author Joe Grandja * @author Krisztian Toth * @since 0.0.1 * @see RegisteredClient * @see AuthorizationGrantType * @see OAuth2Token * @see OAuth2AccessToken * @see OAuth2RefreshToken */ public class OAuth2Authorization implements Serializable { private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID; private String id; private String registeredClientId; private String principalName; private AuthorizationGrantType authorizationGrantType; private Set<String> authorizedScopes; private Map<Class<? extends OAuth2Token>, Token<?>> tokens; private Map<String, Object> attributes; protected OAuth2Authorization() { } /** * Returns the identifier for the authorization. * * @return the identifier for the authorization */ public String getId() { return this.id; } /** * Returns the identifier for the {@link RegisteredClient#getId() registered client}. * * @return the {@link RegisteredClient#getId()} */ public String getRegisteredClientId() { return this.registeredClientId; } /** * Returns the {@code Principal} name of the resource owner (or client). * * @return the {@code Principal} name of the resource owner (or client) */ public String getPrincipalName() { return this.principalName; } /** * Returns the {@link AuthorizationGrantType authorization grant type} used for the authorization. * * @return the {@link AuthorizationGrantType} used for the authorization */ public AuthorizationGrantType getAuthorizationGrantType() { return this.authorizationGrantType; } /** * Returns the authorized scope(s). * * @return the {@code Set} of authorized scope(s) * @since 0.4.0 */ public Set<String> getAuthorizedScopes() { return this.authorizedScopes; } /** * Returns the {@link Token} of type {@link OAuth2AccessToken}. * * @return the {@link Token} of type {@link OAuth2AccessToken} */ public Token<OAuth2AccessToken> getAccessToken() { return getToken(OAuth2AccessToken.class); } /** * Returns the {@link Token} of type {@link OAuth2RefreshToken}. * * @return the {@link Token} of type {@link OAuth2RefreshToken}, or {@code null} if not available */ @Nullable public Token<OAuth2RefreshToken> getRefreshToken() { return getToken(OAuth2RefreshToken.class); } /** * Returns the {@link Token} of type {@code tokenType}. * * @param tokenType the token type * @param <T> the type of the token * @return the {@link Token}, or {@code null} if not available */ @Nullable @SuppressWarnings("unchecked") public <T extends OAuth2Token> Token<T> getToken(Class<T> tokenType) { Assert.notNull(tokenType, "tokenType cannot be null"); Token<?> token = this.tokens.get(tokenType); return token != null ? (Token<T>) token : null; } /** * Returns the {@link Token} matching the {@code tokenValue}. * * @param tokenValue the token value * @param <T> the type of the token * @return the {@link Token}, or {@code null} if not available */ @Nullable @SuppressWarnings("unchecked") public <T extends OAuth2Token> Token<T> getToken(String tokenValue) { Assert.hasText(tokenValue, "tokenValue cannot be empty"); for (Token<?> token : this.tokens.values()) { if (token.getToken().getTokenValue().equals(tokenValue)) { return (Token<T>) token; } } return null; } /** * Returns the attribute(s) associated to the authorization. * * @return a {@code Map} of the attribute(s) */ public Map<String, Object> getAttributes() { return this.attributes; } /** * Returns the value of an attribute associated to the authorization. * * @param name the name of the attribute * @param <T> the type of the attribute * @return the value of an attribute associated to the authorization, or {@code null} if not available */ @Nullable @SuppressWarnings("unchecked") public <T> T getAttribute(String name) { Assert.hasText(name, "name cannot be empty"); return (T) this.attributes.get(name); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } OAuth2Authorization that = (OAuth2Authorization) obj; return Objects.equals(this.id, that.id) && Objects.equals(this.registeredClientId, that.registeredClientId) && Objects.equals(this.principalName, that.principalName) && Objects.equals(this.authorizationGrantType, that.authorizationGrantType) && Objects.equals(this.authorizedScopes, that.authorizedScopes) && Objects.equals(this.tokens, that.tokens) && Objects.equals(this.attributes, that.attributes); } @Override public int hashCode() { return Objects.hash(this.id, this.registeredClientId, this.principalName, this.authorizationGrantType, this.authorizedScopes, this.tokens, this.attributes); } /** * Returns a new {@link Builder}, initialized with the provided {@link RegisteredClient#getId()}. * * @param registeredClient the {@link RegisteredClient} * @return the {@link Builder} */ public static Builder withRegisteredClient(RegisteredClient registeredClient) { Assert.notNull(registeredClient, "registeredClient cannot be null"); return new Builder(registeredClient.getId()); } /** * Returns a new {@link Builder}, initialized with the values from the provided {@code OAuth2Authorization}. * * @param authorization the {@code OAuth2Authorization} used for initializing the {@link Builder} * @return the {@link Builder} */ public static Builder from(OAuth2Authorization authorization) { Assert.notNull(authorization, "authorization cannot be null"); return new Builder(authorization.getRegisteredClientId()) .id(authorization.getId()) .principalName(authorization.getPrincipalName()) .authorizationGrantType(authorization.getAuthorizationGrantType()) .authorizedScopes(authorization.getAuthorizedScopes()) .tokens(authorization.tokens) .attributes(attrs -> attrs.putAll(authorization.getAttributes())); } /** * A holder of an OAuth 2.0 Token and it's associated metadata. * * @author Joe Grandja * @since 0.1.0 */ public static class Token<T extends OAuth2Token> implements Serializable { private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID; protected static final String TOKEN_METADATA_NAMESPACE = "metadata.token."; /** * The name of the metadata that indicates if the token has been invalidated. */ public static final String INVALIDATED_METADATA_NAME = TOKEN_METADATA_NAMESPACE.concat("invalidated"); /** * The name of the metadata used for the claims of the token. */ public static final String CLAIMS_METADATA_NAME = TOKEN_METADATA_NAMESPACE.concat("claims"); private final T token; private final Map<String, Object> metadata; protected Token(T token) { this(token, defaultMetadata()); } protected Token(T token, Map<String, Object> metadata) { this.token = token; this.metadata = Collections.unmodifiableMap(metadata); } /** * Returns the token of type {@link OAuth2Token}. * * @return the token of type {@link OAuth2Token} */ public T getToken() { return this.token; } /** * Returns {@code true} if the token has been invalidated (e.g. revoked). * The default is {@code false}. * * @return {@code true} if the token has been invalidated, {@code false} otherwise */ public boolean isInvalidated() { return Boolean.TRUE.equals(getMetadata(INVALIDATED_METADATA_NAME)); } /** * Returns {@code true} if the token has expired. * * @return {@code true} if the token has expired, {@code false} otherwise */ public boolean isExpired() { return getToken().getExpiresAt() != null && Instant.now().isAfter(getToken().getExpiresAt()); } /** * Returns {@code true} if the token is before the time it can be used. * * @return {@code true} if the token is before the time it can be used, {@code false} otherwise */ public boolean isBeforeUse() { Instant notBefore = null; if (!CollectionUtils.isEmpty(getClaims())) { notBefore = (Instant) getClaims().get("nbf"); } return notBefore != null && Instant.now().isBefore(notBefore); } /** * Returns {@code true} if the token is currently active. * * @return {@code true} if the token is currently active, {@code false} otherwise */ public boolean isActive() { return !isInvalidated() && !isExpired() && !isBeforeUse(); } /** * Returns the claims associated to the token. * * @return a {@code Map} of the claims, or {@code null} if not available */ @Nullable public Map<String, Object> getClaims() { return getMetadata(CLAIMS_METADATA_NAME); } /** * Returns the value of the metadata associated to the token. * * @param name the name of the metadata * @param <V> the value type of the metadata * @return the value of the metadata, or {@code null} if not available */ @Nullable @SuppressWarnings("unchecked") public <V> V getMetadata(String name) { Assert.hasText(name, "name cannot be empty"); return (V) this.metadata.get(name); } /** * Returns the metadata associated to the token. * * @return a {@code Map} of the metadata */ public Map<String, Object> getMetadata() { return this.metadata; } protected static Map<String, Object> defaultMetadata() { Map<String, Object> metadata = new HashMap<>(); metadata.put(INVALIDATED_METADATA_NAME, false); return metadata; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Token<?> that = (Token<?>) obj; return Objects.equals(this.token, that.token) && Objects.equals(this.metadata, that.metadata); } @Override public int hashCode() { return Objects.hash(this.token, this.metadata); } } /** * A builder for {@link OAuth2Authorization}. */ public static class Builder implements Serializable { private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID; private String id; private final String registeredClientId; private String principalName; private AuthorizationGrantType authorizationGrantType; private Set<String> authorizedScopes; private Map<Class<? extends OAuth2Token>, Token<?>> tokens = new HashMap<>(); private final Map<String, Object> attributes = new HashMap<>(); protected Builder(String registeredClientId) { this.registeredClientId = registeredClientId; } /** * Sets the identifier for the authorization. * * @param id the identifier for the authorization * @return the {@link Builder} */ public Builder id(String id) { this.id = id; return this; } /** * Sets the {@code Principal} name of the resource owner (or client). * * @param principalName the {@code Principal} name of the resource owner (or client) * @return the {@link Builder} */ public Builder principalName(String principalName) { this.principalName = principalName; return this; } /** * Sets the {@link AuthorizationGrantType authorization grant type} used for the authorization. * * @param authorizationGrantType the {@link AuthorizationGrantType} * @return the {@link Builder} */ public Builder authorizationGrantType(AuthorizationGrantType authorizationGrantType) { this.authorizationGrantType = authorizationGrantType; return this; } /** * Sets the authorized scope(s). * * @param authorizedScopes the {@code Set} of authorized scope(s) * @return the {@link Builder} * @since 0.4.0 */ public Builder authorizedScopes(Set<String> authorizedScopes) { this.authorizedScopes = authorizedScopes; return this; } /** * Sets the {@link OAuth2AccessToken access token}. * * @param accessToken the {@link OAuth2AccessToken} * @return the {@link Builder} */ public Builder accessToken(OAuth2AccessToken accessToken) { return token(accessToken); } /** * Sets the {@link OAuth2RefreshToken refresh token}. * * @param refreshToken the {@link OAuth2RefreshToken} * @return the {@link Builder} */ public Builder refreshToken(OAuth2RefreshToken refreshToken) { return token(refreshToken); } /** * Sets the {@link OAuth2Token token}. * * @param token the token * @param <T> the type of the token * @return the {@link Builder} */ public <T extends OAuth2Token> Builder token(T token) { return token(token, (metadata) -> {}); } /** * Sets the {@link OAuth2Token token} and associated metadata. * * @param token the token * @param metadataConsumer a {@code Consumer} of the metadata {@code Map} * @param <T> the type of the token * @return the {@link Builder} */ public <T extends OAuth2Token> Builder token(T token, Consumer<Map<String, Object>> metadataConsumer) { Assert.notNull(token, "token cannot be null"); Map<String, Object> metadata = Token.defaultMetadata(); Token<?> existingToken = this.tokens.get(token.getClass()); if (existingToken != null) { metadata.putAll(existingToken.getMetadata()); } metadataConsumer.accept(metadata); Class<? extends OAuth2Token> tokenClass = token.getClass(); this.tokens.put(tokenClass, new Token<>(token, metadata)); return this; } protected final Builder tokens(Map<Class<? extends OAuth2Token>, Token<?>> tokens) { this.tokens = new HashMap<>(tokens); return this; } /** * Adds an attribute associated to the authorization. * * @param name the name of the attribute * @param value the value of the attribute * @return the {@link Builder} */ public Builder attribute(String name, Object value) { Assert.hasText(name, "name cannot be empty"); Assert.notNull(value, "value cannot be null"); this.attributes.put(name, value); return this; } /** * A {@code Consumer} of the attributes {@code Map} * allowing the ability to add, replace, or remove. * * @param attributesConsumer a {@link Consumer} of the attributes {@code Map} * @return the {@link Builder} */ public Builder attributes(Consumer<Map<String, Object>> attributesConsumer) { attributesConsumer.accept(this.attributes); return this; } /** * Builds a new {@link OAuth2Authorization}. * * @return the {@link OAuth2Authorization} */ public OAuth2Authorization build() { Assert.hasText(this.principalName, "principalName cannot be empty"); Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null"); OAuth2Authorization authorization = new OAuth2Authorization(); if (!StringUtils.hasText(this.id)) { this.id = UUID.randomUUID().toString(); } authorization.id = this.id; authorization.registeredClientId = this.registeredClientId; authorization.principalName = this.principalName; authorization.authorizationGrantType = this.authorizationGrantType; authorization.authorizedScopes = Collections.unmodifiableSet( !CollectionUtils.isEmpty(this.authorizedScopes) ? new HashSet<>(this.authorizedScopes) : new HashSet<>() ); authorization.tokens = Collections.unmodifiableMap(this.tokens); authorization.attributes = Collections.unmodifiableMap(this.attributes); return authorization; } } }