手写AOP实现过程
一.手写Aop前基础知识
1.aop是什么?
面向切面编程(AOP):是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP)。
在进行OOP开发时,都是基于对组件(比如类)进行开发,然后对组件进行组合,OOP最大问题就是无法解耦组件进行开发,而AOP就是为了克服这个问题而出现的,它来进行这种耦合的分离。
AOP为开发者提供一种进行横切关注点(比如日志关注点横切了支付关注点)分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而无耦合的完成了我们的功能。
2.aop能干什么?
AOP主要用于横切关注点和织入,因此需要理解横切关注点和织入:
- **关注点:**可以认为是所关注的任何东西,比如上边的支付组件;
- **关注点分离:**将问题细化从而单独部分,即可以理解为不可再分割的组件,如上边的日志组件和支付组件;
- **横切关注点:**一个组件无法完成需要的功能,需要其他组件协作完成,如日志组件横切于支付组件;
- **织入:**横切关注点分离后,需要通过某种技术将横切关注点融合到系统中从而完成需要的功能,因此需要织入,织入可能在编译期、加载期、运行期等进行。
横切关注点可能包含很多,比如非业务的:日志、事务处理、缓存、性能统计、权限控制等等这些非业务的基础功能;还可能是业务的:如某个业务组件横切于多个模块。

面向切面方式,先将横切关注点分离,再将横切关注点织入到支付系统中:


