AOP(Aspect-Oriented Programming)
๊ด์ ์งํฅ ํ๋ก๊ทธ๋๋ฐ
์ ํ๋ฆฌ์ผ์ด์ ๋ก์ง์ ํฌ๊ฒ ํต์ฌ ๊ธฐ๋ฅ๊ณผ ๋ถ๊ฐ ๊ธฐ๋ฅ์ผ๋ก ๋๋ ์ ์๋๋ฐ, ๋ ์ฝ๋๋ฅผ ๊ฐ์ ๊ณณ์ ์์ฑํ๊ฒ ๋๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
์ค๋ณต๋๋ ๋ถ๊ฐ ๊ธฐ๋ฅ ์ฝ๋๊ฐ ์ฌ๋ฌ ๊ณณ์ ํฉ์ด์ ธ ์กด์ฌ
ํต์ฌ ๊ธฐ๋ฅ๊ณผ ๋ถ๊ฐ ๊ธฐ๋ฅ์ด ์์ฌ ์ฝ๋๊ฐ ๋ณต์กํด์ง
๋ถ๊ฐ ๊ธฐ๋ฅ์ ๋ณ๊ฒฝํ ๋ ํต์ฌ ๊ธฐ๋ฅ ์ฝ๋๋ ํจ๊ป ์์ ํด์ผ ํจ
๋๋ฌธ์ ์ด๋ฅผ ๋ถ๋ฆฌํ๋ ค๋ ์๋๋ฅผ ํ๊ธฐ ์์ํ๊ณ , ๋ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ์ ๋ถ๋ฆฌํ์ฌ ์ค๊ณํ์ฌ ๊ฐ๋ฐ ํ๋ ๋ฐฉ๋ฒ์ ๊ด์ ์งํฅ ํ๋ก๊ทธ๋๋ฐ(AOP)์ด๋ผ๊ณ ํ๋ค.
์ฉ์ด ์ ๋ฆฌ
์ ์คํํธ(Aspect)
์ฌ๋ฌ ๊ฐ์ฒด์ ๊ณตํต์ผ๋ก ์ ์ฉ๋๋ ๊ด์ฌ์ฌ(๋ถ๊ฐ ๊ธฐ๋ฅ)์ ๋ชจ๋ํ(์ด๋๋ฐ์ด์ค + ํฌ์ธํธ์ปท)
์คํ๋ง์์๋
@Aspect
์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํ ํด๋์ค๋ก ๊ตฌํ
์กฐ์ธ ํฌ์ธํธ(Join point)
์ถ์์ ์ธ ๊ฐ๋ ์ผ๋ก, ์ด๋๋ฐ์ด์ค๊ฐ ์ ์ฉ๋ ์ ์๋ ๋ชจ๋ ์ง์ ์ ์๋ฏธ
๋ฉ์๋ ์คํ ์์ , ์์ฑ์ ํธ์ถ ์์ , ํ๋ ๊ฐ ์ ๊ทผ ์์ ๋ฑ ์ ํ๋ฆฌ์ผ์ด์ ์คํ ํ๋ฆ์ ํน์ ์ง์ ๋ชจ๋๊ฐ ํด๋น
์ด๋๋ฐ์ด์ค(Advice)
์กฐ์ธ ํฌ์ธํธ์์ ์คํ๋์ด์ผ ํ ๋ถ๊ฐ ๊ธฐ๋ฅ ๋ก์ง
ํฌ์ธํธ์ปท(Pointcut)
์๋ง์ ์กฐ์ธ ํฌ์ธํธ ์ค์์ ์ด๋๋ฐ์ด์ค๋ฅผ ์ ์ฉํ ์ง์ ์ ์ ๋ณํ๋ ๊ธฐ๋ฅ
AspectJ ํํ์์ ์ฌ์ฉํ์ฌ ๊ตฌ์ฒด์ ์ผ๋ก ์ง์
ํ๊ฒ(Target)
์ด๋๋ฐ์ด์ค๊ฐ ์ ์ฉ๋๋ ๋์ ๊ฐ์ฒด
์ด๋๋ฐ์ด์ (Advisor)
ํ๋์ ์ด๋๋ฐ์ด์ค์ ํ๋์ ํฌ์ธํธ์ปท์ผ๋ก ๊ตฌ์ฑ๋ ๊ฐ์ฒด
์คํ๋ง AOP์์๋ง ์ฌ์ฉ๋๋ ์ฉ์ด
์๋น(Weaving)
ํฌ์ธํธ์ปท์ผ๋ก ๊ฒฐ์ ํ ํ๊ฒ์ ์กฐ์ธ ํฌ์ธํธ์ ์ด๋๋ฐ์ด์ค๋ฅผ ์ ์ฉํ๋ ๊ณผ์
์๋น์ ํตํด ํต์ฌ ๊ธฐ๋ฅ ์ฝ๋์ ๋ณ๊ฒฝ ์์ด ๋ถ๊ฐ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์๋ค
AOP ํ๋ก์(AOP Proxy)
AOP ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด ๋ง๋ ํ๋ก์ ๊ฐ์ฒด
JDK ๋์ ํ๋ก์ / CGLIB
์คํ๋ง์์ ํ๋ก์ ํจํด์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๋ค์ ๋ ๊ฐ์ง๊ฐ ์๋ค.
JDK ๋์ ํ๋ก์
์๋ฐ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๊ณตํ๋ ๊ธฐ์
์ธํฐํ์ด์ค ๊ธฐ๋ฐ์ผ๋ก ํ๋ก์๋ฅผ ์์ฑ(ํ๊ฒ์ด ๊ตฌํํ ์ธํฐํ์ด์ค๊ฐ ์์ด์ผ๋ง ํ๋ก์ ์์ฑ ๊ฐ๋ฅ)
InvocationHandler
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ฌ ํ๋ก์์ ๋์ ์ ์
class TestInvocationHandler implements InvocationHandler {
private TargetInterface target; // Target, ์ ์ฉ์ด ๋ ์ค์ ๊ฐ์ฒด
public TestInvocationHandler(TargetInterface target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("log start");
Object result = method.invoke(target, args); // ์ค์ ๊ฐ์ฒด์ ๋ฉ์๋ ํธ์ถ
log.info("log end");
return result;
}
}
CGLIB
์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ๋ฐ์ดํธ์ฝ๋๋ฅผ ์กฐ์ํ์ฌ ํ๋ก์๋ฅผ ์์ฑ
ํด๋์ค ์์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ก์๋ฅผ ์์ฑํ๋ฏ๋ก ์ธํฐํ์ด์ค ํ์ ์์
ํด๋์ค๋ ๋ฉ์๋์
final
ํค์๋๊ฐ ์์ผ๋ฉด ์์์ด ๋ถ๊ฐ๋ฅํ๊ฑฐ๋ ์ค๋ฒ๋ผ์ด๋ฉ์ด ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์final
ํค์๋๊ฐ ์๋ ํด๋์ค๋ ๋ฉ์๋๋ง ์ ์ฉ ๊ฐ๋ฅMethodInterceptor
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ฌ ํ๋ก์์ ๋์์ ์ ์
class TestMethodInterceptor implements MethodInterceptor {
private TargetImpl target; // Target, ์ ์ฉ์ด ๋ ์ค์ ๊ฐ์ฒด
public TestMethodInterceptor(TargetImpl target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
log.info("log start");
Object result = proxy.invoke(target, args); // ์ค์ ๊ฐ์ฒด์ ๋ฉ์๋ ํธ์ถ
log.info("log end");
return result;
}
}
์์ ์ฝ๋์์ ๋ณผ ์ ์๋ฏ์ด ํ๋ก์ ํจํด์ ๊ตฌํํด์ผํ๋ ์ธํฐํ์ด์ค๊ฐ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ๊ตฌํํ๋๋ฐ ๋ณต์ก์ฑ์ ๊ฐ์ง๋๋ฐ, ์คํ๋ง์์๋ ์ด๋ฅผ ์ถ์ํ์์ผ ์ฌ์ฉํ ์ ์๋๋ก Proxy Factory๋ฅผ ์ ๊ณตํ๋ค.
Proxy Factory
Proxy Factory๋ ์คํ๋ง์์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ํต์ผํ์ฌ ์ฌ์ฉํ ์ ์๋๋ก ์ถ์ํํ ๊ฒ์ด๋ค.

