SpringBoot Part2 (5)
AoP - ๊ด์  ์งํฅ ํ๋ก๊ทธ๋๋ฐ
๊ธฐ๋ฅ,๊ด์ฌ ์งํฅ์  ํ๋ก๊ทธ๋๋ฐ
Cross Cutting Concern

https://www.codejava.net/frameworks/spring/understanding-spring-aop
: ์ค๋ณต์ฝ๋๋ฅผ ์ผ์ผํ ์ฌ์ฉํ๋ ๊ฒ์ด ์๋ ํ๋จ์ผ๋ก ๊ฑธ์ณ์ ์ ์ฉํด์ผํ๋ ๋ถ๊ฐ๊ธฐ๋ฅ(Cross Cutting Concern)์ ๋ถ๋ฆฌํด์ ๋ฐฉ์
ํต์ฌ๋ก์ง์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ฝ๊ฒ ์ถ๊ฐํ ์ ์๊ฒํ๋ค.
class ๊ณ์ข์ด์ฒด์๋น์ค {
		method ์ด์ฒด() {
				AAAA
				๋น์ฆ๋์ค ๋ก์ง
				BBBB
		}
		method ๊ณ์ขํ์ธ() {
				AAAA
				๋น์ฆ๋์ค ๋ก์ง
				BBBB
		}
}
class ๋์ถ์น์ธ์๋น์ค {
		method ์น์ธ() {
				AAAA
				๋น์ฆ๋์ค ๋ก์ง
				BBBB
		}
}
๋น์ง๋์ค ๋ฉ์๋๋ ๋น์ง๋์ค ํต์ฌ๋ก์ง์๋ง ์ง์คํ๊ณ , ๋ถ๊ฐ ๊ธฐ๋ฅ(AAA, BBB)์ ๋ณ๋๋ก ๋นผ์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ด๋นํ๋ ๋ชจ๋์์ ๋ด๋นํ๋ค.
AoP ์ ์ฉ ๋ฐฉ๋ฒ

์์ ์ ๋ฐ๋ผ ํฌ๊ฒ 3๊ฐ์ง ๊ด์ ์ ๋๋๋ค.
- ์ปดํ์ผ ์์  → AoP ํ๋ ์์ํฌ๊ฐ ์์คํ์ผ์ ์ปดํ์ผํ๊ธฐ์ ์ ๊ณตํต ๊ตฌํ ์ฝ๋๋ฅผ ์์ค์ ์ฝ์ ํ๋ ๋ฐฉ์
 - ํด๋์ค ๋ก๋ฉ ์์ 
 - ๋ฐํ์ ์์  → Spring์์ ์ ๊ณตํ๋ AoP ๋ฐฉ์, proxy๋ฅผ ์ด์ฉํ์ฌ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋์ํ๊ฒ ํ๋ ๋ฐฉ์
 
Spring AoP - AoP Proxies

- JDK Proxy (interface based → ๋ค์ด๋๋ฏน ํ๋ก์)
 - CGLib Proxy (class based)
 
Spring Proxies
JDK proxy ๊ธฐ๋ฒ → Spring AoP์ ๋ด๋ถ ๋์
// ํ๊ฒ class
class CalculatorImpl implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}
interface Calculator {
    int add(int a,int b);
}
// invokation ํธ๋ค๋ฌ -> targat ๊ฐ์ฒด๋ฅผ ํธ์ถ
class LoggingInvocationHandler implements InvocationHandler{
    private static final Logger log = LoggerFactory.getLogger(LoggingInvocationHandler.class);
    private final Object target;
    LoggingInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("{} executed", method.getName());
        return method.invoke(target,args);
    }
}
public class JdkProxyTest {
    private static final Logger log = LoggerFactory.getLogger(JdkProxyTest.class);
    public static void main(String[] args) {
        Calculator calculator = new CalculatorImpl();
				// proxy ๊ฐ์ฒด ๋ง๋ค๊ธฐ
        Calculator proxyInstance = (Calculator) Proxy.newProxyInstance( 
                LoggingInvocationHandler.class.getClassLoader(), //ClassLoader
                new Class[]{Calculator.class}, // target ์ธํฐํ์ด์ค
                new LoggingInvocationHandler(calculator)); // target์ ์ ๋ฌํ invoke ํธ๋ค๋ฌ
        var res = proxyInstance.add(1,2);
        log.info("Add -> {} ",res);
    }
}
Spring AOP
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@AspectJ
: JDK proxy๋ฅผ ์ด์ฉํ์ฌ ๋ฐํ์์ AoP ์ ์ฉ
์ฃผ์์ฉ์ด

