2018-12-05 수업 내용 정리

Bean 컨테이너

SW의 용어

Engine

  • 일을 실행하는 역할

Parser

  • 해석하는 역할
  • 엔진보다 협소한 의미, 실행까지 하면 엔진이라고 한다

Container

  • 객체의 생성과 소멸을 관리

VM

  • 생성과 실행과 소멸을 담당하는 역할
  • 컨테이너 + 엔진의 느낌
  • Bean

    • 인스턴스, 오브젝트, 객체
  • 의존 객체 주입이 필요

    • 생성자에 파라미터로 던져주던 것들
  • IOC Container(DI Container)

    • 의존 객체를 주입한 빈 컨테이너
      (IOC != DI)
      (IOC > DI)

    • 제어의 역행이 일어난다

      • 메인 흐름과 별개의 이벤트 핸들러(리스너) 메서드가 실행된다

      • 생성하지 않은 객체가 외부에서 만들어져 주입 된다

        IOC의 3대 예 중 2개

        • 이벤트 리스너
        • DI = 의존 객체 주입(생성이 아님)
      • DI를 쓰면 좋은 점

        • 의존 객체 대체가 쉽다
          (예를 들어 지불 수단이라는 인터페이스 급의 객체를 DI해서 넣어두면, 지불 수단 인터페이스를 상속 받은 돈, 카드 등의 재화도 주입이 가능)

        • 단위 테스트가 쉽다

          (예를 들어 원래 과일을 가는 믹서기를 만든다고 했을 때, 믹서기를 만드는 도중 테스트를 한다고 가정하자. 과일까지 모두 조달해서 믹서기를 작동하는 구조 OR 믹서만 작동이 가능한 구조 중에 단위 테스트가 쉬운 건 후자. 테스트할 때 과일 외에도 다양한 식품을 넣어볼 수 있다.)

        • 쓸모 없는 데이터의 생성을 최소화할 수 있다
          (자원 낭비가 덜 함)

Spring IOC Container

