[뉴렉처 6기] SPRING│forward와 return│ModelAndView와 Model│인터페이스 Model
목차
💙 들어가며
스프링하면 떠오르는 것은 Annotation
어떤 주석을 어떻게 적재적소에 사용할 수 있을지에 대해서 잘 알아두자.
이제 우리의 front controller는 스프링이다.
✏️ 학습내용 정리
#향상된 사용자 입력
이전에 우리가 했던 방식은
쿼리스트링을 받기 위해서
request를 위임하고
request를 통해서 getParameter를 해야
값을 뽑아낼 수 있는 방법이었다.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
하지만 이제는 SPRING이
우리의 Front Controller가 되어서
request와 response를 다 처리해주기 때문에(전달)
Controller에서 완전한 POJO처럼 입력 받기가 가능하다.
즉, request가 없이도
바로 쿼리 스트링 값을 매개변수로 받아올 수 있게 되었다.
출처: 뉴렉처(https://www.youtube.com/@newlec1)
#forward과 return
💡 우리의 프론트 컨트롤러는 SPRING (디스패처 서블릿이 중간에서 포워딩의 역할을 한다.)
이제는 SPRING이 forward를 해줄 것이기 때문에 POJO Controller들은 디스패처 서블릿(스프링에서 구현한 프론트 컨트롤러)에게 값을 return해주는 방식으로 만든다.
(디스패처 서블릿이 POJO Controller를 통해 받은 return값을 이용해서 jsp(View)에게 값을 포워딩을 해주기 때문에 일반 POJO Controller들은 더 이상 forward을 할 필요가 없게 되었다.) 출처: 뉴렉처(https://www.youtube.com/@newlec1)
이전에 서블릿을 사용했을 때에는
별도로 request와 response를 모두 전달해줘야 했다.
하지만 SPRING을 사용하게 되면서
별도로 전달할 필요가 없어졌다.
순수한 자바객체처럼
return으로 전달하고 싶은 것들만 반환해주면 된다.
코드로 확인해보자.
서블릿 시절 forward로 request와 response를 전달해서
MVC 모델2를 구현했던 모습이다.
💡 (서블릿 시절) forward를 사용해서 req, resp 전달
@WebServlet("/menu/list") //context 폴더명 (실제경로 아님, 그래서 확장자명 쓰지 않는다.)
public class ListController2 extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//request로 ServletContext 저장소 얻어오기
ServletContext application = req.getServletContext();
//ServletContext로
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) application.getAttribute("sqlSessionFactory");
SqlSession sqlSession = sqlSessionFactory.openSession();
MenuRepository repository = sqlSession.getMapper(MenuRepository.class);
List<Menu> list = repository.findAll();
//pageContext는 하나의 페이지 안에서만 사용할 수 있음
//즉 서블릿이 나눠지면 공유안됨 (JSP와 서블릿으로 나눠지면 pageContext 하나로 공유 불가)
//이런 점을 보완하기 위해 필요한 둘 사이에서만 공유할 수 있는 저장소: request이다.
req.setAttribute("list", list);
//포워딩은 모든 웹 플랫폼이 가지고 있는 기능이다.
//나중에 플랫폼, 언어가 달라지더라도 개념, 만드는 방법, 필요한 재료 등은 다 똑같다.
//(플랫폼이나 언어에 종속될 필요가 없는 이유)
//forward를 해서 req, resp를 보내주면 다른 서블릿에서 이 두 가지를 공유할 수 있다.
req.getRequestDispatcher("/WEB-INF/view/menu/list.jsp").forward(req, resp); //실제경로
}
}
스프링으로 오면서 직접 전달해야 할 request가 사라지고
ModelAndView라는 객체를 사용해서
필요한 값들을 담고 객체를 반환하는 방식으로 바뀌었다.
💡 (SPRING 입문) return을 이용해서 ModelAndView 객체 반환
//POJO는 서블릿이 아니기 때문에
//더 이상 @WebServlet으로 매핑하지 않는다.
public class ListController implements Controller{
//반환타입이 ModelAndView이다.
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ServletContext application = request.getServletContext();
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) application.getAttribute("sqlSessionFactory");
SqlSession sqlSession = sqlSessionFactory.openSession();
MenuRepository repository = sqlSession.getMapper(MenuRepository.class);
List<Menu> list = repository.findAll();
ModelAndView mv = new ModelAndView();
mv.setViewName("/WEB-INF/view/menu/list.jsp");
mv.addObject("list", list);
//★★★return이 forwarding이다!★★★
return mv;
}//handleRequest ends
}//class ends
#ModelAndView와 Model
앞에서 ModelAndView를 이용해서
return으로 출력할 데이터(Model)을 전달했다.
사실 ModelAndView 외에도 출력할 데이터(Model)를
View로 전달하는 방법이 하나 더 있다.
바로 Model, 스프링에서 자체적으로 가지고 있는 객체이다.
앞으로 출력 데이터를 전달할 때에는 Model을 사용할 것이다.
둘의 차이점에 대해서만 간략하게 알아놓자!
💡 ModelAndView vs Model
둘 다 스프링 프레임워크에서 웹 애플리케이션의 데이터 전달과 뷰(View)의 선택을 위해 사용되는 객체이다.
이 두 객체는 주로 스프링 MVC(Controller)에서 사용되며, 웹 요청을 처리한 후에 클라이언트에게 응답을 보낼 때 데이터와 뷰 정보를 담고 전달한다. 언제 어떤 것을 사용할지는 프로젝트 요구사항과 개발자의 선호도에 따라 다를 수 있다.
1. ModelAndView
- ModelAndView는 모델 데이터와 뷰 정보를 함께 설정하고 제어하려는 경우에 사용된다. - 특정 컨트롤러 메서드에서 모델 데이터와 뷰 이름을 명시적으로 설정하고자 할 때 유용하다. - 다수의 모델 데이터 객체를 함께 전달하거나, 뷰 이름을 동적으로 변경해야 하는 경우에 사용할 수 있다. - return타입 자체가 ModelAndView이고 return을 ModelAndView의 인스턴스로 한다. 출처: 챗지피티(https://chat.openai.com/) 2. Model
- Model은 주로 간단한 컨트롤러 메서드에서 사용된다. - 컨트롤러 메서드에서 데이터를 모델에 추가하고, 뷰 이름은 스프링이 자동으로 결정하도록 한다. - 코드가 간결하고 읽기 쉽다. - 스프링이 이미 객체를 가지고 있기 때문에 return타입은 String이 된다. (경로를 적어줄 것이기 때문) - 또한 Model은 파라미터 형태로 들어간다. 출처: 챗지피티(https://chat.openai.com/)
#인터페이스 Model
출력 데이터(Model)를 전달 받아야지만
View단에서 데이터를 출력할 수 있는데,
이제 Model이라는 인터페이스를 사용해서 처리한다.
💡 Model을 참조형으로 한 매개변수 받기
// @ResponseBody //얘 쓰면 얘가 몸통이니 그대로 출력하라는 뜻, 지우면 view라는 뜻
@RequestMapping("list")
//Model 자료형의 객체를 스프링이 가지고 있어서 우리가 view에 따로 전달해줄 필요도 없음
//그냥 매개변수로 Model 참조시키면 알아서 출력변수까지 만들어서 전달해줌
public String list(Model model, HttpServletRequest request) {
// 이렇게 하면 역할분리가 되지 않음 (Dao를 Controller에서 부르는 상황이 발생)
ServletContext application = request.getServletContext();
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) application.getAttribute("sqlSessionFactory");
SqlSession sqlSession = sqlSessionFactory.openSession();
MenuRepository repository = sqlSession.getMapper(MenuRepository.class);
List<Menu> list = repository.findAll();
//★★★ 우리는 이제 mv가 아니라 model 쓸거임 ★★★
// ModelAndView mv = new ModelAndView();
// mv.setViewName("/WEB-INF/view/menu/list.jsp");
// mv.addObject("list", list);
model.addAttribute("list", list);
model.addAttribute("test", "Test임!!!");
return "/WEB-INF/view/menu/list.jsp";
}//list ends
Model은 인터페이스인데,
매개변수로 받아서 처리하는 것이 신기하다.
어떻게 인터페이스인데 객체처럼 사용되는 것일까?
사실은 Model을 사용하는 것처럼 보이지만
Model을 구현한 ExtendedModelMap이라는 클래스가 사용되고 있는 것이다.
#IoC 컨테이너에 SqlSessionFactory 담기
Repository를 컨트롤러에서 만든다?
Dao는 서비스에서 다뤄져야 한다.
IoC 컨테이너는 모든 DI를 위한 모든 객체들을 공유할 수 있는 저장소이다.
SqlSessionFactory를 ServletContext라는 서블릿끼리만 공유하는 저장소 말고
IoC 컨테이너에 담아서 사용하자!
외부라이브러리이기 때문에
@Configuration이라는 어노테이션을 사용하여 클래스를 객체화하고
클래스 내 메소드에는 @Bean이라는 어노테이션을 붙인다.
💡 MyBatis용 config.java파일을 만들자.
/*
* 외부 라이브러리의 경우 내가 직접 소스코드에 접근할 수가 없음
* 그래서 소스코드가 없는 녀석을 콩 보따리에 담아달라고 요청할 것임
*
*/
//Bean객체를 만들기 위해서는 상위 클래스도 객체가 되어야 한다.
//Config와 관련된 것이기 때문에 주석을 Component가 아니라 Configuration이라고 특화된 이름으로 지정한다.
@Configuration
public class MybatisConfig {
//SqlSessionFactory를 IoC 컨테이너에 담는 과정이다.
//외부 라이브러리를 가져오는 것이기 때문에 별도로 @Bean으로 객체를 만들어줘야 한다.
@Bean
SqlSessionFactory sqlSessionFctory() {
String resource = "mybatis-config.xml";
InputStream inputStream;
SqlSessionFactory sqlSessionFactory = null;
try {
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("마이바티스 세션 팩토리 생성과 콩자루에 담기");
return sqlSessionFactory;
}
}// class ends
#(흐름)Mapper→Repository→Service→Controller
그렇다면 전체적인 흐름은 다음과 같이 되겠다.
1. Mapper와 Repository
레파지토리 구현을 위한 클래스를 만들고,
각 함수에서 매퍼를 추가해서 DAO함수를 불러온다.
(SqlSessionFactory는 공유되고 있으나
getMapper를 해주기 위해서는 각각 사용해야 하는 상황 발생)
💡 SqlSessinoFactory의 DI는 field에 한다!
생성자보다 어노테이션이 늦게 실행되기 때문에 생성자에서 SqlSession을 만들 수는 없다. 따라서 Field에서 DI한다.
💡 .getMapper는 각 함수마다 추가해주어야 한다.
@Repository
public class MbMenuRepository implements MenuRepository {
/* ----- Member Varialbes ----- */
//생성자보다 어노테이션이 늦게 실행되기 때문에 생성자에서 SqlSession을 만들 수는 없다.
//Field에서 DI한다.
//(아직 연결 안돼서 객체 없는데 어케 만듬!)
@Autowired
private SqlSessionFactory sqlSessionFactory;
/* ----- Methods ----- */
//DAO함수를 사용해야 하는 메소드마다
//getMapper를 해주기위해 SqlSession을 열어준다.
@Override
public List<Menu> findAll() {
SqlSession sqlSession = sqlSessionFactory.openSession();
MenuRepository repository = sqlSession.getMapper(MenuRepository.class);
List<Menu> list = repository.findAll();
return list;
}
@Override
public int count() {
// TODO Auto-generated method stub
return 0;
}
}//class ends