다음 사이트의 글을 내 맘대로 번역한 글입니다

다운로드

myweblearner.com/springboot_2_aop?fbclid=IwAR3EGtIE2ig2QqvCErXp_Hg26V0HYgM0A18EDdCshE2-FvCicQx5FM6aIsg

 

Spring Boot and Spring AOP - Aspect Oriented Programming

In this tutorial, We are going to learn about using restful web service using spring boot

myweblearner.com

 

Overview

Aspect Oriented Programming (AOP)는 써야하지만 우아하지 않은 코드, 비지니스 로직들을 줄여주고 가독성을 유지시켜주는데 도움이 된다. 어떤 메소드를 실행하기 전 또는 후에 특정 task를 실행하고 싶다면 AOP를 사용하면 된다. 내부적으로 Spring은 proxy를 사용해서 실제 메소드 실행 클래스를 둘러싼 wrapper를 호출한다

  • JointPoint : 대상 메소드
  • PointCut : joint point를 매칭하기 위해 사용하는 predicate
  • Advice : PointCut call 전/후에 실행할 Code/Task
  • Aspect : pointcut과 advice의 묶음
  • Weaving : aspect가 실행되는 시점

 

About Demo

이번 demo에서는 restful webservice에서 Aspect를 사용하는 방법을 보여준다. 전체 package, 특정 class/method처럼 PointCut을 정의하는 방법에는 여러가지가 있다. 여기서는 AOP기반의 @annotation을 사용한다. 2개의 AOP를 만들어서 , 하나는 메소드의 전/후에 로그를 출력하고 다른 하나는 method의 성능을 계산하고 출력한다. DB 대신 List를 사용한다

 

Prerequisites

  • Maven 3.3.9
  • Java 8
  • STS (or Eclipse)

pom.xml

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

 

Model

import lombok.Data;

@Data
public class Employee {
	private String empId;
	private String empName;
	private String deptName;
	
}

 

RestControler

CRUD를 지원한다

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.budnamu.sample.aop.model.Employee;
import com.budnamu.sample.aop.service.EmployeeService;

@RestController
@RequestMapping("/aop")
public class EmployeeController {

	@Autowired
	private EmployeeService dummyService;
	
	// Get the employee detail based on employee id

	@GetMapping("/employee/{empid}")
	public Employee getEmployee(@PathVariable("empid") String empId) {
		return dummyService.getEmployee(empId);
	}
	
	//post the new employee details to backend

	@PostMapping("/employee")
	public String newEmployee(@RequestBody Employee employee) {
		return dummyService.addEmployee(employee);
	}
	
	// update the employee depart using emp id

	@PutMapping("/employee/{empid}")
	public String updateEmployee(@PathVariable("empid") String empId, @RequestParam("deptname") String deptName) {
		return dummyService.updateEmployeeDept(empId, deptName);
	}
	
	// Delete the employee

	@DeleteMapping("/employee/{empid}")
	public String deleteEmployee(@PathVariable("empid") String empId) {
		return dummyService.deleteEmployee(empId);
	}
	

}

 

Custom Annotation

MethodLogger.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Component;

@Component
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLogger {

}

CalculatePerformance.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Component;

@Component
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CalculatePerformance {

}

 

Aspect Definition

@Aspect와 @Configuration annotation을 사용해서 aspect를 정의한다.

여기에서는 적용할 대상, 적용시점 그리고 무엇을 실행할 것인가에 대한 정보를 정의한다.

예를 들어 beforeMethodStart 메소드는 메소드를 호출하기 전에 실행하며 그 대상은 MethodLogger annotation이 선언된 메소드를 대상으로 한다.

import java.time.LocalDateTime;
import java.time.LocalTime;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Configuration;

import static java.time.temporal.ChronoUnit.SECONDS;;

@Configuration
@Aspect
public class LoggerAspect {

	@Before("@annotation(com.budnamu.sample.aop.aop.MethodLogger)")
	public void beforeMethodStart(JoinPoint point) {
		System.out.println("Method " + point.getSignature().getName() + " Started at " + LocalDateTime.now());

	}

	@After("@annotation(com.budnamu.sample.aop.aop.MethodLogger)")
	public void afterMethodStart(JoinPoint point) {
		System.out.println("Method " + point.getSignature().getName() + " Ended at " + LocalDateTime.now());

	}

	@Around("@annotation(com.budnamu.sample.aop.aop.CalculatePerformance)")
	public void calculate(ProceedingJoinPoint point) {
		LocalTime startTime = LocalTime.now();
		try {
			point.proceed();
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			LocalTime endTime = LocalTime.now();
			System.out.println("Processing time of Method " + point.getSignature().getName() + " -> "
					+ SECONDS.between(startTime, endTime));
		}	

	}

}

 

