Java/Spring

[Spring] 12 -3 AOP 적용하기 (Component + 심화)

담크 2021. 7. 6. 17:30

이번에는 component-scan을 이용해서 AOP를 적용해보도록 하겠습니다.

바로 패키지 만들러 가볼까요 ~

Student.java (interface)

package com.test06;

public interface Student {
	
	void classWork();

}

StudentA.java

package com.test06;

public class StudentA implements Student {

	@Override
	public void classWork() {
		System.out.println("컴퓨터를 켜서 뉴스를 본다.");
	}

}

StudentB.java

package com.test06;

public class StudentB implements Student {

	@Override
	public void classWork() {
		System.out.println("컴퓨터를 켜서 주식을 본다.");
	}

}

MyAspect.java

package com.test06;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyAspect {
	
	@Pointcut("execution(public * *(..))")
	public void myClass() {
	}
	
	@Before("myClass()")
	public void before() {
		System.out.println("출석한다.");
	}
	
	@After("myClass()")
	public void after() {
		System.out.println("집에 간다.");
	}
	
}

MTest.java

package com.test06;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MTest {
	
	public static void main(String[] args) {
		ApplicationContext factory = new ClassPathXmlApplicationContext("com/test06/applicationContext.xml");
		
		Student a = factory.getBean("studentA", Student.class);
		Student b = (Student) factory.getBean("studentB");
		
		System.out.println("A입장");
		a.classWork();
		System.out.println("-----------------");
		System.out.println("B입장");
		b.classWork();
	}

}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<aop:aspectj-autoproxy />
	<context:component-scan base-package="com.test06" />
	
</beans>

코드 실행결과

 

지금 위에 적어놓은 코드 그대로 실행하게 되면 에러가 발생합니다. 왜 에러가 발생할까요???

지금 MTest에서 불러올 객체가 생성되지 않아서 그렇습니다.

applicationContext에서 <autoproxy>태그를 이용하여 @Aspect에 있는 코드들을 aspect로 등록한 후 

<component-scan>을 통해서 이를 각각 객체화 시켜주어야합니다.

 

 

 

** component-scan에 대한 설명은 아래를 참고해주세요

https://darmk.tistory.com/entry/Spring-11-Annotation-Component?category=945098 

 

[Spring] 11. Annotation - Component

오늘은 지난번 포스팅에 이어서 spring의 어노테이션 중 하나인 Component에 대해 공부해볼까 합니다. 이번에는 이라는 태그를 사용해서 객체생성과 값 연결을 같이 해보도록 하겠습니다. 패키지를

darmk.tistory.com

 

따라서 위의 출력 결과대로 출력하려면 StudentA, StudentB, MyAspect에 각각 @Component를 적어주면 이 어노테이션이 알아서 객체화시켜줍니다.

이런식으로 @Component를 해줘야합니다.

 

그렇다면 여기서는 어떤 게 joinpoint인지, pointcut인지 감이 오시나요?

 

MTest에서 a.classWork() 이런 부분이 타겟을 호출하는 시점이므로 Joinpoint

이러한 Joinpoint에서 어떤 Joinpoint에 프록시가 만들어져서 요청, 응답을 대신해줄 건지를 나타내 주는 부분이 MyAspect에서 myClass부분이므로 여기가 Pointcut ...

어차피 코드의 흐름은 위에서 아래로 흘러가니까 이렇게 하나하나 찾아보시면 됩니다.

 

 


이 부분은 좀 응용한 버전인데 이런 식으로도 사용하는구나 보기만 하셔도 됩니다.

앞에 부분이 이해가 잘돼서 응용이 필요하다 하시는 분은 따라 해 보셔도 좋을 것 같아요 ㅎㅎ

패키지부터 만들겠습니다.

MessageBean.java (interface)

package com.test07;

public interface MessageBean {
	
	void sayHello();

}

MessageBeanImpl.java

package com.test07;

public class MessageBeanImpl implements MessageBean {
	
	private String name;
	
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public void sayHello() {
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("Hello, " + name);
	}

}

LoggingAdvice.java (MethodInterceptor 잘 보고 상속받아주세요)

package com.test07;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.StopWatch;

public class LoggingAdvice implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		
		String method = invocation.getMethod().getName();
		
		StopWatch timer = new StopWatch();
		
		timer.start(method);
		System.out.println("[Log] method : " + method + " is calling");
		
		Object target = invocation.proceed();
		
		timer.stop();
		System.out.println("[LOG] method : " + method + " was called");
		System.out.println("[LOG] method : " + timer.getTotalTimeSeconds() + " sec");
		
		return target;
	}

}

MTest.java

package com.test07;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MTest {
	
	public static void main(String[] args) {
		ApplicationContext factory = new ClassPathXmlApplicationContext("com/test07/applicationContext.xml");
		
		MessageBean bean = factory.getBean("proxy", MessageBean.class);
		bean.sayHello();
	}

}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- target -->
	<bean id="targetBean" class="com.test07.MessageBeanImpl">
		<property name="name" value="Pengsoo" />
	</bean>
	
	<!-- advice -->
	<bean id="loggingAdvice" class="com.test07.LoggingAdvice" />
	
	<!-- advisor(aspect) -->
	<bean id="myAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="advice" ref="loggingAdvice" />
		<property name="pointcut">
			<bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
				<property name="pattern">
					<value>.*sayHello.*</value>
				</property>
			</bean>
		</property>
	</bean>
	
	<!-- proxy -->
	<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="target" ref="targetBean" />
		<property name="interceptorNames">
			<list>
				<value>myAdvisor</value>
			</list>
		</property>
	</bean>
</beans>

코드 실행결과

[Log] method : sayHello is calling 가 출력되고 2초 후에 나머지 부분이 출력되어야 합니다.

 

 

여기는 applicationContext부분에 어떤 코드들이 어떤 것을 생성하는지 적어뒀습니다.

target부분은 실제로 실행될 코드 부분을 나타내므로 CC

advice부분은 실제로 실행될 코드 부분에 붙어주는 코드이므로 CCC

proxy는 target과 target사이에서 연결해줘야 하는 코드입니다.(인터셉터할 수 있게 만들어줌)

 

Thread.sleep(2000); 는 2000 millisecond(2초) 동안 일시정지입니다. 이 코드 때문에 결과에서 2초의 딜레이가 발생합니다.

.*sayHello.* 부분은 정규표현식 형태입니다. (나중에 따로 공부하는 것을 추천드립니다.)