DI 설명과 장점, Spring MVC 예외 처리 기법

송현진·2023년 7월 20일

Spring

목록 보기
7/10
post-thumbnail

DI(Dependency Injection)란?

의존성 주입입니다. 이는 필요한 객체를 직접 생성하는 것이 아닌 외부로부터 객체를 받아서 사용하는 것이다.
이를 통해 객체간의 결합도를 줄이고 코드의 재사용성을 높일 수 있습니다.

의존성 주입은 생성자 주입, 수정자 주입, 필드 주입 3가지가 있다.
이중 Spring에서 권장하는 의존성 주입 방법은 생성자를 통한 주입 방법이다.
이유는 1. 순환 참조 방지 2. 불변성 가짐 3. 테스트에 용이 하기 때문이다.

잘못된 예

@Service
public class UserServiceImpl implements UserService{
	private UserRepository userRepository;
    private MemberService memberService;
    
    public UserServiceImpl(){
    	this.userRepository = new UserRepository();
        this.memberService = new MemberService();
    }
}
  1. 두 클래스 간 결합성이 높다
  2. 객체들 간의 관계가 아닌 클래스 간의 관계가 맺어짐
  • SOLID 원칙 중 "추상화에 의존해야지, 구체화에 의존하면 안된다"라는 DIP 원칙이 존재한다. 즉, 객체지향 5원칙을 위반하기 때문에 UserServiceImpl 클래스 변경이 어려워진다.

주입 방법

  1. 생성자 주입 방법
@Service
public class UserServiceImpl implements UserService{
	private UserRepository userRepository;
    private MemberService memberService;
    @Autowired // 생략 가능
    public UserServiceImpl(UserRepository userRepository, MemberService memberService){
    	this.userRepository = userRepository;
        this.memberService = memberService;
    }
}

생성자 주입은 호출 시점에 1회 보장
주입받는 객체의 변화가 없거나 반드시 객체의 주입이 필요한 경우 강제하기 위해 사용 가능

  1. 수정자 주입 방법
@Service
public class UserServiceImpl implements UserService{
	private UserRepository userRepository;
    private MemberService memberService;
    @Autowired
    public setUserRepository(UserRepository userRepository){
    	this.userRepository = userRepository;
    }
    @Autowired
    public setMemberService(MemberService memberService){
    	this.memberService = memberService;
    }
}

@Autowired로 주입할 대상 없는 경우(빈에 존재하지 않는 경우) 오류 발생
주입 대상 없도록 하려면 @Autowired(required = falase) 설정 가능

  1. 필드 주입
@Service
public class UserServiceImpl implements UserService{
	@Autowired
    private UserRepository userRepository;
    @Autowired
    private MemberService memberService;
}

외부에서 변경이 불가능
테스트 코드에서 Mock데이터 사용해 주입하면 안됨
어플리케이션의 작동과 무관한 테스트 코드나 설정을 위해서만 사용

장점

  • 두 객체 간의 관계라는 관심사 분리
  • 두 객체 간의 결합도 낮춤
  • 객체의 유연성 높임
  • 테스트 작성을 용이하게 함

Spring MVC 예외처리

API 예외 처리 응답방식

스프링 부트는 BasicErrorController 가 제공하는 기본 정보들을 활용하여 API를 생성해준다.

HandlerExceptionResolver 예외처리

예외가 발생해서 WAS까지 가는 순간 상태 코드는 무조건 500이다.
하지만 예외종류는 많고 400, 404 등 다른 상태코드도 전달하고 싶다.

HandlerExceptionResolver를 통해 동작 방식을 변경할 수 있다.

구현해보자

public MyHandlerExceptionResolver implements HandlerExceptionResolver{
	public ModelAndView resolverException(HttpServletRequest request,
    		HttpServletResponse response, Object handler, Exception ex){
    	try{
        	if(ex instanceof IllegalArgumentException) {
            	log.info("IllegalArgumentException resolver to 400");
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
                return new ModelAndView();
            }
        }catch (IOException e) {
        	log.error("resolver ex", e);
        }
     return null;
    }
}

