Spring 提供了很多的实现AOP的方式:Spring 接口方式,schema配置方式和注解.

本文重点介绍Spring使用接口方式实现AOP. 使用接口方式实现AOP以了解为目的. 更好地理解动态代理. 通常我们使用的更多的是使用注解的方式实现AOP

下面来看看如何实现接口方式的AOP

一. 环境准备

要在项目中使用Spring AOP 则需要在项目中导入除了spring jar包之外, 还需要引入aspectjrt.jar,aspectjweaver.jar,aopalliance.jar ,spring-aop-3.2.0.M2.jar和cglib.jar 

二、Spring接口方式实现AOP步骤

使用Spring aop接口方式实现aop, 可以通过自定义通知来供Spring AOP识别. 常见的自己定义通知有:前置通知, 后置通知, 返回通知, 异常通知, 环绕通知. 对应实现的接口是:

  • 前置通知: MethodBeforeAdvice
  • 后置通知: AfterAdvice
  • 返回通知:AfterReturningAdvice
  • 异常通知:ThrowsAdvice
  • 环绕通知:MethodInterceptor

实现步骤如下:

1. 业务接口实现

package com.lxl.www.aop.interfaceAop;

/**
* 使用接口方式实现AOP, 默认通过JDK的动态代理来实现. 非接口方式, 使用的是cglib实现动态代理
*
* 业务接口类-- 计算器接口类
*
* 定义三个业务逻辑方法
*/
public interface IBaseCalculate { int add(int numA, int numB); int sub(int numA, int numB); int div(int numA, int numB); int multi(int numA, int numB); int mod(int numA, int numB); }

2. 业务类

package com.lxl.www.aop.interfaceAop;//业务类,也是目标对象

import com.lxl.www.aop.Calculate;

import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service; /**
* 业务实现类 -- 基础计算器
*/
@Service
public class BaseCalculate implements IBaseCalculate { @Override
public int add(int numA, int numB) {
System.out.println("执行目标方法: add");
return numA + numB;
} @Override
public int sub(int numA, int numB) {
System.out.println("执行目标方法: sub");
return numA - numB;
} @Override
public int multi(int numA, int numB) {
System.out.println("执行目标方法: multi");
return numA * numB;
} @Override
public int div(int numA, int numB) {
System.out.println("执行目标方法: div");
return numA / numB;
} @Override
public int mod(int numA, int numB) {
System.out.println("执行目标方法: mod"); int retVal = ((Calculate) AopContext.currentProxy()).add(numA, numB);
return retVal % numA;
}
}

3. 通知类

前置通知

package com.lxl.www.aop.interfaceAop;

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* 定义前置通知
*/
@Component
public class BaseBeforeAdvice implements MethodBeforeAdvice { /**
*
* @param method 切入的方法
* @param args 切入方法的参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("===========进入beforeAdvice()============"); System.out.println("目标对象:" + target);
System.out.println("方法名: "+method); System.out.println("即将进入切入点方法");
} }

后置通知

package com.lxl.www.aop.interfaceAop;

import org.aspectj.lang.annotation.AfterReturning;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; public class BaseAfterReturnAdvice implements AfterReturningAdvice { /**
*
* @param returnValue 切入点执行完方法的返回值,但不能修改
* @param method 切入点方法
* @param args 切入点方法的参数数组
* @param target 目标对象
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("==========进入afterReturning()=========== \n");
System.out.println("切入点方法执行完成"); System.out.println("后置通知--目标对象:" + target);
System.out.println("后置通知--方法名: "+method);
System.out.println("后置通知--方法入参: "+ args.toString());
System.out.println("后置通知--方法返回值: "+ returnValue);
} }

异常通知

package com.lxl.www.aop.interfaceAop;

import org.springframework.aop.ThrowsAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method;
@Component
public class BaseAfterThrowsAdvice implements ThrowsAdvice { /**
* @param method 可选:切入的方法
* @param args 可选:切入的方法的参数
* @param target 可选:目标对象
* @param throwable 必填 : 异常子类,出现这个异常类的子类,则会进入这个通知。
*/
public void afterThrowing(Method method, Object[] args, Object target, Throwable throwable) {
System.out.println("出错啦");
}
}

环绕通知

