HTTP 통신 기반의 아키텍처 REST -REST는 Representational State Transfer의 약어로 하나의 URI는 하나의 고유한 리소스를 대표하도록 설계된다는 개념. - REST 방식은 특정한 URI는 반드시 그에 상응하는 데이터 자체라는 것을 의미. 예를 들면 /post/123은 해당 페이지의 번호의 123번이라는 고유한 의미를 가지고 설계하고, 이에 대한 처리는 GET, POST방식과 같이 추가적인 정보를 통해서 결정된다. - REST API는 외부에서 위와 같은 방식으로 특정 URI를 통해서 사용자가 원하는 정보를 제공하는 방식. - REST방식의 서비스 제공이 가능한 것을 Restful 하다고 표현. - 스프링 3부터 @ResponseBody 애노테이션을 지원하면서 REST방식의 처리를 지원하고 있었으며, 스프링 4에 들어와서 @RestController가 본격적으로 적용.
REST API는 다음의 구성되어있다. - 자원(RESOURCE) - URI - 행위(Verb) - HTTP METHOD ( GET, POST, DELETE, PUT ) - 표현(Representations)
@PathVariable을 이용한 경로 변수 처리
- 경로의 특정 위치 값이 고정되지 않고 달라질 때 사용하는 것이 @PathVariable
@RequestBody & @ResponseBody
@RequestBody 과 @ResponseBody는 요청몸체와 응답 몸체 구현 - @RequestBody : JSON 형식의 요청 몸체를 자바 객체로 변환 - @ResponseBody :자바 객체를 JSON 이나 XML 형식의 문자열로 변환 - Spring Framework는 HttpMessageConverter 를 이용해서 자바 객체와 HTTP 요청/응답 몸체 사이의 변환 처리
컨트롤러가 호출되기 전에 실행된다. handler 파라메터는 핸들러 매핑이 찾아 준 컨트롤러 빈 오브젝트다. 컨트롤러 실행 이전에 처리해야 할 작업이 있다거나, 요청정보를 가공하거나 추가하는 경우에 사용할 수 있다. 또는 요청에 요청에 대한 로그를 남기기 위해 사용하기도 한다. 리턴 값이 true 이면 핸들러 실행 체인의 다음 단계로 진행되지만, false 라면 작업을 중단하고 리턴 하므로 컨트롤러와 남은 인터셉터들은 실행되지 않는다. – postHandle()
컨트롤러를 실행하고 난 후에 호출된다. 이 메소드에는 컨트롤러가 돌려준 ModelAndView 타입의 정보가 제공 되서 컨트롤러 작업 결과를 참조하거나 조작할 수 있다. –afterCompletion()
이름 그대로 모든 뷰에서 최종 결과를 생성하는 일을 포함한 모든 작업이 모두 완료된 후에 실행된다. 요청처리 중에 사용한 리소스를 반환해주기에 적당한 메소드다.
핸들러 인터셉터는 하나 이상을 등록할 수 있다. preHandle() 은 인터셉터가 등록된 순서대로 실행된다. 반면에 postHandle() 과 afterCompletion() 은 preHandle() 이 실행된 순서와 반대로 실행된다.
HandlerInterceptor를 통한 요청 가로채기
HandlerInterceptor 인터페이스의 구현 – 핸들러 인터셉터는 HandlerInterceptor 인터페이스를 구현해서 만든다. 이 인터페이스를 구현 할 경우 사용하지 않는 메서드도 구현 해주어야 한다. – 이러한 불편함을 줄여주기 위해 HandlerInterceptorAdaptor 클래스를 제공. – HandlerInterceptor 인터페이스를 구현해야 하는 클래스는 HandlerInterceptorAdaptor 클래스를 상속 받은 뒤 필요한 메서드만 오버라이딩 하여 사용.
1. HandlerMapping에 HandlerInterceptor 설정 하기
HandlerInterceptor를 구현 한 뒤에는 <mvc:interceptors> 의 interceptors 프로퍼티를 사용해
HandlerInterceptorAdaptor 를 등록해 주면 된다.
xmlns:mvc가아니라 생략되어 있으므로 <interceptors>로 등록했다.
2. HandlerInterceptor 인터페이스의 구현
예외처리
@ExceptionHandler 어노테이션을 이용한 처리
– AnnotationMethodHandlerExceptionResolver 클래스는 @Controller 어노테이션이 적용된 클래스에서 @ExceptionHandler 어노테이션이 적용된 메서드를 이용해서 예외 처리한다. @ModelAttribute 에서 ExceptionHandler 에서 지정한 예외가 발생하면 @ExceptionHandler가 적용된 메서드를 이용해서 뷰를 지정.
mypage3에서 강제로 예외를 발생시켜보았다. 에러가 발생시 error/nullpoiner.jsp로 이동한다.
package com.aia.firstspring.member.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.aia.firstspring.domain.Member;
import com.aia.firstspring.member.dao.MemberDao;
@Service
public class MemberRegService {
@Autowired
private MemberDao dao;
public int insertMember(Member member) {
int result=0;
try {
result = dao.insertMember(member);
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
MemberDao에 회원가입 메서드 추가
public int insertMember(Member member) {
//그냥 세개만 하기로 했음 ㅎ..
String sql="insert into member (memberid, membername, password) values (?,?,?)";
return template.update(sql, member.getMemberid(), member.getMembername(), member.getPassword());
}
@Controller & @RequestMapping 는 구현을 위한 필수 애노테이션이다.
@Controller Controller 클래스 정의 @RequestMapping HTTP요청 URL을 처리할 Controller 메소드 정의 @RequestParam HTTP 요청에 포함된 파라미터 참조 시 사용 @ModelAttribute HTTP요청에 포함된 파라미터를 모델 객체로 바인딩
@ModelAttribute의 ‘name’으로 정의한 모델 객체를 다음 View에게 사용 가능
Spring Framework가 지원하는 핵심 기능으로 객체 사이의 의존 관계가 객체 자신이 아닌 외부(조립기)에 의해 설정된다. 외부 조립기는 xml 설정 파일을 기반으로 의존을 설정한다.
package member.dao;
public interface Dao {
// public abstract void insert();
void insert();
void select();
void delete();
void update();
}
package member.dao;
public class MemberDao implements Dao {
@Override
public void insert() {
System.out.println("회원 정보 데이터베이스 저장");
}
@Override
public void select() {
System.out.println("회원 정보 검색");
}
@Override
public void delete() {
System.out.println("회원 정보 삭제");
}
@Override
public void update() {
System.out.println("회원 정보 수정");
}
}
생성자 타입의 주입방식
package member.service;
import member.dao.Dao;
public class MemberRegService implements MemberService {
// Dao dao = new MemberDao(); // 의존성이 높은 코드이므로 X
/* 생성자 방식 */
private Dao dao; // 주입받아야하는 참조변수
// 생성자를 통해서 Dao타입의 인스턴스를 주입받는다.
// Dao dao = new MemberDao(); -> 다형성.
public MemberRegService(Dao dao) {
this.dao = dao;
}
@Override
public Object process() {
System.out.println("MemberRegService 실행");
dao.insert();
return null;
}
}
package member.main;
import org.springframework.context.support.GenericXmlApplicationContext;
import member.service.MemberInfoService;
import member.service.MemberRegService;
public class MemberMain {
public static void main(String[] args) {
//1. Spring 컨테이너 생성
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext("classpath:appCtx.xml");
//2. MemberRegService 객체가 필요
MemberRegService regService = ctx.getBean("memberRegService", MemberRegService.class);
//3. MemberRegService 프로세스 메서드 실행
regService.process();
//4. MemberinfoService 객체가 필요
MemberInfoService infoService = ctx.getBean("memberInfoService", MemberInfoService.class);
//5. MemberInfoService 프로세스 메서드 실행
infoService.process();
}
}
xml 설정파일
1. XML 네임스페이스를 이용한 프로퍼티 설정
<!-- 멤버인포서비스 Bean등록 -->
<bean id="memberInfoService" class="member.service.MemberInfoService">
<!-- <property name="dao" ref="memberDao"></property> -->
<property name="dao">
<ref bean="memberDao"/>
</property>
↓ 아래처럼 간결해짐
<!-- 프로퍼티 방식에서 p를 사용하면 속성을 사용하지 않고 한 줄 처리가 가능 -->
<!-- 멤버인포서비스 Bean등록 -->
<bean id="memberInfoService" class="member.service.MemberInfoService"
p:dao-ref="memberDao">
</bean>
</beans>
package member.main;
import org.springframework.context.support.GenericXmlApplicationContext;
import member.dao.Dao;
import member.service.MemberInfoService;
import member.service.MemberRegService;
public class MemberMain2 {
public static void main(String[] args) {
//1. Spring 컨테이너 생성
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext("classpath:appCtx5.xml");
Dao dao1 = ctx.getBean("memberDao", Dao.class);
Dao dao2 = ctx.getBean("memberDao", Dao.class);
// 아무 처리도 하지않으면 기본적으로 싱글톤이다.
System.out.println("dao1참조변수 == dao2참조변수 ? " +(dao1==dao2));
//2. MemberRegService 객체가 필요
MemberRegService regService1 = ctx.getBean("memberRegService", MemberRegService.class);
MemberRegService regService2 = ctx.getBean("memberRegService", MemberRegService.class);
// 프로포타입이므로 false
System.out.println("regService1==regService2 ? " + (regService1==regService2));
//4. MemberinfoService 객체가 필요
MemberInfoService infoService1= ctx.getBean("memberInfoService", MemberInfoService.class);
MemberInfoService infoService2= ctx.getBean("memberInfoService", MemberInfoService.class);
// 싱글톤타입이므로 true > 둘의참조변수가같다
System.out.println("infoService1==infoService2 ? " + (infoService1==infoService2));
}
}
애노테이션 기반 설정
JDK5 버전부터 추가된 것으로 메타데이터를 XML등의 문서에 설정하는 것이 아니라 소스 코드에 “@애노테이션”의 형태로 표현하며 클래스, 필드, 메소드의 선언부에 적용 할 수 있는 특정 기능이 부여된 표현법이다. 프레임워크들이 활성화 되고 애플리케이션 규모가 커질수록 XML 환경 설정은 복잡해지는데, 이러한 어려움을 개선시키기 위하여 자바 파일에 애노테이션을 적용해서 코드를 작성함으로써 개발자가 설정 파일에 작업하게 될 때 발생시키는 오류의 발생 빈도를 낮춰주기도 한다.
Java 에서 이미 정의되어 있는 애노테이션
@Override - 메소드가 오버라이드 됐는지 검증.
부모 클래스 또는 구현해야 할 인터페이스에서 해당 메소드를 찾을 수 없다면 컴파일 오류. @Deprecated - 메소드를 사용하지 말도록 유도. 만약 사용한다면 컴파일 경고. @SuppressWarnings - 컴파일 경고를 무시. @SafeVarargs - 제너릭 같은 가변인자 매개변수를 사용할 때 경고를 무시.(자바7 이상) @FunctionalInterface - 람다 함수등을 위한 인터페이스를 지정.
메소드가 없거나 두 개 이상 되면 컴파일 오류.(자바 8이상)
@Autowired 애노테이션을 이용한 의존 자동 주입
xml 파일에서 annotation-config.
이전 코드보다 훨신 간결하게 프로퍼티나 생성자 부분 없이 깔끔해졌다.
@Autowired 애노테이션으로 인해 코드가 더깔끔해짐 (위-애노테이션 / 아래-프로퍼티&생성자방식)
@Qualifier 애노테이션을 이용한 의존 객체 선택
package member.dao;
public class GuestDao implements Dao {
@Override
public void insert() {
System.out.println("게스트 회원 정보 데이터베이스 저장");
}
@Override
public void select() {
System.out.println("게스트 회원 정보 검색");
}
@Override
public void delete() {
System.out.println("게스트 회원 정보 삭제");
}
@Override
public void update() {
System.out.println("게스트 회원 정보 수정");
}
}
memberDao를 복사해서 GuestDao를 만들어준 뒤 실행하면 dao가 두개라서 에러가 난다.
Qualifier를 이용해서 value값을 설정해주고 클래스에서 Qulifier 어노테이션으로 지정해준 후 Main 실행
@Autowired 의 필수 여부 지정
주석처리 하니 Dao가 없어서 에러가난다 (당연..)
없더라도 인스턴스는 생성되도록 한다.
대부분은 꼭 있어야하는 Dao같은 상황에 사용하지만, 존재의 유무를 확인할 때 등등 정리해서 쓰면 좋다.
@Resource 애노테이션을 이용한 자동 의존 주입
id를 name으로 써서 구별한다.
그러나 타입으로 구별하는 @autowired를 더 자주사용한다.
타입을 맞추는 것이 더 정확하고 에러가 덜 나기 때문이래나 뭐래나.. 암튼 그렇다!
자동 주입과 명시적 의존 주입 설정이 함께 사용되는 경우 명시적인 의존 주입 설정이 우선한다