- ํ๊ฒ(Target) - CustomerService
- ํต์ฌ ๊ธฐ๋ฅ์ ๋ด๊ณ ์๋ ๋ชจ๋๋ก์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ๋ถ์ฌํ ๋์
 - AoP๋ฅผ ์ ์ฉํ ๋์
 
 - ์กฐ์ธํฌ์ธํธ(Join Point) - createCustmer, getCustmer,..
- ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ฉํ ์ ์๋ ์ง์  (AoP๋ฅผ ์ ์ฉํ ์ ์๋ ์์น)
 - ํ๊ฒ ๊ฐ์ฒด๊ฐ ๊ตฌํํ ์ธํฐํ์ด์ค์ ๋ชจ๋ ๋ฉ์๋
 
 - ํฌ์ธํธ ์ปท(Pointcut)
- ์ด๋๋ฐ์ด์ค(๋ถ๊ฐ๊ธฐ๋ฅ)๋ฅผ ์ ์ฉํ ํ๊ฒ์ ๋ฉ์๋๋ฅผ ์ ๋ณํ๋ ์ ๊ทํํ์
 - ์ฌ๋ฌ ์กฐ์ธํธํฌ์ธํธ ์ค์ ์ด๋ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ฉ์ํฌ์ง ๋ํ๋ธ๋ค.
 - ํฌ์ธํธ์ปท ํํ์์ execution์ผ๋ก ์์ํ๊ณ ๋ฉ์๋์ Signature๋ฅผ ๋น๊ตํ๋ ๋ฐฉ๋ฒ์ ์ฃผ๋ก ์ด์ฉํจ
 
 - ์ ์คํํธ(Aspect)
- ๊ด์ , ๊ธฐ๋ฅ, ๋ถ๊ฐ ๊ธฐ๋ฅ
 - ์ ์คํํธ = ์ด๋๋ฐ์ด์ค + ํฌ์ธํธ์ปท (๋ถ๊ฐ๊ธฐ๋ฅ set)
 - Spring์์๋ Aspect๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํด์ ์ฌ์ฉํฉ๋๋ค.
 
 - ์ด๋๋ฐ์ด์ค(Advice)
- ์ด๋๋ฐ์ด์ค๋ ํ๊ฒ์ ํน์  ์กฐ์ธํธํฌ์ธํธ์ ์ ๊ณตํ ๋ถ๊ฐ๊ธฐ๋ฅ
 - Advice์๋ @Before, @After, @Around, @AfterReturning, @AfterThrowing ๋ฑ์ด ์์ต๋๋ค.
 
 

https://mossgreen.github.io/Spring-Certification-Spring-AOP/
- ์๋น
- ํ์ผ์ ์กฐ์ธ ํฌ์ธํธ์ ์ด๋๋ฐ์ด์ฆ๊ฐ ์ ์ฉ๋๋ ๊ณผ์  (AoP๋ฅผ ์ ์ฉํ๋ ๊ณผ์ )
 
 
์ค์ต
// Aspect ์ ์
@Aspect
@Component
public class LoggingAspect {
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
		// execution(์ ๊ทผ์ง์ ์ ๋ฆฌํดํ์
 ํจํค์ง.๋ฉ์๋(arg))
    @Around("execution(public * org.prgrms.kdt..*.*(..))") // ํฌ์ธํธ์ปท
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Before method called. {}",joinPoint.getSignature().toString());
        var result = joinPoint.proceed();
        log.info("After method called with result => {}",result);
        return result;
    }
}
—
@Configuration
@ComponentScan(basePackages = {"org.prgrms.kdt.voucher","org.prgrms.kdt.aop"})
@EnableAspectJAutoProxy // AoP ์ ์ฉ ์ด๋
ธํ
์ด์
static  class Config{
				....
}
๋ฑ๋ก๋ Bean์ ์ํด์๋ง ์ด๋๋ฐ์ด์ค๊ฐ ๊ฐ๋ฅ
@Around() → ํฌ์ธํธ ์ปท
- execution PCD
 

