Dependency 관리
Dependency 관리는 Spring에서 Famework가 관리하는 Bean을 다른 Bean에서 사용할 수 있도록 설정해주는 역할까지 대행하는 것을 말한다.
예제)
xyz.itwill05.di 패키지 생성
학생정보를 저장하기 위한 클래스, 즉 VO 클래스(DTO 클래스, POJO 클래스 - Plane Old Java Object) [Student]를 선언
→ 학생정보를 표현하기 위한 값을 필드에 저장할 것이다.
* POJO란 특정 기술에 종속되지 않는 순수한 자바 객체를 말한다.
package xyz.itwill05.di;
//학생 정보를 저장하기 위한 VO클래스(DTO 클래스)
public class Student {
private int num;
private String name;
private String email;
public Student() {
System.out.println("### Student 클래스의 기본 생성자 호출 ###");
}
public Student(int num) {
super();
this.num = num;
System.out.println("### Student 클래스의 매개변수(학번)가 선언된 생성자 호출 ###");
}
public Student(int num, String name) {
super();
this.num = num;
this.name = name;
System.out.println("### Student 클래스의 매개변수(학번, 이름)가 선언된 생성자 호출 ###");
}
public Student(int num, String name, String email) {
super();
this.num = num;
this.name = name;
this.email = email;
System.out.println("### Student 클래스의 매개변수(학번, 이름, 이메일)가 선언된 생성자 호출 ###");
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
System.out.println("*** Student 클래스의 setNum(int num) 메소드 호출 ***");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("*** Student 클래스의 setName(String name) 메소드 호출 ***");
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
System.out.println("*** Student 클래스의 setEmail(String email) 메소드 호출 ***");
}
//Object 클래스에 있는 메소드
//toString() 메소드가 빠지면 DTO 클래스이다
@Override
public String toString() {
return "학번 = "+num+", 이름 = "+name+", 이메일 = "+email;
}
}
스프링 컨테이너는 클래스의 기본 생성자를 사용하여 객체를 생성하기 때문에 객체 필드에는 기본값이 들어간다.
(기본값은 숫자형 : 0, 논리형 : false, 참조형 : null 값이다.)
[05-1_di.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 class="xyz.itwill05.di.Student" id="student1"/>
</beans>
main 메소드 선언할 [StudentApp] 클래스 선언
package xyz.itwill05.di;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class StudentApp {
public static void main(String[] args) {
System.out.println("================ Spring Container 초기화 전 ================");
ApplicationContext context=new ClassPathXmlApplicationContext("05-1_di.xml");
System.out.println("================ Spring Container 초기화 후 ================");
Student student1=context.getBean("student1", Student.class);
//참조변수를 출력할 경우 toString() 메소드를 자동으로 호출 - 객체의 초기값 확인
System.out.println(student1);
System.out.println("============================================================");
((ClassPathXmlApplicationContext)context).close();
}
}

객체를 만들 때 객체 필드에 원하는 값을 넣고 싶은 경우 어떻게 해야할까?
Spring을 이용해 Spring Injection이라는 기술을 이용할 수 있다.
의존성 주입(Dependency Injection)
Dependency Injection은 스프링 컨테이너에 의해 Spring Bean Configuration File에 등록된 클래스를 객체로 생성할 때 객체 필드에 필요한 값(객체)를 저장되도록 설정하는 기능이다.
Constructor, Setter, Field 세가지 방법이 존재하는데 그 중에서 생성자(Constructor Injection) 또는 Setter 메소드(Setter Injection)를 사용하여 객체 필드에 값(객체)를 저장하는 방법에 대해 알아보자.
1. Constructor Injection
Constructor Injection은 스프링 컨테이너가 클래스의 매개변수가 선언된 생성자를 사용해 객체를 생성하는 방법이다.
→ 생성자의 매개변수에 전달된 값(객체)을 이용하여 필드값으로 저장한다.
Constructor Injection은 생성자를 사용하여 객체 필드에 값(객체)를 저장하는 방법이다.
이 방법을 사용하기 위해선 bean 엘리먼트의 하위 엘리먼트로 constructor-arg 엘리먼트를 사용해야 한다.
constructor-arg : 생성자 매개변수에 값(객체)를 전달하는 엘리먼트 - 생성자 매개변수에 값이나 객체를 전달해 초기화 시킬 수 있도록 한다.
→ constructor-arg 엘리먼트의 갯수만큼 매개변수가 선언된 생성자를 반드시 작성해주어야 한다. (작성하지 않으면 객체가 생성되지 않는다.)
- value 속성 : 생성자 매개변수에 전달되어 저장될 값을 속성값으로 설정한다.
- → 필드에 값이 저장되도록 설정하는 속성이다. 이를 '값 주입(Value Injection)' 이라고 부른다.
- → 매개변수에 전달되는 값은 기본적으로 문자열로 취급하지만 매개변수의 자료형에 의해 자동 형변환 된다.
- → 매개변수의 자료형에 의해 자동 형변환시 NumberFormatException이 발생될 수 있기 때문에 주의해야 한다. ex) 전달값이 문자형인데 매개변수의 자료형이 int 일 경우
[05_di.xml]
<bean class="xyz.itwill05.di.Student" id="student2">
<constructor-arg value="1000"/><!-- 문자열로 전달되지만 매개변수의 자료형에 의해 자동 형변환된다 -->
</bean>
[StudentApp]
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class StudentApp {
public static void main(String[] args) {
...
ApplicationContext context=new ClassPathXmlApplicationContext("05-1_di.xml");
...
Student student2=context.getBean("student2", Student.class);
System.out.println(student2);
...
((ClassPathXmlApplicationContext)context).close();
}
}