spring 공부 다시 해야할 듯…

  1. dependnciesSpring-context 의존 설정을 해준다.
    (maven에서 검색함)

  2. Spring Ioc 설정 파일 추가

    • xml로 설정하거나

    • class로 설정하는 방법 두 개

      • AppConfig.java(Class) 작성

        • 만약 내가 만든 클래스가 아니라서, 즉 IOC Container가 자동으로 생성할 수 없는 경우 메서드를 정의하여 직접 객체를 생성해야 한다.

          • mybatis 관련 객체의 경우가 대표적으로, sqlSessionFactory가 가 그렇다.
        • SqlSessionFactory 객체를 생성하는 메서드 추가

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          package com.eomcs.lms;

          // ioc Container에게 패키지 이름을 알려준다
          // 이름을 알려주면 그 패키지를 뒤져서 @conponent가 붙은 클래스에 대해
          // 인스턴스를 자동으로 생성
          @ComponentScan("com.eomcs.lms")
          public class AppConfig {
          @Bean // Spring IOC Container에게 이 메서드를 호출하여 리턴 값을 보관하라고 표시하는 어노테이션
          public SqlSessionFactory createSqlSessionFactory() throws Exception{
          String resource = "com/eomcs/lms/conf/mybatis-config.xml";
          InputStream inputStream = Resources.getResourceAsStream(resource);
          return new SqlSessionFactoryBuilder().build(inputStream);
          // 리턴 값을 저장할 때 사용할 이름을 따로 지정하지 않으면 메서드 이름으로 저장되기에
          // 이런 메서드의 이름은 동사가 아닌 객체의 이름인 명사 형태로 짓는다
          }
          }

          이름 지정하는 방법@Bean("이름")은 이러하지만 잘 사용하지 않는다.

        • dao 클래스는 내가 만든 클래스므로, 해당 클래스 위에 객체 자동생성하도록 설정

          이 때 당연히 인터페이스는 객체 생성이 불가하므로, 상속 받은 클래스에 어노테이션 @Component 이용

        • Spring Boot

    • xml 설정이 아닌 java class로 설정한다
  3. Spring IOC Container 준비

    • 실행 클래스에 Spring IOC Container 객체 준비

    • 실행 클래스에서 해당 소스 기술

      1
      ApplicationContext iocContainer = new AnnotationConfigApplicationContext(AppConfig.class);

      그리고 이 소스를

      1
      Command commandHandler = commandMap.get(command);

      이렇게 변경

      1
      2
      3
      Command commandHandler = (Command) iocContainer.getBean(command);
      // 빈을 찾으면 정상 실행, 빈을 못 찾으면 예외가 발생
      // 때문에 if else가 아니라 try catch 처리

      차후에 iocContainer도 close() 필요

      Class 정보를 보고자 할 때

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      >     Class<?> clazz = Class.forName("com.eomcs.lms.AppConfig");
      > // AppConfig 클래스가 메모리에 로딩되어 있지 않다면 로딩 후 클래스 정보 리턴
      > // 어떤 클래스라도 상관 없이 받기 위해 제네릭 <?>
      > // class가 이미 사용되는 예약어라 clazz로 객체 이름 설정이 일반적
      > // AppConfig.class와 같다.
      >
      > // Reflection 클래스 : JVM에 로딩되어 있는 있는 클래스와 메소드 정보를 읽어 올 수 있다
      >
      > Method[] methods = clazz.getMethods();
      > // 클래스의 모든 메서드들이 들어온다
      >
      > Constructor[] constructors = clazz.getConstructors();
      > // 생성자의 정보
      > Class<?> returnType = methods[0].getReturnType();
      > // 메서드의 리턴 타입
      > Parameter[] params = methods[0].getParameters();
      > // 메서드에 들어가는 파라미터
      >
      > ApplicationContext iocContainer = new AnnotationConfigApplicationContext(AppConfig.class);
      > // 확장자(.class)가 아닌 클래스 변수명임을 기억할 것
      > System.out.println(iocContainer.getBeanDefinitionCount());
      > // 몇 개의 객체 생성을 했는지
      > String[] names = iocContainer.getBeanDefinitionNames();
      > // 생성된 객체들의 이름을 리턴
      >
      > for (String name : names) {
      > System.out.printf("%s ===> %s\n", name, iocContainer.getBean(name).getClass().getName());
      > // 객체 이름 ===> 경로 출력됨
      > // 객체의 이름을 지정하지 않았다면 클래스명 맨 앞 대문자를 소문자로 변경해 사용
      > }
      >

Spring IOC 컨테이너와 mybatis 연동

  • buid.gradlemybatis-spring 의존 설정 추가

  • sqlSessionFactory 객체 생성

    • mybatis-spring에서 제공하는 도우미 클래스를 사용해 만드는 것으로 메서드 변경

      • apache common-dbcp 라이브러리 추가
        datasource 구현체(db 커넥션풀 객체)
      • buid.gradlecommons-dbcp2 의존 설정 추가

      기존 소스가 이런 상태에서

      1
      2
      3
      4
      5
      6
      @Bean
      public SqlSessionFactory sqlSessionFactory() throws Exception {
      String resource = "com/eomcs/lms/conf/mybatis-config.xml";
      InputStream inputStream = Resources.getResourceAsStream(resource);
      return new SqlSessionFactoryBuilder().build(inputStream);
      }

      이렇게 변경하고,

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Bean
      public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

      SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

      // datasource 주입
      factoryBean.setDataSource(dataSource);

      return factoryBean.getObject();
      }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
클래스 위에 `@PropertySource` 어노테이션을 더 추가해주고, 가져오는 프로퍼티 파일이 들어갈 수 있도록 `@value` 어노테이션도 클래스 변수 위에 기술해준다.

```
// spring ioc Container에게 프로퍼티 파일을 로딩할 것을 명령
@PropertySource("classpath:/com/eomcs/lms/conf/jdbc.properties")
// 프로퍼티가 있는 위치를 가리키되, 기술하는 방식을 지켜야 한다.
public class AppConfig {

@Value("${jdbc.driver}")
String jdbcDriver;
@Value("${jdbc.url}")
String jdbcUrl;
@Value("${jdbc.username}")
String jdbcUserName;
@Value("${jdbc.password}")
String jdbcPassword;
```

