위 이미지는 Spring 삼각형이라는 유명한 이미지로 Spring의 핵심 개념들을 모두 표현하고 있다.
POJO는 IoC/DI, AOP, PSA를 통해서 달성할 수 있다는 것을 의미한다.
POJO란 Plain Old Java Object의 약자로, 이를 직역하면 순수한 오래된 자바 객체이다.
즉, Java로 생성하는 순수한 객체를 뜻한다.
이를 해석하면 POJO는 객체 지향적인 원리에 충실하면서 환경과 기술에 종속되지 않고, 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 의미한다.
이러한 POJO에 애플리케이션의 핵심 로직과 기능을 담아 설계하고 개발하는 방법을 POJO 프로그래밍이라고 한다.
POJO 프로그래밍
POJO 프로그래밍은 POJO를 이용하여 프로그래밍 코드를 작성하는 것이다.
그러나 순수 자바 객체만을 사용한다고 해서 POJO 프로그래밍이라고 볼 수는 없다.
POJO 프로그래밍으로 작성한 코드가 되기 위해서는 기본적인 규칙들을 지켜야 한다.
1. Java나 Java의 스펙에 정의된 것 이외에는 다른 기술이나 규약에 얽매이지 않아야 한다.
다음 코드는 getter와 setter만 가지고 있는 코드의 에제이다.
public class User {
private String userName;
private String id;
private String password;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
위 코드는 자바에서 제공하는 기능만 사용하기 때문에 해당 클래스는 Java 언어 이외의 특정한 기술에 종속되어 있지 않은 순수한 객체이기 때문에 POJO라고 부를 수 있다.
다음은 특정 기술에 종속적인 예제이다.
public class MessageForm extends ActionForm {
String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
public class MessageAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
MessageForm messageForm = (MessageForm) form;
messageForm .setMessage("Hello World");
return mapping.findForward("success");
}
}
ActionForm 클래스는 과거에 Struts라는 웹 프레임워크에서 지원하는 클래스이다.
MessageForm 클래스는 Struts라는 기술을 사용하기 위해 ActionForm 클래스를 상속받고 있다.
또한, MessageAction 클래스에서 역시 Struts 기술의 Action 클래스를 상속받고 있다.
이러한 방식으로 기술을 상속받아 코드를 작성하게 되면, 애플리케이션의 요구사항이 변경되어 다른 기술로 변경해야 할 때 Struts의 클래스를 명시적으로 사용했던 부분들을 모두 제거하여 수정해야 한다.
또한, Java는 다중 상속을 지원하지 않기 때문에 extends 키워드를 통해 상속을 받게 되면 상위 클래스를 상속받아 하위 클래스를 확장하는 객체지향 설계 기법을 적용하기 어려워진다.
2. 특정 환경에 종속적이지 않아야 한다.
이는 특정한 프레임워크에서만 동작이 가능하면 안된다는 의미를 가진다. POJO는 환경에 독립적이어야 한다. 특히, 비즈니스 로직을 담고 있는 POJO 클래스는 웹 기반의 환경 정보나 웹 기술을 담고 있는 클래스 또는 인터페이스를 사용하면 안된다.
웹 컨트롤러와 연결하여 사용하더라도, 직접적으로 사용 환경을 웹으로 제한하거나 웹에서만 동작하는 API를 직접 사용하지 말아야 한다. 이러한 이유는 웹 이외의 클라이언트는 해당 기능을 요청할 수 없기 때문이다.
따라서 비즈니스 로직을 담는 코드에 HTTPServletRequest, HttpSession, 캐시 등에 관련된 API를 사용하게 된다면 POJO라 할 수 없다.
서블릿(Servlet) 기반의 웹 애플리케이션을 실행시키는 서블릿 컨테이너(Servlet Contatiner)인 아파치 톰캣(Apache Tomcat)을 예로 들어
순수 Java로 작성한 애플리케이션 코드 내에서 Tomcat이 지원하는 API를 직접 가져다가 사용한다고 가정한다.
이후 시스템의 요구 사항이 변경되어 톰캣에서 Zetty라는 다른 서블릿 컨테이너를 사용하게 된다면,
애플리케이션 코드에서 사용하고 있는 Tomcat API 코드들을 모두 제거하고 Zetty로 수정해야 한다.
최악의 경우 애플리케이션을 전부 수정해야 하는 상황에 직면할 수도 있다.
따라서, Java 이외의 다른 기술에 얽매이지 않으며, 특정 환경에 종속적이지 다른 않아야 POJO 프로그래밍이라고 할 수 있다.
POJO 프로그래밍이 필요한 이유
- 특정 환경이나 기술에 종속적이지 않으면 재사용이 가능하고, 확장 가능한 유연한 코드를 작성할 수 있다.
- 저수준 레벨의 기술과 환경에 종속적인 코드를 제거하여 코드를 간결해지며 디버깅하기에도 상대적으로 쉬워진다.
- 특정 기술이나 환경에 종속적이지 않기 때문에 테스트가 단순해진다.
- 객체지향적인 설계를 제한 없이 적용할 수 있다. (가장 중요한 이유)
POJO와 Spring의 관계
Spring은 POJO 프로그래밍을 지향하는 프레임워크이다.
최대한 다른 환경이나 기술에 종속적이지 않도록 하기 위한 POJO 프로그래밍 코드를 작성하기 위해 Spring 프레임워크에서는 IoC/DI, AOP, PSA를 지원하고 있다.
📌AOP란?
AOP(Aspect Oriented Programming) 란 '관점 지향 프로그래밍'으로서 개발 로직과 비즈니스 로직을 분리하여 관리하는 Spring 개념입니다. 즉, 공통 관심 사항과 핵심 관심 사항(코어 코드)을 분리하여 반복된 작업을 줄이는 Spring 개념입니다.
쉽게 설명하자면, 100개 함수가 처리해야 할 공통 기능 코드(예: 로깅) 들을 분리해 별도 Bean으로 관리하는 컨셉입니다.
📌AOP 장점
1) 높은 재사용성 : 중복된 코드를 최대한 제외하여 기능이 필요할 때만 호출하여 쓰기 때문에 재사용성이 높습니다.
2) 깔끔한 코드 : 공통관심사항을 별도로 분리하여 관리하기 때문에 깔끔합니다.
📌Advice / PointCut / JoinPoint
1) Advice
어드바이스는 아래의 PointCut, JoinPoint를 포함한 독립된 클래스입니다.
동작되는 시점에는 before, after, around, after-returning, after-throwing 의 5개 시점이 있습니다.
포인트 컷을 통해 적용되는 메서드를 지정하면 해당
2) PointCut
포인트컷은 여러개의 JoinPoint를 묶은 집합을 의미하며, JoinPoint가 적용이 되는 대상 메서드를 말합니다 .
[예시] @Pointcut("execution(* org.com.test.*(..))")
3) JoinPoint
조인포인트는 Aspect 가 적용되는 지점 또는 시점을 말합니다. (예 : 메소드 진입 전/후, 예외 후, 결과 후)
인스턴스 생성, 메소드 호출, 예외 발생 등 특정 메서드가 시작되는 시점을 의미합니다.
[기능] before, after , around, afterReturning, afterThrowing
[예시] joinPoint.proceed();
📌동작 방식
Spring AOP는 기존 클래스 변경을 하지 않고 원하는 메소드/기능을 실행시키기 때문에 프록시 패턴을 활용하여 동작합니다.
예를 들어 실제 컨트롤러가 아닌 프록시 컨트롤러는 실제 컨트롤러를 흉내내는 가짜 컨트롤러로서, 실제 컨트롤러인 것처럼 연기하며 메서드를 실행시키고 동작합니다.
📌구현 방식
먼저 pom.xml 에 AspectJ 라이브러리가 의존 설정이 되어있는지 확인하여 추가합니다.
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
⚡ 어노테이션을 활용한 적용
AOP 클래스 상단에 @Aspect , @Component 어노테이션을 선언합니다.
Spring이 자동으로 AOP 컴포넌트로 인식하여 아래 클래스를 실행하게 됩니다.
@Around : 해당 타겟 수행 전/후 를 감싸 어드바이스 내용이 주입되고 실행됩니다. ( "execution(* org.com.test.*(..))" )
joinPoint.proceed() : 전체 메소드(AOP 적용된) 를 실행하고 리턴 데이터를 받는 메서드
@Component
@Aspect
public class LogTraceAop {
@Around("execution(* org.com.test.*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable
{
try{
//code
return joinPoint.proceed();
}
catch{
//code
}
finally{
//code
}
}
}
스프링 AOP는 프록시 기반의 AOP 구현체이며, 스프링 Bean에만 AOP 적용 가능하다.
프록시 패턴이란?
프록시 객체는 원래 객체를 감싸고 있는 객체이다. 원래 객체와 타입은 동일하다. 프록시 객체가 원래 객체를 감싸서 client의 요청을 처리하게 하는 패턴이다.
프록시 패턴을 쓰는 이유는 접근을 제어하고 싶거나, 부가 기능을 추가하고 싶을 때 사용한다.
EventService라는 interface가 있고,
EventService를 implements 한 TestEventService 가 있다고 하면,
이 코드를 실행시키는 main이 client라고 볼 수 있고, TestEventService가 원래 객체로 볼 수 있다.
TestEventService의 createEvent 메서드와 publishEvent 메서드에 동일한 기능을 하는 코드를 넣고 싶을 때, 각각 메서드에 넣어주는 것은 효율성이 떨어진다.
이럴 때 프록시패턴을 사용한다.
프록시 객체는 원래 객체와 같은 interface를 구현해줘야한다. 원래 객체를 주입받아서, interface의 메서드들을 위임받아 사용하고, 원하는 추가 코드를 넣어주면 된다.
스프링 AOP
이렇게 해주면, 원래 코드에 손을 쓰지 않고도 기능을 추가 할 수는 있지만, 프록시 객체에 중복코드가 발생할 수 있고, 다른 클래스에서도 동일한 기능을 사용하고자 할 때, 매번 코딩을 해줘야하는 부분에서 효율적이지 못하다.
이런 문제를 해결해주는 게, 런타임시, 동적으로 프록시객체를 만들어주는 것인데, 그것이 스프링 AOP이다.
위의 예제로 살펴보면,
1) TestEventService가 Bean으로 등록이되면,
2) 스프링이 AbstractAutoProxyCreator라는 BeanPostProcessor(어떤 Bean이 등록되면, 그 Bean을 가공할 수 있는 life-cycle interface)로 TestEventService라는 Bean을 감싸는 프록시 Bean을 만들어,
3) 그 프록시 Bean을 TestEventService 대신에 등록해준다.
스프링 AOP를 사용해주기 위해서는 pom.xml에 의존성을 추가해주어야한다.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
Aspect 클래스를 만들어준다.
Aspect에는 2가지 정보가 필요하다. pointcut(어디에 적용할 것인지)과 advice(해야할 일, 기능)이다.
public Object logPerf 자체가 하나의 advice이며, 이 advice가 어디에 적용될 것 인지(pointcut) @Around 어노테이션으로 표시해준다.
@Around("execution(* 패키지.interface.메서드)")
execution말고 어노테이션을 만들어서 사용할 수도 있다.
어노테이션 파일을 만들고, @Retention 어노테이션을 CLASS타입으로 설정해준다.
사용할 곳에 만들어준 어노테이션을 붙여준다.
Aspect 클래스의 @Around 어노테이션에 execution대신 @annotation을 사용한다.
execution과 @annotation 대신 bean을 사용할 수도 있다.
@Around 어노테이션은 강력한 편이고, 간단히 사용할 때는 @Before, @AfterReturning, @AfterThrowing 등을 사용해도 된다.
'Study > 개발일지' 카테고리의 다른 글
[백엔드온라인TIL] java 학습 45일차 (0) | 2023.07.27 |
---|---|
[백엔드온라인TIL] java 학습 44일차 (0) | 2023.07.26 |
[백엔드온라인TIL] java 학습 42일차 (0) | 2023.07.24 |
[백엔드스터디WIL]8주차 학습일지 (0) | 2023.07.21 |
[백엔드온라인TIL] java 학습 41일차 (0) | 2023.07.21 |