만약 매개변수가 하나인 생성자를 두개 선언한 경우에 다시 실행하게 되면, num을 매개변수로 갖고있는 생성자는 사용되지 않는다. (전달값이 "1000" 이기 때문에 자료형이 String인 매개변수의 생성자에 값이 대입된다)
public Student(int num) {
super();
this.num = num;
System.out.println("### Student 클래스의 매개변수(학번)가 선언된 생성자 호출 ###");
}
public Student(String name) {
super();
this.name = name;
System.out.println("### Student 클래스의 매개변수(이름)가 선언된 생성자 호출 ###");
}

따라서 매개변수의 갯수를 다르게 쓰는 것을 권장한다.
indext 속성
contructor-arg 엘리먼트의 작성순서에 의해 매개변수에 값(객체)이 차례대로 전달되어 저장된다.
<!-- 매개변수가 3개인 생성자 사용 -->
<bean class="xyz.itwill05.di.Student" id="student3">
<constructor-arg value="1000"/>
<constructor-arg value="홍길동"/>
<constructor-arg value="abc@itwill.xyz"/>
</bean>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class StudentApp {
public static void main(String[] args) {
...
ApplicationContext context=new ClassPathXmlApplicationContext("05-1_di.xml");
...
Student student3=context.getBean("student3", Student.class);
System.out.println(student3);
...
((ClassPathXmlApplicationContext)context).close();
}
}

index 속성을 이용해 전달 순서를 지정할 수 있다.
index 속성 : 생성자 매개변수에 값(객체)를 전달하기 위한 순서를 속성값으로 설정할 수 있다.
→ index 속성값은 0부터 1씩 증가되는 정수값을 사용하면 된다.
<bean class="xyz.itwill05.di.Student" id="student3">
<constructor-arg value="1000" index="2"/>
<constructor-arg value="abc@itwill.xyz" index="0"/>
<constructor-arg value="홍길동" index="1"/>
</bean>
2) Setter Injection
스프링 컨테이너는 클래스의 기본 생성자를 사용하여 객체를 생성하기 때문에 객체 필드에는 기본값이 저장되는데,
객체 생성 후 필드의 Setter 메소드를 호출하여 필드값으로 저장(변경)하는 것이 Setter Injection이다.
즉 Setter Injection은 Setter 메소드를 사용하여 객체 필드에 값(객체)를 저장하는 방법이다.
Setter Injection은 bean 엘리먼트의 하위 엘리먼트로 property 엘리먼트를 사용하여 설정할 수 있다.
property
: 객체 필드의 Setter 메소드를 호출하여 필드값을 변경하는 엘리먼트
- name 속성 : 필드값을 변경할 필드명을 속성값으로 설정한다. - 자동 완성 기능 사용 가능
- → name 속성값으로 설정된 필드에 대한 Setter 메소드를 자동으로 호출하여 필드값을 변경한다.
- → 필드에 대한 Setter 메소드가 없거나 형식에 맞지 않게 선언된 경우엔 예외가 발생할 수 있다.
- value 속성 : Setter 메소드 매개변수에 전달되어 저장될 값을 속성값으로 설정한다.
- → 매개변수에 저장된 값을 필드값으로 저장되도록 설정하는 설정 - 값 주입(Value Injection)
<bean class="xyz.itwill05.di.Student" id="student4">
<property name="num" value="3000"/>
<property name="name" value="임꺽정"/>
<property name="email" value="xyz@itwill.xyz"/>
</bean>
Constructor Injection과 Setter Injection을 같이 사용하여 객체 초기화 작업하는 것도 가능하다.
<bean class="xyz.itwill05.di.Student" id="student5">
<constructor-arg value="4000"/>
<constructor-arg value="전우치"/>
<property name="email" value="opq@itwill.xyz"/>
</bean>

