- 特定処理にログを入れる
- 開始終了の処理時間を計測する
- 特定処理の呼び出し回数を計測する
今回は、spring mvc を使ってある特定の controller の処理結果をハンドリングし、例外が発生した場合、自動的にエラーオブジェクトを生成して結果を返すような処理を作ってみたので、備忘録的にまとめておきます。
controller は以下、入力を受けて service の処理結果をjsonとして返却します。
@Controller public class HelloController { private static Logger logger = LoggerFactory.getLogger(HelloController.class); @Autowired private HelloService service; @Procedure @RequestMapping(value="/hello/{type}", method={RequestMethod.GET}) public @ResponseBody Output<?> hello( @PathVariable("type") String type) throws Exception { logger.info("- start hello"); try { if ("exception".equalsIgnoreCase(type)) return new Output<Hello>(UUID.randomUUID().toString(), service.runOnException()); else if ("failure".equalsIgnoreCase(type)) return new Output<Hello>(UUID.randomUUID().toString(), service.runOnFailure()); return new Output<Hello>(UUID.randomUUID().toString(), service.runOnSuccess()); } finally { logger.info("- end hello"); } } }
output は以下のようなpojoです。
public class Output<T> { private String trxId; private int statusCode; private String statusMessage; private T data; public Output( String trxId, int statusCode, String statusMessage) { this.trxId = trxId; this.statusCode = statusCode; this.statusMessage = ""; } public Output( String trxId, T data) { this.trxId = trxId; this.statusCode = 200; this.data = data; this.statusMessage = ""; } public String getTrxId() { return trxId; } public void setTrxId(String trxId) { this.trxId = trxId; } public int getStatusCode() { return statusCode; } public void setStatusCode(int statusCode) { this.statusCode = statusCode; } public String getStatusMessage() { return statusMessage; } public void setStatusMessage(String statusMessage) { this.statusMessage = statusMessage; } public T getData() { return data; } public void setData(T data) { this.data = data; } public String toString() { return new StringBuilder().append("@").append(this.trxId).append("-[") .append(this.statusCode).append(": ") .append(this.statusMessage).append("], ").append(data) .toString(); } }
で、AOP を利用しない場合、呼び出し結果は以下のようになります。
10:52:40.811 [http-8080-2] INFO j.b.h.example.aop.HelloController - - start hello 10:52:40.845 [http-8080-2] INFO j.b.h.example.aop.HelloServiceImpl - success 10:52:40.845 [http-8080-2] INFO j.b.h.example.aop.HelloController - - end hello
このままでは例外発生時にエラー情報を返却できないです。
なので、hello() に対し、@Procedure というアノテーションを付けて、@Procedure がついた処理について例外が発生した場合、エラーのOutputを生成しレスポンスしたいと考えてみました。
@Component @Aspect public class ProcedureAspect { static Logger logger = LoggerFactory.getLogger(ProcedureAspect.class); @Pointcut("execution(* jp.blogspot.horiga3.*.*(..)) ") public void targetMethods() {} @Before("@annotation(jp.blogspot.horiga3.example.aop.Procedure)") public void preHandle() { logger.info("Aspect :: preHandle"); } @AfterReturning( pointcut="@annotation(jp.blogspot.horiga3.example.aop.Procedure)", returning="retVal") public void postHandle(Object retVal) { logger.info("Aspect :: postHandle, retVal={}", retVal != null ? retVal.toString() : "null"); } @Around("@annotation(jp.blogspot.horiga3.example.aop.Procedure)") public Object handle(ProceedingJoinPoint pjp) { logger.info("Aspect :: around - start"); Object[] args; try { args = pjp.getArgs(); return args == null ? pjp.proceed() : pjp.proceed(args); } catch (Throwable e) { logger.info("Aspect :: handleException"); int statusCode = 500; String statusMessage = "unknown"; if (e instanceof ProcedureException) { statusCode = ((ProcedureException) e).getStatusCode(); statusMessage = ((ProcedureException) e).getStatusMessage(); } else if (e instanceof IllegalArgumentException) { statusCode = 400; statusMessage = "Invalid parameter"; } Output<Object> error = new Output<Object>(UUID.randomUUID().toString(), statusCode, statusMessage); return error; } finally { logger.info("Aspect :: around - end"); } } }
spring の設定は、aop:aspectj-autoproxy を追加しただけ
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation= "http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <mvc:annotation-driven /> <aop:aspectj-autoproxy/> <context:component-scan base-package="jp.blogspot.horiga3.example.aop"/> </beans>
で、実行すると例外の場合もjsonを返却するようになりました。既存のControllerは何も修正していないですね。
10:52:40.768 [http-8080-2] INFO j.b.h.example.aop.ProcedureAspect - Aspect :: around - start 10:52:40.772 [http-8080-2] INFO j.b.h.example.aop.ProcedureAspect - Aspect :: preHandle 10:52:40.811 [http-8080-2] INFO j.b.h.example.aop.HelloController - - start hello 10:52:40.845 [http-8080-2] INFO j.b.h.example.aop.HelloServiceImpl - success 10:52:40.845 [http-8080-2] INFO j.b.h.example.aop.HelloController - - end hello 10:52:40.845 [http-8080-2] INFO j.b.h.example.aop.ProcedureAspect - Aspect :: around - end 10:52:40.845 [http-8080-2] INFO j.b.h.example.aop.ProcedureAspect - Aspect :: postHandle, retVal=@11d8c89a-6fc2-4e38-a745-f2ade9c3d6ff-[200: ], jp.blogspot.horiga3.example.aop.Hello@6dbf4a72
@AfterReturningが @Around より後に来ることは予想できたけど、@Around が @Before より先に来るんですね。