手写AOP

//不能再invoke 方法里面调用类似 proxy.toString()的方法,会stackoverflow的 原因: Object类里面的一部分可被子类继承的 方法也是被增强了的 可以通过反编译动态代理生成的类文件查看

一、AOP分析

问题1:AOP是什么?

Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强。

问题2:我们需要做什么?

在我们的框架中要向使用用户提供AOP功能,让他们可以通过AOP技术实现对类方法进行功能增强。

从"Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强"这句话我们能得到下面的这些信息:

img

二、AOP概念学习

我们先来看一下下面的这张图

img

说明:

程序运行时会调用很多方法,调用的很多方法就叫做Join points(连接点,可以被选择来进行增强的方法点),在方法的前或者后选择一个地方来切入,切入的的地方就叫做Pointcut(切入点,选择增强的方法),然后把要增强的功能(Advice)加入到切入点所在的位置。Advice和Pointcut组成一个切面(Aspect)

AOP的几个概念:

img

Advice、Pointcut、Weaving的特点:

Advice(功能增强):

1)用户性:由用户提供增强功能的逻辑代码

2)变化的:不同的增强需求,会有不同的逻辑

3)可选时机:可选择在方法前、后、异常时进行功能增强

4)多重的:同一个切入点上可以有多重增强

Pointcut(切入点):

1)用户性:由用户来指定

2)变化的:用户可灵活指定

3)多点性:用户可以选择在多个点上进行功能增强

Weaving(织入):

1)无侵入性,因为不改变原类的代码

2)我们在框架中实现

三、切面实现

我们将要分析Advice、Pointcut、Aspect这三个东西

img

1. Advice设计

Advice是由用户来提供,我们来使用,它是多变得。

问题1:我们如何能识别用户提供的东西?用户在我们写好框架以后使用我们的框架。

问题2:如何让我们的代码隔绝用户提供的多变?

解决方法:

我们定义一套标准接口,用户通过实现接口来提供它们不同的逻辑。

为了应对变化,这里使用到了设计原则:面向接口编程

Advice的特点:可选时机,可选择在方法前、后、异常时进行功能增强

1)有的Advice是在方法执行前进行增强——前置增强

2)有的Advice是在方法执行后进行增强——后置增强

3)有的Advice会在方执行前后都进行增强——环绕增强

4)有的Advice则只是在方法执行抛出异常时进行增强——异常处理增强

问题1:我们需要做什么?

定义标准接口方法,让用户可以实现它,提供各种增强。

问题2:这四种增强所需的参数一样吗?

下面我们来一个一个的分析

1.1 前置增强分析

前置增强:在方法执行前进行增强

问题1:它可能需要什么参数?

目的是对方法进行增强,应该需要的是方法相关的信息。

问题2:运行时,方法有哪些信息?

方法本身 Method

方法属于哪个类 Object

方法的参数 Object [ ]

方法的返回值

问题3:前置增强可能需要什么参数?

方法本身 Method

方法属于哪个类 Object

方法的参数 Object [ ]

问题3:前置增强的返回值是什么?

在方法执行前进行增强,不需要返回值

1.2 后置增强分析

后置增强:在方法执行后进行增强

问题1:后置增强可能需要什么参数?

方法本身 Method

方法属于哪个类 Object

方法的参数 Object [ ]

方法的返回值

问题2:后置增强的返回值是什么?

在方法执行后进行增强,不需要返回值

1.3 环绕增强分析

环绕增强:方法执行前后进行增强(包裹方法进行增强)

问题1:它可能需要什么参数?

方法本身 Method

方法属于哪个类 Object

方法的参数 Object [ ]

问题2:环绕增强的返回值是什么?

方法被它包裹,也即方法将由它来执行,它需要返回方法的返回值。Object

1.4 异常处理增强分析

异常处理增强:捕获方法执行时的异常,进行增强处理。

问题1:它可能需要什么参数?

异常信息

问题2:进行异常处理增强需要包裹方法吗?

需要,把执行代码用try包起来,捕获到哪个异常就在哪里进行增强

img

问题3:那它可否在环绕中实现?

可以

1.5 经过前面的分析,我们一共需要定义三个方法,因为1.3和1.4可以都用环绕实现

问题1:是把这三个方法定义在一个接口中,还是分别定义在三个接口中?

