27 1月 2010

Java Annotation: Inherited

java.lang.annotation中@Retention跟@Target都很容易瞭解,但@Inherited就比較麻煩些,所以簡單列個例子來看@Inherited的影響。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Inherited
public @interface InheritedAnn {

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface NonInheritedAnn {

}
上列兩個Annotation僅差別在有無@Inherited。簡單看一下@Inherited對extends跟implements的影響
@InheritedAnn
@NonInheritedAnn
public class Parent {
 
 @InheritedAnn
 @NonInheritedAnn
 public void notBeOverrided() {}
 
 @InheritedAnn
 @NonInheritedAnn
 public void beOverrided() {}
}

public class Child extends Parent {
 @Override
 public void beOverrided() {}
}


@InheritedAnn
public interface SimpleInterface {
 @InheritedAnn
 void simple();
}

public class SimpleImpl implements SimpleInterface {
 @Override
 public void simple() {}
}
寫個Test吧
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;

import java.lang.reflect.Method;

import org.junit.Test;
public class AnnTest {
 /**
  * 驗証具有@Inherited 的Annotation可以透過extends保留在subclass中
  */
 @Test
 public void testConcreteClassInheritence() throws Exception {
  //測試Annotated Type,僅具有@Inherited會被保留
  assertNotNull(Child.class.getAnnotation(InheritedAnn.class));
  assertNull(Child.class.getAnnotation(NonInheritedAnn.class));
  //測試沒被Override 的Annotated Method,無論是否有@Inherited皆會被保留
  Method notBeOverrided = Child.class.getMethod("notBeOverrided", null);
  assertNotNull(notBeOverrided.getAnnotation(InheritedAnn.class));
  assertNotNull(notBeOverrided.getAnnotation(NonInheritedAnn.class));
  //測試被Override 的Annotated Method,無論是否有@Inherited皆不會被保留
  Method beOverrided = Child.class.getMethod("beOverrided", null);
  assertNull(beOverrided.getAnnotation(InheritedAnn.class));
  assertNull(beOverrided.getAnnotation(NonInheritedAnn.class));
 }
 
 /**
  * 驗証即便具有@Inherited 的Annotation仍無法透過implements interface保留
  */
 @Test
 public void testInterfaceInheritence() throws Exception {
  //無論是Type或Method皆無法保留Annotation
  //測試Annotated Type
  assertNull(SimpleImpl.class.getAnnotation(InheritedAnn.class));
  //測試Annotated Method
  Method simple = SimpleImpl.class.getMethod("simple", null);
  assertNull(simple.getAnnotation(InheritedAnn.class));
 }
}

要說的都寫在Test中...

25 1月 2010

測量執行時間的小工具

我想每個人程式寫久了都會有些小工具,例如StringUtils、DateTimeUtils等,這可能是整個Team所累積的,如果能分享下應該也不錯吧。

這裡提供個測量執行時間的小API,雖說AOP可以測量每個Method所花的時間,但比較耗時,而且偶爾也有AOP不方便的時候。我常用的是下面這種方法。
TrackingStack.enable();
TrackingStack.push("Execution");
TrackingStack.push("Step1");
//Does something
TrackingStack.pop("Step1");
  
TrackingStack.push("Step2");
//Does something
TrackingStack.pop("Step2");
TrackingStack.pop("Execution");
跑出來的結果大概會長得像下面這樣
[Execution]:243 ms
 [Step1]:102 ms
 [Step2]:104 ms

用的程式如下
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TrackingStack {
 private static final Logger logger = LoggerFactory.getLogger(TrackingStack.class);
 private static ThreadLocal current = new ThreadLocal();
 private static boolean ENABLE = false;

 public static final void push(String execution) {
  if (!ENABLE) {
   return;
  }

  ProfilingBean newPb = new ProfilingBean(execution);
  newPb.start();
  if (null == current.get()) {
   current.set(newPb);
  } else {
   current.get().addChild(newPb);
  }

  current.set(newPb);
 }

 public static final void pop(String execution) {
  if (!ENABLE) {
   return;
  }
  ProfilingBean currentPb = current.get();

  if (null == currentPb) {
   return;
  }

  if (currentPb.getExecution().equals(execution)) {
   currentPb.end();

   if (null != currentPb.getParent()) {
    current.set(currentPb.getParent());
   } else {
    logger.info(currentPb.getProfilingMessage());
    current.set(null);
   }
  } else {
   logger.info("Current execution sould be [{}], not [{}]", currentPb.getExecution(), execution);
  }
 }

 public static final void enable() {
  ENABLE = true;
 }

 public static final void disable() {
  ENABLE = false;
 }
}

import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProfilingBean {
 private static final Logger logger = LoggerFactory.getLogger(ProfilingBean.class);
 private ArrayList children = new ArrayList();
 private ProfilingBean parent = null;

 private String execution;

 private long startTime;
 private long endTime;

 public ProfilingBean(String execution) {
  this.execution = execution;
 }

 public String getExecution() {
  return this.execution;
 }

 public void start() {
  this.startTime = System.currentTimeMillis();
 }

 public long getStartTime() {
  return this.startTime;
 }

 public void end() {
  this.endTime = System.currentTimeMillis();
 }

 public long getEndTime() {
  return this.endTime;
 }

 public long getTotalTime() {
  return this.endTime - this.startTime;
 }

 public void setParent(ProfilingBean parent) {
  this.parent = parent;
 }

 public ProfilingBean getParent() {
  return this.parent;
 }

 public void addChild(ProfilingBean child) {
  if (null == child) {
   logger.warn("Child should't be null");
   return;
  }
  this.children.add(child);
  child.setParent(this);
 }

 public String getProfilingMessage() {
  return this.getProfilingMessage("");
 }

 private String getProfilingMessage(String indent) {
  StringBuilder sb = new StringBuilder();
  sb.append("\n" + indent + "[" + this.execution + "]:" + (this.endTime - this.startTime) + " ms");
  for (ProfilingBean pb : this.children) {
   sb.append(pb.getProfilingMessage(indent + "\t"));
  }
  return sb.toString();
 }
}

利用MDC簡化取得特定對象Log資料的方法

這是半反省文。

以往要想取得某一使用者操作過程所產生的Log,由於我會將User放在ThreadLocal中,所以想到的做法就是在LogPattern中加入一個{},並每次自ThreadLocal中取出,例如下列這樣
logger.info("user{} does something.", ThreadLocalHolder.getUserName);
logger.info("user{} create role{}.", ThreadLocalHolder.getUserName, role);
這樣可以很方便的用grep取回這User相關的Log而濾掉非必要的Log.

但是log4j跟logback都有一個Class叫MDC(Mapped Diagnostic Context),可以更簡單地達到這個功能。只要在取得User的那個Filter或是Intercepter,放入MDC中,並在離開時移除即可。

例如使用Filter

public void doFilter(ServletRequest request, ServletResponse response,
    FilterChain chain) throws IOException, ServletException {
    HttpSession session = (HttpServletRequest) request.getSession();
    User user = (User) session.get("User");
    if (null != user) {
        ThreadLocalHolder.putUser(user);
        MDC.put("username", user.getName());
    }
    try {
      chain.doFilter(request, response);
    } finally {
      if (null != user) {
        MDC.remove("username");
      }
    }
  }



皆下來LogPattern只要利用%X{username}就可以帶出user name了。

為什麼是半反省文?因為這樣做就代表綁定了Log的做法,還是有些缺點的。