[Spring] 003 강의노트

배윤석·2022년 7월 4일
0

Spring

목록 보기
3/6

Spring Framew

DI

dependency injection
객체에 대한 생성(초기화), 소멸의 관리

선언하는 방법

  1. 전통적인 방법
    xml : spring, bean, configuration file
    servlet(filter)web.xml
  2. 현대적인 방법
    annotation : (@pathName)

annotation

java file : POJO

POJO

오래된 방식의 간단한 자바 오브젝트
➡ 특정 기술에 종속되어 동작하는 것이 아닌 순수한 자바 객체
POJO 위키백과

POJO의 개념은 MyBatis에서도 사용해봤다.

xml 로 사용할 때도 있었고, annotation 으로 사용할 때도 있었다.
annotation 을 사용할때 바로 POJO의 개념을 사용했다.

Annotation의 사용

  1. Hello.java에서 Interface를 구현하고
  2. HelloBean1.javaHelloBean2.java 의 내용을 실행시켜줄 것이다.
  3. BeanConfig.java 의 입력된 내용으로 호출할 방식을 지정해줄 것이다.
  4. App.java에서 내용을 호출해줄 것이다.
package com.exam.spring01.model;

public interface Hello {
	void sayHello(String name);
}
package com.exam.spring01.model;

public class HelloBean1 implements Hello {

	@Override
	public void sayHello(String name) {
		// TODO Auto-generated method stub
		System.out.println(name + "님 안녕하세요");
	}
}
package com.exam.spring01.model;

public class HelloBean2 implements Hello {

	@Override
	public void sayHello(String name) {
		// TODO Auto-generated method stub
		System.out.println("Hello " + name);
	}
}
package com.exam.spring01.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.exam.spring01.model.Hello;
import com.exam.spring01.model.HelloBean1;
import com.exam.spring01.model.HelloBean2;

@Configuration
public class BeanConfig {
	
	@Bean
	public HelloBean1 helloBean1() {
		return new HelloBean1();
	}
	
	@Bean
	public HelloBean2 helloBean2() {
		return new HelloBean2();
	}
	
	@Bean
	public Hello hello1() {
		return new HelloBean1();
	}
	
	@Bean
	public Hello hello2() {
		return new HelloBean2();
	}
}
package com.exam.spring01;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.exam.spring01.config.BeanConfig;
import com.exam.spring01.model.Hello;
import com.exam.spring01.model.HelloBean1;
import com.exam.spring01.model.HelloBean2;

public class App {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx
		= new AnnotationConfigApplicationContext(BeanConfig.class);
		
		HelloBean1 helloBean1 = (HelloBean1)ctx.getBean("helloBean1");
		helloBean1.sayHello("이순신");
		
		HelloBean2 helloBean2 = (HelloBean2)ctx.getBean("helloBean2");
		helloBean2.sayHello("강감찬");
		
		// 다형성 활용
		Hello hello = (Hello)ctx.getBean("helloBean1");
		hello.sayHello("홍길동");
		
		hello = (Hello)ctx.getBean("helloBean2");
		hello.sayHello("척준경");
		
		hello = (Hello)ctx.getBean("hello1");
		hello.sayHello("허준");
		
		hello = (Hello)ctx.getBean("hello2");
		hello.sayHello("허난설헌");
		
		ctx.close();
	}
}

App.java의 실행결과

09:59:35.117 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
09:59:35.131 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
09:59:35.244 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
09:59:35.247 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
09:59:35.249 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
09:59:35.255 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig'
09:59:35.261 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'helloBean1'
09:59:35.278 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'helloBean2'
09:59:35.281 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'hello1'
09:59:35.282 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'hello2'
이순신님 안녕하세요
Hello 강감찬
홍길동님 안녕하세요
Hello 척준경
허준님 안녕하세요
Hello 허난설헌
09:59:35.321 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 09:59:35 KST 2022

이렇게 사용하는 것을 Annotation 기법이라고 한다.
Config 파일의 Method 명이 xml파일에서의 bean name과 동일하게 사용되는 것을 확인할 수 있다.


[Exception] No bean name

Config파일에서 Method를 추가하지 않고 App.java 실행시켜 보면 어떻게 될까?

hello = (Hello)ctx.getBean("helloBean3");
hello.sayHello("백경");

실행시켜 보면, No bean name Exception이 발생하는 것을 확인할 수 있다.

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'helloBean3' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:816)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1288)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1109)
	at com.exam.spring01.App.main(App.java:34)

그렇다면 등록된 bean으로 변경해서 실행시키면?

hello = (Hello)ctx.getBean("helloBean2");
hello.sayHello("백경");

Exception이 발생하지 않고 정상적으로 실행된다.

Hello 백경

Scope

객체의 생성 시점을 지정해줄 수 있다.

  1. Singleton
    프로그램 초기 실행 시점에 객체를 생성한다.
    또한 Scope의 디폴트 값이기도 하다.
  2. Prototype
    객체 호출 시점에 객체를 생성한다.

프로그램 코드는 인터페이스로 구현받는 부분은 동일하기 때문에 Pass.

package com.exam.spring02.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import com.exam.spring02.model.Hello;
import com.exam.spring02.model.HelloBean1;
import com.exam.spring02.model.HelloBean2;

@Configuration
public class BeanConfig {
	
	@Bean
	public HelloBean1 helloBean1() {
		return new HelloBean1();
	}
	
	@Bean
	@Scope("singleton")
	public HelloBean2 helloBean2() {
		return new HelloBean2();
	}
	
