此路是我開,此樹是我栽,要從此路過,留下買路財。
所謂 AOC (Aspect Orientation Programming) 就是在程式特定的函式,安放一些路障,只要這些函式被呼叫前/後,執行指定的工作。
在這一回中,由前例 simpleDemo 繼續往下發展。
在pom.xml, <dependencies>
中增加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在 src/main/resources/application.properties 中增加幾行
# AOP settings
spring.aop.auto=true
spring.aop.proxy-target-class=true
假設 Target,目標是 RoutingController 中的攔截相關 Routing 的指令, 我們以 System.out.println()的方式列印相關的信息,看到底哪些函式被執行,並知道其執行順序。
@GetMapping({"/", "/hello"})
public String sayHello() {
return "hello";
}
@GetMapping("/hello/{name}")
public String byRestfulGet(@PathVariable String name, Model model) {
desc.setName(name);
model.addAttribute("name", desc.getName());
model.addAttribute("desc", desc.getDescripion());
System.out.println("=== byRestfulGet ===");
return "hello_name";
}
留意 sayHello() 沒有參數,而 byRestfulGet() 有二個參數,同時為了顯示執行的次序,我們在此加上列印 == byRestfulGet ==, 方便閱讀,AOP的程式如下:
@Aspect
@Component
public class AopDemo {
@Pointcut("execution(public * com.example.demo.RoutingController.*(..))")
public void myPointcut() {
System.out.println("--- AppDemo.myPointCut ---");
}
@Before("myPointcut()")
public void myBefore(JoinPoint jp) {
System.out.println("... before ...");
System.out.println(". name: "+jp.getSignature().getName());
System.out.println(". type: "+jp.getSignature().getDeclaringTypeName());
if(jp.getArgs().length>0) System.out.println(". - " + jp.getArgs()[0]);
}
@After("myPointcut()")
public void myAfter() {
System.out.println("... after ...");
}
@Around("myPointcut()")
public void myAround() {
System.out.println("... around ...");
}
}
其中,@Aspect,@Component,指明這個類別是用於 AOP, @Pointcut 指明,攔截點是類別 RoutingController 中所有的公開(public) 函式,注意,myPointcut()函式並不會真正被執行,因此一般都寫成空的指令,即:
@PointCut(...)
public void myPointcut() { }
@Before 是指在路障點前執行 (myBefore(JointPoint j)),JointPoint 可以傳入在執行時,切點的參數。執行的函數也可以不傳入任何參數。除了 @Before 之外,還有 @After (在路障後執行)。@Around 比較特殊,會在 (pjp.)proceed() 前後執行。注意,若沒有呼叫 proceed() 函式,那原來的被攔截的程式將不會被執行,proceed() 就是去執行被攔截的程式。同時 @Around 也會變更路由。如上例中,會回傳輸入的路徑 /hotel/Mattrew 因此會指向 /hotel/mattrew.jsp (而不是 hello_one.jsp)。若是要保留指向 hello_one.jsp,則程式必須改成如下編碼,回傳 proceed() 的結果。
@Around("myPointcut()")
public Object myAround(final ProceedingJoinPoint pjp) throws Throwable {
System.out.println("... around before proceed ...");
Object ret_value = pjp.proceed();
System.out.println("... around after proceed ...");
return ret_value;
}
執行的結果如下(http://localhost:8080/hello/Luke):
由Console欄的信息可以看出,其執行的次序是: @Around
> @Before > [proceed()] > @Around
> @After,至於各道 "令符" @Pointcut(…), @within(…), @Target(…), @annotation(…),@args(…), args(…) 其中參數的格式都有許多變化,各位讀者可以參考其他文章,或是我們有緣再相會。