一、为什么会有AOP

在日常的开发中经常会有这样一种场景,支付相关的业务中经常需要记录日志,而记录的日志大体相同;这样就会造成支付组件和日志组件强关联,耦合在一起了。而AOP的出现就是为了解决这种问题的。

二、什么是AOP

AOP里面有这样几个概念:目标对象、切点、切面支持类、切面、通知。AOP为开发者提供一种横切关注点(如日志组件横切了支付组件)分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而解耦了相关组件。

三、认识AOP

使用AOP功能需要的jar包如下:

1、接口类:

public interface IHelloWorldService
{
public boolean sayHello();
}

2、接口实现类:

public class HelloWorldService implements IHelloWorldService
{
public boolean sayHello()
{
System.out.println("=======Hello World:");
return true;
}
}

3、切面支持类:该类就是通知的具体实现,切面是切点+通知的组合。

public class HelloWorldAspect
{
public void beforeAdvice()
{
System.out.println("=======before advice:");
} public void afterFinalAdvice()
{
System.out.println("========after advice" );
}
}
HelloWorldAspect并不是真正的切面实现,而是定义通知实现的类,我们可以将它看做是缺少了切入点的切面。

4、Spring配置文件

<bean id="helloWorldService" class="aop.HelloWorldService"/>

   <bean id="aspect" class="aop.HelloWorldAspect"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>
<aop:aspect ref="aspect">
<aop:before pointcut-ref="pointcut" method="beforeAdvice()"/>
<aop:after pointcut="execution(* aop..*.*(..))" method="afterFinalAdvice"/>
</aop:aspect>
</aop:config>

1、<aop:config>:AOP相关的功能都在该标签下面

2、<aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>

切点的定义,这种定义的方式可以让多个切面通过id的形式重复引用,expression表达式表示的是匹配aop包(包含子包)下任何类的任何方法。

3、<aop:aspect ref="aspect">

标签<aop:aspect>用来定义切面,其中的ref用来引用切面支持类的方法。

4、<aop:before pointcut-ref="pointcut" method="beforeAdvice()"/>

这里定义的是前置通知,包含切点和通知里面的具体方法。其中定义切点有两种方式:pointcut-ref="pointcut"或者pointcut="execution(* aop..*.*(..))",两者只能选择其一。而method用来引用切面通知实现类中的方法。

5、测试类:

public class AopTest
{
public static void main(String[] args) throws Exception
{
ApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
IHelloWorldService bean =
ctx.getBean("helloWorldService",IHelloWorldService.class);
bean.sayHello();
}
}

6、执行结果:

=======before advice:
=======Hello World:
========after advice

从结果可以看出来,尽管DEMO很简单,但是实现了关联组件的解耦。可以通过定义切面的方式,让目标方法在执行前后,执行我们的通知方法。

四、Spring的通知类型

在前面的实例中,用到了前置通知和后置最终通知两种类型。在现实情况中,组件组合的方式不仅要有很多种,而且还要很灵活。这就要求Spring对切面有不同形式的定义,所以Spring给我们带来了如下几种通知:

1、前置通知:在切入点选择的连接点处的方法执行之前的通知,该通知不影响正常程序的执行流程。

2、后置最终通知:在切入点选择的连接点处的方法返回时执行的通知,不管业务方法是否抛出异常,都会调用该通知,相当于finally块。

3、后置返回通知:在切入点选择的连接点处的方法正常执行完毕时执行的通知(连接点处方法执行完毕后没有抛出任何异常才会去执行该通知)

4、后置异常通知:在切入点选择的连接点处的方法抛出异常返回时执行的通知。(连接点处的方法抛异常才会执行该通知)

5、环绕通知:环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。

我们现在看来下这几种通知的具体实现:

1)前置通知

这次演示前置通知如何传递参数:

目标类:

public class HelloWorldService implements IHelloWorldService
{
public boolean sayHello(String str)
{
System.out.println("=======Hello World: str=" + str);
return true;
}
}

切面支持类:

public class HelloWorldAspect
{
public void beforeAdvice(String str)
{
System.out.println("=======before advice: str=" + str);
} public void afterFinalAdvice()
{
System.out.println("========after advice" );
}
}

切面定义:

<bean id="helloWorldService" class="aop.HelloWorldService"/>
<bean id="aspect" class="aop.HelloWorldAspect"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* aop..*.*(..)) and args(param)"/>
<aop:aspect ref="aspect">
<aop:before pointcut-ref="pointcut" method="beforeAdvice(java.lang.String)" arg-names="param"/>
</aop:aspect>
</aop:config>

切面的配置最关键,这里的切点的表达式定义在原有的基础上新增了一个参数,参数的类型与通知实现方式中的类型相同。

而前置通知里面方法的类型为String,arg-names="param"的作用是切入点中使用“args(param)”匹配的目标方法参数将自动传递给通知实现方法同名参数。

测试类:

