[2022.12.30.금] 스프링의 의존성 주입
스프링의 의존성 주입
1. 수동 의존성 주입
- 애플리케이션 실행에 관여하는 객체를 스프링 컨테이너가 생성하게 한다.
<!--
<bean /> 태그 : 스프링 컨테이너가 객체를 생성하게 함
id 속성 : 생성된 객체를 식별하는 고유 이름
class 속성 : 생성할 객체의 전체이름
--!>
<bean id="userController" class="com.sample.controller.UserController"></bean>
<bean id="orderController" class="com.sample.controller.OrderController"></bean>
<bean id="userDao" class="com.sample.controller.UserOracleDao"></bean>
<bean id="orderDao" class="com.sample.controller.OrderOracleDao"></bean> - 의존성 주입을 받는 객체는 의존하는 객체를 전달받기 위해서 멤버변수와 Setter 메소드를 정의한다.
public class UserController {
// 멤버변수 정의
private UserOracleDao userOracleDao;
// Setter 메소드 정의
public void setUserOracleDao(UserOracleDao userOracleDao) {
this.userOracleDao = userOracleDao;
}
}
public class OrderController {
// 멤버변수 정의
private UserOracleDao userOracleDao;
private OrderOracleDao orderOracleDao;
// Setter 메소드 정의
public void setUserOracleDao(UserOracleDao userOracleDao) {
this.userOracleDao = userOracleDao;
}
public void setOrderOracleDao(OrderOracleDao orderOracleDao) {
this.orderOracleDao = orderOracleDao;
}
} - 스프링 컨테이너가 생성한 객체들을 조립시킨다.
<bean id="userController" class="com.sample.controller.UserController">
<!--
<property /> 태그 : setter 메소드를 이용한 의존성 주입 수행
name 속성 : setter 메소드의 이름
ref 속성 : 주입할 빈의 id
--!>
<property name="userOracleDao" ref="userDao"></property>
</bean>
<bean id="orderController" class="com.sample.controller.OrderController">
<property name="userOracleDao" ref="userDao"></property>
<property name="orderOracleDao" ref="orderDao"></property>
</bean>
<bean id="userDao" class="com.sample.controller.UserOracleDao"></bean>
<bean id="orderDao" class="com.sample.controller.OrderOracleDao"></bean>
1-1. 인터페이스를 이용한 느슨한 결합
Controller는 인터페이스가 아닌, 인터페이스를 구현한 클래스를 의존 -> 구현클래스를 주입
-> 실제로는 인터페이스가 들어가는 것
- PostController는 PostOracleDao 클래스(구현 클래스)의 존재를 알 필요가 없다!
-> 인터페이스만 적어놓으면 어차피 재정의된 메소드가 실행되니까
-> PostOracleDao 객체가 아닌 다른 객체를 전달해줘도 상관없다. (단, PostDao 인터페이스의 구현클래스여야 한다.)
* PostDao (인터페이스)
package com.sample.post.dao;
/*
* 게시글 테이블에 대한 CRUD 작업을 정의하는 인터페이스
* 게시글 테이블에 대한 CRUD 작업을 구현하는 클래스는 이 인터페이스를 구현해야 한다.
*/
public interface PostDao {
/*
* void insertPost(Post post);
* void deletePost(int postNo);
* List<Post> getPosts(Map<String, Object> param);
* Post getPostsByNo(int postNo);
* void updatePost(Post post);
* ...
*/
void insertPost();
}
* PostOracleDao (인터페이스 구현객체)
package com.sample.post.dao;
public class PostOracleDao implements PostDao {
@Override
public void insertPost() {
System.out.println("### 오라클 데이터베이스의 게시글 테이블에 새 게시글 등록함.");
}
}
* PostMySQLDao (인터페이스 구현객체)
package com.sample.post.dao;
public class PostMySQLDao implements PostDao{
@Override
public void insertPost() {
System.out.println("### MySQL 데이터베이스의 게시글 테이블에 새 게시글 등록함");
}
}
* PostController (의존성 주입)
package com.sample.post.controller;
import com.sample.post.dao.PostDao;
/*
* PostController는 게시글 등록/조회/수정/삭제 요청을 처리하는 컨트롤러다.
* PostController는 Post 테이블에 대한 데이터베이스 엑세스 작업을 수행하기 위해서 PostDao 인터페이스를 구현한 객체가 필요하다.
* PostController는 구현클래스 대신 인터페이스 타입의 변수와 Setter 메소드를 정의한다.
*/
public class PostController {
// 의존성 주입을 통해서 PostDao 구현 객체를 전달받아서 저장하는 변수
private PostDao postDao;
// PostDao 구현객체를 의존성 주입으로 전달받기 위해서 정의한 setter 메소드
public void setPostDao(PostDao postDao) {
this.postDao = postDao;
}
public String savePost() {
// 요청파라미터값 조회
// Post 객체에 요청파라미터값 저장
postDao.insertPost();
return "redirect:list.hta";
}
}
* context-2.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="postController" class="com.sample.post.controller.PostController">
<!--
PostController의 setPostDao 메소드의 매개변수로 빈의 아이디가 "postOracleDao"인 객체를 전달(주입)한다.
-->
<property name="postDao" ref="postMySQLDao"></property>
</bean>
<bean id="postOracleDao" class="com.sample.post.dao.PostOracleDao"></bean>
<bean id="postMySQLDao" class="com.sample.post.dao.PostMySQLDao"></bean>
</beans>
* 실행객체
package com.sample.app;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.sample.post.controller.PostController;
public class App2 {
public static void main(String[] args) {
// 스프링 빈 설정파일의 경로
String resource = "spring/context-2.xml";
// ClassPathXmlApplicationContext는 스프링 컨테이너 역할을 수행하는 클래스.
// 스프링 빈 설정파일의 설정정보를 분석해서 객체를 설정하고 조립하고 관리한다.
ApplicationContext ctx = new ClassPathXmlApplicationContext(resource);
// .getBean(Class<?> classType)은 스프링 컨테이너에서 해당 클래스타입의 객체를 검색해서 반환한다.
PostController controller = ctx.getBean(PostController.class);
// PostController객체의 savePost 메소드 실행
controller.savePost();
}
}
* 회계분석 프로그램(핵심기능)의 내용은 하나도 바뀌지 않음 -> xml에서 property의 설정만 바꾸면 됨
1-2. 개발환경, 테스트환경, 운영환경의 환경이 다 다를때의 파일첨부
* 파일첨부할 때 경로를 소스코드로 적어놔버릴 때 문제점
- 소스코드를 바꾸면 -> 개발자 컴퓨터 -> 깃 -> 테스트 서버에서 패키징의 단계를 거쳐야돼 -> 번거로움
- 개발자 컴퓨터는 윈도우 운영체제 -> 파일 저장 경로가 c:/files
테스트 서버는 리눅스 운영체제 -> 파일 저장 경로가 /user/home/tester/files
운영서버는 경로가 또달라
-> 근데 윈도우 경로를 깃에 올려버리면 내려받을 때 다 오류
그냥 의존성 주입을 하면 개발자 컴퓨터에서 value에 입력한 값이 깃을 통해 테스트, 운영 서버로 전달
-> 파일 저장 경로가 어차피 달라서 오류
-> 똑같은 파일명 이름으로 서로 각각의 파일 저장 경로를 저장 (서로 다른 app.properties를 갖는것 )
-> value에 그 파일명을 저장 (각각의 다른 경로가 저장됨)
-> git의 버전관리대상에서 제외 (git에 올려버리면 덮어써지니까)
-> 왜 파일첨부 경로, 사진크기를 따로 빼서 의존성 주입을 받냐
-> 각 환경마다 경로가 다르니까 매번 바꾸지 말고, 각 환경에 맞는 값을 의존성 주입
* app.properties (외부설정파일 : 경로, 사진크기 설정)
file.save.directory=c:/files
file.max.uploadsize=10485760
*context-2.xml (경로, 사진크기의 값을 바꿈)
ref : 아이디
value : 숫자, 문자열
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!--
<context:property-placeholder />
location 속성으로 지정된 properties 파일을 읽어오는 객체가 스프링 컨테이너에 추가된다. (파일 정보를 갖고있는 객체가 추가되는것)
스프링 빈 환경설정 파일에서 ${} 표현식을 사용해서 값을 사용할 수 있다.
-->
<context:property-placeholder location="classpath:spring/app.properties "/>
<bean id="postController" class="com.sample.post.controller.PostController">
<!--
PostController의 setPostDao 메소드의 매개변수로 빈의 아이디가 "postOracleDao"인 객체를 전달(주입)한다.
-->
<property name="saveDirectory" value="${file.save.directory}"></property>
<property name="maxUploadSize" value="${file.max.uploadsize}"></property>
<property name="postDao" ref="postMySQLDao"></property>
</bean>
<bean id="postOracleDao" class="com.sample.post.dao.PostOracleDao"></bean>
<bean id="postMySQLDao" class="com.sample.post.dao.PostMySQLDao"></bean>
</beans>
*postController
package com.sample.post.controller;
import com.sample.post.dao.PostDao;
/*
* PostController는 게시글 등록/조회/수정/삭제 요청을 처리하는 컨트롤러다.
* PostController는 Post 테이블에 대한 데이터베이스 엑세스 작업을 수행하기 위해서 PostDao 인터페이스를 구현한 객체가 필요하다.
* PostController는 구현클래스 대신 인터페이스 타입의 변수와 Setter 메소드를 정의한다.
*/
public class PostController {
// 게시글 등록시 업로드되는 첨부파일의 저장폴더
private String saveDirectory;
// 게시글 등록시 업로드되는 첨부파일의 최대 사이즈
private long maxUploadSize;
// 의존성 주입받기 위해 setter 메소드 정의
public void setSaveDirectory(String saveDirectory) {
this.saveDirectory = saveDirectory;
}
public void setMaxUploadSize(long maxUploadSize) {
this.maxUploadSize = maxUploadSize;
}
// 의존성 주입을 통해서 PostDao 구현 객체를 전달받아서 저장하는 변수
private PostDao postDao;
// PostDao 구현객체를 의존성 주입으로 전달받기 위해서 정의한 setter 메소드
public void setPostDao(PostDao postDao) {
this.postDao = postDao;
}
public String savePost() {
// 요청파라미터값 조회
// Post 객체에 요청파라미터값 저장
postDao.insertPost();
System.out.println("파일 저장 디렉토리: " + saveDirectory);
System.out.println("파일 최대 업로드 사이즈: " + maxUploadSize);
return "redirect:list.hta";
}
}
1-3. 생성자(private)를 이용한 의존성 주입 (여태까지는 setter 메소드(public)를 이용한 의존성 주입)
*productDao (인터페이스)
package com.sample.post.dao;
public interface ProductDao {
void deleteAllProducts();
}
*productOracleDao (인터페이스 구현 객체)
package com.sample.post.dao;
public class ProductOracleDao implements ProductDao {
@Override
public void deleteAllProducts() {
System.out.println("### 오라클 데이터베이스의 상품 테이블에서 모든 상품정보를 삭제함.");
}
}
*productController
package com.sample.post.controller;
import com.sample.post.dao.PostDao;
import com.sample.post.dao.ProductDao;
public class ProductController {
// 값을 전달받기 위한 변수 선언(이 객체들이 필요해서 주입받을거야)
private ProductDao productDao;
private PostDao postDao;
// 생성자 메소드를 이용해서 의존성 주입받기
public ProductController(ProductDao productDao, PostDao postDao) {
this.productDao = productDao;
this.postDao = postDao;
}
public String home() {
productDao.deleteAllProducts();
postDao.insertPost();
return "home.jsp";
}
}
*context-2.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!--
<context:property-placeholder />
location 속성으로 지정된 properties 파일을 읽어오는 객체가 스프링 컨테이너에 추가된다. (파일 정보를 갖고있는 객체가 추가되는것)
스프링 빈 환경설정 파일에서 ${} 표현식을 사용해서 값을 사용할 수 있다.
-->
<context:property-placeholder location="classpath:spring/app.properties "/>
<!--
Setter 메소드를 이용하는 생성자 주입 - <property /> 태그를 사용한다.
처리 순서
1. 객체 생성 및 기본 생성자 메소드 실행
PostController x = new PostController()을 실행해서 객체 생성
2. setter 메소드를 실행해서 의존성 주입
x.setSaveDirectory("c:/files");
x.setMaxuploadSize("10485760");
x.setPostDao(postMySQLDao);
-->
<bean id="postController" class="com.sample.post.controller.PostController">
<property name="saveDirectory" value="${file.save.directory}"></property>
<property name="maxUploadSize" value="${file.max.uploadsize}"></property>
<property name="postDao" ref="postMySQLDao"></property>
</bean>
<!--
생성자 메소드를 이용해서 의존성 주입 - <constructor-arg /> 태그를 사용한다.
<contructor-arg /> 태그는 생성자 메소드의 매개변수와 대응되는 태그다.
생성자 메소드에 매개변수가 2개 -> <contructor-arg /> 태그 2개 정의
생성자 메소드에 매개변수가 3개 -> <contructor-arg /> 태그 3개 정의
처리 순서
1. 객체를 생성하고, <constructor-arg />태그의 개수와 매개변수 개수가 일치하는 생성자 메소드를 실행한다.
ProductController x = new Productcontroller(productOracleDao, postMySQLDao);
-->
<bean id="productController" class="com.sample.post.controller.ProductController">
<constructor-arg name="productDao" ref="productOracleDao"></constructor-arg>
<constructor-arg name="postDao" ref="postMySQLDao"></constructor-arg>
</bean>
<bean id="postOracleDao" class="com.sample.post.dao.PostOracleDao"></bean>
<bean id="postMySQLDao" class="com.sample.post.dao.PostMySQLDao"></bean>
<bean id="productOracleDao" class="com.sample.post.dao.ProductOracleDao"></bean>
</beans>
2. 자동 의존성 주입
* UserDao (데이터베이스 접근객체)
package com.sample.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
}
* PointHistoryDao (데이터베이스 접근객체)
package com.sample.dao;
import org.springframework.stereotype.Repository;
@Repository
public class PointHistoryDao {
}
* app.properties ( 이 값을 수동주입할 때는 bean의 value 값으로 다 넣어줬는데 자동주입때 어떻게 하냐 )
# 고객의 기본 할인율
user.discount.rate=0.002
# 고객의 기본 프로필 이미지
user.default.profile.filename=default.png
* context.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!--
app.properties를 자동주입하고 싶을 때의 설정값
-->
<context:property-placeholder location="classpath:spring/app.properties"/>
<!--
<context:annotation-config/>
- 스프링 컨테이너가 생성한 객체에 정의된 아래와 같은 어노테이션을 감지해서
해당 어노테이션에 대한 적절한 작업을 수행하는 객체를 스프링 컨테이너의 빈(객체)으로 등록시킨다.
=> 자동으로 의존성 주입
- 해당 어노테이션
@Autowired - 의존성 자동주입을 지원한다.
@PostConstruct - 스프링 컨테이너가 객체 생성 후 실행할 초기화 작업이 구현된 메소드에 부착한다.
@PreDestroy - 스프링 컨테이너가 생성한 객체를 폐기하기 전에 실행할 작업이 구현된 메소드에 부착한다.
@Transactional - 선언적 트랜잭션처리를 지원한다.
-->
<context:annotation-config/>
<!--
<context:component-scan />
- base-package="패키지경로"로 지정된 패키지 및 그 하위 패키지에서 아래의 어노테이션이 부착된 클래스를 전부 스캔해서
스프링 컨테이너가 객체를 생성하고, 스프링 컨테이너의 빈으로 등록한다.
=> 자동으로 객체 생성, 빈에 등록
- 해당 어노테이션
@Component - 아래의 모든 어노테이션의 부모 어노테이션이다.
이 어노테이션을 상속받아서 사용자 정의 어노테이션을 정의하면, 그 어노테이션 붙은 클래스도 스캔 대상이 된다.
@Repository - 데이터베이스 액세스 작업을 담당하는 객체에 부착한다.
@Service - 비즈니스 로직 수행을 담당하는 객체에 부착한다.
@Controller - HTTP 요청을 처리하는 컨트롤러 객체에 부착한다.
@RestController - RestAPI를 담당하는 컨트롤러 객체에 부착한다.
@ControllerAdvice - Controller 객체의 공통기능을 담당하는 객체에 부착한다.
@RestControllerAdvice - RestController 객체의 공통기능을 담당하는 객체에 부착한다.
@Configuration - Java 기반 빈 설정을 담당하는 객체에 부착한다.
-->
<context:component-scan base-package="com.sample"></context:component-scan>
</beans>
* UserService
package com.sample.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.sample.dao.PointHistoryDao;
import com.sample.dao.UserDao;
@Service
public class UserService {
/*
* @Autowired
* 의존성 자동 주입을 지원하는 어노테이션이다.
* 멤버변수, Setter 메소드, 생성자 메소드, 메소드의 매개변수에 부착할 수 있다.
* 스프링 컨테이너에서 @Autowired 어노테이션 처리하기
* 1. 스프링 컨테이너가 생성한 객체의 클래스에 @Autowired 어노테이션이 있는지 조사한다.
* 2. @Autowired 어노테이션이 지정된 멤버변수, Setter 메소드, 생성자 메소드의 타입을 조사한다.
* @Autowired
* private UserDao userDao; // UserDao 타입의 객체를 주입해야 한다.
*
* @Autowired
* public void setUserDao(UserDao userDao) { // UserDao 타입의 객체를 주입해야 한다.
* this.userDao = userDao;
* }
*
* @Autowired
* public UserService(UserDao userDao, PointHistoryDao pointHistoryDao){ // UserDao, PointHistoryDao 타입의 객체를 주입해야 한다.
* this.userDao = userDao;
* this.pointHistoryDao = pointHistoryDao;
* }
* 3. 스프링 컨테이너가 생성한 객체 중에서 조사한 타입과 일치하는 객체 혹은 해당 타입의 자식 객체를 찾아서 주입시킨다.
* 4. 단, 조사된 타입과 일치하거나 조사된 타입의 자식 객체가 2개 이상 발견되면 오류가 발생한다.
* 5. 2개 이상인 타입이 발견되더라도 @Primary 어노테이션이 부착된 객체가 있으면 해당 객체를 주입시킨다.
*/
@Autowired
private UserDao userDao;
@Autowired
private PointHistoryDao pointHistoryDao;
/*
* @Value
* - 기본자료형 타입, 문자열 타입의 값을 의존성 주입으로 전달받아서 변수에 대입시킨다.
* - 사용법
* 1. properties 파일 생성하기
* src/main/resources/spring/app.properties 파일 생성, 파일 이름은 어떤 이름이든지 상관없음
*
* app.properties
* # app.properties 파일의 설정값
* user.discount.rate=0.002
* user.default.profile.filename=default.png
*
* 2. 빈 설정 파일에 <context:property-placeholder /> 태그 추가하기
* <context:property-placeholder location="classpath:spring/app.properties"/>
*
* 3. properties 파일의 설정값을 객체의 멤버변수에 주입시키기 (스프링에 빈으로 등록되어있어야 함)
* @Value("${user.discount.rate}")
* private double discountRate;
*
*/
@Value("${user.default.profile.filename}")
private String filename;
@Value("${user.discount.rate}")
private double discountRate;
public void config() {
System.out.println(userDao);
System.out.println(pointHistoryDao);
System.out.println("고객의 기본 할인율: " + discountRate);
System.out.println("고객의 기본 프로필이미지 파일명: " + filename);
}
}
S가 붙은 파일 -> 스프링 컨테이너가 관리하는 빈
2-1. 계층간 관계
웹 계층 : 핵심 로직이 필요 -> 서비스 계층 의존
서비스 계층 : 핵심 로직을 수행하려면 데이터베이스 액세스 필요 -> Dao 의존