์ธํฐํ์ด์ค ์ ๋ฌด ํน์ ์ต์ ์ ๋ฐ๋ผ JDK ๋์ ํ๋ก์์ CGLIB๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก์ ํจํด์ ๊ตฌํํด์ฃผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
Proxy Factory๋ฅผ ์ด์ฉํ ์์ ์ฝ๋
// ๋ถ๊ฐ ๊ธฐ๋ฅ์ ์ ๊ณตํ Advice
class LogTraceAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("log start");
Object result = invocation.proceed(); // ์ค์ ๊ฐ์ฒด์ ๋ฉ์๋ ํธ์ถ
log.info("log end");
return result;
}
}
class ExampleProxyFactory {
public Object getProxy(Object target) {
// Pointcut
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*");
// Advice
LogTraceAdvice advice = new LogTraceAdvice();
// Advisor
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
return new ProxyFactory()
.setTarget(target)
.addAdvisor(advisor)
.getProxy();
}
}
class Main {
public static void main(String[] args) {
Example example = new Example();
ExampleProxyFactory proxyFactory = new ExampleProxyFactory();
Example proxy = (Example) proxyFactory.getProxy(example);
proxy.execute();
}
}
์ด ๋ฐฉ๋ฒ์ผ๋ก ์์ฑ ๋ฐฉ๋ฒ์ ํต์ผ๋์์ง๋ง, ๋ค์๊ณผ ๊ฐ์ ๋จ์ ์ด ์กด์ฌํ๋ค.
์ ์ฉํ ๋ฉ์๋๋ฅผ ํ๋ํ๋ ์๋์ผ๋ก ๋ฑ๋ก ํ์
์ปดํฌ๋ํธ ์ค์บ์ ํ๋ ๊ฒฝ์ฐ ํ๋ก์ ๊ฐ์ฒด ์์ฑ์ด ๋ถ๊ฐ๋ฅ
๋๋ฌธ์ ์ปดํฌ๋ํธ ์ค์บ์ ํฌํจํ ๋ฑ๋ก๋๋ ๋น๋ค์ ๋ํด ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ ์ฉํ๋ ๋น ํ์ฒ๋ฆฌ๊ธฐ ๋ฐฉ๋ฒ์ด ๋ฑ์ฅํ๊ฒ ๋์๋ค.
๋น ํ์ฒ๋ฆฌ๊ธฐ
๋น ํ์ฒ๋ฆฌ๊ธฐ๋ฅผ ์ด์ฉํ๋ฉด ์ปดํฌ๋ํธ ์ค์บ์ ํฌํจํ ๋ฑ๋ก๋๋ ๋น๋ค์ ๋ํด ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ ์ฉํ์ฌ ๋ฑ๋กํ ์ ์๋ค.
// Processor.java
class PackageLogTracePostProcessor implements BeanPostProcessor {
private final String basePackage; // ํ๋ก์ ์ ์ฉ ํจํค์ง
private final Advisor advisor; // Advice + Pointcut
public PackageLogTracePostProcessor(String basePackage, Advisor advisor) {
this.basePackage = basePackage;
this.advisor = advisor;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
String packageName = bean.getClass().getPackageName();
// ํ๋ก์ ์ ์ฉ ๋์ ์ฌ๋ถ ์ฒดํฌ
if (!packageName.startsWith(basePackage)) {
return bean; // ํ๋ก์ ์ ์ฉ ๋์์ด ์๋๋ฉด ์๋ณธ์ ๋ฐํ
}
// ํ๋ก์ ๋์ฑ์ด๋ฉด ํ๋ก์๋ฅผ ๋ง๋ค์ด์ ๋ฐํ
return new ProxyFactory()
.setTarget(bean)
.addAdvisor(advisor)
.getProxy();
}
}
// Config.java
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
class BeanPostProcessorConfig {
@Bean
public PackageLogTracePostProcessor logTracePostProcessor() {
return new PackageLogTracePostProcessor("hello.proxy.app", getAdvisor());
}
@Bean
public DefaultPointcutAdvisor advisor() {
return new DefaultPointcutAdvisor(Pointcut.TRUE, new MyAdvice());
}
}
ํ์ง๋ง ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋๋ผ๋ ํฌ์ธํธ ์ปท(=๋น ํ์ฒ๋ฆฌ๊ธฐ)๊ณผ ์ด๋๋ฐ์ด์ค(=Proxy Factory)๋ฅผ ๋ณ๋๋ก ์ ์ํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ฌ์ ํ ๋ณต์กํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ด๋ ต๋ค๋ ๋จ์ ์ด ์กด์ฌํ๋ค.
@Aspect
ํ๋ก์๋ฅผ ์ง์ ์์ฑํ์ฌ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ด ์๋ CGLib๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํธ ์ฝ๋๋ฅผ ์กฐ์ํ์ฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๋ฐฉ๋ฒ์ผ๋ก, ๊ฐ์ฅ ๊ฐ๋จํ๊ฒ AOP๋ฅผ ๊ตฌํํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋ค.
// Aspect.java
@Aspect
class ExampleAspect {
@Around(value = "execution(* hello.proxy.app..*(..))") // Pointcut
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
// Advice ๋ก์ง
// ...
Object result = joinPoint.proceed(); // ์ค์ ๊ฐ์ฒด์ ๋ฉ์๋ ํธ์ถ
// ...
// Advice ๋ก์ง
return result;
}
}
// Config.java
@Configuration
class AopConfig {
@Bean
public LogTraceAspect logTraceAspect(LogTrace logTrace) {
return new LogTraceAspect(logTrace);
}
}
ํฌ์ธํธ์ปท(Pointcut)๊ณผ ์ด๋๋ฐ์ด์ค(Advice)๋ก ๊ตฌ์ฑ๋ ์ด๋๋ฐ์ด์ (Advisor)์ ์์ฑ์ ํธ๋ฆฌํ๊ฒ ํด์ฃผ๋ ๊ธฐ๋ฅ์ ๊ฐ์ง ์ด๋ ธํ ์ด์ ์ ํตํด ํจ์ฌ ๊ฐ๋จํ๊ฒ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ์ ๊ณตํ ์ ์๋ค.
@Aspect์ ๋์ ๋ฐฉ์
@Aspect๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ ์ฉํ ๋, ์ด๋๋ฐ์ด์ ์์ฑ๊ณผ ํ๋ก์ ๊ฐ์ฒด ์ ์ฉ์ ๋ค์๊ณผ ๊ฐ์ ํ๋ฆ์ผ๋ก ๋์ํ๊ฒ ๋๋ค.
์ด๋๋ฐ์ด์ ์๋ ํ์ ๋ฐ ์์ฑ
์คํ๋ง ์ปจํ ์ด๋ ์ด๊ธฐํ ์
@Aspect
์ ๋ ธํ ์ด์ ์ด ๋ถ์ ๋น ์กฐํ@Aspect
์ ๋ ธํ ์ด์ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ด๋๋ฐ์ด์ ์์ฑ์ด๋๋ฐ์ด์ ๋ฑ๋ก

