1.前言

AOP是面向切面编程,即“Aspect Oriented Programming”的缩写。面对切面,就是面向我们的关注面,不能让非关注面影响到我们的关注面。而现实中非关切面又必不可少,例如获取资源、释放资源、处理异常、记录日志等,太多的非关切面会让关切面的代码变得杂糅,难以维护。此时面向切面编程便是解决此问题的方案,减少非关切面的东西,让我们只专注于核心业务代码。而要理解AOP,就必须要懂得代理模式是啥,能干啥,特别要清楚Cglib动态代理是咋回事(不懂的可以看这篇文章)。

2.实现过程

为了直观看到各个类,我将示例所需的类作为静态内部类放在外层类中。

package demo;

import java.util.Arrays;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory; public class SpringAop { public static class UserDaoImpl {
public int addUser(String user) {
System.out.println("保存了一个用户 " + user);
return 1; } public int deleteUser(int id) { if (id <= 0 || id > 99999999) {
throw new IllegalArgumentException("参数id不能大于99999999或小于0");
} System.out.println("删除了id为" + id + "用户");
return 1;
}
} /**
* 自定义的方法拦截器
*
*/
public static class UserMethodInterceptor implements MethodInterceptor { @Override
public Object invoke(MethodInvocation mi) throws Throwable {
String methodName = mi.getMethod().getName();// 方法名
Object returnVal = null; /*
* 根据方法名的不同设置不同的拦截策类 似于配置文件中的<aop:pointcut
* expression="execution( xxxmethodName)"/>,以此定义切入点。
*
* spring框架中可以根据方法对应的包、对应的参数、返回值、方法名等多个条件,并结合正则表达式来定义切入点
* 为了简单化,此处我只用方法名的前缀来定义切入点。
*
*/ if (methodName.startsWith("add")) { returnVal = beforeEnhance(mi); } else if (methodName.startsWith("delete")) {
returnVal = afterThrowingEnhance(mi);
} else {
returnVal = mi.proceed();
} return returnVal;
} /*
* 前置增强策略
*/
private Object beforeEnhance(MethodInvocation mi) throws Throwable {
/*
* spring框架通过配置文件或注解来获得用户自定义的增强方法名及对其JavaBean的类名, 然后通过反射机制动态地调用用户的增强方法。
* 为了简单化的测试,我这里将增强方法及对应的类给定死了、不能改动, 增强JavaBean就是ConsoloEnhancer,
* 增强方法就是"public void before(MethodInvocation mi)"
*
*/
new ConsoloEnhancer().before(mi); // 调用用户定义的前置处理方式
Object returnVal = mi.proceed(); // 执行目标方法
return returnVal;
} /*
* 异常处理策略
*/
private Object afterThrowingEnhance(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();// 执行目标方法
} catch (Throwable e) {
new ConsoloEnhancer().afterThrowing(mi, e); // 调用用户定义的异常处理方式
throw e; } }
} /**
* 定义包含增强方法的JavaBean
*
*/
public static class ConsoloEnhancer {
/**
* 前置增强方法
*
* @param mi
*/
public void before(MethodInvocation mi) {
/*
* 这里的MethodInvocation类型参数mi和开发中常用的切点工具类JoinPoint的作用类似,它可以获取
* 目标对象("Object getThis()"方法)、目标方法("Method getMethod()"方法)、
* 方法参数("Object[] getArguments()"方法)等信息
*/
System.out.print("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法。方法入参原始值是"
+ Arrays.toString(mi.getArguments()) );
if (mi.getThis().getClass() == UserDaoImpl.class) { //对方法入参进行修改
Object[] args = mi.getArguments();
String enhancedAgr ="user-" +(String) args[0] ;
args[0] = enhancedAgr;
}
System.out.println(".而增强后的方法入参是"+Arrays.toString(mi.getArguments()) );
System.out.println("*********************前置增强加星号 *********************");
} /**
* 处理异常的增强方法
*
* @param mi
* @param e
*/
public void afterThrowing(MethodInvocation mi, Throwable e) {
System.out.println("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法,当方法入参是"
+ Arrays.toString(mi.getArguments()) + "时,发生了异常。");
System.out.println("异常的堆栈信息是" + Arrays.toString(e.getStackTrace()));
System.out.println("!!!!!!!!!!!!!!!!!!!!!异常加感叹号!!!!!!!!!!!!!!!!!!!!!!");
} } public static void main(String[] args) {
UserDaoImpl userDaoImpl = new UserDaoImpl();
ProxyFactory proxyFactory = new ProxyFactory(); // 初始化一个代理工厂
// 设置目标类,以便于Cglib工具包动态生成目标类的子类,即我们所需的代理类
proxyFactory.setTarget(userDaoImpl);
/*
* 设置拦截器,而拦截器的"public Object invoke(MethodInvocation mi)"定义了
* 代理类(实际是UserDaoImpl的子类)的方法生成策略。
*
*/
proxyFactory.addAdvice(new UserMethodInterceptor()); // 向上转型,转型为父类类型
UserDaoImpl proxyUserDaoImpl = (UserDaoImpl) proxyFactory.getProxy(); /**
* 调用代理方法
*/
proxyUserDaoImpl.deleteUser(2); //应该正常执行,没有什何增强效果
System.out.println("");// 换行
proxyUserDaoImpl.addUser("李华"); //入参值会被修改
System.out.println("");// 换行
proxyUserDaoImpl.deleteUser(-1); // 将异常处理效果 }
}

