深入理解spring中的AOP原理 —— 实现MethodInterceptor接口,自已动手写一个AOP
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的更多相关文章
- 通过BeanPostProcessor理解Spring中Bean的生命周期
通过BeanPostProcessor理解Spring中Bean的生命周期及AOP原理 Spring源码解析(十一)Spring扩展接口InstantiationAwareBeanPostProces ...
- 深入理解spring中的各种注解
Spring中的注解大概可以分为两大类: 1)spring的bean容器相关的注解,或者说bean工厂相关的注解: 2)springmvc相关的注解. spring的bean容器相关的注解,先后有:@ ...
- 深入理解spring中的各种注解(转)
Spring中的注解大概可以分为两大类: 1)spring的bean容器相关的注解,或者说bean工厂相关的注解: 2)springmvc相关的注解. spring的bean容器相关的注解,先后有:@ ...
- Spring Aop(十五)——Aop原理之Advised接口
转发地址:https://www.iteye.com/blog/elim-2398726 Spring Aop原理之Advised接口 通过之前我们介绍的ProxyFactory我们知道,Spring ...
- 通过源码理解Spring中@Scheduled的实现原理并且实现调度任务动态装载
前提 最近的新项目和数据同步相关,有定时调度的需求.之前一直有使用过Quartz.XXL-Job.Easy Scheduler等调度框架,后来越发觉得这些框架太重量级了,于是想到了Spring内置的S ...
- 如何快速理解Spring中的DI和AOP
前言 Spring框架通过POJO最小侵入性编程.DI.AOP.模板代码手段来简化了Java 开发,简化了企业应用的开发.POJO和模板代码相对来说好理解,本篇重点解读下DI和AOP. 一 DI DI ...
- 理解Spring中的IOC和AOP
我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂模式和代理模式 IOC就是典型的工厂模式,通过ses ...
- 0000 - Spring 中常用注解原理剖析
1.概述 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来 ...
- Spring 中常用注解原理剖析
前言 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来配置 ...
随机推荐
- C++ 99表
#include<iostream> using namespace std; class Sumes { public: int sum; int i, j; }; int main() ...
- 算法实战(六)Z 字形变换
一.前言 之前因为第五题最长回文字符串需要使用到dp解法,所以我花了很长的时间来研究dp(因为每天又要上班,加上这段时间事情比较多,所以花了三个星期才搞定),好不容易算入了个门,有兴趣的同学可以看看我 ...
- Java基础之内省
Java基础之内省 什么是内省 首先,我们要知道什么是内省.按我自己的理解就是在反射的原理上进行封装,来更方便的操作JavaBean JavaBean就是特殊格式的类,其规范为: JavaBean ...
- 正则表达式入门(ed模糊匹配)
元字符: /b 代表着单词的开头或结尾,也就是单词的分界处.如果要精确地查找hi这个单词的话,我们应该使用/bhi/b. .是另一个元字符,匹配除了换行符以外的任意字符,*同样是元字符,它指定*前边的 ...
- 线程与进程 queue模块
queue模块的基本用法 https://www.cnblogs.com/chengd/articles/7778506.html 模块实现了3种类型的队列,区别在于队列中条目检索的顺序不同.在FIF ...
- 电动车智能充电桩温度报警方案:SI24R2F
由于现在电动自行车便捷不少民众的出行都选择这种交通工具出行,随着越来越多人都使用电动自行车,智能电动车充电桩的需求也在慢慢的变多,电动车智能充电桩的安全性也慢慢成为市场的焦点,对此SI24R ...
- TP中统计指定字段的总数
如统计已激活设备数量和未激活设备数量 $condition = [ ['member_id', '=', $member_id] ]; $field = [ 'COUNT(IF(active_memb ...
- java多线程并发(一)-- 相关基础知识
java多线程的知识是java程序员都应该掌握的技能,目前我接触的项目上用的不多,花点时间熟悉熟悉. 一.基础知识 1.什么是进程? 进程是具有一定独立功能的正在运行过程中的程序,是操作系统进行资源分 ...
- 解决TeamViewer提示商业用途
安装此插件 提取码:i8o3
- Git--rebase合并提交
参考 https://blog.csdn.net/hj7jay/article/details/78809547 https://blog.csdn.net/yangcs2009/article/de ...