	@Bean
	@Scope("prototype")
	public HelloBean2 helloBean3() {
		return new HelloBean2();
	}
}
package com.exam.spring02;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.exam.spring02.model.HelloBean1;
import com.exam.spring02.model.HelloBean2;
import com.exam.spring02.config.BeanConfig;

public class App {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		AnnotationConfigApplicationContext ctx
		= new AnnotationConfigApplicationContext(BeanConfig.class);
		
		// Scope : Default
		HelloBean1 helloBean11 = (HelloBean1)ctx.getBean("helloBean1");
		HelloBean1 helloBean12 = (HelloBean1)ctx.getBean("helloBean1");
		// Scope : singleton
		HelloBean2 helloBean21 = (HelloBean2)ctx.getBean("helloBean2");
		HelloBean2 helloBean22 = (HelloBean2)ctx.getBean("helloBean2");
		// Scope : Prototype
		HelloBean2 helloBean31 = (HelloBean2)ctx.getBean("helloBean3");
		HelloBean2 helloBean32 = (HelloBean2)ctx.getBean("helloBean3");
		
		System.out.println("helloBean11 : "+helloBean11);
		System.out.println("helloBean12 : "+helloBean12);
		System.out.println("helloBean21 : "+helloBean21);
		System.out.println("helloBean22 : "+helloBean22);
		System.out.println("helloBean31 : "+helloBean31);
		System.out.println("helloBean32 : "+helloBean32);
		
		ctx.close();
	}
}
10:31:19.927 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
10:31:19.940 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
10:31:20.056 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
10:31:20.058 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
10:31:20.059 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
10:31:20.064 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig'
10:31:20.067 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'helloBean1'
10:31:20.079 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'helloBean2'
helloBean11 : com.exam.spring02.model.HelloBean1@169bb4dd
helloBean12 : com.exam.spring02.model.HelloBean1@169bb4dd
helloBean21 : com.exam.spring02.model.HelloBean2@1f9e9475
helloBean22 : com.exam.spring02.model.HelloBean2@1f9e9475
helloBean31 : com.exam.spring02.model.HelloBean2@3aa078fd
helloBean32 : com.exam.spring02.model.HelloBean2@d23e042
10:31:20.115 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 10:31:19 KST 2022

실행결과를 확인해보면,
Scope를 입력하지 않은 bean은 두 객체의 주소값이 동일하다.
ScopeSingleton으로 입력한 bean도 마찬가지다.

그런데 ScopePrototype으로 입력한 bean은 두 객체의 주소값이 틀린 것을 확인할 수 있다.


생성자 주입

Annotation 으로 생성자를 주입해보자.

package com.exam.spring03.model;

public class HelloBean {
	private String name;
	
	public HelloBean() {
		// TODO Auto-generated constructor stub
		System.out.println("HelloBean() 호출");
		this.name = "홍길동";
	}
	
	public HelloBean(String name) {
		System.out.println("HelloBean(String name) 호출");
		this.name = name;
	}
	
	public void sayHello() {
		System.out.println(name + "님 안녕하세요");
	}
}
package com.exam.spring03.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.exam.spring03.model.HelloBean;

@Configuration
public class BeanConfig {
	
	@Bean
	public HelloBean helloBean1() {
		return new HelloBean();
	}
	
	@Bean
	public HelloBean helloBean2() {
		return new HelloBean("척준경");
	}
}
package com.exam.spring03;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.exam.spring03.config.BeanConfig;
import com.exam.spring03.model.HelloBean;

public class App {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		AnnotationConfigApplicationContext ctx
		= new AnnotationConfigApplicationContext(BeanConfig.class);
		
		HelloBean helloBean1 = (HelloBean)ctx.getBean("helloBean1");
		helloBean1.sayHello();
		
		HelloBean helloBean2 = (HelloBean)ctx.getBean("helloBean2");
		helloBean2.sayHello();
		
		ctx.close();
	}
}
10:42:10.391 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
10:42:10.407 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
10:42:10.513 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
10:42:10.515 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
10:42:10.516 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
10:42:10.521 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig'
10:42:10.525 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'helloBean1'
HelloBean() 호출
10:42:10.543 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'helloBean2'
HelloBean(String name) 호출
홍길동님 안녕하세요
척준경님 안녕하세요
10:42:10.569 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 10:42:10 KST 2022

실행 결과를 보면서 다시 코드를 확인해보자.

  1. 홍길동 이 결과로 나온 경우
    HelloBean 클래스의 디폴트 생성자에서 이름을(들어온 값이 없을 경우) 홍길동 으로 지정해줬다.
  2. 척준경 이 경과로 나온 경우
    BeanConfig 클래스에서 Bean으로 객체 생성을 해주면서 척준경 으로 값을 주입해주었다.

Annotation 예제 - 10:48 경

여기까지 배웠던 Annotation 기법을 활용해보자.

어제 배웠던 xml 파일로 생성했던 프로젝트 중에서,
게시판 형식을 annotation 으로 구현해보자.

DTO : BoardTO.java
DAO : WriteAction.java
Config : BeanConfig.java
호출 : App.java

package com.exam.spring04.model;

public class BoardTO {
	private int seq;
	private String subject;
	
	public int getSeq() {
		return seq;
	}
	public void setSeq(int seq) {
		System.out.println("setSeq(int seq)");
		this.seq = seq;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		System.out.println("setSubject(String subject)");
		this.subject = subject;
	}
}
package com.exam.spring04.model;

public class WriteAction {
	private BoardTO to;
	
	public BoardTO getTo() {
		return to;
	}
	
