アノテーションを介したメソッド実行時間のカウント

多くのプロジェクトでは、このメソッドまたはそのメソッドが費やした時間を計算する必要があります。 これを行うには、System.currentTimeMillis()の値を手動で保存し、メソッドが経過時間を計算した後にできます。 多くの方法があると、あまり便利ではなくなります。

したがって、メソッドの実行時間を計算する簡単な注釈を作成することにしました。 インターネットで情報を見つけようとすると、このトピックにはほとんど情報がないことに気付きました。 情報を少しずつ収集して、どうにかして出て行かなければなりません。



注釈は、実行時間をミリ秒またはナノ秒で計算するメソッドにフラグを付け、System.out.printlnを介して結果を出力します。



まず、注釈自体を作成します。

package annotations.time;



import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;



@Target({ElementType.METHOD})

@Retention(RetentionPolicy.SOURCE)

public @ interface Time {

public enum TimeInterval { MILLISECOND, NANOSECOND };

annotations.time.Time.TimeInterval interval() default annotations.time.Time.TimeInterval.MILLISECOND;

String format() default "Elapsed %s" ;

}



* This source code was highlighted with Source Code Highlighter .








間隔フィールドは時間間隔(ミリ秒またはナノ秒)を示すために使用され、形式フィールドは結果を出力する形式を設定します。



さて、このアノテーションが正常に機能するためには、AbstractProcessorを拡張するハンドラークラスを作成する必要があります。 このクラスでは、メソッドコードの前に時間の節約が追加され、メソッドコード自体がtry-finallyブロックにコピーされ、finallyブロックがメソッドによって費やされた時間を計算してコンソールに表示します。

package annotations.time;



import com.sun.tools.javac.code.Flags;

import com.sun.tools.javac.code.TypeTags;

import com.sun.tools.javac.model.JavacElements;

import com.sun.tools.javac.processing.JavacProcessingEnvironment;

import com.sun.tools.javac.tree.JCTree;

import com.sun.tools.javac.tree.JCTree.JCBlock;

import com.sun.tools.javac.tree.JCTree.JCCatch;

import com.sun.tools.javac.tree.JCTree.JCExpression;

import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;

import com.sun.tools.javac.tree.JCTree.JCMethodDecl;

import com.sun.tools.javac.tree.JCTree.JCStatement;

import com.sun.tools.javac.tree.JCTree.JCVariableDecl;

import com.sun.tools.javac.tree.TreeMaker;

import com.sun.tools.javac.util. List ;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;

import javax.annotation.processing.ProcessingEnvironment;

import javax.annotation.processing.RoundEnvironment;

import javax.annotation.processing.SupportedAnnotationTypes;

import javax.annotation.processing.SupportedSourceVersion;

import javax.lang.model.SourceVersion;

import javax.lang.model.element.Element;

import javax.lang.model.element.TypeElement;

import javax.lang.model.util.Elements;



@SupportedAnnotationTypes( value = {TimeAnnotationProcessor.ANNOTATION_TYPE})

@SupportedSourceVersion(SourceVersion.RELEASE_6)

public class TimeAnnotationProcessor extends AbstractProcessor {



public static final String ANNOTATION_TYPE = "annotations.time.Time" ;

private JavacProcessingEnvironment javacProcessingEnv;

private TreeMaker maker;



@Override

public void init(ProcessingEnvironment procEnv) {

super.init(procEnv);

this .javacProcessingEnv = (JavacProcessingEnvironment) procEnv;

this .maker = TreeMaker.instance(javacProcessingEnv.getContext());

}



@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

if (annotations == null || annotations.isEmpty()) {

return false ;

}



final Elements elements = javacProcessingEnv.getElementUtils();



final TypeElement annotation = elements.getTypeElement(ANNOTATION_TYPE);



if (annotation != null ) {

// ,

final Set<? extends Element> methods = roundEnv.getElementsAnnotatedWith(annotation);



JavacElements utils = javacProcessingEnv.getElementUtils();

for (final Element m : methods) {

Time time = m.getAnnotation(Time. class );

if (time != null ) {

JCTree blockNode = utils.getTree(m);

//

if (blockNode instanceof JCMethodDecl) {

//

final List <JCStatement> statements = ((JCMethodDecl) blockNode).body.stats;



//

List <JCStatement> newStatements = List .nil();

//

JCVariableDecl var = makeTimeStartVar(maker, utils, time);

newStatements = newStatements.append( var );



// try,

List <JCStatement> tryBlock = List .nil();

for (JCStatement statement : statements) {

tryBlock = tryBlock.append(statement);

}



// finally,

JCBlock finalizer = makePrintBlock(maker, utils, time, var );

JCStatement stat = maker.Try(maker.Block(0, tryBlock), List .<JCCatch>nil(), finalizer);

newStatements = newStatements.append(stat);



//

((JCMethodDecl) blockNode).body.stats = newStatements;

}

}

}



return true ;

}



return false ;

}