各个内部类

 实现细节

1)先定义一个业务类。

这个简单的业务类有保存用户、删除用户的功能。

	public static class UserDaoImpl {
public int addUser(String user) {
System.out.println("保存了一个用户 " + user);
return 1; } public int deleteUser(int id) { if (id <= 0 || id > 99999999) {
throw new IllegalArgumentException("参数id不能大于99999999或小于0");
} System.out.println("删除了id为" + id + "用户");
return 1;
}
}

 

2)自定义一个通知(实际上是一个拦截器)

这个Advice通知实现了 MethodInterceptor接口,而MethodInterceptor接口继承了Interceptor接口,Intercepto接口又继承了Advice接口,因此我个将这拦截器称为一个通知。

为了偷懒,此处的Advice通知一次性实现了两个功能,前置增强和后异常处理增强,明显不满足“单一职责”的原则,此处同时要处理两种增强。而spring框架中,当读到配置文件的"<aop:before>"标签,就安排一个AspectJMethodBeforeAdvice类(继承于AbstractAspectAdive抽象类,此抽象类实现了Advice接口)去处理前置增强;当读到配置文件的"<aop:after-throwing>"标签,则会安排一AspectJAfterThrowingAdvice类(实现了MethodInterceptor接口)去做异常增强处理。spring框架切实做到了遵循”单一职责“原则,专门组织一个类去处理一种类型(前置、后置或最终等)的增强。

另外需要注意的是:这里的MethodInterceptor接口是位于"org.aopalliance.intercept"包,除此之外Cglib工具包中的"org.springframework.cglib.proxy"包也有MethodInterceptor接口,但这个接口不是我们所需要的。

Advice接口结构体系图(配图引用自"spring适配器模式-aop中的MethodInterceptor拦截器")

	public static class UserMethodInterceptor implements MethodInterceptor {

		@Override
public Object invoke(MethodInvocation mi) throws Throwable {
String methodName = mi.getMethod().getName();// 方法名
Object returnVal = null; /*
* 根据方法名的不同设置不同的拦截策类 似于配置文件中的<aop:pointcut
* expression="execution( xxxmethodName)"/>,以此定义切入点。
*
* spring框架中可以根据方法对应的包、对应的参数、返回值、方法名等多个条件,并结合正则表达式来定义切入点
* 为了简单化,此处我只用方法名的前缀来定义切入点。
*
*/
if (methodName.startsWith("add")) { returnVal = beforeEnhance(mi); } else if (methodName.startsWith("delete")) {
returnVal = afterThrowingEnhance(mi);
} else {
returnVal = mi.proceed();
} return returnVal;
} /*
* 前置增强策略
*/
private Object beforeEnhance(MethodInvocation mi) throws Throwable {
/*
* spring框架通过配置文件或注解来获得用户自定义的增强方法名及对其JavaBean的类名, 然后通过反射机制动态地调用用户的增强方法。
* 为了简单化的测试,我这里将增强方法及对应的类给定死了、不能改动, 增强JavaBean就是ConsoloEnhancer,
* 增强方法就是"public void before(MethodInvocation mi)"
*
*/
new ConsoloEnhancer().before(mi); // 调用用户定义的前置处理方式
Object returnVal = mi.proceed(); // 执行目标方法
return returnVal;
} /*
* 异常处理策略
*/
private Object afterThrowingEnhance(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();// 执行目标方法
} catch (Throwable e) {
new ConsoloEnhancer().afterThrowing(mi, e); // 调用用户定义的异常处理方式
throw e; } }
}

 

