티스토리 뷰
3) xml파일을 이용한 설정
Maven으로 Java프로젝트 만들기
|
|
|
|
|
- pom.xml 파일에 JDK를 사용하기 위한 플러그인 설정을 추가합니다.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>kr.or.connect</groupId> <artifactId>diexam01</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>diexam01</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> -------------------------------------추가---------------------------------------------------- <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> ---------------------------------------------------------------------------------------------- </project> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 이 부분까지가 간단하게 Maven으로 프로젝트를 하나 만들어보는 과정을 수행한 것입니다.
App.java
package kr.or.connect.diexam01;
/** * Hello world! * */ public class App { public static void main( String[] args ) { System.out.println( "Hello World!" ); } } |
AppTest.java
package kr.or.connect.diexam01;
import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite;
/** * Unit test for simple App. */ public class AppTest extends TestCase { /** * Create the test case * * @param testName name of the test case */ public AppTest( String testName ) { super( testName ); }
/** * @return the suite of tests being tested */ public static Test suite() { return new TestSuite( AppTest.class ); }
/** * Rigourous Test :-) */ public void testApp() { assertTrue( true ); } } |
Bean class란?
- 예전에는 Visual한 컴포넌트를 Bean이라고 불렀지만, 근래 들어서는 일반적인 Java클래스를 Bean클래스라고 보통 말합니다.
- Bean클래스의 3가지 특징은 다음과 같습니다.
- 기본생성자를 가지고 있습니다.
- 필드는 private하게 선언합니다.
- getter, setter 메소드를 가집니다.
getName() setName() 메소드를 name 프로퍼티(property)라고 합니다. (용어 중요)
- 아래의 UserBean이라는 클래스는 생성하는 것을 사용자가 직접 하는 것이 아니라 컨테이너에 맡길 겁니다. 항상 기억해야 될 것은 내가 직접 하는 게 아니라 누군가가 대신 뭔가를 해줘야 되는데 이 때는 반드시 규칙들이 있어야 합니다.
- 그리고 한가지 더, 실제 이 클래스에 각각의 필드에 접근해서 일을 해야 될 텐데 다 private 하잖아요. 객체지향 프로그래밍에서 이 객체의 속성에 직접 접근하는 거는 별로 바람직하지 않다고 생각하기 때문에 항상 메서드(여기서는 게터, 세터)를 통해서 접근을 하시는 게 좋습니다.
- UserBean을 만들면 이 클래스를 Spring이 가진 공장이 만들어내게 해야합니다. 아무리 공장이 자동으로 한다고 해도 반드시 여러분이 알려줘야 될 정보는 알려줘야 된다는 것을 기억해야 합니다.
- UserBean을 src/java/main의 기존 패키지에 생성합니다.
UserBean.java
package kr.or.connect.diexam01;
//빈클래스 public class UserBean {
//필드는 private private String name; private int age; private boolean male;
//기본생성자를 반드시 가지고 있어야 한다. public UserBean() { }
public UserBean(String name, int age, boolean male) { //객체를 편하게 생성하기 위함. this.name = name; this.age = age; this.male = male; }
// setter, getter메소드는 프로퍼티라고 한다. public void setName(String name) { this.name = name; }
public String getName() { return name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public boolean isMale() { return male; }
public void setMale(boolean male) { this.male = male; }
}
|
Spring Bean Factory를 이용하여 Bean객체 이용하기
- 이 예제에서는 Spring의 bean 팩토리를 이용해서 bean을 생성합니다. 그렇게 하기 위해서는 Spring을 이용해야 합니다. Spring을 이용하시려면 Spring 라이브러리들이 필요할텐데 현재 우리는 Maven을 이용하기 때문에 Maven에다가 dependency 정보만 알려주면 될 거예요.
- 일단 properties 태그에다가 이런 설정을 하나 넣어줍니다.
<spring.version> 4.3.14.RELEASE</spring.version> - 위의 설정들은 상수처럼 사용할 수 있는 것입니다. dependency나 pom.xml 안에서 해당 값들이 자주 필요한 경우가 있습니다. 그럴 때 값 자체로 그냥 사용하는 게 아니라 spring.version이라는 이름으로 상수로 사용할 수 있도록 도와주는 거라고 생각하시면 됩니다.
- 이제 Spring 라이브러리를 추가합니다. 사용할 Spring에 관련된 dependency를 추가하시면 됩니다. 여기서는 spring공장이 필요하므로 maven repository 사이트에서 spring context를 검색하여 4. 대 중 가장 최신버전 dependency를 복붙하면 됩니다. 여기선 4.3.14를 씁니다.
- 이런 부분이 maven에서 편한 부분입니다. 왜냐하면 라이브러리를 하나씩 다 찾아서 여러분이 lib 디렉터리에다가 직접 넣고 사용하는 것은 매우 불편하기 때문입니다.
- properties에서 설정한 spring.version은 ${spring.version}과 같이 사용합니다. 이를 쓰는 이유는 여러 spring 기능들을 사용할 때 모든 것들에 대하여 버전을 통일시켜 변경하기 쉽게 하기 위함입니다.
1) pom.xml 파일을 다음과 같이 수정합니다.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>kr.or.connect</groupId> <artifactId>diexam01</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>diexam01</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version> 4.3.14.RELEASE</spring.version> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project> |
|
2) 추가된 라이브러리 확인합니다.
- 여기까지 한 일은 Spring 공장을 추가한 것입니다. 이제 해야 될 일은 뭐냐면 이 Spring 공장한테 UserBean에 대한 정보를 알려줘야 합니다.
3) resources 소스 폴더를 생성합니다.
|
|
|
|
|
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="userBean" class="kr.or.connect.diexam01.UserBean"></bean> </beans> |
- Spring 컨테이너는 이런 객체를 하나만 생성해서 가지고 있어요. 이렇게 객체를 딱 하나만 가지고 있는 것을 싱글턴 패턴이라고 합니다.
- 이렇게 Spring 설정 파일을 작성했으니까 해당 설정 파일을 읽어들이는 객체도 하나 생성을 해야겠죠.
- src/main/java의 패키지에 ApplicationContextExam01 클래스를 생성합니다. 이 클래스는 이 프로그램을 시작시킬 시작점 역할을 하게 됩니다. 그래서 main 메서드가 필요합니다.
- 그리고 spring이 가진 공장을 생성합니다. spring 공장 중 ApplicationContext라는 공장을 사용합니다. ApplicationContext는 실제로 인터페이스라서 ApplicationContext를 구현하고 있는 객체들 중에 ClassPathXmlApplicationContext라는 객체를 사용해서 bean을 가져오려 합니다.
- 인자에 classpath:applicationContext.xml이라는 문자열을 넣어 Bean정보를 알려줍니다. 그러니까 xml파일을 읽어서 bean 정보를 읽고 공장을 세울 수 있도록 알려줍니다.
- 생성한 다음 라인에 sysout을 뿌려 동작이 잘 하는지 확인합니다. 잘 가져왔다는 가정하에 이제 공장을 이용하여 객체 정보를 얻어오면 됩니다. UserBean에 대한 레퍼런스 변수를 선언합니다. 그러나 실제로 생성은 내가 하지 않습니다. 공장한테 getBean()이라는 메서드를 이용하여 UserBean의 정보를 얻어냅니다. 이때 getBean()의 리턴값은 object이므로 형변환이 필요합니다.
- 이제 getBean()에다가 "userBean"이라는 String을 보냈으니 공장은 해당 xml에서 id가 인자값이랑 일치하는 것이 있는지를 찾을 겁니다. 그리고 찾으면 등록되어있는 해당 클래스 이름을 알아낸 뒤 클래스를 생성합니다. 그런 다음에 생성한 클래스를 리턴할 것입니다. 즉, getBean()이라는 메서드가 해당 작업을 수행하는 것입니다.
- Bean을 하나 얻어왔을 겁니다. 얻어온 bean에다가 세터를 사용합니다. userBean.setName(“kang”); 그런 다음에 제대로 들어갔는지 게터를 통해 확인합니다.
- 이번에는 객체를 하나 더 얻어내보겠습니다. userBean2라는 이름으로 아까와 동일하게 코딩합니다. 그리고 if문을 사용하여 같은지 확인해보겠습니다.
- 실행하면 같다고 나옵니다. 왜냐면 싱글턴을 이용했기 때문입니다. 그러니까 사용자가 계속 getBean() 해서 요청을 한다고 하더라도 그 객체를 계속 만들어내는 게 아니라 하나 만든 bean을 이용하는 거다라고 생각하시면 될 것 같습니다.
ApplicationContextExam01.java
package kr.or.connect.diexam01;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ApplicationContextExam01 {
public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext( "classpath:applicationContext.xml"); System.out.println("초기화 완료.");
UserBean userBean = (UserBean)ac.getBean("userBean"); userBean.setName("kang"); System.out.println(userBean.getName());
UserBean userBean2 = (UserBean)ac.getBean("userBean"); if(userBean == userBean2) { System.out.println("같은 인스턴스이다."); }
} } |
- 잠깐 정리하면서 설명해보겠습니다.
- ApplicationContext는 인터페이스입니다. 그렇다는 것은 ApplicationContext을 구현하는 다양한 컨테이너가 존재할 것이고 그 중에서 이런 xml 파일을 classpath에서 읽어들여서 사용하는 객체가 바로 ClassPathXmlApplicationContext 입니다.
- main의 resources 폴더는 소스폴더이며 이 폴더에서 생성한 설정파일, xml은 자동으로 classpath로 지정이 됩니다. Java 디렉터리에서 만들어진 클래스들과 마찬가지로 bean 디렉터리에 생성되어 있을 거다는 거 알고 계시면 되겠고 그렇기 때문에 ClassPathXmlApplicationContext로 읽어서 사용할 수가 있다고 이해하시면 됩니다.
- ClassPathXmlApplicationContext 인스턴스가 생성될 때 생성자 파라미터로 지정된 설정 파일을 읽어들인 후에 그 안에 선언된 bean들을 모두 메모리에 올려줄 겁니다. 만약 applicationContext.xml 파일에 등록한 bean들이 여러 개가 있다면 그 bean들의 정보를 다 읽어들여가지고 설정되어 있는 객체들을 전부 생성해서 메모리에다가 올려놓아요. 만약 이때 문제가 발생하면 해당 애플리케이션은 종료가 됩니다.
- ApplicationContext가 가지고 있는 getBean()이라는 메서드가 존재하죠. getBean() 메서드의 파라미터로 xml의 설정에다가 적어놓았던 id 값을 넣어주면 해당 객체의 레퍼런스가 반환이 됩니다. 이런 Spring 설정 파일의 객체들은, Spring이 제공하는 공장이 만들어내는 객체들은 다양한 객체를 생성할 수가 있어요. 그래서 getBean()은 object 타입으로 리턴을 하게 됩니다. 필요에 따라서 형변환을 해야 사용할 수 있습니다.
- id를 파라미터로 넘겨 등록돼 있는 userBean을 얻어내서 얻어낸 bean한테 setter를 통해 값을 설정하고 getter를 통해 값을 얻어 올 수 있습니다.
- 그리고 getBean()으로 등록되어 있는 동일한 id로 객체를 2개 만들어 보면 처음에 얻은 userBean 객체랑 두 번째 요청해서 얻은 userBean 객체랑 같은 객체라는 것을 알 수 있었습니다. 이 bean 공장이 싱글턴 패턴을 이용해서 bean들을 생성하기 때문에 같은 객체라는 것을 알 수 있었습니다.
- 이렇게 객체를 대신 생성해주고 싱글턴으로 관리해주는 기능 등을 IOC 제어의 역전이라고 합니다.
DI 확인하기
- 이번에는 DI 즉 의존성 주입을 확인해보도록 하겠습니다.
- Car와 Engine이라는 클래스 2개를 src/main/java의 패키지에 생성합니다.
//////////Engine.java////////// package kr.or.connect.diexam01;
public class Engine { public Engine() { System.out.println("Engine 생성자"); }
public void exec() { System.out.println("엔진이 동작합니다."); } } |
|
//////////Car.java/////////// package kr.or.connect.diexam01;
public class Car { Engine v8;
public Car() { System.out.println("Car 생성자"); }
public void setEngine(Engine e) { this.v8 = e; }
public void run() { System.out.println("엔진을 이용하여 달립니다."); v8.exec(); } } |
|
//////////main///////////// Engine e = new Engine(); Car c = new Car(); c.setEngine( e ); c.run(); |
실제 Car라는 자동차 클래스가 제대로 동작하려면 어떤 코드가 있어야 할까요? 자동차는 엔진이 필요하니까 일단 Engine 클래스가 하나 생성이 되어야 합니다. 다음에는 자동차가 만들어질 거예요. 그리고 자동차가 갖고 있는 setEngine() 메서드에다가 먼저 생성된 엔진을 넣어주면 되겠죠. 그런 다음에 자동차한테 달리라고 실행하면 엔진이 생성됐고, 자동차가 생성됐고 다음은 엔진은 이용해서 달리고 엔진이 동작합니다. |
- 개발자가 직접 객체를 생성한다면 이런 순서대로 만들어져야 합니다. 그러나 이제는 이런 것을 직접 만드는 것이 아니라 배운대로 객체를 생성하고 있는 부분을 제어의 역전으로 넘겨야 합니다. 그러니까 이렇게 객체가 생성이 되어야 하는 것을 Spring IoC 컨테이너가 만들도록 해야 합니다.
- 이런 과정을 Spring 컨테이너가 하게 하려면 설정 파일에다가 해당 Bean들을 등록해야 합니다.
- 그 부분을 수행하게 하기 위해서 resources 폴더 안에 있는 applicationConfig.xml 파일에다가 bean을 하나 등록해보도록 합니다.
- 엔진을 'e'라고 만들었었으니까 id를 'e'하시면 될 것 같고요. 그런 다음에 class를 패키지의 Engine이라는 클래스를 정의해주면 됩니다. 자동차 Car 객체도 똑같이 등록을 하시면 됩니다. id는 c로 하고 클래스를 적어서 등록합니다.
- 이렇게 하면 Engine과 Car의 인스턴스가 싱글턴으로 생성이 될 겁니다. 하지만 xml에서 Car에다가 Engine을 Set 하라는 의미의 코드가 존재하지 않습니다. Car 인스턴스의 엔진을 Set 하기 위해 해당 bean의 <bean>과 </bean> 사이에 property라고 하는 element를 사용할 수 있습니다. bean의 getter, setter를 프로퍼티라고 합니다.
- <property>의 name에다가 name="engine"이라고 할 거고요. 그 뒤에 ref를 적어주는데 ref는 참조할 bean을 의미하며 지금 이 xml에서 생성된 bean 중 참조할 bean을 입력하면 됩니다. 다시 설명하면 property는 setter, getter 메서드 중에 하나를 의미합니다. 그리고 여기 적어놓은 name이 "engine"인 property는 setEngine 혹은 getEngine이라는 메서드를 의미하는데 지금 bean 태그 안에 들어가 있고 bean 태그 안에서는 모두 값을 설정하므로 setEngine()이라는 메서드를 의미할 것입니다.
- Car.java에서 setEngine()이라는 메서드는 파라미터로 Engine 타입을 받는다고 했어요. 그래서 ref 부분은 'id가 "e"로 선언된 인스턴스를 setEngine() 메서드에 파라미터로 전달해주세요' 이런 의미를 가집니다.
- 그러니까 지금 여기서 우리가 xml에서 수행해준 것이 실제 메인 메서드에서 실행했다면 2개의 객체를 생성하고 엔진을 자동차에 설정한 것(c.setEngine(e);)까지 실행한 거라고 생각하시면 될 거예요.
applicationConfig.xml | 자바 코드로 변환 시 |
<bean id="e" class="kr.or.connect.diexam01.Engine"></bean> <bean id="car" class="kr.or.connect.diexam01.Car"> <property name="engine" ref="e"></property> </bean> |
Engine e = new Engine(); Car c = new Car(); c.setEngine( e ); |
- 이번에는 이런 설정들을 읽어들여가지고 실행하는 ApplicationContextExam02를 만들겠습니다.
- 실행하기 위한 ApplicationContextExam02 클래스를 생성하고 메인 메서드를 선언하여 Spring 컨테이너 객체를 생성합니다. Spring 컨테이너 중에 ApplicationContext를 이용할 거고 이 ApplicationContext를 구현하고 있는 ClassPathXmlApplicationContext를 생성합니다. applicationContext.xml에 설정해놓은 정보를 넘기기 위해 ClassPathXmlApplicationContext 클래스의 생성자에다가 해당 xml 파일에 대한 정보를 입력합니다.
- 그 다음 자동차 객체를 하나 생성합니다. 자동차 객체를 생성하는 것이 이제 주체가 내가 아니라 이 Spring 컨테이너이므로 ApplicationContext가 갖고 있는 getBean()이라는 메서드를 사용하여 자동차 객체를 생성합니다. 이때 applicationContext.xml에다가 등록했던 Bean id를 파라미터로 넘깁니다. 아까 c라고 등록했기 때문에 파라미터에 "c"를 넣고 꺼내오면 엔진을 탑재한 자동차 객체가 만들어졌을 겁니다.
- 이 자동차가 가진 run()이라는 메서드를 실행하면 콘솔에서 엔진이 먼저 생성되고, 자동차가 생성되고 엔진을 이용해서 달리는데 실제 차의 진짜 엔진 객체가 동작하는 것을 볼 수 있습니다.
- 콘솔을 보면 다음과 같이 실행된 것을 알 수 있습니다.
ApplicationContextExam02.java
package kr.or.connect.diexam01;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ApplicationContextExam02 {
public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext( "classpath:applicationContext.xml");
Car car = (Car)ac.getBean("c"); car.run(); } } |
- 이렇게 어떤 객체에게 객체를 주입하는 것을 DI라고 합니다. DI를 사용했을 때 장점은 사용자가 사용할 클래스만 알고 있으면 된다는 것입니다. Car에 속해 있는 Engine에 대해서는 몰라도 Car를 사용할 수 있다는 것입니다.
- 나중에 Car를 상속받고 있는 Bus라는 클래스가 생성되도록 xml 파일만 바꿔주고 그리고 Bus가 상속받고 있는 Engine 클래스도 Electric Engine으로 주입받도록 바꿔준다면 실행 클래스의 코드는 하나도 바뀌지 않고 전기 버스가 동작하도록 할 수 있다는 것입니다.
- 여기까지 xml을 이용해서 bean을 생성하고 주입하는 방법에 대해서 알아보았습니다.
- Spring이 버전 업이 되면서 xml보다는 어노테이션과 자바 Config를 함께 사용해서 설정하는 방법이 더 많이 이용되고 있습니다. 그러니까 이 방법만 하더라도 일일이 bean을 하나씩 등록해야 되는 점들이 또 불편하다고 생각된 거죠. 그러다 보니 또 버전이 올라가면서 조금 더 편한 방법, 조금 더 편한 방법 이렇게 바뀌어 가는 거 같습니다. 다음 시간에는 이번 시간에 배웠던 설정을 자바 Config와 어노테이션을 이용해서 사용하는 방법으로 수정해보도록 하겠습니다.
생각해보기
- Spring컨테이너가 관리하는 객체를 빈(Bean)이라고 말합니다. (여러분들이 직접 new연산자로 생성해서 사용하는 객체는 빈(Bean)이라고 말하지 않습니다.) Spring은 빈을 생성할 때 기본적으로 싱글톤(Singleton)객체로 생성합니다. 싱글톤이란 메모리에 하나만 생성한다는 것입니다. 메모리에 하나만 생성되었을 경우, 해당 객체를 동시에 이용한다면 어떤 문제가 발생할 수 있을까요? 이런 문제를 해결하려면 어떻게 해야할까요? ( 참고로 Spring에서 빈을 생성할 때 스코프(scope)를 줄 수 있습니다. 스코프를 줌으로써 기본으로 설정된 싱글톤 외에도 다른 방법으로 객체를 생성할 수 있습니다. )
참고 자료
[참고링크] Appendix C. XML Schema-based configuration
https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/xsd-config.html
https://www.slipp.net/wiki/pages/viewpage.action?pageId=25528177
'부스트코스 웹 프로그래밍 > 3. 웹 앱 개발: 예약서비스 1' 카테고리의 다른 글
8. Spring JDBC - BE (1) (0) | 2019.08.04 |
---|---|
7. Spring Core - BE (4) (0) | 2019.08.04 |
7. Spring Core - BE (2) (0) | 2019.08.03 |
7. Spring Core - BE (1) (0) | 2019.08.03 |
6. Tab UI 실습 - FE (2) (0) | 2019.08.03 |