分三个接口,这样可以通过类型来区分不同的增强(Advice)

类图如下:

img

2. Pointcut设计

2.1 Pointcut分析

Pointcut的特点:

1)用户性:由用户来指定

2)变化的:用户可灵活指定

3)多点性:用户可以选择在多个点上进行增强

我们需要做什么?

为用户提供一个东西,让他们可以灵活的指定多个方法点,而且我们又能懂

切入点是由用户来指定在哪些方法点上进行增强,那么这个哪些方法如何来表示,能满足上面的点?

分析:

1)指定哪些方法,是不是一个描述信息

2)如何来指定一个方法——某类的某个方法

3)方法重载怎么办——加上参数类型

4)有没有感觉其实就是一个完整的方法签名

com.study.design.mode.samples.proxy.Girl.dating(float length)

com.study.design.mode.samples.proxy.Girl.dating(long time)

5)如何做到多点性,灵活性?在一个描述中指定一类类的某些方法?

a)某个包下的某个类的某个方法

b)某个包下的所有类中的所有方法

c)某个包下的所有类中的do开头的方法

d)某个包下的以service结尾的类中的do开头的方法

e)某个包下的及其子包下的以service结尾的类中的do开头的方法

总结:我们需要一个能描述上面a-e这些信息的表达式

6)要表达哪些信息?

包名、类名、方法名(参数类型)

7)每部分的要求是怎样的?

包名:有父子特点,要能模糊匹配

类名:要能模糊匹配

方法:要能模糊匹配

参数类型:参数可以有多个

8)这个表达式将被我们用来决定是否需要对某个类的某个方法进行功能增强,这个决定过程应该是怎样的?

匹配类、匹配方法

9)一个表达式不好实现,分成多个表达式进行组合是否更容易些?

可以这么考虑

10)我们掌握的表达式有哪些?

正则表达式

Ant Path表达式

AspectJ的Pointcut表达式——execution(* com.study.design.mode.samples.proxy.Girl.*(…))

总结:正则表达式是可以的,AspectJ本就是切面编程的组件,也是可以的

2.2 AspectJ的Pointcut表达式语法学习

AspectJ是什么?
  AspectJ是java里面切面编程的库,能够帮助我们完成预编译时的代码增强,和eclispe配合使用可以生成相关的字节码。我们在AOP里面只使用了他的表达式解析匹配相关的API

AspectJ的Pointcut表达式是用来表示应该在哪个类的哪个方法进行切入进行方法增强的。

语法如下:

img

示例:

* com.study.design.mode.samples.proxy.Girl.*(…)

2.3 Pointcut设计

下面开始对Pointcut进行接口、类设计了

问题1:切点应有什么属性?

切点定义表达式

问题2:切点应对外提供什么行为(方法)?

问题3:切点将被我们用来做什么?

对类、方法进行匹配

切点应该提供匹配类、匹配方法的行为

问题4:如果在我们设计的框架中要能灵活扩展的切点的实现方式,我们该如何设计?

这又是一个支持可多变的问题,像通知一样,我们来定义一套标准接口,定义好基本行为,面向接口编程,屏蔽掉具体的实现

无论哪种实现,都实现匹配类、匹配方法的接口

Pointcut标准接口的类图:

img

到这里,功能增强(Advice)和Pointcut我们都实现了,下面来看看用户如何使用我们提供的东西了

img

说明:

从上面的Advice和Pointcut的类图我们可以知道,用户要使用我们提供的Advice和Pointcut,只需要实现自己的一个Advice,如MyBeforeAdvice,并把实现的Advice配置成bean,然后传入一个表达式到AspectJExpressionPointcut里面就可以了。

配置的实现的Advice的bean的名字(adviceBeanName)和表达式(expression)组成一个切面

上面的使用还是不太好,这个时候我们需要为上面的使用抽象出一个接口,使得用户的使用更加简单!!!请继续看下面的内容

2.4 Aspect(切面)设计

为用户提供更简单的外观,Advisor(通知者)组合Advice和Pointcut

img

扩展不同的Advisor实现:

img

还可把AspectJPointcutAdvisor和RegExpressionPointcutAdvisor的公共部分提取出来减少冗余代码:

img

四、织入实现

1. Weaving织入分析

织入要完成什么?

将用户提供的增强功能加到指定的方法上。这一部分是我们要实现的

思考以下问题:

问题1:在什么时候做织入?

创建bean实例的时候,在bean初始化完成后,再对其进行增强

问题2:如何确定bean要增强?

对bean类及其方法挨个匹配用户指定的切面,如果有切面匹配就是要增强的

问题3:如何织入

代理

2. Weaving织入设计

整理一下AOP的使用流程,帮助我们更好地去设计织入

img

问题1:用户到哪里去注册切面?

BeanFactory?

问题2:判断匹配、织入的逻辑写在哪里?

写在BeanFactory中?

我们现在是不是要在Bean创建的过程中加入一项处理?后续可能在Bean创建过程中还会加入更多别的处理,如果直接在BeanFactory中实现会有什么不好?

BeanFactory的代码会越来越多

不易扩展

那么该怎么来设计呢?

回顾一下Bean产生的过程中都经历了什么

img

在Bean产生的过程中,会有很多的处理逻辑加入到过程的不同阶段,比如bean初始化前、bean初始化后等等

我们如何来设计能让我们的BeanFactory一次写好后,后面就不改代码,就可以灵活扩展呢?

在各个节点加入扩展点、加入注册机制

什么是扩展点,什么是注册机制?

这里就需要用到前面学习的观察者模式(监听模式)了,BeanFactory就是主题(保证写好一次后就不在改变),6个扩展点就是观察者,主题面向观察者编程,BeanFactory(主题)里面可以添加、删除、通知6个扩展点(观察者)

观察者模式类图:

img

说明:

主题Subject面向观察者接口Observer编程,主题里面可以添加、删除和通知观察者Observer;
注意每个观察者都有一个回调方法update,如果有变化就会在主题的notifyObservers()方法里面调用update方法,把最新的变化给到观察者

变化之处:观察者会变,观察者的数量会变。

不变:主题的代码要不受观察者变化的影响。

观察者模式定义:

定义了对象之间一对多的依赖关系,当一端对象改变状态时,它的所有依赖者都会收到通知并自动更新(被调用更新方法)。也称为:监听模式、发布订阅模式。提供一种对象之间松耦合的设计方式。

使用观察者模式来加入我们的AOP织入

img

3. Weaving织入实现

3.1 判断bean是否需要织入增强

img

问题1:如何判断bean实例是否要增强?

1)通过反射获取bean类及所有方法

java.lang.Class.getMethods() : Method[ ] 获取所有修饰符为public的方法,包括实现的接口和继承的父类里面的所有public修饰的方法

java.lang.Class.getMethod(String, Class<?>…) : Method 获取一个指定的public修饰符修饰的方法,包括实现的接口里面的public修饰的方法

java.lang.Class.getDeclaredMethods() : Method[ ] 获取类里面的所有方法,包括public, protected, default (package) access, and private 这些修饰符修饰的方法,但是不能获取从父类继承来的方法

总结:上面的三种方式都不能保证获取到所有的方法,如果要获取所有的方法就得递归调用,找到所有的类再去获取对应的方法,这一点Spring已经有实现好了的了,我们直接拿来用就行了。

Spring获取类的所有方法的API如下:

//获取类的所有方法,包括继承的父类和实现的接口里面的方法
    private List<Method> getAllMethodForClass(Class<?> beanClass) {
        List<Method> allMethods = new LinkedList<>();
        //获取beanClass的所有接口
        Set<Class<?>> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(beanClass));
        classes.add(beanClass);
        
        //遍历所有的类和接口反射获取到所有的方法
        for (Class<?> clazz : classes) {
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            for (Method m : methods) {
                allMethods.add(m);
            }
        }

        return allMethods;
    }

[复制代码](javascript:void(0)😉

说明:

获取类的所有接口:org.springframework.util.ClassUtils.getAllInterfacesForClassAsSet(Class<?>)

获取类的所有方法:org.springframework.util.ReflectionUtils.getAllDeclaredMethods(Class<?>)

2)遍历Advisor(通知者),取Advisor中的Pointcut(切入点)来匹配类、匹配方法

img

3.2 代理增强

img

问题2:代理增强的逻辑是怎么样的?

img

回忆一下JDK动态代理:

在运行时,对接口创建代理对象

img

生成代理类$Proxy0的方法:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

参数说明:

ClassLoader loader:类加载器

Class<?>[] interfaces:需要被代理的目标对象实现的接口,可以传入多个