public class AopTest
{
public static void main(String[] args) throws Exception
{
ApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
IHelloWorldService bean =
ctx.getBean("helloWorldService",IHelloWorldService.class);
bean.sayHello("hangzhou");
}
}

执行结果:

=======before advice: str=hangzhou
=======Hello World: str=hangzhou

 2)后置返回通知

目标类:目标方法返回值为true

public class HelloWorldService implements IHelloWorldService
{
public boolean sayHello()
{
System.out.println("=======Hello World");
return true;
}
}

通知实现类,这里接收

public class HelloWorldAspect
{
public void beforeAdvice(String str)
{
System.out.println("=======before advice: str=" + str);
} public void afterFinalAdvice(Object arg)
{
System.out.println("========after advice.arg=" + arg);
}
}

切面配置:其中arg-names="retVal"表示指定通知方法的参数名为“retVal”;returning="retVal"用于将目标方法的返回值赋给通知实现方法中参数名为“retVal”的参数

 <bean id="helloWorldService" class="aop.HelloWorldService"/>
<bean id="aspect" class="aop.HelloWorldAspect"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>
<aop:aspect ref="aspect">
<aop:after-returning pointcut-ref="pointcut"
method="afterFinalAdvice" arg-names="retVal" returning="retVal"/>
</aop:aspect>
</aop:config>

执行结果:

=======Hello World
========after advice.arg=true

这里可以看到目标方法的返回值赋给了通知实现方法中的参数。

3)后置异常通知

目标类:

public class HelloWorldService implements IHelloWorldService
{
public boolean sayHello()
{
System.out.println("=======Hello World");
throw new RuntimeException();
}
}

通知实现类:

public class HelloWorldAspect
{
public void beforeAdvice(String str)
{
System.out.println("=======before advice: str=" + str);
} public void afterFinalAdvice(Exception arg)
{
System.out.println("========after advice.arg=" + arg );
}
}

切面定义部分:这里与参数传递类似,目标类抛出异常,通知方法才会被执行。且这里通过参数传递的方式,实现了将目标方法抛出的异常作为参数传递到通知实现方法中

<aop:config>
<aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>
<aop:aspect ref="aspect">
<aop:after-throwing pointcut-ref="pointcut"
method="afterFinalAdvice" arg-names="retVal" throwing="retVal"/>
</aop:aspect>
</aop:config>

执行结果:

=======Hello World
========after advice.arg=java.lang.RuntimeException

 4)环绕通知:环绕通知时十分强大,可以决定目标方法是否执行、什么时候执行、执行时是否需要替换方法参数、执行完毕后是否需要替换返回参数。

环绕通知方法中的第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型,在通知实现方法内部使用ProceedingJoinPoint的proceed()方法使目标方法执行,proceed方法可以传入可选的Object[]数组,该数组的值将作为目标方法执行时的参数。

目标类:

public class HelloWorldService implements IHelloWorldService
{
public boolean sayHello()
{
System.out.println("=======Hello World");
throw new RuntimeException();
} @Override
public void around(String param)
{
System.out.println("======Hello Around param:" + param);
}
}

通知实现类片段:

public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable
{
Object retVal = pjp.proceed(new Object[] {"hangzhou"});
System.out.println("===========around after advice");
return retVal;
}

切面定义部分:

<aop:config>
<aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>
<aop:aspect ref="aspect">
<aop:around pointcut-ref="pointcut"
method="aroundAdvice"/>
</aop:aspect>
</aop:config>

测试类:

  public static void main(String[] args) throws Exception
{
ApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
IHelloWorldService bean =
ctx.getBean("helloWorldService",IHelloWorldService.class);
bean.around("haha");
}

执行结果:

======Hello Around param:hangzhou
===========around after advice

从结果看到,我们在测试类中传入的参数“haha”被通知实现类中的方法替换成了“hangzhou”。

这里要注意的是:Object retVal = pjp.proceed(new Object[] {"hangzhou"}); ,proceed方法用于执行目标方法,且目标方法的参数被new Object[]该数组替换,最终返回retVal字段。

引入

Spring允许为目标类引入新的接口,通过在< aop:aspect>标签内使用< aop:declare-parents>标签进行引入

引入新增接口:

public interface IProductService
{
public void product();
}

接口实现类:

public class ProductService implements IProductService
{
@Override
public void product()
{
System.out.println("==========hello product");
}
}

配置文件:types-matching表示匹配接口IHelloWorldService的实现类,如HelloWorldService;implement-interface表示引入的接口;default-impl表示引入接口的实现。

<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="aop.IHelloWorldService+"
implement-interface="aop.IProductService"
default-impl="aop.ProductService"/>
</aop:aspect>
</aop:config>

测试类:ctx.getBean("helloWorldService", IProductService.class);可以直接获取到引入的接口。

public static void main(String[] args) throws Exception
{
ApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
// IHelloWorldService bean =
// ctx.getBean("helloWorldService",IHelloWorldService.class);
// bean.around("haha");
IProductService productService =
ctx.getBean("helloWorldService", IProductService.class);
productService.product();
}

