Spring -- AOP入门基础&基于Aspect的AOP通知用法
来源:互联网 发布:js实现select 编辑:程序博客网 时间:2024/05/22 06:39
动态代理
我们在日常开发过程中是否会遇到下图中的这种状况
红框中的是我们要输出的日志,你是否发现,日志中大部分信息都是相同的,并且如果我们要修改一个地方,所有的地方都需要改,而且代码看起来还比较冗余
下面我们就可以通过动态代理的方式解决这个问题
看下代码
public interface Calculation { public int add(int x, int y); public int sub(int x, int y); public int mul(int x, int y); public int dev(int x, int y);}
定义接口,加减乘除方法。
public class CalculationImpl implements Calculation { @Override public int add(int x, int y) { int result = x + y; return result; } @Override public int sub(int x, int y) { int result = x - y; return result; } @Override public int mul(int x, int y) { int result = x * y; return result; } @Override public int dev(int x, int y) { int result = x / y; return result; }}
具体实现类,这里我们看到,没有植入日志。
public class CalculationProxy { private Calculation calculation = null; CalculationProxy(Calculation calculation) { this.calculation = calculation; } public Calculation getCalculationLog() { Calculation proxy = null; ClassLoader loader = calculation.getClass().getClassLoader(); Class[] interfaces = new Class[] { Calculation.class }; InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out .println("GP-->invoke begin , execute method is " + method.getName() + ", args is " + Arrays.asList(args)); Object obj = method.invoke(calculation, args); return obj; } }; proxy = (Calculation) Proxy.newProxyInstance(loader, interfaces, h); return proxy; }}
动态代理类的实现,在实现invoke方法的时候,我们输出日志在调用目标方法之前。
测试
public class Main { public static void main(String[] args) { Calculation calculation = new CalculationImpl(); CalculationProxy calculationProxy = new CalculationProxy(calculation); Calculation cal = calculationProxy.getCalculationLog(); System.out.println(cal.add(1, 3)); System.out.println(cal.mul(1, 5)); }}
输出结果
GP–>invoke begin , execute method is add, args is [1, 3]
4
GP–>invoke begin , execute method is mul, args is [1, 5]
5
看起来是不是要清晰多了,我们将日志单独提取到动态代理的方法中,对核心的业务方法没有侵入,而且还便于我们的维护。
AOP简介
AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充.
AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点.
在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里.
AOP 的好处:
- 每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
- 业务模块更简洁, 只包含核心业务代码.
AOP术语
切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice): 切面必须要完成的工作
目标(Target): 被通知的对象
代理(Proxy): 向目标对象应用通知之后创建的对象
连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
基于Aspect的AOP – 前置通知
更多内容,参考:http://jinnianshilongnian.iteye.com/blog/1420689
涉及JAR包
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.1.jar
spring-aop-4.1.7.RELEASE.jar
spring-aspects-4.1.7.RELEASE.jar
spring-beans-4.1.7.RELEASE.jar
spring-context-4.1.7.RELEASE.jar
spring-core-4.1.7.RELEASE.jar
spring-expression-4.1.7.RELEASE.jar
看下代码
public interface Calculation { public int add(int x, int y); public int sub(int x, int y); public int mul(int x, int y); public int dev(int x, int y);}
@Componentpublic class CalculationImpl implements Calculation { @Override public int add(int x, int y) { int result = x + y; return result; } @Override public int sub(int x, int y) { int result = x - y; return result; } @Override public int mul(int x, int y) { int result = x * y; return result; } @Override public int dev(int x, int y) { int result = x / y; return result; }}
实现接口,并且使用@Component标记为IOC容器中的组件。
<context:component-scan base-package="com.gp.spring.aop.impl"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
加入扫描注解的包路径。
Spring默认不支持@AspectJ风格的切面声明,增加aspectj-autoproxy,这样Spring就能发现@AspectJ风格的切面并且将切面应用到目标对象。
@Aspect@Componentpublic class CalculationAspect { @Before("execution(public int com.gp.spring.aop.impl.Calculation.add(int, int))") public void beforeMethod(JoinPoint joinPoint) { String name = joinPoint.getSignature().getName(); List<Object> list = Arrays.asList(joinPoint.getArgs()); System.out.println("Method begin ... ,method=" + name + ", args = " + list); }}
- CalculationAspect 标记为Component组件,注册到IOC容器中
- @Aspect标记,spring对其进行AOP相关的配置,生成相应的代理类
- @Before,为前置通知,方法执行前的通知
- “execution(public int com.gp.spring.aop.impl.Calculation.add(int,
int))”表达式,表示此通知要执行的包路径、方法的相关信息。此表达式可进行模糊匹配,比如”execution(public int
com.gp.spring.aop.impl.Calculation.*(..))”,表示Calculation类下的所有方法。 - 方法中的参数JoinPoint joinPoint,表示连接点,通过此参数可以获取到执行方法的相关信息,比如方法名、方法参数。
执行测试方法
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Calculation calculation = (Calculation)context.getBean("calculationImpl"); int result = calculation.add(3, 4); System.out.println(result); }
输出结果
Method begin … ,method=add, args = [3, 4]
7
基于Aspect的AOP – 后置通知
后置通知与前置通知用法类似,区别就是在执行方法之后执行后置通知方法。
代码如下
@After("execution(public int com.gp.spring.aop.impl.Calculation.add(int, int))") public void afterMethod(JoinPoint joinPoint) { String name = joinPoint.getSignature().getName(); List<Object> list = Arrays.asList(joinPoint.getArgs()); System.out.println("Method end ... ,method=" + name + ", args = " + list); }
这里用的@After注解,其他都没变化。
测试输出结果如下
Method begin … ,method=add, args = [3, 4]
executeing …
Method end … ,method=add, args = [3, 4]
7
我在对add方法增加了一个输出,便于区分前置通知、后置通知
public int add(int x, int y) { int result = x + y; System.out.println("executeing ..."); return result; }
基于Aspect的AOP – 返回通知
方法执行成功后,调用返回通知,如果方法在运行过程中抛出异常,则不会调用。
代码
@AfterReturning(value = "execution(public int com.gp.spring.aop.impl.Calculation.add(int, int))", returning = "ret") public void afterReturnMethod(JoinPoint joinPoint, Object ret) { String name = joinPoint.getSignature().getName(); List<Object> list = Arrays.asList(joinPoint.getArgs()); System.out.println("@AfterReturning ... ,method=" + name + ", args = " + list + ", return = " + ret); }
与后置通知不同的是,后置通知在方法抛出异常,仍会被调用,这里我们可以使用try … catch … 进行类比。
输出结果
@Before … ,method=add, args = [3, 4]
executeing …
@After … ,method=add, args = [3, 4]
@AfterReturning … ,method=add, args = [1, 3], return = 4
7
基于Aspect的AOP – 异常通知
方法在运行过程中,如果抛出异常,则调用异常通知
代码
@AfterThrowing(value = "execution(public int com.gp.spring.aop.impl.Calculation.*(int, int))", throwing = "ex") public void afterThrowMethod(JoinPoint joinPoint, Exception ex) { System.out.println("@AfterThrowing ... ,ex = " + ex); }
此处与之前的用法有些却别,在注解的参数中增加了throwing,然后在方法中增加了要捕获的异常,此处类似于try…catch…的catch中代码块
输出结果
Exception in thread “main” @AfterThrowing … ,ex = java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
at com.gp.spring.aop.impl.CalculationImpl.dev(CalculationImpl.java:29)
基于Aspect的AOP – 环绕通知
环绕通知,类似与我们最开始讲解的动态代理,在此通知中你可以去调用目标方法,并在目标方法的上下做各种通知(前置、返回、后置、异常等处理)
代码:
@Around("execution(public int com.gp.spring.aop.impl.Calculation.add(int, int))") public Object aroundMethod(ProceedingJoinPoint pjd) { String name = pjd.getSignature().getName(); List<Object> list = Arrays.asList(pjd.getArgs()); Object obj = null; System.out.println("前置通知 ... ,method=" + name + ", args = " + list); try { obj = pjd.proceed(); System.out.println("返回通知 ... ,method=" + name + ", args = " + list); } catch (Throwable e) { System.out.println("异常通知 ... , exception = " + e); e.printStackTrace(); } System.out.println("后置通知 ... ,method=" + name + ", args = " + list); return obj; }
返回结果:
前置通知 … ,method=add, args = [1, 3]
executeing …
返回通知 … ,method=add, args = [1, 3]
后置通知 … ,method=add, args = [1, 3]
4
基于Aspect的AOP – 切入方法优先级
下面我们在来增加一个前置通知,创建一个新类
代码如下
@Aspect@Componentpublic class ValidateAspect { @Before("execution(public int com.gp.spring.aop.impl.Calculation.add(int, int))") public void validate(){ System.out.println("验证方法 com.gp.spring.aop.impl.ValidateAspect"); }}
此类我们做了一个验证。
然后运行测试方法(代码复用基于Aspect的AOP – 环绕通知)
前置通知 … ,method=add, args = [1, 3]
验证方法 com.gp.spring.aop.impl.ValidateAspect
executeing …
返回通知 … ,method=add, args = [1, 3]
后置通知 … ,method=add, args = [1, 3]
@AfterReturning … ,method=add, args = [1, 3], return = 4
4
验证方法 com.gp.spring.aop.impl.ValidateAspect,输出了我们新增加的切入方法,那么这里的先后顺序怎么制定呢,我们可以使用@order(1)
如下
@Order(1)@Aspect@Componentpublic class ValidateAspect {}
@Order(1)表示优先执行此切入方法。数值越小,优先级越高。
基于Aspect的AOP – 重用切点表达式
我们之前学习过,定义一个切面方法,要在其注解中,增加切入的目标方法信息,那如果多个方法都切入同样的目标方法的时候,我们可不可以将相同的目标方法信息提取出来呢。
下面我们演示下代码(复用之前代码)
@Order(2)@Aspect@Componentpublic class CalculationAspect { @Pointcut("execution(public int com.gp.spring.aop.impl.Calculation.*(..))") public void aspectName(){ } @AfterReturning(value = "aspectName()", returning = "ret") public void afterReturnMethod(JoinPoint joinPoint, Object ret) { String name = joinPoint.getSignature().getName(); List<Object> list = Arrays.asList(joinPoint.getArgs()); System.out.println("@AfterReturning ... ,method=" + name + ", args = " + list + ", return = " + ret); } @Around("aspectName()") public Object aroundMethod(ProceedingJoinPoint pjd) { String name = pjd.getSignature().getName(); List<Object> list = Arrays.asList(pjd.getArgs()); Object obj = null; System.out.println("前置通知 ... ,method=" + name + ", args = " + list); try { obj = pjd.proceed(); System.out.println("返回通知 ... ,method=" + name + ", args = " + list); } catch (Throwable e) { System.out.println("异常通知 ... , exception = " + e); e.printStackTrace(); } System.out.println("后置通知 ... ,method=" + name + ", args = " + list); return obj; } }
我们定义了一个aspectName方法,然后增加@Pointcut(“execution(public int com.gp.spring.aop.impl.Calculation.*(..))”)注解。
然后后续,直接调用此方法名即可。
如果在其他类中呢,增么调用aspectName方法,代码如下
@Order(1)@Aspect@Componentpublic class ValidateAspect { @Before("com.gp.spring.aop.impl.CalculationAspect.aspectName()") public void validate(){ System.out.println("验证方法 com.gp.spring.aop.impl.ValidateAspect"); }}
方法所在类的全路径名即可。
运行结果
验证方法 com.gp.spring.aop.impl.ValidateAspect
前置通知 … ,method=add, args = [1, 3]
executeing …
返回通知 … ,method=add, args = [1, 3]
后置通知 … ,method=add, args = [1, 3]
@AfterReturning … ,method=add, args = [1, 3], return = 4
4
- Spring -- AOP入门基础&基于Aspect的AOP通知用法
- Spring AOP @Aspect用法
- 基于@Aspect的AOP
- Spring AOP 的@Aspect
- spring-aop-@Aspect详细用法
- spring-aop-@Aspect详细用法
- 第三章 AOP 基于@Aspect的AOP
- 基于@Aspect的AOP配置
- 基于@Aspect的AOP配置
- 基于@Aspect的AOP配置
- 基于@Aspect的AOP配置
- 基于@Aspect的AOP配置
- 基于@Aspect的AOP配置
- 基于@Aspect的AOP配置
- 基于@Aspect的AOP配置
- spring的aop:advisor和aop:aspect
- spring 基于Aspect和注解的切面编程(aop)
- Spring框架 基于@Aspect的AOP配置(六)
- easyui-combobox加载json中对象的属性(转)
- ScrollView的滑动时的下拉距离的响应事件。
- 利用Arcengine 直接加载shp文件
- [技巧心得] CSS、DIV命名规则
- python 得到web传递参数
- Spring -- AOP入门基础&基于Aspect的AOP通知用法
- 数据结构与算法-线性表的定义与特点
- adb 设置远程调试
- 区间最值的问题
- PAT (Advanced Level) 1066. Root of AVL Tree (25) AVL树的插入建树
- DCMTK开源库:程序包简介+支持的一些信息
- 【图论】【二分图匹配】[POJ 3041]I'm Telling the Truth
- mybatis入门基础(七)----延迟加载
- SQL Server索引(1)-- 基础