스프링 - Bean Factory, Component Scan, DI

letsbebrave·2022년 8월 29일
0

spring

목록 보기
2/3
post-thumbnail

공부의 목적

Spring Framework로 구현한 펫팔 프로젝트의 소스코드를 다시 보면서 Spring Framework에 대한 지식을 정리해보려고 한다. 정리해보려는 것은 다음과 같다!

  • Spring Framework 개념
  • Spring Framework 동작 방식
  • STS를 활용한 프로젝트의 구조를 보면서 root-context나 servlet-context 파일 등의 의미 파악
  • 왜 코드를 저렇게 작성했는지 정리해보고 더 나은 방법은 없었을지 고민 (플러스 알파!)

다시 Spring Framework를 공부하면서 놓쳤던 개념과 "개념의 존재 이유"에 대해 제대로 파악하고 싶다.
파이팅 🍍🍍


Spring 개념

Spring IoC

Inversion of Control
프로그램을 구동하는데 필요한 객체에 대한 생성, 변경 등의 관리를 프로그램을 개발하는 사람이 아닌 프로그램을 구동하는 컨테이너에서 직접 관리하는 것
구동 시 필요한 객체의 생성부터 생명 주기까지 해당 객체에 대한 관리를 직접 수행

🔼 Spring IoC container
POJO : 우리가 만드는 순수한 자바 코드
Configuration Metadata : 모든 설정값, 스프링 컨테이너가 구현시킴
Spring Container : URL 매핑 등.. 객체의 생명주기 관리 (특정 시점에 얘를 동작시켜줌)

IoC 컨테이너

Application Context 인터페이스
여러 개의 인터페이스 덩어리
빈 객체를 등록해놓고 꺼내와서 씀

  • Bean : 스프링에서 관리하는 객체, id나 type을 통해 인지해서 가져올 수 있음
  • Bean Factory : Bean의 생성과 설정, 관리 등의 역할하는 컨테이너 (객체들의 모음)
    스프링 컨테이너의 최상위 컨테이너이며, ApplicationContext와 함께 스프링 컨테이너라고 불림
    스프링 컨테이너는 처음 실행할 때 XML에 등록해놓은 모든 객체들을 Bean으로 만들어놓음
    IoC 컨테이너는 필요할 때 Bean으로 등록된 객체를 가져다가 씀!
    (Bean들을 관리하는 것은 Bean Factory)


    🔼 여러 인터페이스들을 상속받은 ApplicationContext 인터페이스
	// IoC 컨테이너 - ApplicationContext
    ApplicationContext context = new GenericXmlApplicationContext("com/greedy/section01/xmlconfig/spring-context.xml");
		
	System.out.println(context);
	
    // IoC 컨테이너를 통해 등록된 객체인 Bean을 가져올 수 있음!
	MemberDTO member = (MemberDTO) context.getBean("member");

cf. IoC 컨테이너 생성될 때 필수 설정값들 다 컨테이너에 알려줘야 함 -> 그래야 Bean으로 만들어 놓고 필요할 때 가져다 쓸 수 있음
실행하기 전에 환경 설정에 필요한 값들 다 알려주어야 함

  • 인터페이스들이 여러 개로 나뉘어져 있는 이유?
    하나의 코드 수정 시 다른 코드들도 수정해야 하는 의존성을 낮추기 위해
    cf.
    - 모듈 : 모듈화를 통해 분리된 시스템의 각 기능들, 모듈의 독립성은 결합도와 응집도에 의해 측정됨
    - 결합도 : 모듈 간에 상호 의존하는 정도 및 두 모듈 사이의 연관 관계
    - 응집도 : 정보 은닉 개념을 확장한 것으로, 명령어나 호출문 등 모듈의 내부 요소들의 서로 관련되어 있는 정도, 즉 모듈이 독립적인 기능으로 정의되어 있는 정도
    - 결합도가 낮고 응집도가 높을수록 품질이 좋은 모듈
    https://m.blog.naver.com/gluestuck/221899977072

  • ApplicationContext : IoC 컨테이너

  • GenericXml ApplicationContext : 하위에 상속받은 클래스 / XML로 어플리케이션의 설정값들 모아둔 것

