그리고 클래스 레벨에 @SessionAttributes("id")를 설정한다. 클래스 레벨에 @SessionAttributes 어노테이션을 이용하여 세션에 값을 담을 key값을 설정 해두면 Model 영역에 해당 key로 값이 추가되는 경우 session에 자동 등록을 한다.
@Controller
@RequestMapping("/first/*")
@SessionAttributes("id")
public class FirstController {
...생략
}
세션에 값이 잘 저장 되었음을 응답 화면을 통해 확인할 수 있다. 단, 로그아웃 버튼을 눌러도 세션 객체가 제거 되지 않는 문제가 있다. 따라서 로그아웃 버튼도 다시 추가해서 로그아웃 로직을 추가 작성한다.
SessionAttributes로 등록된 값은 session의 상태를 관리하는 SessionStatus의 setComplete()메소드를 호출해야 사용이 만료된다.
@GetMapping("logout2")
public String logoutTest2(SessionStatus sessionStatus) {
/* 현재 컨트롤러 세션에 저장된 모든 정보를 제거한다. 개별 제거는 불가능하다. */
sessionStatus.setComplete();
return "first/loginResult";
}
해당 화면에서 사용자 입력 양식에 값을 입력하고 submit을 누르면 POST 방식의 /first/regist요청이 발생한다.
발생하는 요청을 매핑할 controller의 handler method이다.
이 때 파라미터 선언부에WebRequest타입은 선언하면 해당 메소드 호출 시 인자로 값을 전달해 준다.
핸들러 메소드 매개변수로HttpServletRequest,HttpServletResponse도 사용 가능하다.
상위 타입인ServletRequest,ServletResponse도 사용 가능하다.
WebRequest는HttpServletRequest의 요청 정보를 거의 대부분 그대로 가지고 있는API로Servlet에 종속적이지 않다.
HttpServletRequest는Servlet API의 일부이고,WebRequest는Spring의 일부이기 때문에 Spring 기반의 프로젝트에서 더 자주 사용 된다.
@PostMapping("regist")
public String registMenu(Model model, WebRequest request) {
/* WebRequest 객체의 getParameter 등의 메서드를 통해 클라이언트로부터 전달 된 파라미터를 가져올 수 있다. */
String name = request.getParameter("name");
int price = Integer.parseInt(request.getParameter("price"));
int categoryCode = Integer.parseInt(request.getParameter("categoryCode"));
/* 클라이언트로부터 전달 받은 값을 통해 응답할 화면의 메세지를 생성한다. */
String message
= name + "을(를) 신규 메뉴 목록의 " + categoryCode + "번 카테고리에 " + price + "원으로 등록 하셨습니다!";
System.out.println(message);
model.addAttribute("message", message);
return "first/messagePrinter";
}
클라이언트에서 입력 된 값이 컨트롤러 핸들러 메서드의 WebRequest객체에 잘 담겨서 전달 되었음을 응답 화면을 통해 확인할 수 있다.
@RequestMapping은 Spring Web MVC에서 요청(Request)을 처리하는 데 사용되는 어노테이션이며, 클래스 레벨이나 메소드 레벨에서 사용 가능하다. 이를 통해 어떤 URL이 어떤 메소드에서 처리되는지, 어떤 HTTP Method를 처리할지 등을 정할 수 있다.
DispatcherServlet은 웹 요청을 받으면@Controller가 달린 컨트롤러 클래스에 처리를 위임한다. 그 과정은 컨트롤러 클래스의핸들러 메서드에 선언된 다양한@RequestMapping 설정 내용에 따른다.
1. Method Mapping
클래스를 생성하고@Controller어노테이션을 설정 한 뒤 요청 매핑 테스트를 진행한다.
@Controller어노테이션이 붙은 클래스는 웹 요청을 처리하는 컨트롤러임을 나타내며, Spring MVC에서는@Controller어노테이션이 붙은 클래스를 자동으로 스캔해서 Bean으로 등록한다. 이후 요청이 들어오면@RequestMapping어노테이션을 이용하여 어떤 메소드가 요청을 처리할지 지정한다.
1. Method 방식 미지정
@Controller 어노테이션이 설정 된 클래스에 @RequestMapping 어노테이션을 설정한 메소드를 정의한다. 그리고 @RequestMapping에는 요청 URL 값을 설정한다.
/* 요청 URL 설정 */
@RequestMapping("/menu/regist")
public String registMenu(Model model) {
/* Model 객체에 addAttribute 메서드를 이용해
* key, value를 추가하면 추후 view에서 사용할 수 있다.
* chap03-view-resolver에서 다시 다룬다. */
model.addAttribute("message", "신규 메뉴 등록용 핸들러 메소드 호출함...");
/* 반환 하고자 하는 view의 경로를 포함한 이름을 작성한다.
* resources/templates 하위부터의 경로를 작성한다.
* chap03-view-resolver에서 다시 다룬다. */
return "mappingResult";
}
요청에 대해 반환하고자 하는 view를 생성하고 model 객체에 추가했던 message를 화면에 출력하는 thymeleaf 코드를 작성한다. thymeleaf는 뷰 템플릿의 한 종류인데 자세한 문법은 추후 다룬다. 여기에서는 th:text="${key}"를 통해 값을 화면에 출력하도록 한다.
필요한 환경 설정을 최소화하고 개발자가 비즈니스 로직에 집중할 수 있도록 도와줘 생산성을 크게 향상시킬 수 있도록 스프링의 단점을 보완하여 만든 프로젝트이다.
스프링 부트는 틀림없이 자바에서 REST 기반 마이크로서비스 웹 애플리케이션을 개발하는 가장 빠른 방법 중 하나로 도커 컨테이너 배포 및 빠른 프로토타이핑에도 매우 적합하다.
간혹 스프링부트를 스프링 프레임워크와 전혀 다른 것으로 오해하지만 스프링부트는 스프링프레임워크라는 큰 틀 속에 속하는 도구일 뿐이다.
Spring Boot를 사용하면 "그냥 실행할 수 있는" 독립 실행형 production-grade Spring 기반 애플리케이션을 쉽게 생성할 수 있다.우리는 당신이 최소한의 고민으로 시작할 수 있도록 Spring 플랫폼과 third-party libraries에 대한 견해를 가지고 있다. 대부분의 Spring Boot 애플리케이션은 최소한의 Spring 구성으로 동작시킬 수 있다. 출처 : https://spring.io/projects/spring-boot
2012년 10월 Mike Youngstrom는 Spring 프레임워크에서 컨테이너 없는 웹 애플리케이션 아키텍처에 대한 지원을 요청하는 기능 요청했다. main 메소드에서 부트스트랩된 스프링 컨테이너 내에서 웹 컨테이너 서비스를 구성하는 것에 대해 말했는데 다음은 '이슈' 기반의 프로젝트 관리 도구 jira 요청에서 발췌한 내용이다.
Spring 컴포넌트와 설정 모델을 위에서 아래로 활용하는 도구와 참조 아키텍처를 제공한다면 Spring의 웹 애플리케이션 아키텍처는 상당히 단순화될 수 있다고 생각한다. 간단한 main() 메소드에서 부트스트랩된 Spring Container 내에 이러한 공통 웹 컨테이너 서비스의 구성을 포함하고 통합한다.
Mike Youngstrom의 요청으로 인해 2013년 초에 시작되는 스프링 부트 프로젝트 개발이 시작되었고 2014년 4월에 스프링 부트 1.0.0이 릴리즈 되었다. 그 이후로 많은 스프링 부트 마이너 버전이 나왔다.
Java에서 프록시(Proxy)는 대리자를 의미한다. 프록시는 기존의 객체를 감싸서 그 객체의 기능을 확장하거나 변경할 수 있게 해준다. 예를 들어, 프록시 객체를 사용하면 객체에 대한 접근을 제어하거나, 객체의 메소드 호출 전후에 로깅 작업 등을 수행할 수 있다. 또한, 프록시 객체를 사용하여 원격으로 실행되는 객체를 호출할 수도 있다. 프록시는 주로 AOP(Aspect Oriented Programming)에서 사용된다.
프록시 생성은 크게 두 가지 방식이 제공된다.
1. JDK Dynamic Proxy 방식
리플렉션을 이용해서 proxy 클래스를 동적으로 생성해주는 방식으로, 타겟의 인터페이스를 기준으로 proxy를 생성해준다. 사용자의 요청이 타겟을 바라보고 실행될 수 있도록 타겟 자체에 대한 코드 수정이 아닌 리플렉션을 이용한 방식으로, 타겟의 위임 코드를InvocationHandler를 이용하여 작성하게 된다. 하지만 사용자가 타겟에 대한 정보를 잘못 주입하는 경우가 발생할 수 있기 때문에 내부적으로 주입된 타겟에 대한 검증 코드를 거친 후 invoke가 동작하게 된다.
CGLib 방식
동적으로 Proxy를 생성하지만 바이트코드를 조작하여 프록시를 생성해주는 방식이다. 인터페이스 뿐 아니라 타겟의 클래스가 인터페이스를 구현하지 않아도 프록시를 생성해준다. CGLib(Code Generator Library)의 경우에는 처음 메소드가 호출된 당시 동적으로 타켓 클래스의 바이트 코드를 조작하게 되고, 그 이후 호출 시부터는 변경된 코드를 재사용한다. 따라서 매번 검증 코드를 거치는 1번 방식보다는 invoke시 더 빠르게 된다. 또한 리플렉션에 의한 것이 아닌 바이트코드를 조작하는 방식이기 때문에 성능면에서는 더 우수하다.
하지만 CGLib 방식은 스프링에서 기본적으로 제공되는 방식은 아니었기에 별도로 의존성을 추가하여 개발해야 했고, 파라미터가 없는 default 생성자가 반드시 필요했으며, 생성된 프록시의 메소드를 호출하면 타겟의 생성자가 2번 호출되는 등의 문제점들이 있었다.
스프링 4.3, 스프링부트 1.3 이후부터 CGLib의 문제가 된 부분이 개선되어 기본 core 패키지에 포함되게 되었고, 스프링에서 기본적으로 사용하는 프록시 방식이 CGLib 방식이 되었다.
1. 로직을 포함하는 코드 작성
Student 인터페이스 작성
public interface Student {
void study(int hours);
}
Student 클래스 작성 (Student인터페이스 구현)
public class Student implements Student {
@Override
public void study(int hours) {
System.out.println(hours + "시간 동안 열심히 공부합니다.");
}
}
Java 리플렉션(Reflection)은 실행 중인 자바 프로그램 내부의 클래스, 메소드, 필드 등의 정보를 분석하여 다루는 기법을 말한다. 이를 통해 프로그램의 동적인 특성을 구현할 수 있다. 예를 들어, 리플렉션을 이용하면 실행 중인 객체의 클래스 정보를 얻어오거나, 클래스 내부의 필드나 메소드에 접근할 수 있다. 이러한 기능들은 프레임워크, 라이브러리, 테스트 코드 등에서 유용하게 활용된다.
⇒ 스프링에서는 이 Reflection 기술을 사용해 런타임 시 등록한 빈을 애플리케이션 내에서 사용할 수 있게 한다.
/* .class 문법을 이용하여 Class 타입의 인스턴스를 생성할 수 있다. */
Class class1 = Account.class;
System.out.println("class1 : " + class1);
/* Object 클래스의 getClass() 메소드를 이용하면 Class 타입으로 리턴받아 이용할 수 있다. */
Class class2 = new Account().getClass();
System.out.println("class2 : " + class2);
/* Class.forName() 메소드를 이용하여 런타임시 로딩을 하고 그 클래스 메타정보를 Class 타입으로 반환받을 수 있다. */
try {
Class class3 = Class.forName("project.reflection.Account");
System.out.println("class3 : " + class3);
/* Double자료형 배열을 로드할 수 있다. */
Class class4 = Class.forName("[D");
Class class5 = double[].class;
System.out.println("class4 : " + class4);
System.out.println("class5 : " + class5);
/* String자료형 배열을 로드할 수 있다. */
Class class6 = Class.forName("[Ljava.lang.String;");
Class class7 = String[].class;
System.out.println("class6 : " + class6);
System.out.println("class7 : " + class7);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
/* 원시 자료형을 사용하면 컴파일 에러 발생 */
// double d = 1.0;
// Class class8 = d.getClass();
/* TYPE 필드를 이용하여 원시형 클래스를 반환받을 수 있다. */
Class class8 = Double.TYPE;
System.out.println("class8 : " + class8);
Class class9 = Void.TYPE;
System.out.println("class9 : " + class9);
/* 클래스의 메타 정보를 이용하여 여러 가지 정보를 반환받는 메소드를 제공한다. */
/* 상속된 부모 클래스를 반환한다. */
Class superClass = class1.getSuperclass();
System.out.println("superClass : " + superClass);
/*
class1 : class project.reflection.Account
class2 : class project.reflection.Account
class3 : class project.reflection.Account
class4 : class [D
class5 : class [D
class6 : class [Ljava.lang.String;
class7 : class [Ljava.lang.String;
class8 : double
class9 : void
superClass : class java.lang.Object
*/
2. field
field 정보에 접근할 수 있다.
Field[] fields = Account.class.getDeclaredFields();
for(Field field : fields) {
System.out.println("modifiers : " + Modifier.toString(field.getModifiers()) +
", type : " + field.getType() +
", name : " + field.getName() );
}
/*
modifiers : private, type : class java.lang.String, name : backCode
modifiers : private, type : class java.lang.String, name : accNo
modifiers : private, type : class java.lang.String, name : accPwd
modifiers : private, type : int, name : balance
*/
Method[] methods = Account.class.getMethods();
Method getBalanceMethod = null;
for(Method method : methods) {
System.out.println(Modifier.toString(method.getModifiers()) + " " +
method.getReturnType().getSimpleName() + " " +
method.getName());
if("getBalance".equals(method.getName())) {
getBalanceMethod = method;
}
}
/*
public String getBalance
public String withDraw
public String deposit
public final native void wait
public final void wait
public final void wait
public boolean equals
public String toString
public native int hashCode
public final native Class getClass
public final native void notify
public final native void notifyAll
*/
@AllArgsConstructor
@ToString
public class MemberDTO {
private Long id;
private String name;
}
2. MemberDAO
@Repository
public class MemberDAO {
private final Map<Long, MemberDTO> memberMap;
public MemberDAO(){
memberMap = new HashMap<>();
memberMap.put(1L, new MemberDTO(1L, "유관순"));
memberMap.put(2L, new MemberDTO(2L, "홍길동"));
}
public Map<Long, MemberDTO> selectMembers(){
return memberMap;
};
public MemberDTO selectMember(Long id) {
MemberDTO returnMember = memberMap.get(id);
if(returnMember == null) throw new RuntimeException("해당하는 id의 회원이 없습니다.");
return returnMember;
}
}
3. MemberService 클래스 생성
@Service
public class MemberService {
private final MemberDAO memberDAO;
public MemberService(MemberDAO memberDAO) {
this.memberDAO = memberDAO;
}
public Map<Long, MemberDTO> selectMembers(){
System.out.println("selectMembers 메소드 실행");
return memberDAO.selectMembers();
}
public MemberDTO selectMember(Long id) {
System.out.println("selectMember 메소드 실행");
return memberDAO.selectMember(id);
}
}
4. Application 클래스 생성
public class Application {
public static void main(String[] args) {
ApplicationContext context
= new AnnotationConfigApplicationContext("project.aop");
MemberService memberService = context.getBean("memberService", MemberService.class);
System.out.println("=============== selectMembers ===============");
System.out.println(memberService.selectMembers());
System.out.println("=============== selectMember ===============");
System.out.println(memberService.selectMember(3L));
}
}
/*
=============== selectMembers ===============
{1=MemberDTO(id=1, name=유관순), 2=MemberDTO(id=2, name=홍길동)}
=============== selectMember ===============
Exception in thread "main" java.lang.RuntimeException: 해당하는 id의 회원이 없습니다.
...생략
*/
2. 라이브러리 의존성 추가
aspectjweaver , aspectjrt 라이브러리가 있어야 AOP 기능이 동작할 수 있으므로 build.gradle.kts파일에 추가한다.
ContextConfiguration 빈 설정 파일을 생성한다. aspectj의 autoProxy 사용에 관한 설정을 해 주어야 advice가 동작한다. proxyTargetClass=true 설정은 cglib를 이용한 프록시를 생성하는 방식인데, Spring 3.2부터 스프링 프레임워크에 포함되어 별도 라이브러리 설정을 하지 않고 사용할 수 있다. 성능 면에서 더 우수하다.
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ContextConfiguration {
}
4. Aspect 생성
LoggingAspect클래스를 생성하고 빈 스캐닝을 통해 빈 등록을 한다.
@Aspect: ponitcut과 advice를 하나의 클래스 단위로 정의하기 위한 어노테이션이다.
@Aspect
@Component
public class LoggingAspect {}
1. Pointcut
LoggingAspect클래스에 포인트 컷을 정의한다.
@Pointcut: 여러 조인 포인트를 매치하기 위해 지정한 표현식
@Pointcut("execution(* project.aop.*Service.*(..))")
public void logPointcut() {}
execution설명
execution은 AOP에서 가장 많이 사용되는 포인트컷 표현식 중 하나이다.execution표현식은 메서드 실행 시점에 일치하는 조인포인트를 정의하는 데 사용된다.execution표현식의 기본 구성은 다음과 같다.
com.example.* 패키지 내의 클래스에서 반환값이 void인 메소드 중, 메소드명이 "get*"으로 시작하는 메소드를 포함하는 표현식은 다음과 같다.
execution(void com.example.*.*.get*(..))
void: 리턴 타입 패턴으로 반환값이void인 메소드를 나타낸다.
com.example.*.*: 클래스 이름 패턴으로com.example패키지 내의 모든 클래스를 나타낸다.
get*: 메소드 이름 패턴으로"get"으로 시작하는 모든 메소드를 나타낸다.
..: 파라미터 타입 패턴으로 모든 파라미터를 나타낸다.
com.example패키지 내의 클래스에서 메소드명이"set*"으로 시작하는 메소드 중, 인자로java.lang.String타입의 인자를 갖는 메소드를 포함하는 표현식은 다음과 같다.
execution(* com.example..set*(java.lang.String))
*: 리턴타입 패턴으로 모든 반환값을 나타낸다.
com.example..: 클래스 이름 패턴으로com.example패키지 내의 모든 클래스를 나타낸다.
set*: 메소드 이름 패턴으로"set"으로 시작하는 모든 메소드를 나타낸다.
java.lang.String: 파라미터 타입 패턴으로 인자로java.lang.String타입 하나만을 나타낸다.
2. Before
Before어드바이스는 대상 메소드가 실행되기 이전에 실행되는 어드바이스이다. 미리 작성한 포인트 컷을 설정한다.
JoinPoint는 포인트컷으로 패치한 실행 지점이다. 매개변수로 전달한 JoinPoint 객체는 현재 조인 포인트의 메소드명, 인수값 등의 자세한 정보를 엑세스 할 수 있다.
@Before("LoggingAspect.logPointcut()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before joinPoint.getTarget() " + joinPoint.getTarget());
System.out.println("Before joinPoint.getSignature() " + joinPoint.getSignature());
if(joinPoint.getArgs().length > 0){
System.out.println("Before joinPoint.getArgs()[0] " + joinPoint.getArgs()[0]);
}
}
/*
=============== selectMembers ===============
Before joinPoint.getTarget() project.aop.MemberService@6ed3f258
Before joinPoint.getSignature() Map project.aop.MemberService.selectMembers()
selectMembers 메소드 실행
{1=MemberDTO(id=1, name=유관순), 2=MemberDTO(id=2, name=홍길동)}
=============== selectMember ===============
Before joinPoint.getTarget() project.aop.MemberService@6ed3f258
Before joinPoint.getSignature()
MemberDTO project.aop.MemberService.selectMember(Long)
Before joinPoint.getArgs()[0] 3
selectMember 메소드 실행
Exception in thread "main" java.lang.RuntimeException: 해당하는 id의 회원이 없습니다.
...생략
*/
MemberService 클래스의 selectMembers 메소드와 selectMember 메소드가 실행 되기 전 Before어드바이스의 실행 내용이 삽입 되어 동작하는 것을 확인할 수 있다.
3. After
After어드바이스는 대상 메소드가 실행된 이후에(정상, 예외 관계없이) 실행되는 어드바이스이다. 미리 작성한 포인트 컷을 설정한다. 포인트 컷을 동일한 클래스 내에서 사용하는 것이면 클래스명은 생략 가능하다. 단, 패키지가 다르면 패키지를 포함한 클래스명을 기술해야 한다.
Before어드바이스와 동일하게 매개변수로 JoinPoint 객체를 전달 받을 수 있다.
@After("logPointcut()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After joinPoint.getTarget() " + joinPoint.getTarget());
System.out.println("After joinPoint.getSignature() " + joinPoint.getSignature());
if(joinPoint.getArgs().length > 0){
System.out.println("After joinPoint.getArgs()[0] " + joinPoint.getArgs()[0]);
}
}
/*
=============== selectMembers ===============
Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@5443d039
Before joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
selectMembers 메소드 실행
After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@5443d039
After joinPoint.getSignature() Map com.ohgiraffers.section01.aop.MemberService.selectMembers()
{1=MemberDTO(id=1, name=유관순), 2=MemberDTO(id=2, name=홍길동)}
=============== selectMember ===============
Before joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@5443d039
Before joinPoint.getSignature()
MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
Before joinPoint.getArgs()[0] 3
selectMember 메소드 실행
After joinPoint.getTarget() com.ohgiraffers.section01.aop.MemberService@5443d039
After joinPoint.getSignature()
MemberDTO com.ohgiraffers.section01.aop.MemberService.selectMember(Long)
After joinPoint.getArgs()[0] 3
Exception in thread "main" java.lang.RuntimeException: 해당하는 id의 회원이 없습니다.
...생략
*/
MemberService 클래스의 selectMembers 메소드와 selectMember 메소드가 실행 된 후에 After 어드바이스의 실행 내용이 삽입 되어 동작하는 것을 확인할 수 있다. Exception발생 여부와 무관하게 항상 실행된다.
4. AfterReturing
AfterReturning어드바이스는 대상 메소드가 정상적으로 실행된 이후에 실행되는 어드바이스이다. 미리 작성한 포인트 컷을 설정한다.
returning속성은 리턴값으로 받아올 오브젝트의 매개변수 이름과 동일해야 한다. 또한 joinPoint는 반드시 첫 번째 매개변수로 선언해야 한다. 이 어드바이스에서는 반환 값을 가공할 수도 있다.
@AfterReturning(pointcut="logPointcut()", returning="result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("After Returning result " + result);
/* 리턴할 결과값을 변경해 줄 수 도 있다. */
if(result != null && result instanceof Map) {
((Map<Long, MemberDTO>) result).put(100L, new MemberDTO(100L, "반환 값 가공"));
}
}
/*
=============== selectMembers ===============
Before joinPoint.getTarget() project.aop.MemberService@2a62b5bc
Before joinPoint.getSignature() Map project.aop.MemberService.selectMembers()
selectMembers 메소드 실행
After Returning result {1=MemberDTO(id=1, name=유관순), 2=MemberDTO(id=2, name=홍길동)}
After joinPoint.getTarget() project.aop.MemberService@2a62b5bc
After joinPoint.getSignature() Map project.aop.MemberService.selectMembers()
{1=MemberDTO(id=1, name=유관순), 2=MemberDTO(id=2, name=홍길동),
100=MemberDTO(id=100, name=반환 값 가공)}
=============== selectMember ===============
Before joinPoint.getTarget() project.aop.MemberService@2a62b5bc
Before joinPoint.getSignature()
MemberDTO project.aop.MemberService.selectMember(Long)
Before joinPoint.getArgs()[0] 3
selectMember 메소드 실행
After joinPoint.getTarget() project.aop.MemberService@2a62b5bc
After joinPoint.getSignature()
MemberDTO project.aop.MemberService.selectMember(Long)
After joinPoint.getArgs()[0] 3
Exception in thread "main" java.lang.RuntimeException: 해당하는 id의 회원이 없습니다.
...생략
*/
MemberService 클래스의 selectMembers 메소드가 실행 된 후에 AfterReturning 어드바이스의 실행 내용이 삽입 되어 동작하는 것을 확인할 수 있다. Exception 이 발생한 selectMember메소드에서는 동작하지 않는다.
5. AfterThrowing
AfterThrowing어드바이스는 예외가 발생했을 때 실행되는 어드바이스이다. 미리 작성한 포인트 컷을 설정한다.
throwing 속성의 이름과 매개변수의 이름이 동일해야 한다. 이 어드바이스에서는Exception에 따른 처리를 작성할 수 있다.
@AfterThrowing(pointcut="logPointcut()", throwing="exception")
public void logAfterThrowing(Throwable exception) {
System.out.println("After Throwing exception " + exception);
}
/*
=============== selectMembers ===============
Before joinPoint.getTarget() project.aop.MemberService@24111ef1
Before joinPoint.getSignature() Map project.aop.MemberService.selectMembers()
selectMembers 메소드 실행
After Returning result {1=MemberDTO(id=1, name=유관순), 2=MemberDTO(id=2, name=홍길동)}
After joinPoint.getTarget() project.aop.MemberService@24111ef1
After joinPoint.getSignature() Map project.aop.MemberService.selectMembers()
{1=MemberDTO(id=1, name=유관순), 2=MemberDTO(id=2, name=홍길동),
100=MemberDTO(id=100, name=반환 값 가공)}
=============== selectMember ===============
Before joinPoint.getTarget() project.aop.MemberService@24111ef1
Before joinPoint.getSignature()
MemberDTO project.aop.MemberService.selectMember(Long)
Before joinPoint.getArgs()[0] 3
selectMember 메소드 실행
After Throwing exception java.lang.RuntimeException: 해당하는 id의 회원이 없습니다.
After joinPoint.getTarget() project.aop.MemberService@24111ef1
After joinPoint.getSignature()
MemberDTO project.aop.MemberService.selectMember(Long)
After joinPoint.getArgs()[0] 3
Exception in thread "main" java.lang.RuntimeException: 해당하는 id의 회원이 없습니다.
...생략
*/
MemberService 클래스의 selectMember 메소드가 실행 된 후에 AfterThrowing 어드바이스의 실행 내용이 삽입 되어 동작하는 것을 확인할 수 있다. Exception 이 발생하지 않은 selectMembers 메소드에서는 동작하지 않는다.
6. Around
Around어드바이스는 대상 메소드 실행 전/후에 적용되는 어드바이스이다. 미리 작성한 포인트 컷을 설정한다.
Around Advice는 가장 강력한 어드바이스이다. 이 어드바이스는 조인포인트를 완전히 장악하기 때문에 앞에 살펴 본 어드바이스 모두 Around 어드바이스로 조합할 수 있다.
AroundAdvice의 조인포인트 매개변수는 ProceedingJoinPoint로 고정되어 있다. JoinPoint의 하위 인터페이스로 원본 조인포인트의 진행 시점을 제어할 수 있다.
조인포인트 진행하는 호출을 잊는 경우가 자주 발생하기 때문에 주의해야 하며 최소한의 요건을 충족하면서도 가장 기능이 약한 어드바이스를 쓰는게 바람직하다.
@Around("logPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around Before " + joinPoint.getSignature().getName());
/* 원본 조인포인트를 실행한다. */
Object result = joinPoint.proceed();
System.out.println("Around After " + joinPoint.getSignature().getName());
/* 원본 조인포인트를 호출한 쪽 혹은 다른 어드바이스가 다시 실행할 수 있도록 반환한다. */
return result;
}
/*
=============== selectMembers ===============
Around Before selectMembers
Before joinPoint.getTarget() project.aop.MemberService@21a21c64
Before joinPoint.getSignature() Map project.aop.MemberService.selectMembers()
selectMembers 메소드 실행
After Returning result {1=MemberDTO(id=1, name=유관순), 2=MemberDTO(id=2, name=홍길동)}
After joinPoint.getTarget() project.aop.MemberService@21a21c64
After joinPoint.getSignature() Map project.aop.MemberService.selectMembers()
Around After selectMembers
{1=MemberDTO(id=1, name=유관순), 2=MemberDTO(id=2, name=홍길동),
100=MemberDTO(id=100, name=반환 값 가공)}
=============== selectMember ===============
Around Before selectMember
Before joinPoint.getTarget() project.aop.MemberService@21a21c64
Before joinPoint.getSignature()
MemberDTO project.aop.MemberService.selectMember(Long)
Before joinPoint.getArgs()[0] 3
selectMember 메소드 실행
After Throwing exception java.lang.RuntimeException: 해당하는 id의 회원이 없습니다.
After joinPoint.getTarget() project.aop.MemberService@21a21c64
After joinPoint.getSignature()
MemberDTO project.aop.MemberService.selectMember(Long)
After joinPoint.getArgs()[0] 3
Exception in thread "main" java.lang.RuntimeException: 해당하는 id의 회원이 없습니다.
...생략
*/
MemberService 클래스의 selectMember 메소드가 실행 된 후에 AfterThrowing 어드바이스의 실행 내용이 삽입 되어 동작하는 것을 확인할 수 있다. Exception 이 발생하지 않은 selectMembers메소드에서는 동작하지 않는다.