	public void setTo(BoardTO to) {
		this.to = to;
	}
	
	public void execute() {
		System.out.println("execute() 호출");
		System.out.println(to.getSeq());
		System.out.println(to.getSubject());
	}
}
package com.exam.spring04.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.exam.spring04.model.BoardTO;
import com.exam.spring04.model.WriteAction;

@Configuration
public class BeanConfig {
	private BoardTO dto;
	
	@Bean
	public WriteAction action() {
		this.dto = new BoardTO();
		this.dto.setSeq(1);
		this.dto.setSubject("제목");
		WriteAction writeAction = new WriteAction();
		writeAction.setTo(this.dto);
		return writeAction;
	}
}
package com.exam.spring04;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.exam.spring04.config.BeanConfig;
import com.exam.spring04.model.WriteAction;

public class App {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		AnnotationConfigApplicationContext ctx
		= new AnnotationConfigApplicationContext(BeanConfig.class);
		
		WriteAction action = (WriteAction)ctx.getBean("action");
		action.execute();
		
		ctx.close();
	}
}
10:58:44.789 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
10:58:44.802 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
10:58:44.899 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
10:58:44.901 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
10:58:44.903 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
10:58:44.908 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig'
10:58:44.911 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'action'
setSeq(int seq)
setSubject(String subject)
execute() 호출
1
제목
10:58:44.955 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 10:58:44 KST 2022

xml 파일로 생성했을때와 동일하게 구현이 되었다.
코드를 보면서 실행결과를 다시 되짚어 보자.

  1. BeanConfig.java 에서 BoardTO를 인스턴스화 하고
    해당 변수에 seq 값과 subject 값을 강제로 넣어주었다.
    그리고 WriteAction을 생성하여 seq와 subject 값이 들어간 BoardTO를 매개변수로 입력했다.
    해당 Bean( action )을 호출시에는 방금 값이 들어간 WriteAction을 return 하도록 한다.
  2. App.java AnnotationConfigApplicationContext 를 사용해 BeanConfig.java 를 클래스화 해서 가져왔다.
  3. WriteAction을 자료형으로 하는 action 변수를 생성한다.
    이때의 action 변수에 들어갈 데이터는 2번 항목에서 가져온 action 이라는 이름의 Bean을 호출해서 가져온다.
  4. action의 메서드를 실행시켰다.

MariaDB 연동 예제 - 11:33 경

annotation 기법을 사용해서 DB와 연동하는 프로그램을 생성한다.

package com.exam.spring01.model2;

public interface BoardAction {
	void execute();
}
package com.exam.spring01.model2;

import java.util.ArrayList;

import com.exam.spring01.model1.BoardDAO;

public class ListAction implements BoardAction {
	private BoardDAO dao;
	
	public ListAction(BoardDAO dao) {
		// TODO Auto-generated method stub
		this.dao = dao;
	}
	
	@Override
	public void execute() {
		// TODO Auto-generated method stub
		ArrayList<String> lists = dao.boardList();
		for(String list : lists) {
			System.out.println(list);
		}
	}
}
package com.exam.spring01.model1;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;

public class BoardDAO {
	private Connection conn;
	
	public BoardDAO() {
		// TODO Auto-generated constructor stub
		String url = "jdbc:mariadb://localhost:3306/board";
		String user = "board";
		String password = "!123456";
		
		try {
			Class.forName("org.mariadb.jdbc.Driver");
			this.conn = DriverManager.getConnection(url, user, password);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			System.out.println("[Error] "+e.getMessage());
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			System.out.println("[Error] "+e.getMessage());
		}
	}
	
	public ArrayList<String> boardList() {
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		ArrayList<String> lists = new ArrayList<String>();
		
		try {
			String sql = "SELECT * "
					+ "FROM board1 "
					+ "LIMIT 0,5 ";
			pstmt = this.conn.prepareStatement(sql);
			
			rs = pstmt.executeQuery();
			
			while(rs.next()) {
				lists.add(String.format("%s [%s] %s", 
						rs.getString("seq"), 
						rs.getString("writer"), 
						rs.getString("subject")));
			}
		} catch(SQLException e) {
			// TODO Auto-generated catch block
			System.out.println("[Error] "+e.getMessage());
		} finally {
			if(rs != null) try {rs.close();} catch(SQLException e) {}
			if(pstmt != null) try {pstmt.close();} catch(SQLException e) {}
			if(conn != null) try {conn.close();} catch(SQLException e) {}
		}
		
		return lists;
	}
}
package com.exam.spring01.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import com.exam.spring01.model1.BoardDAO;
import com.exam.spring01.model2.ListAction;

@Configuration
@Scope("prototype")
public class BeanConfig {

	public BoardDAO dao() {
		return new BoardDAO();
	}
	
	@Bean
	public ListAction action() {
		return new ListAction(dao());
	}
}
package com.exam.spring01;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.exam.spring01.config.BeanConfig;
import com.exam.spring01.model2.BoardAction;

public class App {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx
		= new AnnotationConfigApplicationContext(BeanConfig.class);
		
		BoardAction action = (BoardAction)ctx.getBean("action");
		action.execute();
		
		ctx.close();
	}
}
11:48:45.907 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
11:48:45.921 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
11:48:46.021 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
11:48:46.024 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
11:48:46.025 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
11:48:46.030 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'action'
1 [이름] 제목
3 [test1] subject11
5 [글쓴이당] 제목이당33
6 [추가추가글쓴이를] 추가한다제목을
8 [22] 11
11:48:46.198 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 11:48:45 KST 2022

