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 |
๋๊ธ