Spring_02 快速入门 AOP理解&使用

目录

一、对AOP的概念

二、AOP中的相关概念

专业的词汇概念定义:

                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​​编辑三、其他的一些内容

四、AOP之问题解决 

本章目标

aop的简介

前置通知

后置通知

环绕通知

异常通知

过滤通知

五、思维导图


一、对AOP的概念

        在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

然后我们举一个比较容易理解的例子(来自:Spring 之 AOP):

        要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。

        我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。

按照正常的逻辑,我们可以这么做。


        这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。

 

 

 


        同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。

 

        这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。
红框处,就是面向切面编程。

二、AOP中的相关概念

专业的词汇概念定义:

Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 Advice 的目标对象.。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程


然后举一个容易理解的例子:
        看完了上面的理论部分知识, 我相信还是会有不少朋友感觉到 AOP 的概念还是很模糊, 对 AOP 中的各种概念理解的还不是很透彻. 其实这很正常, 因为 AOP 中的概念是在是太多了, 我当时也是花了老大劲才梳理清楚的.
        下面我以一个简单的例子来比喻一下 AOP 中 Aspect, Joint point, Pointcut 与 Advice之间的关系.
        让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.

来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系.
        首先我们知道, 在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
为什么可以这样类比呢?

        Joint point : 爪哇的小县城里的百姓: 因为根据定义, Joint point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 Joint point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.

        Pointcut :男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 Pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.

        Advice :抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.

        Aspect : Aspect 是 point cut 与 Advice 的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个 Aspect.

最后是一个描述这些概念之间关系的图:


三、其他的一些内容

 


        AOP中的Joinpoint可以有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。也就是说在AOP的概念中我们可以在上面的这些Joinpoint上织入我们自定义的Advice,但是在Spring中却没有实现上面所有的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint。

Advice 的类型

        before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)

        after return advice, 在一个 join point 正常返回后执行的 advice

        after throwing advice, 当一个 join point 抛出异常后执行的 advice
        after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
        around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
        introduction,introduction可以为原有的对象增加新的属性和方法。
在Spring中,通过动态代理和动态字节码技术实现了AOP。

四、AOP之问题解决 

本章目标

  1. aop的简介及解决的问题
  2. 前置通知
  3. 后置通知
  4. 环绕通知
  5. 异常通知
  6. 过滤通知

通过以下案例进行讲解:

前置通知

实现org.springframework.aop.MethodBeforeAdvice接口

买书、评论前加系统日志

后置通知

实现org.springframework.aop.AfterReturningAdvice接口

买书返利(存在bug)

环绕通知

org.aopalliance.intercept.MethodInterceptor

类似拦截器,会包括切入点,目标类前后都会执行代码。

异常通知

org.springframework.aop.ThrowsAdvice

出现异常执行系统提示,然后进行处理。价格异常为例

过滤通知(适配器)

org.springframework.aop.support.RegexpMethodPointcutAdvisor

处理买书返利的bug

aop的简介

解决的问题:解决了需求的改变,造成了原有没必要改变的代码,需要去改变它;

比如:书籍的增删改,本身只需要完成增删改的功能即可,这是如果需要添加日志功能,那么需要在原有的代码基础上,去修改添加日志功能,受牵连的方法就三个(add/edit/del)了;

AOP中关键性概念

连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.

目标(Target):被通知(被代理)的对象

注1:完成具体的业务逻辑

通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)

注2:完成切面编程

代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),

             例子:外科医生+护士

注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的

切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。

                 (也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)

    

适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)

前置通知

目标

package com.zking.aop.biz.impl;

import com.zking.aop.biz.IBookBiz;

import com.zking.aop.exception.PriceException;

public class BookBizImpl implements IBookBiz {

public BookBizImpl() {

super();

}

public boolean buy(String userName, String bookName, Double price) {

// 通过控制台的输出方式模拟购书

if (null == price || price <= 0) {

throw new PriceException("book price exception");

}

System.out.println(userName + " buy " + bookName + ", spend " + price);

return true;

}

public void comment(String userName, String comments) {

// 通过控制台的输出方式模拟发表书评

System.out.println(userName + " say:" + comments);

}

}

package com.zking.aop.biz;

public interface IBookBiz {

// 购书

public boolean buy(String userName, String bookName, Double price);

// 发表书评

public void comment(String userName, String comments);

}

通知

package com.zking.aop.advice;

import java.lang.reflect.Method;

import java.util.Arrays;

import org.springframework.aop.MethodBeforeAdvice;

/**

 * 买书、评论前加系统日志

 * @author Administrator

 *

 */

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {

@Override

public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {

// 在这里,可以获取到目标类的全路径及方法及方法参数,然后就可以将他们写到日志表里去

String target = arg2.getClass().getName();

String methodName = arg0.getName();

String args = Arrays.toString(arg1);

System.out.println("【前置通知:系统日志】:"+target+"."+methodName+"("+args+")被调用了");

}

}

<!-- 目标 -->

<bean class="com.zking.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>

<!-- 前置通知 -->

<bean class="com.zking.aop.advice.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean>

<!-- 利用目标+通知生成代理对象 -->

<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">