작성한 코드를 보면서 다시 확인해보자.
1. BoardAction 인터페이스를 만들어줬다.
2. BoardAction을 구현한 ListAction.java 을 만들었다.
3. DB 와 연동하여 데이터를 가져올 코드인 BoardDAO.java 를 생성
4. BeanConfig.java 에서 BoardDAO를 호출하고 ListAction에 넣어준다.
ListAction은
5. 4️⃣ 항목을 어노테이션으로 Bean 지정해준다.
6. App.java 에서 AnnotationConfigApplicationContext 을 통하여 Bean을 호출한다.


Spring Bean Life Cycle 이란?

Spring Container의 관리 항목

  1. Bean Object를 생성
  2. Property를 할당
  3. 객체의 초기화를 수행
  4. 객체의 사용이 끝나면 소멸

Spring Container의 관리 방식

  1. Spring이 제공하는 특정 인터페이스를 상속받아 Bean을 구현
  2. Spring 설정에서 특정 메서드 호출을 지정

Spring은 Life Cycle에 특화된 인터페이스를 제공한다.
ex)
Bean 객체의 클래스가 InitalizingBean 인터페이스를 구현하고 있으면 InitalizingBean 인터페이스에 정의된 메서드를 호출해서 Bean 객체가 초기화를 진행할 수 있도록 한다.
또한 스프링 설정에서 초기화 메서드를 지정하면 스프링은 그 메서드를 호출해서 Bean이 초기화를 수행할 수 있도록 한다.

참고한 블로그 : Spring - 빈 객체의 라이프 사이클

참고하면 좋을 영문 사이트 : Spring Bean Life Cycle


Spring Bean Life Cycle 코드

Spring이 객체를 만들기 위해서 흘러가는 과정을 확인해 보자.

package com.exam.spring01.model;

public interface BoardAction {
	void execute();
}
package com.exam.spring01.model;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class WriteAction implements BoardAction, BeanNameAware, BeanClassLoaderAware, 
		BeanFactoryAware, ApplicationContextAware, DisposableBean, InitializingBean {
	private String writer;
	
	public WriteAction() {
		// TODO Auto-generated constructor stub
		System.out.println("1. 빈의 생성자 실행");
	}
	
	public void setWriter(String writer) {
		System.out.println("2. setWriter(String Writer) 실행");
		this.writer = writer;
	}

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		System.out.println("*. execute() 실행");
		System.out.println(this.writer);
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("8. afterPropertiesSet() 실행");
	}

	@Override
	public void destroy() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("11. destroy() 실행");
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		// TODO Auto-generated method stub
		System.out.println("6. setApplicationContext(ApplicationContext applicationContext) 실행");
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		// TODO Auto-generated method stub
		System.out.println("5. setBeanFactory(BeanFactory beanFactory) 실행");
	}

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		// TODO Auto-generated method stub
		System.out.println("4. setBeanClassLoader(ClassLoader classLoader) 실행");
	}

	@Override
	public void setBeanName(String name) {
		// TODO Auto-generated method stub
		System.out.println("3. setBeanName(String name) 실행");
	}
}
package com.exam.spring01.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import com.exam.spring01.model.WriteAction;

@Configuration
@Scope("prototype")
public class BeanConfig {

	@Bean
	public WriteAction writeAction() {
		WriteAction action = new WriteAction();
		action.setWriter("홍길동");
		return action;
	}
}
package com.exam.spring01;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.exam.spring01.config.BeanConfig;
import com.exam.spring01.model.BoardAction;

public class App {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx
		= new AnnotationConfigApplicationContext(BeanConfig.class);
		
		BoardAction action = (BoardAction)ctx.getBean("writeAction");
		action.execute();
		
		ctx.close();
	}
}
12:33:32.073 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
12:33:32.089 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
12:33:32.208 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
12:33:32.210 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
12:33:32.212 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
12:33:32.216 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'writeAction'
1. 빈의 생성자 실행
2. setWriter(String Writer) 실행
3. setBeanName(String name) 실행
4. setBeanClassLoader(ClassLoader classLoader) 실행
5. setBeanFactory(BeanFactory beanFactory) 실행
6. setApplicationContext(ApplicationContext applicationContext) 실행
8. afterPropertiesSet() 실행
*. execute() 실행
홍길동
12:33:32.269 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 12:33:32 KST 2022
11. destroy() 실행

코드와 실행결과를 비교해보자.
내가 호출은 안해도 Bean 객체의 Life Cycle과 관련된 메서드는 Spring이 자동적으로 호출해주는 것을 확인할 수 있다.


Spring Bean Life Cycle 순서도


Spring Bean Life Cycle 사진 출저
1. IoC 컨테이너 내에서 클래스 생성자 를 사용하여 스프링 빈을 생성한다.
2. 이제 setter 메서드를 사용하여 종속성 주입을 수행한다.
3. 의존성 주입이 완료되면 BeanNameAware.setBeanName()가 호출된다.
이 Bean을 생성한 Bean Factory에 Bean의 이름을 설정한다.
4. 이제 <code>BeanClassLoaderAware.setBeanClassLoader()가 호출되어 빈 인스턴스에 빈 클래스 로더를 제공한다.
5. 이제 빈 인스턴스에 소유 팩토리를 제공하는 <code>BeanFactoryAware.setBeanFactory()가 호출된다.
6. 이제 IoC 컨테이너가 .BeanPostProcessor.postProcessBeforeInitialization를 Bean에 호출한다.
이 방법을 사용하면 원래 빈에 래퍼를 적용할 수 있다.
7. 이제 어노테이션 @PostConstruct가 호출된다.
8. 이후 @PostConstruct 메소드 InitializingBean.afterPropertiesSet()가 호출된다.
9.init-method 이제 XML 설정에서 Bean의 속성으로 지정한 메소드 를 호출한다.
10. 그리고 나서 BeanPostProcessor.postProcessAfterInitialization()호출된다.
또한 Bean에 래퍼를 적용하는 데 사용할 수도 있다.
11. 이제 빈 인스턴스를 사용할 준비가 되었다. Bean을 사용하여 작업을 수행한다.
12. 이제 다음 ApplicationContext을 사용하여 종료할 때 registerShutdownHook() 어노테이션 @PreDestroy가 호출된다.
13. DisposableBean.destroy() 메소드가 bean에서 호출된 후
14. 이제 XML 설정에서 Bean의 속성으로 지정된 메소드 destroy-method가 호출된다.
15. garbage conllection 전에 finalize()의 메소드 Object가 호출된다.

