여러 메서드에 공통 코드를 추가해야 한다면??
예를들어
class MyClass{
void aaa(){
system.out.println("[before]{");
system.out.println("aaa() is called.");
system.out.println("}[after]");
}
void aaa2(){
system.out.println("[before]{");
system.out.println("aaa2() is called.");
system.out.println("}[after]");
}
void bbb(){
system.out.println("[before]{");
system.out.println("bbb() is called.");
system.out.println("}[after]");
}
}
이러한 코드가 있다고 하자.
모든 메서드에는
system.out.println("[before]{")
system.out.println("}[after]")
이 들어간다. 공통되는 메서드를 일일이 코딩하는 것은 비효율적인 코드라고 할 수 있다.
따라서 공통되는 코드만 앞으로 빼줄 수 있다면 굉장히 좋을 것이다.
다음 코드를 보자.
public class AopMain{
public static void main(String[] args){
MyAdvice myAdvice = new MyAdvice();
Class myClass = Class.forName("Package.AopMain"); //Package에는 자신의 package명 적어주면됨
Object obj = myClass.newInstance();
for(Method m : myClass.getDeclaredMethods()){
myAdvice.invoke(m,obj,null);
}
}
}
class MyAdvice{
void invoke(Method m, Object obj, Object... args) throws Exception{
System.out.println("[Before]{");
m.invoke(obj, args); // aaa(),aaa2(),bbb() 호출가능
System.out.println("}[after]");
}
}
class MyClass{
void aaa(){
System.out.println("aaa() is called.");
}
void aaa2(){
System.out.println("aaa2() is called.");
}
void bbb(){
System.out.println("bbb() is called.");
}
}
위의 코드와 비교했을때 잘 만들어진 코드라는 것을 알 수가 있다.
AOP는 이처럼 공통되는 코드를 따로 빼주어서 메서드가 좀 더 특화될 수 있게 해준다.
이번에는 a로 시작하는 메서드만 실행될 수 있게 만들어보자.
Pattern p = Pattern.compile("a.*")
// a로 시작하는 것들을 패턴으로 만들어서 p에 저장,comile()안에 들어가는 것은 정규식(regex)이다
public class AopMain{
public static void main(String[] args){
MyAdvice myAdvice = new MyAdvice();
Class myClass = Class.forName("Package.AopMain"); //Package에는 자신의 package명 적어주면됨
Object obj = myClass.newInstance();
for(Method m : myClass.getDeclaredMethods()){
myAdvice.invoke(m,obj,null);
}
}
}
class MyAdvice{
boolean matcehs(Method m){ // m 이 일치하면 true
return Pattern.matches("a.*","m.getName()"); // m이 일치하면 true
}
void invoke(Method m, Object obj, Object... args) throws Exception{
if(matches(m))
System.out.println("[Before]{");
m.invoke(obj, args); // aaa(),aaa2(),bbb() 호출가능
if(matches(m))
System.out.println("}[after]");
}
}
class MyClass{
void aaa(){
System.out.println("aaa() is called.");
}
void aaa2(){
System.out.println("aaa2() is called.");
}
void bbb(){
System.out.println("bbb() is called.");
}
}
내가 원하는 메서드만 사용할 수 있도록 하기 위해서 Pattern을 사용했었다. 이와 비슷하게 사용되는 것이 @Transactional이다.
메서드 위에 annotation 표시를 해주면 getAnnotation()을 사용해서 annotation 표시가 있는 메서드만 사용할 수 있게 한다.
class MyAdvice{
void invoke(Method m, Object obj, Object... args) throws Exception{
if(m.getAnnotation(Transactional.class)!=null)
System.out.println("[Before]{");
m.invoke(obj, args); // aaa(),aaa2(),bbb() 호출가능
if(m.getAnnotation(Transactional.class)!=null)
System.out.println("}[after]");
}
}
class MyClass{
@Transactional
void aaa(){
System.out.println("aaa() is called.");
}
@Transactional
void aaa2(){
System.out.println("aaa2() is called.");
}
@Transactional
void bbb(){
System.out.println("bbb() is called.");
}
}
Aspect Oriented Programming 관점 지향 프로그래밍 이라고 부른다.
부가기능을 동적으로 추가해주는 기술
메서드의 시작 또는 끝에 자동으로 코드를 추가
target : advice가 추가될 객체
advice: target에 동적으로 추가될 부가 기능(코드)
join point: advice가 추가(join)될 대상(메서드)
pointcut: join point들을 정의한 패턴
proxy : target에 advice가 동적으로 추가되어서 생성된 객체
weaving: target에 advice를 추가해서 proxy를 생성하는 것
종류 | 에너테이션 | 설명 |
---|---|---|
around advice | @Around | 메서드의 시작과 끝 부분에 추가되는 부가 기능 |
before advice | @Before | 메서드의 시작 부분에 추가되는 부가 기능 |
after advice | @After | 메서드의 끝 부분에 추가되는 부가 기능 |
after returning | @AfterRetruning | 예외가 발생하지 않았을 때,실행되는 부가 기능(try) |
after throwing | @AfterThrowing | 예외가 발생했을 때, 실행되는 부가 기능(catch) |
Advice의 설정은 XML과 Annotation, 두가지 방법으로 가능하다.
advice가 추가될 메서드를 지정하기 위한 패턴
execution(반환타입 패키지명.클래스명.메서드명(매개변수 목록))
예) @Around("execution(* com.aaa.bbb.aop.*.*(..))")
모든 반환타입(*), com.aaa.bbb.aop라는 패키지, 모든 클래스(*),모든 메서드(*)
@Component
public class MyMath {
public int add(int a,int b){
return a+b;
}
public int add(int a,int b,int c){
return a+b+c;
}
public int subtract(int a,int b){
return a-b;
}
public int multiply(int a,int b){
return a*b;
}
}
@Component
@Aspect
public class LoggingAdvice{
@Around("execution(* com.aaa.bbb.aop.MyMath.*(..))")
public Object methodCalling(ProceedingJoinPoint pjp)throws Throwable{
long start = System.currentTimeMillis(); // 현재 시간 저장
System.out.println("<<[start]"+pjp.getSignature().getName()+Arrays.toString(pjp.getArgs())); // 메서드 이름과 매개변수를 보여준다.
Object result = pjp.proceed(); // target의 메서드를 호출
System.out.println("result = "+result);
System.out.println("[end] = "+(System.currentTimeMillis()-start)+"ms");
return result;
}
}
@Component로 bean에 등록해주고 나서 횡단관심사(부가기능)라는 것을 알려주기 위해 @Aspect를 써준다.
@Around에서 MyMath.*을 등록해주어서 MyMath 클래스에 있는 모든 메서드를 실행할때마다 밑에 정의되어있는 부가기능이 같이 사용된다.
만약에 MyMath에 있는 특정한 메서드만 부가기능을 사용하고 싶다면
@Around("execution(* com.aaa.bbb.aop.MyMath.add*(..))")
이런식으로 해당 메서드이름을 써주면 된다.