origoni's Blog from Millky

origoni의 스프링 블로그 입니다.

[블로그개발_14] 스프링 WebMvc 설정 (Interceptor, ArgumentResolver)

블로그개발 시리즈 - 다른글 : http://millky.com/@origoni/folder/30/post/list

라이브 데모 : http://blog.millky.com/post/list

자바 웹 개발 시작하기 : http://www.slideshare.net/origoni/presentations




프로젝트가 끝나고 안정화가 되었다. 일정을 맞추느라 마지막 열을쯤 안하던 야근을 하니 몸이 ㅠㅠ

... 오래간만에 글을 적어본다.


오늘은 컨트롤러 도우미 둘을 사용해 보려고 한다.

지난시간 컨트롤러를 보면.. 사용자 정보를 가지고 오기위해 매우 복잡해 졌다.


https://github.com/origoni/Spring-Blog/blob/v0.0.13/src/main/java/com/millky/blog/presentation/controller/PostController.java


컨트롤러에 사용자 정보를 가지고 오는 메서드가 추가되고..

private User getConnect() {
	Connection<Facebook> connection = connectionRepository.findPrimaryConnection(Facebook.class);
	if (connection == null) {
		return null;
	}
	ConnectionData data = connection.createData();
	return new User(data.getProviderUserId(), data.getDisplayName());
}


사용한는 곳마다 실행을 시켰다.

@RequestMapping(value = "/write", method = RequestMethod.POST)
public String write(@Valid Post post, BindingResult bindingResult) {
	User user = getConnect();

	...
}


그리고 뷰단에 내려주기 위해.


@RequestMapping("/{id}")
public String view(Model model, @PathVariable int id) {
	...

	User user = getConnect();
	model.addAttribute("user", user);

	return "post/post";
}

이런식의 작업도 하였다.


뭐 한둘 두줄 늘어나는것이긴 하지만. 이런것들이 모이면 점점 복잡도가 증가한다.

스프링에서는 아주 깔끔하게 뺄 수 있는 방법이 있다.




우선은 HandlerInterceptor 확인해보자.


HandlerInterceptor 는 컨트롤러 실행 전후에 공통적으로 해야 하는 작업을 처리 해준다.

인터페이스는 요렇게 생겼다.





우리는 preHandle 만 사용할 예정이어서 HandlerInterceptorAdapter 를 사용하였다.

https://github.com/origoni/Spring-Blog/blob/v0.0.14/src/main/java/com/millky/blog/application/aop/UserSessionInterceptor.java


package com.millky.blog.application.aop;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.facebook.api.Facebook;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.millky.blog.domain.model.UserSession;

public class UserSessionInterceptor extends HandlerInterceptorAdapter {

	private ConnectionRepository connectionRepository;

	public UserSessionInterceptor(ConnectionRepository connectionRepository) {
		this.connectionRepository = connectionRepository;
	}

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

		Connection<Facebook> connection = connectionRepository.findPrimaryConnection(Facebook.class);

		if (connection != null) {
			ConnectionData data = connection.createData();

			request.setAttribute("user",
					new UserSession(data.getProviderUserId(), data.getImageUrl(), data.getDisplayName()));
		}

		return true;
	}
}


간단하게 살펴보면.

리퀘스트 요청이 있을때마다. 사용자 정보를 가지고 와서 있으면 user에 넣어준다. (음 우선 호환성을 위해 user 를 그냥 사용하였고. 다음번에 가독성을 높이기 위해 변경해보자.)


이전의 

User user = getConnect();
model.addAttribute("user", user);

를 대신하는 부분이다.


아직 컨트롤러가 많이 않아서 느끼기 힘들수도 있지만. 잘 활용하면 많은 부분은 줄여준다.




다음으로 ArgumentResolver를 확인해 보자.


뷰에서도 사용자 정보가 필요하지만 컨트롤러에서도 사용할 일이 많다.

가끔... HandlerInterceptor 에서 뷰에 내려준 정보를 이용해서... 사용자 정보등을 넣으시는 분이 계신데. 절대로.. 절대로 뷰에서 넘어오는 값은 믿어서는 안된다.

get 요청이야. 원래 조작하라고 있는거소. post 도 정말 간단한 툴만 가지고 조작이 가능하다.

(그래서... 글 쓸때 entity 바로 담아서 오지 말고... command 같은것을... 나중에 만들어 보겠다.)





HandlerMethodArgumentResolver 를 구현하여 만들었다.


https://github.com/origoni/Spring-Blog/blob/v0.0.14/src/main/java/com/millky/blog/application/aop/UserSessionArgumentResolver.java


package com.millky.blog.application.aop;

import org.springframework.core.MethodParameter;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.facebook.api.Facebook;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.millky.blog.domain.model.UserSession;

public class UserSessionArgumentResolver implements HandlerMethodArgumentResolver {