간략하게 정리한 것으로 보자.


Bean 객체의 전처리와 후처리

package com.exam.spring01.model;

import org.springframework.beans.BeansException;
… 생략 …
import org.springframework.context.ApplicationContextAware;

public class WriteAction implements BoardAction, BeanNameAware, BeanClassLoaderAware, 
		BeanFactoryAware, ApplicationContextAware, DisposableBean, InitializingBean {
	private String writer;

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		System.out.println("*. execute() 실행");
		System.out.println(this.writer);
	}
    
    … 생략 …
	
	// 전처리
	public void init_method() {
		System.out.println("9. init_method() 전처리메서드 실행");
	}
	
	// 후처리
	public void destory_method() {
		System.out.println("12. destory_method() 후처리메서드 실행");
	}
}
package com.exam.spring01.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import com.exam.spring01.model.WriteAction;

@Configuration
@Scope("prototype")
public class BeanConfig {

	@Bean(initMethod = "init_method", destroyMethod = "destory_method")
	public WriteAction writeAction() {
		WriteAction action = new WriteAction();
		action.setWriter("홍길동");
		return action;
	}
}
14:23:26.636 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
14:23:26.651 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
14:23:26.751 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
14:23:26.753 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
14:23:26.754 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
14:23:26.758 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'writeAction'
1. 빈의 생성자 실행
2. setWriter(String Writer) 실행
3. setBeanName(String name) 실행
setBeanName : writeAction
4. setBeanClassLoader(ClassLoader classLoader) 실행
5. setBeanFactory(BeanFactory beanFactory) 실행
6. setApplicationContext(ApplicationContext applicationContext) 실행
8. afterPropertiesSet() 실행
9. init_method() 전처리메서드 실행
*. execute() 실행
홍길동
11. destroy() 실행
12. destory_method() 후처리메서드 실행
14:23:26.804 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 14:23:26 KST 2022

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-4.3.xsd">
	
	<bean id="writeAction" class="com.exam.spring01.model.WriteAction" scope="prototype" init-method="init_method" destroy-method="destroy_method">
		<property name="writer" value="홍길동"></property>
	</bean>
</beans>
package com.exam.spring01;

import org.springframework.context.support.GenericXmlApplicationContext;

import com.exam.spring01.model.BoardAction;

public class App02 {
	public static void main(String[] args) {
		GenericXmlApplicationContext ctx
		= new GenericXmlApplicationContext("classpath:com/exam/spring01/context.xml");
		
		BoardAction action = (BoardAction)ctx.getBean("writeAction");
		action.execute();
		
		ctx.removeBeanDefinition("writeAction");
		
		ctx.close();
	}
}
14:41:35.592 [main] DEBUG o.s.b.f.xml.XmlBeanDefinitionReader - Loaded 1 bean definitions from class path resource [com/exam/spring01/context.xml]
14:41:35.594 [main] DEBUG o.s.c.s.GenericXmlApplicationContext - Refreshing org.springframework.context.support.GenericXmlApplicationContext@1d7acb34
1. 빈의 생성자 실행
2. setWriter(String Writer) 실행
3. setBeanName(String name) 실행
3. writeAction
4. setBeanClassLoader(ClassLoader classLoader) 실행
5. setBeanFactory(BeanFactory beanFactory) 실행
6. setApplicationContext(ApplicationContext applicationContext) 실행
8. afterPropertiesSet() 실행
9. init_method() 전처리메서드 실행
*. execute() 실행
홍길동
14:41:35.711 [main] DEBUG o.s.c.s.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@1d7acb34, started on Mon Jul 04 14:41:35 KST 2022

왜 어노테이션@과는 다르게 xml로 실행하면 destroy가 보이지 않을까?
강사님 말로는 시점 차이라고 한다.
따라서 destroy를 사용할때는 어노테이션 말고 xml로 하는 것이 좋다고 한다.


마무리 작업의 다른 것은 무엇이 있을까?

  1. 초기화 전의 작업
  2. 초기화 후의 작업

package com.exam.spring02.config;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		// TODO Auto-generated method stub
		System.out.println("초기화 전처리");
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		// TODO Auto-generated method stub
		System.out.println("초기화 후처리");
		return bean;
	}
}
package com.exam.spring02.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import com.exam.spring02.model.WriteAction;

@Configuration
@ComponentScan(basePackageClasses = {CustomBeanPostProcessor.class})
public class BeanConfig {