- ํฌ์ธํธ์ปท ๋ชจ๋ํ
 
public class CommonPointcut {
    @Pointcut("execution(public * org.prgrms.kdt..*.*(..))")
    public void servicePublicMethodPointcut(){};
    @Pointcut("execution(* org.prgrms.kdt..*Repository.*(..))")
    public void repositoryPublicMethodPointcut(){};
}
—
@Aspect
@Component
public class LoggingAspect {
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
    @Around("org.prgrms.kdt.aop.CommonPointcut.repositoryPublicMethodPointcut()") // ํฌ์ธํธ์ปท ํclass ๋ช
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Before method called. {}",joinPoint.getSignature().toString());
        var result = joinPoint.proceed();
        log.info("After method called with result => {}",result);
        return result;
    }
}
- ์ด๋ ธํ์ด์ ์ ์ด์ฉํ ํฌ์ธํธ์ปท
 
// ์ฌ์ฉ์ ์ด๋
ธํ
์ด์
 ๋ง๋ค๊ธฐ
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackTime {
}
—
// Aspect
@Aspect
@Component
public class LoggingAspect {
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
		// TrackTime ์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ class๋ฅผ ํฌ์ธํธ์ปท์ผ๋ก ์ค๋ค.
    @Around("@annotation(org.prgrms.kdt.aop.TrackTime)")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Before method called. {}",joinPoint.getSignature().toString());
        var startTime = System.nanoTime(); // 1 -> 1,000,000,000
        var result = joinPoint.proceed();
        var endTime = System.nanoTime() - startTime;
        log.info("After method called with result => {} and time taken {} nanoseconds",result,endTime);
        return result;
    }
}
—
// ์ด๋
ธํ
์ด์
 ์ ์ฉ
public class MemoryVoucherRepository {
	private  final Map<UUID, Voucher> storage = new ConcurrentHashMap<>();
  
	@Override
	@TrackTime
	public Voucher insert(Voucher voucher) {
	    storage.put(voucher.getVoucherId(),voucher);
	    return voucher;
	}
	....
}
์ค์  AoP๋ฅผ ์ง์  ๋ง๋ค์ด ์ฐ๋ ๊ฒฝ์ฐ๋ ์ข ์ข ์์ง๋ง ๊ฑฐ์ ๊ณตํตํ(ํ๋ ์์ํฌ ํ)์์ ์ด๋ ธํ ์ด์ , aspect๋ฑ์ ๋ง๋ค์ด ์ฃผ๊ณ ๋น์ง๋์ค ๋ก์ง์๋ง ์ง์คํ ์ ์๋๋ก ์งํํ๊ฒ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
ํธ๋์ญ์ 
Spring ํธ๋์ญ์ ์ ์ด๋ ธํ ์ด์  ๊ธฐ๋ฐ์ผ๋ก ํธ๋์ญ์  ๊ด๋ฆฌ์ ๋ํ Aspect๋ฅผ ์ ๊ณตํด์ฃผ๋ ๋์์ด๋ค.
@Configuration
@ComponentScan(basePackages = {"org.prgrms.kdt.voucher","org.prgrms.kdt.aop"})
@EnableAspectJAutoProxy // AoP ์ ์ฉ ์ด๋
ธํ
์ด์
static  class Config{
				....
}
PlatfoemTranactionManager
PlatfoemTranactionManager๋ฅผ ํตํด ํธ๋์ญ์  ๊ด๋ฆฌ๊ฐ ์ด๋ฃจ์์ง๋ค.

DataTranactionManager ํตํด ์ง์  ํธ๋์ญ์  ๋ฑ๋ก
// Bean๋ฑ๋ก
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
    return new DataSourceTransactionManager(dataSource);
}
—
public class CustomerJdbcRepository implements CustomerRepository {
	