Class를 Bean으로 등록하는 방법?

  1. spring-context.xml에서 bean으로 해당 class를 등록함

    <?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 등록 -->
        <!-- 
             Container는 bean 목록에서 bean을 찾을 때 고유한 이름으로 사용.
             만약 id를 생략하면 클래스명의 앞 글자를 소문자로 하는 naming rule로 자동 bean의 id를 생성.
             class 속성은 bean으로 만들 객체의 타입을 풀 패키지명으로 작성.	
         -->
        <bean id="member" class="com.greedy.section01.xmlconfig.MemberDTO">
            <!-- 생성자 쪽으로 값을 전달할 때 index와 name속성을 사용한다. -->
            <!-- =new MemberDTO(1,"user01","pass01","홍길동") 이랑 같은거 -->
            <constructor-arg index="0" value="1"/>
            <constructor-arg name="id" value="user01"/>
            <constructor-arg index="2"><value>pass01</value></constructor-arg>
            <constructor-arg name="name"><value>홍길동</value></constructor-arg>
        </bean>
    </beans>
    


    이전에 new 키워드를 이용해 객체 생성하던 게 bean이라는 스프링에서 관리하는 객체들로 대체됨!
    기본생성자를 통해서 객체 만들어주는 것과 같음

  2. java config로 해당 class를 등록함

    @Configuration
    public class ContextConfiguration {
    
        /* bean을 등록하기 위해서는 @Bean 어노테이션을 이용한다. */
        /*
         * @Bean(name="myName") 혹은 @Bean("myName")을 이용하여 bean의 id를 설정할 수 있다.
         * 이 때 bean의 이름을 지정하지 않으면 메소드의 이름을 bean의 id로 자동 인식한다.
         */
        @Bean(name="member")
        public MemberDTO getMember() {
            return new MemberDTO(1,"user01","pass01","홍길동");
        }
    }

    @Configuration

    환경설정을 하는 class라고 알려주는 annotation
    xml을 통해 환경설정한 것과 동일한 의미!

    @Bean

    bean을 등록

Component Scan

여러 개의 Bean을 스캔해서 가져오는 방식
Annotation 설정해놓은 걸 다 읽어들이는 것
IoC 컨테이너가 등록해놓은 Bean을 Spring Container는 알고 있어야 함

페이지가 로드 될 때, 즉 IoC 컨테이너로 Spring Container가 만들어질 때 등록되어 있는 annotation에 대한 걸 모두 가지고 있어야 함
-> 이것을 Component Scan으로 함

  • 명시적 조회 (패키지 단위 - 하위의 모든 것 -> 속도 느림)
  • 전체 조회
  • 다 조회해야 하므로 속도 느림 -> 어느 부분 빼고 조회해라 할 수 있음

IoC 컨테이너에서 특정 빈을 빼거나 범위 제한하는 방법들

1. basePackages로 특정 하위에 있는 클래스만 Bean으로 등록

base-package로 설정된 경로 하위에 특정 어노테이션을 가지고 있는 클래스를 이용하여 bean으로 등록한다.

/* basePackages에 등록되지 않은 패키지는 스캔에서 제외하고, 등록된 패키지 내의 @Component 어노테이션을 탐색한다.
 * 이 때 basePackage를 등록하지 않으면 현 설정클래스가 존재하는 패키지를 자동으로 basePackage로 설정한다.
 * 문자열 배열 형태로 여러 basePackage를 등록할 수 있다.
 */
@Configuration
@ComponentScan(basePackages = "com.greedy.section01.javaconfig")
public class ContextConfiguration1 {
	
}

2. excludeFilters로 스캐닝에서 제외할 타입 설정

/* excludeFilter로 스캐닝에서 제외할 타입을 기술하면 해당 타입은 스캐닝에서 제외한다. */

@Configuration
@ComponentScan(basePackages = "com.greedy.section01.javaconfig",
               excludeFilters = {
            		   @ComponentScan.Filter(
            				   /* 1. 타입으로 설정 ASSIGNABLE_TYPE -> 클래스타입으로 */
//            				   type=FilterType.ASSIGNABLE_TYPE,
//            				   classes = { MemberDAO.class }
            				   /* 2. 어노테이션 종류로 설정 */
//            				   type=FilterType.ANNOTATION,
//            				   classes = { org.springframework.stereotype.Component.class }
            				   )
            		   }
               )
public class ContextConfiguration2 {

}

3. includeFilters로 스캐닝에서 포함할 타입 설정

@Configuration
/*
 * basePackage의 기본 설정 경로를 지정하고 useDefaultFilters를 false로 하여
 * 모든 어노테이션을 스캔하지 않는다.
 * 이때 스캔할 대상 클래스만 따로 지정할 수도 있다.
 */
@ComponentScan(basePackages = "com.greedy.section01.javaconfig",
               useDefaultFilters = false, // 디폴트 annotation 스캔 안 함
               includeFilters = {
            		   @ComponentScan.Filter(
            				   /* exclude 필터 설정하는 방식과 동일하다. */
            				   type=FilterType.ASSIGNABLE_TYPE,
            				   classes = { MemberDAO.class }
            				   )
               })
public class ContextConfiguration3 {

}