3) 用户定义包含增强方法的JavaBean

MthodInvocation接口比较有意思,它的祖先级接口是Joinpoint ,注意不是开发中的JoinPoint,但MthodInvocation封装的信息和JoinPoint差不多,都包含目标对象、方法的参数、目标方法等。

	public static class ConsoloEnhancer {
/**
* 前置增强方法
*
* @param mi
*/
public void before(MethodInvocation mi) {
/*
* 这里的MethodInvocation类型参数mi和开发中常用的切点工具类JoinPoint的作用类似,它可以获取
* 目标对象("Object getThis()"方法)、目标方法("Method getMethod()"方法)、
* 方法参数("Object[] getArguments()"方法)等信息
*/
System.out.print("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法。方法入参原始值是"
+ Arrays.toString(mi.getArguments()) );
if (mi.getThis().getClass() == UserDaoImpl.class) { //对方法入参进行修改
Object[] args = mi.getArguments();
String enhancedAgr ="user-" +(String) args[0] ;
args[0] = enhancedAgr;
}
System.out.println(".而增强后的方法入参是"+Arrays.toString(mi.getArguments()) );
System.out.println("*********************前置增强加星号 *********************");
} /**
* 处理异常的增强方法
*
* @param mi
* @param e
*/
public void afterThrowing(MethodInvocation mi, Throwable e) {
System.out.println("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法,当方法入参是"
+ Arrays.toString(mi.getArguments()) + "时,发生了异常。");
System.out.println("异常的堆栈信息是" + Arrays.toString(e.getStackTrace()));
System.out.println("!!!!!!!!!!!!!!!!!!!!!异常加感叹号!!!!!!!!!!!!!!!!!!!!!!");
} }

  

 

4)方法测试

	public static void main(String[] args) {
UserDaoImpl userDaoImpl = new UserDaoImpl();
ProxyFactory proxyFactory = new ProxyFactory(); // 初始化一个代理工厂
// 设置目标类,以便于Cglib工具包动态生成目标类的子类,即我们所需的代理类
proxyFactory.setTarget(userDaoImpl);
/*
* 设置拦截器,而拦截器的"public Object invoke(MethodInvocation mi)"定义了
* 代理类(实际是UserDaoImpl的子类)的方法生成策略。
*
*/
proxyFactory.addAdvice(new UserMethodInterceptor()); // 向上转型,转型为父类类型
UserDaoImpl proxyUserDaoImpl = (UserDaoImpl) proxyFactory.getProxy(); /**
* 调用代理方法
*/
proxyUserDaoImpl.deleteUser(2); //应该正常执行,没有什何增强效果
System.out.println("");// 换行
proxyUserDaoImpl.addUser("李华"); //入参值会被修改
System.out.println("");// 换行
proxyUserDaoImpl.deleteUser(-1); // 将异常处理效果 }

  控制台输出

3.总结

spring框架的AOP非常强大,可以实现前置增强、后置增强、最终增强、环绕增强等处理,另外还可以对方法入参进行控制过滤,而影响目标方法的执行中的状态。AOP都是基于(Cglib)代理模式实现的,其中的关键点在于实现MethodInterceptor接口,在其“public Object invoke(MethodInvocation mi)”方法中制定代理方法的生成策略,而从此方法的MethodInvocation类型参数mi中可以获得目标对象、方法的参数、目标方法等信息,根据这些信息可以精确地控制增强效果。

深入理解spring中的AOP原理 —— 实现MethodInterceptor接口,自已动手写一个AOP的更多相关文章

  1. 通过BeanPostProcessor理解Spring中Bean的生命周期

    通过BeanPostProcessor理解Spring中Bean的生命周期及AOP原理 Spring源码解析(十一)Spring扩展接口InstantiationAwareBeanPostProces ...

  2. 深入理解spring中的各种注解

    Spring中的注解大概可以分为两大类: 1)spring的bean容器相关的注解,或者说bean工厂相关的注解: 2)springmvc相关的注解. spring的bean容器相关的注解,先后有:@ ...

  3. 深入理解spring中的各种注解(转)

    Spring中的注解大概可以分为两大类: 1)spring的bean容器相关的注解,或者说bean工厂相关的注解: 2)springmvc相关的注解. spring的bean容器相关的注解,先后有:@ ...

  4. Spring Aop(十五)——Aop原理之Advised接口

    转发地址:https://www.iteye.com/blog/elim-2398726 Spring Aop原理之Advised接口 通过之前我们介绍的ProxyFactory我们知道,Spring ...

  5. 通过源码理解Spring中@Scheduled的实现原理并且实现调度任务动态装载

    前提 最近的新项目和数据同步相关,有定时调度的需求.之前一直有使用过Quartz.XXL-Job.Easy Scheduler等调度框架,后来越发觉得这些框架太重量级了,于是想到了Spring内置的S ...

  6. 如何快速理解Spring中的DI和AOP

    前言 Spring框架通过POJO最小侵入性编程.DI.AOP.模板代码手段来简化了Java 开发,简化了企业应用的开发.POJO和模板代码相对来说好理解,本篇重点解读下DI和AOP. 一 DI DI ...

  7. 理解Spring中的IOC和AOP

    我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂模式和代理模式 IOC就是典型的工厂模式,通过ses ...

  8. 0000 - Spring 中常用注解原理剖析

    1.概述 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来 ...

  9. Spring 中常用注解原理剖析

    前言 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来配置 ...