package com.lxl.www.aop.interfaceAop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* 环绕通知
*/
@Component
public class BaseAroundAdvice implements MethodInterceptor { /**
* invocation :连接点
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("===========around环绕通知方法 开始==========="); // 调用目标方法之前执行的动作
System.out.println("环绕通知--调用方法之前: 执行"); // 调用方法的参数
Object[] args = invocation.getArguments();
// 调用的方法
Method method = invocation.getMethod();
// 获取目标对象
Object target = invocation.getThis();
System.out.println("输入参数:" + args[0] + ";" + method + ";" + target); // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
Object returnValue = invocation.proceed(); System.out.println("环绕通知--调用方法之后: 执行"); System.out.println("输出参数:" + args[0] + ";" + method + ";" + target + ";" + returnValue); System.out.println("===========around环绕通知方法 结束==========="); return returnValue;
} }

4. 自定义切点

package com.lxl.www.aop.interfaceAop;

/**
* 切点
*
* 继承NameMatchMethodPointcut类,来用方法名匹配
*/
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Component
public class Pointcut extends NameMatchMethodPointcut { private static final long serialVersionUID = 3990456017285944475L; @SuppressWarnings("rawtypes")
@Override
public boolean matches(Method method, Class targetClass) {
// 设置单个方法匹配
this.setMappedName("add");
// 设置多个方法匹配
String[] methods = { "add", "div" }; //也可以用“ * ” 来做匹配符号
// this.setMappedName("get*"); this.setMappedNames(methods); return super.matches(method, targetClass);
} }

5. 配置xml文件

<?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:p="http://www.springframework.org/schema/p"
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"
> <!-- ==============================aop配置================================ -->
<!-- 声明一个业务类 -->
<bean id="baseCalculate" class="com.lxl.www.aop.interfaceAop.BaseCalculate"/> <!-- 声明通知类 -->
<bean id="baseBefore" class="com.lxl.www.aop.interfaceAop.BaseBeforeAdvice"/>
<bean id="baseAfterReturn" class="com.lxl.www.aop.interfaceAop.BaseAfterReturnAdvice"/>
<bean id="baseAfterThrows" class="com.lxl.www.aop.interfaceAop.BaseAfterThrowsAdvice"/>
<bean id="baseAround" class="com.lxl.www.aop.interfaceAop.BaseAroundAdvice"/> <!-- 指定切点匹配类 -->
<bean id="pointcut" class="com.lxl.www.aop.interfaceAop.Pointcut"/> <!-- 包装通知,指定切点 -->
<bean id="matchBeforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<ref bean="pointcut"/>
</property>
<property name="advice">
<ref bean="baseBefore"/>
</property>
</bean> <!-- 使用ProxyFactoryBean 产生代理对象 -->
<bean id="businessProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理对象所实现的接口 ,如果有接口可以这样设置 -->
<property name="proxyInterfaces">
<value>com.lxl.www.aop.interfaceAop.IBaseCalculate</value>
</property> <!-- 设置目标对象 -->
<property name="target">
<ref bean="baseCalculate"/>
</property>
<!-- 代理对象所使用的拦截器 -->
<property name="interceptorNames">
<list>
<value>matchBeforeAdvisor</value>
<value>baseAfterReturn</value>
<value>baseAround</value>
</list>
</property>
</bean>
</beans>

6. 方法入口

package com.lxl.www.aop.interfaceAop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class InterfaceMainClass{ public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/aop.xml");
BaseCalculate calculate = (BaseCalculate) context.getBean("baseCalculate");
calculate.add(1, 3);
} }

三. 分析

各种类型通知的执行顺序: 前置方法会在切入点方法之前执行,后置会在切入点方法执行之后执行,环绕则会在切入点方法执行前执行同事方法结束也会执行对应的部分。主要是调用proceed()方法来执行切入点方法。来作为环绕通知前后方法的分水岭

在xml 配置 businessProxy这个bean的时候,ProxyFactoryBean类中指定了,proxyInterfaces参数。这里把他配置了IBaseCalculate接口。因为在项目开发过程中,往往业务类都会有对应的接口,以方便利用IOC解耦。但Spring AOP却也能支持没有接口的代理。这就是为什么需要导入cglib.jar包了。看过spring的源码,知道在目标切入对象如果有实现接口,spring会默认使用jdk动态代理来实现代理类。如果没有接口,则会通过cglib来实现代理类。

  这个业务类现在有 前置通知,后置通知,环绕三个通知同时作用,可能以及更多的通知进行作用。那么这些通知的执行顺序是怎么样的?就这个例子而言,同时实现了三个通知。在例 子xml中,则显示执行before通知,然后执行around的前处理,执行切点方法,再执行return处理。最后执行around的后处理。经过测 试,知道spring 处理顺序是按照xml配置顺序依次处理通知,以队列的方式存放前通知,以压栈的方式存放后通知。所以是前通知依次执行,后通知到切入点执行完之后,从栈里 在后进先出的形式把后通知执行。
  在实现过程中发现通知执行对应目标对象的整个类中的方法,如何精确到某个方法,则需要定义一个切点匹配的方式:spring提供了方法名匹配或正则方式来匹配。然后通过DefaultPointcutAdvisor来包装通知,指定切点。

 使用接口方式配置起来,可见代码还是非常的厚重的,定义一个切面就要定义一个切面类,然而切面类中,就一个通知方法,着实没有必要。所以Spring提供了,依赖aspectj的schema配置和基于aspectj 注解方式。这两种方式非常简单方便使用,也是项目中普遍的使用方式。

