오브젝트 및 의존관계
들어가며
토비 님의 토비스프링 책을 참고하여 작성했으며, gradle로 파일을 설정하며 lombok 등의 플로그인을 사용하여 코드를 단순화했습니다.
오브젝트
오브젝트란
자바(Java)에서 "오브젝트"는 객체 지향 프로그래밍(Object-Oriented Programming, OOP) 개념에 기반한 핵심 요소입니다. 객체 지향 프로그래밍은 현실 세계의 사물을 모델링하고 이를 소프트웨어로 구현하는 개념입니다.
스프링을 이해하려면 일단 오브젝트에 대해서 관심을 두어야한다. 애플리케이션에서 오브젝트가 어떻게 생성되며, 어떤 관계 맺고 사용되고 소멸되기까지 전 과정을 이해해야 하는 것이 중요하다. 결국 오브젝트를 의 설계 방향으로 발전했다. 객체지향 설계의 기초를 비롯해서이다.(재활용과, 더 깔끔한 구조를 통해 지속적으로 개선하기 위함)
간단한 DAO
DAO(Data Access Object)란 DB를 생성해 데이터를 조회하거나 조작하는 기는을 전담하도록 만든 오브젝트
먼저 정보를 저장할 User.class를 만든다.
User
@Getter
@Setter
public class User {
private String id;
private String name;
private String password;
}
- Getter, Setter은 lombok 플러그인을 사용하여 코드를 단순화
MySql을 사용하며 위와같은 값을 넣어 User 테이블을 생성한다.
UserDao
사용자 정보를 DB에 넣고 관리할수 있는 DAO를 생성하며 CRUD를 만들 예정이다. 일단 등록과 조회기능만 먼저 구현해 볼 것이다.
public class UserDao {
public void add(User user) throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/스티마명", "아이디", "비밀번호"
);
PreparedStatement ps = c.prepareStatement(
"insert into users(id,name, password) values (?,?,?)"
);
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
ps.executeUpdate();
ps.close();
c.close();;
}
public User get(String id) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/스티마명", "아이디", "비밀번호"
);
PreparedStatement ps = c.prepareStatement(
"select * from users where id = ?");
ps.setString(1,id);
ResultSet rs = ps.executeQuery();
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
rs.close();
ps.close();
c.close();
return user;
}
}
실행
ublic class main {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao dao = new UserDao();
User user = new User();
user.setId("TestId");
user.setName("Test");
user.setPassword("1234");
dao.add(user);
System.out.println(user.getId()+"등록 성공");
User user2 = dao.get(user.getId());
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId()+"조회 성공");
}
}
콘솔창에 기대했던 것처럼 나오게 된다. 그리고 DB에 가서 확인해 보면 값도 입력되어 있는 것을 볼수가있다. 하지만 앞서 작성한 코드는 매우 불편한것들이 몇가지있다. UserDao 코드를 보게되면 DB를 연결하는 부분이 똑같지만 add()와 get()메소드부분에서 중복적으로 작성되어 있는것을 볼 수 있다. 이러면 지금은 MySql을 사용하고 있지만 다른 DB를 사용하게 되면 DB관련코드를 하나씩 모두 수정해야 하는 불편함이 있다. 이를 해결하기 위해서는 관심사의 분리를 통해서 중복된 코드를 메서드로 뽑아 사용하거나 별도의 클래스를 만들어 사용하면 코드 한 번의 수정으로 모든 값이 변경되게 할 수 있다.
클래스의 확장
앞서 말한 것처럼 2가지 방법이 있다. 그중에 클래스의 확장을 통해서 관심사의 분리를 해볼 것이다. 그전에 먼저 알아야 할 개념이 있다. 애플리케이션을 설계할 때에는 높은 응집도(Cohesion)를 유지하며 낮은 결합도(Coupling)를 가지게 설계해야 한다. 응집도는 모듈이나 클래스 내부 요소들이 얼마나 강하게 연결되어 있는지를 나타내며 응집도가 높다는 것은 유지보수가 쉽고 재사용이 가능하는 것이며, 결합도는. 다를 모듈이나 클래스와 얼마나 강하게 연결되어 있는지를 나타낸다. 낮은 결합도일수록 독립적이며 변경에 영향을 적게 받는다는 것이다. 응집도와 결합도를 통해서 UserDao를 본다면 UserDao는
DB에 의존적이다. 그 뜻은 위에 말한 것처럼 DB만 바뀌어도 모든 코드에 영향을 받는 것으로 결합도가 강하게 되어있다는 뜻이다. 이것을 해결하기 위해서는 느슨한 결합을 통해 변경에는 닫혀있고 확장에는 열려있는 OCP(개방 폐쇄 원칙)을 사용하여 변경해 볼 예정이다.
이런 문제를 해결하기 위해서는 인터페이스를 통해서 분리를 해주는 것이다.
UserDao는 DBconnectio의 인터페이스 에만 의존하면 UsrDao는 한 클래스에 종속적으로 의존하지 않게 할 수 있다. 이를 통해 Mysql에서 Oralce로 바뀐다 하더라고 UserDao는 인터페이스에게만 의존하기 때문에 변경에서 자유로워진다는 장점이 있다.
DBConnection
public interface DBConnection {
public Connection makeCon() throws ClassNotFoundException, SQLException;
}
인페이스로 생성 후에 MySqlCon클래스를 만들어 DBConnection을 상속받는다.
MySqlCon
public class MySqlCon implements DBConnection{
@Override
public Connection makeCon() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/toby", "root", "zxc45SDW!@");
return c;
}
}
UserDao에 DB부분 코드를 추출하여 작성한다. 그 후에 UserDao부분을 변경한다.
UserDao
public class UserDao {
private DBConnection dbConnection;
public UserDao1(DBConnection dbConnection) {
this.dbConnection = dbConnection;
}
public void add(User user) throws SQLException, ClassNotFoundException {
Connection c = dbConnection.makeCon();
//이전과 동일
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = dbConnection.makeCon();
//이전과 동일
}
}
수정 후에 main() 메드에서 수정후 실행해 보면 정상적으로 실행되는 것을 볼 수 있다.
DBConnection mySqlCon = new MySqlCon();
UserDao1 dao = new UserDao1(mySqlCon);
이처럼 코드를 변경했으나 실행을 결과는 같지만 코드를 수정하여 가독성, 유지보수성, 확장성을 개선하는 것을 리팩토링이라고 한다.
제어의 역전(IoC)
하지만 여기 코드에서도 문제점이 있다. 지금 까지 해왔던 관심사의 분리를 해왔는데. main메서드에서 어떤 오브젝트를 사용할 건지 결정하고 있다. 이것도 분리해야 한다.
팩토리
여기서 사용하는 팩토리는 관심사의 분리를 위해서 오브젝트를 생성하고 어떤 오브젝트를 사용하는지에 대한 결정을 넘겨주는 용도로 사용하는 것이다.
DaoFactory
public class DaoFactory {
public UserDao userDao() {
DBConnection mySqlCon = new MySqlCon();
UserDao1 userDao = new UserDao1(mySqlCon);
return userDao;
}
}
이제는 UserDao는 어떻게 초기화되는지에 관심을 두지 않고 DaoFactory를 통해서 오브젝트를 받아서 사용하면 된다.
UserDaoFactory를 사용한 UserDao
UserDao dao = new DaoFactory().userDao();
이처럼 리팩토링을 하여 실행하 보면 정상적으로 작동하는 것을 볼 수 있다.
앞선 코드를 실행해보고 나면 제어의 역전의 대해서 어느 정도 이해가 갈수도 있다.
제어의 역전이라는 것은 간단히 프로그램의 제어의 흐름 구조가 뒤바뀌는 것이라고 할 수 있다.
처음 설계했던 UserDao는 main()에서 오브젝트 생성, 사용, 결정등을 하는 구조로 설계되어 있다. 하지만 변경한 UserDao는 main()에서 스스로 판단하는 것이(수동적인 존재) 아닌 즉 사용해야 할 오브젝트 등을 받아서 그저 실행만 하는 구조로 바뀌게 되었다. 이것이 제어의 역전(IoC)이다. 하지만 지금은 스프링 없이 IoC 설계를 해봤다. 이제부터는 스프링을 통해 좀 더 강력한 IoC방법으로 해볼 것이다.
스프링의 IoC
스프링에서는 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트를 빈 팩토리라고 부른다. 이를 더확장한 것을 애플리케이션 콘텍스트를 주로 사용한다. 일종의 빈 팩토리라고 생각하면 된다.
애노테이션을 이용한 IOC
DaoFactory
@Configuration ----> 애플리케이션 컨텍스트 또는 빈 팩토리가 사영할 설정정보 표시
public class DaoFactory {
@Bean ---> 오브젝트 생성을 담당하는 IoC용 메소드
public UserDao userDao() {
return new UserDao(dbConnection());
}
@Bean
public DBConnection dbConnection(){
return new MySqlCon();
}
}
main()
ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
- 설정파일을 애노테이션으로 사용했기 때문에 AnnotationConfigApplicationContext()를 사용해 준다.
- "userDao"는 팩토리의 등록한 메서드명을 적어준다.
그 후에 오류 없이 잘 동작하는 것을 볼 수 있다.
애플리케이션 콘텍스트의 동작방식
스프링에서는 애플리케이션 콘텍스트를 IoC컨테이너라고 부른다.
애플리케이션 컨텍스트를 사용했을 때의 장점
- 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
- 애플리케이션 컨텍스트는 종합 IoC서비스를 제공한다.
- 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.
의존관계 주입 DI
스프링이 제공하는 IoC의 기능을 확실하게 짚어주는 의존간계주입(DI)이라는 것을 통해 차별성을 뒀다.
의존관계
위 그림에서 B가 변하면 A는 영향을 받는다. 하지만 A가 변한다고 B는 아무런 영향을 받지 않는다. 이럴 때 A는 B에 의존하고 있다고 말한다.
위에 설계했던 UserDao를 보자 UserDao는 현재 DBConnection에 의존하고 있다. 그리고 MySqlCon은 DBConnection에 의존하고있다. 이처럼 느슨한 연결을 통해서 설계되어 있다. 그러나 UserDao는 미리 어떤 오브젝트를 사용할 것인지는 알수가없다 런타임시에 생성자를 통해서 어떤 오브젝트를 사용할것인지 결정하게 된다. 이 오브젝트를 의존 오브젝트(dependent Object)라고 한다. 결국은 DI란 다음과 세 가지 조건을 충족하는 조건을 말한다.
- 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스가 의존하고 있어야 한다.
- 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.
- 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해 줌으로써 만들어진다.
여기서 3의 존재는 DaoFactory와 같은 팩토리나 애플리케이션 컨텍스트라고 볼 수 있다.
스프링은 의존주입을 할 때 여러 가지 방법이 있다.
- 생성자주입
- Setter을 이용한 주입
- 일반 메서드를 이용한 주입
xml파일의 형식으로 의존성을 주입하기 위해 생성자주입이 아닌 수정자메서드를 사용한다.
XML 설정
xml을 통해 DI를 하기위해서 set메소드를 이용한 주입을 사용한다. 기존에 만들었던 DBConnection은 스프링에서 기본적으로 DataSource가 제공된다 그래서 굳이 만들지않아도 사용할수있다. DataSource에 DI를해 사용하는법을 알아보자
일단 UserDao에 코드를 추가한다.
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
applicationContext.xml
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/테이블명"/>
<property name="username" value="아이디"/>
<property name="password" value="비밀번호"/>
</bean>
<bean id="userDao" class="todby.study.dao.UserDao">
<property name="dataSource" ref="dataSource"/>
</bean>
- <property> :
- name = 빈 내에서 설정하려는 속성의 이름을 나타 낸다.
- value = 속성에주입할 값
- ref = 다른빈의 참조를 나타낸다.
main메소드를 아래와 같이 수정후 실행해보면 동일한 결과를 얻을수있다.
ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
UserDao dao = context.getBean("userDao", UserDao.class);
마지막으로
스프링이란 " 어떻게 오브젝트가 설계되고, 만들어지고, 어떻게 관계를 맺고 사용되는지에 관심을 갖는 프레임 워크"라는 것을 꼭 기억해두자 또한 스프링이 완벽하게 코드를 만들어주는것이 아닌 편리하게 설계 할수 있도록 도와주는 도구라는것도 잊지말자!
참고
'Spring > spring' 카테고리의 다른 글
[스프링] AOP(Aspect-Oriented-Programming)이해 (0) | 2023.11.22 |
---|---|
[스프링] 트랜잭션(Transaction)이란 (0) | 2023.11.22 |
스프링 서비스 추상화 (1) | 2023.11.19 |
스프링 예외처리(Exception) (0) | 2023.11.17 |
스프링이란? (0) | 2023.05.28 |