객체 생성 메서드를 만들어준다. (basic datasource 방식)

```
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(jdbcDriver);
dataSource.setUrl(jdbcUrl);
dataSource.setUsername(jdbcUserName);
dataSource.setPassword(jdbcPassword);
return dataSource;
}
```

이제 db 커넥션을 java 파일에서 하므로, `mybatis-config.xml`에서 중복 기술 사항을 삭제한다.

- db 커넥션 풀
- db 연결 정보를 담은 프로퍼티 파일 로딩
  • 트랜젝션 관리자

    • spring 트랜젝션 관련 라이브러리 의존 설정(Spring JDBC) 추가

    • AppConfig에 PlatformTransactionManager를 리턴하는 메서드 구현

      • 이 때 이 객체의 이름은 반드시 transactionManager로 설정

        • 다른 이름으로 설정하면 트랜젝션과 관련한 다른 객체를 생성할 때 그 객체가 트랜젝션 관리자를 자동으로 찾지 못한다.

        생성자 메서드 소스는 다음과 같다.

        1
        2
        3
        public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
        }
    • 이제 db 트랜젝션 관리 또한 java 파일에서 하므로, mybatis-config.xml에서 중복 기술 사항을 삭제한다.

  • 도메인 클래스에 별명 구현

    • SqlSessionFactory를 반환하는 메서드에 다음의 설정을 세팅한다.

      1
      factoryBean.setTypeAliasesPackage("com.eomcs.lms.domain");
    • 마찬가지로 별명 지정 또한 java 파일에서 했으므로, mybatis-config.xml에서 중복 기술 사항을 삭제한다.

  • SQL을 보관한 XML 파일 경로 또한 java 파일에서 가능

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource, ApplicationContext iocContainer) throws Exception {

    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

    factoryBean.setMapperLocations(iocContainer.getResources("classpath:/com/eomcs/lms/mapper/*Mapper.xml"));
    // sql mapper 로딩
    // sql 파일이 있는 위치를 파라미터로 보내야 하는 상황
    // sql 파일의 위치 정보를 resource 객체에 담아 넘겨야 함
    // resource 객체는 Spring IOC Container를 통해 만들 수 있다.
    // Spring IOC Container 객체를 얻는 방법 : 메서드의 파라미터로 받는다.
    // 이 경우 ioc container 생성은 실행 클래스에서 하기에
    // 실행 클래스에서 해당 객체가 넘어온다.

    return factoryBean.getObject();
    }
  • mybatis-config.xml가 더 이상 필요 없다

웹 어플리케이션 이론

  • 웹 브라우저는 http 프로토콜에 맞춰서 웹 서버에 요청한다.
  • 웹 서버는 우리가 만든 프로그램을 실행해서 결과를 받아서 http 프로토콜 규칙에 맞춰 응답해야 한다.

  • 그런데 웹서버의 역할은 프로그램을 실행하는 게 아니다. 웹서버는 정적 웹 리소스 html, css, java script, png, jpeg 등을 읽어서 웹 브라우저에게 리턴하는 게 원래 역할이다. 웹서버가 프로그램을 실행해야하는 상황이니까 프로그램을 실행할 중간 객체를 필요로 하게 된다.

    실행해야 하는 리소스(DB와 같은 것)은 동적 리소스(Dynamic Web Resource)

  • ioc container는 웹서버의 요청이 들어오면 메서드를 호출해 프로그램을 실행시켜 리턴 값을 전달한다.

  • 웹 서버는 웹 브라우저로 http 프로토콜 규칙에 맞춰 리턴 값을 응답한다.

  • 웹 브라우저는 응답 받은 것에 맞춰 화면을 출력한다.