随机推荐

  1. CentOS7上防火墙操作

    firewalld打开关闭防火墙与端口 启动: systemctl start firewalld 关闭: systemctl stop firewalld 查看状态: systemctl statu ...

  2. junit基础学习之-参数初始化(5)

    package swust.edu.cn.postdoctors.service.impl; import java.util.Arrays; import java.util.Collection; ...

  3. 第十七篇 ORM跨表查询和分组查询---二次剖析

    ORM跨表查询和分组查询---二次剖析 阅读目录(Content) 创建表(建立模型) 基于对象的跨表查询 一对多查询(Publish与Book) 多对多查询 (Author 与 Book) 一对一查 ...

  4. 学习spring第五天 mybatis+spring的整合(maven多模块数据查询使用了分页和连接池),以及aop

    mybatis+spring的整合: 导入的依赖:1.数据库连接:mysql-connector-java 2.连接池:druid 3.servlet:javax.servlet-api 4.jstl ...

  5. python outline

    1.列表/数组/numpy/Pandas Python list 初始化技巧   (2018-12-27 11:54) python3 sort list   (2019-05-23 14:52) P ...

  6. xml学习-语法规则

    XML 指可扩展标记语言(eXtensible Markup Language).XML 被设计用来传输和存储数据. XML 语法规则 XML 文档必须有根元素 XML 必须包含根元素,它是所有其他元 ...

  7. LeetCode题解分类汇总(包括剑指Offer和程序员面试金典,持续更新)

    LeetCode题解汇总(持续更新,并将逐步迁移到本博客列表中) 剑指Offer 数据结构 链表 序号 题目 难度 06 从尾到头打印链表 简单 18 删除链表的节点 简单 22 链表中倒数第k个节点 ...

  8. idea修改web项目的访问路径

    转 新建好了项目发现项目只能以localhost:8080这样的访问路径访问到主页,也就是index.jsp 那么之前我用eclipse新建的项目都是localhost:8080/xxx(项目名称)来 ...

  9. C++编程学习(四)声明/枚举

    一.typedef 声明 typedef 为一个已有的类型取一个新的名字 typedef int num;//feet是int的另一个名字num a;//a是int类型 二.枚举类型 enum col ...

  10. SQL的查询结果复制到Excel 带标题Head 有换行符导致换行错乱 的解决方案

    将SQL查询到的结果保存为excel有很多方法,其中最简单的就是直接复制粘贴了 1.带Head的复制粘贴 1)先左击红色区域实现选择所有数据 2)随后右击选择Copy with Headers  再粘 ...