ํ๋ก์ ๊ฐ์ฒด ์ ์ฉ
์คํ๋ง ๋น ๋์์ด ๋๋ ๊ฐ์ฒด ์์ฑ
์์คํ๋ง ์ปจํ ์ด๋๋ ๋ฑ๋ก๋๋ ๋ชจ๋ ๋น์ ๋ํด
BeanPostProcessor
๋ฅผ ํตํด 2์ฐจ ๊ฐ๊ณต ์๋์คํ๋ง ๋น ์ปจํ ์ด๋์์ ์ด๋๋ฐ์ด์ ์กฐํ
์์ฑ๋ ๋น์ด ์บ์ฑ๋ ์ด๋๋ฐ์ด์ ๋ค์ ํฌ์ธํธ์ปท ์กฐ๊ฑด ๋์์ธ์ง ๊ฒ์ฌ
๋์์ด๋ผ๋ฉด, ํ๋ก์ ํฉํ ๋ฆฌ๋ฅผ ํตํด ํด๋น ๋น์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์์ฑ(์๋ณธ ๊ฐ์ฒด๋ ํ๋ก์ ๋ด๋ถ์์ ์ฐธ์กฐ)
์๋ณธ ๊ฐ์ฒด ๋์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์คํ๋ง ๋น์ผ๋ก ์ปจํ ์ด๋์ ๋ฑ๋ก
ํ๋ก์ ๋ด๋ถ ํธ์ถ (Self-Invocation) ๋ฌธ์
AOP ํ๋ก์๊ฐ ์ ์ฉ๋ ๊ฐ์ฒด์ ๋ด๋ถ์์ ๋ค๋ฅธ ๋ฉ์๋๋ฅผ ํธ์ถํ ๋ AOP๊ฐ ์ ์ฉ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค. ์ด๋ ํ๋ก์๋ฅผ ๊ฑฐ์น์ง ์๊ณ this
์ฐธ์กฐ๋ฅผ ํตํด ์๋ณธ ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ์ง์ ํธ์ถํ๊ธฐ ๋๋ฌธ์ด๋ค.
@Service
class MyService {
public void external() {
// ... ๋ก์ง ...
this.internal(); // ํ๋ก์๋ฅผ ๊ฑฐ์น์ง ์๊ณ ์๋ณธ ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ์ง์ ํธ์ถ
}
@Transactional
public void internal() {
// ํธ๋์ญ์
์ด ์ ์ฉ๋๊ธฐ๋ฅผ ๊ธฐ๋ํ๋ ๋ก์ง
}
}
์ ์ฝ๋์์ external()
์ ํธ์ถํ๋ฉด internal()
์ ํธ๋์ญ์
์ด๋๋ฐ์ด์ค๋ฅผ ํ์ง ์๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
์๊ธฐ ์์ ์ฃผ์ : ์์ฑ์๋
@Autowired
๋ฅผ ํตํด ํ๋ก์ ๊ฐ์ฒด์ธ ์๊ธฐ ์์ ์ ์ฃผ์ ๋ฐ์ ํธ์ถํ๋ ๋ฐฉ์์ง์ฐ ์กฐํ:
ApplicationContext
๋ObjectProvider
๋ฅผ ์ฌ์ฉํด ์ค์ ํ์ํ ์์ ์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์กฐํ(lookup)ํ์ฌ ์ฌ์ฉํ๋ ๋ฐฉ์๊ตฌ์กฐ ๋ณ๊ฒฝ: ์คํ๋ง์ด ๊ฐ์ฅ ๊ถ์ฅํ๋ ๋ฐฉ๋ฒ ๋ด๋ถ ํธ์ถ์ด ํ์ํ ๋ก์ง์ ๋ณ๋์ ํด๋์ค๋ก ๋ถ๋ฆฌํ์ฌ ์์กด์ฑ์ ์ฃผ์ ๋ฐ์ ์ฌ์ฉํ๋ ๋ฐฉ์
์คํ๋ง๊ณผ CGLIB
์ต์ ๋ฒ์ ์ ์คํ๋ง ๋ถํธ์์๋ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก CGLIB๋ฅผ ์ฌ์ฉํ๋๋ฐ, ์ด๋ฅผ ํตํด ์๋์ ๊ฐ์ ์ฅ์ ์ ๊ฐ์ง๊ฒ ๋๋ค.
์ธํฐํ์ด์ค๊ฐ ์๋ ํด๋์ค๋ ํ๋ก์๋ก ๋ง๋ค ์ ์์
์์กด๊ด๊ณ ์ฃผ์ ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์(JDK ๋์ ํ๋ก์๋ ๋ฐ์)
ํ์ง๋ง CGLIB๋ ํด๋์ค๋ฅผ ์์๋ฐ์ ์์ฑํ๋ ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ ์ ์ฝ์ฌํญ์ด ์กด์ฌํ์ง๋ง 3๋ฒ์ ์ ์ธํ๊ณ ๋ ์ด๋ฏธ ํด๊ฒฐ๋์๋ค.(objenesis
๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ํด๊ฒฐ)
๋์ ํด๋์ค์ ๊ธฐ๋ณธ ์์ฑ์๊ฐ ๋ฐ๋์ ์กด์ฌํด์ผ ํจ
์์ฑ์ ํธ์ถ์ด ๋ ๋ฒ ๋ฐ์(์ค์ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ + ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋)
final ํด๋์ค, final ๋ฉ์๋๊ฐ ์๋ ๊ฒฝ์ฐ ํ๋ก์๋ฅผ ์์ฑํ ์ ์์
3๋ฒ ๋ฌธ์ ๋ ์์ง ํด๊ฒฐํ์ง ๋ชปํ์ผ๋ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ final ํค์๋๋ฅผ ์ ์ฌ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์ ํฐ ๋ฌธ์ ๊ฐ ๋์ง ์๋๋ค.
์ฐธ๊ณ ์๋ฃ
Last updated
Was this helpful?