执行结果:

==========hello product

Spring AOP的解读的更多相关文章

  1. 详细解读 Spring AOP 面向切面编程(二)

    本文是<详细解读 Spring AOP 面向切面编程(一)>的续集. 在上篇中,我们从写死代码,到使用代理:从编程式 Spring AOP 到声明式 Spring AOP.一切都朝着简单实 ...

  2. 详细解读 Spring AOP 面向切面编程(一)

    又是一个周末, 今天我要和大家分享的是 AOP(Aspect-Oriented Programming)这个东西,名字与 OOP 仅差一个字母,其实它是对 OOP 编程方式的一种补充,并非是取而代之. ...

  3. spring源码解读-aop

    aop是指面向切面编程,ProxyFactoryBean是spring aop的底层实现与源头,为什么这么说呢?首先我们看一段配置: 1.target是目标对象,需要对其进行切面增强 2.proxyI ...

  4. Spring源码解读(二):Spring AOP

    一.AOP介绍 面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP).OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面.方面实现了诸如跨越多种类型和对象的事务 ...

  5. spring aop 源码解读之我见

    spring aop 都是动态代理,分为jdk代理和cglib代理.默认的情况下,如果类有实现了接口,使用jdk代理.如果没有实现接口,则使用cglib代理.在下面的代码中,我会标明对应的这段代码. ...

  6. Spring AOP高级——源码实现(2)Spring AOP中通知器(Advisor)与切面(Aspect)

    本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/Spring%20AOP%E9%A ...

  7. spring AOP 之二:@AspectJ注解的3种配置

    @AspectJ相关文章 <spring AOP 之二:@AspectJ注解的3种配置> <spring AOP 之三:使用@AspectJ定义切入点> <spring ...

  8. 《Spring 5官方文档》 Spring AOP的经典用法

    原文链接 在本附录中,我们会讨论一些初级的Spring AOP接口,以及在Spring 1.2应用中所使用的AOP支持. 对于新的应用,我们推荐使用 Spring AOP 2.0来支持,在AOP章节有 ...

  9. Spring Aop 详解二

    这是Spring Aop的第二篇,案例代码很详解,可以查看https://gitee.com/haimama/java-study/tree/master/spring-aop-demo. 阅读前,建 ...

随机推荐

  1. SQL数据库文件修复/用友/金蝶/管家婆/速达/思讯数据库恢复 硬盘恢复

    硬盘的故障情况可以分为以下几类: 1.控制电路故障 大部分外电路的问题是电源芯片或主轴驱动芯片烧坏引起的,由于硬盘电路板质量问题.设计缺陷.市电波动.突然断电.芯片老化或者散热不良.静电等原因造成芯片 ...

  2. bzoj3809

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3809 题目大意: Autumn和Bakser又在研究Gty的妹子序列了!但他们遇到了一个难题 ...

  3. shell 创建带参数的命令方法

    主要用到case in,和shift命令. shift 命令是从参数数组中,删除当前第一个参数. while [ "$1" != "" ] do case $1 ...

  4. iOS 之 获取View所在控制器

    1. UIResponder UIViewController *uvc; UIResponder* nextResponder = [self.superview.superview.supervi ...

  5. iconfont.cn阿里巴巴矢量图下载字体图标实战

    1.阿里巴巴矢量图网址:www.iconfont.cn 2.然后用新浪微博账号登录 3.输入要查找的图标相应的关键字,回车 4.滑过要找的图标,点击购物车,让图标存储到暂存架中 5.点击暂存架,存储为 ...

  6. 内容页获取当前栏目的上一级栏目ID

    发现V9在列表页可以用$parentid 得到当前栏目的父级ID,但在内容页这个函数的值却为空了,需要按下面的写法才行, V9 在内容页获取当前栏目的上一级栏目ID {$CAT[parentid]} ...

  7. WPF学习之数据绑定

    WPF中的数据绑定提供了很强大的功能.与普通的WinForm程序相比,其绑定功能为我们提供了很多便利,例如Binding对象的自动通知/刷新,Converter,Validation Rules,Tw ...

  8. EntityFramework Core解决并发详解

    前言 对过年已经无感,不过还是有很多闲暇时间来学学东西,这一点是极好的,好了,本节我们来讲讲EntityFramewoek Core中的并发问题. 话题(EntityFramework Core并发) ...

  9. loadrunner controller:实时查看VUser的运行情况

    1)         如下图,在Run标签页,点击"Vusers..."打开Vuser窗口: 2)         如下图选中一个Vuser点击按钮可以打开Run-Time Vie ...

  10. jdbcTemplate获取mysql的blob

    java使用jdbcTemplate操作mysql数据库,遇到blob字段,正常读取出来是blob的地址,而不是blob的内容. 下面采用2中方式读取blob的内容: 1.以二进制数组形式读取blob ...