@Component 어노테이션이 작성된 클래스를 인식하여 bean으로 만들게 되면, 특수 목적에 따라 세부 기능을 제공하는 @Controller, @Service, @Repository(DAO), @Configuration(환경설정) 등을 인식한다.
(목적에 따른 분리 - 각각의 클래스별로 트랜잭션 처리 따로 해줘야 하는데 그렇지 않으면 처리해줄 게 많아졌음! + 어느 부분에서 오류 났는지 명확해짐!)

Annotation 종류

스프링 컨테이너가 스캐닝 기능을 이용하여 해당 클래스를 bean으로 등록할 수 있도록 어노테이션을 설정
value 속성을 이용하여 bean의 id를 설정할 수 있으며, value는 생략 가능
이름(id)를 설정하지 않으면 class의 앞글자를 소문자로 하여 bean으로 생성

@Component : 스프링에서 관리되는 객체임을 표시하기 위해 사용하는 가장 기본적인 어노테이션이다.
@Controller : Web MVC 코드에서 사용되는 어노테이션으로 @RequestMapping 어노테이션은 해당 어노테이션이 작성된 클래스 내에서만 사용 가능하다.
@Service : 비즈니스 로직이 작성된 클래스에 사용한다. 추가적인 기능은 없지만 추가적인 기능을 제공할 가능성이 있으니 계층을 명시할 때 사용한다.
@Repository : 플랫폼별 예외를 포착하여 PersistenceExceptionTransacttionPostProcesser가 DataAccessException으로 변환하여 다시 발생한다.
계층이 명확하지 않은 경우에는 @Component를 사용한다.

Bean을 컨테이너에서 가져오는 방법?

