JSP

[JSP]MVC Pattern 정리(1)

siyuning 2022. 6. 16. 09:30

Jsp MVC패턴을 공부하면서 전체적인 흐름에 대해 공부한 것을 정리해보았다 !

[ 참고 ]

  • 톰캣은 켜자마자 Servers 프로젝트안에 환경변수 문서부터 읽는다.
  • *현재 MVC파일에서*
    web.xml에서 Servlet은 2개다(main과 member 있기 때문)
    Servlet 태그가 2개라는 것은? DispatcherServlet의 객체가 2개라는 것 !
    그리고 init()가 각각 순서대로 2번 호출된다 !!
    또 ApplicationContext 객체는 2개

  • DispatcherServlet가 가지고 있는 메서드?
    처음 init() 실행 → 요청이 오면 doGet() [ 또는 doPost() ] → 마지막에 사라지면 destroy() 실행

  • 톰캣 켰을 때 init() 호출되게 하려면? web.xml에서 load-on-startup을 양수로 설정하기

  • ServletConfig config는 web.xml 문서에서 init-param 데려옴
    ServletContext application은 context-param 데려옴
#web.xml
<servlet>
    <servlet-name>Test1</servlet-name>
    <servlet-class>a.b.Test1</servlet-class>
    <init-param>
      <param-name>abc</param-name>
      <param-value>def</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
----------------------------------------------------------------
ex) Test1의 init메서드에서 def 출력하는 문장
public class Test1 extends HttpServlet{
@Override
public void init(ServletConfig config) throws ServletException{
System.out.println(config.getInitParameter("abc"));
    } //def
}

💡application.getRealPath
절대경로를 얻어온다. 톰캣버전이 바뀌어도 경로를 구해주기 때문에 유지보수에 좋다!

http://localhost:8282/ABC/a.jsp

<%=application.getRealPath("/def/a.txt") %>

실행하면 ? —> c:/tomcat/webapps/ABC/def/a.txt

// '/'를 적으면 프로젝트명 전까지 절대경로를 얻어온다
#web.xml
<servlet>
    <servlet-name>main</servlet-name>
    <servlet-class>kr.co.seoul.common.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>configFile</param-name>
      <param-value>/WEB-INF/config/main.properties</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
-----------------------------------------------------------------------------------
#DispatcherServlet.java
public class DispatcherServlet extends HttpServlet{
@Override
    String fn=config.getInitParameter("configFile"); //param-name의 param-value얻어옴
    String aPath=application.getRealPath(fn); //절대 경로, 리턴타입 : 문자열
    FileInputStream fis=new FileInputStream(aPath);
    }
}
/*init()에서 FileInputStream사용하면, FileNotFoundException이 발생함 
→ application.getRealPath 사용하기*/

⚡톰캣을 키면 → sever.xml문서 읽어들이고 → 각각의 프로젝트 web.xml문서 읽어서 → web.xml의 DispatcherServlet같은 것을 객체로 만들어야함(메모리에 올려야함) → DispatcherServlet 객체를 2개 만드는데 init()가 자동으로 2번 호출된다.(load-on-startup은 양수로 1, 2로 해놨으니까)

 

*현재 main.properties, member.properties가 있음

main.properties
member.properties

Map<String,Object> map
map.put("urlFilenameViewController",new kr.co.seoul.common.servlet.mvc.UrlFilenameViewController);
//맵 객체가 2개 : properties가 2개니까