Service

annotaion을 적용할 메소드에 선언하면  aspect가 알아서 처리해 준다

getEmployee 메소드를 보면 @CalculatePerformance가 선언되어 있는데 이것은 메소드 호출 전후에 적용된다

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import org.springframework.stereotype.Component;

import com.budnamu.sample.aop.aop.CalculatePerformance;
import com.budnamu.sample.aop.aop.MethodLogger;
import com.budnamu.sample.aop.model.Employee;

@Component
public class EmployeeServiceImpl implements EmployeeService {

	private static List<Employee> employeeLst = new ArrayList<>();

	@Override
	@CalculatePerformance
	public Employee getEmployee(String empId) {
		Stream<Employee> empStream = employeeLst.stream().filter(emp -> {
			return emp.getEmpId().equalsIgnoreCase(empId);
		});
		sleepForSeconds(5000L);
		return empStream.findAny().get();
	}

	@Override
	@MethodLogger
	public String addEmployee(Employee e) {
		employeeLst.add(e);
		sleepForSeconds(3000L);
		return "Success";
	}

	@Override
	@MethodLogger
	public String updateEmployeeDept(String empId, String deptName) {
		employeeLst.stream().forEach(emp -> {
			if (emp.getEmpId().equalsIgnoreCase(empId)) {
				emp.setDeptName(deptName);
			}
		});
		sleepForSeconds(2000L);

		return "SUCCESS";
	}

	@Override
	@MethodLogger
	public String deleteEmployee(String empId) {
		System.out.println("Employee Id -->" + empId);
		if (employeeLst.removeIf(emp -> emp.getEmpId().equalsIgnoreCase(empId)))
			return "SUCCESS";
		else
			return "FAIURE";
	}

	public void sleepForSeconds(Long period) {
		try {
			Thread.sleep(period);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	static {
		Employee emp1 = new Employee();
		Employee emp2 = new Employee();
		// employee 1
		emp1.setEmpId("A1234");
		emp1.setEmpName("Sam");
		emp1.setDeptName("IT");
		// employee 2
		emp2.setEmpId("B1234");
		emp2.setEmpName("Tom");
		emp2.setDeptName("Finance");

		employeeLst.add(emp1);
		employeeLst.add(emp2);
	}

}

등록

로그

 

 

 

 

aop.zip
0.07MB

이 글은 다음의 글을 내 맘대로 번역한 글입니다

www.baeldung.com/spring-boot-repackage-vs-mvn-package

 

Difference Between spring-boot:repackage and Maven package | Baeldung

Learn the difference between mvn:package and spring-boot:repackage

www.baeldung.com

 

 

1. Overview

Apach Maven은 널리 사용되는 프로젝트 의존성 관리 tool이자 project building tool이다. 지난 몇 년간 Spring Boot는 application 개발을 위한 아주 인기가 많은 프레임워크가 되었다. Spring boot Maven Plugin이 있는데 Maven에서 Spring Boot supoort를 제공한다

 

 

application을 JAR or WAR artifact 형태로 Maven 을 이용해서 패키징하고 싶은 때가 있다. 이 때 mvn package를 사용하면 된다. 하지만 Spring Boot Maven Plugin은 repackage goal로 출고되는데 mvn command라고 한다.

 

가끔 두개의 명령어가 혼란스럽다. 이번 toturial에서는 mvn pckagespring-boot:repackage의 차이점에 대해서 설명한다

 

 

 

2. A Spring Boot Application Example

맨 처음 바로 Spring Boot application 을 만든다

@SpringBootApplication
public class SpringBootArtifacts2Application {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootArtifacts2Application.class, args);
	}

}

그리고 application이 빌드되고 기동된다는 것을 검증하기 위해 간단한 REST endpoint를 생성한다

@RestController
public class DemoRestController {
    @GetMapping(value = "/welcome")
    public ResponseEntity welcomeEndpoint() {
        return ResponseEntity.ok("Welcome to Baeldung Spring Boot Demo!");
    }
}

 

3. Maven package Goal

Spring Boot application을 빌드하려면 spring-boot-starter-web 의존성만 있으면된다.

<artifactId>spring-boot-artifacts-2</artifactId>
<packaging>jar</packaging>
...
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
...

하지만 STS를 이용해서 Spring Boot 프로젝트를 선택하면 아래처럼 maven-plugin도 자동으로 적용된다. 일단 이부분은 주석처리를 하고 실행한다

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

 

 

Maven's package goal은 코드를 컴파일하고 패키징한다. 이 예제에서는 JAR format이다

