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&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 클래스 참조형 객체로 가지는 것이다.
여기까지 흐름을 이해했다면, 다시 메인메서드로 돌아가서 다음 코드를 보자
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 = .... ~~ ;
.... ~~ (이하 구현코드 생략)
}
}
다음과 같이 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 로 할당되는 것을 확인할 수 있다.
후!!! 여기까지가 기본적이지만 가장 핵심포인트인 스프링 빈의 객체 생성 및 관리 흐름이다. 처음엔 이게 도대체 뭔말인가 여긴어딘가 나는 누군가....
미친듯이 복잡하지만 이 흐름에 익숙해지고 나면, 이전의 코드에 비해 얼마나 효율적으로 개발이 편해지는 지 알수 있다고 하니.... . . ..
믿어보는수 밖에 . ...🤔