Back-end/JAVA,Spring

스프링 흐름 파악하기. Spring Flow -소스코드 예제

cheersHena 2018. 8. 25. 21:11
반응형

SpringExample 

Key point: 객체생성 대한 책임을 개발자에서 ---> 스프링 프레임워크에게 분리.

1. pom.xml  Check. - dependency 의존성 확인.  


pom.xml
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <!-- Spring framework -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
            <version>2.5.6</version>
        </dependency>

        <!-- MySQL database driver -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
        </dependency>

스프링을 추가해주면 스프링 프레임워크의 라이브러리를 사용할 준비가 된것.

 (사용할수있는게아니라 준.비.가 된것. 사용하기 위해서는 당연히 더 많은 것들이 필요하다.) 


2.  메인메서드 Check(본 예제의 메인메서드: App.java) 

App.java

public class App {

    public static void main( String[] args ) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Module.xml");

ApplicationContext 는 스프링 컨테이너(중 하나)이고, 

ClassPathXmlApplicationContext란 가장많이 사용되는 어플리케이션 컨텍스트 구현체이고, 기능은 클래스패스에 위치한 xml 파일에서 컨텍스트 정의 내용을 읽어들인다.

xml파일 : 설정 파일. 


3. 설정파일(xml) Check. (xml파일은 java가 아니므로 resources 에 존재.)
Spring-Module.xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    
    <import resource="database/Spring-Datasource.xml" />
    <import resource="board/Spring-Board.xml" />

</beans>
설정파일을 확인해보면 또다른 설정파일을 Spring-Datasource.xml , board/Spring-Board.xml 을 import하고 있다. 해당 xml 설정파일도 check .

**스프링 빈<bean>(=객체) 생성 필수조건 
① id   ②객체생성에 필요한 정보 property (없으면 안적어도됨)   ③ class (생성할 클래스) 
id 를 호출하면 프레임워크가 해당 아이디에 연결된 클래스를 준다. (객체 생성 책임: 개발자 -> 프레임 워크)
즉, 클래스 생성대신 클래스를 불러와 쓰기위해 필요한것 = id

Spring-Datasource.xml
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/[DB명]?serverTimezone=UTC&amp;characterEncoding=UTF-8" />
        <property name="username" value="[mysql 아이디]" />
        <property name="password" value="[mysql 비밀번호]" />
설정파일의 스프링 빈을 확인하면 내용을 알수있다.
id="dataSource" id= dataSource를 호출하면 DriverManagerDataSource 클래스를 불러오는 것.
 ** DriverManagerDataSource클래스는 MysqlConnector 클래스이다. 
클래스가 경로가 "org.springframework. ~~ 이면 스프링에서 제공하는 클래스임을 알수있으며 따로 코드 조회는 불가하다 
(개발자가 만든 클래스로 연결되는 경우는 경로를 따라가면 클래스 코드를 조회할 수 있다.)

property: 연결 정보: 즉, MysqlConnector 클래스 생성위해 필요한 정보들. ex) mysql DB명, ID, PW ... 
(필요정보가 없는경우 안적어줘도 된다.)

