@Configuration // 빈을 등록할 때 사용
@ComponentScan(basePackageClass = Application.class)
pubic class Config {
@Bean // Singleton으로 생성됨
public ...() { }
@Bean
public ...() { }
}
@Component // 직접 생성한 클래스를 빈으로 등록
@Scope("prototype") // 스코프 정의
public class BeanA {
private BeanB beanB;
@Autowired // 같은 타입의 빈을 자동 주입
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
@Component
public class BeanB { }
같은 타입의 빈을 자동 주입한다.
@Qualifier
에 지정된 이름과 같은 이름의 빈을 찾아 자동 주입한다.
@Autowired
@Qualifier("BeanC")
private BeanC bean1;
필드명과 이름이 같은 빈을 찾아 자동 주입한다.
@Resource(name="beanD")
private BeanA beanD;
@Value
를 사용해 주입할 값을 지정할 수 있다.properties
값을 불러올 수 있다.name=spring
작성되어있다면,@Value("${name}")
private String name;
스프링에서 지원하는 표현식이다.
#{표현식}
@Configuration
public class AppConfig {
@Bean
public void hello() {
String name = "Spring";
}
@Bean
public void names() {
@Value("#{hello.name}")
String helloname;
}
}
스프링 컨테이너는 초기화 과정에서 몇 가지 빈을 기본적으로 등록해준다.
ApplicationContext, BeanFactory, ResourceLoader, ApplicationEventPublisher, systemProperties, systemEnvironment
기본적으로 스프링의 빈은 싱글톤으로 만들어진다. 즉, 애플리케이션 컨텍스트마다 빈 오브젝트는 한 개만 만들어진다.
프로토타입 스코프를 갖는 Bean은 요청이 있을 때마다 컨테이너가 생성, 초기화, DI까지 해주지만 일단 Bean을 제공하고 나면 컨테이너는 더 이상 Bean 오브젝트를 관리하지 않는다. 따라서 프로토타입 Bean 오브젝트는 DL이나 DI를 통해 컨테이너 밖으로 전달된 후에는 오브젝트를 가져가 코드나 DI로 주입받은 다른 Bean이 오브젝트를 관리하게 된다.
DL(Dependency Lookup) : 의존성 검색
Bean에 접근하기 위해 컨테이너가 제공하는 API를 이용하여 Bean을 찾는 것을 의미한다.
@Component
@Scope("prototype")
public class ServiceRequest { ... }
DataSource
: DB 연결 풀 기능을 지원한다.
DB Connection pool
WAS가 실행되면서 connection 객체를 미리 pool에 저장해두었다가 HTTP 요청에 따라 pool에서 connection 객체를 가져다 쓰고 반환한다.이와 같은 방식으로 물리적인 데이터베이스 연결 부하를 줄이며, pool에 미리 connection이 생성되어 있어서 요청 시 생성 시간이 소비되지 않는다.
application.properties
파일에서 예약된 키에 프로퍼티 설정을 하면 자동으로 jpa 설정과 DataSource
빈을 생성해준다.
커넥션과 sql을 일일히 작성해야 된다.
sql Mapper로 인해 개발 코드는 줄었다.
JPA가 적절한 sql을 생성해준다.
EntityManager를 사용해 JPA 코드를 작성한다. EntityManager를 직접 주입받으려면 @PersistenceContext
애노테이션을 사용한다.
Java Persistent API 약자로 자바의 ORM(Object Relational Mapping) 기술 표준으로 인터페이스의 모임이다.
ORM(Object Relational Mapping)
객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 것을 뜻한다.
@Entity
public class Memeber {
@Id
int id;
@Column(length=100)
String name;
@Column(nullable=false)
double point;
}
JPA가 관리하는 엔티티 오브젝트로 지정한다. 다른 설정이 없으면 매핑되는 테이블 이름은 클래스 이름을 따른다.
DB의 기본키에 대응되는 필드이다.
자동 생성키
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
int id;
@SequenceGenerator
작성이 필요하다.@Column(name="이름")
속성 name에 해당하는 이름이나 name 속성이 없을 경우, 필드 이름이 DB 컬럼에 매핑된다.
자바 기반의 ORM 프레임워크 오픈소스이다.
스프링이 직접 제공하는 서블릿 기반의 MVC 프레임워크이다. 프론트 컨트롤러 역할을 하는 DispatcherServlet
을 핵심 엔진으로 사용한다.
(2) DispatcherServlet에서 핸들러 매핑을 통해 컨트롤러로 HTTP 요청 위임한다.
(3) 모델을 생성하고 정보를 넣어준다.
(4) 템플릿 파일 이름을 리턴해 준다.
(5) 뷰 오브젝트에게 모델을 전달해준다.
(6) 클라이언트에게 돌려줄 최종 결과물을 생성한다.
@RequestMapping("/hello")
@RequestMapping("/view.*")
@RequestMapping("/admin/**/user")
@RequestMapping("/hello", "/hi")
패스 변수(path variable)를 사용하여 컨트롤러 메소드에서 파라미터로 값을 전달 받을 수 있다.
@RequestMapping("/user/{userid}")
public String index(@PathVariable String userid) {
return userid + " 님이 접속하였습니다." ;
}
@RequestMapping("/user/add", method=RequestMethod.GET)
/user/edit?type=admin
이라는 URL로 요청 받을 시
@RequestMapping("/user/edit", params="type=admin")
type이라는 파라미터가 아예 존재하지 않는 경우 매핑
@RequestMapping("/user/edit", params="!type")
@RequestMapping("/view", headers="content-type=text/*")
= @RequestMapping("/view", consumes="text/*")
@ResponseBody
가 없다면 스트링 타입의 리턴 값은 뷰 이름으로 인식된다. 하지만 @ResponseBody
는 리턴 값을 스트링 타입을 지원하는 메시지 컨버터를 통해 HTTP 응답 메시지 본문으로 전환된다.
@RequestMapping("hello")
@ResponseBody
public String hello() {
return "<html><body>Hello Spring</body></html>";
}
이 어노테이션이 붙은 파라미터에는 HTTP 요청 본문(Body) 부분이 그대로 전달된다. XML이나 JSON 기반의 메시지를 사용하는 요청의 경우에 사용된다.
public void message(@RequestBody String body) { ... }
위와 같다면 요청 메세지의 본문이 모두 스트링으로 변환돼서 body
변수에 전달된다.
보통 @ResponseBody
와 함께 사용된다.
Controller에서 생성된 데이터를 View 로 전달할 때 사용하는 객체이다.
model.addAtrribute("키", "값");
@RequestMapping("/hello")
public String hello(@RequestParam String name, Model model) {
model.addAttribute("name", name);
return "hello";
}
HTTP 요청 파라미터를 받기 위해 사용한다. 1:1로 매칭된다.
예를 들어 사용자가 /?name=sik
로 요청한다면, 매개변수 name에 sik
이 매핑된다.
@RequestParam
과 비슷하나 파라미터를 DTO/VO로 받을 경우 사용한다.
스프링은 JSP 뷰에서 모델 맵에 담긴 오브젝트를 JSP EL을 통해 접근할 수 있다.
컨트롤러 메소드에서 다음과 같이 모델 오브젝트를 추가했다고 하면,
model.addAttribute("name", "Spring");
JSP 뷰에서 다음과 같이 EL을 사용하여 모델 오브젝트의 값을 출력할 수 있다. <div>이름 : ${name}</div>
핸들러 메소드에서 리다이렉트를 사용하려면 Redirectview
나 redirect:
접두어가 붙은 뷰 이름을 리턴하면 된다.
RedirectAttributes
는 리다이렉트 뷰에서 필요하지 않은 Model
정보를 제외하고 제공해 준다.
public String saveForm(@ModelAttribute("user") User user, RedirectAttributes ra) {
...
ra.addFlashAttribute("message", "저장됐습니다");
ra.addAttribute("status", status);
return "redirect:/경로";
}
flash attributes
RPG(POST/REDIRECT/GET) 패턴을 보완하기 위해 등장하였다.
flash attributes는 수명이 짧다. 따라서 리다이렉션 직전에 기본 저장소에 임시로 저장된다. 리다이렉션 후 후속 요청에 계속 사용할 수 있다가 사라진다.이렇게 저장된 플래시 애트리뷰트는 다음 GET 요청에서 자동으로 모델에 추가된다.
<div class="flash_message"> ${message} </div>
@Aspect
클래스에는 포인트컷과 어드바이스를 정의할 수 있다.
클래스를 aspect
로 사용하려면 먼저 빈으로 등록해야 한다.
@Component
@Aspect
public class SimpleMonitoringAspect {
@Around("execution(* hello(..))")
private void all() { }
}
pointcut과 advice에 대한 자세한 설명은 이전에 작성한
Spring & Spring Boot 글의 3. AOP 참조
어드바이스는 다섯 가지 종류가 있다.
@Before, @After, @AfterReturning, @AfterThrowing, @Around
@Around("myPointcut()")
public Object doNothing(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed();
return ret;
}
ProceedingJoinPoint
의 proceed()
는 타깃 오브젝트의 메소드를 호출하고 그 결과값을 받을 수 있다.
@AfterReturning(pointcut="myPointcut()", returning="ret")
public void logReturnValue(Object ret) { ... }
타깃 오브젝트의 메소드가 정상 종료된 후에 호출되기 때문에 메소드의 리턴 값을 참조할 수 있다. 어노테이션의 returning
엘리먼트를 이용해서 리턴 값을 담을 파라미터 이름을 지정한다.
@AfterThrowing(pointcut="daoLayer()", throwing="ex")
public void logDAException(DataAccessException ex) { ... }
throwing
엘리먼트를 이용해서 예외를 전달받을 메소드 파라미터 이름을 지정할 수 있다. 파라미터 타입이 발생한 예외와 일치할 경우만 어드바이스가 호출된다.
return 타입은 반드시 void로 한다.
@Pointcut("execution(* *hello*(..))")
private void hello() {}
()
까지 포함해서 포인트컷 이름으로 사용된다.
@Around("execution(* hello.hellospring..*(..)) && hello()")
JUnit : 자바 테스트 프레임워크
스프링은 테스트가 사용하는 컨텍스트를 캐싱해서 여러 테스트에서 하나의 컨텍스트를 공유할 수 있는 방법을 제공한다.
@Test
가 붙은 모든 메소드가 각각 하나의 독립적인 테스트가 된다.
class BeanScannerTest {
@Test
@DisplayName("빈 등록 확인")
void beanFind() { ... }
}
컨텍스트의 빈 오브젝트를 조작하고 수정하는 테스트일 경우 사용한다. @DirtiesContext
어노테이션이 붙은 테스트가 수행되고 나면 스프링은 현 테스트 컨텍스트를 강제로 제거한다.
@Test
와 @Transactional
어노테이션을 함께 사용했을 경우 테스트가 끝나면 롤백된다.
롤백하고 싶지 않다면 @Rollback(false)
를 사용한다.
@Test
@Transactional
void txTest() {
dao.deleteAll();
dao.add(new Member(10, "Spring", 7.8));
assertThat(dao.count()).isEqualTo(1);
}
@Before, @After
은 트랜잭션 안에서 실행된다.
트랜잭션이 시작되기 전에 호출한다.
@BeforeTransaction
public void setUpBeforeTx() { ... }
트랜잭션이 끝난 후에 호출된다.
@AfterTransaction
public void tearDownAfterTx() { ... }
AppConfig 설정을 이용한 테스트를 할 경우 @ContextConfiguration
의 class 앨리먼트를 이용하여 사용한다.
@Configuration
@ComponentScan
public class AppConfig { ... }
@ContextConfiguration(classes=AppConfig.class)
@Transactional
public class DaoTest{ ... }
테스트에서 사용할 @Configuration
클래스가 여러 개라면 다음과 같이 {}
에 모두 넣어준다.
@ContextConfiguration(classes={AppConfig.class, OtherConfig.class})
@ContextConfiguration
에 아무 값도 지정하지 않는다면, 디폴트 설정정보가 있는지 확인하고 이를 사용한다.
다음 코드를 살펴보자.
@ContextConfiguration
public class MyAppTest{
...
@Configuration
static class MyAppConfig() { ... }
@Configuration
static class MyExtraConfig() { ... }
}
테스트 클래스에서 중첩된 스태틱 클래스 중 @Configuration
이 붙은 것을 모두 디폴트 설정정보로 사용한다.
@ActiveProfiles("default")
public class MyAppTest { ... }
다음 테스트 클래스 설정정보 중에서 default
프로파일만 활성화해서 테스트를 수행한다.
@Configuration
public class HelloConfig {
@Bean
@Profile("default")
HelloService defaultService() {
return new DefaultService();
}
@Bean
@Profile("trend")
HelloService trendService() {
return new TrendService();
}
}
@Scheduled(cron="0 0 12 1 * MON-FRI")
public void checkSystemStatus() { ... }
@Async
가 부여된 메소드는 비동기 방식으로 실행된다. 리턴타입은 void
또는 Future
타입이여야 한다.
@Async
void complexWork(String s) { ... }
캐시는 기본적으로 성능 향상을 위해 사용한다. 캐시는 값을 저장해두고 불러오기 때문에 반복적으로 동일한 결과가 돌아오는 작업에 주로 이용된다.
캐시 기능을 사용하려면 설정 파일에 @EnableCaching
어노테이션을 추가한다.
@EnableCaching
@Configuration
public class AppConfig { ... }
그 다음으로 캐시를 관리해줄 캐시매니저를 빈으로 등록해 주어야 한다. 스프링은 다음과 같은 캐시 매니저들을 제공하고 있다.
ConcurrentMapCacheManager, SimpleCacheManager, EhCacheCacheManager, CompositeCacheManager, NoOpCacheManager
스프링의 캐시 서비스 추상화는 AOP를 이용한다. 보통 메소드 단위로 사용한다.
@Cacheable("product")
public Product bestProduct(String productNo) { ... }
다음과 같은 순서로 bestProduct
메소드가 호출된다면
bestProduct("A-001"); // (1)
bestProduct("A-001"); // (2)
(1)
product
캐시에 저장된 오브젝트가 없을 경우 캐시 어드바이스틑 bestProduct()
메소드를 실행한다. 그리고 메소드의 결과가 리턴되면 리턴 값인 Product
오브젝트를 A-001
키 값으로 캐시에 저장한다.
(2)
메소드가 실행 될 때 이미 product
캐시에 저장되어 있기 때문에 캐시에 저장된 Product
오브젝트를 가져와 바로 리턴한다. 이 때는 bestProduct()
메소드가 실행 되지 않는다
캐시를 제거하는 방법은 2가지가 있다.
캐시 제거에 사용될 메소드에 어노테이션을 붙여준다.
@CashEvict(value="product", key="#product.productNo")
public void updateProducts(Product product) { ... }
productNo
와 같은 키 값을 가진 캐시만 제거된다.
캐시에 값을 저장하는 용도로만 사용한다. 저장된 캐시의 내용을 사용하지는 않고 항상 메소드를 실행한다.
ex ) user
의 type
이 admin
인 경우에만 캐시에 저장한다.
@CachePut(value="user", condition="#user.type == 'admin'")
public User addUser(User user) { ... }
토비의 스프링 3.1 Vol2
Spring flash attributes
https://www.baeldung.com/spring-web-flash-attributes