2018년 10월 26일 금요일

Instantiation of bean failed; Failed to instantiate [......]: Specified class is an interface 에러 문제






Spring(혹은 전자정부프레임워크)에서 maven build가 정상적으로 수행된 후에 『Run As - Java Application』으로 해당 클래스를 실행했을 때 다음과 같은 에러가 발생했다면 무엇이 문제라는 것일까?


Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageBean' defined in class path resource [context-hello.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.joe.MessageBean]: Specified class is an interface
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1105)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1050)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at com.joe.HelloApp.main(HelloApp.java:10)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.joe.MessageBean]: Specified class is an interface
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:68)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1098)
... 13 more

위의 에러메시지에서 몇 가지 단서들을 확인할수 있는데 
 -. context-hello.xml에 있는 messageBean이라는 이름의 bean 객체를 생성하지 못했다는 것이고
    Error creating bean with name 'messageBean' defined in class path resource [context-hello.xml]
 -. bean을 생성하기 위해 지정된 클래스가 interface이기 때문에 bean(객체)를 생성할수 없다
    Failed to instantiate [com.joe.MessageBean]: Specified class is an interface

참고로 context-hello.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-3.2.xsd">

<bean name="messageBean" class="com.joe.MessageBean" />

</beans>

그러면 bean을 생성할 대상이 되는 클래스인 com.joe.MessageBean를 보면 다음과 같다.

package com.joe;

public interface MessageBean {
public void sayHello(String name);
}

따라서 결국은 문제가 된 것은 context-hello.xml에서 bean 생성 할 대상으로 지정한 클래스가 interface여서 발생한 에러였다. Java는 근본적으로 interface를 막바로 객체(bean) 생성할수가 없다. 만일 객체를 생성할수 있다고 한들 그 객체가 행할수 있는 기능(메소드) 자체가 정의되어 있지 않기 때문에, 위에서 보듯이 sayHello()라는 메소드가 무엇을 행할지 내용이 없다. 이것이 interface이다. 

따라서 interface는 객체를 막바로 생성할수가 없고 생성할수 있다고 해도 그 객체(bean)이 아무런 동작도 할것이 없는것이다.
따라서 interface를 구현한(implements) 클래스를 객체로(bean)으로 생성하도록 context-hello.xml의 내용을 변경해 주어야 한다.

여기서 MessageBean 인터페이스를 구현한 하위 클래스는 다음과 같다.

package com.joe;


public class MessageBeanEng implements MessageBean {
@Override public void sayHello(String name){
System.out.println("Hello, "+name);
}
}

따라서 context-hello.xml의 내용을 바꾸어 주어야하는데 interface인 MessageBean을 그 구현 클래스인 MessageBeanEng로 변경해 주어야 하는 것이다.

<bean name="messageBean" class="com.joe.MessageBean" />

를 아래와 같이 바꾸어 주어야 한다.

<bean name="messageBean" class="com.joe.MessageBeanEng" />

2018년 10월 18일 목요일

Spring(혹은 전자정부프레임워크)에서 DI(Dependency Injection 의존성 주입)에 대한 개념과 예제 코드






Spring(혹은 전자정부프레임워크)에서 자주 사용되는 개념인 DI(Dependency Injection)에 대해서 간단한 예제를 통해 살펴보고자 한다.

DI란 '의존성 주입'이라고 번역이 되는데 표현 자체가 거창해서 그렇지 사실은 그동안 프로그래밍에서 사용되어 오던 것이다.
예를들어 업로드 파일을 서버에 저장하는 SaveFile이라는 클래스가 있을 때 저장되는 파일 이름을 모두 대문자로 변환해서 저장하는 ToUpper라는 클래스를 SaveFile에서 사용한다고 하면 SaveFile은 ToUpper 클래스에 의존되어 있다고 표현한다.

class SaveFile
{
private File file;
private ToUpper upper;

private void saveFile() {
//여기서 upper객체를 사용해서 파일이름을 대문자로 변환후 저장하는 코드
}
.......
}

이럴 경우 ToUpper 클래스를 SaveFile 클래스 내부에서 new로 생성해서 사용할수도 있지만 ToUpper 클래스를 외부에서 주입해서(이걸 DI라고 한다) 사용할수 있을 것이다.
가장 보편적인 방법은 SaveFile의 생성자에서 매개인자(파라미터)로 ToUpper 클래스의 객체를 받아서 사용하는 형태가 있을 것이다.

class SaveFile
{
private File file;
private ToUpper upper;

public SaveFile(ToUpper upper){ //생성자를 통해 의존성 주입(DI)하는 방식
this.upper = upper;
}

private void saveFile() {
//여기서 upper객체를 사용해서 파일이름을 대문자로 변환후 저장하는 코드
}
.......
}

그런데 이 방식외에 Spring 컨테이너의 도움을 받아서 XML 설정 파일의 property 태그를 이용하는 방법도 있다.
이때 사용되는 것은 setter 메소드이다.


class SaveFile
{
private File file;
private ToUpper upper;

//setter만드는 규약에 맞게 setter 메소드를 만들어 두면 Spring 컨테이너가 
//XML 설정 파일의 property 태그를 이용해서 ToUpper 클래스의 객체를
//아래 메소드의 파라미터인 mUpper에 주입(DI)해 준다.
//따라서 ToUpper 클래스의 객체를 new로 생성하지 않아도 정상적으로 사용할수 있는것이다.
//setter를 만드는 규칙은 XML 설정 파일의 property name가 upper로 되어 있는 것을 근거로
//upper의 첫 글자를 대문자로(U) 바꾼 후 앞에 set을 추가한 형태인 setUpper로 만들면
//Spring 컨테이너가 알아서 이 setter를 찾아서 해당 property의 ref가 지정한 bean을
//mUpper에 주입해 준다. 이것이 프라퍼티를 이용한 DI 방식이다.
public void setUpper(ToUpper mUpper) {
this.upper = mUpper;
}

private void saveFile() {
//여기서 upper객체를 사용해서 파일이름을 대문자로 변환후 저장하는 코드
}
.......
}

※ ToUpper 클래스의 내용은 생략(해당 클래스가 잘 만들어져 있다고 가정...)

아래는 XML 설정파일의 정보이다. 파일 이름이 MyBean.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-3.0.xsd">

   <!-- SaveFile 클래스를 임의 클래스에서 객체로 사용하도록 bean 정보 설정 -->
   <bean id = "mySaveFile" class = "com.joe.SaveFile">
      <!-- SaveFile 클래스의 setUpper()에게로 ref가 지정하는 bean(객체)를 Spring 컨테이너가 주입해준다
즉 SaveFile 클래스에서 setUpper()의 매개인자로 ref가 지정하는 bean을 주입해 준다.
즉 setUpper(myUpper)식이 되는 것이다.
 -->
      <property name = "upper" ref = "myUpper"/>
   </bean>

   <!-- SaveFile 클래스가 사용하게 될 ToUpper 클래스에 대한 bean -->
   <bean id = "myUpper" class = "com.joe.ToUpper"></bean>
</beans>

이제 사용하는 방법은 다음과 같다.

package com.joe;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
   public static void main(String[] args) {
      ApplicationContext context = new ClassPathXmlApplicationContext("MyBean.xml");

      SaveFile sf = (SaveFile) context.getBean("mySaveFile");
      sf.saveFile();
   }


}