Spring Framework로 구현한 펫팔 프로젝트의 소스코드를 다시 보면서 Spring Framework에 대한 지식을 정리해보려고 한다. 정리해보려는 것은 다음과 같다!
다시 Spring Framework를 공부하면서 놓쳤던 개념과 "개념의 존재 이유"에 대해 제대로 파악하고 싶다.
파이팅 🍍🍍
Inversion of Control
프로그램을 구동하는데 필요한 객체에 대한 생성, 변경 등의 관리를 프로그램을 개발하는 사람이 아닌 프로그램을 구동하는 컨테이너에서 직접 관리하는 것
구동 시 필요한 객체의 생성부터 생명 주기까지 해당 객체에 대한 관리를 직접 수행
🔼 Spring IoC container
POJO : 우리가 만드는 순수한 자바 코드
Configuration Metadata : 모든 설정값, 스프링 컨테이너가 구현시킴
Spring Container : URL 매핑 등.. 객체의 생명주기 관리 (특정 시점에 얘를 동작시켜줌)
Application Context 인터페이스
여러 개의 인터페이스 덩어리
빈 객체를 등록해놓고 꺼내와서 씀
// 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로 어플리케이션의 설정값들 모아둔 것
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이라는 스프링에서 관리하는 객체들로 대체됨!
기본생성자를 통해서 객체 만들어주는 것과 같음
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을 등록
여러 개의 Bean을 스캔해서 가져오는 방식
Annotation 설정해놓은 걸 다 읽어들이는 것
IoC 컨테이너가 등록해놓은 Bean을 Spring Container는 알고 있어야 함
페이지가 로드 될 때, 즉 IoC 컨테이너로 Spring Container가 만들어질 때 등록되어 있는 annotation에 대한 걸 모두 가지고 있어야 함
-> 이것을 Component Scan으로 함
base-package로 설정된 경로 하위에 특정 어노테이션을 가지고 있는 클래스를 이용하여 bean으로 등록한다.
/* basePackages에 등록되지 않은 패키지는 스캔에서 제외하고, 등록된 패키지 내의 @Component 어노테이션을 탐색한다.
* 이 때 basePackage를 등록하지 않으면 현 설정클래스가 존재하는 패키지를 자동으로 basePackage로 설정한다.
* 문자열 배열 형태로 여러 basePackage를 등록할 수 있다.
*/
@Configuration
@ComponentScan(basePackages = "com.greedy.section01.javaconfig")
public class ContextConfiguration1 {
}
/* 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 {
}
@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(환경설정) 등을 인식한다.
(목적에 따른 분리 - 각각의 클래스별로 트랜잭션 처리 따로 해줘야 하는데 그렇지 않으면 처리해줄 게 많아졌음! + 어느 부분에서 오류 났는지 명확해짐!)
스프링 컨테이너가 스캐닝 기능을 이용하여 해당 클래스를 bean으로 등록할 수 있도록 어노테이션을 설정
value 속성을 이용하여 bean의 id를 설정할 수 있으며, value는 생략 가능
이름(id)를 설정하지 않으면 class의 앞글자를 소문자로 하여 bean으로 생성
@Component : 스프링에서 관리되는 객체임을 표시하기 위해 사용하는 가장 기본적인 어노테이션이다.
@Controller : Web MVC 코드에서 사용되는 어노테이션으로 @RequestMapping 어노테이션은 해당 어노테이션이 작성된 클래스 내에서만 사용 가능하다.
@Service : 비즈니스 로직이 작성된 클래스에 사용한다. 추가적인 기능은 없지만 추가적인 기능을 제공할 가능성이 있으니 계층을 명시할 때 사용한다.
@Repository : 플랫폼별 예외를 포착하여 PersistenceExceptionTransacttionPostProcesser가 DataAccessException으로 변환하여 다시 발생한다.
계층이 명확하지 않은 경우에는 @Component를 사용한다.
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);
}
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
@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(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("/*")
@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"만 남겨둬도 좋았을 것 같다.
🔼 ViewResolver 객체
어느 페이지로 갈 건지 지정
user/main/mainpage를 받으면 /WEB-INF/views/를 앞에 붙이고 .jsp를 뒤에 붙인 다음 포워딩
인터페이스 기반의 컴포넌트화 실현 - p80
인터페이스의 추상메소드를 통해 해당 메소드에 대한 강제성 부여 가능
추상메소드 오버라이딩해서 사용해야
연관된 클래스들 간 의존 관계 및 결합성 약하게 만들 수 있음!
ServiceImpl ---> Service (I) <--- DAO
~Impl : 인터페이스 상속받아서 재구성 한 클래스를 말함
Service와 DAO 간의 의존성이 낮아짐 (즉 결합도 낮아짐!)
예전에는 Service가 DAO를 직접 호출해서 하나 수정하면 양쪽 다 수정했어야 했음
그러나 양쪽이 인터페이스를 바라보게 하면 둘 간의 관계에서만 수정해주면 됨 => Service와 DAO 사이의 연관관계(결합도)가 약해짐!
프레임워크 - 각각이 모듈화되어 있음
다른 곳에서도 쓰고 싶으면 인터페이스만 들고 가서 모듈을 쉽게 띄워줄 수 있음
https://intrepidgeeks.com/tutorial/the-difference-between-request-mapping-and-getmapping-and-postmapping
https://kingofbackend.tistory.com/77