property ---> set메서드 를 찾아간다. 
<property name="set메서드" value="(해당 set메서드에 필요한) 매개변수" />
name: id가 가리키는 클래스의 set메서드. 
ex) name="url"DriverManagerDataSource클래스에 setUrl 메서드를 가르키는 것.. 
name에서는 set을 같이 쓰지 않기때문에 헷갈릴 수 있으나 property -> set메서드 는 스프링에서 일종의 약속이다. ** 외우도록 하자- !! 
value : 해당 set메서드에 필요한 매개변수. 
(매개변수가 기본형 값인 경우= value / 매개변수가 참조형 객체인 경우 =ref )   
                       (매개변수가 늘어나면 property의 개수도 늘어난다. 

board/Spring-Board.xml
    <bean id="boardDAO" class="com.mkyong.customer.dao.impl.JdbcBoardDAO">                                              
        <property name="dataSource" ref="dataSource" /> <!--property: set메서드. -->
    </bean>                                           
    
id="boardDAO" id = boardDAO 를 호출하면 클래스를 불러온다.
클래스는 경로를 보면 org.springframework.~~이 아닌 개발자가 작성한 클래스임을 알수있다. 해당 경로를 따라가서 해당 클래스 JdbcBoardDAO를 확인한다. 
property 연결정보를 보면 value 가 아닌 ref 가 있는데 ref 는 reference(참조하다) 즉 참조형 객체라는 뜻이다. (like DTO)

property ---> set메서드 를 찾아간다. 
<property name="set메서드" ref ="(해당 set메서드에 필요한) '객체' 매개변수" />
ref="dataSource" 이또한 스프링 빈의 id명을 가르키고 있고, 그 id에 해당하는 클래스 객체가 매개변수로 필요하다는 의미이다.
본 예제에서는 앞에서 살펴보았던 mysqlConnector인 DriverManagerDataSource  클래스가 매개변수로 필요하다는 의미이다. 

 JdbcBoardDAO 는 개발자가 작성한 클래스이기 때문에 해당 클래스를 찾아가보면 실제로 
public class JdbcBoardDAO implements BoardDAO {
                                               
    private DataSource dataSource; //class변수
    
     // <property name="dataSource" ref="dataSource" /> name이 찾아가는 set메서드.

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
setDataSource() 라는 set메서드가 존재하고 있는것을 확인할 수 있다. 또한 매개변수를 dataSource로 하고 있는것도 확인할 수 있다. 

이때 매개변수 (DataSource dataSource) 는 객체이며 클래스 이름이 아니라 스프링 빈의 id 이름이다. 해당 dataSource id를 찾아가보면 
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
실제로는 DriverManagerDataSource 클래스 참조형 객체로 가지는 것이다. 


여기까지 흐름을 이해했다면, 다시 메인메서드로 돌아가서 다음 코드를 보자 

App.java

public class App {

    public static void main( String[] args ) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Module.xml");
          
/*
         * @Autowired
         * private BoardDao boardDAO;
         */

BoardDAO boardDAO = (BoardDAO) context.getBean("boardDAO");
boardDAO.insert(newBoard);

context.getBean("boardDAO"); : 스프링이 생성한 스프링빈의 boardDAO를 쓴다는 의미이다.
스프링에게 객체생성의 책임을 넘겨주기 전에는 개발자가 직접 객체 생성(new)을 해야했다.
기존의 방법 :  BoardDAO boardDAO = new BoardDAO(); 에서 new로 생성하는 대신 겟빈 .getBean("boardDAO"); 을 사용해서 객체를 호출하여 리턴하는 것이다. 즉, 개발자가 직접 객체를 생성하는대신 스프링빈이 객체를 대신 생성해주고, 개발자는 그 객체를 호출하여 갖다 쓰는것이다 !!! 

자, 그렇다면 이제 스프링 빈의 id  boardDAO 를 따라가보자. 
    <bean id="boardDAO" class="com.mkyong.customer.dao.impl.JdbcBoardDAO">                          
        <property name="dataSource" ref="dataSource" />
    </bean>     

스프링 빈 id="boardDAO"를 찾아가서 클래스를 확인해보았더니. 응...?? 뭔가 이상하다? 

코드를 보면 BoardDAO boardDAO = (BoardDAO) context.getBean("boardDAO");

으로 분명히 BoardDAO 라는 객체를 리턴하는데, 실제 스프링 빈 bean id="boardDAO"에서는 BoardDAO 가 아닌 JdbcBoardDAO 클래스를 가리키고 있다...??

WHAT THE.. HECK...? 🤯

(여기서 1차 멘붕주의. 하지만 걱정마시라 멘붕오는게 당연하다. 그러니 마음을 가다듬고, 왜 그런지 이유를 살펴보자.. )

그 이유는 JdbcBoardDAO 클래스에 직접 가서 확인해볼 수 있다. 

public class JdbcBoardDAO implements BoardDAO {
... }

JdbcBoardDAO클래스에 가보면 BoardDAO 를 implements (구현) 하고있다. 즉, BoardDAO는 클래스가 아니라 인터페이스 임을 확인할 수 있다!! 

즉, JdbcBoardDAO 클래스는 BoardDAO 인터페이스의 구현 클래스임을 알 수 있다. 

** 인터페이스 구현 (implements Interface) 의 개념은 앞에 따로 정리한 포스팅 참조. (https://cheershennah.tistory.com/56)

따라서, BoardDAO boardDAO = (BoardDAO) context.getBean("boardDAO"); 에서 

BoardDAO 는 클래스가 아닌 인터페이스 이다. 직접  BoardDAO 인터페이스를 살펴보면, 

public interface BoardDAO // 인터페이스 == 작업지시서.
{   //작업: 메서드.
    //2개의 메서드 insert, findByBoardNo
    public void insert(Board board);
    public Board findByBoardNo(int no);
}

인터페이스는 작업지시서만을 가지고 있는 일종의 추상클래스이고, 

오직 추상 메서드(작업)와 상수(매개변수)만을 가지므로, 인터페이스의 메서드에는 위와같이 구현부 { ... } 가 존재하지 않는다.

대신, 해당 인터페스를 구현하는 구현클래스에서 구현한 메서드가 실행되는 것이다. 

그렇다면 이번에는 BoardDAO 인터페이스를 구현하고 있는 클래스인 JdbcBoardDAO 클래스를 살펴보면

public class JdbcBoardDAO implements BoardDAO {
    private DataSource dataSource; //class변수

    public void setDataSource(DataSource dataSource) { // 이 메서드는 구현메서드 아닌 자기자신이 가지는 메서드
        this.dataSource = dataSource;
    }
    
    public void insert(Board board){ // implements의 작업 가져와서 구현한 insert 메서드
        
        String sql = "INSERT INTO board (name, password, title, content, read_cnt, create_date) VALUES (?, ?, ?, ?, ?, now())";
        Connection conn = null;

        .... ~~ (이하 구현코드 생략)
}

    public Board findByBoardNo(int no){ // implements의 작업 가져와서 구현한 insert 메서드.
        
        String sql = "SELECT * FROM board WHERE no = ?";
        Connection conn = null;
        Board board = .... ~~ ;

        .... ~~ (이하 구현코드 생략)
return board;
}
 }      

다음과 같이 BoardDAO 인터페이스에 있는 작업(==메서드) 의 구현부를 완성하고 있는 모습을 확인할 수 있다. 

여기서 첫번째메서드  setDataSource(DataSource dataSource)는 JdbcBoardDAO클래스 스스로 가지는 메서드이다.

구현 클래스는 인터페이스에 있는 메서드를 반드시 구현하되, 자신만의 메서드를 추가구현 할 수 있다. 

즉, 여기서 JdbcBoardDAO클래스는 적어도 2개이상의 BoardDAO인터페이스로 부터 지시받은 작업메서드 ( insert(Board board)메서드, findByBoardNo(int no)메서드 ) 를 반드시 구현해야 하는 것이다. 

-_ㅜ

그러하고 한다. ... 엄청나게 헷갈리지만 .... 다시, 메인 메서드로 돌아가서..

 BoardDAO boardDAO = (BoardDAO) context.getBean("boardDAO"); 의 BoardDAO 는 인터페이스이고, 

즉, BoardDAO boardDAO = new JdbcBoardDAO(); 가 되는 것이다.

boardDAO.insert(newBoard); 는 BoardDAO 인터페이스의 insert(newBoard) 메서드를 호출하는 것인데,

앞에서 살펴보았듯, 인터페이스의 메서드는 구현되지 않고 텅- 비어있다. 

즉, 이때 실행되는 메서드는 인터페이스를 구현하고 있는 구현클래스 JdbcBoardDAO의 insert(Board board)메서드가 실행되는 것이다.!!!

public class JdbcBoardDAO implements BoardDAO {
public void insert(Board board){ // implements의 작업 가져와서 구현한 insert 메서드
String sql = "INSERT INTO board (name, password, title, content, read_cnt, create_date) VALUES (?, ?, ?, ?, ?, now())";
Connection conn = null;

.... ~~ (이하 구현코드 생략)
}
실제로 실행되는 메서드: public class JdbcBoardDAO implements BoardDAO 의 insert(Board board){} 메서드.
void 타입이므로 리턴값은 없다.

하나의 예를 더 들어,  
Board board = boardDAO.findByBoardNo(1);
코드를 실행한다고 하면, 
public class JdbcBoardDAO implements BoardDAO {
private DataSource dataSource; //class변수
public Board findByBoardNo(int no){ // implements의 작업 가져와서 구현한 insert 메서드.
String sql = "SELECT * FROM board WHERE no = ?";
Connection conn = null;
Board board = .... ~~ ;

.... ~ ~ (이하 구현코드 생략)
return board;
}
}
실제로 실행되는 메서드: public class JdbcBoardDAO implements BoardDAO 의 Board findByBoardNo(int no){} 메서드.
Board 타입이므로 리턴값 Board 가 리턴 될것이고, 때문에 실행코드에, 

Board board = boardDAO.findByBoardNo(1);

역시 Board board 로 할당되는 것을 확인할 수 있다.


후!!! 여기까지가 기본적이지만 가장 핵심포인트인 스프링 빈의 객체 생성 및 관리 흐름이다.  처음엔 이게 도대체 뭔말인가 여긴어딘가 나는 누군가....

미친듯이  복잡하지만 이 흐름에 익숙해지고 나면, 이전의 코드에 비해 얼마나 효율적으로 개발이 편해지는 지 알수 있다고 하니.... . . ..

 믿어보는수 밖에 . ...🤔


 

반응형