public void init(ServletConfig config) throws ServletException (
    String fn=config.getlnitParameter ("configFile");
    System.out.println(fn); // WEB-INF/config/main.properties
    String aPath=application.getRealPath(fn);
    FilelnputStream fis=new FilelnputStream(aPath);
    //here
--------------------------------------------------------------------
Map<String,Object> map
map.put(키,new 클래스명());

public void init(ServletConfig config) throws ServletException {
    String fn=config.getlnitParameter("configFile");
    System.out.printin(fn); // WEB-INF/config/memeber.properties
    String aPath=application.getRealPath(fn);
    FilelnputStream fis=new FilelnputStream(aPath);
    //here

//🤔그런데! init메서드에 이렇게 다 적으면 좋지않다. 
//main.properties, memeber.properties을 대신 읽어주는 클래스가 있다

public class DispatcherServlet extends HttpServlet{
    ApplicationContext applicationContext;
    /*main.properties, member.properties를 불러오는
    applicationContext 멤버변수 등록*/
    public void init(ServletConfig config) throws ServletException{
        ServletContext application=this.getServletContext(); //context-param 데려옴
        applicationContext=new ApplicationContext();
        //init()에 ApplicationContext 객체 생성
        applicationContext.메서드(config,application);
        //config와 application 전달
--------------------------------------------------------------------
public class ApplicationContext{
    Map<String,Object> map; //Map에 등록
    메서드(ServletConfig config, ServletContext application)
        //config와 application 받음
        String fn=config.getlnitParameter("configFile");
        String aPath=application.getRealPath(fn);
        FilelnputStream fis=new FilelnputStream(aPath);
        Properties p=new.... //등록하는 작업
        p.load(fis);
            for(String key : p.stringPropertyNames()){ //키 값 가져오고
                String cn=p.getProperty(key); //키를 주면 클래스 네임 얻어옴
                map.put(key, Class.forName(cn).newlnstance() );
                //클래스 이름을 주고 newlnstance로 객체 생성
        }
    }
}

                

  • 톰캣을 키면 HDD에 있는 properties를 메모리에 올리는 작업을 함. 이것들의 위치를 알려줘야함. 그 위치는 web.xml 문서에 있음(이렇게 하는 것이 유지보수상 좋음, 만약 java안에 있으면 경로가 바뀌면 수정해야하는 번거로움)
  • main.properties, member.properties를 불러오는 것은 applicationContext → 그래서 멤버변수에 등록되어있다 → ApplicationContext는 init()에서 객체 생성하면된다 → Map에 등록하기 → 등록하는 작업해야함
  • 클래스 이름 2개  기억하기: DispatcherServlet, ApplicationContext
  • main(DispatcherServlet) , ApplicationContext(main.properties)
  • member(DispatcherServlet) , ApplicationContext(member.properties)

  • 즉, properties파일은 Map이다
/menu.html=urIFilenameViewController
/member/list.do=memberListController
/emp/list.do=empListController
/item/list.do=itemListController
/error.html=urlFilenameViewController

=> 이렇게 실행한다
http://localhost:8282/MVC1/menu.html
http://localhost:8282/MVC1/member/list.do
http://localhost.8282/MVC1/emp/list.do
<servlet-name>main</servlet-name>
<url-pattern>*.do</url-pattern>

<servlet-name>main</servlet-name>
<url-pattern>*.html</url-pattern>

<servlet-name>member</servlet-name>
<url-pattern>/member/*</url-pattern>
--------------------------------------------------------------------
http://localhost:8282/MVC1/menu.html   //카테고리 없다
http://localhost:8282/MVC1/member/list.do   //카테고리 있다

//서블릿이 메인과 멤버 각각 2개
//디스패처서블릿이 객체가 각각 2개 - 위, 아래는 다른 것임
//맵 객체도 2개 있다
<servlet-name>main</servlet-name>
<url-pattern>*.html</url-pattern>

<servlet-name>member</servlet-name>
<url-pattern>/member/*</url-pattern>
--------------------------------------------------------------------------
/*main.properties(Map<String,Object>), member.properties(Map<String,Object>)
아래와 같이 요청했을 때 어떤 클래스의 메서드가 호출됩니까?

http://localhost:8282/MVC1/menu.html
DispatcherServlet(doGet메서드)     main.properties

아래와 같이 요청했을 때 어떤 클래스의 메서드가 호출됩니까?
http://localhost.8282/MVC1/member/list.do
DispatcherServlet(doGet메서드)     member.*/

  • context-param은 모든 서블릿이 공유하는 것 (ServletContext application으로 얻어옴)
    유지보수를 카테고리별로 하려면 init-param에 등록해야함 (ServletConfig config으로 얻어옴)
  • urlmapping.properties는 카테고리 구분되어 있지않음
DispatcherServlet 멤버변수
    private ApplicationContext( member.properties, main.properties )
    private SimpleUrlHandlerMapping ( urlmapping.properties )

public class DispatcherServlet extends HttpServlet{
        @Override
        public void init(ServletConfig config){
            super(config);
            ServletContext.application=this.getServletContext();
            applicationContext=new ApplicationContext(application,config);
            simpleUrlHandlerMapping=SimpleUrIHandlerMapping.getInstance(application);
  • main.properties, member.properties 2개니까 init()가 각각 호출되는데, new ApplicationContext(), new SimpleUrIHandlerMapping()으로 로직을 짜면 객체 2개 만들어지니까 1개 만들기 위해 싱글톤패턴으로 만듬 !

  • application Context.메서드(application,config); 이런식으로 로직짜지 않고 생성자에 전달해서 메서드 호출하지 않음(리팩토링)

  • SimpleUrIHandlerMapping은 urlmapping.properties를 얻어올건데, 이것은 web.xml에 세팅 되어있고, context-param임. 얻어오려면 application있으니까 전달함

    => 톰캣켰을 때 일어나는 일들..

1️⃣ 톰캣이 켜지면 properties파일들을 메모리에 로드하는 일이 일어난다.

DispatcherServlet 멤버변수
        private ApplicationContext( member.properties, main.properties )
        private SimpleUrlHandlerMapping ( urlmapping.properties )
        private InternalResourceViewResolver ( path.properties )

⚡3개의 멤버변수는 각각()안에 있는 것 들고있다.
⚡DispatcherServlet은 멤버변수로 private하게 3개를 가지고 있다.

⚡톰캣 켰을 때 init()에서 이 클래스들을 객체 생성한다 -> properties파일이 Map형태로 메모리에 로드된다.

⚡path.properties 가지고 있는 것는 InternalResourceViewResolver(화면처리자)

2️⃣ 요청하기

아래와 같이 요청하면 자동으로 실행되는 메서드는 무엇인가요? doGet
http://localhost:8282/MVC1/menu.html
http://localhost:8282/MVC1/member/list.do
http://localhost:8282/MVC1/error.html

⚡doGet에서 request, response 매개변수로 받아온다.
a.jsp를 만들면 -> 자동으로 톰캣엔진이 a_jsp.java만듬 -> 이걸 컴파일하면 a_jsp.class(톰캣이 자동으로 만든 서블릿 클래스) 생김,
a_jsp.class가 _jspService()메서드 가지고 있음, _jspService()은 매개변수로 request, response 있음

⚡bean객체는 controller다.

⚡bean객체가 등록되어 있는 Map은 총 4개인데, 어떤 클래스의 멤버변수인가?
http://localhost:8282/MVC1/menu.html 이렇게 요청했을 때 ? ApplicationContext

 

- DispatcherServlet

- ApplicationContext(main.properties)

- SimipleUrlHandlerMapping

- InternalResourceViewResolver
http://localhost:8282/MVC1/member.html 이렇게 요청했을 때? ApplicationContext(member.properties)


⚡controller는 interface, 즉 handleRequest은 추상메서드

⚡urlmapping에 이름 등록되어 있다. ( /menu.html ), 그 뒤에 있는 것은 bean의 이름 (urlFilenameViewController )


💡1. url-path에서 url이름을 얻은 뒤 simpleUrlHandlerMapping에서 빈이름을 얻습니다.
💡2. bean객체 얻기 applicationContext에서 빈이름을 주면 빈객체를 얻어올 수 있습니다.

http://localhost:8282/MVC1/menu.html 요청했을 때
=> 이렇게 요청했을 때 DispatcherServlet의 doGet() 호출
=> /menu.html 이름을 얻어야함 -> bean 이름을 얻을 수 있음 -> bean 객체를 얻을 수 있음

public class DispatcherServlet extends HttpServlet{
    public void doGet(request,response){
        Controller controller=simpleUrlHandlerMapping.getController();
        //controller가 bean객체
        String url=request.getRequestURI();
        System.out.println(uri);//     /MVC1/menu.html : url을 얻어와야함, 프로젝트 이름부터 싹 가져옴
        String contextPath=request.getContextPath();
        System.out.println(contextPath);//     /MVC1 : Context경로 얻기(프로젝트명)
        String name=url.substring(contextPath.length());
       	System.out.println(name);//     /menu.html
    }
}

// /menu.html 을 urlmapping.properties가 가지고 있음.
// simpleUrlHandlerMapping에서 getController객체를 하나 만든다.

3️⃣ http://localhost:8282/MVC1/menu.html 이라고 요청했을 때 !

public class DispatcherServlet extends HttpServlet{
    private ApplicationContext applicationContext;
    private SimipleUrlHandlerMapping simipleUrlHandlerMapping;
    private InternalResourceViewResolver internalResourceViewResolver;
    public void doGet(request,response){    

        1. bean객체얻기
        Controller controller=simpleUrlHandlerMapping
                    .getController(request,applicationContext);
//bean객체 가지고 있는 것 applicationContext인데 simpleUrlHandlerMapping 먼저 호출 이유? 
//bean객체의 이름이 없고 url주소만 있어서
//applicationContext주소 전달

//💡즉 !! main.properties에서 bean의 이름 urlFilenameViewController은 어떤 객체다?
//urlFilenameViewController다, 객체 주소를 얻어옴~

        2.
        ModelAndView mav=controller.handleRequest(request,response);
//Controller는 interface, bean이 가진 추상메서드 handleRequest(요청처리)
//controller.handleRequest의 리턴형 ModelAndView

        3. 
        internalResourceViewResolver.resolveView(mav, request, response);
//화면해결자 : 화면처리
//ModelAndView 받아올꺼니까 mav, forward할거니까 request, response 전달

//forward한다는 것? (request주소값이 다음 페이지에 연결된다)
//즉, request.setAttribute하겠다는 것, db연동해서 data있으면 여기서 세팅 !
    }
}

⚡  .html은 DB연동X   /   .do는 DB연동O (이 파일에서 정한 것)
urlFilenameViewController DB연동X, 화면만 이동하겠다는 것 !

⭐ModelAndView mav : DB연동해서 다음 화면에 응답할 데이터 *data를 map에 등록해서 여기 담음

⚡View는 jsp → 누르면 DispatcherServlet에 가서 Cotroller bean객체 얻어옴, doGet호출해서

회원리스트보여줘~ 모델단에 가서 data를 가져옴(DTO,VO) → 다음화면 jsp가 응답

⚡ServiceFacade는 모델단의 입구

3-1

public class SimpleUrlHandlerMapping{ //object가 부모
    private Map<String,String> map;
//키,값을 가짐
    public Controller getController(request,applicationContext){
//반환형 Controller, bean객체를 주는 getController
        String key=request.getRequestURI()
                .substring( 
                request.getContextPath().length() 
                );   

        return (Controller)applicationContext.getBean( 
            map.get(key)  //"urlFilenameViewController"(bean의 이름)
        );
//형변환해야함 : getController 리턴타입이 Controller
//Map이 들고있는 key는 /menu.html이다. 그 value를 들고 있는 것이 bean의 이름
    }
}

3-2

public class ApplicationContext{
    private Map<String,Object> map //이게 bean
    public Object getBean( String beanName ){ //반환형이 Object
        return map.get(beanName); //bean이름 있어, bean객체를 줘, object줘
    }
}

3-3 UrlFilenameViewController

public class UrlFilenameViewController implements Controller{
    public ModelAndView handlRequest(request, ){ //반환형 ModelAndView
        /*사용자가 아래와 같이 요청했을때 화면이름부분에 어떤값이 들어가야하나요? menu
        http://localhost:8282/MVC1/menu.html*/
        String uri=request.getRequestURI();   //   /MVC1/menu.html
        String contextPath=request.getContextPath();  //  /MVC1
        String str=uri.substring( contextPath.length()+1 ); // str=menu.html  
//+1을 해야 /빼고 menu.html 얻어옴

        return new ModelAndView( str.split("[.]")[0] ,null );
//화면이름 str.split("[.]")[0] 꺼내옴 --> menu
//.html은 db안가니까 전달할게 없어서 null
//정규표현식에서 .은 특수문자이기 때문에 .으로 사용하고 싶으면 []에 해야함
    }
}

3-4

@AllArgsConstructor //생성자
public class ModelAndView{
    private String viewName;
    private Map<String,Object> map;
}

3-5

public class InternalResourceViewResolver
    public void resolveView(){
        화면이동하기
    }

/menu.html=kr.co.seoul.common.controller.urlFilenameViewController
/member/list.do=kr.co.seoul.member.controller.memberListController
/error.html=kr.co.seoul.common.controller.ErrorController

path.properties에서

prefix =/WEB-INF/jsp ⇒ 접두어

postfix=.jsp ⇒ 접미어

http://localhost:8282/MVC1/menu.html 이라고 요청했을 때 !

 

결론 : 다음 화면은 메뉴 ! web-inf의 menu.jsp가 응답했다.