웹 어플리케이션이란

  • 웹 기반으로 프로그램 실행되는 자바 프로그램

  • 웹 어플리케이션이 하나의 객체로 이루어진 게 아닌 만큼, 기능을 잘게 쪼개서 하나의 클래스가 하나의 기능을 수행하게끔 만들어야 한다.

  • 웹 어플리케이션은 서버 프로그램인데 간단한 작업만을 수행한다.

  • 때문에 서버 어플리케이션의 작은 조각이라는 의미로 접미사 let을 붙인다.

    서블릿(Servlet)

  • 웹 어플리케이션을 구성하는 요소들

  • 작은 서버 프로그램

    서블릿 컨테이너(WAS)

  • 웹 서버 상에서 실행되는 자바 컨테이너

  • 서블릿 컨테이너의 대표적인 예는 톰캣

    • 서블릿 컨테이너는 이미 다운 받아 사용하는 것이고
    • 개발자는 서블릿을 만드는 것
  • 웹 서버, 웹 브라우저는 종속이 아니지만(http 프로토콜 규칙 때문)

  • 서블릿은 서블릿 컨테이너에 종속된다. 예를 들어 버전이 맞지 않는다면 실행되지 않는다.
  • 서블릿 컨테이너와 서블릿이 통신할 수 있도록 규칙이 있고 이들은 인터페이스로 구성되어 있다.

    • 서블릿
    • 필터
    • 리스너
  • 규칙이 들어 있는 인터페이스들이 자동으로 들어있는 것은 JAVA EE(웹 기술와 분산 기술-EJB-, 웹 서비스 기술, 자원 관리 기술)

    • 대표적인 것 : 자바 EE 구현체
      • 웹로직
      • 웹스피어(IBM 서버)
      • JBoss
      • Glasspish
      • Geronemo
      • 톰캣 : EE 모두 구현이 아닌 웹 기술만 뽑아서 만든 것
      • Jeus

웹 어플리케이션을 구성하는 요소

  • 서블릿
  • 필터
  • 리스너

servlet의 메서드

인터페이스로 구현되어 있는 기본 5개의 메서드

  • init()
  • service()
  • detory()
  • getServletInfo()
  • getServletConfig()

서블릿 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 어노테이션을 이용해 톰캣 서버에 서블릿이 있음을 알리고
// 루트 디렉토리부터 쓴 url을 매핑해준다.
// 톰캣 서버에 서블릿을 추가한 후 서블릿을 변경하면 일정 시간이 지난 후 리로딩 가능
// 추가하는 경우에만 서버 재시작 필요
@WebServlet("/board/list")
public class BoardListServlet implements Servlet{

(4개의 메서드 생략)

@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
res.setContentType("text/palin;charset=utf-8");
PrintWriter out = res.getWriter();
// 클라이언트 쪽에 출력할 때 필요한 객체준비
out.println("게시물 목록");
}
1
public class BoardListServlet extends GenericServlet {}

이렇게 추상클래스 GenericServlet을 상속 받아도 된다. GenericServlet에는 service메서드를 제외한 메서드가 구현이 되어 있기에 service만 구현하면 된다.

또한 HttpServletRequestHttpServletResponse를 파라미터로 사용하기 위해 추상클래스 HttpServlet(GenericServlet을 상속 받음)을 상속 받아도 된다.

1
2
3
4
5
public class BoardListServlet extends HttpServlet{
@Override
public void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException { .. }
}

HttpServletRequestHttpServletResponse를 파라미터로 사용할 수 있는 servicedogetdopost 호출이 가능하다.

  • httpServletservice 실행 시 부모인 GenericServlet에 정의된 service메서드를 부르고, 이 메서드는 다시 HttpServletRequestHttpServletResponse를 파라미터로 쓰는 service메서드를 내부적으로 부르며 실행된다.
  • 서블릿 컨테이너는 바로 dogetdopost를 직접 부르지 못한다.

filter의 메서드

  • init()
  • service()
  • detory()

listener의 메서드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@WebListener
public class ContextLoaderListener implements ServletContextListener{
// 웹 어플리케이션이 시작되거나 종료될 때 호출되는 메서드를 정의한 것

AnnotationConfigApplicationContext iocContainer;
// 다른 메서드에서도 접근이 가능하도록 전역변수로 올린다.

@Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
System.out.println("웹 어플리케이션이 종료될 때 자동 호출");
// Spring IOC Container 자원을 해제
iocContainer.close();
}