PropertyPlaceholderConfigurer 클래스를 Spring Bean으로 등록해보자
[student.properties] 파일 생성

PropertyPlaceholderConfigurer 클래스
: Properties 파일을 제공받아 파일에 저장된 값을 Spring Bean Configuratino File에서 사용할 수 있도록 제공하는 클래스
- locations 필드에 Properties 파일의 경로를 전달하여 저장시키면 된다.
- Properties 파일에 의해 제공되는 값을 Spring Bean Configuratino File에서는 ${이름} 형식으로 표현하여 사용할 수 있다.
<bean class="org.springframework.context.support.PropertyPlaceholderConfigurer">
<property name="locations" value="xyz/itwill05/di/student.properties"/>
</bean>
<bean class="xyz.itwill05.di.Student" id="student6">
<property name="num" value="${num}"/>
<property name="name" value="${name}"/>
<property name="email" value="${email}"/>
</bean>
Student student6=context.getBean("student6", Student.class);
System.out.println(student6);

Spring 5.2 이상에서는 PropertySourcesPlaceholderConfigurer 클래스를 사용해야 한다.
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="xyz/itwill05/di/student.properties"/>
</bean>
예제)
학생정보를 처리하는 DAO 클래스가 상속받아야 하는 인터페이스 [StudentDAO] 를 선언한다.
인터페이스를 선언하는 이유는 객체간의 결합도를 낮춰 유지보수의 효율성을 증가시키기 위해서이다.
package xyz.itwill05.di;
import java.util.List;
//학생정보를 처리하는 DAO 클래스가 상속받아야 하는 인터페이스
public interface StudentDAO {
int insertStudent(Student student);
int updateStudent(Student student);
int deleteStudent(int num);
Student selectStudent(int num);
List<Student> selectStudentList();
}
DAO 클래스 [StudentJdbcDAO] 선언
*DAO 클래스 : 저장매체(File, DBMS 등) 행의 삽입, 변경, 삭제, 검색 기능을 제공하는 클래스
저장매체의 종류 또는 방법에 따라 DAO 클래스는 변경될 수 있다.
따라서 DAO 클래스가 변경되더라도 DAO 클래스를 사용하는 클래스(Service 클래스)의 영향을 최소화 하기 위해서는 인터페이스를 반드시 상속받아서 작성해야 한다. 그래야만 결합도를 낮춰 유지보수의 효율성을 증가시킬 수 있기 때문이다.
package xyz.itwill05.di;
import java.util.List;
//DAO 클래스 : 저장매체(File, DBMS 등) 행의 삽입, 변경, 삭제, 검색 기능을 제공하는 클래스
// => 저장매체의 종류 또는 방법에 따라 DAO 클래스는 변경 가능
public class StudentJdbcDAO implements StudentDAO {
public StudentJdbcDAO() {
System.out.println("### StudentJdbcDAO 클래스의 기본 생성자 호출 ###");
}
@Override
public int insertStudent(Student student) {
System.out.println("*** StudentJdbcDAO 클래스의 insertStudent(Student student) 메소드 호출***");
return 0;
}
@Override
public int updateStudent(Student student) {
System.out.println("*** StudentJdbcDAO 클래스의 updateStudent(Student student) 메소드 호출***");
return 0;
}
@Override
public int deleteStudent(int num) {
System.out.println("*** StudentJdbcDAO 클래스의 deleteStudent(int num) 메소드 호출***");
return 0;
}
@Override
public Student selectStudent(int num) {
System.out.println("*** StudentJdbcDAO 클래스의 selectStudent(int num) 메소드 호출***");
return null;
}
@Override
public List<Student> selectStudentList() {
System.out.println("*** StudentJdbcDAO 클래스의 selectStudentList() 메소드 호출***");
return null;
}
}
실제 필요한 데이터 처리 역할은 Service 클래스가 해준다.
Service 인터페이스 [StudentService]를 먼저 생성해주자 (학생정보를 처리하는 Service 클래스가 반드시 상속받아야 되는 인터페이스)
package xyz.itwill05.di;
import java.util.List;
//학생정보를 처리하는 Service 클래스가 반드시 상속받아야 되는 인터페이스
public interface StudentService {
void addStudent(Student student);
void modifyStudent(Student student);
void removeStudent(int num);
Student getStudent(int num);
List<Student> getStudentList();
}
Service 클래스인 [StudentServiceImpl] 를 선언한다.
*Service 클래스는 프로그램 실행에 필요한 데이터 처리 기능을 제공하는 클래스이며, 컴포넌트라고도 부른다.
- Service 클래스의 메소드는 다수의 DAO 객체로 메소드를 호출하여 작성한다. 이를 DAO 모듈화 라고 한다.
- Service 클래스의 메소드에서 DAO 객체를 사용하기 위해 포함관계(의존관계)로 설정해야 한다.
- Service 클래스가 변경돼도 Service 클래스를 사용하는 클래스(컨트롤러, 즉 모델)에 영향을 최소화 하기 위해 반드시 인터페이스를 상속받아서 작성해야 한다. (결합도를 낮추어 유지보수의 효율성이 증가한다.)
[StudentServiceImpl] Service 클래스에 StudentJdbcDAO 클래스로 필드 선언 - StudentJdbcDAO 객체만 저장이 가능한 필드
→ 필드에 StudentJdbcDAO 객체를 저장해야만 포함관계(의존관계)가 완성된다.
→ StudentServiceImpl 클래스의 메소드에서 필드에 저장된 StudentJdbcDAO 객체로 메소드를 호출할 수 있다.
private StudentJdbcDAO studentJdbcDAO;
하지만 DAO 클래스가 변경될 경우 Service 클래스의 필드 및 메소드를 변경할 수 있다는 문제점이 발생할 수 있다.
때문에 결합도가 높아 유지보수가 어려워진다.
때문에 StudentDAO 인터페이스로 필드를 선언해주는 것이 좋다.
- 필드에 StudentDAO 인터페이스를 상속받은 DAO 클래스의 객체를 저장해야지만 포함관계(의존관계)가 완성된다.
- StudentServiceImpl 클래스의 메소드에서 인터페이스로 생성된 필드의 추상메소드를 호출하면 필드에 저장된 자식클래스 객체의 메소드를 호출한다. 이를 오버라이드에 의한 다형성이라고 한다.
- DAO 클래스가 변경돼도 Service 클래스의 영향을 최소화 시킬 수 있다. - 결합도를 낮춰 유지보수의 효율성이 증가한다.
package xyz.itwill05.di;
import java.util.List;
public class StudentServiceImpl implements StudentService {
//StudentJdbcDAO 클래스로 필드 선언 - StudentJdbcDAO 객체만 저장 가능한 필드
// => 결합도가 높아 유지보수의 효율성 감소
//private StudentJdbcDAO studentJdbcDAO;
//StudentDAO 인터페이스로 필드 선언 - StudentDAO 인터페이스를 상속받은 자식클래스로 생성된 모든 객체 저장
// => DAO 클래스가 변경돼도 Service 클래스의 영향 최소화 - 결합도를 낮춰 유지보수의 효율성 증가
private StudentDAO studentDAO;
public StudentServiceImpl() {
System.out.println("### StudentServiceImpl 클래스의 기본 생성자 호출 ###");
}
public StudentServiceImpl(StudentDAO studentDAO) {
super();
this.studentDAO = studentDAO;
System.out.println("### StudentServiceImpl 클래스의 매개변수가 선언된 생성자 호출 ###");
}
public StudentDAO getStudentDAO() {
return studentDAO;
}
public void setStudentDAO(StudentDAO studentDAO) {
this.studentDAO = studentDAO;
System.out.println("*** StudentServiceImpl 클래스의 setStudentDAO(StudentDAO studentDAO) 메소드 호출 ***");
}
@Override
public void addStudent(Student student) {
System.out.println("*** StudentServiceImpl 클래스의 addStudent(Student student) 메소드 호출 ***");
studentDAO.insertStudent(student);
}
@Override
public void modifyStudent(Student student) {
System.out.println("*** StudentServiceImpl 클래스의 modifyStudent(Student student) 메소드 호출 ***");
studentDAO.updateStudent(student);
}
@Override
public void removeStudent(int num) {
System.out.println("*** StudentServiceImpl 클래스의 removeStudent(int num) 메소드 호출 ***");
studentDAO.deleteStudent(num);
}
@Override
public Student getStudent(int num) {
System.out.println("*** StudentServiceImpl 클래스의 getStudent(int num) 메소드 호출 ***");
return studentDAO.selectStudent(num);
}
@Override
public List<Student> getStudentList() {
System.out.println("*** StudentServiceImpl 클래스의 getStudentList() 메소드 호출 ***");
return studentDAO.selectStudentList();
}
}
DAO클래스와 Service 클래스를 Springn Bean으로 등록해주자
[05-1_di.xml]
<!-- StudentDAO 인터페이스를 상속받은 자식클래스(DAO 클래스)를 Spring Bean으로 등록 -->
<bean class="xyz.itwill05.di.StudentJdbcDAO" id="studentJdbcDAO"/>
<!-- StudentDAO 인터페이스를 상속받은 자식클래스(Service 클래스)를 Spring Bean으로 등록 -->
<bean class="xyz.itwill05.di.StudentServiceImpl" id="studentServiceImpl"/>
클래스의 기본 생성자를 이용해 객체를 생성하기 때문에 객체 필드에는 기본값이 저장된다.
문제점)
[StudentServiceImpl] 클래스로 생성된 객체의 studentDAO 필드에는 [null]이 저장되어 studentServiceImpl 클래스의 메소드에서 필드로 메소드를 호출할 경우 NullPointerException이 발생한다 - 포함관계(의존관계) 미구현
해결법)
StudentServiceImpl 클래스로 생성된 객체의 studentDAO 필드에 StudentDAO 인터페이스를 상속받은 자식클래스의 객체가 저장되도록 설정하는 것이다. 이를 통해 포함(의존)관계가 구현될 수 있도록 해야한다.
이번엔 StudentServiceImpl 클래스의 매개변수가 선언된 생성자를 이용하여 객체를 생성해보자
→ 생성자 매개변수에 StudentDAO 인터페이스를 상속받은 자식클래스(StudentJdbcDAO)의 객체를 전달하여 studentDAO 필드에 저장 - Constructor Injection
constructor-arg 엘리먼트를 사용하여 StudentServiceImpl 클래스가 객체로 생성될 때 생성자 매개변수에 StudentJdbcDAO 객체를 전달하여 필드에 저장해준다. (의존관계가 구현된다)
<bean class="xyz.itwill05.di.StudentServiceImpl" id="studentServiceImpl">
<constructor-arg ref="studentJdbcDAO"/>
</bean>
여기서 사용한 ref 속성에는 스프링 컨테이너로 관리되는 Spring Bean의 식별자(beanName)을 속성값으로 설정하면 된다.
→ 스프링 컨테이너에게 Spring Bean을 제공받아 객체 필드에 저장한다. 이를 의존성 주입(DI)라고 한다.
이번엔 Setter Injection을 사용해보자
클래스의 기본 생성자를 이용하여 객체를 생성한다. - 객체 필드에는 기본값이 저장된다.
→ Setter 메소드를 호출하여 StudentDAO 인터페이스를 상속받은 자식클래스(DAO 클래스)의 객체를 필드에 저장할 수 있도록 한다. - Setter Injection
property 엘리먼트를 사용하여 StudentServiceImpl 객체의 필드에 대한 Setter 메소드를 호출하여 StudentDAO 인터페이스를 상속받은 자식클래스의 객체를 필드에 저장한다. - 의존관계 구현
<bean class="xyz.itwill05.di.StudentServiceImpl" id="studentServiceImpl">
<property name="studentDAO" ref="studentJdbcDAO"/>
</bean>
[StudentApp]
//프로그램 실행에 필요한 데이터 처리 기능을 제공하는 Service 객체를 제공받아 저장
//StudentServiceImpl service=context.getBean("StudentServiceImple", StudentServiceImpl.class);
//클래스로 참조변수(필드)를 생성하여 객체를 저장하는 것보다 인터페이스로 참조변수(필드)를
//생성하여 객체를 저장하는 것이 유지보수의 효율성을 증가하는 방법이다.
StudentService service=context.getBean("studentServiceImpl", StudentService.class);
//Service 객체의 메소드를 호출하여 필요한 데이처 처리 기능을 구현
service.addStudent(student1);
service.modifyStudent(student1);
service.removeStudent(1000);
service.getStudent(1000);
service.getStudentList();