	@Bean()
	@Scope("prototype")
	public WriteAction writeAction() {
		WriteAction action = new WriteAction();
		action.setWriter("홍길동");
		return action;
	}
}
15:18:05.854 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
15:18:05.868 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
15:18:05.921 [main] DEBUG o.s.c.a.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\Java\spring-workspace\spring07\target\classes\com\exam\spring02\config\CustomBeanPostProcessor.class]
15:18:06.006 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
15:18:06.008 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
15:18:06.009 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
15:18:06.012 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'customBeanPostProcessor'
15:18:06.020 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig'
초기화 전처리
초기화 후처리
1. 빈의 생성자 실행
2. setWriter(String Writer) 실행
초기화 전처리
초기화 후처리
*. execute() 실행
홍길동
15:18:06.057 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 15:18:05 KST 2022

다수의 어노테이션 파일 만들어 연결하기

package com.exam.spring01.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.exam.spring01.model.WriteAction;

@Configuration
public class BeanConfig01 {

	@Bean
	public WriteAction writeAction() {
		return new WriteAction();
	}
}
package com.exam.spring01.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.exam.spring01.model.ListAction;
@Configuration
public class BeanConfig02 {

	@Bean
	public ListAction listAction() {
		return new ListAction();
	}
}
package com.exam.spring01;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.exam.spring01.config.BeanConfig01;
import com.exam.spring01.config.BeanConfig02;
import com.exam.spring01.model.ListAction;
import com.exam.spring01.model.WriteAction;

public class App {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx
		= new AnnotationConfigApplicationContext(BeanConfig01.class, BeanConfig02.class);
		
		WriteAction writeAction = (WriteAction)ctx.getBean("writeAction");
		writeAction.execute();
		
		ListAction listAction = (ListAction)ctx.getBean("listAction");
		listAction.execute();
		
		ctx.close();
	}
}
15:29:31.502 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
15:29:31.516 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
15:29:31.619 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
15:29:31.622 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
15:29:31.623 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
15:29:31.628 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig01'
15:29:31.631 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig02'
15:29:31.632 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'writeAction'
WriteAction() 실행
15:29:31.646 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'listAction'
ListAction() 실행
execute() 실행
execute() 실행
15:29:31.686 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 15:29:31 KST 2022

의존성 자동 주입

  • @Autowired 의 개념

package com.exam.spring02.model;

public class BoardDAO {
	public BoardDAO() {
		// TODO Auto-generated constructor stub
		System.out.println("BoardDAO() 실행");
	}
}
package com.exam.spring02.model;

public class WriteAction {
	public WriteAction() {
		// TODO Auto-generated constructor stub
		System.out.println("WriteAction() 실행");
	}
	
	public WriteAction(BoardDAO dao) {
		// TODO Auto-generated constructor stub
		System.out.println("WriteAction(BoardDAO dao) 실행");
	}
}
package com.exam.spring02.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.exam.spring02.model.BoardDAO;

@Configuration
public class BeanConfig01 {

	@Bean
	public BoardDAO dao() {
		return new BoardDAO();
	}
}
package com.exam.spring02.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.exam.spring02.model.BoardDAO;
import com.exam.spring02.model.WriteAction;

@Configuration
public class BeanConfig02 {

	// new(X)
	@Autowired
	private BoardDAO dao;
	
	@Bean
	public WriteAction writeAction1() {
		return new WriteAction();
	}
	
	@Bean
	public WriteAction writeAction2() {
		return new WriteAction(dao);
	}
}
package com.exam.spring02;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.exam.spring02.config.BeanConfig01;
import com.exam.spring02.config.BeanConfig02;
import com.exam.spring02.model.WriteAction;

public class App {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx
		= new AnnotationConfigApplicationContext(BeanConfig01.class, BeanConfig02.class);
		
		WriteAction writeAction1 = (WriteAction)ctx.getBean("writeAction1");
		WriteAction writeAction2 = (WriteAction)ctx.getBean("writeAction2");
		
		ctx.close();
	}
}
15:48:50.349 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
15:48:50.365 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
15:48:50.478 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
15:48:50.480 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
15:48:50.482 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
15:48:50.487 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig01'
15:48:50.489 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig02'
15:48:50.505 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dao'
BoardDAO() 실행
15:48:50.517 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'writeAction1'
WriteAction() 실행
15:48:50.526 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'writeAction2'
WriteAction(BoardDAO dao) 실행
15:48:50.551 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 15:48:50 KST 2022

BeanConfig02.java에서는 @Bean으로 BoardDAO 객체 생성을 해주지 않았음에도 불구하고,
해당 객체를 writeAction2에서 사용하는 것을 확인할 수 있다.


Autowire의 호출 관계를 알아보자.

BeanConfig01.java 를 다음과 같이 수정한다.

package com.exam.spring02.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.exam.spring02.model.BoardDAO;

@Configuration
public class BeanConfig01 {

	@Bean
	public BoardDAO dao() {
		System.out.println("dao() 호출");
		return new BoardDAO();
	}
}

BeanConfig02.java 를 다음과 같이 수정한다.

package com.exam.spring02.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.exam.spring02.model.BoardDAO;
import com.exam.spring02.model.WriteAction;

@Configuration
public class BeanConfig02 {

	// new(X)
	//@Autowired
	private BoardDAO dao;
	
	@Bean
	public WriteAction writeAction1() {
		return new WriteAction();
	}
	
	@Bean
	public WriteAction writeAction2() {
		System.out.println(dao.toString());
		return new WriteAction(dao);
	}
}

실행시키면?

