오브젝트 사이의 의존 정보는 틀에 박힌 구조를 갖고 있으며, 일일이 자바 코드로 만들어주기 번거롭다. 또, DI 구성이 바뀔 때마다 자바 코드를 수정하고 클래스를 다시 컴파일하기도 귀찮다.
사실 현재에 와서는 스프링 부트 이후 레거시 프로젝트 외에는 XML로 의존관계 설정을 하는 것을 본적은 없다. 지금은 사장된 것 같지만 일단 배워두자. (2021-07-12 작성)
<beans>
를 루트 엘리먼트로 사용한다. <beans>
내부에는 여러개의 <bean>
을 정의할 수 있다.
XML 설정은 @Configuration
, @Bean
애노테이션이 붙은 자바 클래스로 만든 설정 내용과 결국 동일하다.
@Configuration
은 <beans>
에 대응된다.@Bean
은 <bean>
에 대응된다.@Bean
메소드 이름이 빈의 이름이 되었다.@Bean
애노테이션이 붙은 메소드의 new 클래스명
의 클래스명
이 될 수 있다.@Bean
메소드에서도 다른 @Bean
메소드를 호출하여 의존 오브젝트를 설정했었다.자바와 XML 비교
자바
: @Configuration
XML
: <beans>
자바
: @Bean methodName()
XML
: <bean id="methodName">
자바
: return new BeanClass();
XML
: class="a.b.c...BeanClass">
IDE는 클래스 이름만으로 빈 클래스를 쉽게
import
할 수 있었는데, XML은 상대적으로 번거롭다.
@Bean // 오브젝트 생성을 담당하는 IoC용 메소드라는 표시이다.
public ConnectionMaker connectionMaker() {
return new DConnectionMaker();
}
<bean
id="connectionMaker"
class="toby_spring.chapter1.user.connection_maker.DConnectionMaker" />
XML로 의존관계 정보를 만들 때는 자바빈의 관례를 따라 수정자 메소드를 프로퍼티로 사용한다. 프로퍼티 이름은 set
을 제외한 나머지 부분을 사용한다.
위에서 정의한 @Bean connectionMaker()
메소드를 이용한 의존성 주입자바코드는 아래와 같다.
userDao.setConnectionMaker(connectionMaker());
XML에서는 의존관계 정보를 줄 때, <bean id="userDao">
내부에 아래와 같이 치환될 것이다.
<property name="connectionMaker" ref="connectionMaker" />
<beans>
<bean id="connectionMaker" class="toby_spring.chapter1.user.connection_maker.DConnectionMaker" />
<bean id="userDao" class="toby_spring.chapter1.user.dao.UserDao">
<property name="connectionMaker" ref="connectionMaker" />
</bean>
</beans>
properties
내부에 name
과 ref
속성은 문자열 자체는 같지만, 의미가 다르다. name
은 수정자 프로퍼티를 가리키는 것이고, ref
는 주입할 오브젝트를 정의한 빈의 ID이다.
보통 빈의 이름은 바뀔 수 있는 클래스의 이름보다는 인터페이스의 이름을 많이 사용한다. 이로 인해 프로퍼티 이름과 빈의 이름이 같은 경우가 흔하다. 물론 빈의 이름(id
)과 프로퍼티 이름이 달라도 상관없다. 다만, 빈의 이름을 바꿀 때는 해당 빈을 프로퍼티로 쓰는 ref
의 이름도 잘 바꿔주자.
XML은 자바코드에 비해 상대적으로 리팩토링에 주의가 많이 필요하다.
<beans>
<bean id="myConnectionMaker" class="toby_spring.chapter1.user.connection_maker.DConnectionMaker" />
<bean id="userDao" class="toby_spring.chapter1.user.dao.UserDao">
<property name="connectionMaker" ref="myConnectionMaker" />
</bean>
</beans>
connectionMaker
를 myConnectionMaker
로 바꾸어보았다. 빈의 id
와 프로퍼티의 ref
를 둘 다 변경했다.
<beans>
<bean id="dConnectionMaker" class="toby_spring.chapter1.user.connection_maker.DConnectionMaker" />
<bean id="nConnectionMaker" class="toby_spring.chapter1.user.connection_maker.NConnectionMaker" />
<bean id="userDao" class="toby_spring.chapter1.user.dao.UserDao">
<property name="connectionMaker" ref="nConnectionMaker" />
</bean>
</beans>
위와 같이 dConnectionMaker
, nConnectionMaker
등 여러개의 빈을 정의해놓고 필요한 것을 골라쓸 수도 있다. local
, test
, production
등의 빈을 만들어놓고 테스트하면 용이하다.
DTD
와 스키마
를 활용하면 XML이 미리 정해진 구조를 따라 작성했는지 알 수 있다. 스프링의 XML 설정파일은 이 두가지 방식을 모두 지원한다.
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
위는 DTD
를 이용한 방식이다.
<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-3.0.xsd">
위는 스키마
를 이용한 방식이다.
스프링은 DI를 위한 기본 태그인 <beans>
, <bean>
이외에도 특별한 목적을 위해 별도의 태그를 사용할 수 있는 방법을 제공한다. 이런 태그를 이용하려면 별개의 스키마 파일을 이용해 독립적인 네임스페이스를 제공해야 해서 DTD
대신 스키마
를 이용하는 것이 바람직하다.
GenericXmlApplicationContext
를 이용하여 애플리케이션 컨텍스트를 만들어보자.
Intellij
의 위 메뉴를 이용하면 손쉽게 Spring Config
XML 파일을 생성할 수 있다.
애플리케이션 컨텍스트의 XML 파일 이름은 관례를 따라
applicationContext.xml
으로 지정했다.
java
내부에 만드는 것이 아니라 resources
내부에 만들어야 한다.
기본적인 네임스페이스들은 전부 설정되어 있다.
빈을 추가할 때도 class
애트리뷰트는 앞글자만 입력해도 자동완성된다.
수정자가 없으면 위와 같이 수정자를 만들라는 안내 메세지가 나온다.
정상적으로 수정자가 생성되면 위와 같이 IDE에서 연결된 프로퍼티를 표기해준다.
public class XmlUserDaoTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
ApplicationContext applicationContext = new GenericXmlApplicationContext("spring/applicationContext.xml");
UserDao userDao = applicationContext.getBean(UserDao.class);
User user = new User();
user.setId("22522");
user.setName("제이크22522");
user.setPassword("jakejake");
userDao.add(user);
System.out.println(user.getId() + " register succeeded");
User user2 = userDao.get(user.getId());
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId() + " query succeeded");
}
}
위와 같이 소스코드를 작성해주고 테스트한 결과 정상적으로 동작한다.
사실 자바에서는 ConnectionMaker
의 기능을 지원하는 인터페이스가 이미 있다. DataSource
는 DB 커넥션을 가져오는 기능 외에도 여러 개의 메소드를 추가적으로 갖고 있다. 또한 스프링에는 해당 DataSource
를 구현하여 DB 연결, 풀링 등 많은 기능을 갖춘 클래스를 제공한다.
당연히 DB의 종류, 아이디, 비밀번호는 얼마든지 바꾸어도 구현 클래스를 다시 만들지 않아도 되는 정도의 유연성은 제공한다.
스프링에서 제공하는 구현체를 사용하기 위해
implementation group: 'org.springframework', name: 'spring-jdbc', version: '5.3.8'
의존성을 추가해주자.
SimpleDriverDataSource
는 스프링에서 제공하는 구현 클래스 중 테스트 환경에서 간단히 사용할 수 있는 DataSource
이다. 이 클래스를 사용하도록 DI를 재구성하자.
UserDao.java
public class UserDao {
DataSource dataSource;
public UserDao() {
}
public UserDao(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
DaoFactory.java
@Configuration
public class DaoFactory {
@Bean
public UserDao userDao() throws ClassNotFoundException {
return new UserDao(dataSource());
}
@Bean
public DataSource dataSource() {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(org.postgresql.Driver.class);
dataSource.setUrl("jdbc:postgresql://localhost/toby_spring");
dataSource.setUsername("postgres");
dataSource.setPassword("iwaz123!@#");
return dataSource;
}
}
위와 같이 UserDao
가 의존하는 객체를 DataSource
인터페이스로 바꾼 뒤에 DaoFactory
에서 새로 생성한 SimpleDriverDataSource
오브젝트를 주입하면 된다. SimpleDriverDataSource
는 setter
로 DriverClass
, URL
, Username
, Password
를 삽입할 수 있다.
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="username" value="postgres" />
<property name="password" value="iwaz123!@#" />
<property name="driverClass" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost/toby_spring" />
</bean>
<bean id="userDao" class="toby_spring.chapter1.user.dao.UserDao">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
위와 같이 작성해주었다. 자바 코드와 같은 내용을 xml
로만 바꾼 것이다.
bean
으로 정의한 것을 프로퍼티로 주입할 때는ref
를 사용하고, 일반 값을 프로퍼티로 주입할 때는value
를 사용한다.
위에 driverClass
를 자바 소스로 적용할 때와는 다르게 ...Driver.class
까지 적어주지 않았는데, 그 이유는 스프링이 프로퍼티의 값을 수정자 메소드 파라미터의 타입을 참조하여 적절하게 변환해주기 때문이다. 스프링은 수정자 메소드의 파라미터 타입이 Class
임을 확인하고 org.postgresql.Driver.class
오브젝트로 자동 변경해준다.
스프링은 value
에 지정한 텍스트 값을 적절한 자바 타입으로 변환해주는데, Integer
, Double
, String
, Boolean
과 같은 기본 타입은 물론이고, Class
, URL
, File
, Charset
같은 오브젝트로 변환이 가능하다. 값이 여러개라면 List
, Map
, Set
, Properties
나 배열로도 주입이 가능하다.
변경 후에 테스트를 돌려본 결과 정상적으로 동작한다. 항상 테스트는 돌려보자.