	private ConnectionRepository connectionRepository;

	public UserSessionArgumentResolver(ConnectionRepository connectionRepository) {
		this.connectionRepository = connectionRepository;
	}

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return UserSession.class.isAssignableFrom(parameter.getParameterType());
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		Connection<Facebook> connection = connectionRepository.findPrimaryConnection(Facebook.class);
		if (connection == null) {
			return null;
		}
		ConnectionData data = connection.createData();
		return new UserSession(data.getProviderUserId(), data.getImageUrl(), data.getDisplayName());
	}
}


두개의 매서드가 있는데.

supportsParameter 는 파라미터를 확인해서 Resolver 적용 가능한지 확인을 한다. (한번 확인된 것은 캐시 한다. HandlerMethodArgumentResolverComposite 요기 참고)

resolveArgument 는 지원되는 파라미터가 들어오면 작업을 하여 생성된 객체를 리턴 한다.


뭐 말이 써놓고 보니 어려운데.

간단하게 말하면. 파라미터로 들어온것중 UserSession 가 있으면.. connection 정보를 가지고 와서 new UserSession(...) 이렇게 생성해서 리턴한다.


글쓰기 부분을 보자.


https://github.com/origoni/Spring-Blog/blob/v0.0.14/src/main/java/com/millky/blog/presentation/controller/PostController.java


@RequestMapping(value = "/write", method = RequestMethod.POST)
public String write(@Valid Post post, BindingResult bindingResult, UserSession user) {

	if (bindingResult.hasErrors()) {
		return "post/form";
	}

	post.setRegDate(LocalDateTime.now());
	post.setUserId(user.getProviderUserId());
	post.setName(user.getDisplayName());
	return "redirect:/post/" + postDao.save(post).getId();
}


이전에 


User user = getConnect();

이런식으로 사용자 정보를 가지고 오던 부분이


write(..., UserSession user)

이렇게 가지고 올 수 있게 되었다.

UserSession은 UserSessionArgumentResolver에 정의 하였기에 바로 사용할 수 있다.


우리는 사용자 정보 넣은것 하나 만들었지만 이미 만들어진 많은 ArgumentResolver 들이 있다.




컨트롤러가 많이 깔끔해 짐을 느낄 수 있다.

(아.. 아직 뭔가 너무 많다. 점점 이동할 예정이니 기다려 보자 ^^;)


그리고. 이 두 클래스의 패키지 위치가 ...aop 인것도... 무시하자.

필터라고 해야 하나, 인터셉터라고 해야하나.. 뷰(presentation/support)에 넣어야 하나 아직도 고민이 많다.

(사실 a로 시작해서 가장 위로 올라가서 했다... 는 아니다. ^^; 고민 중이다.)



마지막으로 설정 부분을 보자.


WebMvcConfigurerAdapter 를 상속받아 만들었다.


https://github.com/origoni/Spring-Blog/blob/v0.0.14/src/main/java/com/millky/blog/application/configuration/WebMvcConfig.java


package com.millky.blog.application.configuration;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.millky.blog.application.aop.UserSessionArgumentResolver;
import com.millky.blog.application.aop.UserSessionInterceptor;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

	@Autowired
	private ConnectionRepository connectionRepository;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new UserSessionInterceptor(connectionRepository));
	}

	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		argumentResolvers.add(new UserSessionArgumentResolver(connectionRepository));
	}
}


역시 Adapter 라서 다른 많은 설정들이 있지만 일단 addInterceptors, addArgumentResolvers 만 사용한다.

connectionRepository를 여기서 주입해 주고 있다는 점을 확인 하자.



오늘은 여기까지..



오래간만에 글을 쓰려니... 뭔가 좀 감을 잃은 느낌이다 ㅠ

개인적인 일이 조금 있어 피곤하기도 하도...

아까 커밋할때 깃헙도 꼬여서;;; 지금 뭔가가 이상하다. 내일 다시 봐야지 ㅠㅠ



다음시간에는 카테고리 기능을 넣어 볼까 생각 중이다.



감사합니다.



오늘 작업한 코드는 https://github.com/origoni/Spring-Blog/tree/v0.0.14 여기를 참고 부탁드린다.


라이브 데모 : http://blog.millky.com/post/list

변경사항은 : https://github.com/origoni/Spring-Blog/commit/

깃헙 : https://github.com/origoni/Spring-Blog

프로젝트 다운받아 시작하는 방법은(for Windows User)? http://millky.com/@origoni/post/1145

블로그개발 시리즈 - 다른글 : http://millky.com/@origoni/folder/30/post/list

역순 정렬도 있어요.. : http://millky.com/@origoni/folder/30/post/list?sort=regDate,asc

자바 웹 개발 시작하기 : http://www.slideshare.net/origoni/presentations


back to top