2011年5月2日 星期一

Spring AOP Exception - Common causes of this problem include using a final class or a non-visible class

在使用AOP時實作下面的Class, 用來check aop method執行的時間
這邊需要 ,在applicationContext.xml 裡加上
<aop:aspectj-autoproxy proxy-target-class="false"/>
<!-- **load-time-weave 
<context:load-time-weaver />
-->

<bean class="x.y.z.ProcessTimeLogger">
 <property name="beforeMessage" value="Before %s %s" />
 <property name="afterMessage" value="After %s %s" />
</bean>
執行的時候出現了 "Common causes of this problem include using a final class or a non-visible class " Exception , 相關問題是method constructor 被設為final or private
查了一下Spring 相關的文件


<aop:aspectj-autoproxy />
有一個proxy-target-class屬性,默認為false,表示使用JDK動態Proxy,當配置為
<aop:aspectj-autoproxy proxy-target-class="true" />
時,表示使用CGLib動態Proxy技術。 不過即使proxy-target-class設置為false,如果目標類沒有聲明interface,則Spring將自動使用CGLib動態Proxy。
Spring Doc 說明:
To force CGLIB proxying when using the @AspectJ autoproxy support, set the 'proxy-target-class' attribute of the <aop:aspectj-autoproxy> element to true:
<aop:aspectj-autoproxy proxy-target-class="true"/>

使用上的不同跟限制如下,注意要AOP class 的method的限制

JDK dynamic proxies:
The class has to implement interfaces. Otherwise you will get ClassCastExceptions saying that $Proxy0 can not be casted to the particular class.
Eventually dynamic proxies force you to program to interfaces since you can not cast the proxy to the class – a feature I really like about them.


CGLib proxies:
The proxies are created by sub-classing the actual class. This means wherever an instance of the class is used it is also possible to use the CGLib proxy.
The class needs to provide a default constructor, i.e. without any arguments. Otherwise you’ll get an IllegalArgumentException: “Superclass has no null constructors but no arguments were given.” This makes constructor injection impossible.
The proxying does not work with final methods since the proxy sub class can not override the class’ implementation.
The CGLib proxy is final, so proxying a proxy does not work. You will get an IllegalArgumentException saying “Cannot subclass final class $Proxy0″. But this feature is usually not needed anyway. (This issue might be solved in the future.)
Since two objects are created (the instance of the class and the proxy as instance of a sub class) the constructor is called twice. In general this should not matter. I consider changing the class’ state based on constructor calls a code smell anyway.


相關的說明這在Spring 文件可以找到
Link -> 7.2 @AspectJ support



簡單的AOP Class,
ProcessTimeLogger.java
import java.util.Arrays;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;

@Aspect
public class ProcessTimeLogger {
 private String beforeMessage;
 private String afterMessage;
 private static final Log logger = MyUtils.getLog(); 
 
 @Pointcut("execution(* com.x.service.*.*(..))")
 private void testBeanExecution() { }
 
 
 @Pointcut("execution(* com.x.dao.impl.*.*(..))")
 private void daoExecution() { }
 
 @Around("daoExecution()")
 public Object doProcess(ProceedingJoinPoint pjp) throws Throwable {
  String className = pjp.getTarget().getClass().getName();
  String method = pjp.getSignature().getName();
  logger.info(String.format(this.beforeMessage,className, method,Arrays.toString(pjp.getArgs())));
  
  StopWatch watch = new StopWatch();
  watch.start();
  Object result = pjp.proceed();
  watch.stop();
  
  logger.info(String.format(this.afterMessage,method,className, Arrays.toString(pjp.getArgs())));
  String log = className   " Run "   method   "method complete in "  watch.getTotalTimeMillis()   " ms";
  logger.info(log);
  return result;
//  // Do what you want with the join point arguments
//  for ( Object object : joinPoint.getArgs()) {
//   
//   logger.info("****** AOP object ="   object);
//  }
//  return joinPoint;
  
 }
 
 @PostConstruct
 public void initialize() {
     Assert.notNull(this.beforeMessage,
         "The [beforeMessage] property of ["   getClass().getName()  
         "] must be set.");
     Assert.notNull(this.afterMessage,
         "The [afterMessage] property of ["   getClass().getName()  
         "] must be set.");
 }
 public void setBeforeMessage(String beforeMessage) {
     this.beforeMessage = beforeMessage;
 }
 public void setAfterMessage(String afterMessage) {
     this.afterMessage = afterMessage;
 }
}

Log結果大概長這樣
Ts-[[-] ] ,2011/05/02 14:30:00 [INFO ] CaseDAOImpl.java/getRunScheduleReviewers:110, getRunScheduleReviewers result.size():3
 Ts-[[-] ] ,2011/05/02 14:30:00 [INFO ] ProcessTimeLogger.java/doProcess:33, Before com.gfactor.emaildiscovery.service.CaseService getCaseDAO
 Ts-[[-] ] ,2011/05/02 14:30:00 [INFO ] ProcessTimeLogger.java/doProcess:40, After getCaseDAO com.gfactor.emaildiscovery.service.CaseService
 Ts-[[-] ] ,2011/05/02 14:30:00 [INFO ] ProcessTimeLogger.java/doProcess:42, com.gfactor.emaildiscovery.service.CaseService Run getCaseDAOmethod complete in 0 ms

當然也會有些錯誤
Ts-[[-] ] ,2011/05/02 14:56:10 [WARN ] Cglib2AopProxy.java/doValidateClass:256, Unable to proxy method [public final void 
org.springframework.dao.support.DaoSupport.afterPropertiesSet() throws 
java.lang.IllegalArgumentException,org.springframework.beans.factory.BeanInitializationExcep
tion] because it is final: All calls to this method via a proxy will be routed directly to the proxy.



註:
Load-time-weaver , 有些class不在spring的管理之中,通過 load-time weaving,可以将spring context植入這些class中,從而獲得context中的其他bean,實現ioc


load-time-weave 要打開必須在server startup增加jvm option, 需要spring-agent.jar or org.springframework.instrument-{version}.jar
-javaagent:path/to/org.springframework.instrument-{version}.jar 
(previously named spring-agent.jar).

<context:load-time-weaver aspectj-weaving="on" /> 
使 spring 開啟 loadtimeweaver, 注意 aspectj-weaving 有三个選项 : on, off, auto-detect,
如果設置為, on 以强制使用 aspectj, 设置为 auto-detect, spring 将會在 classpath 中查找 aspejct 需要的 META-INF/aop.xml, 如果找到则開啟 aspectj weaving, 這個邏輯在 LoadTimeWeaverBeanDefinitionParser#isAspectJWeavingEnabled 方法中。


More information :
Aspect Oriented Programming with Spring

沒有留言:

張貼留言