最近做项目时,接入了四个极其重要的外部接口。为了核对接口的准确性,把这四个接口的所有入参和返回值都打了日志。完全是重复性的机械劳动。端午节的时候回想这件事,发现AOP不正是职业解决这种事情的吗!而且用AOP打日志,原有的业务代码省去了日志代码,更加精简。
先写了个AOP的demo体验下功能。
导入aop相关的依赖
虽然没有显式引入cglib.jar,但是spring-aop-3.2.jar已经集成了cglib的功能。所以我的服务类没有实现接口,但依然能实现AOP的功能。
<properties>
<v.spring>3.2.0.RELEASE</v.spring>
<v.fastjson>1.1.36</v.fastjson>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${v.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${v.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${v.spring}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${v.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${v.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${v.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${v.spring}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${v.fastjson}</version>
</dependency>
</dependencies>
需要被切入的服务类
package com.jd.ls;
import org.springframework.stereotype.Service;
/**
* Created by Administrator on 2016/6/8.
*/
@Service
public class Hello {
public String hello(String name) {
String s = "hello, " + name;
System.out.println(s);
return s;
}
public void throwEx() {
throw new NullPointerException();
}
}
AOP切面类
package com.jd.ls;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Created by Administrator on 2016/6/8.
*/
@Component
@Aspect
public class AopTest {
//如果要设置多个切点可以使用 || 拼接
@Pointcut("execution(* com.jd.ls.Hello.*(..))")
private void anyMethod() {
}
@Before(value = "anyMethod()")
public void doBefore(JoinPoint joinPoint) {
System.out.println("前置通知");
System.out.println("方法签名:" + joinPoint.getSignature().toLongString());
}
@AfterReturning(value = "anyMethod()", returning = "result")
public void doAfter(JoinPoint jp, String result) {
System.out.println("后置通知, result:\t" + result);
}
@After("anyMethod()")
public void after() {
System.out.println("最终通知");
}
@AfterThrowing(value = "anyMethod()", throwing = "e")
public void doAfterThrow(JoinPoint joinPoint, Throwable e) {
e.printStackTrace();
}
@Around("anyMethod()")
public Object doBasicProfiling(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("进入环绕通知");
System.out.println("目标类名称:" + joinPoint.getTarget().getClass().getName());
System.out.println("方法签名:" + joinPoint.getSignature().toLongString());
System.out.println("方法参数:");
for (Object o : joinPoint.getArgs()) {
System.out.println(o.getClass().getName() + ":\t" + o.toString());
}
System.out.println("target" + joinPoint.getTarget());
System.out.println("staticPart:" + joinPoint.getStaticPart().toShortString());
System.out.println("kind:" + joinPoint.getKind());
System.out.println("sourceLocation:" + joinPoint.getSourceLocation());
Object object = joinPoint.proceed();// 执行该方法
System.out.println("执行结果" + JSON.toJSONString(object));
System.out.println("退出方法");
System.out.println(joinPoint.toLongString());
return object;
}
}
@Pointcut的路径参数指定了哪些类的哪些方法会作为切入点进行横向编程。而@Before,@After等注解则指定在什么时机织入代码。
spring配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<context:component-scan base-package="com.jd.ls"/>
<!-- 打开aop 注解 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
单元测试
package com.jd.ls;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class AppTest {
@Autowired
Hello hello;
@Test
public void testAop() {
System.out.println("====================启动服务");
hello.hello("world");
System.out.println("====================终止服务");
}
@Test
public void testAopEx() {
System.out.println("====================启动服务");
hello.throwEx();
System.out.println("====================终止服务");
}
}
测试结果
方法1的运行结果
====================启动服务
进入环绕通知
目标类名称:com.jd.ls.Hello
方法签名:public java.lang.String com.jd.ls.Hello.hello(java.lang.String)
方法参数:
java.lang.String: world
targetcom.jd.ls.Hello@4ac216
staticPart:execution(Hello.hello(..))
kind:method-execution
sourceLocation:org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@1777b1
前置通知
方法签名:public java.lang.String com.jd.ls.Hello.hello(java.lang.String)
hello, world
执行结果"hello, world"
退出方法
execution(public java.lang.String com.jd.ls.Hello.hello(java.lang.String))
最终通知
后置通知, result: hello, world
====================终止服务
方法二的运行结果
====================启动服务
进入环绕通知
目标类名称:com.jd.ls.Hello
方法签名:public void com.jd.ls.Hello.throwEx()
方法参数:
targetcom.jd.ls.Hello@4ac216
staticPart:execution(Hello.throwEx())
kind:method-execution
sourceLocation:org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@1777b1
前置通知
方法签名:public void com.jd.ls.Hello.throwEx()
最终通知
java.lang.NullPointerException
at com.jd.ls.Hello.throwEx(Hello.java:18)
at com.jd.ls.Hello$$FastClassByCGLIB$$3f78c6d2.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:50)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
at com.jd.ls.AopTest.doBasicProfiling(AopTest.java:59)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:42)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:50)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
at com.jd.ls.Hello$$EnhancerByCGLIB$$d4ecbb7c.throwEx(<generated>)
at com.jd.ls.AppTest.testAopEx(AppTest.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
方法二抛了异常,异常的堆栈在AOP的切面代码里被输出。