InvocationHandler h:功能增强的接口

功能增强的接口:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

参数说明:

Object proxy:被代理的目标对象(接口)

Method method:要调用的目标对象的方法

Object[] args:要调用的目标对象的方法的参数

回忆一下cglib动态代理:

在运行期为类、接口生成动态代理对象。 以达到不改动原类代码而实现功能增强的目的

img

说明:

实现思想和前面的JDK动态代理一样,只是使用了不同的API。

代理类由Enhancer生成,代理类实现被代理的类或者接口,特定的功能增强的实现MyMethodInterceptor实现MethodInterceptor接口,特定的功能增强实现MyMethodInterceptor里面持有被代理的类或者接口target

下面就把生成代理对象的部分和功能增强实现的部分分别实现

无论是JDK动态代理还是cglib动态代理都是生成代理对象,因此可以对这两种代理进行抽象,先看下面的类图

img

要生成代理对象,完成织入增强,JDK动态代理这里需要一些什么数据?

要实现的接口——要实现抽象生成代理对象的接口AopProxy和JDK动态代理生成代理对象的接口InvocationHandler

目标对象——需要增强的Bean

匹配的Advisors——Advice和Pointcut组成的切面去匹配被增强的Bean及Bean里面的方法

BeanFactory ?

要生成代理对象,完成织入增强,cglib动态代理这里需要一些什么数据?

要继承的类 ?

要实现的接口——要实现抽象生成代理对象的接口AopProxy和cglib动态代理生成代理对象的接口MethodInterceptor

目标对象——需要增强的Bean

匹配的Advisors——Advice和Pointcut组成的切面去匹配被增强的Bean及Bean里面的方法

BeanFactory ?

构造参数类型 ?

构造参数 ?

3.3 实现重要的增强逻辑

逻辑如下:

img

增强逻辑代码应该写在JDK动态代理的invoke方法和cglib动态代理的intercept方法里面

img

由于都是增强的代理,逻辑是一样的就提取成一个公共的方法com.study.spring.aop.AopProxyUtils.applyAdvices(Object, Method, Object[], List, Object, BeanFactory)

JdkDynamicAopProxy代码:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return AopProxyUtils.applyAdvices(target, method, args, matchAdvisors, proxy, beanFactory);
    }

CglibDynamicAopProxy代码:

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        return AopProxyUtils.applyAdvices(target, method, args, matchAdvisors, proxy, beanFactory);
    }

3.4 实现代理对象的创建

img

先实现JDK动态代理对象的方式的:

需要的参数:

要实现的接口 ——AopProxy和InvocationHandler

[复制代码](javascript:void(0)😉

    //创建代理对象
    @Override
    public Object getProxy() {
        return this.getProxy(target.getClass().getClassLoader());
    }

    //创建代理对象
    @Override
    public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("为" + target + "创建代理。");
        }
        return Proxy.newProxyInstance(classLoader, target.getClass().getInterfaces(), this);
    }

[复制代码](javascript:void(0)😉

实现cglib动态代理方式的:

需要的参数:

继承的参数

实现的接口

Callback

构造参数类型

构造参数

问题:构造参数类型、构造参数从哪来?

创建对象实例时会有

传递构造参数类型和构造参数:

img

如何来传递创建bean实例时获得的数据到初始化后的AOP中呢?

在BeanDefinition中用ThreadLocal保存创建bean实例时获得的数据(构造参数类型、构造参数)

2.5 AdvisorAutoProxyCreator中怎么使用动态代理AopProxy

img

img

把选择使用哪个动态代理的逻辑交给工厂去判断

img

默认使用cglib

如何判断使用哪一种代理方式?

 	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}
	- 在代理对象不是借口类型或不是代理类时,指定proxyTargetClass=true后,执行CGLIB代理
- 代理对象是接口类型或是代理类,使用JDK代理

opConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}
  • 在代理对象不是借口类型或不是代理类时,指定proxyTargetClass=true后,执行CGLIB代理
  • 代理对象是接口类型或是代理类,使用JDK代理

如何判断是不是代理类?
这个Proxy类里面有一个弱引用的缓存,会记录每次创建了的代理类的引用

转载来源:学spring源码可以跟着这个博主学一下,很不错的
https://www.cnblogs.com/leeSmall/p/10050916.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值