Java AOP 實例踩坑記
其實這篇文章是存了很久的草稿,寫了一半沒有繼續完稿。終于得空繼續完善它,之后還會抽時間繼續研究Spring AOP 的使用和實現( ̄? ̄),絕不放過任何絆腳石。
嘗試Spring AOP未果
項目中遇到一個性能監控需求:在線搜集統計服務方法的調用次數和平均開銷時間,用于服務性能調優和服務死活監控。考慮到涉及到的統計點很多,一個個手寫采集點代碼非常傻,而且代碼侵入性很大。
想起之前為了重構代碼中的手工auto-retry(見下面的代碼庫 Orz),曾經找到過jcabi這樣的庫,其中是采用了Java中的一大“神器”,面相切面編程(AOP)。于是性能點采集邏輯也打算采用AOP的方式來實現。
// 坑爹的手工 for retry
for (int i=0; i<MAX_TRY; i++){
try{
doSomething();
break;
}catch(Exception ignore){
}
}
考慮到項目中使用了一部分spring功能(依賴注入),于是網上找資料,很多都是關于Spring AOP+AspectJ來實現的例子。比如參考的[1]、[2],里面詳細的講解了關于AOP的概念,如何使用Spring AOP和AspectJ風格的注釋來實現動態代理方式的AOP編程。然而卻走上了Spring AOP的踩坑之路。大名鼎鼎的Spring,自動裝配bean,Spring AOP號稱還能自動運行時織入切點。但是卻怎么嘗試都 “不work!” 。參考了一下 [2] 里面的坑,還是不行。(百分百被大眾/官網實例坑的設定)
暫時放棄Spring AOP,老老實實的學習一下AspectJ吧。
完工的代碼(去掉了公司業務的代碼框架)
來一段AspectJ風格的代碼
@Aspect
public class Monitor {
private static final Logger logger = LoggerFactory.getLogger(Monitor.class);
@Pointcut("execution(public * *(..)) && @annotation(demo.APM)")
private void apmHandler(){}
@Around("apmHandler()")
public Object apmRecordTime(ProceedingJoinPoint pjp) throws Throwable{
Object ret = null;
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
APM apmAnnotation = method.getAnnotation(APM.class);
String commandName = apmAnnotation.value();
try {
long startTime = System.nanoTime();
ret = pjp.proceed();
long processTime = (System.nanoTime() - startTime); // avg_time unit: nano seconds
logger.trace("command[{}] spend avg_time:{} ms", commandName, processTime/1000000d);
} finally{
}
return ret;
}
@AfterThrowing(pointcut="apmHandler()", throwing= "error")
public void apmRecordException(JoinPoint jp, Throwable error){
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
APM apmAnnotation = method.getAnnotation(APM.class);
String commandName = apmAnnotation.value();
logger.trace("command[{}] throw exception: {}", commandName, error);
}
}
為了方便設定切點,我使用了Java annotation的方式來做標記:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface APM {
String value() default "";
}
凡是標記了@APM 的方法,都會被AOP切入。例如代碼里定義的 pointcut 可以捕捉所有 public 并且帶有@APM("xxxx") 的函數調用,通過Java Reflection 可以拿到APM的value(作為command名字,其實也可以考慮用method name)
實例化的坑
當然實際中實現的AOP類沒有那么簡單,還需要加入統計的工具,尤其是當需要注入外部的對象時,就不得不通過Spring bean的方式來配置管理它。例如上面的Monitor類,在Spring 的 Java Config里:
@configure
public AppConfig{
@Bean
public Monitor monitor(){
Monitor monitor = new Monitor();
// monitor.config(xxx);
// monitor.register(xxxx);
return monitor;
}
}
乍眼一看感覺上面的代碼沒問題是吧?查看日志的時候發現Monitor實例化了兩次!翻看AspectJwen dang發現一段說Aspect類的實例化是由AspectJ接管的!
Like classes, aspects may be instantiated, but AspectJ controls how that instantiation happens -- so you can't use Java's new form to build new aspect instances. By default, each aspect is a singleton , so one aspect instance is created. This means that advice may use non-static fields of the aspect, if it needs to keep state around
The aspect is a singleton object and is created outside the Spring container. A solution with XML configuration is to use Spring's factory method to retrieve the aspect.
<bean id="syncLoggingAspect" class="uk.co.demo.SyncLoggingAspect"
factory-method="aspectOf" />
如何讓spring來控制這個spring之外實例化的東西呢?
參考 Configuring AspectJ aspects using Spring IoC with JavaConfig?
With this configuration the aspect will be treated as any other Spring bean and the autowiring will work as normal.
You have to use the factory-method also on Enum objects and other objects without a constructor or objects that are created outside the Spring container.
通過如此如下方式是可以成功得到AOP的bean
import org.aspectj.lang.Aspects;
@configure
public AppConfig{
@Bean
public Monitor monitor(){
Monitor monitor = Aspects.aspectOf(Monitor.class);
// monitor.config(xxx);
// monitor.register(xxxx);
return monitor;
}
}
實例化的坑(續):默認構造函數
AspectJ會使用默認構造函數來實例化Aspect的類,當你無意中實現了一個非默認構造函數又沒有默認構造函數時,他會報下面的錯誤:
aspectj method <init>()V not found
所以請使用默認構造函數來實例化Aspect類!(終于明白我要拿到AOP bean的苦衷了吧)
maven配置AspectJ weaving
未完待續( ̄? ̄)
參考:
- Spring實戰4—面向切面編程
- Spring AOP 注解方式實現的一些“坑”
- How to get a method's annotation value from a ProceedingJoinPoint?
- Aspectj @Around pointcut all methods in Java
- http://aspects.jcabi.com/
- Strategies for using AspectJ in a Maven multi-module reactor
來自:http://www.jianshu.com/p/62f22d821333