		// ์์ฑ์๋ฅผ ํตํด ์ฃผ์
		private final PlatformTransactionManager transactionManager;
		....
		public void testTransaction(Customer customer){
        var transaction = transactionManager.getTransaction(new DefaultTransactionDefinition()); //ํธ๋์ญ์
 ์์ฑ
        try {
            jdbcTemplate.update("UPDATE customers SET name= :name  WHERE customer_id = UUID_TO_BIN(:customerId)",toParamMap(customer));
            jdbcTemplate.update("UPDATE customers SET email= :email WHERE customer_id = UUID_TO_BIN(:customerId)",toParamMap(customer));
            transactionManager.commit(transaction);
        }catch (DataAccessException e) {
            logger.info(e.getMessage());
            transactionManager.rollback(transaction);
        }
    }
}
tranaction template ์ฌ์ฉ
// Bean๋ฑ๋ก
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
    return new DataSourceTransactionManager(dataSource);
}
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager platformTransactionManager){
    return new TransactionTemplate(platformTransactionManager);
}
—
public class CustomerJdbcRepository implements CustomerRepository {
	
		// ์์ฑ์๋ฅผ ํตํด ์ฃผ์
		private final TransactionTemplate transactionTemplate;
		....
		public void testTransaction(Customer customer){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                jdbcTemplate.update("UPDATE customers SET name= :name  WHERE customer_id = UUID_TO_BIN(:customerId)",toParamMap(customer));
                jdbcTemplate.update("UPDATE customers SET email= :email WHERE customer_id = UUID_TO_BIN(:customerId)",toParamMap(customer));
            }
        });
    }
}
@Transactional
spring์์ ์ด๋ ธํ ์ด์ ์ ์ด์ฉํด ํธ๋์ญ์ ์ ์ ๊ณตํ๋ค.(์ ์ธ์  ํธ๋์ญ์  ๊ด๋ฆฌ)
Aop proxy๋ฅผ ํตํด ๋์ํ๋ค.
: service๋จ ์์ ๋ง์ด ์ฌ์ฉํ๊ฒ๋๋ค. ์ผ๋ จ์ ๋์๋ค์ ํ๋๋ก ๋ฌถ๊ธฐ์ํด
public class CustomerServiceImpl implements CustomerService {
    
    private final CustomerRepository customerRepository;
    public CustomerServiceImpl(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }
    @Override
    @Transactional
    public void createCustomers(List<Customer> customers) {
        customers.forEach(customerRepository::insert);
    }
}
ํธ๋์ญ์  ์ ํ
ํน์  ๋ฉ์๋ A ๋ด ํธ๋ ์ น์ ์ด ์ฒ๋ฆฌ๋๋ ๊ณผ์ ์์์ ๋ ๋ค๋ฅธ ๋ฉ์๋ B ํธ๋ ์ ์ ์ด ์ฒ๋ฆฌ๋๋ ๊ณผ์ 