@Override
public void contextInitialized(ServletContextEvent sce) {
// TODO Auto-generated method stub
System.out.println("웹 어플리케이션이 시작될 때 자동으로 호출");

// AppConfig 클래스가 메모리에 로딩되어 있지 않다면,
// Spring IoC 컨테이너 준비하기
iocContainer =
new AnnotationConfigApplicationContext(AppConfig.class);

System.out.println(iocContainer.getBeanDefinitionCount());
String[] names = iocContainer.getBeanDefinitionNames();

for (String name : names) {
System.out.printf("%s ===> %s\n", name,
iocContainer.getBean(name).getClass().getName());
}
// Spring IOC Container를 servlet이 사용할 수 있도록
// servletContext라는 보관소에 저장
ServletContext sc = sce.getServletContext();
// 파라미터로 들어온 ServletContextEvent를 이용해 IOC Container를 받을 준비
sc.setAttribute("iocContainer", iocContainer);
}
}
  • WAS가 요청을 받으면 service 메서드가 실행되며 doget이나 dopost 실행됨

  • servlet 호출됨

    • 이 순간부터 IOC Container가 관리하는 게 아니라 관리자가 WAS로 넘어감

    • 그런데 dao부터는 IOC Container가 관리

    • 때문에 servlet에 IOC Container의 주소를 알려줘야 함

      1
      sc.setAttribute("iocContainer", iocContainer);

      이 작업이 이루어지는 이유

      그리고 HttpServlet를 상속 받은 BoardListServlet 소스 코드는 다음과 같다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      // 어노테이션을 이용해 톰캣 서버에 서블릿이 있음을 알리고
      // 루트 디렉토리부터 쓴 url을 매핑해준다.
      @WebServlet("/board/list")
      public class BoardListServlet extends HttpServlet{

      ApplicationContext iocContainer;
      BoardDao boardDao;

      @Override
      public void init() throws ServletException{
      // 서블릿 인터페이스에 정의된 init(ServletConfig)가 먼저 호출되고, init(ServletConfig)가 init()를 호출하는 것
      // 톰캣이 바로 호출하는 게 아님
      // boardDao 객체를 꺼내기 위해 먼저 IOC Container를 꺼낸다.
      ServletContext sc = this.getServletContext();
      // ServletContext는 웹 어플리케이션 당 한 개 뿐이다.
      // ContextLoaderListener에서 꺼낸 것과 같은 객체가 온다.
      iocContainer = (ApplicationContext) sc.getAttribute("iocContainer");
      // 오브젝트 자료형이 리턴되어 applicationcontext 인터페이스로 받는다

      try {
      boardDao = iocContainer.getBean(BoardDao.class);
      }catch (Exception e) {
      e.printStackTrace();
      }
      }

      @Override
      public void service(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {

      res.setContentType("text/palin;charset=utf-8");
      PrintWriter out = res.getWriter();
      // 클라이언트 쪽에 출력할 때 필요한 객체준비
      out.println("게시물 목록");

      try {
      List<Board> list = boardDao.findAll();

      for (Board board : list) {
      out.printf("%3d, %-20s, %s, %d\n",
      board.getNo(),
      board.getContents(),
      board.getCreatedDate(),
      board.getViewCount());
      }
      } catch (Exception e) {
      e.printStackTrace();
      }

      }
      }
  • servlet에서 dao 도달

  • dao에서 mariaDB 도달

설정

서블릿 어플리케이션 개발에 사용할 라이브러리 추가

  • build.gradle에서 빌드 명령 사용이 가능할 수 있도록 설정

    • eclipse 대신 eclipse-wtp 플러그인 추가
    • 웹 어플리케이션 배치 파일(.war)을 만들 war 플러그인 추가
    • 단독으로 실행이 불가하므로 application 플러그인도 제거
    • mainClassName = 'App' 도 제거
    1
    2
    3
    4
    5
    plugins {
    id 'java'
    id 'eclipse-wtp'
    id 'war'
    }
  • servlet-api 의존 설정

    1
    providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
    • providedCompile : 개발하는 동안에만 사용하겠다
    • compile : 개발하는 동안에도 사용하고, 배포 시에도 함께 쓰겠다.
  • src/main/webapp : 웹 자원을 둘 디렉토리 생성