3.aop的优点?
- 完善OOP;
- 降低组件和模块之间的耦合性;
- 使系统容易扩展;
- 而且由于关注点分离从而可以获得组件的更好复用。
二.aop底层实现原理:代理模式
关于静态代理模式,详情请参阅我另一片博客:
https://www.cnblogs.com/tc971121/p/13474638.html
三.手写aop主要实现过程
1.定义配置标记
@Aspect:配置标记横切对象(方法)的地址
@Order:配置标记横切的顺序
@Aspect/@Order
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
String pointcut();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Order {
/**
* 控制类的执行顺序,值越小优先级越高
*/
int value();
}
2.定义默认切面类
package org.simplespring.aop.aspect;
import java.lang.reflect.Method;
/**
* 定义默认切面类
*/
public abstract class DefaultAspect {
/**
* 事前拦截
* @param targetClass 被代理的目标类
* @param method 被代理的目标方法
* @param args 被代理的目标方法对应的参数列表
* @throws Throwable
*/
public void before(Class<?> targetClass, Method method, Object[] args) throws Throwable{
}
/**
* 事后拦截
* @param targetClass 被代理的目标类
* @param method 被代理的目标方法
* @param args 被代理的目标方法对应的参数列表
* @param returnValue 被代理的目标方法执行后的返回值
* @throws Throwable
*/
public Object afterReturning(Class<?> targetClass, Method method, Object[] args, Object returnValue) throws Throwable{
return returnValue;
}
/**
*
* @param targetClass 被代理的目标类
* @param method 被代理的目标方法
* @param args 被代理的目标方法对应的参数列表
* @param e 被代理的目标方法抛出的异常
* @throws Throwable
*/
public void afterThrowing(Class<?> targetClass, Method method, Object[] args, Throwable e) throws Throwable{
}
}
3.定义切面织入器
核心流程:
1.获取所有的切面类
2.遍历容器管理的类
3.筛选出匹配容器管理类的切面aspectlist
4.尝试进行Aspect的织入 生成动态代理对象 并替换beancontainer中原先class对应所对应的实例对象
package org.simplespring.aop;
import org.simplespring.aop.annotation.Aspect;
import org.simplespring.aop.annotation.Order;
import org.simplespring.aop.aspect.AspectInfo;
import org.simplespring.aop.aspect.DefaultAspect;
import org.simplespring.core.BeanContainer;
import org.simplespring.util.ValidationUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @Classname AspectWeaver
* @Description 切面织入器
* @Date 2020/8/8 19:43
* @Created by zhangtianci
*/
public class AspectWeaver {
/**
* 拥有一个bean容器
*/
private BeanContainer beanContainer;
public AspectWeaver(){
beanContainer = BeanContainer.getInstance();
}
/**
* 1.获取所有的切面类
* 2.遍历容器里的类
* 3.筛选出匹配类的切面aspect
* 4.尝试进行Aspect的织入 生成动态代理对象
*/
public void doAop() {
//1.获取所有的切面类
Set<Class<?>> aspectSet = beanContainer.getClassesByAnnotation(Aspect.class);
if(ValidationUtil.isEmpty(aspectSet)){return;}
//2.拼装AspectInfoList
List<AspectInfo> aspectInfoList = packAspectInfoList(aspectSet);
//3.遍历容器里的类
Set<Class<?>> classSet = beanContainer.getClasses();
for (Class<?> targetClass: classSet) {
//排除AspectClass自身
if(targetClass.isAnnotationPresent(Aspect.class)){
continue;
}
//4.粗筛符合条件的Aspect
List<AspectInfo> roughMatchedAspectList = collectRoughMatchedAspectListForSpecificClass(aspectInfoList, targetClass);
//5.尝试进行Aspect的织入
wrapIfNecessary(roughMatchedAspectList,targetClass);
}
}
private void wrapIfNecessary(List<AspectInfo> roughMatchedAspectList, Class<?> targetClass) {
if(ValidationUtil.isEmpty(roughMatchedAspectList)){return;}
//创建动态代理对象
AspectListExecutor aspectListExecutor = new AspectListExecutor(targetClass, roughMatchedAspectList);
Object proxyBean = ProxyCreator.createProxy(targetClass, aspectListExecutor);
beanContainer.addBean(targetClass, proxyBean);
}
private List<AspectInfo> collectRoughMatchedAspectListForSpecificClass(List<AspectInfo> aspectInfoList, Class<?> targetClass) {
List<AspectInfo> roughMatchedAspectList = new ArrayList<>();
for(AspectInfo aspectInfo : aspectInfoList){
//粗筛
if(aspectInfo.getPointcutLocator().roughMatches(targetClass)){
roughMatchedAspectList.add(aspectInfo);
}
}
return roughMatchedAspectList;
}
private List<AspectInfo> packAspectInfoList(Set<Class<?>> aspectSet) {
List<AspectInfo> aspectInfoList = new ArrayList<>();
for(Class<?> aspectClass : aspectSet){
if (verifyAspect(aspectClass)){
Order orderTag = aspectClass.getAnnotation(Order.class);
Aspect aspectTag = aspectClass.getAnnotation(Aspect.class);
DefaultAspect defaultAspect = (DefaultAspect) beanContainer.getBean(aspectClass);
//初始化表达式定位器
PointcutLocator pointcutLocator = new PointcutLocator(aspectTag.pointcut());
AspectInfo aspectInfo = new AspectInfo(orderTag.value(), defaultAspect, pointcutLocator);
aspectInfoList.add(aspectInfo);
} else {
//不遵守规范则直接抛出异常
throw new RuntimeException("@Aspect and @Order must be added to the Aspect class, and Aspect class must extend from DefaultAspect");
}
}
return aspectInfoList;
}
//框架中一定要遵守给Aspect类添加@Aspect和@Order标签的规范,同时,必须继承自DefaultAspect.class
//此外,@Aspect的属性值不能是它本身
private boolean verifyAspect(Class<?> aspectClass) {
return aspectClass.isAnnotationPresent(Aspect.class) &&
aspectClass.isAnnotationPresent(Order.class) &&
DefaultAspect.class.isAssignableFrom(aspectClass);
}
}
切面信息包装类(增强的动作/增强对象地址/横切顺序)
package org.simplespring.aop.aspect;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.simplespring.aop.PointcutLocator;
@AllArgsConstructor
@Getter
public class AspectInfo {
private int orderIndex;
private DefaultAspect aspectObject;
private PointcutLocator pointcutLocator;
}
采用cglib动态的实现方式:
实现net.sf.cglib.proxy.MethodInterceptor接口,定义代理后方法的动作(相当于实现jdk动态代理中的InvocationHandler接口)
package org.simplespring.aop;
import lombok.Getter;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.simplespring.aop.aspect.AspectInfo;
import org.simplespring.util.ValidationUtil;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
public class AspectListExecutor implements MethodInterceptor {
//被代理的类
private Class<?> targetClass;
//排好序的Aspect列表
@Getter
private List<AspectInfo>sortedAspectInfoList;
public AspectListExecutor(Class<?> targetClass, List<AspectInfo> aspectInfoList){
this.targetClass = targetClass;
this.sortedAspectInfoList = sortAspectInfoList(aspectInfoList);
}
/**
* 按照order的值进行升序排序,确保order值小的aspect先被织入
*
* @param aspectInfoList
* @return
*/
private List<AspectInfo> sortAspectInfoList(List<AspectInfo> aspectInfoList) {
Collections.sort(aspectInfoList, new Comparator<AspectInfo>() {
@Override
public int compare(AspectInfo o1, AspectInfo o2) {
//按照值的大小进行升序排序
return o1.getOrderIndex() - o2.getOrderIndex();
}
});
return aspectInfoList;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue = null;
collectAccurateMatchedAspectList(method);
if(ValidationUtil.isEmpty(sortedAspectInfoList)){
returnValue = methodProxy.invokeSuper(proxy, args);
return returnValue;
}
//1.按照order的顺序升序执行完所有Aspect的before方法
invokeBeforeAdvices(method, args);
try{
//2.执行被代理类的方法
returnValue = methodProxy.invokeSuper(proxy, args);
//3.如果被代理方法正常返回,则按照order的顺序降序执行完所有Aspect的afterReturning方法
returnValue = invokeAfterReturningAdvices(method, args, returnValue);
} catch (Exception e){
//4.如果被代理方法抛出异常,则按照order的顺序降序执行完所有Aspect的afterThrowing方法
invokeAfterThrowingAdvides(method, args, e);
}
return returnValue;
}
private void collectAccurateMatchedAspectList(Method method) {
if(ValidationUtil.isEmpty(sortedAspectInfoList)){return;}
Iterator<AspectInfo> it = sortedAspectInfoList.iterator();
while (it.hasNext()){
AspectInfo aspectInfo = it.next();
if(!aspectInfo.getPointcutLocator().accurateMatches(method)){
it.remove();
}
}
}
//4.如果被代理方法抛出异常,则按照order的顺序降序执行完所有Aspect的afterThrowing方法
private void invokeAfterThrowingAdvides(Method method, Object[] args, Exception e) throws Throwable {
for (int i = sortedAspectInfoList.size() - 1; i >=0 ; i--){
sortedAspectInfoList.get(i).getAspectObject().afterThrowing(targetClass, method, args, e);
}
}
//3.如果被代理方法正常返回,则按照order的顺序降序执行完所有Aspect的afterReturning方法
private Object invokeAfterReturningAdvices(Method method, Object[] args, Object returnValue) throws Throwable {
Object result = null;
for (int i = sortedAspectInfoList.size() - 1; i >=0 ; i--){
result = sortedAspectInfoList.get(i).getAspectObject().afterReturning(targetClass, method, args, returnValue);
}
return result;
}
//1.按照order的顺序升序执行完所有Aspect的before方法
private void invokeBeforeAdvices(Method method, Object[] args) throws Throwable {
for(AspectInfo aspectInfo : sortedAspectInfoList){
aspectInfo.getAspectObject().before(targetClass, method, args);
}
}
}
创建代理类:
package org.simplespring.aop;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
public class ProxyCreator {
/**
* 创建动态代理对象并返回
* @param targetClass 被代理的Class对象
* @param methodInterceptor 方法拦截器
* @return
*/
public static Object createProxy(Class<?> targetClass, MethodInterceptor methodInterceptor){
return Enhancer.create(targetClass, methodInterceptor);
}
}
解析Aspect表达式并且定位被织入的目标工具类:
package org.simplespring.aop;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.ShadowMatch;
import java.lang.reflect.Method;
/**
* 解析Aspect表达式并且定位被织入的目标
*/
public class PointcutLocator {
/**
* Pointcut解析器,直接给它赋值上Aspectj的所有表达式,以便支持对众多表达式的解析
*/
private PointcutParser pointcutParser= PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(
PointcutParser.getAllSupportedPointcutPrimitives()
);
/**
* 表达式解析器
*/
private PointcutExpression pointcutExpression;
public PointcutLocator(String expression){
this.pointcutExpression = pointcutParser.parsePointcutExpression(expression);
}
/**
* 判断传入的Class对象是否是Aspect的目标代理类,即匹配Pointcut表达式(初筛)
*
* @param targetClass 目标类
* @return 是否匹配
*/
public boolean roughMatches(Class<?> targetClass){
//couldMatchJoinPointsInType比较坑,只能校验within
//不能校验 (execution(精确到某个类除外), call, get, set),面对无法校验的表达式,直接返回true
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
}
/**
* 判断传入的Method对象是否是Aspect的目标代理方法,即匹配Pointcut表达式(精筛)
* @param method
* @return
*/
public boolean accurateMatches(Method method){
ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method);
if(shadowMatch.alwaysMatches()){
return true;
} else{
return false;
}
}
}
总结实现aop的主要流程:
1.定义配置标记
2.定义默认切面类
3.定义切面织入器
核心流程:
1.获取所有的切面类
2.遍历容器管理的类
3.筛选出匹配容器管理类的切面aspectlist
4.尝试进行Aspect的织入 生成动态代理对象 并替换beancontainer中原先class对应所对应的实例对象
4.其他类:切面信息包装类/InvocationHandler实现类/创建代理实例类/解析Aspect表达式工具类等。
参考博客地址:https://www.iteye.com/blog/jinnianshilongnian-1418596
手写AOP实现过程的更多相关文章
- Spring 08: AOP面向切面编程 + 手写AOP框架
核心解读 AOP:Aspect Oriented Programming,面向切面编程 核心1:将公共的,通用的,重复的代码单独开发,在需要时反织回去 核心2:面向接口编程,即设置接口类型的变量,传入 ...
- 手写IOC实现过程
一.手写ioc前基础知识 1.什么是IOC(Inversion of Control 控制反转)? IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合.更优良 ...
- 手写SpringMVC实现过程
1. Spring Boot,Spring MVC的底层实现都是Servlet的调用. 2. Servlet的生命周期里面首先是类的初始化,然后是类的方法的调用,再次是类的销毁. 3. 创建一个spr ...
- 手写Spring AOP,快来瞧一瞧看一看撒!
目录 AOP分析 Advice实现 定义Advice接口 定义前置.后置.环绕和异常增强接口 Pointcut实现 定义PointCut接口 定义正则表达式的实现类:RegExpressionPoin ...
- MNIST手写数据集在运行中出现问题解决方案
今天在运行手写数据集的过程中,出现一个问题,代码没有问题,但是运行的时候一直报错,错误如下: urllib.error.URLError: <urlopen error [SSL: CERTIF ...
- 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)
一.为什么要提供配置的方法 经过前面的手写Spring IOC.手写Spring DI.手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创 ...
- 手写spring事务框架, 揭秘AOP实现原理。
AOP面向切面编程:主要是通过切面类来提高代码的复用,降低业务代码的耦合性,从而提高开发效率.主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等. AOP实现原理:aop是通过cgli ...
- 初学源码之——银行案例手写IOC和AOP
手写实现lOC和AOP 上一部分我们理解了loC和AOP思想,我们先不考虑Spring是如何实现这两个思想的,此处准备了一个『银行转账」的案例,请分析该案例在代码层次有什么问题?分析之后使用我们已有知 ...
- 闭关修炼180天--手写IOC和AOP(xml篇)
闭关修炼180天--手写IOC和AOP(xml篇) 帝莘 首先先分享一波思维导图,涵盖了一些Spring的知识点,当然这里并不全面,后期我会持续更新知识点. 在手写实现IOC和AOP之前(也就是打造一 ...
随机推荐
- ref和动态组件
ref--------指引 另一种获取表单值的方法 是Vue环境中一个内置的属性.它可以使用this.$refs可以快速拿到DOM对象.
- pta习题:退休日期推算
6-3 退休日期推算 (10分) 关于日期的结构定义如下: struct DateG{ int yy,mm,dd;}; 编写两个函数,一个计算自公元1年1月1日到指定的日期共经历了多少天.另一个是 ...
- Shell基本语法---处理海量数据的awk命令
awk命令 其实是一门编程语言,支持条件判断,数组,循环等功能,与grep,sed被称为linux三剑客 之所以叫AWK是因为取其三位创始人 Alfred Aho,Peter Weinberger, ...
- 【mysql】- 锁篇(上)
回顾 问题 事务并发执行时可能带来各种问题,并发事务访问相同记录的情况大致可以划分为3种 读-读情况:即并发事务相继读取相同的记录 读取操作本身不会对记录有什么影响,并不会引起什么问题,所以允许这种情 ...
- Python3的一些基本输入输出
# python3 # 基本输入输出 #学会使用split()的使用是关键,input()的返回值一定是字符类型 .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. ...
- 想理解JVM看了这篇文章,就知道了!(一)
前言 本章节属于Java进阶系列,前面关于设计模式讲解完了,有兴趣的童鞋可以翻看之前的博文,后面会讲解JVM的优化,整个系列会完整的讲解整个java体系与生态相关的中间件知识.本次将对jvm有更深 ...
- C# File.Exists 判断系统文件,警惕32位和64位的差异
今天在调试一个Winform程序,使用File.Exists 判断一个已经存在的驱动文件,程序一直返回false.因为驱动文件属于系统目录,心想难道是权限不够导致的?然后用管理员身份运行软件,依然返回 ...
- laravel报错1071 Specified key was too long; max key length is 1000 bytes
Laravel 默认使用utf8mb4字符编码,而不是的utf8编码.因此运行php artisan migrate会出现如下错误: [Illuminate\Database\QueryExcepti ...
- 仓库储存,存在添加减少,不存在插入ON DUPLICATE KEY UPDATE
原文链接:https://blog.csdn.net/qq_42269354/article/details/100589640 首先声明:ON DUPLICATE KEY UPDATE 为MySQL ...
- PHP is_dir() 函数
定义和用法 is_dir() 函数检查指定的文件是否是一个目录. 如果目录存在,该函数返回 TRUE. 语法 is_dir(file) 参数 描述 file 必需.规定要检查的文件. 提示和注释 注释 ...