@펌글: https://shlee0882.tistory.com/206

 

1. Srping AOP 핵심기능과 부가기능

 

- 핵심 기능(Core Concerns): 업무 로직을 포함하는 기능

- 부가기능(Cross-cutting Concerns): 핵심 기능을 도와주는 부가적인 기능(로깅, 보안)

 

2. AOP란?

객체지향의 기본원칙을 적용하여도 핵심기능에서 부가기능을 분리해서 모듈화하는 것은 매우 어렵다.

AOP는 애플리케이션에서의 관심사의 분리(기능의 분리), 핵심적인 기능에서 부가적인 기능을 분리한다.

분리한 부가기능을 Aspect 라는 독특한 모듈형태로 만들어서 개발하는 방법이다.

 

- OOP를 적용하여도 핵심기능에서 부가기능을 쉽게 분리된 모듈로 작성하기 어려운 문제점을 AOP 가 해결해준다고 볼 수 있다.

- AOP는 부가기능을 Aspect 로 정의하며, 핵심기능에서 부가기능을 분리함으로써 핵심기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 개념이다.

 

3. Aspect 란?

- Aspect는 부가기능을 정의한 코드인 Advice 와 Advice 를 어디에 적용할지를 결정하는 PointCut 을 합친 개념이다.

Aspect = Advice + PointCut

- AOP 개념을 적용하면 핵심기능 코드 사이에 침투된 부가기능을 독립적인 Aspect 로 구분해 낼 수 있다.

- 구분된 부가기능 Aspect를 런타임 시에 필요한 위치에 동적으로 참여할 수 있다.

 

4. AOP 용어 정리

  • Target

- 핵심 기능을 담고 있는 모듈로 타겟은 부가기능을 부여할 대상이 된다.

  • Advice

- 타켓에 제공할 부가기능을 담고 있는 모듈이다.

  • Join Point

- 어드바이스가 적용 될 수 있는 위치를 말한다.

- 타겟 객체가 구현한 인터페이스의 모든 메서드는 조인 포인트가 된다.

  • Pointcut

- 어드바이스를 적용할 타겟의 메서드를 선별하는 정규표현식이다.

- 포인트컷 표현식은 execution 으로 시작하고 메서드의 Signature 를 비교하는 방법을 주로 이용한다.

  • Aspect

- AOP의 기본 모듈이다.

- Aspect = Advice + Pointcut

- 싱글톤 형태의 객체로 존재한다.

  • Advisor

- Advisor = Advice + Pointcut

- Spring AOP에서만 사용되는 특별한 용어이다.

  • Weaving

- Pointcut 에 의해서 결정된 타겟의 Joinpoint 에 부가기능(Advice)를 삽입하는 과정을 뜻한다.

- AOP가 핵심기능(타겟)의 코드에 영향을 주지 않으면서 필요한 부가기능(Advice)를 추가할 수 있도록 해주는 핵심적인 처리과정이다.

 

5. Spring AOP 특징

1. Spring은 프록시 기반 AOP를 지원한다.

- Spring 은 타겟 객체에 대한 프록시를 만들어서 제공한다.

- 타겟을 감싸는 프록시는 실행시간(Runtime)에 생성된다.

- 프록시는 어드바이스를 타겟 객체에 적용하면서 생성되는 객체이다.

 

2. Proxy 가 호출을 가로챈다(Intercept)

- 프록시는 타겟 객체에 대한 호출을 가로챈 다은 어드바이스의 부가기능 로직을 수행하고 난 후에 타겟의 핵심기능 로직을 호출한다.(전처리 어드바이스)

- 타겟의 핵심기능 로직 메서드를 호출한 후에 부가기능을 수행하는 경우도 있다.(후처리 어드바이스)

 

3. Spring AOP 는 메서드 조인 포인트만 지원한다.

- Spring은 동적 프록시를 기반으로 AOP를 구현하므로 메서드 조인포인트만 지원한다.

- 핵심기능(Target)의 메서드가 호출되는 런타임 시점에만 부가기능(Advice)를 적용할 수 있다.

- 반면, AspectJ 같은 고급 AOP 프레임워크를 사용하면 객체의 생성, 필드값의 조회와 조작, static 메서드 호출 및 초기화 등의 다양한 작업에 부가기능을 적용할 수 있다.

 

6. Spring AOP의 구현 방식

1. XML 기반의 POJO 클래스를 이용한 AOP 구현

- 부가기능을 제공하는 Advice 클래스를 작성한다.

- XML 설정 파일에 <aop:config> 를 이용해서 Aspect 를 설정한다.