16:11:07.077 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
16:11:07.090 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
16:11:07.200 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
16:11:07.203 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
16:11:07.205 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
16:11:07.210 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig01'
16:11:07.213 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig02'
16:11:07.214 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dao'
dao() 호출
BoardDAO() 실행
16:11:07.228 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'writeAction1'
WriteAction() 실행
16:11:07.232 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'writeAction2'
16:11:07.236 [main] WARN  o.s.c.a.AnnotationConfigApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'writeAction2' defined in com.exam.spring02.config.BeanConfig02: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.exam.spring02.model.WriteAction]: Factory method 'writeAction2' threw exception; nested exception is java.lang.NullPointerException: Cannot invoke "Object.toString()" because "this.dao" is null
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'writeAction2' defined in com.exam.spring02.config.BeanConfig02: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.exam.spring02.model.WriteAction]: Factory method 'writeAction2' threw exception; nested exception is java.lang.NullPointerException: Cannot invoke "Object.toString()" because "this.dao" is null

… 이후 Exception 내용 생략 …

Exception 에러가 발생한다.

하지만 그 전에, BoardDAO()가 실행되기 전에 dao()를 먼저 호출해주는 것을 볼 수 있다.

이제 다시 Autowire를 활성화 한 후, 프로그램을 재실행 해보자.

16:12:27.472 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0
16:12:27.485 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
16:12:27.612 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
16:12:27.614 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
16:12:27.615 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
16:12:27.620 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig01'
16:12:27.622 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanConfig02'
16:12:27.635 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dao'
dao() 호출
BoardDAO() 실행
16:12:27.648 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'writeAction1'
WriteAction() 실행
16:12:27.652 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'writeAction2'
com.exam.spring02.model.BoardDAO@2dfaea86
WriteAction(BoardDAO dao) 실행
16:12:27.677 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4f51b3e0, started on Mon Jul 04 16:12:27 KST 2022

정상적으로 실행되는 것을 확인할 수 있다.


AOP

AOP란?

여러 객체에 공통으로 적용할 수 있는 기능을 분리하여 재사용성을 높여주는 프로그래밍 기법.
AOP는 다음 두 가지의 기능 구현을 분리힌다.

  1. 핵심 기능
  2. 공통 기능

AOP의 목적

핵심 기능을 구현한 코드의 수정 없이 공통 기능을 적용할 수 있도록 만들어 주는 것
핵심 기능의 코드는 수정하지 않으면서 공통 기능의 구현을 추가하는 것.

참고 블로그 : AOP 프로그래밍 분석하기 tistory

AOP의 구현법

  1. DI를 사용하여 구현 : xml
  2. ApectJ (더 큰 AOP 기법)
    ApectJ 공식 사이트
  3. Spring annotation 사용 : @

AOP 용어

용어의미
Advice공통 기능을 핵심 로직에 어느 시점에 적용할지를 정의
ex) 메서드 호출 전, 메서드 종료 후
WeavingAdvice를 핵심 로직 코드에 적용하는 것
Aspect공통 기능을 의미.
여러 객체에 공통으로 적용되는 기능을 Aspect라고 함
ex) 트랜잭션 시작, 트랜잭션 종료, 수행 시간 구하기
JointpointAdvice를 적용 가능한 지점을 의미한다. 메서드 호출, 필드 값 변경 등이 Joinpoint에 해당.
스프링은 Proxy를 이용해서 AOP를 구현하기 때문에 메서드 호출에 대한 Joinpoint만 지원함.

구현 가능한 Advice의 종류

용어설명
Before Advice대상 객체의 메서드 호출 전에 공통 기능을 실행한다.
After Returning Advice대상 객체의 메서드가 익셉션 없이 실행된 이후에 공통 기능을 실행한다.
After Throwing Advice대상 객체의 메서드를 실행하는 도중 익셉션이 발생한 경우에 공통 기능을 실행한다.
After Advice익셉션 발생 여부에 상관없이 대상 객체의 메서드 실행 후 공통 기능을 실행한다.
Around Advice대상 객체의 메서드를 실행 전, 후 또는 익셉션 발생 시점에 공통 기능을 실행한다.

스프링은 Proxy를 이용해서 메서드를 호출 시점에 Aspect(공통 기능)를 적용한다.
물론 Around Advice가 가장 널리 사용되고 있다.

참고 블로그 : AOP 프로그래밍 분석하기 tistory


공통 기능에 대한 전처리와 후처리

xml Proxy를 이용한 전처리와 후처리

package com.exam.aop01;

public interface BoardAction {
	void execute();
}
package com.exam.aop01;

public class WriteAction implements BoardAction {
	private String writer;
	
	public WriteAction() {
		// TODO Auto-generated constructor stub
		System.out.println("WriteAction() 실행");
	}
	
	public void setWriter(String name) {
		this.writer = name;
	}

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		System.out.println("execute() 시작");
		System.out.println("writer : " + this.writer);
		System.out.println("execute() 종료");
	}
}
package com.exam.aop01.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class BasicAdvice1 implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// TODO Auto-generated method stub
		
		System.out.println("전처리 : 공통 관심");
		
		Object rtnObj = invocation.proceed();
		
		System.out.println("후처리 : 공통 관심");
		
		return null;
	}
}
<?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-4.3.xsd">
	
	<!-- AOP 에 대한 인스턴스화 -->
	<bean id="basicAdvice1" class="com.exam.aop01.advice.BasicAdvice1" />
	
	<!-- 내부꺼를 설정 위치를 찾아준다. -->
	<bean id="pointcutAdvisor1" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="advice" ref="basicAdvice1" />
		<property name="pointcut">
			<bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
				<property name="pattern" value=".*execute.*"></property>
			</bean>
		</property>
	</bean>
	
	<!-- 기존꺼 -->
	<!-- <bean id="writeAction" class="com.exam.aop01.WriteAction" scope="prototype"/> -->
	<bean id="writeAction" class="com.exam.aop01.WriteAction" scope="prototype">
		<property name="writer" value="홍길동" />
	</bean>
	
	<!-- AOP 적용 -->
	<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target" ref="writeAction"></property>
		<property name="interceptorNames">
			<list>
				<value>pointcutAdvisor1</value>
			</list>
		</property>
	</bean>