의존성 주입을 사용하면 좋은점은 Service 클래스에서 사용하던 DAO 클래스의 객체가 변경돼도 Service 클래스를 변경하지 않고 Spring Bean Configuration File만 수정해도 객체와의 의존관계를 변경할 수 있다는 것이다.
이를 통해 객체간의 결합도를 가장 느슨하게 만들어줄 수 있으며, 유지보수의 효율성이 높아진다.
예제)
DAO를 [StudentMybatisDAO] 로 바꾸어보자
package xyz.itwill05.di;
import java.util.List;
public class StudentMybatisDAO implements StudentDAO {
public StudentMybatisDAO() {
System.out.println("### StudentMybatisDAO 클래스의 기본 생성자 호출 ###");
}
@Override
public int insertStudent(Student student) {
System.out.println("*** StudentMybatisDAO 클래스의 insertStudent(Student student) 메소드 호출 ***");
return 0;
}
@Override
public int updateStudent(Student student) {
System.out.println("*** StudentMybatisDAO 클래스의 updateStudent(Student student) 메소드 호출 ***");
return 0;
}
@Override
public int deleteStudent(int num) {
System.out.println("*** StudentMybatisDAO 클래스의 deleteStudent(int num) 메소드 호출 ***");
return 0;
}
@Override
public Student selectStudent(int num) {
System.out.println("*** StudentMybatisDAO 클래스의 selectStudent(int num) 메소드 호출 ***");
return null;
}
@Override
public List<Student> selectStudentList() {
System.out.println("*** StudentMybatisDAO 클래스의 selectStudentList() 메소드 호출 ***");
return null;
}
}
Spring Bean Configuration File만 수정해도 객체와의 의존관계를 변경할 수 있다
...
<!-- StudentDAO 인터페이스를 상속받은 자식클래스(DAO 클래스)를 Spring Bean으로 등록 -->
<bean class="xyz.itwill05.di.StudentMybatisDAO" id="studentMybatisDAO"/>
<bean class="xyz.itwill05.di.StudentServiceImpl" id="studentServiceImpl">
<!-- <property name="studentDAO" ref="studentJdbcDAO"/> -->
<property name="studentDAO" ref="studentMybatisDAO"/>
</bean>
...
'학원 > 복기' 카테고리의 다른 글
[Spring] Annotation 이용한 의존성 주입 (수정..) (0) | 2023.07.30 |
---|---|
[Spring] Bean 컬렉션 주입 / autowire 속성 (0) | 2023.07.30 |
[Spring] 스프링 빈(Spring Bean) (0) | 2023.07.26 |
[Spring] 제어의 역전(Inversion of Control, IoC) (0) | 2023.07.26 |
[Spring] 로그 구현체 설정 (0) | 2023.07.26 |