묻고 답해요
169만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
@Controller, @RequestMapping 질문드립니다.
안녕하세요. @Controller, @RequestMapping의 로직에 대해 제가 이해한바가 맞는지 질문드립니다. 다른 분들이 올리신 질문도 다 참고해보고 구글링도 해봤는데 궁금증이 풀리지않아 비슷한 내용의 질문을 드려서 죄송합니다..@Controller, @RequestMapping을 사용해서 애플리케이션을 실행하는 과정에서,서버가 실행되면, @Controller에 의해 핸들러(컨트롤러)로 등록된다.요청이 들어오면, 핸들러 매핑이 핸들러를 찾고 핸들러 어댑터가 핸들러를 실행해야 하는데, 먼저 스프링 내부에 등록되어 있는 RequestMappingHandlerMapping이 핸들러를 조회한다. @RequestMapping을 사용했기때문에 애노테이션 기반 컨트롤러를 조회할수 있는 Requestmappinghandlermapping이 핸들러를 조회하는데, 이때 @Controller로 등록된 핸들러중에 @RequestMapping에 담긴 논리url을 통해 클라이언트가 전송한 url과 매핑을해서 해당되는 핸들러를 조회한다. 그리고 이 조회한 핸들러를 처리할수 있는 어댑터인RequestMappingHandlerAdapter가 핸들러를 호출(process()를 호출)한다. 이렇게 이해하면 될까요?
-
해결됨실무 환경 그대로 주문게시판 만들기 웹개발 기초 마스터
시퀀스 질문
시퀀스에서 Cycle을 사용하면 중복키 관리는 안하게 되는건가요?
-
미해결스프링 핵심 원리 - 기본편
생성자 주입으로 변경했을 때 Provider 생성할때 오류
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.단위 테스트로 할 때 필드 주입이 좋지 않다고 하셔서 생성자 주입으로 변경 후 돌려봤을 때 Provider를 적용하기 전에 ObjectProvider<PrototypeBean> 으로 했을 때는 테스트가 성공했지만 Provider로 할때는 다음과 같은 에러가 나서 왜 발생했는지 질문드립니다org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'singletonWithPrototype.ClientBean': Unsatisfied dependency expressed through constructor parameter 1: No qualifying bean of type 'javax.inject.Provider<hello.core.scope.SingletonWithPrototype$PrototypeBean>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}아래는 전체 코드 입니다.package hello.core.scope; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Scope; import javax.inject.Provider; import static org.assertj.core.api.Assertions.assertThat; public class SingletonWithPrototype { @Test void prototypeFind() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class); PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class); prototypeBean1.addCount(); assertThat(prototypeBean1.getCount()).isEqualTo(1); PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class); prototypeBean2.addCount(); assertThat(prototypeBean2.getCount()).isEqualTo(1); } @Test void singletonClientUsePrototype() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class); ClientBean clientBean1 = ac.getBean(ClientBean.class); int count1 = clientBean1.logic(); assertThat(count1).isEqualTo(1); ClientBean clientBean2 = ac.getBean(ClientBean.class); int count2 = clientBean2.logic(); assertThat(count2).isEqualTo(1); } @Scope("singleton") static class ClientBean { private final PrototypeBean prototypeBean; //생성시점에 주입되어서 계속 같은 거 사용. private final Provider<PrototypeBean> prototypeBeanProvider; @Autowired //생성자 하나니까 @Autowired 생략해도 되긴 함 public ClientBean(PrototypeBean prototypeBean, Provider<PrototypeBean> prototypeBeanProvider) { this.prototypeBean = prototypeBean; this.prototypeBeanProvider = prototypeBeanProvider; } public int logic() { PrototypeBean prototypeBean = prototypeBeanProvider.get(); prototypeBean.addCount(); return prototypeBean.getCount(); } } @Scope("prototype") static class PrototypeBean { private int count = 0; public void addCount() { count++; } public int getCount() { return count; } @PostConstruct public void init() { System.out.println("PrototypeBean.init " + this); } @PreDestroy public void destroy() { System.out.println("PrototypeBean.destroy"); } } }
-
해결됨자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
mysql 비밀번호 재설정
https://goodteacher.tistory.com/291 위 블로그를 참고해서 비밀번호를 초기화했는데mysql -u root -p Enter password: ************ ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)위와 같이 나옵니다. 위 에러 메시지는 비밀번호를 잘못입력했을 때 나오는 걸로 아는데저 한영 잘못친 것도 없고 numlock도 풀려있어서 제대로 잘 입력했는데 왜 그럴까요ㅠ mysql --version 해보면 mysql ver8.0.27 로 잘 나옵니다
-
해결됨스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
싱글톤 질문 드립니다.
강의 교재의 "스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다(유일하게 하나만 등록해서 공유한다) 따라서 같은 스프링 빈이면 모두 같은 인스턴스다." 이 문장에서 같은 스프링 빈이면 모두 같은 인스턴스라는 말이 하나의 인스턴스로 자원을 공유한다는 의미 같은데 어떤 경우에 싱글톤을 사용해 자원을 공유하는지 궁금합니다.
-
해결됨스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
레포지토리 인터페이스 질문 드립니다.
강의에서 데이터 베이스를 아직 정하지 못해 인터페이스로 추상화 한다고 설명해 주셨는데 인터페이스 말고 추상 클래스를 사용하면 안되나요? 그리고 만약 데이터 베이스를 정했어도 추상화 할 때 인터페이스를 써야 하는지 궁금합니다.
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
TEST오류
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요 회원가입에서 오류가 떠요aused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springDataJpaMemberRepository' defined in com.example.hello_spring.repository.SpringDataJpaMemberRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract void com.example.hello_spring.repository.MemberRepository.clearStore(); Reason: Failed to create query for method public abstract void com.example.hello_spring.repository.MemberRepository.clearStore(); No property 'clearStore' found for type 'Member'이런식으로 뭐가 정의되있다는데 뭐죠 ㅠㅠ
-
미해결스프링 DB 2편 - 데이터 접근 활용 기술
public, private 트랜잭션
안녕하세요! 고급편을 듣고 DB2 편을 듣고잇습니다. 여전히 뭔가 이해가 안가서요. 고급원리에도 질문을 하긴했지만 명확하지않았어요. 답변에 DB 2편을 보라고 하셨는데.. ㅠㅠ 이해잘안되네요. 죄송합니다. 궁금한게 뭐냐면,@Tansactional public void externalMethod() { this.internalMethod(); // logic } private void internalMethod() { // logic }여기서 internalMethod는 당연히 트랜잭션이 안탄다고 생각을 했어요. 이유는 우선 DI로 주입이 CGBLIB 프록시가 주입이 되는 것이고 이제 여기서 어노테이션을 보고 프록시 적용을 한다음.프록시 externalMethod 를 불러서 트랜잭션이 탔고internalMethod는 targetMethod 기에 프록시를 안탄다고 생각했거든요?근데 잘타네요.. 이해가 잘안갑니다.. ㅠㅠㅠ--[수정]아 이게.. 프록시 -> 타겟으로 가니까 어떻게보면 당연히 트랜잭션이 먹히는게 맞네요..반대가 안먹히는거고.. 맞죠? ㅠ
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
hasText 부분 질문있습니다.
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]여기에 질문 내용을 남겨주세요.아래 코드에서 orderStatus 부분은 널로 체크하고 memberName 부분은 hasText로 체크한 이유가 있을까요??orderSearch.getMemberName() != null이런식으로 체크하면 안되는지 궁금합니다. if (orderSearch.getOrderStatus() != null) { query = query.setParameter("status", orderSearch.getOrderStatus()); } if (StringUtils.hasText(orderSearch.getMemberName())) { query = query.setParameter("name", orderSearch.ge tMemberName()); }
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
엔티티 클래스 개발2 오류
실행시켰는데 오류가 이렇게 나타납니다거의 모든 클래스들에서 jakarta가 빨간색으로 표시됩니다build.gradle과 application.yml도 첨부합니다.
-
해결됨스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
ControllerV3의 ModelView 클래스
modelView 클래스의 model의 경우 MemberFormControllerV3, MemberSaveControllerV3, MemberListControllerV3 객체가 모두 사용해야 하므로@Getter @Setter public class ModelView { private final String viewName; private static final Map<String, Object> model = new HashMap<>(); public ModelView(String viewName) { this.viewName = viewName; } public static Map<String, Object> getModel() { return model; } }이와 같이 private static final로 선언한 후 모델 객체를 사용할 때 하기 코드와 같이 ModelView.getModel()형식으로 불러와도 될까요?public class MemberListControllerV3 implements ControllerV3 { private final MemberRepository memberRepository = MemberRepository.getInstance(); @Override public ModelView process(Map<String, String> requestParamMap) { List<Member> members = memberRepository.findAll(); ModelView modelView = new ModelView("members"); ModelView.getModel().put("members", members); return modelView; } } ModelView의 viewName의 경우 호출하는 객체마다 다르게 들어올 수 있으니 static 없이 final로만 선언하면 될까요? 감사합니다:)
-
해결됨실전! 스프링 데이터 JPA
실무에서 페이징 처리
안녕하세요! 영한님!강의는 너무나 잘들었습니다!👍👍강의를 듣고 나서 본격적으로 JPA를 이용한 개발을 진행하려 하는데, 실무에서 적용 시 궁금한 점이 있어서 질문을 남기게 되었습니다.강의 내용에서 Page 를 활용해서 DB 데이터의 페이징 처리를 할 수 있다는 점에 대해 알려주셔서 굉장히 유용한 방식이라 생각이 듭니다.이를 실무에서 실제로 적용하기 앞서 고민이 되는 부분은, 페이징이나 정렬과 관련하여 쿼리문을 이용해 처리를 하는 것과 전제 데이터를 가져와 비즈니스 로직에서 처리 하는 방식 중 어떤 것이 효율 적인지 고민이 되고 검색도 해봤는데 의견이 다 나뉘더라구요. 그래서 영한님께서는 보통 어떤 것을 기준으로 두 가지 방식 중에 선택을 하여 적용을 하시는지 의견을 듣고 싶어서 질문 드리게 되었습니다. 감사합니다!!
-
해결됨스프링 DB 1편 - 데이터 접근 핵심 원리
오타 제보
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]"3.트랜잭션 이해.pdf" 수업자료 32, 44페이지의 memberB가 memberEx로 정정돼야할 것 같습니다!<32페이지> <44페이지>수업 너무 잘 듣고 있습니다. 감사합니다.
-
미해결스프링 핵심 원리 - 기본편
수정자 주입
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]'생성자 주입을 선택하라!' 강의 5분쯤에 나온 NullPointerException이 왜 발생한 건지 궁금합니다.@Component public class OrderServiceImpl implements OrderService { private MemberRepository memberRepository; private DiscountPolicy discountPolicy; @Autowired public void setMemberRepository(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Autowired public void setDiscountPolicy(DiscountPolicy discountPolicy) { this.discountPolicy = discountPolicy; } }class OrderServiceImplTest { @Test void createOrder() { OrderServiceImpl orderService = new OrderServiceImpl(); orderService.createOrder(1L, "itemA", 10000); } }@Autowired를 해주었기 때문에 자동으로 의존관계 주입이 되는 거 아닌가요?추가) AnnotationConfigApplicationContext로 스프링 빈을 등록하지 않아서 그런 건가요?
-
해결됨실전! 스프링 데이터 JPA
스프링 데이터 jpa delete 최적화
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오) 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)예[질문 내용]강사님 강의 정말 잘 보고 있다가 의문점이 생겨 이렇게 질문 남깁니다. 일단 간단하게 예시를 만들면 양방향 관계로 묶인 Member와 Team이 있을때 Team을 삭제할때 해당 Team에 있던 Member도 전부 삭제를 원하는 상황입니다.데이터는 Team 2개 Team 하나당 3개의 Member씩이 있는 상태입니다.Member.javapackage com.example.demo.entity; import lombok.*; import javax.persistence.*; @Entity @Table(name = "member") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor public class Member { @Id @Column(name = "member_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long memberId; @Column(name = "member_name") private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "team") private Team team; public Member(String name, Team team) { this.name = name; this.team = team; } } Team.javapackage com.example.demo.entity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.*; import java.util.ArrayList; import java.util.List; @Entity @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Table(name = "team") public class Team { @Id @Column(name = "team_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true) List<Member> members = new ArrayList<>(); } controller.javapackage com.example.demo.entity.controller; import com.example.demo.entity.repository.TeamRepository; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor public class Controller { private final TeamRepository teamRepository; @DeleteMapping("/") public void deleteTeam(@RequestParam Long id) { teamRepository.deleteById(id); //deleteById는 JpaRepository에서 제공하는 기본 } } 위의 코드를 실행하고 delete요청을 했을때Hibernate:selectteam0_.team_id as team_id1_1_0_fromteam team0_whereteam0_.team_id=? Hibernate:selectmembers0_.team as team3_0_0_,members0_.member_id as member_i1_0_0_,members0_.member_id as member_i1_0_1_,members0_.member_name as member_n2_0_1_,members0_.team as team3_0_1_frommember members0_wheremembers0_.team=? Hibernate:deletefrommemberwheremember_id=? Hibernate:deletefrommemberwheremember_id=? Hibernate:deletefrommemberwheremember_id=? Hibernate:deletefromteamwhereteam_id=? 이렇게 나오고 Team의 members의 fetch type을 eager로 바꾸었을때는 selectteam0_.team_id as team_id1_1_0_,members1_.team as team3_0_1_,members1_.member_id as member_i1_0_1_,members1_.member_id as member_i1_0_2_,members1_.member_name as member_n2_0_2_,members1_.team as team3_0_2_fromteam team0_left outer joinmember members1_on team0_.team_id=members1_.teamwhereteam0_.team_id=? Hibernate:deletefrommemberwheremember_id=?Hibernate:deletefrommemberwheremember_id=? Hibernate:deletefrommemberwheremember_id=? Hibernate:deletefromteamwhereteam_id=?이러한 쿼리가 발생하는것을 볼 수 있습니다. 여기서 제가 든 궁금점은 (편의상 맨 위의 쿼리를 1번쿼리라 하겠습니다)지연로딩을 할때 Team의 Member는 프록시이므로 member select 하긴 할텐데 selectmembers0_.team as team3_0_0_,members0_.member_id as member_i1_0_0_,members0_.member_id as member_i1_0_1_,members0_.member_name as member_n2_0_1_,members0_.team as team3_0_1_이 쿼리(2번 쿼리)가 어떻게 나오게 된건지 이해가 되질 않습니다. 최대한 쿼리를 최적화 해보려 했지만 지연로딩을 할때 fetch join, entity graph등을 사용할 수 없어 1번과 2번 쿼리를 즉시로딩을 사용할때 처럼 최적화 할 수가 없었고 delete문도 멤버를 한번에 delete, 그다음 team delete 이렇게 두번으로 최적화 하고 싶었는데 방법이 떠오르질 않습니다. 정확하게 알려주시기 번거로우시면 키워드나 따로 어떻게 공부하면 해결 할 수 있을지 알려주시면 정말 감사드리겠습니다.
-
미해결스프링 DB 1편 - 데이터 접근 핵심 원리
connection 반환 관련하여 질문 드립니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 아래의 코드는 강의 코드 중 커넥션을 풀로 반환하는 로직입니다. 해당 코드에서는 커넥션 반환 중 예외가 발생했을 때 처리하는 별도의 코드가 없는 것으로 판단됩니다. 만약 con.close()가 정상 수행되지 않고, 예외가 발생한다면, 그 후 어떤 과정이 진행되는지 궁금합니다.그냥 계속해서 사용된 커넥션이 active 상태로 유지되는 건가요?항상 답변 해주셔서 감사합니다.
-
미해결스프링 DB 1편 - 데이터 접근 핵심 원리
커넥션 풀 질문
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/)[질문 내용]커넥션 풀이라는게사람이랑 1:1 관계인거면 수천명이 이용하는 서비스면커넥션풀도 수천개를 준비해놔야 할까요?기본이 10개인데
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
h2 연결
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예[질문 내용]h2로 웹사이트 들어가려는데 오류가 떠요 ㅠㅠ 왜이런거죠?
-
해결됨스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
DI 적용해보기
안녕하세요. 항상 좋은 강의 감사합니다.FrontController에서 어댑터들을 DI로 적용할 수 있다고 하셔서 이를 구현해보고자 하였습니다.@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*") public class FrontControllerServletV5 extends HttpServlet { ... @Autowired private Map<String, Object> handlerMappingMap; ... }처음에 컬렉션 객체들을 Bean으로 등록하여 주입받고자 HandlerMappingConfig클래스를 만들었습니다.@Configuration public class HandlerMappingConfig { @Bean public Map<String, Object> handlerMappingMap() { Map<String, Object> handlerMappingMap = new HashMap<>(); // V2 Controller handlerMappingMap.put("/front-controller/v5/v2/members/new-form", new MemberFormControllerV2()); handlerMappingMap.put("/front-controller/v5/v2/members/save", new MemberSaveControllerV2()); handlerMappingMap.put("/front-controller/v5/v2/members", new MemberListControllerV2()); // V3 Controller & V4 Controller ... return handlerMappingMap; } }그리고 제대로 주입을 받았는지 테스트하기 위해 FrontControllerServletV5 클래스의 service() 메소드에서 handlerMappingMap을 출력하도록 작성하였습니다.@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*") public class FrontControllerServletV5 extends HttpServlet { ... @Autowired private Map<String, Object> handlerMappingMap; ... @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 정상적으로 주입되었는지 테스트하기 위해 출력 System.out.println("handlerMappingMap = " + handlerMappingMap); ... } }서버를 실행시켜 확인해보니 아래 그림과 같이 handlerMappingMap에 마치 컨테이너의 빈들이 다 들어온 것처럼 출력된 것을 확인할 수 있었습니다.왜 이런 현상이 나타나는지 모르겠습니다. 혹시 타입이 Map<String, Object>라 그런걸까요?감사합니다.
-
해결됨스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
인터셉터의 호출 순서 질문입니다.
강의를 따라가다 문득 preHandle, postHandle, afterCompletion 의 순서를 알고 싶어서 LoginCheckInterceptor 에 해당 메서드를 추가하여 로그를 찍어보았습니다.질문은 맨 밑에 있으며, 코드는 이해를 돕기 위해 첨부했습니다.LogInterceptor@Slf4j public class LogInterceptor implements HandlerInterceptor { public static final String LOG_ID = "logId"; @Override public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler ) throws Exception { String requestURI = request.getRequestURI(); String uuid = UUID.randomUUID().toString(); request.setAttribute(LOG_ID, uuid); // @RequestMapping: HandlerMethod // 정적 리소스: ResourceHttpRequestMethod if (handler instanceof HandlerMethod) { // 호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다. HandlerMethod hm = (HandlerMethod) handler; } log.info("[{}][{}] LogInterceptor preHandle", requestURI, uuid); return true; } @Override public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView ) throws Exception { String requestURI = request.getRequestURI(); String uuid = (String) request.getAttribute(LOG_ID); log.info("[{}][{}] LogInterceptor postHandle", requestURI, uuid); } @Override public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex ) throws Exception { String requestURI = request.getRequestURI(); String uuid = (String) request.getAttribute(LOG_ID); log.info("[{}][{}] LogInterceptor afterComplete", requestURI, uuid); if (ex != null) { log.error("LogInterceptor afterComplete Error: ", ex); ex.printStackTrace(); } } }LoginCheckInterceptor@Slf4j public class LoginCheckInterceptor implements HandlerInterceptor { @Override public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler ) throws Exception { String requestURI = request.getRequestURI(); String uuid = (String) request.getAttribute(LogInterceptor.LOG_ID); log.info("[{}][{}] LoginCheckInterceptor preHandle", requestURI, uuid); HttpSession session = request.getSession(); if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) { log.info("미인증 사용자 요청 {}", requestURI); response.sendRedirect("/login?redirectURL=" + requestURI); return false; } return true; } @Override public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView ) throws Exception { String requestURI = request.getRequestURI(); String uuid = (String) request.getAttribute(LogInterceptor.LOG_ID); log.info("[{}][{}] LoginCheckInterceptor postHandle", requestURI, uuid); } @Override public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex ) throws Exception { String requestURI = request.getRequestURI(); String uuid = (String) request.getAttribute(LogInterceptor.LOG_ID); log.info("[{}][{}] LoginCheckInterceptor afterCompletion", requestURI, uuid); } }InterceptorConfig@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LogInterceptor()) .order(1) .addPathPatterns("/**") // 모두 허용 .excludePathPatterns("/css/**", "/*.ico", "/error"); // BlackList registry.addInterceptor(new LoginCheckInterceptor()) .order(2) .addPathPatterns("/**") .excludePathPatterns( "/", "/members/css", "/login", "/logout", "/css/**", "*.ico", "/error" ); } }결과 1 - 미인증 사용자 요청// /items 요청 [/items][45e50a37-57a0-4298-b50e-e42141005426] LogInterceptor preHandle [/items][45e50a37-57a0-4298-b50e-e42141005426] LoginCheckInterceptor preHandle 미인증 사용자 요청 /items [/items][45e50a37-57a0-4298-b50e-e42141005426] LogInterceptor afterComplete // Redirect - /login [/login][18f052ab-b849-4690-83ec-43866660f570] LogInterceptor preHandle [/login][18f052ab-b849-4690-83ec-43866660f570] LogInterceptor postHandle [/login][18f052ab-b849-4690-83ec-43866660f570] LogInterceptor afterComplete로그상으로 LoginCheckInterceptor의 afterComplete가 누락되었습니다.결과2 - 정상 처리// /login 요청 [/login][19bad338-e02d-4bbe-8b3a-5dfc55ad4428] LogInterceptor preHandle LoginService: 'test', 'test!' login? Member(id=1, loginId=test, name=테스터, password=test!) [/login][19bad338-e02d-4bbe-8b3a-5dfc55ad4428] LogInterceptor postHandle [/login][19bad338-e02d-4bbe-8b3a-5dfc55ad4428] LogInterceptor afterComplete // Redirect - /items [/items][679dc279-ff3a-4ee3-a424-26b18ac8dbbd] LogInterceptor preHandle [/items][679dc279-ff3a-4ee3-a424-26b18ac8dbbd] LoginCheckInterceptor preHandle [/items][679dc279-ff3a-4ee3-a424-26b18ac8dbbd] LoginCheckInterceptor postHandle [/items][679dc279-ff3a-4ee3-a424-26b18ac8dbbd] LogInterceptor postHandle [/items][679dc279-ff3a-4ee3-a424-26b18ac8dbbd] LoginCheckInterceptor afterCompletion [/items][679dc279-ff3a-4ee3-a424-26b18ac8dbbd] LogInterceptor afterCompletepreHandle은 order의 순위대로 로그가 찍히는데, postHandle과 afterCompletion의 경우 order의 순위와 반대로 호출이 됩니다. 혹시 위 2개의 결과에 대한 이유를 설명해주실 수 있을까요..? 그냥 로직때문에 그런 것인지 궁금합니다.