(즉, Advice 와 Pointcut 을 설정함)

 

2. @Aspect 어노테이션을 이용한 AOP 구현

- @Aspect 어노테이션을 이용해서 부가기능을 제공하는 Aspect 클래스를 작성한다.

- 이 때, Aspect 클래스는 Advice 를 구현하는 메서드와 Pointcut 을 포함한다.

 

7. Advice 의 종류

  • Around 어드바이스

타겟의 메서드가 호출되기 이전(before) 시점과 이후(after) 시점에 모두 처리해야 할 필요가 있는 부가기능을 정의한다.

-> Joinpoint 앞과 뒤에 실행되는 Advice

 

  • Before 어드바이스

타겟의 메서드가 실행되기 이전(before) 시점에 처리해야 할 필요가 있는 부가기능을 정의한다.

-> Joinpoint 앞에 실행되는 Advice

 

  • After Returning 어드바이스

타겟의 메서드가 정상적으로 실행된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능을 정의한다.

-> Joinpoint 메서드 호출이 정상적으로 종료된 뒤에 실행되는 Advice

 

  • After Throwing 어드바이스

타겟의 메서드가 예외를 발생된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능을 정의한다.

-> 예외가 던져 질 때 실행되는 Advice

 

8. Advice 를 정의하는 태그

  • <aop:before>

- 메서드 실행전에 적용되는 어드바이스를 정의한다.

 

  • <aop:after-returning>

- 메서드가 정상적으로 실행 된 후에 적용되는 어드바이스를 정의한다.

 

  • <aop:after-throwing>

- 메서드가 예외를 발생시킬 때 적용되는 어드바이스를 정의한다.

- try-catch 블록에서 catch 블록과 비슷하다.

 

  • <aop:after>

- 메서드가 정상적으로 실행되는지 또는 예외를 발생시키는지 여부에 상관없이 어드바이스를 정의한다.

- try-catch-finally 에서 finally 블록과 비슷하다.

 

  • <aop:around>

- 메서드 호출 이전, 이후, 예외발생 등 모든 시점에 적용 가능한 어드바이스를 정의한다.

 

9. JoinPoint 인터페이스

- JoinPoint 는 Spring AOP 혹은 AspectJ에서 AOP가 적용되는 지점을 뜻한다.

- 해당 지점을 AspectJ에서 JoinPoint라는 인터페이스로 나타낸다.

 

* JoinPoint 메서드

getArgs() 메서드 아규먼트를 반환한다.
getThis() 프록시 객체를 반환한다.
getTarget() 대상 객체를 반환한다.
getSignature() Advice 또는 메서드의 설명(description)을 반환한다.
toString() Advice 되는 메서드의 설명을 출력한다.
블로그 이미지

uchacha

개발자 일지

,

- @참고: https://stackoverflow.com/questions/38777670/querydsl-paging-functionality

 

spring에서 Querydsl을 연동하여 paging을 하는 기능이 있어서 나중에 사용하면 좋을 것 같아 여기에 메모를 남겨놓는다.

org.springframework.data.jpa.repository.support;
org.springframework.data.domain.PageImpl;
org.springframework.data.domain.Pageable;

Querydsl querydsl = new Querydsl(entityManager, (new PathBuilderFactory()).create(<EntityClass>.class));
JPQLQuery<?> query = new JPAQuery<>(entityManager);

//TODO: prepare your query here 

//Get the count
Long totalElements = query.fetchCount();

//Apply the pagination
List<?> result = querydsl.applyPagination(pageable, query).fetch();

//return a paged response
return new PageImpl<>(result, pageable, totalElements);
블로그 이미지

uchacha

개발자 일지

,
블로그 이미지

uchacha

개발자 일지

,

- @참고: https://mangkyu.tistory.com/75

 

Spring Bean 을 등록할 때 사용하는 어노테이션에 대해 

1. @Configuration @Bean 을 사용하는 경우와, 2. @Component를 사용하는 경우를 나누어 설명하고자 한다.

1. 은 

  • 초기 설정을 하기 위해 활용할 때

사용하는 것으로 bean을 등록하는 Config 클래스에 @Configuration을 붙이고, Config 클래스의 bean을 생성하는 메소드에 @Bean을 붙인다.

 

2. 는

  • 개발자가 직접 개발한 클래스를 bean으로 등록하고자 할 때

등록하고자 하는 class 위에 @Component 어노테이션을 붙이고, 상위 패키지 실행 클래스인 Main, App 클래스에서 @ComponentScan을 통해서 탐색범위를 지정하여 실행한다. 다만 SpringBoot의 경우에는 @SpringBootConfiguration 하위에 @ComponentScan이 기본적으로 포함되어 있어 별도의 설정이 필요 없다.

 