private JCExpression makeCurrentTime(TreeMaker maker, JavacElements utils, Time time) {

// System.nanoTime System.currentTimeMillis

JCExpression exp = maker.Ident(utils.getName( "System" ));

String methodName;

switch (time.interval()) {

case NANOSECOND:

methodName = "nanoTime" ;

break ;

default :

methodName = "currentTimeMillis" ;

break ;

}

exp = maker.Select(exp, utils.getName(methodName));

return maker.Apply( List .<JCExpression>nil(), exp, List .<JCExpression>nil());

}



protected JCVariableDecl makeTimeStartVar(TreeMaker maker, JavacElements utils, Time time) {

// . time_start_{random}

JCExpression currentTime = makeCurrentTime(maker, utils, time);

String fieldName = fieldName = "time_start_" + ( int ) ( Math .random() * 10000);

return maker.VarDef(maker.Modifiers(Flags.FINAL), utils.getName(fieldName), maker.TypeIdent(TypeTags.LONG), currentTime);

}



protected JCBlock makePrintBlock(TreeMaker maker, JavacElements utils, Time time, JCVariableDecl var ) {

// System.out.println

JCExpression printlnExpression = maker.Ident(utils.getName( "System" ));

printlnExpression = maker.Select(printlnExpression, utils.getName( "out" ));

printlnExpression = maker.Select(printlnExpression, utils.getName( "println" ));



// (currentTime - startTime)

JCExpression currentTime = makeCurrentTime(maker, utils, time);

JCExpression elapsedTime = maker.Binary(JCTree.MINUS, currentTime, maker.Ident( var .name));



//

JCExpression formatExpression = maker.Ident(utils.getName( "String" ));

formatExpression = maker.Select(formatExpression, utils.getName( "format" ));



//

List <JCExpression> formatArgs = List .nil();

formatArgs.append(maker.Literal(time.format()));

formatArgs.append(elapsedTime);



JCExpression format = maker.Apply( List .<JCExpression>nil(), formatExpression, formatArgs);



List <JCExpression> printlnArgs = List .nil();

printlnArgs.append(format);



JCExpression print = maker.Apply( List .<JCExpression>nil(), printlnExpression, printlnArgs);

JCExpressionStatement stmt = maker.Exec(print);



List <JCStatement> stmts = List .nil();

stmts.append(stmt);



return maker.Block(0, stmts);

}

}



* This source code was highlighted with Source Code Highlighter .








javaコンパイラがコントロールクラスを使用するには、META-INF / javax.annotation.processing.Processorファイルを作成する必要があります。このファイルには、次の行を記述する必要があります。

annotations.time.TimeAnnotationProcessor



その後、すべてのファイルをannotations.jarに収集し、プロジェクトのクラスパスに追加します。



ここで、メソッドの実行時間を計算するには、このメソッドにTime注釈を追加するだけです。実行後、メソッドに費やされた時間がコンソールに表示されます。

@Time(format= "method time: %s ms" )

public void start() {

Thread.sleep(1000);

}



* This source code was highlighted with Source Code Highlighter .








その結果、コンソールに次のように表示されます。

メソッド時間:1000ミリ秒



UPD:コードにコメントを追加しました

UPD2:注釈パラメーターで指定された値よりも大きい場合にのみ、System.errのメソッドが費やした時間を表示するように例を修正することもできます。 これは、プロファイラーを使用することが必ずしも便利ではない本番サーバーに役立つ可能性があります。



All Articles