// propagation ์ผ๋ก ๊ฐ์ ๋ณ๊ฒฝํ  ์ ์๋ค.
@Transactional(propagation = Propagation.REQUIRED) // defualt
public void createCustomers(List<Customer> customers) {
    customers.forEach(customerRepository::insert);
}
REQUIRED
: ํ์ฌ ์งํ์ค์ธ ํธ๋ ์ ์ (addCustomer)์ด ์๋ค๋ฉด ์๋ก์ด ํธ๋ ์ ์ ์ด ๋ง๋ค์ด ์ง์ง์๊ณ ํด๋น ํธ๋ ์ ์ ์ ์ฌ์ฉํ๋ค. ์๋ค๋ฉด ์๋ก ๋ง๋ค์ด์ง๋ค.
MANDATORY
: ์ด๋ฏธ ์งํ์ค์ธ ๋ฉ์๋(์์ ๋ฉ์๋)์ ํธ๋ ์ ์ ์ด ์๋ค๋ฉด ์๋ฌ ๋ฐ์. ํธ์ถ์ ์ ๋ฐ๋์ ์งํ ์ค์ธ ํธ๋ ์ ์ ์ด ์์ด์ผํ๋ค.
REQURED_NEW
: ์ด๋ฏธ ์งํ์ค์ธ ํธ๋ ์ ์ ์ด ์์ด๋ ํญ์ ์๋ก์ด ํธ๋ ์ ์ (createCustomer, findOne)์ด ์๊ฒจ๋๋ค. ์งํ ์ค์ด๋ ํธ๋ ์ ์ ์ ์ ์ ์ค๋จ๋๋ค.
: ์๋ก์ด ํธ๋ ์ ์ ์์ ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ํด๋น ํธ๋ ์ ์ ์์๋ง rollback์ด ๋๊ณ ์ด์ ์ ํธ๋ ์ ์ (addCustomer)์๋ ์ํฅ์ ์ฃผ์ง์๋๋ค.
SUPPORTES
: ํ์ฌ ์งํ์ค์ธ ํธ๋ ์ ์ (addCustomer)์ด ์๋ค๋ฉด ์๋ก์ด ํธ๋ ์ ์ ์ ๋ง๋ค์ง์๊ณ ํด๋น ํธ๋ ์ ์ ์ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด ํธ๋ ์ ์ ์ ๋ง๋ค์ง์๋๋ค.
NOT_SUPPORTED
: ํ์ฌ ์งํ์ค์ธ ํธ๋ ์ ์ (addCustomer)์ด ์์ด๋ ์๋ก์ด ํธ๋ ์ ์ ์ด ๋ง๋ค์ง์๊ณ ์ ์ ๋ฉ์ถฐ์๋ค๊ฐ ์ข ๋ฃ๊ฐ๋๋ฉด ๋ค์ ์คํ๋๋ค.
ํธ๋์ญ์  ๊ฒฉ๋ฆฌ
ํธ๋ ์ ์ ์ด ์งํ ์ค์ผ๋ ํ์ ํธ๋ ์ ์ ์์ ์์์ ๋ณ๊ฒฝํ ๋, ๊ฐ๋ณ ํธ๋ ์ ์ ์ ๋ ๋ฆฝ์ฑ์ ๋จ๊ณ๋ฅผ ๋๋ ๊ฒ.
@Transactional(isolation = Isolation.DEFAULT) // ์ฌ์ฉํ๋ DBMS์ default๊ฐ์ ์ฌ์ฉํ๊ฒ ๋ค.
public void createCustomers(List<Customer> customers) {
    customers.forEach(customerRepository::insert);
}


https://techannotation.wordpress.com/2014/12/04/5-minutes-with-spring-transaction-isolation-level/
dirty reads
: ์์ง ์์ ์ค์ธ ๋ฐ์ดํฐ(commit ๋์ง์์ ๋ฐ์ดํฐ)๋ฅผ ๋ค๋ฅธ ํธ๋ ์ ์ ์์ ์ฝ์ ์ ์๋ค. rollback์ด ๋ฐ์ํ์ ๋ ๋ฌธ์ .
non-repeatable
: ํ ํธ๋ ์ ์  ๋ด์์ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ๋๋ฒ ์ฌ์ฉํ ๋, ๋ค๋ฅธ ํธ๋ ์ ์ ์ด ๊ทธ ์ฌ์ด ๊ฐ์ ์์ ํ๋ฉด ์ผ๊ด๋์ง์์ ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค.
phantom reads
: ํ ํธ๋์ญ์ ์์์ ์ผ์  ๋ฒ์์ ๋ ์ฝ๋๋ฅผ ์ฌ๋ฌ๋ฒ ์ฝ์ด์ฌ๋, ์ฒซ๋ฒ์งธ ์ฟผ๋ฆฌ์์ ์๋ ์ ๋ น์ ๋ ์ฝ๋๊ฐ ๋๋ฒ์งธ ์ฟผ๋ฆฌ์์ ๋ฐ์ํ๋ค.
์ถ์ฒ - ํด๋ฆฌ : SpringBoot Part1
'Back-end ๋ฐ๋ธ์ฝ์ค > week 03 - 05 TIL (Spring)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [TIL] 221115 - SpringBoot Part3 : Spring MVC - jsp, Thymeleaf (0) | 2022.11.22 | 
|---|---|
| [TIL] 221114 - SpringBoot Part3 : ์น ๊ธฐ์ Overview, Servelt (0) | 2022.11.22 | 
| [TIL] 221110 - SpringBoot Part2 : Embedded DB, Named Parameter Template, ํธ๋์ญ์  (0) | 2022.11.11 | 
| [TIL] 221109 - SpringBoot Part2 : Spring์ JDBC์ง์ (0) | 2022.11.10 | 
| [TIL] 221108 - SpringBoot Part2 : JDBC (0) | 2022.11.08 | 
										
									
										
									
										
									
										
									
๋๊ธ