반환타입에 따른 처리

  • ModelAndView 반환
    • 빈 모델인 경우 : 뷰를 렌더링 하지 않고 정상흐름으로 반환
    • 정보 있는 경우 : 뷰를 렌더링
  • NULL : 다음 ExceptionResolver를 찾아 실행
    • 처리가 안되면 기존 발생한 예외를 서블릿 밖으로 보냄

ExceptionResolver 동작 방식

  1. 요청이 들어오면 preHandler 호출
  2. Handler Adapter 호출
  3. Controller 호출 -- 여기서 예외 발생
  4. 예외 전달
  5. ExceptionResolver가 Exception을 가로채어 해결 시도한다.
  6. 해결하면 알맞은 View를 호출
  7. afterCompletion을 호출
  8. ExceptionResolver가 예외를 해결 했기 때문에 200 응답으로 나감

등록

단순 구현이 아니라 등록 해줘야 한다.

@Configuration
public class WebConfig implements WebMvcCongfigurer{
	// Exception Resolver 등록
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    	resolvers.add(new MyHandlerException());
    }
}

예제

public class TestHandlerException implements HandlerExceptionResolver{
	private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public ModelAndView resolveEx(HttpServletRequest request,
    		HttpServletResponse response, Object handler, Exception ex){
     	if(ex instanceof TestExcpetion){
        	String acceptHeader = request.getHeader("accept");
            response.setStatus(HttpServletResponse.SC_FORBBIDDEN);
            
            if("application/json".equals(acceptHeader)){
            	Map<String, Object> errorResult = new HashMap<>();
                errorResult.put("예외 종류", ex.getClass());
                errorResult.put("예외메세지", ex.getMessage());
                String result = null;
                
                try{
                	result = objectMapper.writeValueAsString(errorResult);
                    response.setContentType("application/json");
                    response.setCharacterEncoding("utf-8");
                    response.getWriter().write(result);
                    return new ModelAndView();
                }catch (JsonProccessingException e) {
                	e.printStackTrace();
                }catch (IOException e) {
                	e.printStackTrace();
                }
            }else {
            	new ModelAndView("/500.html");
            }
        }
        return null;
    }
}
  • accept가 json일 경우 json 형식으로 보내주고 그렇지 않으면 HTML 화면으로 이동
  • 중요한 건 response.setStatus이다
    htttp status를 지정해 주지 않으면 200으로 나가기 때문에 여기서 변경해줘야 한다.

@ControllerAdvice

@ExceptionHandler가 하나의 클래스에 대한 것이면, @ControllerAdvice는 모든 @Controller 즉, 전역에서 발생하는 예외를 처리하는 어노테이션이다.

@RestControllerAdvice
public class Myadvice{
	@ExceptionHandler(NullPointException.class)
    public Object nullex(Exception e){
    	System.out.println(e.getClass());
    	return "hi";
    }
}

먼저 @RestControllerAdvice로 클래스를 만들고
그 이후 @ExceptionHandler로 처리하고 싶은 예외를 잡아 처리하면 된다.

이러면 모든 패키지 전역에 있는 컨트롤러를 다 잡아준다.

💡@RestControllerAdvice@ControllerAdvice와 동일한 역할을 하지만 @ResponseBody를 통해 객체를 리턴할 수도 있다
✏️ @ExceptionHandler({NullPointException.class, CustomException.class}) 이렇게 2개 이상도 사용 가능, NullPointException이 발생하면 @ExceptionHandler(NullPointException.class) 메소드 호출

Controller 내에서의 @ExceptionHandler사용

참고
의존성 주입 자료
Spring MVC 예외처리

profile
개발자가 되고 싶은 취준생

1개의 댓글

comment-user-thumbnail
2023년 7월 20일

아주 유익한 내용이네요!

답글 달기