</beans>
package com.exam.aop01;

import org.springframework.context.support.GenericXmlApplicationContext;

public class App {
	public static void main(String[] args) {
		GenericXmlApplicationContext ctx
		= new GenericXmlApplicationContext("classpath:com/exam/aop01/context.xml");
		
		//BoardAction action = (BoardAction)ctx.getBean("writeAction");
		BoardAction action = (BoardAction)ctx.getBean("proxy");
		
		// Java Source로 처리
		System.out.println("전처리 구간");
		action.execute();
		System.out.println("후처리 구간");
		
		ctx.close();
	}
}
17:14:13.480 [main] DEBUG o.s.b.f.xml.XmlBeanDefinitionReader - Loaded 6 bean definitions from class path resource [com/exam/aop01/context.xml]
17:14:13.483 [main] DEBUG o.s.c.s.GenericXmlApplicationContext - Refreshing org.springframework.context.support.GenericXmlApplicationContext@1d7acb34
17:14:13.522 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'basicAdvice1'
17:14:13.532 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'pointcutAdvisor1'
17:14:13.581 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'basicAdvice2'
17:14:13.581 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'pointcutAdvisor2'
17:14:13.582 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'proxy'
WriteAction() 실행
17:14:13.631 [main] DEBUG o.s.aop.framework.ProxyFactoryBean - Advice has changed; re-caching singleton instance
17:14:13.631 [main] DEBUG o.s.aop.framework.ProxyFactoryBean - Advice has changed; re-caching singleton instance
전처리 구간
전처리 : 공통 관심
execute() 시작
writer : 홍길동
execute() 종료
후처리 : 공통 관심
후처리 구간
17:14:13.646 [main] DEBUG o.s.c.s.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@1d7acb34, started on Mon Jul 04 17:14:13 KST 2022

BasicAdvice2.java 등록하기 예제 - 17:10 경

전처리 및 후처리 추가하기.
xml 파일 수정해주기만 하면 되는 간단한 문제다.

package com.exam.aop01.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class BasicAdvice2 implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// TODO Auto-generated method stub
		
		System.out.println("전처리2 : 공통 관심");
		
		Object rtnObj = invocation.proceed();
		
		System.out.println("후처리2 : 공통 관심");
		
		return null;
	}
}
<?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-4.3.xsd">
	
	<bean id="basicAdvice1" class="com.exam.aop01.advice.BasicAdvice1" />
	
	<bean id="pointcutAdvisor1" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="advice" ref="basicAdvice1" />
		<property name="pointcut">
			<bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
				<property name="pattern" value=".*execute.*"></property>
			</bean>
		</property>
	</bean>
	
	<bean id="basicAdvice2" class="com.exam.aop01.advice.BasicAdvice2" />
	
	<bean id="pointcutAdvisor2" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="advice" ref="basicAdvice2" />
		<property name="pointcut">
			<bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
				<property name="pattern" value=".*execute.*"></property>
			</bean>
		</property>
	</bean>
	
	<bean id="writeAction" class="com.exam.aop01.WriteAction" scope="prototype">
		<property name="writer" value="홍길동" />
	</bean>
	
	<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target" ref="writeAction"></property>
		<property name="interceptorNames">
			<list>
				<value>pointcutAdvisor1</value>
				<value>pointcutAdvisor2</value>
			</list>
		</property>
	</bean>
</beans>
… 앞부분 생략 …

17:15:38.385 [main] DEBUG o.s.aop.framework.ProxyFactoryBean - Advice has changed; re-caching singleton instance
전처리 구간
전처리 : 공통 관심
전처리2 : 공통 관심
execute() 시작
writer : 홍길동
execute() 종료
후처리2 : 공통 관심
후처리 : 공통 관심
후처리 구간
17:15:38.399 [main] DEBUG o.s.c.s.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@1d7acb34, started on Mon Jul 04 17:15:38 KST 2022

전처리 / 후처리가 다수일때의 시점 변경

아까의 xml 파일로 돌아가서, <list> 태그 내부 value 값의 순서를 변경해준다.

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="writeAction"></property>
	<property name="interceptorNames">
		<list>
        	<value>pointcutAdvisor2</value>
			<value>pointcutAdvisor1</value>
		</list>
	</property>
</bean>

실행해보면?

pointcutAdvisor2 처리가 먼저 실행되도록 순서가 바뀐 것을 확인할 수 있다.

17:32:54.275 [main] DEBUG o.s.aop.framework.ProxyFactoryBean - Advice has changed; re-caching singleton instance
전처리 구간
전처리2 : 공통 관심
전처리 : 공통 관심
execute() 시작
writer : 홍길동
execute() 종료
후처리 : 공통 관심
후처리2 : 공통 관심
후처리 구간
17:32:54.291 [main] DEBUG o.s.c.s.GenericXmlApplicationContext - Closing org.springframework.context.support.GenericXmlApplicationContext@1d7acb34, started on Mon Jul 04 17:32:54 KST 2022
profile
차근차근 한 걸음씩 걸어나가는 개발자 꿈나무.

0개의 댓글