package com.greedy.section01.xmlconfig;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class Application {

	public static void main(String[] args) {

		/* Bean 등록된 IoC컨테이너 */
		
		ApplicationContext context = new GenericXmlApplicationContext("com/greedy/section01/xmlconfig/spring-context.xml");
		
		System.out.println(context);
		
		/* 1. bean의 id를 이용해서 bean을 가져오는 방법
		 * id를 이용하는 경우 bean의 정확한 type을 유추할 수 없기 때문의 Object타입으로 반환하므로
		 * 다운캐스팅해서 사용한다.
		 */
//		MemberDTO member = (MemberDTO) context.getBean("member");
		
		/* 2. Bean의 클래스 메타 정보를 전달하여 가져오는 방법 
		 * 가져오려는 bean의 타입을 명확히 전달하기 때문에 형변환이 필요없다.
		 */
//		MemberDTO member = context.getBean(MemberDTO.class);
		
		/* 3. bean의 id와 클래스 메타정보를 전달하여 가져오는 방법
		 * 가져오려는 bean의 타입을 전달하기 때문에 형변환이 필요 없다.
		 */
		MemberDTO member = context.getBean("member",MemberDTO.class);
		
		System.out.println(member);
	}

DI

Bean 객체를 어떻게 불러와서 쓸 거냐에 따라 방식이 달라지는 것

생성자를 통한 의존성 주입

@Configuration
public class ContextConfiguration1 {

	@Bean
	public Account accountGenerator() {
		return new PersonalAccount(20, "110-123-45678", "1234");
	}
	
	/* 생성자를 통해 Account을 생성하는 메소드를 호출한 리턴값을 사용하여 bean을 조립할 수 있다. */
	@Bean
	public MemberDTO memberGenerator() {
		return new MemberDTO(1,"홍길동","010-1234-5678","hong123@greedy.com",accountGenerator()); 
        // 생성자를 통한 의존성 주입
        // 다른 Bean 객체를 생성자를 통해 의존성 주입
	}
}

cf. 기본 생성자와 매개변수 생성자

  • 생성자란?
    클래스의 변수들은 메서드에 의해 값이 변경될 수 도 있지만, 생성자를 사용하여 초기화시켜줄 수도 있다.
    생성자는 인스턴스를 만들고, 인스턴스의 변수들을 초기화시켜주는 역할을 한다

  • 기본 생성자?
    생성자를 정의하지 않았을 때 자바 컴파일러에서 자동으로 만들어주는 생성자를 디폴트 생성자라 고한다.
    디폴트 생성자는 매개변수가 없고 코드도 없다.

  • 매개변수 생성자? (aka. 사용자 정의 생성자)
    생성자는 주로 멤버 변수에 대한 값들을 매개변수로 받아서 인스턴스가 새로 생성될 때 멤버 변수를 초기화하는 역할을 한다.

생성자를 하나라도 정의하면 디폴트 생성자는 사용할 수 없다. 디폴트 생성자를 사용하고 싶다면 직접 추가해 주어야 한다.

https://miyakita.tistory.com/202

생성자 주입의 장점

  1. 필드 주입이 간단하긴 하지만 단일책임의 원칙 관점에서 볼 때 필드는 많은 빈들을 주입받을 수 있다.
  2. 필드에 final 키워드 사용이 가능해진다. 따라서 변경 불가능하게 사용할 수 있다.
  3. 순환참조 방지 (필드 주입이나 세터 주입의 경우 메소드 실행 시점에만 발생할 수 있지만, 생성자 주입을 하면 애플리케이션 실행 시점에 확인 가능하다.)
  4. DI컨테이너가 결합도가 낮기 때문에 테스트하기 좋다.

Getter, Setter를 통한 의존성 주입

@Component
public class MakeRandomString {

	private RandomGenerator random;
	
	public MakeRandomString() {}
	
	@Autowired
	public void setRandom(RandomGenerator random) {
		this.random = random;
	}
	
}

필드를 통한 의존성 주입

Service
public class BookService {

	/* BookDAO "타입"의 빈으로 생성된 객체를 이 프로퍼티에 자동으로 연결해준다. */
	@Autowired
	private BookDAO bookDAO;
	
	public BookService() {}
	
	public BookService(BookDAO bookDAO) {
		this.bookDAO = bookDAO;
	}
	
	// 도서 정보 전체 조회용 메소드
	public List<BookDTO> selectAllBooks() {
		return bookDAO.selectBookList();
	}
	
}

구조

pom.xml

POM(Project Object Model)은 하나의 프로젝트에서 사용하는 자바 버전, 라이브러리, 플러그인 구성을 통합하여 관리할 수 있게 각 설정 정보를 XML 문서화 한 것

🔼 펫팔 프로젝트의 pom.xml 파일 일부
spring-context와 spring-webmvc 등 필요한 프레임워크와 라이브러리들이 들어가 있음

메인 페이지

package com.nobanryeo.petpal.main.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/*")
public class MainController {
	
	@GetMapping(value={"/","main"})
	public String main() {
		
//		return "admin/main/manager_main"; // 관리자 페이지 첫페이지


		return "user/main/mainpage"; 


	}
	
	
}

@Controller

@RequestMapping("/*")

  • url 뒤에 아무것도 작성하지 않았을 때 매핑됨

@GetMapping(value={"/", "main"})

  • @GetMapping은 @RequestMapping (method = RequestMethod. GET) 의 줄임말
    cf. @PostMapping은 @RequestMapping (method = RequestMethod. POST) 의 줄임말

    @RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
    
    // 간소화 
    @GetMapping("/get/{id}")
  • "/"와 "main"로 다중 매핑해주었다.
    관리자 페이지의 첫페이지를 똑같은 main 함수에서 실행해주려다보니 "main"으로만 매핑한 것이 아니라 "/"로도 접속할 수 있도록 다중 매핑을 해주었던 것 같다. 나중에 리팩토링을 했다면 "main"만 남겨둬도 좋았을 것 같다.


servlet-context


🔼 ViewResolver 객체
어느 페이지로 갈 건지 지정
user/main/mainpage를 받으면 /WEB-INF/views/를 앞에 붙이고 .jsp를 뒤에 붙인 다음 포워딩


Service에 interface service와 class serviceImpl 구분해놓은 이유?

인터페이스 기반의 컴포넌트화 실현 - p80

인터페이스의 추상메소드를 통해 해당 메소드에 대한 강제성 부여 가능
추상메소드 오버라이딩해서 사용해야
연관된 클래스들 간 의존 관계 및 결합성 약하게 만들 수 있음!

ServiceImpl ---> Service (I) <--- DAO
~Impl : 인터페이스 상속받아서 재구성 한 클래스를 말함
Service와 DAO 간의 의존성이 낮아짐 (즉 결합도 낮아짐!)
예전에는 Service가 DAO를 직접 호출해서 하나 수정하면 양쪽 다 수정했어야 했음
그러나 양쪽이 인터페이스를 바라보게 하면 둘 간의 관계에서만 수정해주면 됨 => Service와 DAO 사이의 연관관계(결합도)가 약해짐!

프레임워크 - 각각이 모듈화되어 있음
다른 곳에서도 쓰고 싶으면 인터페이스만 들고 가서 모듈을 쉽게 띄워줄 수 있음

  1. 인터페이스로 메소드를 강제화한 후 구현하여 사용하면 강제성이 부여된다.
  2. 결합관계를 느슨하게 만들 수 있다.

Ref.

https://intrepidgeeks.com/tutorial/the-difference-between-request-mapping-and-getmapping-and-postmapping
https://kingofbackend.tistory.com/77

profile
그게, 할 수 있다고 믿어야 해

0개의 댓글