[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------< com.example:spring-boot-artifacts-2 >-----------------
[INFO] Building spring-boot-artifacts-2 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ spring-boot-artifacts-2 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ spring-boot-artifacts-2 ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ spring-boot-artifacts-2 ---
[INFO] Not copying test resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ spring-boot-artifacts-2 ---
[INFO] Not compiling test sources
[INFO] 
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ spring-boot-artifacts-2 ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ spring-boot-artifacts-2 ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.016 s
[INFO] Finished at: 2021-02-03T01:14:27+09:00
[INFO] ------------------------------------------------------------------------

mvn package 명령어를 실행하고 나면 jar 파일이 생성된 것을 확인할 수 있고 JAR 파일 내부를 살펴보자

 

 

위 사진에서 볼 수 있듯이 mvn package 명령어의 결과로 JAR 파일이 만들어졌고 내부에는 resources와 컴파일된 Java class만이 포함되어 있다

실행에 필요한 libs도 없다.

 

이 JAR 파일을 이용해서 다른 프로젝트에 의존성으로 사용할 수도 있다. 하지만 Spring Boot application이면 이 JAR파일을 "java -jar JAR_FILE" 명령어를 이용해서 기동시키지 못한다. runtime 의존성이 포함되어 있지 않기 때문이다. 예를 들자면 web context를 기동시킬 수 있는 servlet container가 없다.

 

"java -jar" 명령어를 이용해서 간단하게 실행시킬려면 fat JAR이 필요하다. 이것을 만들기 위해 Spring Boot Maven Plugin이 필요하다

 

 

4. The Spring Boot Maven Plugin's repackage Goal

spring-boot:repackage가 무엇을 하는지 알아보자

 

 

4.1 Spring Boot Maven Plugin 추가하기

이미 위에서 설명했듯이 기본적으로 적용되어 있다.

 

4.2 spring-boot:repackage Goal 실행

[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------< com.example:spring-boot-artifacts-2 >-----------------
[INFO] Building spring-boot-artifacts-2 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ spring-boot-artifacts-2 ---
[INFO] Deleting D:\workspaces\git\spring-boot-artifacts-2\target
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.4.2:repackage (default-cli) @ spring-boot-artifacts-2 ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.193 s
[INFO] Finished at: 2021-02-03T01:35:19+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.4.2:repackage (default-cli) on project spring-boot-artifacts-2: Execution default-cli of goal org.springframework.boot:spring-boot-maven-plugin:2.4.2:repackage failed: Source file must not be null -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginExecutionException

 

실패했다. 왜냐하면 spring-boot:repackage goal은 존재하는 JAR or WAR archive를 입력소스로 취하고 project runtime 의존성을 전부 final artifact내에 재패키징한다. 이런 방법으로 재패키징 된 artifact는 "java -jar JAR_FILE.jar" 명령어로 실행이 가능하다

 

따라서 먼저 JAR 파일을 만들고 그 후에 spring-boot:repackage goal을 실행해야 한다

[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------< com.example:spring-boot-artifacts-2 >-----------------
[INFO] Building spring-boot-artifacts-2 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ spring-boot-artifacts-2 ---
[INFO] Deleting D:\workspaces\git\spring-boot-artifacts-2\target
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ spring-boot-artifacts-2 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ spring-boot-artifacts-2 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to D:\workspaces\git\spring-boot-artifacts-2\target\classes
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ spring-boot-artifacts-2 ---
[INFO] Not copying test resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ spring-boot-artifacts-2 ---
[INFO] Not compiling test sources
[INFO] 
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ spring-boot-artifacts-2 ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ spring-boot-artifacts-2 ---
[INFO] Building jar: D:\workspaces\git\spring-boot-artifacts-2\target\spring-boot-artifacts-2-0.0.1-SNAPSHOT.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.4.2:repackage (repackage) @ spring-boot-artifacts-2 ---
[INFO] Replacing main artifact with repackaged archive
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.4.2:repackage (default-cli) @ spring-boot-artifacts-2 ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.056 s
[INFO] Finished at: 2021-02-03T01:41:21+09:00
[INFO] ------------------------------------------------------------------------

  

아래 그림에서 볼 수 있듯이 original JAR file과 repackaged JAR file을 볼 수 있다.

 

JAR 파일 내부를 살펴보자

repackaged JAR 파일을 보면 컴파일된 Java classes와 Spring Boot application을 실행하는데 필요한 모든 runtime libraries 가 모두 포함되어 있다. 예를 들어, embedded tomcat library는 BOOT-INF/lib directory에 패키징되어 있다

 

4.3 Maven's package Lifecycle동안 spring-boot:repackage Goal 실행하기

pom.xml에서 Maven lifecycle의 package phase동안 Spring Boot Maven Plugin이 artifact를 재패키징한다는 것을 안다. 다른 말로하면 mvn package를 실행하면 spring-boot:repackage가 자동으로 실행된다다는 것이다

 

 

 

+ Recent posts