<property name="target" ref="bookBiz"></property>

<property name="proxyInterfaces">

<list>

<value>com.zking.aop.biz.IBookBiz</value>

</list>

</property>

<property name="interceptorNames">

<list>

<value>myMethodBeforeAdvice</value>

</list>

</property>

</bean>

测试

package com.zking.aop.test;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.zking.aop.biz.IBookBiz;

public class AopTest {

@SuppressWarnings("resource")

public static void main(String[] args) {

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml");

// IBookBiz bean = (IBookBiz) applicationContext.getBean("bookBiz");

IBookBiz bean = (IBookBiz) applicationContext.getBean("bookProxy");

bean.buy("张三", "Java编程思想", 9.9);

bean.comment("张三", "太深奥了,看不懂");

}

}

后置通知

目的:实现买书返利的功能

package com.zking.aop.advice;

import java.lang.reflect.Method;

import java.util.Arrays;

import org.springframework.aop.AfterReturningAdvice;

/**

 * 买书返利

 * @author Administrator

 *

 */

public class MyAfterReturningAdvice implements AfterReturningAdvice {

@Override

public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {

String target = arg3.getClass().getName();

String methodName = arg1.getName();

String args = Arrays.toString(arg2);

System.out.println("【后置通知:买书返利】:"+target+"."+methodName+"("+args+")被调用了,"+"该方法被调用后的返回值为:"+arg0);

}

}

配置到spring上下文中

<!-- 后置通知 -->

<bean class="com.zking.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>

<!-- 利用目标+通知生成代理对象 -->

<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">

<property name="target" ref="bookBiz"></property>

<property name="proxyInterfaces">

<list>

<value>com.zking.aop.biz.IBookBiz</value>

</list>

</property>

<property name="interceptorNames">

<list>

<value>myMethodBeforeAdvice</value>

<value>myAfterReturningAdvice</value>

</list>

</property>

</bean>

测试结果

环绕通知

package com.zking.aop.advice;

import java.util.Arrays;

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

/**

 * 环绕通知

 * 包含了前置和后置通知

 *

 * @author Administrator

 *

 */

public class MyMethodInterceptor implements MethodInterceptor {

@Override

public Object invoke(MethodInvocation arg0) throws Throwable {

String target = arg0.getThis().getClass().getName();

String methodName = arg0.getMethod().getName();

String args = Arrays.toString(arg0.getArguments());

System.out.println("【环绕通知调用前:】:"+target+"."+methodName+"("+args+")被调用了");

// arg0.proceed()就是目标对象的方法

Object proceed = arg0.proceed();

System.out.println("【环绕通知调用后:】:该方法被调用后的返回值为:"+proceed);

return proceed;

}

}

Spring上下文配置

<!-- 环绕通知 -->

<bean class="com.zking.aop.advice.MyMethodInterceptor" id="myMethodInterceptor"></bean>

<!-- 利用目标+通知生成代理对象 -->

<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">

<property name="target" ref="bookBiz"></property>

<property name="proxyInterfaces">

<list>

<value>com.zking.aop.biz.IBookBiz</value>

</list>

</property>

<property name="interceptorNames">

<list>

<value>myMethodBeforeAdvice</value>

<value>myAfterReturningAdvice</value>

<value>myMethodInterceptor</value>

</list>

</property>

</bean>

测试结果

异常通知

package com.zking.aop.advice;

import org.springframework.aop.ThrowsAdvice;

import com.zking.aop.exception.PriceException;

/**

 * 出现异常执行系统提示,然后进行处理。价格异常为例

 * @author Administrator

 *

 */

public class MyThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(PriceException ex) {

System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!");

}

}

Spring上下文配置

<!-- 异常通知 -->

<bean class="com.zking.aop.advice.MyThrowsAdvice" id="myThrowsAdvice"></bean>

<!-- 利用目标+通知生成代理对象 -->

<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">

<property name="target" ref="bookBiz"></property>

<property name="proxyInterfaces">

<list>

<value>com.zking.aop.biz.IBookBiz</value>

</list>

</property>

<property name="interceptorNames">

<list>

<value>myMethodBeforeAdvice</value>

<value>myAfterReturningAdvice</value>

<value>myMethodInterceptor</value>

<value>myThrowsAdvice</value>

</list>

</property>

</bean>

测试结果如下

  

过滤通知

需求:处理买书返利的bug

在spring-context.xml文件中设置过滤通知

<!-- 过滤通知 -->

<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="myAfterReturningAdvicePlus">

<property name="advice" ref="myAfterReturningAdvice"></property>

<property name="pattern" value=".*buy"></property>

</bean>

<!-- 利用目标+通知生成代理对象 -->

<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy">

<property name="target" ref="bookBiz"></property>

<property name="proxyInterfaces">

<list>

<value>com.zking.aop.biz.IBookBiz</value>

</list>

</property>

<property name="interceptorNames">

<list>

<value>myMethodBeforeAdvice</value>

<!-- <value>myAfterReturningAdvice</value> -->

<value>myAfterReturningAdvicePlus</value>

<value>myMethodInterceptor</value>

<value>myThrowsAdvice</value>

</list>

</property>

</bean>

五、思维导图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值