as

5.3 Spring5源码--Spring AOP使用接口方式实现的更多相关文章

  1. 5.2 Spring5源码--Spring AOP源码分析二

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  2. 5.2 spring5源码--spring AOP源码分析二--切面的配置方式

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  3. 5.1 Spring5源码--Spring AOP源码分析一

    目标: 1.什么是AOP, 什么是AspectJ, 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP 1.1 什么是 ...

  4. 5.2 spring5源码--spring AOP源码分析三---切面源码分析

    一. AOP切面源码分析 源码分析分为三部分 1. 解析切面 2. 创建动态代理 3. 调用 源码的入口 源码分析的入口, 从注解开始: 组件的入口是一个注解, 比如启用AOP的注解@EnableAs ...

  5. 1. spring5源码 -- Spring整体脉络 IOC加载过程 Bean的生命周期

    可以学习到什么? 0. spring整体脉络 1. 描述BeanFactory 2. BeanFactory和ApplicationContext的区别 3. 简述SpringIoC的加载过程 4. ...

  6. vc++获取网页源码之使用import+接口方式

    1.使用IWinHttpRequest获取网页源码 首先要创建基于对话框的mfc应用程序 2.import+接口方式 首先导入winhttp.dll,使用IWinHttpRequest接口 #impo ...

  7. Spring5源码解析-Spring框架中的单例和原型bean

    Spring5源码解析-Spring框架中的单例和原型bean 最近一直有问我单例和原型bean的一些原理性问题,这里就开一篇来说说的 通过Spring中的依赖注入极大方便了我们的开发.在xml通过& ...

  8. Spring5源码解析-论Spring DispatcherServlet的生命周期

    Spring Web框架架构的主要部分是DispatcherServlet.也就是本文中重点介绍的对象. 在本文的第一部分中,我们将看到基于Spring的DispatcherServlet的主要概念: ...

  9. Spring 源码学习——Aop

    Spring 源码学习--Aop 什么是 AOP 以下是百度百科的解释:AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程通过预编译的方式和运行期动态代理实 ...

随机推荐

  1. js练习题之查找数组中的位子

    输出描述: 如果数组中存在 item,则返回元素在数组中的位置,否则返回 -1 输入例子: indexOf([ 1, 2, 3, 4 ], 3) 输出例子: 2 function indexOf(ar ...

  2. svn“Previous operation has not finished; run 'cleanup' if it was interrupted“报错的解决方案

    今天SVN提交代码遇到"Previous operation has not finished; run 'cleanup' if it was interrupted"报错,&q ...

  3. 重置ubuntu13.04 密码

    方法如下: Restart Machine HOLD Shift Button ( You will get message "GRUB Loading") Select the ...

  4. 配置xenserver本地存储

    查询磁盘对应关系: [root@xenserver-eqtwbths ~]# ll /dev/disk/by-id/ total 0 lrwxrwxrwx 1 root root 9 Jun 5 13 ...

  5. elementui checkbox复选框实现层级联动

    使用elementui 实现复选框的层级联动,可能我的表述不准确,先上一个效果图. 实际开发中可能遇到这样的场景,当选择高一层级的复选框时它包含的低级的复选框就不需要再勾选,需要默认选中并且禁止选用. ...

  6. spring-boot-starter-parent和spring-boot-dependencies

    如何创建一个SpringBoot项目,SpringBoot的依赖引入都是基于starter的,通常创建一个SpringBoot项目都是通过继承关系指定pom文件中的parent. <parent ...

  7. Java(6)集合

    一.Java集合框架概述 1.什么是集合 集合框架:用于存储数据的容器. 数组.集合等存储数据的结构,叫Java容器. 此时的存储,是指内存层面的存储,不涉及持久化的存储. 任何集合框架都包含三大块的 ...

  8. ABBYY FineReader 14如何查看PDF文档

    使用 ABBYY FineReader,您可以轻松查看和编辑任何类型的 PDF文档,就像是一款功能强大的PDF编辑转换器,不仅如此,它还能够允许您复制其中的文本.图片和表格.本文我们来看看如何从&qu ...

  9. 细说FL Studio中的Wasp合成器功能

    FL Studio 简称FL,因其Logo像水果,故国人亲切的叫他"水果"本章节采用图文结合的方式给大家讲解FL Studio中的Wasp合成器功能.感兴趣的朋友可以一起来交流哦. ...

  10. 通过Camtasia来添加各种各样的光标效果

    在十几二十年前的时候,我们想要学习新的知识需要到学校和培训班才行,但是现在只要有一台电脑.一部手机或者平板,我们在家里也能找到我们喜欢的课程来学习了,微课也因此而生. 同样的,有了想要学习知识的学生, ...