해당 내용은 김영한씨의 강의 및 스프링 프레임워크 공식 문서, 공식문서 번역을 참고하여 작성한 글입니다.
올바르지 않은 내용이 있다면 의견 남겨주세요. 언제든지 환영입니다.
1. 프론트 컨트롤러
1-1. VER.1
MVC 패턴을 적용하였지만, 하나의 컨트롤러에 공통 + 비즈니스 로직이 같이 존재한다. 하지만 프론트 컨트롤러 패턴을 사용하면 웹 애플리케이션의 구조를 단순화하고, 공통된 처리 로직을 중앙 집중화할 수 있다. 스프링 MVC는 dispatcherServlet이 프론트컨트롤러 패턴으로 구현되어있다.
공통을 집중한 프론트 컨트롤러 패턴은 다음과 같은 장점이 있다
단일 진입점에서 처리하여 공통된 처리 로직을 중앙 집중화할 수 있으므로 코드의 일관성을 유지하고, 중복된 코드를 줄일 수 있다.
package hello.servlet.web.frontcontroller.v1;
import hello.servlet.web.frontcontroller.v1.controller.MemberFormControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberListControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberSaveControllerV1;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/* * 하위에 있는 다 들어옴*/
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
/* 해당 url 호출하면 FrontControllerServletV1 이 호출 되야 하므로 */
Map<String, ControllerV1> controllerV1Map = new HashMap<String, ControllerV1>();
/*
* 1. 해당 컨트롤러 호출
* 2. 생성자 > url 주소에 해당하는 Controller 저장
* 2-1. controllerV1Map의 url 주소와 일치하는 ControllerV1이 실행 */
public FrontControllerServletV1() {
controllerV1Map.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
controllerV1Map.put("/front-controller/v1/members/save", new MemberSaveControllerV1());
controllerV1Map.put("/front-controller/v1/members", new MemberListControllerV1());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
System.out.println("========= FrontControllerServletV1 ==============");
// 생성자에 일치하는 URL 찾기
/*ControllerV1 controller = controllerV1Map.get(requestURI) 는 곧
* ControllerV1 controller = new MemberFormControllerV1() 와 같다 (url이 /front-controller/v1/members/new-form 일 경우) => 다형성 */
String requestURI = req.getRequestURI();
ControllerV1 controller = controllerV1Map.get(requestURI);
// 만약 없으면
if (controller == null) {
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controller.process(req, res);
}
}
URL 주소를 '/front-controller/v1/*' 로 설정하여 주소가 '/front-controller/v1/save', '/front-controller/v1/list/123' 등 '/front-controller/v1/' 로만 시작한다면 FrontControllerServletV1이 실행된다.
다형성을 이용한 controllerV1Map 생성자에서 url 주소에 해당하는 Controller를 찾는다.
해당 내용은 김영한씨의 강의 및 스프링 프레임워크 공식 문서, 공식문서 번역을 참고하여 작성한 글입니다.
올바르지 않은 내용이 있다면 의견 남겨주세요. 언제든지 환영입니다.
1. 서블릿 코드
package hello.servlet.domain.member;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*
* 동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용
* */
public class MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static Long sequence = 0L; // id가 하나씩 증가하는 것 사용
// 순수하게 만드는 것이므로 싱글톤
private static final MemberRepository instance = new MemberRepository();
public static MemberRepository getInstance() {
return instance;
}
private MemberRepository() {}
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
public Member findById(Long id) {
return store.get(id);
}
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore() {
store.clear();
}
}
public static MemberRepository getInstance() {
return instance;
}
getInstance()로 싱글톤 패턴을 구현하였으며, 스프링 빈을 이용하지 않고 순수하게 서블릿을 만들었다. 비즈니스 로직만 구현하면 되니 보다 편하게 구현할 수 있었으나, 자바코드로 html을 만들어 간단한 회원등록 화면임에도 복잡하고 비효율적인 코드를 작성할 수 밖에 없었다. 그래서 자바 코드로 html을 만드는 대신 html 문서에 자바 코드를 동적으로 넣을 수 있다면 편리하지 않을까하고 고민하여 나온 것이 템플릿 엔진이다.
모든 출력을 Java 코드 내에서 문자열로 작성해야 한다는 점에서 오류가 발생하기 쉬웠다. 이때 등장한 JSP는 JAVA 코드를 삽입할 수 있도록 허용하게 했다. 그렇기에 사람들은 데이터베이스 코드, 비즈니스 로직, HTML 마크업등 모든 자바스크립트 코드를 한 곳에서 작성하기 시작했고 이것은 코드 재사용을 불가능하게 하였다. 이러한 구조를 MVC1(Model1)로 정하기도 하였다.
MVC2 패턴은 MVC1의 문제를 개선하였으며 1970년 후반 스몰토크를 연구하던 개발자들과 합의로 인해 나온 패턴이다. 구조는 위의 사진과 같다.
⚠️ 아래 적용한 코드의 Service는 이전에 만든 MemberRepository로 사용하고, Model은 request 내부 저장소를 이용하였다. 그리고 request.getAttribute() , request.setAttribute()를 사용하여 데이터 보관 조회하였다.
해당 내용은 김영한씨의 강의 및 스프링 프레임워크 공식 문서, 공식문서 번역을 참고하여 작성한 글입니다.
올바르지 않은 내용이 있다면 의견 남겨주세요. 언제든지 환영입니다.
1. Lifecycle Callbacks
데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다.
아래는 김영한씨의 강의의 예제 코드를 참고하여 우리가 의도하는 결과가 나오는지 테스트해보았다.
public class NetworkClientTest {
private String url;
public NetworkClientTest() {
System.out.println("현재 url은 다음과 같습니다 : " + url);
connect();
call("초기화 연결되었습니다");
}
// 연결함수
public void connect() {
System.out.println("connect, 연결된 url : " + url);
}
public void disconnect() {
System.out.println("disconnect, 연결 끊어버린 url : " + url);
}
// URL 세팅 함수
public void setUrl(String url) {
this.url = url;
}
// 호출함수
// @param msg : 호출하려는 메세지
public void call(String msg) {
System.out.println("call message : " + msg + " url : " + url);
}
}
lifecycle 테스트 결과
나는 객체가 생성되면서, 원하는 url이 세팅되도록 의도했으나 그게 되지 않았다. 당연하다. 스프링 빈은 객체를 생성할 때 생성자 > 세터기반으로 생성하고 그 뒤에 의존관계를 주입한다. 이미 저 코드는 주입까지 모두 마친 후에 내가 초기값을 할당한게 된다. 그럼 connect() 함수가 실행 되기 전, 초기값을 세팅하고 싶으면 어떻게 해야할까? 방법은 아래 3가지로 나타낼 수 있다.
2. InitializingBean, DisposableBean
InitializingBean, DisposableBean 인터페이스들로 초기화 작업 및 연결을 끊은 이후의 작업을 진행할 수 있다.
public class NetworkClientTest implements InitializingBean, DisposableBean {
private String url;
public NetworkClientTest() {
System.out.println("현재 url은 다음과 같습니다 : " + url);
}
// 연결함수
public void connect() {
System.out.println("connect, 연결된 url : " + url);
}
public void disconnect() {
System.out.println("disconnect, 연결 끊어버린 url : " + url);
}
// URL 세팅 함수
public void setUrl(String url) {
this.url = url;
}
// 호출함수
// @param msg : 호출하려는 메세지
public void call(String msg) {
System.out.println("call message : " + msg + " url : " + url);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet, 초기화 작업 함수");
connect();
call("초기화 연결되었습니다");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy, 연결 파괴될 때 callback");
}
}
InitializingBean, DisposableBean lifecycle 테스트 결과
다만, 위의 인터페이스들은 스프링 전용 인터페이스이며 스프링 공식문서에서는 JSR-250의 @PostConstruct @PreDestroy을 사용하길 권장한다. 해당 @은 스프링 전용 인터페이스가 아니고 자바 표준이며 스프링이 아닌 다른 컨테이너에서도 동작하기 때문이다.
3. @PostConstruct @PreDestroy
public class NetworkClientTest{
private String url;
public NetworkClientTest() {
System.out.println("현재 url은 다음과 같습니다 : " + url);
}
// 연결함수
public void connect() {
System.out.println("connect, 연결된 url : " + url);
}
public void disconnect() {
System.out.println("disconnect, 연결 끊어버린 url : " + url);
}
// URL 세팅 함수
public void setUrl(String url) {
this.url = url;
}
// 호출함수
// @param msg : 호출하려는 메세지
public void call(String msg) {
System.out.println("call message : " + msg + " url : " + url);
}
@PostConstruct
public void init() throws Exception {
System.out.println("PostConstruct 초기화 작업 함수");
connect();
call("초기화 연결되었습니다");
}
@PreDestroy
public void close() throws Exception {
System.out.println("PreDestroy, 연결 파괴될 때 callback");
}
}
@PostConstruct @PreDestroy lifecycle 테스트 결과
@PostConstruct, @PreDestroy가 붙은 메서드는 명시적으로 선언된 callback 메서드와 생명주기의 같은 지점에서 호출된다. @만 붙이면 되므로 매우 편리한 장점을 가지고 있다. 다만 외부 라이브러리에는 적용하지 못하므로, 외부 라이브러리를 초기화, 종료해야한다면 @Bean의 기능을 사용한다.
4. Receiving Lifecycle Callbacks
public class NetworkClientTest{
private String url;
public NetworkClientTest() {
System.out.println("현재 url은 다음과 같습니다 : " + url);
}
// 연결함수
public void connect() {
System.out.println("connect, 연결된 url : " + url);
}
public void disconnect() {
System.out.println("disconnect, 연결 끊어버린 url : " + url);
}
// URL 세팅 함수
public void setUrl(String url) {
this.url = url;
}
// 호출함수
// @param msg : 호출하려는 메세지
public void call(String msg) {
System.out.println("call message : " + msg + " url : " + url);
}
public void init() throws Exception {
System.out.println("init 초기화 작업 함수");
connect();
call("초기화 연결되었습니다");
}
public void close() throws Exception {
System.out.println("PreDestroy, 연결 파괴될 때 callback");
}
}
Receiving Lifecycle Callbacks lifecycle 테스트 결과
@Bean을 통해 정의된 모든 클래스는 일반적인 생명주기 콜백을 지원한다. @Bean은 Spring XML의 bean 엘리먼트 속성인 init-method, destroy-method에 초기화, 파괴용도의 콜백 메서드를 지정할 수 있다. 만약, 기본적으로 'close', 'shoutdown'이라는 이름을 가진 public 메서드를 갖고 있다면 자동적으로 콜백에 참여하게 된다. 호출하기를 원하지 않는다면 @Bean(destroyMethod="") 와 같이 추가한다. @Bean은 기본적으로 (inferred, 추론) 하기 때문이다.
5. Combining Lifecycle Mechanisms
빈(Bean) 생명주기 동작 방법을 3가지를 알아보았다. 만약 하나의 빈에 여러개의 생명주기 동작이 설정되었고, 각기 다른 메서드 이름으로 구성되었을 때 각 메서드는 다음과 같은 순서로 실행된다. 만일 중복된 메서드 이름이 존재한다면 한번만 실행된다.
Initialization methods
Destroy methods
1. @PostConstruct 2. InitializingBean 인터페이스에서 정의된 afterPropertiesSet() 3. 커스텀한 init()
1. @PreDestroy 2. DisposableBean 인터페이스에서 정의된 destroy() 3. 커스텀한 destroy()
프로젝트 하다보면 하나의 객체만 생성하고 그 객체를 공유할 경우가 많다. 그럴 때, 이 싱글톤 패턴을 활용해서 적용할 수 있다. 해당 싱글톤 패턴을 사용하면 객체 공유가 가능하며 여러 고객이 동시에 요청할 때 요청시마다 객체가 생성되는 것이 아니므로 메모리 낭비를 줄일 수 있다. 즉 싱글톤은 JVM당 하나의 인스턴스만 갖도록 하는 디자인 패턴인 것이다.
public class SingleObject {
//create an object of SingleObject
private static SingleObject instance = new SingleObject();
//make the constructor private so that this class cannot be
//instantiated
private SingleObject(){}
//Get the only object available
public static SingleObject getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
singleton 참조값 일치
다만, 이런 싱글톤 패턴을 사용하기 위해서는 내부에서 private static 변수를 선언하고, 객체를 new() 로 생성할 수 없도록 생성자를 작성해주어야 한다. 이것은 작성 코드가 길어진다는 단점이 된다. 또한 값을 변경할 수 있는 필드가 있을 경우 문제가 발생한다는 큰 단점이 존재한다. 아래 코드는 김영한씨의 강의 중 유지(stateful)하게 설계되었을 때의 문제코드이다.
public class StatefulService {
//상태를 유지하는 필드
private int price;
public void order(String name, int price) {
System.out.println("name = " + name + " price = " + price);
//여기가 문제!
this.price = price;
}
public int getPrice() {
return price;
}
}
하나의 객체를 공유하다보니 내가 원하지 않는 결과가 나왔다. 그렇기에 싱글톤 패턴을 사용하기 위해서는
특정 클라이언트에 의존적인 필드가 있으면 안된다.
특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
가급적 읽기만 가능해야한다.
필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal등을 사용한다.
2. Singleton Container
스프링 컨테이너는 우리가 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다. 싱글톤 scope로 정의한 bean에 대해 딱 하나의 인스턴스를 만드는 것이다. 그리고 이러한 싱글톤 인스턴스는 싱글톤 bean을 모아두는 캐시에 저장이 되며 요청이 오면 그 캐시에서 인스턴스를 반환해준다. 아래 그림은 스프링 공식 문서의 싱글톤 scope 동작 그림이다.
스프링 공식 문서 - singleton scope 사진
그럼, 우리는 이제 궁금해지기 시작한다. 공식 문서에서 accountDao를 수없이 작성했지만 하나만 생성될 수 있던 이유가 무엇일까
컨테이너 생성 흐름
위의 사진과 같은 흐름으로 컨테이너가 생성이 된다. 또한 scope란 속성이 싱글톤이므로 doRegisterBeanDefinitions에서 싱글톤으로 생성이 되겠다.
<!-- the following is equivalent, though redundant (singleton scope is the default); using spring-beans-2.0.dtd -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
3. Java Based Singleton Container
그럼 java를 기반으로 한 싱글톤은 어떻게 생성이 되는 것일까,
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(getMemberRepository());
}
@Bean
public MemberRepository getMemberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(getMemberRepository(), getDiscountPolicy());
}
}
다음과 같은 AppConfig.java 에서 new로 객체를 생성해주고 있기 때문이다. 특히 getMemberRepository()는 memberService(), getMemberRepository(), orderService()를 통해서 최소 3번 new로 객체생성하고 있어 궁금증이 생길 수 있다. 정답은 @Configuration으로 가능하다. (사실은 @Configuration 클래스 안에 있는 @Bean 메서드가 처리되는 것이다)
The @Bean methods in a regular Spring component are processed differently than their counterparts inside a Spring @Configuration class. The difference is that @Component classes are not enhanced with CGLIB to intercept the invocation of methods and fields. CGLIB proxying is the means by which invoking methods or fields within @Bean methods in @Configuration classes creates bean metadata references to collaborating objects. Such methods are not invoked with normal Java semantics but rather go through the container in order to provide the usual lifecycle management and proxying of Spring beans, even when referring to other beans through programmatic calls to @Bean methods. In contrast, invoking a method or field in a @Bean method within a plain @Component class has standard Java semantics, with no special CGLIB processing or other constraints applying.
공식문서에 보면, spring 컴포넌트의 @Bean 메서드는 spring이 @configuration 클래스에 있는 @Bean 메서드와는 다르게 처리된다. 차이점은 메서드나 필드 호출을 가로채기 위한 @Component 클래스에 CGLIB가 사용되지 않는다는 점이다.
CGLIB는 바이트 코드 생성 라이브러리로 Java 바이트 코드를 생성하고 변환하기 위한 고급 API이다. 동적 프록시 객체를 생성하고 필드 액세스를 가로채기 위해 AOP, 테스트, 데이터 액세스 프레임워크에서 사용된다고 한다.
제어의 역전 IoC(Inversion of Control) 기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행했다. 한마디로 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다. 개발자 입장에서는 자연스러운 흐름이다. 하지만 프로그램의 제어 흐름을 구현 객체가 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 한다.
예를 들어,
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
위의 코드를 보면, OrderServicempl가 MemoryMemberRepository, FixDiscountPolicy란 객체를 생성하고 연결했다.
역할과 구현으로 나누었다고 하지만 OrderServiceImpl은 MemberRepository와 MemoryMemberRepository를 바라보고 있으며 또 DiscountPolicy, FixDiscountPolicy를 동시에 바라보고 있다.
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// 생성자
public OrderServiceImpl (MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
};
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(getMemberRepository());
}
private MemberRepository getMemberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(getMemberRepository(), getDiscountPolicy());
}
private DiscountPolicy getDiscountPolicy() {
return new RateDiscountPolicy();
}
}
그렇기에 AppConfig 파일을 만듦으로써, OrderServiceImpl은 자신의 로직을 실행하는 역할만 담당하고 어떤 구현 객체들이 실행될지 알 수 없어지게 수정했다. 즉 이전의 OrderServiceImpl가 가지고 있던 제어 흐름을 이제 AppConfig가 가져간 것이다. 이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)인 것이다.
2. Introduction to the Spring IoC Container
IoC의 원리는 의존성 주입(DI)*이다. 스프링 프레임워크는 빈(🫘) 생성 시에 컨테이너에 의존성을 주입한다. 이러한 과정은 Service Locator pattern**과 같은 메커니즘을 사용하여 종속성의 인스턴스화 또는 위치를 제어하는 빈 자체의 역(즉, 제어의 반전)인 것이다.
org.springframework.beans와 org.springframework.context 패키지는 스프링 IOC 컨테이너의 기본 패키지들이다. BeanFactory 인터페이스는 객체 생성과 검색에 대한 기능을 정의하고 있다. 예를 들어 생성된 객체를 검색하는데 필요한 getBean() 메서드가 존재하며, 객체 검색 이외에도 싱글톤/프로토타입 빈인지 여부 확인하는 기능이 존재한다. ApplicationContext는 메시지 소스를 활용한 국제화 기능(한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로), 환경 변수(로컬, 개발, 운영 구분 처리), 애플리케이션 이벤트(이벤트 발행하고 구독하는 모델), 리소스조회(파일, 클래스패스, 외부)를 가지고 있는 BeanFactory의 서브인터페이스다.
BeanFactory, ApplicationContext
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 중복 오류가 발생한다")
void findBeanByParentTypeDuplicate(){
DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
Assertions.assertThrows(NoUniqueBeanDefinitionException.class, ()-> ac.getBean(DiscountPolicy.class));
}
@Configuration
static class TestConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
3. XML-basedContainerConfiguration and Java-based Container Configuration
xml과 @을 기반으로 한 IOC 설정방법은 다음과 같다.
같은 코드의 java, xml based container configuration
요즘 많은 개발자들은 Java를 기반으로 한 스프링 설정을 많이 사용한다.XML 기반은 <bean>을 이용해 구성한다면 Java 구성에서는 일반적으로 @Configuration 클래스 내에서 @Bean을 사용한다.
* DI는 [Spring] DI로 따로 정리되어있다.
** Service Locator pattern은 디자인 패턴 중 하나로, 중개자를 통해 접근하도록 하는 패턴이다.
2023/01/26 : 왜 BeanFactory보다 ApplicationContext를 써야하는지 정리할 것
위의 코드에서 OrderServiceImpl는 현재 memberRepository, discountPolicy를 의존(Dependency)하고 있다. 그리고 생성자를 통해 OrderServiceImpl이 의존하고 있는 memberRepository, discountPolicy를 주입(Injection) 받았다. 이것을 의존성 주입이라고 한다. DI의 방법은 Constructor-based Dependency Injection 와 Setter-based Dependency Injection 2가지가 있다.
2. Constructor-based Dependency Injection, 생성자 기반 의존성 주입
생성자 기반 의존성 주입은 컨테이너가 여러 인수로 생성자를 호출하여 수행되며, 각각 종속성을 나타낸다.
생성자를 이용하여 의존 객체를 주입할 때는 <constructor-arg> 태그를 사용한다. 의존 객체를 두 개 이상으로 사용할 때도
<constructor-arg>를 사용하여 순서대로 작성해주면 된다. 아래는 스프링 공식 문서에 작성되어 있는 예제이다.
Consturctor-based Dependency Injection 예제
3. Setter-based Dependency Injection, 세터 기반 의존성 주입
set으로 시작하는 설정 메서드를 통해서 의존성 주입하는 방법이다. 해당 방법은 다음과 같은 형식을 지닌다.
메서드 이름이 set으로 시작한다.
set뒤에는 프로퍼티* 이름의 첫글자를 대문다로 치환한 이름을 사용한다. (예: setCamelCase)
한개의 파라미터를 가지며, 파라미터의 타입은 프로퍼티의 타입이다.
아래는 스프링 공식 문서에 작성되어 있는 예제이다.
Setter-based Dependency Injection 예제
4. Constructor-based or setter-based DI?
생성자 기반 DI와 설정자 기반 DI를 혼합할 수 있으므로 필수 종속성에는 생성자를 사용하고 선택적 종속성에는 설정 방법 또는 구성 방법을 사용하는 것이 좋다. 설정자 메서드에서 @Autowired 주석***을 사용하여 속성을 필수 종속성으로 만들 수 있지만, 인수의 프로그램적 유효성 검사를 사용하는 생성자 주입이 선호된다.
*** 스프링 빈이 없어도 동작하길 원한다면 @Autowired(return=false), public void setNoBean2(@Nullable Member member) , public void setNoBean3(Optional member)로 작성한다. @Nullable,Optional는 null, Optional.empty를 반환해준다.
Spring 팀은 일반적으로 생성자 주입을 지지한다. 응용 프로그램 구성 요소를 불변 개체로 구현하고 필요한 종속성이 null이 되지 않도록 보장하기 때문이다. 또한 생성자가 주입한 구성 요소는 항상 완전 초기화 상태에서 클라이언트(호출) 코드로 반환된다. (객체 생성할 때 딱 1번 호출되고 이후에 호출되는 일이 없다) 부가적으로, 많은 수의 생성자 인수는 나쁜 코드이며, 이는 클래스가 너무 많은 책임을 가지고 있을 가능성이 있으며 적절한 문제 분리를 더 잘 해결하기 위해 리팩터링되어야 한다는 것을 의미한다.
또한 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없으며, 대부분 의존관계가 애플리케이션 종료전까지 변하면 안되므로 생성자 주입이 더 좋다.
세터 주입은 주로 클래스 내에서 합리적인 기본값을 할당할 수 있는 선택적 종속성에만 사용되어야 한다. 그렇지 않으면 코드가 종속성을 사용하는 모든 곳에서 null이 아닌 검사를 수행해야 한다. 세터 인젝션의 한 가지 이점은 세터 메소드가 해당 클래스의 객체를 나중에 재구성하거나 재인젝션할 수 있게 한다는 것이다. (: public으로 열어두어야 한다. 이것은 누군가 실수로 변경할 수도 있다.) 따라서 JMX MBeans를 통한 관리는 세터 주입을 위한 강력한 사용 사례이다.
* 프로퍼티 : 자바빈즈(JavaBeans)**에 규약된 프로퍼티를 의미한다. bean 클래스의 프로퍼티를 정의할 때는 public getter setter 메서드를 제공해야한다.
public class FaceBean {
private int mMouthWidth = 90;
public int getMouthWidth() {
return mMouthWidth;
}
public void setMouthWidth(int mw) {
mMouthWidth = mw;
}
}
정적 웹 페이지는 누군가 수동으로 변경할 때까지 동일하게 유지된다 그리고 정적사이트는 항상 모든 사람에게 동일하게 보인다.(In static web pages, Pages will remain the same until someone changes it manually. And static site will always look the same to everyone.)
우리가 URL(http://localhost:8080/hello-static.html)을 호출한다고 했을 때,
김영한(스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술) PDF p13
1. hello-static 관련 컨트롤러를 찾는다 ▶없다
2. hello-static.html을 찾는다.
동적 웹 페이지(dynamic web page)
동적 웹 사이트는 방문자에 따라 다른 정보를 제공한다. 방문자의 지역, 시간, 혹은 설정 및 선호도, 웹 사이트에서 취한 행동과 같은 여러요소에 의해 방문자의 콘텐츠가 결정될 수 있으므로 보다 맞춤화된 대화형 콘텐츠를 만들 수 있다.
(dynamic website presents different information to different visitors. The content that a visitor sees can be determined by several factors, such as their location, local time, settings and preferences, and/or actions they’re taken on the website (e.g., shopping habits), making for a more tailored and interactive experience.)
우리가 URL(http://localhost:8080/hello-mvc)을 호출한다고 했을 때,
김영한(스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술) PDF p14
1. hello-mvc 관련 컨트롤러를 찾는다 ▶있다.
2. ViewResolver가 hello-template.html을 찾는다.
3.hello-template.html에 model에 담긴 값을 세팅하여 보여준다.
** ViewResolver
스프링은 특정 뷰 기술에 얽매이지 않고 브라우저에서 모델을 렌더링할 수 있도록 ViewResolver를 제공한다. 스프링은 뷰를 처리하는 방식에 중요한 두개의 인터페이스 ViewResolver, View를 제공한다. ViewResolver는 뷰 이름과 실제 뷰 간의 매핑을 제공한다.
(Spring provides view resolvers, which enable you to render models in a browser without tying you to a specific view technology. The two interfaces which are important to the way Spring handles views are ViewResolver and View. The ViewResolver provides a mapping between view names and actual views)
UrlBasedViewResolver가 있다. 위와 같이 설정하면 return "test" 시 /WEB-INF/jsp/test.jsp로 보낼 RequestDispatcher 로 전달한다.
(When returningtestas a viewname, this view resolver will hand the request over to theRequestDispatcherthat will send the request to/WEB-INF/jsp/test.jsp.)