블로그 이미지

uchacha

개발자 일지

,

- @참고: javannspring.tistory.com/231

 

Spring Framework 실행순서

사전지식 POJO 스프링의 특징 중 하나 평범한 옛날 자바 객체 Not POJO = Servlet의 특징 javax.servlet, javax.servlet.http 패키지를 import해야 한다. Servlet, Generic Servlet, HttpServlet 중 하나를 상속해..

javannspring.tistory.com

 

Spring 실행 순서

spring framework cycle. ref: https://javannspring.tistory.com/231

 

1. web.xml을 로딩

2. web.xml에 등록된 ContextLoaderListner 생성

3. ContextLoaderListner가 root-context.xml을 로딩

4. root-context.xml에서 web 제외한 component를 스캔 및 생성

5. 6. 클라이언트 요청에 의해 DispatcherServlet 생성

7. DispatcherServlet이 servlet-context.xml을 로딩

8. servlet-context.xml에서 web 관련 component를 스캔 및 생성

 

Spring 구동 순서

 

Spring Framework 구동 순서. ref. https://javannspring.tistory.com/231

 

블로그 이미지

uchacha

개발자 일지

,
블로그 이미지

uchacha

개발자 일지

,

- @참고 : https://sup2is.tistory.com/70

- @참고2 : https://gdtbgl93.tistory.com/164

에러 내역

No qualifying bean of type [javax.servlet.ServletContext] found for dependency

원인

junit이 돌아갈때는 javax.servlet 패키지에 대한 의존관계를 아무도 해결해주지 않기때문에 스프링 컨테이너가 올라가는 시점에 에러가 난다.

해결법

@WebAppConfiguration 어노테이션 사용

 

spring doc
The presence of 
@WebAppConfiguration on a test class indicates that a WebApplicationContext should be loaded for the test using a default for the path to the root of the web application. 

테스트 클래스에 @WebAppConfiguration이 있으면 웹 응용 프로그램의 루트에 대한 경로에 대한 기본값을 사용하여 테스트를 위해 WebApplicationContext를로드해야 함을 나타냅니다. 

 

에러 내역

java.lang.NoClassDefFoundError: javax/servlet/SessionCookieConfig

원인

서블릿 버전 3.1 이하에서는 SessionCookieConfig 클래스를 찾지 못하는 오류가 발생한다.

해결법

서블릿 jar 파일을 3.1 버전으로 업데이트

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

 

블로그 이미지

uchacha

개발자 일지

,

- @참고 : https://www.baeldung.com/spring-response-header

 

How to Set a Header on a Response with Spring 5 | Baeldung

Learn how to set a header on a specific response or on all response in Spring.

www.baeldung.com

1. 개관

이 빠른 튜토리얼에서, 비 반응형 엔드포인트 또는 스프링 5 WebFlux 프레임워크를 사용하는 API를 위한 서비스 응답에 헤더를 설정하는 다양한 방법을 살펴 볼 것이다.

이전 포스트에서 이 프레임워크에 대해 더 많은 정보를 찾을 수 있다.

2. 논-리액티브 구성 요소의 헤더

하나의 응답에 대해 헤더를 설정하는 걸 원한다면 HttpServletResponse 또는 ResponseEntity 오브젝트를 사용 할 수 있다. 반면에, 우리의 목표가 전체 또는 다수의 응답에 필터를 추가하는 것이라면, 필터를 구성해야 한다.

2.1. HttpServletResponse 사용

단순히 HttpServletResponse 객체를 REST 엔드포인트에 인수로 추가한 다음, addHeader() 메소드를 사용하면 된다.

@GetMapping("/http-servlet-response")
public String usingHttpServletResponse(HttpServletResponse response) {
	response.addHeader("Baeldung-Example-Header", "Value-HttpServletResponse");
    return "Response with header using HttpServletResponse";
}

예제에 보이는 것처럼, 응답 객체를 반환 할 필요는 없다.

2.2. ResponseEntity 사용

이 경우에, ResponseEntity 클래스에 의해 제공되는 BodyBuilder를 사용하자.

@GetMapping("/response-entity-builder-with-http-headers")
public ResponseEntity<String> usingResponseEntityBuilderAndHttpHeaders() {
	HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("Baeldung-Example-Header", "Value-ResponseEntityBuilderWithHttpHeaders");
    
    return ResponseEntity.ok()
    	.headers(responseHeaders)
        .body("Response with header using ResponseEntity");
}

