스프링에서는 DaoFactory와 같은 자바 클래스를 이용하는 거 말고도 XML을 이용해 DI를 위한 의존관계 정보를 담을 수 있다고 한다.
우선 기존의 DaoFactory에서 얻을 수 있는 빈 DI의 정보는 다음 3가지였다.
- 빈의 이름: @Bean 메서드의 이름이 빈의 이름이다. getBean()에서 사용된다.
- 빈의 클래스: 빈 오브젝트를 어떤 클래스를 이용해서 만들지를 정의한다.
- 빈의 의존 옵젝트: 빈의 생성자나 수정자 메서드를 통해 의존 오브젝트를 넣어준다. 의존 오브젝트도 하나의 빈이기에 이름이 있을 것이고, 그 이름에 해당하는 메서드를 호출해서 의존 오브젝트를 가져온다.
XML은 그렇다고 해서 자바 코드처럼 유연하게 정의될 수 있는 것은 아니기에, 핵심 요소를 잘 짚어서 태그와 속성을 알아야 한다고 한다.
클래스에서와 XML에서의 설정은 다음과 같이 대응된다.
자바 코드 설정정보 | XML 설정정보 | |
빈 설정파일 | @Configuration | <beans> |
빈의 이름 | @Bean methodName() |
<bean id="methodName"> |
빈의 클래스 | return new BeanClass(); | class = "a.b.c...BeanClass"> |
기존에 작성했던 코드를 대응해보면
@Bean // <bean
public ConnectionHelper connectionHelper(){ // id = "connectionHelper"
return new SeungkyuConnection(); // class = "package...SeungkyuConnection"/>
}
XML에서는 property 태그를 통해 의존 오브젝트와의 관계를 정의한다.
<property> 태그를 통해 관계를 정의하는데, name은 프로퍼티의 이름을 정의하고 ref는 수정자 메서드를 통해 주입해줄 오브젝트의 빈 이름이다.
수정자를 통해 주입을 하게 된다.
만약 다음과 같이 코드를 작성했다고 하자.
public class UserService {
private String name;
public void setName(String name) {
this.name = name;
}
public void printName() {
System.out.println("User name: " + name);
}
}
public class UserController {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void handleRequest() {
userService.printName();
}
}
그리고 UserController에서 UserService를 사용하기 위해 setter로 주입을 받으려고 한다면 XML을 다음과 같이 작성해준다.
<bean id="userService" class="com.example.UserService">
<property name="name" value="Seungkyu" />
</bean>
<bean id="userController" class="com.example.UserController">
<property name="userService" ref="userService" />
</bean>
이제 애플리케이션 컨텍스트가 DaoFactory 대신 XML 설정 정보를 활용하도록 만들어보자.
XML에서 빈의 의존관계 정보를 이용하는 IoC/DI 작업에는 GenericXmlApplicationContext를 사용한다.
<?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">
<!-- ConnectionHelper 타입의 Bean 등록 -->
<bean id="connectionHelper" class="seungkyu.SeungkyuConnection" />
<!-- UserDao Bean 등록 및 connectionHelper 주입 -->
<bean id="userDao" class="seungkyu.UserDao">
<constructor-arg ref="connectionHelper" />
</bean>
</beans>
다음과 같이 applicationContext.xml을 작성하고 resources 폴더에 넣어준다.
그리고 DaoFactory에서 @Configuration과 @Bean을 제거해준다.
Annotation이 아닌 XML을 통해서 Bean을 주입하기 때문이다.
ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
UserDao userDao = context.getBean("userDao", UserDao.class);
그리고 이렇게 Bean을 가져오면 정상적으로 작동하는 것을 볼 수 있다.
근데 알겠지만 이미 스프링에는 데이터베이스의 연결정보를 관리하는 DataSourceProperties라는 클래스가 존재한다.
@ConfigurationProperties("spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
private boolean generateUniqueName = true;
private String name;
private Class<? extends DataSource> type;
private String driverClassName;
private String url;
private String username;
private String password;
private String jndiName;
private EmbeddedDatabaseConnection embeddedDatabaseConnection;
private Xa xa = new Xa();
private String uniqueName;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.embeddedDatabaseConnection == null) {
this.embeddedDatabaseConnection = EmbeddedDatabaseConnection.get(this.classLoader);
}
}
public DataSourceBuilder<?> initializeDataSourceBuilder() {
return DataSourceBuilder.create(getClassLoader())
.type(getType())
.driverClassName(determineDriverClassName())
.url(determineUrl())
.username(determineUsername())
.password(determinePassword());
}
public boolean isGenerateUniqueName() {
return this.generateUniqueName;
}
public void setGenerateUniqueName(boolean generateUniqueName) {
this.generateUniqueName = generateUniqueName;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Class<? extends DataSource> getType() {
return this.type;
}
public void setType(Class<? extends DataSource> type) {
this.type = type;
}
/**
* Return the configured driver or {@code null} if none was configured.
* @return the configured driver
* @see #determineDriverClassName()
*/
public String getDriverClassName() {
return this.driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String determineDriverClassName() {
String driverClassName = findDriverClassName();
if (!StringUtils.hasText(driverClassName)) {
throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this,
this.embeddedDatabaseConnection);
}
return driverClassName;
}
String findDriverClassName() {
if (StringUtils.hasText(this.driverClassName)) {
Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName);
return this.driverClassName;
}
String driverClassName = null;
if (StringUtils.hasText(this.url)) {
driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
}
return driverClassName;
}
private boolean driverClassIsLoadable() {
try {
ClassUtils.forName(this.driverClassName, null);
return true;
}
catch (UnsupportedClassVersionError ex) {
// Driver library has been compiled with a later JDK, propagate error
throw ex;
}
catch (Throwable ex) {
return false;
}
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public String determineUrl() {
if (StringUtils.hasText(this.url)) {
return this.url;
}
String databaseName = determineDatabaseName();
String url = (databaseName != null) ? this.embeddedDatabaseConnection.getUrl(databaseName) : null;
if (!StringUtils.hasText(url)) {
throw new DataSourceBeanCreationException("Failed to determine suitable jdbc url", this,
this.embeddedDatabaseConnection);
}
return url;
}
public String determineDatabaseName() {
if (this.generateUniqueName) {
if (this.uniqueName == null) {
this.uniqueName = UUID.randomUUID().toString();
}
return this.uniqueName;
}
if (StringUtils.hasLength(this.name)) {
return this.name;
}
if (this.embeddedDatabaseConnection != EmbeddedDatabaseConnection.NONE) {
return "testdb";
}
return null;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String determineUsername() {
if (StringUtils.hasText(this.username)) {
return this.username;
}
if (EmbeddedDatabaseConnection.isEmbedded(findDriverClassName(), determineUrl())) {
return "sa";
}
return null;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public String determinePassword() {
if (StringUtils.hasText(this.password)) {
return this.password;
}
if (EmbeddedDatabaseConnection.isEmbedded(findDriverClassName(), determineUrl())) {
return "";
}
return null;
}
public String getJndiName() {
return this.jndiName;
}
public void setJndiName(String jndiName) {
this.jndiName = jndiName;
}
public EmbeddedDatabaseConnection getEmbeddedDatabaseConnection() {
return this.embeddedDatabaseConnection;
}
public void setEmbeddedDatabaseConnection(EmbeddedDatabaseConnection embeddedDatabaseConnection) {
this.embeddedDatabaseConnection = embeddedDatabaseConnection;
}
public ClassLoader getClassLoader() {
return this.classLoader;
}
public Xa getXa() {
return this.xa;
}
public void setXa(Xa xa) {
this.xa = xa;
}
public static class Xa {
/**
* XA datasource fully qualified name.
*/
private String dataSourceClassName;
/**
* Properties to pass to the XA data source.
*/
private Map<String, String> properties = new LinkedHashMap<>();
public String getDataSourceClassName() {
return this.dataSourceClassName;
}
public void setDataSourceClassName(String dataSourceClassName) {
this.dataSourceClassName = dataSourceClassName;
}
public Map<String, String> getProperties() {
return this.properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
}
static class DataSourceBeanCreationException extends BeanCreationException {
private final DataSourceProperties properties;
private final EmbeddedDatabaseConnection connection;
DataSourceBeanCreationException(String message, DataSourceProperties properties,
EmbeddedDatabaseConnection connection) {
super(message);
this.properties = properties;
this.connection = connection;
}
DataSourceProperties getProperties() {
return this.properties;
}
EmbeddedDatabaseConnection getConnection() {
return this.connection;
}
}
}
JPA를 사용했다면 여기에 username과 password, url의 정보를 입력한 경험이 있을 것이다.
이런 값들은 어떻게 주입을 하는걸까?
당연히 수정자를 통해 주입을 하는 것이고, 수정자 메서드에는 다른 빈이나 오브젝트 뿐만 아니라 스트링과 같은 단순 값을 넣어줄 수도 있다.
만약 기존에 다음과 같이 DB 연결정보를 주입받고 있었다면
dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
dataSource.setUrl("대충 URL");
dataSource.setUsername("seungkyu");
dataSource.setPassword("1234);
다음과 같이 XML을 통해서 주입 할 수 있는 것이다.
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/test"/>
<property name="username" value="seungkyu"/>
<property name="password" value="1204"/>
그렇기에 우리는 지금까지 자바 코드를 수정하지 않고도 데이터베이스의 연결 정보를 바꿀 수 있었던 것이다.
여기에서 driverClass 타입은 단순 문자열이 아닌 클래스 타입이다.
이렇게 클래스를 넣을 수 있는 이유도 setDriverClass() 메서드의 파라미터 타입을 참고로 적절한 형태로 변환해주기 때문이다.
com.mysql.jdbc.Driver을 com.mysql.jdbc.Driver.class의 오브젝트로 자동 변경해주는 것이다.
내부적으로는 다음과 같은 작업이 일어난다고 한다.
Class driverClass = Class.forName("com.mysql.jdbc.Driver");
dataSource.setDriverClass(driverClass);
이렇게 XML을 통해 Bean을 주입하는 방법을 알아보았다.
'Spring > 토비의 스프링' 카테고리의 다른 글
[토비의 스프링] 2.2 UserDaoTest 개선 (0) | 2025.05.18 |
---|---|
[토비의 스프링] 2.1 UserDaoTest 다시 보기 (0) | 2025.05.17 |
[토비의 스프링] 1.7 의존관계 주입(DI) (0) | 2025.05.15 |
[토비의 스프링] 1.6 싱글톤 레지스트리와 오브젝트 스코프 (1) | 2025.05.14 |
[토비의 스프링] 1.5 스프링의 IoC (0) | 2025.05.13 |