TIL - 0531
스프링부트
HTTP 세션 데이터 쉽게 가져오기 - 커스텀 파라미터 만들기
- HandleMethodArgumentResolver를 사용함
- 파라미터를 커스텀하는 역할, 두개의 메소드를 오버라이드 한 후 스프링 MVC에 리졸버를 등록하고, 실제로 적용할 부분에 선언해서 사용함
- Interceptor와 컨트롤러 로직 사이에 존재하는 것으로 생각 : 코드 중복을 줄일 수 있음(같은 파라미터를 가지고 기능 구현을 한다면)
어노테이션 생성하기 : 커스텀 파라미터로 사용할 파라미터라는 표시(설정)
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
public boolean required() default true;
}
커스텀 파라미터 만들기 - HandleMethodArgumentResolver
public class LoginUserHandlerMethodArgumentResolver implements HandleMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(LoginUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
User user = HttpSessionUtils.getUserFromSession(webRequest);
if (!user.isGuestUser()) {
return user;
}
LoginUser loginUser = parameter.getParameterAnnotation(LoginUser.class);
if (loginUser.required()) {
throw new UnAuthorizedException("You're required Login!");
}
return user;
}
}
supportsParameter
: 리졸버가 해당 클래스에 적용되는지 확인 - LoginUser(어노테이션)의 적용범위가 ElementType.PARAMETER인지 확인함resolveArgument
: 파라미터를 커스텀 하는 메소드, 리턴으로 지정하는 객체가 파라미터로 사용됨
커스텀 파라미터 MVC에 적용 : 실제로 사용하기위해선 설정사항에 넣어야함
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
.
.
.
@Bean
public LoginUserHandlerMethodArgumentResolver loginUserArgumentResolver() {
return new LoginUserHandlerMethodArgumentResolver();
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(loginUserArgumentResolver());
}
}
@Bean 적용 메소드
: 리졸버 스프링 빈을 생성하는 메소드addArgumentResolvers
: 생성한 리졸버(스프링빈)를 리졸버 리스트에 추가함 - 실행하기위해선 추가해줘야하나봄
커스텀 파라미터 사용하기
@Controller
public class UserController {
.
.
.
@PutMapping("/{id}")
public String update(@LoginUser User user, @PathVariable Long id, User updateInfo) {
...
}
}
@LoginUser
: 치환한 어노테이션명User
: 커스텀 파라미터의 리턴 타입, resolveArgument에서 리턴한 오브젝트의 타입
Null Object 패턴 처리와 Optional 처리
- 두 방법은 모두 안전한 NullPointerException 처리를 위한 방법
- 두 방법의 차이라면 Null을 래핑하는 오브젝트 역할이 많다면, 많게된다면 Null Object 패턴을 사용하는 것이 좋다고 생각됨
- 그렇지않다면 일일이 Optional에서 객체가 존재한다면 get()을 통해서 뺴온 후 작업을 해야함 : 코드를 지저분하게 만듦(따로 로직을 만들 필요가 없음), 새로운 null check 패턴을 배워보니 신기!
public class User {
private static GuestUser guestUser = new GuestUser();
public boolean isQuestUser() {
return false;
}
private static class GuestUser extends User {
@Override
public boolean isQuestUser() {
return true;
}
}
}
Mockito 활용해서 테스트 진행하기
- RestTemplate가 end to end 테스트였다면, 서버 로직에 대한 단위 테스트를 하려면 Mock Test를 함 : Mockito 사용
- 두 테스트의 케이스는 같지만 테스트 하고자 하는 바가 다름
- Mock 객체 DI를 활용한 테스트 : mock 개념이 없었다면 서버 프로그램의 단위 테스트를 위해서 환경 세팅을 다 해두고 브라우저로 테스트하는 것처럼 했어야했음
- Mock 객체 사용이 가능한 이유는 스프링이 객체지향적인 프레임워크라서 : 해당 타입 혹은 하위 타입의 인스턴스라면 DI할 수 있기때문(표준 인터페이스를 만들어두고 인터페이스를 구현) - 같은 메소드로 같은 맥락의 기능을 사용하지만 각각의 인스턴스에 따라 다르게 동작
- Mock 테스트는 실제 서블릿 컨테이너 환경을 구성하지않고 테스트하기때문에 빠른 테스트가 가능함
@RunWith(MockitoJUnitRunner.class)
public class QnaServiceTest {
@Mock
private QuestionRepository questionRepo;
@InjectMocks
private QnaService qnaService;
@Test
public void create() {
Question question = new Question("test", "test");
when(qnaService.create(new User(), question)).thenReturn(question);
}
.
.
.
.
.
생략
}
RunWith(MockitoJUnitRunner.class)
: 테스트를 Mockito를 통해서 하겠다는 설정@Mock
: mock 객체 생성하겠다는 설정@InjectMocks
: 해당 객체의 디펜던시(위임 대상)로 @Mock으로 생성한 객체를 인젝션하겠다는 설정- when ~ thenReturn : Mockito에서 제공하는 테스트 관련 메소드, 직관적임, 테스트 프레임워크 사용과 관련해서는 너무 많으니깐 생략! 쓰면서 배우자~~!
- 성공 케이스보다 중요한건 뭐다? 실패 케이스
- 해당 부분에 대한 로직 처리가 되어있는지 확인하는 것이 핵심
자바
VO와 DTO
- VO(Value Object) : 말그대로 값을 저장하는 객체, 값이라하면 변수와는 반대되는 개념, immutable object, 한번 초기화 이후에는 상태값이 변경될 수 없음
- String은 불변객체 : 내부적으로 값을 변경하면 새로운 객체를 만들어서 리턴함
- 아래의 예제는 Car를 가변객체를 불변객체로 만들려면 어떤 작업을 해야하는지 볼 수 있는 코드임 : 불변객체를 만들면 힙 메모리 효율이 떨어지겠지만 불변객체가 필요할 떄가 있음, 가변 객체와 불변객체 둘 중 어떤 것을 사용할지 상황에 따라 선택하기 - 어찌되었건 이런 선택지가 있다는 점을 알게됨
public class Car {
private int position;
private MoveStrategy strategy;
public Car(MoveStrategy strategy, int position) {
this.strategy = strategy;
this.position = position;
}
// mutable
public void move(int randomVal) {
if (strategy.isMovable(randomwVal) {
position++;
}
}
// immutable
public Car move(int randomVal) {
if (strategy.isMovable(randomwVal) {
return new Car(strategy, position++);
}
return this;
}
}
- DTO : DTO의 역할은 도메인 객체를 그대로 전달하지않고 당시의 상태를 프리징시키는 역할(비즈니스 로직과 상태값을 따로 떼어냄 - 로직을 더이상 수행하지못하도록)
- VO와는 달리 set 메소드가 있어서 변경은 할 수 잇음 : mutable
- 주의할 점 : setter가 없다고 하더라도 조심해야할 점은 인스턴스 변수(상태)가 레퍼런스 타입이라면 getter 메소드로 get하여 해당 객체를 변경시킬 수 있음
- 콜렉션 프레임워크를 인스턴스 변수로 사용한다면 조심해야함
- Collections.unmodifiableList() 와 같이 읽기전용 콜렉션으로 변경시켜 get하게끔 해야함
public class Question {
private List<Answer> answers = new ArrayList<>();
public List<Answer> getAnswers() {
return Collections.unmodifiableList(answers);
}
}