초기 조직은 애플리케이션을 물리 서버에서 실행했었다. 한 물리 서버에서 여러 애플리케이션의 리소스 한계를 정의할 방법이 없었기에, 리소스 할당의 문제가 발생했다. 예를 들어 물리 서버 하나에서 여러 애플리케이션을 실행하면, 리소스 전부를 차지하는 애플리케이션 인스턴스가 있을 수 있고, 결과적으로는 다른 애플리케이션의 성능이 저하될 수 있었다. 이에 대한 해결책은 서로 다른 여러 물리 서버에서 각 애플리케이션을 실행하는 것이 있다. 그러나 이는 리소스가 충분히 활용되지 않는다는 점에서 확장 가능하지 않았으므로, 물리 서버를 많이 유지하기 위해서 조직에게 많은 비용이 들었다.
Virtualized Deployment가상화를 사용하면 물리 서버에서 리소스를 보다 효율적으로 활용할 수 있으며, 쉽게 애플리케이션을 추가하거나 업데이트할 수 있고 하드웨어 비용을 절감할 수 있어 더 나은 확장성을 제공한다. 가상화를 통해 일련의 물리 리소스를 폐기 가능한(disposable) 가상 머신으로 구성된 클러스터로 만들 수 있다.
각 VM은 가상화된 하드웨어 상에서 자체 운영체제를 포함한 모든 구성 요소를 실행하는 하나의 완전한 머신이다.
그 해결책으로 가상화가 도입되었다. 이는 단일 물리 서버의 CPU에서 여러 가상 시스템 (VM)을 실행할 수 있게 한다. 가상화를 사용하면 VM간에 애플리케이션을 격리하고 애플리케이션의 정보를 다른 애플리케이션에서 자유롭게 액세스 할 수 없으므로, 일정 수준의 보안성을 제공할 수 있다.
Container Deployment
컨테이너는 VM과 유사하지만 격리 속성을 완화하여 애플리케이션 간에 운영체제(OS)를 공유한다. 그러므로 컨테이너는 가볍다고 여겨진다. VM과 마찬가지로 컨테이너에는 자체 파일 시스템, CPU 점유율, 메모리, 프로세스 공간 등이 있다. 기본 인프라와의 종속성을 끊었기 때문에, 클라우드나 OS 배포본에 모두 이식할 수 있다.
가상화
가상화 기술
종속 된 컴퓨터 리소스를 추상화하여 서버, 스토리지, 네트워크 등의 소프트웨어 IT 서비스를 생성하는 솔루션
하이퍼바이저 가상화
하드웨어 위에 가상화 전문 소프트웨어인 하이퍼바이저를 설치하고 가상 환경을 제어하는 것
호스트 가상화호스트 OS 상의 오버헤드가 크기 때문에 낮은 사양의 PC에서는 선호되지 않음
오버헤드: 가상화를 위해 필요한 추가 CPU 리소스, 디스크 용량, 메모리 사용량
가상 환경을 만들기 위해 운영 중인 하드웨어(호스트)에 가상화 소프트웨어를 설치해 게스트 OS를 운영하는 것
컨테이너 가상화호스트 OS의 자원을 컨테이너들끼리 공유하며 각각 필요한 자원을 할당 받아 실행 되기 때문에 오버헤드가 적음
하이퍼 바이저와 게스트 OS가 필요하지 않아 용량이 작고, 운영체제 부팅이 필요하지 않아 서비스 시작 시간도 짧음
호스트 OS 상에서 논리적으로 구역을 나눠 애플리케이션 동작을 위한 라이브러리와 애플리케이션 등을 그 안에 넣고 사용하는 것
Docker란?
애플리케이션을 개발, 배포, 실행하기 위한 플랫폼
컨테이너를 사용하여 애플리케이션 및 지원 구성 요소를 개발
리눅스 자체 기능을 사용하여 프로세스 단위의 격리 환경을 제공
배포 용량과 시간을 단축하고, 성능 손실을 최소화 시킴
Docker의 장점
도커 엔진 아래에는 물리적인 컴퓨터 호스트가 있으며 도커 엔진 위에 각각의 컨테이너가 개별적인 리소스를 할당 받아서 마치 하나의 단독 컴퓨터처럼 동작함
전통적인 배포 방식은 개발 후 컴퓨터에 OS를 설치하고 런타임 환경을 구성 한 뒤 배포 파일을 넣고 실행하는 것인데 이 과정을 한번에 처리할 수 있는 스크립트(이미지라고 부름)를 도커를 통해 손쉽게 만들 수 있음
이미지는 원격 저장소에서 도커 엔진을 사용해 내려 받을 수 있고 내려 받은 이미지로 어떤 호스트 PC건 동일하게 컨테이너 생성이 가능함
즉, 컴퓨터 초기 세팅이 없는 상태에서 물리적인 처리 없이 논리적으로 run 만 하면 순식간에 환경을 설정할 수 있음
확장 편리
→ 서버의 부하로 인해 분산이 필요하여 서버의 수를 늘려야 할 경우 컴퓨터를 더 사서 런타임 환경을 구축하고 배포 파일을 실행하는 처리 또는 해당 서버가 불필요해졌을 때 낭비되는 상황 없이 유연하게 컨테이너를 올렸다 내렸다 하면서 리소스를 조절하며 확장 할 수 있음
Docker Swarm, Kubernetes
오케스트레이션 툴
컨테이너화된 애플리케이션의 자동 디플로이, 스케일링 등을 제공하는 관리 시스템
→ 오토스케일링 : 리소스의 임계치를 설정하여 컨테이너 상태 및 리소스 메트릭 정보를 모니터링 해서 특정 이벤트에 대해 컨테이너를 확장 및 축소하는 기능 → 리소스 : CPU, 메모리, WAS의 Thread 등등
1번 권한을 가진 사람을 AUTHORITY_CODE 1을, 2번 권한을 가진 사람은 AUTHORITY_CODE 1, 2 코드를 모두 가지고 있다.
스프링 스타터 프로젝트로 새 프로젝트를 만들어 준다.
추가한 의존성은 다음과 같다.
프로젝트 전체 구조는 다음과 같다.
Application.yml
오라클DB를 정보를 기입하여 연결한다.
# server port config
server :
port : 8001
# datasource config
spring :
datasource :
driver-class-name : oracle.jdbc.driver.OracleDriver
url : jdbc:oracle:thin:@localhost:1521:xe
username :
password :
# mybatis config
mybatis :
mapper-locations : mappers/**/*.xml
테스트를 위한 간단한 메인과 로그인 화면을 구현한다.
main.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>main</title>
</head>
<body>
<h1 align="center">Spring Security Project에 오신 것을 환영합니다.</h1>
<div align="right">
<!-- isAuthenticated() : 인증(로그인) 되어 있는지 확인 -->
<th:block sec:authorize="isAuthenticated()">
<!--
principal : 로그인 된 User 객체의 정보를 담고 있다.
principal.username : 로그인 시 입력한 id 값
그 외의 정보가 필요할 경우 User 타입을 상속한 클래스를
만들어서 커스터마이징 할 수 있다.
-->
<h3><span sec:authentication="principal"></span>님 환영합니다</h3>
<h3><span sec:authentication="principal.username"></span>님 환영합니다</h3>
<h3><span sec:authentication="principal.name"></span>님 환영합니다</h3>
<button onclick="location.href='/member/logout'">로그아웃</button>
</th:block>
<!-- isAnonymous() : 인증(로그인) 되어 있지 않은 경우 -->
<th:block sec:authorize="isAnonymous()">
<h3>로그인이 필요한 서비스입니다.</h3>
<button onclick="location.href='/member/login'">로그인</button>
<button>회원가입</button>
</th:block>
</div>
<button onclick="location.href='/menu/list'">메뉴 보기</button>
<th:block sec:authorize="hasRole('MEMBER')">
<button onclick="location.href='/order'">주문하기</button>
</th:block>
<th:block sec:authorize="hasRole('ADMIN')">
<button onclick="location.href='/admin/dashboard'">관리자메뉴</button>
</th:block>
</body>
</html>
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login page</title>
</head>
<body>
<!--
spring security의 formlogin을 할 경우 username, password로 꺼내게 되어 있으므로
name 속성 설정에 유의한다.
"/member/login" 요청은 시큐리티에 설정해두었으므로 해당 요청을 가로채서
userDetailsService의 용도로 memberService를 사용하겠다고 전달 했기
때문에 loadUserbyUserName 메소드가 호출 되어
User 객체를 리턴하게 되며 비밀번호 확인(설정 된 passwordEncoder 사용)
후 인증 되면 세션에 저장하는 기능이 자동으로 동작한다.
-->
<h1 align="center">로그인 페이지</h1>
<div align="center">
<form action="/member/login" method="post">
<div>
<p style="color:red;">[[${errorMessage}]]</p>
</div>
<div>
<span>아이디 : </span>
<input type="text" name="username">
</div>
<div>
<span>비밀번호 : </span>
<input type="password" name="password">
</div>
<div>
<button>로그인</button>
</div>
</form>
</div>
</body>
</html>
config 폴더 내 클래스를 작성한다.
Chap04SecuritySessionLoginApplication
package com.greedy.security.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Chap04SecuritySessionLoginApplication {
public static void main(String[] args) {
SpringApplication.run(Chap04SecuritySessionLoginApplication.class, args);
}
}
SpringSecurityConfiguration
package com.greedy.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.greedy.security.handler.LoginFailHandler;
import com.greedy.security.member.model.service.MemberService;
/* 스프링 시큐리티 설정 활성화 + bean 등록 가능 */
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final MemberService memberService;
private final PasswordEncoder passwordEncoder;
@Autowired
public SpringSecurityConfiguration(MemberService memberService,
PasswordEncoder passwordEncoder) {
this.memberService = memberService;
this.passwordEncoder = passwordEncoder;
}
/* 암호화에 사용할 객체 BCryptPasswordEncoder bean 등록 - ContextConfiguration */
/* 정적 리소스는 권한이 없어도 요청 가능하게 무시할 경로를 작성한다 */
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/images/**");
}
/* HTTP 요청에 대한 설정 */
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() /* csrf는 기본적으로 활성화 되어 있으므로 비활성화 처리 */
/* 요청에 대한 권한 체크 */
.authorizeHttpRequests()
/* 요청 보안 수준의 세부적인 설정 */
/* "/menu/**" 요청은 인증(로그인) 되어야 함을 명시 */
.antMatchers("/menu/**").authenticated()
/* "/menu/**" 의 GET 요청은 member 에게 허용 */
.antMatchers(HttpMethod.GET, "/menu/**").hasRole("MEMBER")
/* "/menu/**" 의 POST 요청은 admin 에게 허용 */
/* hasRole 앞에 ROLE_가 자동으로 붙음 */
.antMatchers(HttpMethod.POST, "/menu/**").hasRole("ADMIN")
/* "/admin/**" 의 요청은 admin 에게 허용 */
.antMatchers("/admin/**").hasRole("ADMIN")
/* 그 외의 모든 요청은 허가함 - 인증(로그인) 되지 않은 사용자도 요청 가능 */
.anyRequest().permitAll()
.and()
/* 로그인 설정 */
.formLogin()
/* 로그인 페이지 설정 */
.loginPage("/member/login")
/* 성공 시 랜딩 페이지 설정 */
.successForwardUrl("/")
/* 로그인 실패 시의 핸들러 설정 */
.failureHandler(loginFailHandler())
.and()
/* 로그아웃 설정 */
.logout()
/* 로그아웃 주소 */
.logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
/* JSESSIONID 쿠키 삭제 */
.deleteCookies("JSESSIONID")
/* 세션 만료 */
.invalidateHttpSession(true)
/* 성공 시 랜딩 페이지 */
.logoutSuccessUrl("/")
.and()
/* 인증/인가 예외 처리 */
.exceptionHandling()
/* 인가 되지 않았을 때 - 권한이 없을 때 이동할 페이지 */
.accessDeniedPage("/common/denied");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/* 로그인, 로그아웃은 MemberController에 작성하지 않고
스프링 시큐리티 모듈을 통해 처리 */
/* 사용자 인증을 위해서 사용할 MemberService 등록,
사용하는 비밀번호 인코딩 방식 설정 */
auth.userDetailsService(memberService).passwordEncoder(passwordEncoder);
}
/* 로그인 실패 핸들러 bean 등록 */
@Bean
public LoginFailHandler loginFailHandler() {
return new LoginFailHandler();
}
}
MybatisConfiguration
package com.greedy.security.config;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan(basePackages = "com.greedy.security", annotationClass = Mapper.class)
public class MybatisConfiguration {
}
main 폴더 내 MainController를 작성한다.
MainController
package com.greedy.security.main.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MainController {
@GetMapping(value = {"/", "/main"})
public String main() {
return "main/main";
}
@PostMapping(value="/")
public String redirectMain() {
return "redirect:/";
}
}
회원에 관련돼 구현될 클래스를 작성한다.
MemberController
package com.greedy.security.member.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/member")
public class MemberController {
@GetMapping("/login")
public void loginForm() {}
@PostMapping("/login")
public void loginForm(@RequestParam(required=false) String errorMessage, Model model) {
model.addAttribute("errorMessage", errorMessage);
}
}
Spring AOP 란, 관점 지향 프로그래밍의 약자로 일반적으로 사용하는 클래스(Service, Dao 등) 에서 중복되는 공통 코드 부분(commit, rollback, log 처리)을 별도의 영역으로 분리해 내고, 코드가 실행 되기 전이나 이 후의 시점에 해당 코드를 붙여 넣음으로써 소스 코드의 중복을 줄이고, 필요할 때마다 가져다 쓸 수 있게 객체화하는 기술을 말한다.
Spring AOP 개요
아래 이미지와 같이 공통되는 부분을 따로 빼내어 필요한 시점에 해당 코드를추가해주는 기술을 AOP라고 말한다.
Spring AOP의 구조
공통되는 부분을 따로 빼내어 작성하는 클래스를 Advice라고 이야기 하며, 해당 시점을 Joinpoint, 그리고 그 시점에 공통 코드를 끼워 넣는 작업을 Weaving 이라고 말한다.
Aspect 란?
어스펙트(Aspect)는 부가기능을 정의한 코드인 어드바이스(Advice)와 어드바이스를 어디에 적용하지를 결정하는 포인트컷(PointCut)을 합친 개념이다.
AOP 개념을 적용하면 핵심기능 코드 사이에 끼어있는 부가기능을 독립적인 요소로 구분해 낼 수 있으며, 이렇게 구분된 부가기능 아스펙트는 런타임 시에 필요한 위치에 동적으로 참여하게 할 수 있다.
Spring AOP 의 핵심 용어
AOP 구조 정리
Spring AOP의 특징
1) Spring은 프록시(Proxy) 기반 AOP를 지원한다.
Spring은 대상 객체(Target Object)에 대한 프록시를 만들어 제공하며, 타겟을 감싸는 프록시는 서버 Runtime 시에 생성된다.
이렇게 생성된 프록시는 대상 객체를 호출 할 때 먼저 호출되어 어드바이스의 로직을 처리 후 대상 객체를 호출한다.
Proxy : 대상 객체를 직접 접근하지 못하게 '대리인'으로써 요청을 대신 받는 기술
2) Proxy는 대상 객체의 호출을 가로챈다(Intercept).
Proxy는 그 역할에 따라 타겟 객체에 대한 호출을 가로챈 다음 어드바이스의 부가기능 로직을 수행하고 난 후에 타겟의 핵심기능 로직을 호출 (전처리 어드바이스)
혹은 타겟의 핵심기능 로직 메서드를호출한 후에 부가기능(어드바이스)을 수행한다. (후처리 어드바이스)
3) Spring AOP는 메소드 조인 포인트만 지원한다.
Spring은 동적 프록시를 기반으로 AOP를 구현하기 때문에 메소드 조인 포인트만 지원한다.
즉, 핵심기능(대상 객체)의 메소드가 호출되는 런타임 시점에만 부가기능(어드바이스)을 적용할 수 있다.
하지만, AspectJ 같은 고급 AOP 프레임워크를 사용하면 객체의 생성, 필드값의 조회와 조작, static 메서드 호출 및 초기화 등의 다양한 작업에 부가기능을 적용할 수 있다.
XML 기반의 aop 네임스페이스를 통한 AOP 구현
부가기능을 제공하는 Advice 클래스를 작성한다.
XML 설정 파일에 <aop:config>를 이용해서 아스펙트를 설정한다. (즉, 어드바이스와 포인트컷을 설정함)
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) {
/* BeanFactory란?
* 스프링 컨테이너의 최상위 컨테이너이며, ApplicationContext와 함께
스프링 컨테이너라고 한다.
* Bean의 생성과 설정, 관리 등의 역할을 맡고 있다.
* */
/* MemberDTO(POJO) 클래스와 spring-context(Configuration) XML 설정 정보를 이용해서
* bean 등록, 생성 예제 확인
* */
/* ApplicationContext의 하위 구현체인 GenericXmlApplicationContext는
XML 설정 메타정보를 읽어와서
* BeanFactory를 동작 시킨다.
* GenericXmlApplicationContext는 설정 메타 정보가 담긴 XML 파일의 경로를
클래스패스 하위 경로 전부 기술해야 설정 파일을 읽어온다.
* */
ApplicationContext context
= new GenericXmlApplicationContext("com/greedy/section01/xmlconfig/spring-context.xml");
/* 1. bean의 id를 이용해 bean을 가져오는 방법
* id를 이용하는 경우 bean의 정확한 타입을 유추할 수 없기 때문에 Object 타입으로
반환하므로 다운캐스팅 필요
* */
//MemberDTO member = (MemberDTO)context.getBean("member");
/* 2. bean의 클래스 메타 정보를 전달하여 가져오는 방법
* 가져오려는 bean의 타입이 명확하기 때문에 형변환이 필요 없다.
* */
//MemberDTO member = context.getBean(MemberDTO.class);
/* 3. bean의 id와 클래스 메타 정보를 전달하여 가져오는 방법
* */
MemberDTO member = context.getBean("member", MemberDTO.class);
System.out.println(member);
}
}
ApplicationContext은 BeanFactory 를 확장한 여러 인터페이스를 통합해서 상속받은 인터페이스
인터페이스이다보니 그 자체로 실질적인 어떤 구현체를 생성할 수 없음
해서 ApplicationContext 안에있는 강제화된 기능들을 구현해놓은 구현체인 GenericXmlApplicationContext
GenericXmlApplicationContext는 XML 설정파일을 읽어서 구동이 되는 클래스
정의되어있지않기에 뜨는 화면
우선 Spring Context를 추가한다. 그 안에 ApplicationContext 과 GenericXmlApplicationContext 등이 정의되어있음
mvn 레파지토리에서 라이브러리를 다운받아옴으로서 추가 해 볼 것이다.
mvnrepository.com
release 된 버전 중 가장 사용 빈도가 높은 5.2.9 버전을 클릭한 후 .jar 파일을 다운 받는다.
* 유의할 점, Spring 모듈 끼리는 버전을 맞춰주어야 한다. 현재 5.2.9 버전을 사용한다면 앞으로 추가하는 모듈들 끼리의 버전은 5.2.9버전으로 맞춰 주어야 한다는 뜻이다. 그래야 충돌을 미연에 방지할 수 있다.
이후 프로젝트 내 lib라는 폴더를 만들어 다운 받은 파일을 옮겨 붙여 준다.
이렇게 해줄 시 물리적으론 jar 파일이 프로젝트 내 존재하는 것은 맞지만, 실질적인 이 프로젝트의 사용 라이브러리로 등록되어있는 것은 아니다.
프로젝트 우클릭 > Properties > Java Build Path 카테고리 > Library 탭 > Classpath 선택 > Add JARs... 선택
물리적으로 존재하는 폴더 내 .jar 파일을 선택 후 확인을 눌러준다.
추가된 모습
라이브러리 빌드 등록 후 두가지 기능이 정상적으로 import 가 가능해지는 것을 확인할 수 있다.
생성을 할 인스턴트 쪽에 설정 메타정보를 기재해 준다.
여기에 기재된 정보는 spring-context.xml 로 만든 bean configuration 파일이다.
IoC 컨테이너를 만들때 이러한 설정정보를 쓰라고 전달하는 의미이다.
그런데, 작성한 설정 정보에 에러줄이 발생한다. 왜일까? 이는 스프링의 구성모듈과 관련있다.
방금 Context를 추가를 했는데 다음으로 Context 내에서 Core 안에있는 어떤 기능을 쓰려고 한다.
xml 파일이 읽어 올때, 리소스를 처리할 수 있는 기능이 이 Core 안에 기재되어있기 때문에 사실상 Context 만으로는 동작하기 어렵다.
5.2.9 버전을 jar 파일을 동일하게 다운 받은 후 lib 물리 구조에 붙여넣기 한다.
추가이후 build path를 통해 라이브러리 등록을 해준다.
이후 사라진 에러를 확인할 수 있다.
이 설정정보를 전달받은 스프링 컨테이너가 생성될 수 있는지를 테스트 해보자.
콘솔창의 에러를 확인한다. 현재 springframework 아래 beans 라는 경로를 찾지못했음을 알 수 있다.
Beans 가 없다는 의미이다.
이런식으로 콘솔창을 통해 어떤 기능이 포함되지못했는지 하나씩 유추해가는 방식이 필요하다.
MVN 레파지 토리에서 Spring Beans를 가져온다.
버전을 맞추어 다운 받는 것에 유의한다.
앞서 했던 것과 마찬가지로 lib 에 다운받은 파일을 추가후 build path 에서 추가해준다.
추가 이후 다시 실행했더니 다음과같은 에러가 콘솔창에 떴다.
마찬가지로 MVN에서 commons logging을 가져오도록 한다.
1.2 버전을 다운받는다. 마찬가지로 빌드 패스까지 마무리 지어준다.
파싱처리에 필요한 Spring expression 이 없음을 확인할 수 있다.
마찬가지로 다운받은 후 빌드패스까지 해준다.
이와같이 라이브러리 추가를 완료한 후 실행해 주었을때 (이전 에러 출력값이 사라지고) 콘솔창에 아무것도 뜨지않음으로서 GenericXmlApplicationContext 의 객체가 성공적으로 생성되었음을 알 수 있다.
정리
- ApplicationContext의 하위 구현체인 GenericXmlApplicationContext는 XML 설정 메타정보를 읽어와서 BeanFactory를 동작 시킨다. - GenericXmlApplicationContext는 설정 메타 정보가 담긴 XML 파일의 경로를 클래스패스 하위 경로 전부 기술해야 설정 파일을 읽어온다.
여기까지 왔을 때, 이 설정정보가 잘 만들어졌음을 어떻게 알 수 있을까?
현재 spring-context.xml 안에는 bean이 있는데, 이 bean 이 잘 등록되어, 생성되었는지를 확인 해본다.
bean을 꺼내오는 첫번째) getBean 메소드
bean에는 고유 아이디가 있는데, 이때 "member"라는 아이디를 가진 bean을 리턴하도록 한다.
타입 미스매치 에러가 뜬다. 이유는 고유 id만으로 타입을 유추할 수 없기 때문이다. (타입=/=아이디)
유추할 수 없는 타입은 Object로 리턴이 되기 때문에, 원하는 타입으로 리턴값을 담기위해서는 다운캐스팅이 필요하다.
객체가 잘 등록되어서 생성되었다는 것을 콘솔창에서 확인 해 볼 수 있다.
우리는 다음의 과정을 거치면서 MemberDTO member = new MemberDTO(1, "user01"...) 과 같은 객체를 생성한 적이 없다.
그러나 콘솔창을 보면 생성되었음을 알게되는데, 이건 누가 해준걸까? 바로 ApplicationContext 이다.
spring-context 안에 MemberDTO를 다음과같이 등록한다는 정보를 작성해주었기 때문이다.
이로서 빈을 생성했고, 잘 읽어 올 수 있었음을 확인하였다.
spring-context.xml
Bean 가져오는 방법 세가지
1. bean의 id를 이용해 bean을 가져오는 방법 - id를 이용하는 경우 bean의 정확한 타입을 유추할 수 없기 때문에 Object 타입으로 반환하므로 다운캐스팅 필요
MemberDTO member = (MemberDTO)context.getBean("member");
2. bean의 클래스 메타 정보를 전달하여 가져오는 방법 - 가져오려는 bean의 타입이 명확하기 때문에 형변환이 필요 없다.
애초에 MemberDTO 타입의 bean을 달라는 의미이다.
즉, 반환타입이 Object 가 아니므로 다운캐스팅이 필요없다.
3. bean의 id와 클래스 메타 정보를 전달하여 가져오는 방법 MemberDTO member = context.getBean("member", MemberDTO.class); System.out.println(member);
만약, MemberDTO 타입의 bean이 여러개라면, 2번의 방법이 무의미하다.
이럴때 빈의 아이디와 클래스 정보를 둘 다 기입한다. 역시 클래스를 기입해주었으므로 다운캐스팅은 필요없다.
정리
1. 제어의 역전을 구현하기 위해 Bean Factory에 빈을 등록하여 사용한다. 2. GenericXmlApplicationContext는 설정 메타 정보가 XML 에 만들어져있는 환경이다.
XML에 설정 메타정보가 기입되었던것과 마찬가지로, java-config 파일에서도 똑같이 동작하는지 그 방법과 결과를 살펴보도록 한다.
테스트할 클래스를 만들어준다.
동일한 테스트를 위해 MemberDTO 파일을 복사해와 경로만 수정해준다.
이 DTO 파일이라는 오브젝트를 사용하기위해 설정 메타 정보라는 것이 필요하다.
아까는 그 정보를 XML에 파일에 작성했다면 이번엔 자바 클래스에 만들어 보도록 한다.
컨테이너를 만들때 전달할 설정정보를 정의할 클래스 정의
다음과같이 생성된 ContextConfiguration 파일에 @Configuration 이라는 어노테이션을 붙인다.
즉, 이 클래스가 설정 메타 정보를 가지고 있다는 의미를 가진 어노테이션을 추가한다.
컨테이너를 생성할 시 해당 어노테이션이 달린 클래스를 먼저 인식하여 컨테이너를 설정한다.
xml 파일에서 빈을 등록했듯이, 자바 클래스 파일에서도 빈을 등록할 수 있어야 한다.
여기서는 '메소드' 형식을 사용한다.
리턴타입이 MemberDTO, 리턴타입의 객체에 원하는 정보를 new 생성자를 통해 설정한다.
bean을 등록하기 위해서는 @Bean 어노테이션을 이용한다.
즉, 메소드 위에 'Bean'임을 알 수 있도록 @Bean 어노테이션을 붙여준다.
앞서 xml에서 빈의 정보를 id로 알았다. 여기서는 어떻게 해야 id의 역할을 할 수 있을까?
이름을 등록하기 위해 @Bean(name="member") 와 같이 사용한다.
만일 bean에대한 아이디를 지정하지 않으면 [메소드명]을 사용하게되니 유의한다.
다음과같이 작성이 되어야한다.
@Configuration
public class ContextConfiguration {
@Bean(name="member")
public MemberDTO getMember() {
return new MemberDTO(1, "user01", "pass01", "배산임수");
}
}
정리
- @Bean(name="myName") 혹은 @Bean("myName")을 이용하여 bean의 id를 설정할 수 있다. - 이 때 bean의 이름을 지정하지 않으면 메소드의 이름을 bean의 id로 자동 인식한다.
Application 에서실행하여 마찬가지로 빈이 잘 생성되고 등록되었는지 확인한다.
똑같이 스프링 컨테이너를 만들기 위해 ApplicationContext context 을 사용하는 것은 동일하다.
그러나 앞서 xml 파일로 테스트 했다면 이번엔 자바 클래스 파일을 통해서 테스트 하는 것이기 때문에 메타 정보를 연결하는 객체가 달라야 한다. 그러한 설정정보를 읽어왔을때 사용하는 구현체도 달라진다.
즉, GenericXmlApplicationContext 구현체가 아닌 AnnotationConfigApplicationContext 을 사용하게 된다.
여기에 방금작성한 설정정보가 담긴 ContextConfiguration.class 라는 클래스(타입)을 전달한다.
- AnnotationConfigApplicationContext라는 어노테이션 설정 정보를 읽어서 컨테이너 설정을 하는 구현체 이용한다. - 인자로 @Configuration 어노테이션이 달린 설정 클래스의 메타 정보를 전달하여 인스턴스를 생성한다.
Bean이 잘 등록됐는지 확인하기 위해 context를 통해 빈을 꺼내온다.
앞서 했던 빈을 꺼내오는 세가지 방법중 이름과 타입을 정확히 기입해서 꺼내오는 방법을 사용
위를 실행할시 콘솔창에 다음과같이 aop가 없음을 확인할 수 있다.
AnnotationConfigApplicationContext 의 기능을 이용하기 위해서 스프링의 aop라는 기능이 필요함을 알 수 있다.
앞서 해왔던 대로 MVN 레파지 토리 -> Spring AOP 검색 -> 5.2.9 버전 다운 로드 -> Build Path 등록
라이브러리가 잘 build path 등록되었다면 콘솔창에서 다음과같은 결과를 확인할 수 있다.
xml-config 에서 확인했던것과 마찬가지로 컨테이너에서 빈이 잘 생성되고 등록된것을 알 수 있다.
정리
- 제어의 역전, 빈 팩토리를 통해서 빈을 등록할 수 있다. - 빈 등록은 xml 을 통한 방식도 있고 자바 클래스를 통한 방식도 있다. - 각 방식에 따라서 실제 내부적인 구현체 역시 달라진다. - 그렇지만 구현체가 달라져도 다음 사실은 동일하다 : 설정메타정보를 읽어와서 생성이 된 컨테이너 안에는 -개발자가 직접적으로 객체를 생성하는 것이 아니라- 컨테이너 안에서 관리되는 Bean 객체로서 존재를 하게 된다.
Configuration Metadata를 작성하는 방법에 대해 서술된 부분을 보자
다음으로 Component Scan을 통해 Bean이 등록되는 모습을 살펴보자.
특정 어노테이션을 이용해 Bean으로 등록해줄 것을 표시할 수 있는데 바로 @Component 이다.
MemberDTO
public class MemberDTO {
private int sequence;
private String id;
private String pwd;
private String name;
public MemberDTO() {}
public MemberDTO(int sequence, String id, String pwd, String name) {
super();
this.sequence = sequence;
this.id = id;
this.pwd = pwd;
this.name = name;
}
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "MemberDTO [sequence="
+ sequence + ", id=" + id + ", pwd=" + pwd + ", name=" + name + "]";
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
public static void main(String[] args) {
/* AnnotationConfigApplicationContext라는 어노테이션 설정 정보를 읽어서
컨테이너 설정을 하는 구현체 이용한다.
* 인자로 @Configuration 어노테이션이 달린 설정 클래스의 메타 정보를
전달하여 인스턴스를 생성한다.
* */
ApplicationContext context
= new AnnotationConfigApplicationContext(ContextConfiguration.class);
MemberDTO member = context.getBean("member", MemberDTO.class);
System.out.println(member);
}
}
ContextConfiguration
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/* 이 클래스가 설정 메타 정보를 가지고 있다는 의미를 가진 어노테이션을 추가한다.
* 컨테이너를 생성할 시 해당 어노테이션이 달린 클래스를 먼저 인식하여 컨테이너를 설정한다.
* */
@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", "배산임수");
}
}
MemberDTO
public class MemberDTO {
private int sequence;
private String id;
private String pwd;
private String name;
public MemberDTO() {}
public MemberDTO(int sequence, String id, String pwd, String name) {
super();
this.sequence = sequence;
this.id = id;
this.pwd = pwd;
this.name = name;
}
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "MemberDTO [sequence=" + sequence + ",
id=" + id + ", pwd=" + pwd + ", name=" + name + "]";
}
}
ComponentScan이란? base-package로 설정 된 하위 경로에 특정 어노테이션을 가지고 있는 클래스를 이용하여 bean으로 등록한다.
@Component 어노테이션이 작성 된 클래스를 인식하여 bean으로 만들게된다.
@Component는 좀 더 상위의 개념이며, 특수 목적에 따라 세부 기능을 제공하는 @Controller, @Service, @Repository, @Configuration 등을 인식한다.
즉, @Component 어노테이션이 작성된 클래스의 빈을 스캔하여 그것을 빈으로서 등록해주는 것이다.
테스트를 위한 설정 : MemberDTO 를 그대로 가져와 패키지만 변경해준다.
다음으로 MemberDAO라는 이름의 인터페이스를 작성한다.
인터페이스는 추상메소드를 작성하는 공간이며, 구조적인 부분을 강제화 할 수 있는 기능이다.
여기서는 필요로하는 두가지 기능을 작성해본다. 1) 회원정보 조회 메소드 2) 회원정보 저장 메소드
인터페이스에서 작성한 내용을 강제화하기 위한 기능인 MemberDAOImpl 을 만들어준다.
인터페이스의 장점 1) 인터페이스를 이용하면 기능을 구현할 것을 강제화할 수 있다는 점과, 2) 기능에 변화가 있을때마다 해당 인터페이스를 사용하는 클래스와의 결합도를 느슨하게 해준다. (유지보수 용이)
- 인터페이스로 메소드를 강제화 한 후 구현하여 사용하면 강제성이 부여 된다. - 또한 결합 관계를 느슨하게 만들 수 있다.
db 연동을 하는 것이 아니므로 데이터를 다음과같이 적어준다.
매개변수로 전달받은 회원 번호를 map에서 조회 후 회원 정보를 리턴해주는 용도의 메소드
매개변수로 전달받은 회원 정보를 map에 추가하고 성공 실패 여부를 boolean으로 리턴하는 메소드
전달받은 멤버의 정보중에서 시퀀스에 해당하는 값을 키값으로, 나머지 객체값(newMember)은 VALUE 값으로 해서 MAP 에 넣겠다는 것.
MAP은 동일한 값은 중복저장하지 않으므로, 잘 저장되었는지는 MAP의 사이즈 (.size) 를통해서 알아보도록 한다.
사이즈가 늘어났다면 true 를, 그렇지않다면 false 를 반환할 것이다.
스프링 컨테이너가 스캐닝 기능을 이용하여 해당 클래스를 bean으로 등록할 수 있는 어노테이션을 설정한다.
value 속성을 이용하면 bean의 id를 설정할 수 있으며, value는 생략 가능하다.
이름(id)를 설정하지 않으면 Class의 앞 글자를 소문자로 하여 bean을 생성한다.
@Controller, @Service, @Repository와 동일한 기능을 가지지만 각 계층을 표현하는 어노테이션은 특정 용도에 맞는 부가적인 혜택이 있으니 게층별로 구분하여 사용하는 것이 좋다. (계층이 명확하지않다면 @Component를 사용해도 좋다.)
클래스 위에 @Component 어노테이션을 붙여준다. 그럼 나중에 이러한 어노테이션이 붙은 대상을 가지고 bean 등록할 수 있다.
이때, VALUE 속성을 통해 해당 컴포넌트의 이름을 지어줄 수 있다.
만약, VALUE 속성을 지정해 주지않았다면? 마찬가지로 메소드 명을 빈의 이름으로 자동 지정될 것이다.
memberDAOImpl > 앞글자를 소문자로 만들어 빈의 이름으로 등록했을 것이다.
다음으로 필요한 것은 설정파일이다.
설정파일임을 스프링 컨테이너에서 알 수 있도록 @Configuration 어노테이션을 붙여준다.
단, MemberDAOImpl 에 작성한 @Component 를 잘 스캔 해 빈으로 등록해주는지 확인하는 것이 이번 테스트의 목적이므로, 해당 설정파일에는 아무것도 적지않는다.
Application1 으로 돌아온다. 만들어야 하는 것은 <스프링 컨테이너>에 해당하는 것이다.
어노테이션 기반으로 스프링 파일을 읽어와서 스프링 컨테이너를 만들겠다는 의미이다.
전달한 값은 따로 빈 등록을 하지않은 빈 껍데기 설정파일이다.
여기서 기대하는 것: @Component 어노테이션이 스캔시 '나를 빈으로 등록해라' 라고 하는 어노테이션이기 때문에 빈으로 잘 등록이 되는지 확인
context 안에 담겨있는, 정의된 빈의 이름들을 배열로 가져온다.
해당 배열안에 어떤 빈들이 등록되어있는지는 반복문을 통해서 출력 해본다.
실행시 다음과같은 결과를 콘솔창에서 확인할 수 있다.
앞글자가 소문자로 바뀐 설정 파일 클래스는 확인할 수 있지만, 기대했던 클래스는 빈 등록이 되지않은 것 같다.
그렇다면 왜 등록이 되지않은 것일까?
Context Configuration 은 어떤 기준으로 스캔을 하고 있는걸까?
본인이 현재 중재하는 경로 기준, 경로가 지엽적이라면 (base package를 설정하지않았다면) 본인의 패키지를 기준으로 스캔을 한다. 즉, 스캔이 안된것이 문제이다.
그렇다면 base-package에 대한 설정을 추가를 해주도록 한다.
다시 설정파일로 돌아와 다음과같은 어노테이션을 추가로 붙여준다. -> @ComponentScan
basePackages에 등록되지 않은 패키지는 스캔에서 제외하고, 등록된 패키지 내의 @Component 어노테이션을 탐색한다.
이 때 basePackage를 등록하지 않으면 현 설정 클래스가 존재하는 패키지를 자동 basePackage로 설정한다.
위와같은 어노테이션을 붙여줌으로서, 명시적으로 탐색할 기준이 되는 베이스 패키지를 설정해 주게 되었다.
이제 config 폴더내 설정파일만 탐색하는 것이 아니라, javaconfig 하위의 모든 어노테이션을 탐색할 수 있게 되었다.
베이스 패키지 설정 후 다음과 같이 MemberDAO 가 빈으로 등록되었음을 확인할 수 있다.
빈등록이 잘 되었다면 좀 전에 작성한 메소드를 이용해보는 것도 가능하다.
1번 멤버 조회와 3번 멤버 인서트, 3번 멤버 조회를 차례로 작성한다.
콘솔창에 출력된 모습
ComponentScan
스프링 컨테이너는 빈들의 생명주기를 관리하며, 그러한 설정 정보를 설정하는 방식을 지금까지 살펴보았다.
ComponentScan 어노테이션에 basePackages 외에 더 추가할 수 있는 방법들을 알아보고자 한다.
테스트를 위해 별도의 경로를 작성해준다. Application2 라는 이름의 javaconfig 작성
테스트를 위한 ApplicationContext 를 생성한다.
앞선 테스트와같이 설정 정보를 저장하기 위한 Context Configuration를 전달한 뒤 에러밑줄을 선택해 만들어 준다.
만들어진 클래스에는 이것이 [설정정보]임을 나타내는 어노테이션 @Configuration 을 달아준다.
또한 basePackages는 다음과같이 작성한다.
추가할 수 있는 기능은 다음과같다. 베이스 패키지를 하나이상 설정할 수 있다는 것
어노테이션에 추가하는 것은 문법적으로 [ 콤마, ] 로 나타낸다.
만약 단일 값인 경우엔 패키지명을 포함한 클래스이름을 나열하면 되지만, 하나 이상의 베이스 패키지인 경우 {} 중괄호를 이용하여 나열한다. (중괄호=나열)
베이스 패키지를 설정하면 그 하위의 파일들은 전부다 스캔이 된다. 그러나 관련없는 클래스까지 스캔하는 것은 어떤 관점에서는 낭비이므로, 임의의 타입은 스캐닝에서 제외시킬 수 있다.
excludeFilter로 스캐닝에서 제외할 타입을 기술하면 해당 타입은 스캐닝에서 제외한다.
1. 타입으로 설정
ASSIGNABLE_TYPE이란 것은 타입을 지정한다는 것이다.
스캐닝에서 제외할 클래스를 다음과같이 작성해주면 된다.
단, 같은 패키지 하위에 같은 베이스 패키지를 기술하면 오류의 원인이 될 수 있으므로 사전에 중복된 베이스 패키지는 주석 처리한다.
@Configuration
@ComponentScan(basePackages="com.greedy.section01.javaconfig",
excludeFilters= {
@ComponentScan.Filter(
type=FilterType.ASSIGNABLE_TYPE,
classes= {MemberDAO.class}
})
public class ContextConfiguration2 {
}
앞선 설정파일의 빈 등록 코드를 그대로 가져와 실행해 주면 콘솔창에 다음과같이 뜬다.
MemberDAO.class를 찾을 수 없다는 의미이므로, 컴포넌트 스캔에 잘 제외되었음을 알 수 있다.
2. 어노테이션 종류로 설정
Component 어노테이션이 붙어있는 클래스를 스캔하지않겠다는 의미
@Configuration
@ComponentScan(basePackages="com.greedy.section01.javaconfig",
excludeFilters= {
@ComponentScan.Filter(
type=FilterType.ANNOTATION,
classes= {org.springframework.stereotype.Component.class}
})
public class ContextConfiguration2 {
}
3. 표현식으로 설정
REgular Expression 표현식으로 타입을 설정한 후, section01 하위의 모든 클래스를 제외하겠다는 의미
@Configuration
@ComponentScan(basePackages="com.greedy.section01.javaconfig",
excludeFilters= {
@ComponentScan.Filter(
type=FilterType.REGEX,
pattern= {"com.greedy.section01.*"}
public class ContextConfiguration2 {
}
정리
- 컴포넌트 스캔이라는 기술을 사용할 때, base package 하위의 모든 클래스를 스캔하는 것이 시간이 소요될 수 있으니 이 스캐닝과 관련이 없는 대상은 제외해주는 옵션을 사용할 수 있다. : [ , excludeFilters = { } ] - 그 옵션을 사용하는 방법은 다양한데 세가지만 살펴보았다 : 1. 타입 2. 어노테이션 3. 표현식으로 설정
또 다른 테스트를 위해 같은 경로 내에 ContextConfiguration3 을 만들어준다.
설정 메타 정보 클래스를 만드는 것이 용도이다.
1) basePackages의 기본 설정 경로를 지정하고 2) useDefaultFilters를 false로 하면 모든 어노테이션을 스캔하지 않는다.
이 때 스캔할 대상 클래스만 따로 지정할 수 도 있다.
excludeFilters 가 제외하는 방식이라면, includeFilters 는 포함하는 방식이다.
exclude 필터 설정하는 방식과 동일하다
useDefaultFilters=false : 기본적으로 필터링 하지 않음을 의미
확인 테스트를 위한 Application3을 만들고 코드는 (1,2) 그대로 유지한다.
MemberDAO 만 빈으로 생성되고 등록되었음을 확인할 수 있다.
ContextConfiguration 을 XML 형태로 테스트
javaconfig / xmlconfig 호환해서 테스트 해보기 위함
다음 클래스를 작성한다.
앞서 작성한 기능을 그대로 사용하여 테스트 해보기 위해 section01 에서 위 코드르를 그대로 가져온다.
복사 붙여넣기 후 패키지명 변경
xml 설정 정보를 만들기위해 Spring 컨테이너와 그 xml 을 읽어올 수 있는 GenericXmlApplicationContext를 생성한다.
설정 메타정보 경로 작성
경로를 작성한 메타 정보 파일은 실제 존재하지 않으므로 다음과같은 단계를 통해 만들어주도록 한다.
해당 xml 파일에 설정하고 싶은 정보는 다음과같다.
@Component 혹은 @Repository 등이 기재된 클래스의 [빈스캐닝]을 해올 것
자바 config 에서 어노테이션을 붙여 작성했다면, xml에서는 태그를 이용해 다음과같이 작성한다.
컴파일 에러 줄이 뜨는 이유 : Context 의 sckima 스키마가 정의되어있지 않기 때문
에러 없애는 법은 다음과 같다.
하단의 Namespace 클릭 > beans 와 context 체크 선택 > 다시 Source 로 돌아와 에러가 사라진 것을 확인
스키마 정의 후 이전엔 존재하지 않았던 스키마정의들을 확인할 수 있다.
context 스키마가 있어야 정의할 수 잇으므로 하단의 namespaces 탭에서 context 스키마를 추가하고 작성할 것
이러한 xml 에서의 설정은 하단의 자바 클래스에서 작성한 어노테이션과 의미적으로 다르지않다.
실행하면 다음과같은 콘솔창을 확인할 수 있다.
xml 설정파일의 빈 스캐닝 하기위한 설정에 따라서 xmlconfig 하위의 클래스들을 스캐닝한 후,
MemberDAO 에 작성된 어노테이션을 읽어 들여서 ApplicationContext context = ... 가 빈 등록후 생성을 한다.
앞서 했던 것과 마찬가지로, 자바 클래스가 아닌 xml config 파일에서 includeFilters 와 excludeFilters 를 어떻게 사용하는지 살펴본다.
1번 파일을 그대로 붙여넣기하여 2번 파일을 생성한다.
context: 뒤에 올 알맞은 엘리먼트로 다음과같은 추천이 뜬다.
exclude-filter 를 정의하기위해 다음과같이 작성한다.
type = 타입이 무엇인지 / expression = "빈 스캐팅에서 제외할 경로"
위 xml 설정과 자바 config 에서 코드로 작성했던 내용을 비교대조 해볼 수 있다. 의미적으론 일치한다.
필터링에서 빠지는지 확인할 어플리 케이션 클래스 작성
exclude 필터를 넣어 빈 스캐닝에서 제외된 것을 확인할 수 있다.
ContextConfiguration1
package com.greedy.section01.javaconfig.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/* basePackages에 등록되지 않은 패키지는 스캔에서 제외하고, 등록된 패키지 내의
@Component 어노테이션을 탐색한다.
* 이 때 basePackage를 등록하지 않으면 현 설정 클래스가 존재하는 패키지를 자동
basePackage로 설정한다. */
//@Configuration
//@ComponentScan(basePackages="com.greedy.section01.javaconfig")
public class ContextConfiguration1 {
}
ContextConfiguration2
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import com.greedy.section01.javaconfig.MemberDAO;
/* excludeFilter로 스캐닝에서 제외할 타입을 기술하면 해당 타입은 스캐닝에서 제외한다. */
//@Configuration
//@ComponentScan(basePackages="com.greedy.section01.javaconfig",
// excludeFilters= {
// @ComponentScan.Filter(
// /* 1. 타입으로 설정 */
// //type=FilterType.ASSIGNABLE_TYPE,
// //classes= {MemberDAO.class}
// /* 2. 어노테이션 종류로 설정 */
// //type=FilterType.ANNOTATION,
// //classes= {org.springframework.stereotype.Component.class}
// /* 3. 표현식으로 설정 */
// //type=FilterType.REGEX,
// //pattern= {"com.greedy.section01.*"}
// )
// })
public class ContextConfiguration2 {
}
ContextConfiguration3
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import com.greedy.section01.javaconfig.MemberDAO;
/* basePackages의 기본 설정 경로를 지정하고 useDefaultFilters를 false로
하면 모든 어노테이션을 스캔하지 않는다.
* 이 때 스캔할 대상 클래스만 따로 지정할 수 도 있다.
* */
@Configuration
@ComponentScan(basePackages="com.greedy.section01.javaconfig",
useDefaultFilters=false,
includeFilters= { @ComponentScan.Filter(
/* exclude 필터 설정하는 방식과 동일하다 */
type=FilterType.ASSIGNABLE_TYPE,
classes= {MemberDAO.class}
)})
public class ContextConfiguration3 {
}
Application1
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.greedy.section01.javaconfig.config.ContextConfiguration1;
public class Application1 {
public static void main(String[] args) {
/* ComponentScan 기능을 이용한 bean 등록 설정
* ComponentScan이란?
* base-package로 설정 된 하위 경로에 특정 어노테이션을 가지고 있는
클래스를 이용하여 bean으로 등록한다.
* @Component 어노테이션이 작성 된 클래스를 인식하여 bean으로 만들게 되며
* 특수 목적에 따라 세부 기능을 제공하는
@Controller, @Service, @Repository, @Configuration 등을 인식한다.
* */
ApplicationContext context
= new AnnotationConfigApplicationContext(ContextConfiguration1.class);
String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
System.out.println("beanName : " + beanName);
}
MemberDAO memberDAO = context.getBean(MemberDAO.class);
System.out.println(memberDAO.selectMember(1));
System.out.println(memberDAO.insertMember(new MemberDTO(
3, "user03", "pass03", "새로운멤버" )));
System.out.println(memberDAO.selectMember(3));
}
}
public interface MemberDAO {
/* 회원 번호로 회원 정보를 조회하는 메소드 */
MemberDTO selectMember(int sequence);
/* 회원 정보를 저장하고 결과를 리턴하는 메소드 */
boolean insertMember(MemberDTO newMember);
}
MemberDAOImpl implements MemberDAO
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Repository;
/* 인터페이스로 메소드를 강제화 한 후 구현하여 사용하면 강제성이 부여 된다.
* 또한 결합 관계를 느슨하게 만들 수 있다.
* */
/* 스프링 컨테이너가 스캐닝 기능을 이용하여 해당 클래스를 bean으로 등록할
수 있는 어노테이션을 설정한다.
* value 속성을 이용하면 bean의 id를 설정할 수 있으며, value는 생략 가능하다.
* 이름(id)를 설정하지 않으면 Class의 앞 글자를 소문자로 하여 bean을 생성한다.
* @Controller, @Service, @Repository와 동일한 기능을 가지지만 각 계층을
표현하는 어노테이션은
* 특정 용도에 맞는 부가적인 혜택이 있으니 게층별로 구분하여 사용하는 것이 좋다.
* */
//@Component(value="memberDAO")
@Repository(value="memberDAO")
public class MemberDAOImpl implements MemberDAO{
private final Map<Integer, MemberDTO> memberMap;
public MemberDAOImpl() {
memberMap = new HashMap<>();
memberMap.put(1, new MemberDTO(1, "user01", "pass01", "홍길동"));
memberMap.put(2, new MemberDTO(2, "user02", "pass02", "유관순"));
}
/* 매개변수로 전달받은 회원 번호를 map에서 조회 후 회원 정보를
리턴해주는 용도의 메소드 */
@Override
public MemberDTO selectMember(int sequence) {
return memberMap.get(sequence);
}
/* 매개변수로 전달받은 회원 정보를 map에 추가하고 성공 실패 여부를
boolean으로 리턴하는 메소드 */
@Override
public boolean insertMember(MemberDTO newMember) {
int before = memberMap.size();
memberMap.put(newMember.getSequence(), newMember);
int after = memberMap.size();
return (after > before) ? true : false;
}
}
MemberDTO
public class MemberDTO {
private int sequence;
private String id;
private String pwd;
private String name;
public MemberDTO() {}
public MemberDTO(int sequence, String id, String pwd, String name) {
super();
this.sequence = sequence;
this.id = id;
this.pwd = pwd;
this.name = name;
}
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "MemberDTO [sequence="
+ sequence + ", id=" + id + ", pwd=" + pwd + ", name=" + name + "]";
}
}
chap02-component-scan : section02
spring-context1.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 스키마가 있어야 정의할 수 잇으므로 하단의 namespaces 탭에서
context 스키마를 추가하고 작성할 것 -->
<context:component-scan base-package="com.greedy.section02.xmlconfig"/>
</beans>
spring-context2.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 스키마가 있어야 정의할 수 잇으므로 하단의 namespaces 탭에서
context 스키마를 추가하고 작성할 것 -->
<context:component-scan base-package="com.greedy.section02.xmlconfig">
<context:exclude-filter type="assignable"
expression="com.greedy.section02.xmlconfig.MemberDAO"/>
</context:component-scan>
</beans>
public interface MemberDAO {
/* 회원 번호로 회원 정보를 조회하는 메소드 */
MemberDTO selectMember(int sequence);
/* 회원 정보를 저장하고 결과를 리턴하는 메소드 */
boolean insertMember(MemberDTO newMember);
}
MemberDAOImpl implements MemberDAO
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Repository;
/* 인터페이스로 메소드를 강제화 한 후 구현하여 사용하면 강제성이 부여 된다.
* 또한 결합 관계를 느슨하게 만들 수 있다.
* */
/* 스프링 컨테이너가 스캐닝 기능을 이용하여 해당 클래스를 bean으로 등록할 수 있는
어노테이션을 설정한다.
* value 속성을 이용하면 bean의 id를 설정할 수 있으며, value는 생략 가능하다.
* 이름(id)를 설정하지 않으면 Class의 앞 글자를 소문자로 하여 bean을 생성한다.
* @Controller, @Service, @Repository와 동일한 기능을 가지지만 각 계층을
표현하는 어노테이션은
* 특정 용도에 맞는 부가적인 혜택이 있으니 게층별로 구분하여 사용하는 것이 좋다.
* */
//@Component(value="memberDAO")
@Repository(value="memberDAO")
public class MemberDAOImpl implements MemberDAO{
private final Map<Integer, MemberDTO> memberMap;
public MemberDAOImpl() {
memberMap = new HashMap<>();
memberMap.put(1, new MemberDTO(1, "user01", "pass01", "홍길동"));
memberMap.put(2, new MemberDTO(2, "user02", "pass02", "유관순"));
}
/* 매개변수로 전달받은 회원 번호를 map에서 조회 후 회원 정보를
리턴해주는 용도의 메소드 */
@Override
public MemberDTO selectMember(int sequence) {
return memberMap.get(sequence);
}
/* 매개변수로 전달받은 회원 정보를 map에 추가하고 성공 실패 여부를
boolean으로 리턴하는 메소드 */
@Override
public boolean insertMember(MemberDTO newMember) {
int before = memberMap.size();
memberMap.put(newMember.getSequence(), newMember);
int after = memberMap.size();
return (after > before) ? true : false;
}
}
MemberDTO
public class MemberDTO {
private int sequence;
private String id;
private String pwd;
private String name;
public MemberDTO() {}
public MemberDTO(int sequence, String id, String pwd, String name) {
super();
this.sequence = sequence;
this.id = id;
this.pwd = pwd;
this.name = name;
}
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "MemberDTO [sequence="
+ sequence + ", id=" + id + ", pwd=" + pwd + ", name=" + name + "]";
}
}