HttpHeaders 클래스는 가장 일반적인 헤더를 설정하기 위한 많은 편리한 메소드를 많이 제공한다. GitHub repo에 이러한 요점을 보여주는 더 많은 예제를 볼 수 있다.

 

2.3. 모든 응답에 헤더 추가

이제 많은 엔드포인트에 특정 헤더를 설정하려고 한다고 생각해보자. 

물론, 이전 코드를 각각의 매핑 메소드에 복제해야 한다면 실망스러울 것이다.

이를 달성하기 위한 더 좋은 방법은 서비스에 필터를 구성하는 것이다.

@WebFilter("/filter-response-header/*")
public class AddResponseHeaderFilter implement Filter {
	
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    	HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Baeldung-Example-Filter-Header", "Value-Filter");
        chain.doFilter(request, response);
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    	// ...
    }
    
    @Override
    public void destroy() {
    	// ...
    }
}

@WebFilter 어노테이션은 이 필터가 적용될 url패턴을 나타내는 수 있게 한다.

아래와 같이 필터를 스프링이 검색 할 수 있게 하려면 스프링 어플리케이션 클래스에 @ServletComponentScan 어노테이션을 추가해야 한다.

@ServletComponentScan
@SpringBootApplication
public class ResponseHeadersApplication {
	
    public static void main(String[] args) {
    	SpringApplication.run(ResponseHeaderApplication.class, args);
    }
}

@WebFilter에 의해 제공되는 기능이 필요하지 않다면 필터 클래스에서 @Component 어노테이션을 사용해 이 마지막 단계를 피할 수 있다.

3. 반응형 엔드포인트를 위한 헤더

다시, ServletHttpResponse, ResponseEntity 또는 ServerResponse(기능적 엔드포인트의 경우) 클래스 및 인터페이스를 사용하여 단일 엔드포인트 응답에 헤더를 어떻게 설정하는지 볼 것이다.

모든 응답에 헤더를 추가하기 위해 스프링 5 WebFilter를 어떻게 구현하는지 배울 것이다.

3.1. ServerHttpResponse 사용

이 접근법은 HttpServletResponse 부분과 상당히 비슷하다.

@GetMapping("/server-http-response")
public Mono<String> usingServerHttpResponse(ServerHttpResponse response) {
	response.getHeader().add("Baeldung-Example-Header", "Value-ServerHttpResponse");
    return Mono.just("Response with header using ServerHttpResponse");
}

3.2. ResponseEntity 사용

비 반응형 엔트포인트와 마찬가지로 ResponseEntity 클래스를 사용할 수 있다.

@GetMapping("/server-http-response")
public Mono<ResponseEntity<String>> usingResponseEntityBuilder() {
	String responseHeaderKey = "Baeldung-Example-Header";
    String responseHeaderValue = "Value-ResponseEntityBuilder";
    String responseBody = "Response with header using ResponseEntity (builder)";
    
    return Mono.just(ResponseEntity.ok()
    	.header(responseHeaderKey, responseHeaderValue)
        .body(responseBody));
}

3.3. ServerResponse 사용

마지막 두 하위 섹션에 소개된 클래스와 인터페이스는 @Controller가 달린 클래스에서 사용될 수 있으나 새로운 스프링 5 기능적 웹 프레임워크에는 적합하지 않다.

HandlerFunction에 대한 헤더를 설정하고 싶다면 ServerResponse 인터페이스에 손을 대야 한다.

public Mono<ServerResonse> userHandler(final ServerRequest request) {
	return ServerResponse.ok()
    	.header("Baeldung-Example-Header", "Value-Handler")
        .body(Mono.just("Response with header using Handler"), String.class);
}

3.4. 모든 응답에 헤더를 추가

마지막으로, 스프링 5는 서비스가 검색한 모든 응답에 헤더를 설정하는 WebFilter 인터페이스를 제공한다.

@Component
public class AddResponseHeaderWebFilter implements WebFilter {
	
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    	exchange.getResponse()
        	.getHeaders()
            .add("Baeldung-Example-Filter-Header", "Value-Filter");
		return chain.filter(exchange);
    }
}

4. 결론

결론적으로, 응답에 헤더를 설정하는 다양한 다른 방법을 배웠고, 또한 단일 엔드포인트에 설정하길 원하거나 모든 rest API를 구성하길 원하면, 반응형 스택에 마이그레이션하더라도, 이제 우리는 그 모든 것을 할 지식을 갖고 있다.

항상 그래왔듯, 비 반응형과 스프링 5 명세 기능을 사용한 것 둘 다 모든 예제는 깃헙 저장소에 있다.

블로그 이미지

uchacha

개발자 일지

,